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.
Files changed (87) hide show
  1. package/README.md +60 -0
  2. package/index.js +380 -0
  3. package/package.json +36 -0
  4. package/template/.env.example +8 -0
  5. package/template/.oxlintrc.json +8 -0
  6. package/template/README.md +196 -0
  7. package/template/_gitignore +24 -0
  8. package/template/components.json +25 -0
  9. package/template/index.html +13 -0
  10. package/template/package-lock.json +6096 -0
  11. package/template/package.json +40 -0
  12. package/template/public/_redirects +1 -0
  13. package/template/public/favicon.svg +1 -0
  14. package/template/public/icons.svg +24 -0
  15. package/template/src/App.tsx +60 -0
  16. package/template/src/assets/hero.png +0 -0
  17. package/template/src/assets/vite.svg +1 -0
  18. package/template/src/components/app-sidebar.tsx +53 -0
  19. package/template/src/components/credit-balance.tsx +31 -0
  20. package/template/src/components/docs/doc-primitives.tsx +125 -0
  21. package/template/src/components/full-page-loader.tsx +14 -0
  22. package/template/src/components/header-user.tsx +64 -0
  23. package/template/src/components/layouts/app-layout.tsx +80 -0
  24. package/template/src/components/missing-env.tsx +24 -0
  25. package/template/src/components/nav-main.tsx +87 -0
  26. package/template/src/components/nav-user.tsx +69 -0
  27. package/template/src/components/theme-toggle.tsx +39 -0
  28. package/template/src/components/ui/avatar.tsx +106 -0
  29. package/template/src/components/ui/breadcrumb.tsx +124 -0
  30. package/template/src/components/ui/button.tsx +57 -0
  31. package/template/src/components/ui/card.tsx +102 -0
  32. package/template/src/components/ui/collapsible.tsx +19 -0
  33. package/template/src/components/ui/dropdown-menu.tsx +267 -0
  34. package/template/src/components/ui/field.tsx +238 -0
  35. package/template/src/components/ui/input.tsx +19 -0
  36. package/template/src/components/ui/label.tsx +19 -0
  37. package/template/src/components/ui/separator.tsx +22 -0
  38. package/template/src/components/ui/sheet.tsx +136 -0
  39. package/template/src/components/ui/sidebar.tsx +721 -0
  40. package/template/src/components/ui/skeleton.tsx +13 -0
  41. package/template/src/components/ui/sonner.tsx +47 -0
  42. package/template/src/components/ui/tooltip.tsx +65 -0
  43. package/template/src/components/user-menu.tsx +96 -0
  44. package/template/src/config/env.ts +14 -0
  45. package/template/src/config/navigation.ts +52 -0
  46. package/template/src/hooks/use-mobile.ts +19 -0
  47. package/template/src/hooks/use-user-identity.ts +27 -0
  48. package/template/src/index.css +134 -0
  49. package/template/src/lib/http.ts +40 -0
  50. package/template/src/lib/sso/account-settings.tsx +53 -0
  51. package/template/src/lib/sso/auth-context.ts +16 -0
  52. package/template/src/lib/sso/auth-provider.tsx +42 -0
  53. package/template/src/lib/sso/auth-screens.tsx +152 -0
  54. package/template/src/lib/sso/config.ts +13 -0
  55. package/template/src/lib/sso/entitlements.ts +101 -0
  56. package/template/src/lib/sso/index.ts +33 -0
  57. package/template/src/lib/sso/protected-route.tsx +72 -0
  58. package/template/src/lib/sso/queries.ts +55 -0
  59. package/template/src/lib/sso/require-plan.tsx +19 -0
  60. package/template/src/lib/sso/token-store.ts +62 -0
  61. package/template/src/lib/utils.ts +1 -0
  62. package/template/src/main.tsx +42 -0
  63. package/template/src/routes/app/example.tsx +125 -0
  64. package/template/src/routes/auth/forgot-password.tsx +5 -0
  65. package/template/src/routes/auth/login.tsx +5 -0
  66. package/template/src/routes/auth/signup.tsx +5 -0
  67. package/template/src/routes/auth/verify-email.tsx +5 -0
  68. package/template/src/routes/docs/api.tsx +66 -0
  69. package/template/src/routes/docs/auth.tsx +145 -0
  70. package/template/src/routes/docs/billing.tsx +103 -0
  71. package/template/src/routes/docs/configuration.tsx +113 -0
  72. package/template/src/routes/docs/overview.tsx +97 -0
  73. package/template/src/routes/docs/routing.tsx +126 -0
  74. package/template/src/routes/docs/structure.tsx +150 -0
  75. package/template/src/routes/docs/theming.tsx +88 -0
  76. package/template/src/routes/not-found.tsx +12 -0
  77. package/template/src/styles/aas-theme.css +21 -0
  78. package/template/src/vite-env.d.ts +12 -0
  79. package/template/tsconfig.app.json +30 -0
  80. package/template/tsconfig.json +12 -0
  81. package/template/tsconfig.node.json +23 -0
  82. package/template/vercel.json +3 -0
  83. package/template/vite.config.ts +17 -0
  84. package/variants/minimal/src/App.tsx +43 -0
  85. package/variants/minimal/src/components/app-sidebar.tsx +50 -0
  86. package/variants/minimal/src/config/navigation.ts +19 -0
  87. 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,12 @@
1
+ {
2
+ "files": [],
3
+ "references": [
4
+ { "path": "./tsconfig.app.json" },
5
+ { "path": "./tsconfig.node.json" }
6
+ ],
7
+ "compilerOptions": {
8
+ "paths": {
9
+ "@/*": ["./src/*"]
10
+ }
11
+ }
12
+ }
@@ -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,3 @@
1
+ {
2
+ "rewrites": [{ "source": "/(.*)", "destination": "/index.html" }]
3
+ }
@@ -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
+ }