Skip to content

Commit 8879663

Browse files
committed
Add OpenApi + Scalar UI + about/privacy mdx pages + links
1 parent 710e07f commit 8879663

File tree

9 files changed

+217
-12
lines changed

9 files changed

+217
-12
lines changed

MyApp.Client/app/about/page.mdx

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import Layout from "@/components/layout-article"
2+
3+
export const meta = {
4+
title: 'About this .NET 8 React SPA Template'
5+
}
6+
export default ({ children }) => <Layout {...meta}>{children}</Layout>
7+
8+
ServiceStack's new .NET 8 React SPA Template enhances the default ASP.NET Vue App templates with several modern, high-productivity features, including:
9+
10+
- [React](https://react.dev) - Enhance your statically rendered Blazor Apps with Vue.js for all your interactive UIs
11+
- [Tailwind CSS](https://tailwindcss.com) - Style your Blazor Apps with the modern popular utility-first CSS framework for creating beautiful, maintainable responsive UIs with DarkMode support
12+
- [Shadcn UI Components](https://react.servicestack.net) - Rapidly develop beautiful React Apps integrated with Rich high-productivity UI Tailwind Components
13+
- [ASP .NET Identity Auth](https://learn.microsoft.com/en-us/aspnet/core/blazor/security/) - Use the same ASP .NET Identity Auth used in ASP.NET's .NET 8 Blazor Apps, with all Identity Pages upgraded with beautiful Tailwind CSS styling
14+
- [Entity Framework](https://learn.microsoft.com/ef/) & [OrmLite](https://docs.servicestack.net/ormlite/) - Choose the Best ORM to build each App feature, with a unified solution that sees [OrmLite's Code-First DB Migrations](https://docs.servicestack.net/ormlite/db-migrations) run both EF and OrmLite migrations, inc. Seed Data with a single command at Development or Deployment
15+
- [AutoQuery](https://docs.servicestack.net/autoquery/) - Rapidly developing data-driven APIs, UIs and CRUD Apps
16+
- [Markdown](https://docs.servicestack.net/razor-press/syntax) - Maintain SEO-friendly documentation and content-rich pages like this one with just Markdown, beautifully styled with [@tailwindcss/typography](https://tailwindcss.com/docs/typography-plugin)
17+
- [Built-in UIs](https://servicestack.net/auto-ui) - Use ServiceStack's Auto UIs to [Explore your APIs](https://docs.servicestack.net/api-explorer) at **/ui/**
18+
or Query your [App's Database Tables](https://docs.servicestack.net/admin-ui-database) at [/admin-ui/database](/admin-ui/database)
19+
- [Built-in Docker Deployments](/deploy) - Use the built-in GitHub Actions to effortlessly deploy .NET 8 containerized Blazor Apps with Docker and GitHub Registry via SSH to any Linux Server

MyApp.Client/app/privacy/page.mdx

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import Layout from "@/components/layout-article"
2+
3+
export const meta = {
4+
title: 'Privacy Policy'
5+
}
6+
7+
export default ({ children }) => <Layout {...meta}>{children}</Layout>
8+
9+
[Your business name] is committed to providing quality services to you and this policy outlines our ongoing obligations to you in respect of how we manage your Personal Information.
10+
11+
We have adopted the Australian Privacy Principles (APPs) contained in the Privacy Act 1988 (Cth) (the Privacy Act). The NPPs govern the way in which we collect, use, disclose, store, secure and dispose of your Personal Information.
12+
13+
A copy of the Australian Privacy Principles may be obtained from the website of The Office of the Australian Information Commissioner at https://www.oaic.gov.au/.
14+
15+
What is Personal Information and why do we collect it?
16+
Personal Information is information or an opinion that identifies an individual. Examples of Personal Information we collect includes names, addresses, email addresses, phone and facsimile numbers.
17+
18+
This Personal Information is obtained in many ways including [interviews, correspondence, by telephone and facsimile, by email, via our website www.yourbusinessname.com.au, from your website, from media and publications, from other publicly available sources, from cookies- delete all that aren’t applicable] and from third parties. We don’t guarantee website links or policy of authorised third parties.
19+
20+
We collect your Personal Information for the primary purpose of providing our services to you, providing information to our clients and marketing. We may also use your Personal Information for secondary purposes closely related to the primary purpose, in circumstances where you would reasonably expect such use or disclosure. You may unsubscribe from our mailing/marketing lists at any time by contacting us in writing.
21+
22+
When we collect Personal Information we will, where appropriate and where possible, explain to you why we are collecting the information and how we plan to use it.
23+
24+
Sensitive Information
25+
Sensitive information is defined in the Privacy Act to include information or opinion about such things as an individual's racial or ethnic origin, political opinions, membership of a political association, religious or philosophical beliefs, membership of a trade union or other professional body, criminal record or health information.
26+
27+
Sensitive information will be used by us only:
28+
29+
- For the primary purpose for which it was obtained
30+
31+
- For a secondary purpose that is directly related to the primary purpose
32+
33+
- With your consent; or where required or authorised by law.
34+
35+
Third Parties
36+
Where reasonable and practicable to do so, we will collect your Personal Information only from you. However, in some circumstances we may be provided with information by third parties. In such a case we will take reasonable steps to ensure that you are made aware of the information provided to us by the third party.
37+
38+
Disclosure of Personal Information
39+
Your Personal Information may be disclosed in a number of circumstances including the following:
40+
41+
- Third parties where you consent to the use or disclosure; and
42+
43+
- Where required or authorised by law.
44+
45+
Security of Personal Information
46+
Your Personal Information is stored in a manner that reasonably protects it from misuse and loss and from unauthorized access, modification or disclosure.
47+
48+
When your Personal Information is no longer needed for the purpose for which it was obtained, we will take reasonable steps to destroy or permanently de-identify your Personal Information. However, most of the Personal Information is or will be stored in client files which will be kept by us for a minimum of 7 years.
49+
50+
Access to your Personal Information
51+
You may access the Personal Information we hold about you and to update and/or correct it, subject to certain exceptions. If you wish to access your Personal Information, please contact us in writing.
52+
53+
[Your business name] will not charge any fee for your access request, but may charge an administrative fee for providing a copy of your Personal Information.
54+
55+
In order to protect your Personal Information we may require identification from you before releasing the requested information.
56+
57+
Maintaining the Quality of your Personal Information
58+
It is an important to us that your Personal Information is up to date. We will take reasonable steps to make sure that your Personal Information is accurate, complete and up-to-date. If you find that the information we have is not up to date or is inaccurate, please advise us as soon as practicable so we can update our records and ensure we can continue to provide quality services to you.
59+
60+
Policy Updates
61+
This Policy may change from time to time and is available on our website.
62+
63+
Privacy Policy Complaints and Enquiries
64+
If you have any queries or complaints about our Privacy Policy please contact us at:
65+
66+
67+
[Your business address]
68+
69+
[Your business email address]
70+
71+
[Your business phone number]
72+
73+
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
"use client";
2+
3+
import { usePathname } from "next/navigation"
4+
import { TextLink } from "@servicestack/react"
5+
6+
export default () => {
7+
const pathname = usePathname()
8+
const navClass = (path: string) => [
9+
"text-sm leading-6 text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-50",
10+
pathname?.startsWith(path) ? "font-bold" : "",
11+
].join(" ")
12+
13+
return (
14+
<nav className="pt-8 columns-2 sm:flex sm:justify-center sm:space-x-12" aria-label="Footer">
15+
<div className="pb-6">
16+
<TextLink href="/chat" className={navClass("/chat")}>AI Chat</TextLink>
17+
</div>
18+
<div className="pb-6">
19+
<TextLink href="/scalar" className={navClass("/scalar")}>Scalar UI</TextLink>
20+
</div>
21+
<div className="pb-6">
22+
<TextLink href="/ui" className={navClass("/ui")}>API Explorer</TextLink>
23+
</div>
24+
<div className="pb-6">
25+
<TextLink href="/admin-ui" className={navClass("/admin-ui")}>Admin UI</TextLink>
26+
</div>
27+
<div className="pb-6">
28+
<TextLink href="/about" className={navClass("/about")}>About</TextLink>
29+
</div>
30+
<div className="pb-6">
31+
<TextLink href="/posts" className={navClass("/posts")}>Archive</TextLink>
32+
</div>
33+
<div className="pb-6">
34+
<TextLink href="/privacy" className={navClass("/privacy")}>Privacy</TextLink>
35+
</div>
36+
</nav>
37+
)
38+
}

MyApp.Client/components/footer.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
import Container from "./container"
2+
import FeatureLinks from "./feature-links"
23

34
const Footer = () => {
45
return (
56
<footer className="bg-accent-1 dark:bg-gray-800 border-t border-accent-2 dark:border-gray-700">
6-
<Container>
7+
8+
<FeatureLinks />
9+
10+
<Container>
711
<div className="py-28 flex flex-col lg:flex-row items-center">
812
<h3 className="text-4xl lg:text-6xl font-bold tracking-tighter leading-tight text-center lg:text-left mb-10 lg:mb-0 lg:pr-4 lg:w-1/2 text-gray-900 dark:text-gray-100">
913
A ServiceStack Project

MyApp.Client/components/intro.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import { useState } from "react"
44
import { TextInput } from "@servicestack/react"
55
import { GettingStarted, AutoUis } from "react-net-templates"
6+
import FeatureLinks from "./feature-links"
67
import { swrClient } from "@/lib/gateway.client"
78
import { Hello } from "@/lib/dtos"
89
import { CMS_NAME } from "@/lib/constants"
@@ -47,6 +48,7 @@ const Intro = () => {
4748
</div>
4849
<AutoUis className="mt-60" />
4950
</div>
51+
<FeatureLinks />
5052
</>)
5153
}
5254

MyApp.Client/lib/gateway.ts

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,29 @@
1-
import { JsonServiceClient, combinePaths } from "@servicestack/client"
1+
import { appendQueryString, nameOf, IReturn, JsonServiceClient } from "@servicestack/client"
2+
import useSWR from "swr"
23
import { Authenticate } from "@/lib/dtos"
4+
import { useMetadata } from "@servicestack/react"
5+
6+
const serverRoutePaths = [
7+
'/Identity',
8+
'/api',
9+
'/ui',
10+
'/chat',
11+
'/admin-ui',
12+
'/swagger',
13+
'/scalar',
14+
]
15+
16+
export function isServerRoute(path:string) {
17+
return serverRoutePaths.some(x => path.startsWith(x))
18+
}
319

420
export const Routes = {
521
signin: (redirectTo?: string) => redirectTo ? `/signin?redirect=${redirectTo}` : `/signin`,
622
forbidden: () => '/forbidden',
723
}
824

9-
export const BaseUrl = typeof window === 'undefined'
10-
? (process.env.apiBaseUrl || '')
11-
: (process.env.apiBaseUrl || '')
12-
13-
export function apiUrl(path: string) {
14-
const base = typeof window === 'undefined' ? process.env.apiBaseUrl : process.env.apiBaseUrl
15-
return combinePaths(base || '', path)
16-
}
17-
1825
export const client = new JsonServiceClient()
26+
export const metadata = useMetadata(client)
1927

2028
// Load Metadata & Auth State on Startup
2129
// This needs to be called on client side only
@@ -45,3 +53,21 @@ export function getRedirect(searchParams: URLSearchParams | Record<string, strin
4553
? redirect[0]
4654
: redirect
4755
}
56+
57+
// Typed Stale While Revalidate client
58+
class SwrClient {
59+
client: JsonServiceClient
60+
61+
constructor(client: JsonServiceClient) {
62+
this.client = client
63+
}
64+
65+
get<T>(fn: () => IReturn<T> | string) {
66+
return useSWR(() => {
67+
let request = fn()
68+
return appendQueryString(`SwrClient:${nameOf(request)}`, request)
69+
}, _ => this.client.get(fn()))
70+
}
71+
}
72+
73+
export const swrClient = new SwrClient(client)

MyApp.Client/next-env.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/// <reference types="next" />
22
/// <reference types="next/image-types/global" />
3-
import "./dist/dev/types/routes.d.ts";
3+
import "./.next/types/routes.d.ts";
44

55
// NOTE: This file should not be edited
66
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.

MyApp/Configure.OpenApi.cs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
using Scalar.AspNetCore;
2+
using ServiceStack;
3+
4+
[assembly: HostingStartup(typeof(MyApp.ConfigureOpenApi))]
5+
6+
namespace MyApp;
7+
8+
//TODO: Fix build error by adding to <PropertyGroup> https://github.com/dotnet/roslyn/issues/74511
9+
// <InterceptorsNamespaces>$(InterceptorsNamespaces);Microsoft.AspNetCore.OpenApi.Generated</InterceptorsNamespaces>
10+
11+
public class ConfigureOpenApi : IHostingStartup
12+
{
13+
public void Configure(IWebHostBuilder builder) => builder
14+
.ConfigureServices((context, services) => {
15+
if (context.HostingEnvironment.IsDevelopment())
16+
{
17+
services.AddOpenApi();
18+
services.AddServiceStackOpenApi();
19+
// services.AddBasicAuth<Data.ApplicationUser>();
20+
// services.AddApiKeys();
21+
// services.AddJwtAuth();
22+
23+
services.AddTransient<IStartupFilter,StartupFilter>();
24+
}
25+
});
26+
27+
public class StartupFilter : IStartupFilter
28+
{
29+
public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next) => app =>
30+
{
31+
app.UseRouting();
32+
app.UseEndpoints(endpoints =>
33+
{
34+
endpoints.MapOpenApi();
35+
endpoints.MapScalarApiReference();
36+
});
37+
next(app);
38+
};
39+
}
40+
}

MyApp/MyApp.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
<InvariantGlobalization>true</InvariantGlobalization>
88
<RootNamespace>MyApp</RootNamespace>
99
<PublishProfile>DefaultContainer</PublishProfile>
10+
<InterceptorsNamespaces>$(InterceptorsNamespaces);Microsoft.AspNetCore.OpenApi.Generated</InterceptorsNamespaces>
1011
</PropertyGroup>
1112

1213
<ItemGroup>
@@ -23,8 +24,10 @@
2324
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="10.*" />
2425
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.*" />
2526
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="10.*" />
27+
<PackageReference Include="Scalar.AspNetCore" Version="2.11.0" />
2628
<PackageReference Include="ServiceStack" Version="8.*" />
2729
<PackageReference Include="ServiceStack.Mvc" Version="8.*" />
30+
<PackageReference Include="ServiceStack.OpenApi.Microsoft" Version="8.10.1" />
2831
<PackageReference Include="ServiceStack.Server" Version="8.*" />
2932
<PackageReference Include="ServiceStack.Extensions" Version="8.*" />
3033
<PackageReference Include="ServiceStack.Ormlite.Sqlite.Data" Version="8.*" />

0 commit comments

Comments
 (0)