create-ab-app 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +60 -0
- package/index.js +380 -0
- package/package.json +36 -0
- package/template/.env.example +8 -0
- package/template/.oxlintrc.json +8 -0
- package/template/README.md +196 -0
- package/template/_gitignore +24 -0
- package/template/components.json +25 -0
- package/template/index.html +13 -0
- package/template/package-lock.json +6096 -0
- package/template/package.json +40 -0
- package/template/public/_redirects +1 -0
- package/template/public/favicon.svg +1 -0
- package/template/public/icons.svg +24 -0
- package/template/src/App.tsx +60 -0
- package/template/src/assets/hero.png +0 -0
- package/template/src/assets/vite.svg +1 -0
- package/template/src/components/app-sidebar.tsx +53 -0
- package/template/src/components/credit-balance.tsx +31 -0
- package/template/src/components/docs/doc-primitives.tsx +125 -0
- package/template/src/components/full-page-loader.tsx +14 -0
- package/template/src/components/header-user.tsx +64 -0
- package/template/src/components/layouts/app-layout.tsx +80 -0
- package/template/src/components/missing-env.tsx +24 -0
- package/template/src/components/nav-main.tsx +87 -0
- package/template/src/components/nav-user.tsx +69 -0
- package/template/src/components/theme-toggle.tsx +39 -0
- package/template/src/components/ui/avatar.tsx +106 -0
- package/template/src/components/ui/breadcrumb.tsx +124 -0
- package/template/src/components/ui/button.tsx +57 -0
- package/template/src/components/ui/card.tsx +102 -0
- package/template/src/components/ui/collapsible.tsx +19 -0
- package/template/src/components/ui/dropdown-menu.tsx +267 -0
- package/template/src/components/ui/field.tsx +238 -0
- package/template/src/components/ui/input.tsx +19 -0
- package/template/src/components/ui/label.tsx +19 -0
- package/template/src/components/ui/separator.tsx +22 -0
- package/template/src/components/ui/sheet.tsx +136 -0
- package/template/src/components/ui/sidebar.tsx +721 -0
- package/template/src/components/ui/skeleton.tsx +13 -0
- package/template/src/components/ui/sonner.tsx +47 -0
- package/template/src/components/ui/tooltip.tsx +65 -0
- package/template/src/components/user-menu.tsx +96 -0
- package/template/src/config/env.ts +14 -0
- package/template/src/config/navigation.ts +52 -0
- package/template/src/hooks/use-mobile.ts +19 -0
- package/template/src/hooks/use-user-identity.ts +27 -0
- package/template/src/index.css +134 -0
- package/template/src/lib/http.ts +40 -0
- package/template/src/lib/sso/account-settings.tsx +53 -0
- package/template/src/lib/sso/auth-context.ts +16 -0
- package/template/src/lib/sso/auth-provider.tsx +42 -0
- package/template/src/lib/sso/auth-screens.tsx +152 -0
- package/template/src/lib/sso/config.ts +13 -0
- package/template/src/lib/sso/entitlements.ts +101 -0
- package/template/src/lib/sso/index.ts +33 -0
- package/template/src/lib/sso/protected-route.tsx +72 -0
- package/template/src/lib/sso/queries.ts +55 -0
- package/template/src/lib/sso/require-plan.tsx +19 -0
- package/template/src/lib/sso/token-store.ts +62 -0
- package/template/src/lib/utils.ts +1 -0
- package/template/src/main.tsx +42 -0
- package/template/src/routes/app/example.tsx +125 -0
- package/template/src/routes/auth/forgot-password.tsx +5 -0
- package/template/src/routes/auth/login.tsx +5 -0
- package/template/src/routes/auth/signup.tsx +5 -0
- package/template/src/routes/auth/verify-email.tsx +5 -0
- package/template/src/routes/docs/api.tsx +66 -0
- package/template/src/routes/docs/auth.tsx +145 -0
- package/template/src/routes/docs/billing.tsx +103 -0
- package/template/src/routes/docs/configuration.tsx +113 -0
- package/template/src/routes/docs/overview.tsx +97 -0
- package/template/src/routes/docs/routing.tsx +126 -0
- package/template/src/routes/docs/structure.tsx +150 -0
- package/template/src/routes/docs/theming.tsx +88 -0
- package/template/src/routes/not-found.tsx +12 -0
- package/template/src/styles/aas-theme.css +21 -0
- package/template/src/vite-env.d.ts +12 -0
- package/template/tsconfig.app.json +30 -0
- package/template/tsconfig.json +12 -0
- package/template/tsconfig.node.json +23 -0
- package/template/vercel.json +3 -0
- package/template/vite.config.ts +17 -0
- package/variants/minimal/src/App.tsx +43 -0
- package/variants/minimal/src/components/app-sidebar.tsx +50 -0
- package/variants/minimal/src/config/navigation.ts +19 -0
- package/variants/minimal/src/routes/app/home.tsx +45 -0
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Cell,
|
|
3
|
+
Code,
|
|
4
|
+
CodeBlock,
|
|
5
|
+
DocPage,
|
|
6
|
+
Note,
|
|
7
|
+
P,
|
|
8
|
+
Row,
|
|
9
|
+
Section,
|
|
10
|
+
Table,
|
|
11
|
+
} from "@/components/docs/doc-primitives"
|
|
12
|
+
|
|
13
|
+
export default function StructurePage() {
|
|
14
|
+
return (
|
|
15
|
+
<DocPage
|
|
16
|
+
title="Project structure"
|
|
17
|
+
summary="Where things live and which folder owns what."
|
|
18
|
+
>
|
|
19
|
+
<Section>
|
|
20
|
+
<CodeBlock>{`src/
|
|
21
|
+
config/
|
|
22
|
+
env.ts Every environment value, plus the preflight check
|
|
23
|
+
navigation.ts The sidebar, as data
|
|
24
|
+
lib/
|
|
25
|
+
sso/ Auth + billing. The only folder that imports the SDK
|
|
26
|
+
http.ts Client for your own API (bearer + 401 handling)
|
|
27
|
+
utils.ts cn()
|
|
28
|
+
components/
|
|
29
|
+
ui/ shadcn primitives
|
|
30
|
+
docs/ Building blocks for these documentation pages
|
|
31
|
+
layouts/
|
|
32
|
+
app-layout.tsx Sidebar shell for authenticated routes
|
|
33
|
+
app-sidebar.tsx Sidebar composition
|
|
34
|
+
nav-main.tsx Nav group, entitlement-aware
|
|
35
|
+
nav-user.tsx Sidebar user menu
|
|
36
|
+
header-user.tsx Header user menu (same items)
|
|
37
|
+
user-menu.tsx Shared menu pieces
|
|
38
|
+
theme-toggle.tsx Light / dark / system
|
|
39
|
+
hooks/
|
|
40
|
+
use-user-identity.ts
|
|
41
|
+
routes/
|
|
42
|
+
docs/ These pages
|
|
43
|
+
app/example.tsx Live data example
|
|
44
|
+
auth/ One-line wrappers over the SDK screens
|
|
45
|
+
styles/
|
|
46
|
+
aas-theme.css Maps SDK tokens onto shadcn tokens
|
|
47
|
+
App.tsx Route map
|
|
48
|
+
main.tsx Providers`}</CodeBlock>
|
|
49
|
+
</Section>
|
|
50
|
+
|
|
51
|
+
<Section title="Inside src/lib/sso">
|
|
52
|
+
<P>
|
|
53
|
+
This is the important folder. Everything the SDK needs is bound here so
|
|
54
|
+
the rest of the app stays plain React.
|
|
55
|
+
</P>
|
|
56
|
+
<Table head={["File", "Responsibility"]}>
|
|
57
|
+
<Row>
|
|
58
|
+
<Cell>
|
|
59
|
+
<Code>config.ts</Code>
|
|
60
|
+
</Cell>
|
|
61
|
+
<Cell>
|
|
62
|
+
The three SDK inputs, bound once. Spread <Code>ssoProps</Code> into any
|
|
63
|
+
SDK component.
|
|
64
|
+
</Cell>
|
|
65
|
+
</Row>
|
|
66
|
+
<Row>
|
|
67
|
+
<Cell>
|
|
68
|
+
<Code>token-store.ts</Code>
|
|
69
|
+
</Cell>
|
|
70
|
+
<Cell>
|
|
71
|
+
The only place that touches token storage. Cross-tab aware; falls back
|
|
72
|
+
to memory when storage is blocked.
|
|
73
|
+
</Cell>
|
|
74
|
+
</Row>
|
|
75
|
+
<Row>
|
|
76
|
+
<Cell>
|
|
77
|
+
<Code>auth-provider.tsx</Code>
|
|
78
|
+
</Cell>
|
|
79
|
+
<Cell>
|
|
80
|
+
Owns the token app-wide. Pairs with <Code>auth-context.ts</Code>, which
|
|
81
|
+
exports <Code>useAuth()</Code>.
|
|
82
|
+
</Cell>
|
|
83
|
+
</Row>
|
|
84
|
+
<Row>
|
|
85
|
+
<Cell>
|
|
86
|
+
<Code>auth-screens.tsx</Code>
|
|
87
|
+
</Cell>
|
|
88
|
+
<Cell>
|
|
89
|
+
The SDK's Login / Signup / ForgotPassword / VerifyEmail, bound to
|
|
90
|
+
config, token store and router.
|
|
91
|
+
</Cell>
|
|
92
|
+
</Row>
|
|
93
|
+
<Row>
|
|
94
|
+
<Cell>
|
|
95
|
+
<Code>protected-route.tsx</Code>
|
|
96
|
+
</Cell>
|
|
97
|
+
<Cell>
|
|
98
|
+
<Code>ProtectedRoute</Code> and <Code>PublicOnlyRoute</Code>.
|
|
99
|
+
</Cell>
|
|
100
|
+
</Row>
|
|
101
|
+
<Row>
|
|
102
|
+
<Cell>
|
|
103
|
+
<Code>queries.ts</Code>
|
|
104
|
+
</Cell>
|
|
105
|
+
<Cell>
|
|
106
|
+
Cached, pre-bound useProfile / useWallet / useCreditWallets /
|
|
107
|
+
useRawSubscription.
|
|
108
|
+
</Cell>
|
|
109
|
+
</Row>
|
|
110
|
+
<Row>
|
|
111
|
+
<Cell>
|
|
112
|
+
<Code>entitlements.ts</Code>
|
|
113
|
+
</Cell>
|
|
114
|
+
<Cell>
|
|
115
|
+
Normalises the raw /subscription payload into something checkable.
|
|
116
|
+
</Cell>
|
|
117
|
+
</Row>
|
|
118
|
+
<Row>
|
|
119
|
+
<Cell>
|
|
120
|
+
<Code>require-plan.tsx</Code>
|
|
121
|
+
</Cell>
|
|
122
|
+
<Cell>Per-feature gate component.</Cell>
|
|
123
|
+
</Row>
|
|
124
|
+
<Row>
|
|
125
|
+
<Cell>
|
|
126
|
+
<Code>account-settings.tsx</Code>
|
|
127
|
+
</Cell>
|
|
128
|
+
<Cell>
|
|
129
|
+
Opens the SDK's Account Settings modal — the billing surface.
|
|
130
|
+
</Cell>
|
|
131
|
+
</Row>
|
|
132
|
+
<Row>
|
|
133
|
+
<Cell>
|
|
134
|
+
<Code>index.ts</Code>
|
|
135
|
+
</Cell>
|
|
136
|
+
<Cell>The barrel. This is what the rest of the app imports.</Cell>
|
|
137
|
+
</Row>
|
|
138
|
+
</Table>
|
|
139
|
+
</Section>
|
|
140
|
+
|
|
141
|
+
<Section title="Why the barrel matters">
|
|
142
|
+
<Note>
|
|
143
|
+
Product code imports <Code>@/lib/sso</Code>. If you ever swap the auth
|
|
144
|
+
provider, or the SDK changes its signatures, the blast radius is this one
|
|
145
|
+
folder instead of every component that happened to call it.
|
|
146
|
+
</Note>
|
|
147
|
+
</Section>
|
|
148
|
+
</DocPage>
|
|
149
|
+
)
|
|
150
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Code,
|
|
3
|
+
CodeBlock,
|
|
4
|
+
DocPage,
|
|
5
|
+
Note,
|
|
6
|
+
P,
|
|
7
|
+
Section,
|
|
8
|
+
} from "@/components/docs/doc-primitives"
|
|
9
|
+
|
|
10
|
+
export default function ThemingPage() {
|
|
11
|
+
return (
|
|
12
|
+
<DocPage
|
|
13
|
+
title="UI & theming"
|
|
14
|
+
summary="shadcn/ui, the SDK's own stylesheet, and how the two are kept in sync."
|
|
15
|
+
>
|
|
16
|
+
<Section title="Components">
|
|
17
|
+
<P>
|
|
18
|
+
shadcn/ui with the <Code>sidebar-07</Code> block as the app shell. Blocks
|
|
19
|
+
are installed once and then owned by you —{" "}
|
|
20
|
+
<Code>npx shadcn@latest add sidebar-07</Code> overwrites{" "}
|
|
21
|
+
<Code>app-sidebar.tsx</Code> and friends, so never re-run it against
|
|
22
|
+
customised files.
|
|
23
|
+
</P>
|
|
24
|
+
<P>
|
|
25
|
+
The block's team switcher was dropped (one project per app) and its nav
|
|
26
|
+
data replaced by <Code>src/config/navigation.ts</Code>. The user menu is
|
|
27
|
+
shared between the sidebar footer and the header via{" "}
|
|
28
|
+
<Code>user-menu.tsx</Code>, so both stay in sync.
|
|
29
|
+
</P>
|
|
30
|
+
</Section>
|
|
31
|
+
|
|
32
|
+
<Section title="Two stylesheets, one theme">
|
|
33
|
+
<P>
|
|
34
|
+
The SDK's CSS is scoped (<Code>[data-aas]</Code>, an <Code>sso:</Code>{" "}
|
|
35
|
+
utility prefix) and shipped <em>unlayered</em>. That means it cannot leak
|
|
36
|
+
into your app and your Tailwind preflight cannot override it — but it also
|
|
37
|
+
means it will not inherit your theme. So the tokens are mapped explicitly:
|
|
38
|
+
</P>
|
|
39
|
+
<CodeBlock>{`/* src/styles/aas-theme.css */
|
|
40
|
+
:root,
|
|
41
|
+
.dark {
|
|
42
|
+
--aas-primary: var(--primary);
|
|
43
|
+
--aas-background: var(--background);
|
|
44
|
+
--aas-border: var(--border);
|
|
45
|
+
--aas-radius: var(--radius);
|
|
46
|
+
/* … */
|
|
47
|
+
}`}</CodeBlock>
|
|
48
|
+
<Note tone="warn" title="Import order matters">
|
|
49
|
+
Tailwind first, then <Code>ab-ecosystem-sso/styles.css</Code>, then the
|
|
50
|
+
bridge. Both selectors are declared so the mapping resolves against
|
|
51
|
+
whichever element carries the active palette.
|
|
52
|
+
</Note>
|
|
53
|
+
</Section>
|
|
54
|
+
|
|
55
|
+
<Section title="Overriding SDK styles">
|
|
56
|
+
<P>
|
|
57
|
+
Because the SDK's CSS is unlayered, a plain Tailwind utility of equal
|
|
58
|
+
specificity loses to it. Use the important modifier:
|
|
59
|
+
</P>
|
|
60
|
+
<CodeBlock>{`// src/lib/sso/auth-screens.tsx
|
|
61
|
+
const authClassNames = { card: "max-w-sm!" } // narrows the SDK's max-w-md`}</CodeBlock>
|
|
62
|
+
<P>
|
|
63
|
+
Each screen also accepts <Code>root</Code>, <Code>button</Code>,{" "}
|
|
64
|
+
<Code>input</Code>, <Code>label</Code>, <Code>link</Code>,{" "}
|
|
65
|
+
<Code>logo</Code>, <Code>error</Code> and more.
|
|
66
|
+
</P>
|
|
67
|
+
</Section>
|
|
68
|
+
|
|
69
|
+
<Section title="Dark mode">
|
|
70
|
+
<P>
|
|
71
|
+
<Code>next-themes</Code> with <Code>attribute="class"</Code> toggles{" "}
|
|
72
|
+
<Code>.dark</Code> on the root element, which is what both the shadcn
|
|
73
|
+
tokens and the bridge above key off. The header toggle offers light, dark
|
|
74
|
+
and system. Because the bridge is theme-aware, the SDK's auth screens and
|
|
75
|
+
Account Settings modal follow along.
|
|
76
|
+
</P>
|
|
77
|
+
</Section>
|
|
78
|
+
|
|
79
|
+
<Section title="Notifications">
|
|
80
|
+
<P>
|
|
81
|
+
Sonner, mounted in <Code>main.tsx</Code>. Auth toasts all fire from{" "}
|
|
82
|
+
<Code>auth-screens.tsx</Code> under a single toast id, so a retry replaces
|
|
83
|
+
the previous notification instead of stacking a second one.
|
|
84
|
+
</P>
|
|
85
|
+
</Section>
|
|
86
|
+
</DocPage>
|
|
87
|
+
)
|
|
88
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Link } from "react-router-dom"
|
|
2
|
+
|
|
3
|
+
import { Button } from "@/components/ui/button"
|
|
4
|
+
|
|
5
|
+
export default function NotFoundPage() {
|
|
6
|
+
return (
|
|
7
|
+
<div className="flex min-h-svh flex-col items-center justify-center gap-4">
|
|
8
|
+
<p className="text-muted-foreground text-sm">404 — page not found</p>
|
|
9
|
+
<Button render={<Link to="/" />}>Back to dashboard</Button>
|
|
10
|
+
</div>
|
|
11
|
+
)
|
|
12
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Maps the SDK's --aas-* variables onto the shadcn tokens so its screens and
|
|
3
|
+
* modal match the app. Declared on both selectors so the mapping resolves
|
|
4
|
+
* against whichever element carries the active palette.
|
|
5
|
+
*/
|
|
6
|
+
:root,
|
|
7
|
+
.dark {
|
|
8
|
+
--aas-primary: var(--primary);
|
|
9
|
+
--aas-primary-foreground: var(--primary-foreground);
|
|
10
|
+
--aas-background: var(--background);
|
|
11
|
+
--aas-foreground: var(--foreground);
|
|
12
|
+
--aas-card: var(--card);
|
|
13
|
+
--aas-card-foreground: var(--card-foreground);
|
|
14
|
+
--aas-muted: var(--muted);
|
|
15
|
+
--aas-muted-foreground: var(--muted-foreground);
|
|
16
|
+
--aas-border: var(--border);
|
|
17
|
+
--aas-input: var(--input);
|
|
18
|
+
--aas-ring: var(--ring);
|
|
19
|
+
--aas-destructive: var(--destructive);
|
|
20
|
+
--aas-radius: var(--radius);
|
|
21
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/// <reference types="vite/client" />
|
|
2
|
+
|
|
3
|
+
interface ImportMetaEnv {
|
|
4
|
+
readonly VITE_AUTH_PROJECT_ID: string
|
|
5
|
+
readonly VITE_AUTH_API_BASE_URL?: string
|
|
6
|
+
readonly VITE_API_BASE_URL?: string
|
|
7
|
+
readonly VITE_APP_NAME?: string
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface ImportMeta {
|
|
11
|
+
readonly env: ImportMetaEnv
|
|
12
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
|
4
|
+
"target": "es2023",
|
|
5
|
+
"lib": ["ES2023", "DOM"],
|
|
6
|
+
"module": "esnext",
|
|
7
|
+
"types": ["vite/client"],
|
|
8
|
+
"allowArbitraryExtensions": true,
|
|
9
|
+
"skipLibCheck": true,
|
|
10
|
+
|
|
11
|
+
/* Bundler mode */
|
|
12
|
+
"moduleResolution": "bundler",
|
|
13
|
+
"allowImportingTsExtensions": true,
|
|
14
|
+
"verbatimModuleSyntax": true,
|
|
15
|
+
"moduleDetection": "force",
|
|
16
|
+
"noEmit": true,
|
|
17
|
+
"jsx": "react-jsx",
|
|
18
|
+
"strict": true,
|
|
19
|
+
|
|
20
|
+
/* Paths */
|
|
21
|
+
"paths": { "@/*": ["./src/*"] },
|
|
22
|
+
|
|
23
|
+
/* Linting */
|
|
24
|
+
"noUnusedLocals": true,
|
|
25
|
+
"noUnusedParameters": true,
|
|
26
|
+
"erasableSyntaxOnly": true,
|
|
27
|
+
"noFallthroughCasesInSwitch": true
|
|
28
|
+
},
|
|
29
|
+
"include": ["src"]
|
|
30
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
|
4
|
+
"target": "es2023",
|
|
5
|
+
"lib": ["ES2023"],
|
|
6
|
+
"types": ["node"],
|
|
7
|
+
"skipLibCheck": true,
|
|
8
|
+
|
|
9
|
+
/* Bundler mode */
|
|
10
|
+
"module": "nodenext",
|
|
11
|
+
"allowImportingTsExtensions": true,
|
|
12
|
+
"verbatimModuleSyntax": true,
|
|
13
|
+
"moduleDetection": "force",
|
|
14
|
+
"noEmit": true,
|
|
15
|
+
|
|
16
|
+
/* Linting */
|
|
17
|
+
"noUnusedLocals": true,
|
|
18
|
+
"noUnusedParameters": true,
|
|
19
|
+
"erasableSyntaxOnly": true,
|
|
20
|
+
"noFallthroughCasesInSwitch": true
|
|
21
|
+
},
|
|
22
|
+
"include": ["vite.config.ts"]
|
|
23
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import path from "node:path"
|
|
2
|
+
import tailwindcss from "@tailwindcss/vite"
|
|
3
|
+
import react from "@vitejs/plugin-react"
|
|
4
|
+
import { defineConfig } from "vite"
|
|
5
|
+
|
|
6
|
+
// https://vite.dev/config/
|
|
7
|
+
export default defineConfig({
|
|
8
|
+
plugins: [react(), tailwindcss()],
|
|
9
|
+
resolve: {
|
|
10
|
+
alias: {
|
|
11
|
+
"@": path.resolve(import.meta.dirname, "./src"),
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
server: {
|
|
15
|
+
port: 5173,
|
|
16
|
+
},
|
|
17
|
+
})
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Navigate, Route, Routes } from "react-router-dom"
|
|
2
|
+
|
|
3
|
+
import { AppLayout } from "@/components/layouts/app-layout"
|
|
4
|
+
import { ProtectedRoute, PublicOnlyRoute } from "@/lib/sso"
|
|
5
|
+
import ForgotPasswordPage from "@/routes/auth/forgot-password"
|
|
6
|
+
import LoginPage from "@/routes/auth/login"
|
|
7
|
+
import SignupPage from "@/routes/auth/signup"
|
|
8
|
+
import VerifyEmailPage from "@/routes/auth/verify-email"
|
|
9
|
+
import HomePage from "@/routes/app/home"
|
|
10
|
+
import NotFoundPage from "@/routes/not-found"
|
|
11
|
+
|
|
12
|
+
export default function App() {
|
|
13
|
+
return (
|
|
14
|
+
<Routes>
|
|
15
|
+
{/* Auth — no layout wrapper: each SDK screen renders its own centered page. */}
|
|
16
|
+
<Route element={<PublicOnlyRoute />}>
|
|
17
|
+
<Route path="/login" element={<LoginPage />} />
|
|
18
|
+
<Route path="/signup" element={<SignupPage />} />
|
|
19
|
+
<Route path="/forgot-password" element={<ForgotPasswordPage />} />
|
|
20
|
+
</Route>
|
|
21
|
+
|
|
22
|
+
{/*
|
|
23
|
+
Emailed links. Not behind PublicOnlyRoute: a user who just signed up is
|
|
24
|
+
already signed in, and must still be able to verify or reset.
|
|
25
|
+
*/}
|
|
26
|
+
<Route path="/reset-password/:token" element={<ForgotPasswordPage />} />
|
|
27
|
+
<Route path="/verify/:token" element={<VerifyEmailPage />} />
|
|
28
|
+
{/* Aliases for links that carry the token as ?token= instead. */}
|
|
29
|
+
<Route path="/reset-password" element={<ForgotPasswordPage />} />
|
|
30
|
+
<Route path="/verify-email" element={<VerifyEmailPage />} />
|
|
31
|
+
|
|
32
|
+
{/* Add enforceActiveSubscription for a hard paywall on the whole shell. */}
|
|
33
|
+
<Route element={<ProtectedRoute />}>
|
|
34
|
+
<Route element={<AppLayout />}>
|
|
35
|
+
<Route path="/" element={<HomePage />} />
|
|
36
|
+
</Route>
|
|
37
|
+
</Route>
|
|
38
|
+
|
|
39
|
+
<Route path="/404" element={<NotFoundPage />} />
|
|
40
|
+
<Route path="*" element={<Navigate to="/404" replace />} />
|
|
41
|
+
</Routes>
|
|
42
|
+
)
|
|
43
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { Link } from "react-router-dom"
|
|
3
|
+
import { GalleryVerticalEndIcon } from "lucide-react"
|
|
4
|
+
|
|
5
|
+
import { NavMain } from "@/components/nav-main"
|
|
6
|
+
import { NavUser } from "@/components/nav-user"
|
|
7
|
+
import { CreditBalance } from "@/components/credit-balance"
|
|
8
|
+
import {
|
|
9
|
+
Sidebar,
|
|
10
|
+
SidebarContent,
|
|
11
|
+
SidebarFooter,
|
|
12
|
+
SidebarHeader,
|
|
13
|
+
SidebarMenu,
|
|
14
|
+
SidebarMenuButton,
|
|
15
|
+
SidebarMenuItem,
|
|
16
|
+
SidebarRail,
|
|
17
|
+
} from "@/components/ui/sidebar"
|
|
18
|
+
import { env } from "@/config/env"
|
|
19
|
+
import { navMain } from "@/config/navigation"
|
|
20
|
+
|
|
21
|
+
export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
|
|
22
|
+
return (
|
|
23
|
+
<Sidebar collapsible="icon" {...props}>
|
|
24
|
+
<SidebarHeader>
|
|
25
|
+
<SidebarMenu>
|
|
26
|
+
<SidebarMenuItem>
|
|
27
|
+
<SidebarMenuButton size="lg" render={<Link to="/" />}>
|
|
28
|
+
<div className="bg-primary text-primary-foreground flex aspect-square size-8 items-center justify-center rounded-lg">
|
|
29
|
+
<GalleryVerticalEndIcon className="size-4" />
|
|
30
|
+
</div>
|
|
31
|
+
<div className="grid flex-1 text-left text-sm leading-tight">
|
|
32
|
+
<span className="truncate font-medium">{env.appName}</span>
|
|
33
|
+
</div>
|
|
34
|
+
</SidebarMenuButton>
|
|
35
|
+
</SidebarMenuItem>
|
|
36
|
+
</SidebarMenu>
|
|
37
|
+
</SidebarHeader>
|
|
38
|
+
|
|
39
|
+
<SidebarContent>
|
|
40
|
+
<NavMain items={navMain} />
|
|
41
|
+
</SidebarContent>
|
|
42
|
+
|
|
43
|
+
<SidebarFooter>
|
|
44
|
+
<CreditBalance />
|
|
45
|
+
<NavUser />
|
|
46
|
+
</SidebarFooter>
|
|
47
|
+
<SidebarRail />
|
|
48
|
+
</Sidebar>
|
|
49
|
+
)
|
|
50
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { HomeIcon } from "lucide-react"
|
|
2
|
+
|
|
3
|
+
/** Anything that renders like a Lucide icon. */
|
|
4
|
+
export type IconComponent = React.ComponentType<{ className?: string }>
|
|
5
|
+
|
|
6
|
+
export type NavItem = {
|
|
7
|
+
title: string
|
|
8
|
+
url: string
|
|
9
|
+
icon?: IconComponent
|
|
10
|
+
/** Item ids that unlock this entry. Omit to show it to every signed-in user. */
|
|
11
|
+
requiresIds?: string[]
|
|
12
|
+
items?: { title: string; url: string; requiresIds?: string[] }[]
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** The sidebar, as data. */
|
|
16
|
+
export const navMain: NavItem[] = [{ title: "Home", url: "/", icon: HomeIcon }]
|
|
17
|
+
|
|
18
|
+
/** Every nav entry, flattened — used for breadcrumb titles. */
|
|
19
|
+
export const allNavItems: NavItem[] = [...navMain]
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Button } from "@/components/ui/button"
|
|
2
|
+
import {
|
|
3
|
+
Card,
|
|
4
|
+
CardContent,
|
|
5
|
+
CardDescription,
|
|
6
|
+
CardHeader,
|
|
7
|
+
CardTitle,
|
|
8
|
+
} from "@/components/ui/card"
|
|
9
|
+
import { useAccountSettings, useEntitlements, useProfile } from "@/lib/sso"
|
|
10
|
+
|
|
11
|
+
export default function HomePage() {
|
|
12
|
+
const { data: profile } = useProfile()
|
|
13
|
+
const { isActive, planId } = useEntitlements()
|
|
14
|
+
const { open } = useAccountSettings()
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<div className="mx-auto flex w-full max-w-3xl flex-col gap-6">
|
|
18
|
+
<div>
|
|
19
|
+
<h1 className="text-2xl font-semibold tracking-tight">
|
|
20
|
+
Welcome{profile?.firstName ? `, ${profile.firstName}` : ""}
|
|
21
|
+
</h1>
|
|
22
|
+
<p className="text-muted-foreground text-sm">
|
|
23
|
+
Start building. Auth and billing live in <code>src/lib/sso</code>.
|
|
24
|
+
</p>
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
<Card>
|
|
28
|
+
<CardHeader>
|
|
29
|
+
<CardTitle>Subscription</CardTitle>
|
|
30
|
+
<CardDescription>
|
|
31
|
+
{isActive ? `Active — ${planId}` : "No active plan"}
|
|
32
|
+
</CardDescription>
|
|
33
|
+
</CardHeader>
|
|
34
|
+
<CardContent>
|
|
35
|
+
<Button
|
|
36
|
+
variant={isActive ? "outline" : "default"}
|
|
37
|
+
onClick={() => open("plans")}
|
|
38
|
+
>
|
|
39
|
+
{isActive ? "Manage plan" : "Choose a plan"}
|
|
40
|
+
</Button>
|
|
41
|
+
</CardContent>
|
|
42
|
+
</Card>
|
|
43
|
+
</div>
|
|
44
|
+
)
|
|
45
|
+
}
|