@clubedge/create-clubedge-app 0.3.0 → 0.4.0

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 (81) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +29 -13
  3. package/dist/cli/args.js +5 -0
  4. package/dist/cli/ui.js +2 -0
  5. package/dist/core/customize.js +40 -1
  6. package/dist/core/manifest.js +41 -3
  7. package/dist/core/plan.js +68 -3
  8. package/dist/index.js +42 -7
  9. package/dist/utils/fs.js +3 -0
  10. package/package.json +5 -4
  11. package/template/.dockerignore +3 -0
  12. package/template/.github/workflows/ci.yml +51 -4
  13. package/template/.prettierignore +1 -0
  14. package/template/AGENTS.md +3 -2
  15. package/template/CONTRIBUTING.md +2 -2
  16. package/template/README.md +18 -5
  17. package/template/SETUP.md +23 -1
  18. package/template/_gitignore +4 -0
  19. package/template/apps/start/.env.example +27 -0
  20. package/template/apps/start/AGENTS.md +11 -0
  21. package/template/apps/start/Dockerfile +38 -0
  22. package/template/apps/start/package.json +37 -0
  23. package/template/apps/start/public/.well-known/logo.svg +1 -0
  24. package/template/apps/start/public/favicon.ico +0 -0
  25. package/template/apps/start/src/components/auth/auth-form-controls.tsx +108 -0
  26. package/template/apps/start/src/components/branding/clubedge-mark.tsx +24 -0
  27. package/template/apps/start/src/components/dashboard/account-button.tsx +33 -0
  28. package/template/apps/start/src/components/dashboard/app-sidebar.tsx +90 -0
  29. package/template/apps/start/src/components/landing/copy-command.tsx +46 -0
  30. package/template/apps/start/src/components/theme/theme-provider.tsx +41 -0
  31. package/template/apps/start/src/components/theme/theme-toggle.tsx +21 -0
  32. package/template/apps/start/src/config/site.json +11 -0
  33. package/template/apps/start/src/config/site.ts +20 -0
  34. package/template/apps/start/src/env/server.test.ts +48 -0
  35. package/template/apps/start/src/env/server.ts +67 -0
  36. package/template/apps/start/src/lib/login-url.test.ts +16 -0
  37. package/template/apps/start/src/lib/login-url.ts +16 -0
  38. package/template/apps/start/src/routeTree.gen.ts +220 -0
  39. package/template/apps/start/src/router.tsx +27 -0
  40. package/template/apps/start/src/routes/__root.tsx +59 -0
  41. package/template/apps/start/src/routes/api/health.ts +15 -0
  42. package/template/apps/start/src/routes/auth/callback.ts +10 -0
  43. package/template/apps/start/src/routes/auth/sign-in.ts +11 -0
  44. package/template/apps/start/src/routes/auth/sign-out.ts +11 -0
  45. package/template/apps/start/src/routes/auth/sign-up.ts +11 -0
  46. package/template/apps/start/src/routes/dashboard.tsx +351 -0
  47. package/template/apps/start/src/routes/index.tsx +233 -0
  48. package/template/apps/start/src/routes/login.tsx +164 -0
  49. package/template/apps/start/src/server/auth-forms.test.ts +169 -0
  50. package/template/apps/start/src/server/auth-forms.ts +118 -0
  51. package/template/apps/start/src/server/auth.functions.ts +27 -0
  52. package/template/apps/start/src/server/auth.ts +51 -0
  53. package/template/apps/start/src/server/cache.ts +38 -0
  54. package/template/apps/start/src/server/cookies.test.ts +41 -0
  55. package/template/apps/start/src/server/cookies.ts +31 -0
  56. package/template/apps/start/src/server/db.ts +16 -0
  57. package/template/apps/start/src/server/storage.ts +39 -0
  58. package/template/apps/start/src/server/users.ts +17 -0
  59. package/template/apps/start/src/start.ts +40 -0
  60. package/template/apps/start/src/styles.css +2 -0
  61. package/template/apps/start/tsconfig.json +19 -0
  62. package/template/apps/start/vite.config.ts +27 -0
  63. package/template/apps/web/package.json +2 -2
  64. package/template/{scripts → apps/web/scripts}/start-standalone.mjs +1 -2
  65. package/template/clubedge.template.json +17 -1
  66. package/template/e2e/app.ts +12 -0
  67. package/template/{apps/web/e2e → e2e}/auth.spec.ts +2 -2
  68. package/template/eslint.config.mjs +4 -0
  69. package/template/package.json +3 -3
  70. package/template/packages/auth/package.json +1 -1
  71. package/template/packages/cache/package.json +1 -1
  72. package/template/packages/core/package.json +1 -1
  73. package/template/packages/db/package.json +1 -1
  74. package/template/packages/storage/package.json +1 -1
  75. package/template/packages/ui/package.json +1 -1
  76. package/template/playwright.config.ts +6 -2
  77. package/template/pnpm-lock.yaml +1382 -19
  78. package/template/turbo.json +1 -1
  79. package/template/vitest.config.ts +2 -0
  80. package/template.lock.json +50 -5
  81. /package/template/{apps/web/e2e → e2e}/home.spec.ts +0 -0
@@ -0,0 +1,351 @@
1
+ import {
2
+ Activity,
3
+ ArrowRight,
4
+ ArrowUpRight,
5
+ Blocks,
6
+ Check,
7
+ CircleDashed,
8
+ Database,
9
+ Github,
10
+ LockKeyhole,
11
+ Settings2,
12
+ ShieldCheck,
13
+ Workflow,
14
+ } from "lucide-react";
15
+ import { Link, createFileRoute } from "@tanstack/react-router";
16
+
17
+ import { Badge } from "@clubedge/ui/components/badge";
18
+ import { Button } from "@clubedge/ui/components/button";
19
+ import {
20
+ Card,
21
+ CardContent,
22
+ CardDescription,
23
+ CardHeader,
24
+ CardTitle,
25
+ } from "@clubedge/ui/components/card";
26
+ import { Separator } from "@clubedge/ui/components/separator";
27
+ import {
28
+ Table,
29
+ TableBody,
30
+ TableCell,
31
+ TableHead,
32
+ TableHeader,
33
+ TableRow,
34
+ } from "@clubedge/ui/components/table";
35
+ import {
36
+ Breadcrumb,
37
+ BreadcrumbItem,
38
+ BreadcrumbLink,
39
+ BreadcrumbList,
40
+ BreadcrumbPage,
41
+ BreadcrumbSeparator,
42
+ } from "@clubedge/ui/components/breadcrumb";
43
+ import { SidebarInset, SidebarProvider, SidebarTrigger } from "@clubedge/ui/components/sidebar";
44
+ import { siteConfig } from "@/config/site";
45
+ import { getPageUser } from "@/server/auth.functions";
46
+ import { AccountButton } from "@/components/dashboard/account-button";
47
+ import { AppSidebar } from "@/components/dashboard/app-sidebar";
48
+ import { ThemeToggle } from "@/components/theme/theme-toggle";
49
+
50
+ const modules = [
51
+ {
52
+ name: "PostgreSQL + Drizzle",
53
+ description: "Typed schema and migrations for application data.",
54
+ icon: Database,
55
+ status: "Configure",
56
+ variant: "secondary" as const,
57
+ },
58
+ {
59
+ name: "Supabase Auth",
60
+ description: "Cookie-based sessions and server-validated users.",
61
+ icon: LockKeyhole,
62
+ status: "Optional setup",
63
+ variant: "outline" as const,
64
+ },
65
+ {
66
+ name: "Storage adapters",
67
+ description: "S3-compatible storage or Supabase Storage.",
68
+ icon: Workflow,
69
+ status: "Ready to configure",
70
+ variant: "secondary" as const,
71
+ },
72
+ {
73
+ name: "Redis cache",
74
+ description: "Optional cache and distributed rate limiting.",
75
+ icon: CircleDashed,
76
+ status: "Optional",
77
+ variant: "outline" as const,
78
+ },
79
+ ];
80
+
81
+ const setupRows = [
82
+ {
83
+ item: "Shared UI workspace",
84
+ detail: "shadcn/ui components with Base UI primitives",
85
+ status: "Ready",
86
+ },
87
+ {
88
+ item: "Database connection",
89
+ detail: "Set DATABASE_URL in apps/web/.env.local",
90
+ status: "Configure",
91
+ },
92
+ {
93
+ item: "Authentication provider",
94
+ detail: "Add Supabase URL and publishable key when needed",
95
+ status: "Optional",
96
+ },
97
+ ];
98
+
99
+ export const Route = createFileRoute("/dashboard")({
100
+ loader: () => getPageUser({ data: { pathname: "/dashboard" } }),
101
+ component: DashboardPage,
102
+ });
103
+
104
+ function DashboardPage() {
105
+ const user = Route.useLoaderData();
106
+
107
+ return (
108
+ <SidebarProvider defaultOpen>
109
+ <AppSidebar user={user} />
110
+ <SidebarInset>
111
+ <header className="sticky top-0 z-10 border-b bg-background/90 backdrop-blur">
112
+ <div className="mx-auto flex h-16 max-w-7xl items-center justify-between px-4 sm:px-6 lg:px-8">
113
+ <div className="flex items-center gap-2 text-sm text-muted-foreground">
114
+ <SidebarTrigger aria-label="Toggle navigation sidebar" />
115
+ <Breadcrumb>
116
+ <BreadcrumbList>
117
+ <BreadcrumbItem>
118
+ <BreadcrumbLink render={<Link to="/" />}>Home</BreadcrumbLink>
119
+ </BreadcrumbItem>
120
+ <BreadcrumbSeparator />
121
+ <BreadcrumbItem>
122
+ <BreadcrumbPage>Dashboard</BreadcrumbPage>
123
+ </BreadcrumbItem>
124
+ </BreadcrumbList>
125
+ </Breadcrumb>
126
+ </div>
127
+ <div className="ml-auto flex items-center gap-2">
128
+ <Badge className="hidden sm:inline-flex" variant="outline">
129
+ <span className="size-1.5 rounded-full bg-primary" aria-hidden="true" />
130
+ Starter ready
131
+ </Badge>
132
+ <ThemeToggle />
133
+ <Button
134
+ aria-label="Open GitHub repository"
135
+ render={<a href={siteConfig.links.repository} target="_blank" rel="noreferrer" />}
136
+ size="icon"
137
+ variant="ghost"
138
+ >
139
+ <Github aria-hidden="true" />
140
+ </Button>
141
+ <AccountButton user={user} />
142
+ </div>
143
+ </div>
144
+ </header>
145
+
146
+ <main
147
+ className="mx-auto flex w-full max-w-7xl flex-col gap-8 px-4 py-8 sm:px-6 lg:px-8 lg:py-10"
148
+ id="overview"
149
+ >
150
+ <section className="flex flex-col justify-between gap-5 sm:flex-row sm:items-end">
151
+ <div className="max-w-2xl space-y-3">
152
+ <div className="flex items-center gap-2 text-sm font-medium text-primary">
153
+ <ShieldCheck aria-hidden="true" className="size-4" />
154
+ Production-minded application foundation
155
+ </div>
156
+ <h1 className="text-3xl font-semibold tracking-tight sm:text-4xl">
157
+ A better place to start building.
158
+ </h1>
159
+ <p className="max-w-xl text-sm leading-6 text-muted-foreground sm:text-base">
160
+ Your workspace is ready. Connect the services you need, then build your product on
161
+ top of a consistent, replaceable foundation.
162
+ </p>
163
+ </div>
164
+ <Button
165
+ render={<a href={siteConfig.links.setupGuide} target="_blank" rel="noreferrer" />}
166
+ >
167
+ Read setup guide <ArrowRight aria-hidden="true" />
168
+ </Button>
169
+ </section>
170
+
171
+ <section
172
+ aria-label="Starter summary"
173
+ className="grid gap-4 sm:grid-cols-2 xl:grid-cols-4"
174
+ >
175
+ <Card className="gap-4 py-5">
176
+ <CardHeader className="flex-row items-center justify-between gap-2 px-5">
177
+ <CardDescription>Workspace packages</CardDescription>
178
+ <span className="grid size-9 place-items-center rounded-lg bg-primary/10 text-primary">
179
+ <Blocks aria-hidden="true" className="size-4" />
180
+ </span>
181
+ </CardHeader>
182
+ <CardContent className="px-5">
183
+ <CardTitle className="text-2xl">3</CardTitle>
184
+ <p className="mt-1 text-xs text-muted-foreground">
185
+ Web, shared UI, and root tooling
186
+ </p>
187
+ </CardContent>
188
+ </Card>
189
+ <Card className="gap-4 py-5">
190
+ <CardHeader className="flex-row items-center justify-between gap-2 px-5">
191
+ <CardDescription>Database</CardDescription>
192
+ <span className="grid size-9 place-items-center rounded-lg bg-primary/10 text-primary">
193
+ <Database aria-hidden="true" className="size-4" />
194
+ </span>
195
+ </CardHeader>
196
+ <CardContent className="px-5">
197
+ <CardTitle className="text-2xl">PostgreSQL</CardTitle>
198
+ <p className="mt-1 text-xs text-muted-foreground">Drizzle manages app data</p>
199
+ </CardContent>
200
+ </Card>
201
+ <Card className="gap-4 py-5">
202
+ <CardHeader className="flex-row items-center justify-between gap-2 px-5">
203
+ <CardDescription>Shared UI</CardDescription>
204
+ <span className="grid size-9 place-items-center rounded-lg bg-primary/10 text-primary">
205
+ <Blocks aria-hidden="true" className="size-4" />
206
+ </span>
207
+ </CardHeader>
208
+ <CardContent className="px-5">
209
+ <CardTitle className="text-2xl">shadcn/ui</CardTitle>
210
+ <p className="mt-1 text-xs text-muted-foreground">
211
+ Base UI primitives in @clubedge/ui
212
+ </p>
213
+ </CardContent>
214
+ </Card>
215
+ <Card className="gap-4 py-5">
216
+ <CardHeader className="flex-row items-center justify-between gap-2 px-5">
217
+ <CardDescription>Health endpoint</CardDescription>
218
+ <span className="grid size-9 place-items-center rounded-lg bg-primary/10 text-primary">
219
+ <Activity aria-hidden="true" className="size-4" />
220
+ </span>
221
+ </CardHeader>
222
+ <CardContent className="px-5">
223
+ <CardTitle className="text-2xl">Available</CardTitle>
224
+ <p className="mt-1 text-xs text-muted-foreground">
225
+ <a
226
+ className="inline-flex items-center gap-1 text-primary hover:underline"
227
+ href="/api/health"
228
+ >
229
+ Check liveness <ArrowUpRight aria-hidden="true" className="size-3" />
230
+ </a>
231
+ </p>
232
+ </CardContent>
233
+ </Card>
234
+ </section>
235
+
236
+ <section className="grid gap-6 xl:grid-cols-[1.1fr_0.9fr]" id="modules">
237
+ <Card className="gap-0 py-0">
238
+ <CardHeader className="flex-row items-start justify-between gap-3 border-b px-5 py-5 sm:px-6">
239
+ <div className="space-y-1.5">
240
+ <CardTitle className="text-base">Application modules</CardTitle>
241
+ <CardDescription>
242
+ Provider boundaries are ready for your project configuration.
243
+ </CardDescription>
244
+ </div>
245
+ <Badge variant="secondary">4 modules</Badge>
246
+ </CardHeader>
247
+ <CardContent className="px-5 py-2 sm:px-6">
248
+ {modules.map(({ name, description, icon: Icon, status, variant }, index) => (
249
+ <div key={name}>
250
+ {index > 0 && <Separator />}
251
+ <div className="flex items-center gap-3 py-4">
252
+ <span className="grid size-9 shrink-0 place-items-center rounded-lg border bg-muted/60 text-muted-foreground">
253
+ <Icon aria-hidden="true" className="size-4" />
254
+ </span>
255
+ <div className="min-w-0 flex-1">
256
+ <p className="truncate text-sm font-medium">{name}</p>
257
+ <p className="mt-1 text-xs leading-5 text-muted-foreground">
258
+ {description}
259
+ </p>
260
+ </div>
261
+ <Badge className="shrink-0" variant={variant}>
262
+ {status}
263
+ </Badge>
264
+ </div>
265
+ </div>
266
+ ))}
267
+ </CardContent>
268
+ </Card>
269
+
270
+ <Card className="gap-0 py-0" id="activity">
271
+ <CardHeader className="border-b px-5 py-5 sm:px-6">
272
+ <CardTitle className="text-base">Project setup</CardTitle>
273
+ <CardDescription>Recommended first steps for a new application.</CardDescription>
274
+ </CardHeader>
275
+ <CardContent className="p-0">
276
+ <Table>
277
+ <TableHeader>
278
+ <TableRow className="hover:bg-transparent">
279
+ <TableHead className="ps-5 sm:ps-6">Item</TableHead>
280
+ <TableHead className="pe-5 text-end sm:pe-6">Status</TableHead>
281
+ </TableRow>
282
+ </TableHeader>
283
+ <TableBody>
284
+ {setupRows.map((row) => (
285
+ <TableRow key={row.item}>
286
+ <TableCell className="whitespace-normal py-4 ps-5 sm:ps-6">
287
+ <span className="block font-medium">{row.item}</span>
288
+ <span className="mt-1 block text-xs leading-5 text-muted-foreground">
289
+ {row.detail}
290
+ </span>
291
+ </TableCell>
292
+ <TableCell className="pe-5 text-end sm:pe-6">
293
+ <Badge variant={row.status === "Ready" ? "default" : "outline"}>
294
+ {row.status === "Ready" && <Check aria-hidden="true" />}
295
+ {row.status}
296
+ </Badge>
297
+ </TableCell>
298
+ </TableRow>
299
+ ))}
300
+ </TableBody>
301
+ </Table>
302
+ </CardContent>
303
+ <Separator />
304
+ <CardContent className="flex items-center justify-between gap-4 px-5 py-4 sm:px-6">
305
+ <p className="text-xs text-muted-foreground">Need help connecting a provider?</p>
306
+ <Button
307
+ render={<a href={siteConfig.links.setupGuide} target="_blank" rel="noreferrer" />}
308
+ size="sm"
309
+ variant="ghost"
310
+ >
311
+ Setup docs <ArrowUpRight aria-hidden="true" />
312
+ </Button>
313
+ </CardContent>
314
+ </Card>
315
+ </section>
316
+
317
+ <Card className="gap-4 border-dashed bg-muted/30 py-5 shadow-none">
318
+ <CardContent className="flex flex-col gap-4 px-5 sm:flex-row sm:items-center sm:justify-between sm:px-6">
319
+ <div className="flex items-start gap-3">
320
+ <span className="grid size-9 shrink-0 place-items-center rounded-lg bg-background text-primary shadow-sm">
321
+ <Settings2 aria-hidden="true" className="size-4" />
322
+ </span>
323
+ <div>
324
+ <p className="text-sm font-medium">Make this foundation yours</p>
325
+ <p className="mt-1 text-xs leading-5 text-muted-foreground">
326
+ Configure environment values, review the architecture, and add only the UI
327
+ components your product needs.
328
+ </p>
329
+ </div>
330
+ </div>
331
+ <Button
332
+ render={<a href={siteConfig.links.setupGuide} target="_blank" rel="noreferrer" />}
333
+ size="sm"
334
+ variant="outline"
335
+ >
336
+ Get started <ArrowRight aria-hidden="true" />
337
+ </Button>
338
+ </CardContent>
339
+ </Card>
340
+
341
+ <footer className="flex flex-col gap-2 border-t pt-5 text-xs text-muted-foreground sm:flex-row sm:items-center sm:justify-between">
342
+ <span>{siteConfig.name} · TanStack Start application foundation</span>
343
+ <span className="inline-flex items-center gap-2">
344
+ <Check aria-hidden="true" className="size-3.5 text-primary" /> Built to be extended
345
+ </span>
346
+ </footer>
347
+ </main>
348
+ </SidebarInset>
349
+ </SidebarProvider>
350
+ );
351
+ }
@@ -0,0 +1,233 @@
1
+ import { ArrowRight, ArrowUpRight, Github, LayoutDashboard } from "lucide-react";
2
+ import { Link, createFileRoute } from "@tanstack/react-router";
3
+
4
+ import { Button } from "@clubedge/ui/components/button";
5
+ import { ClubedgeMark } from "@/components/branding/clubedge-mark";
6
+ import { CopyCommand } from "@/components/landing/copy-command";
7
+ import { siteConfig } from "@/config/site";
8
+ import { ThemeToggle } from "@/components/theme/theme-toggle";
9
+
10
+ const REPO_URL = siteConfig.links.repository;
11
+ const SETUP_URL = siteConfig.links.setupGuide;
12
+ const CREATE_COMMAND = "pnpm dlx @clubedge/create-clubedge-app my-app";
13
+
14
+ const included = [
15
+ {
16
+ name: "TanStack Start",
17
+ detail: "A full-stack React app with type-safe routing, server functions, and streaming SSR.",
18
+ },
19
+ {
20
+ name: "Supabase Auth",
21
+ detail:
22
+ "Email and password sign-in, with sessions verified on the server and stored in secure cookies.",
23
+ },
24
+ {
25
+ name: "Drizzle and PostgreSQL",
26
+ detail: "Typed data access that you can extend or swap for your own infrastructure.",
27
+ },
28
+ {
29
+ name: "Base UI design system",
30
+ detail: "Shared, accessible components in @clubedge/ui, used across the whole repo.",
31
+ },
32
+ {
33
+ name: "TypeScript",
34
+ detail: "Typed from the database layer through to the UI.",
35
+ },
36
+ {
37
+ name: "Dashboard",
38
+ detail: "A starting point for your product, ready to open at /dashboard.",
39
+ },
40
+ ];
41
+
42
+ const steps = [
43
+ "Create your project with the command above.",
44
+ "Add your Supabase URL and publishable key to apps/web/.env.local.",
45
+ "Start the app, create an account, and sign in.",
46
+ ];
47
+
48
+ export const Route = createFileRoute("/")({
49
+ component: LandingPage,
50
+ });
51
+
52
+ function LandingPage() {
53
+ return (
54
+ <div className="flex min-h-svh flex-col">
55
+ <header className="sticky top-0 z-20 border-b bg-background/85 backdrop-blur-xl">
56
+ <nav
57
+ aria-label="Main navigation"
58
+ className="mx-auto flex h-16 max-w-6xl items-center justify-between gap-4 px-4 sm:px-6 lg:px-8"
59
+ >
60
+ <Link
61
+ aria-label={`${siteConfig.name} home`}
62
+ className="flex shrink-0 items-center gap-2.5 rounded-md outline-none focus-visible:ring-2 focus-visible:ring-ring"
63
+ to="/"
64
+ >
65
+ <ClubedgeMark alt="" className="size-9 rounded-xl" priority />
66
+ <span className="hidden text-sm font-semibold tracking-tight min-[380px]:inline">
67
+ {siteConfig.name}
68
+ </span>
69
+ </Link>
70
+
71
+ <div className="hidden items-center gap-6 text-sm text-muted-foreground md:flex">
72
+ <a className="transition-colors hover:text-foreground" href="#stack">
73
+ Stack
74
+ </a>
75
+ <a
76
+ className="inline-flex items-center gap-1 transition-colors hover:text-foreground"
77
+ href={SETUP_URL}
78
+ rel="noreferrer"
79
+ target="_blank"
80
+ >
81
+ Documentation <ArrowUpRight aria-hidden="true" className="size-3.5" />
82
+ </a>
83
+ <a
84
+ className="transition-colors hover:text-foreground"
85
+ href={REPO_URL}
86
+ rel="noreferrer"
87
+ target="_blank"
88
+ >
89
+ GitHub
90
+ </a>
91
+ </div>
92
+
93
+ <div className="flex shrink-0 items-center gap-2 sm:gap-3">
94
+ <ThemeToggle />
95
+ <Button
96
+ aria-label="Go to dashboard"
97
+ className="sm:hidden"
98
+ render={<Link to="/dashboard" />}
99
+ size="icon"
100
+ variant="ghost"
101
+ >
102
+ <LayoutDashboard aria-hidden="true" />
103
+ </Button>
104
+ <Button
105
+ className="hidden sm:inline-flex"
106
+ render={<Link to="/dashboard" />}
107
+ size="sm"
108
+ variant="ghost"
109
+ >
110
+ Dashboard
111
+ </Button>
112
+ <Button render={<Link to="/login" />} size="sm">
113
+ Sign in
114
+ </Button>
115
+ </div>
116
+ </nav>
117
+ </header>
118
+
119
+ <main className="flex-1">
120
+ <section className="mx-auto w-full max-w-6xl px-4 pb-16 pt-16 sm:px-6 sm:pb-24 sm:pt-24 lg:px-8">
121
+ <div className="max-w-3xl">
122
+ <h1 className="text-balance text-4xl font-semibold tracking-tight sm:text-5xl lg:text-6xl">
123
+ Start with the foundation. Build what matters.
124
+ </h1>
125
+ <p className="mt-6 max-w-2xl text-pretty text-base leading-7 text-muted-foreground sm:text-lg sm:leading-8">
126
+ A modular TanStack Start starter with a shared Base UI design system, typed data
127
+ access, and replaceable infrastructure. Spend your time on the product, not the setup.
128
+ </p>
129
+
130
+ <div className="mt-8 max-w-xl">
131
+ <CopyCommand command={CREATE_COMMAND} />
132
+ </div>
133
+
134
+ <div className="mt-6 flex flex-col gap-3 sm:flex-row">
135
+ <Button className="h-11 px-5" render={<Link to="/dashboard" />}>
136
+ Explore the dashboard <ArrowRight aria-hidden="true" />
137
+ </Button>
138
+ <Button
139
+ className="h-11 px-5"
140
+ render={<a href={SETUP_URL} rel="noreferrer" target="_blank" />}
141
+ variant="outline"
142
+ >
143
+ Read the setup guide <ArrowUpRight aria-hidden="true" />
144
+ </Button>
145
+ </div>
146
+ </div>
147
+ </section>
148
+
149
+ <section
150
+ aria-labelledby="stack-heading"
151
+ className="scroll-mt-16 border-t bg-muted/30"
152
+ id="stack"
153
+ >
154
+ <div className="mx-auto w-full max-w-6xl px-4 py-16 sm:px-6 sm:py-20 lg:px-8">
155
+ <h2 className="text-2xl font-semibold tracking-tight" id="stack-heading">
156
+ What you get
157
+ </h2>
158
+ <p className="mt-2 max-w-xl text-sm leading-6 text-muted-foreground">
159
+ Every layer is wired together and replaceable, so you can keep what fits and change
160
+ the rest.
161
+ </p>
162
+
163
+ <dl className="mt-10 grid gap-px overflow-hidden rounded-xl border bg-border sm:grid-cols-2 lg:grid-cols-3">
164
+ {included.map((item) => (
165
+ <div className="bg-background p-6" key={item.name}>
166
+ <dt className="text-sm font-semibold">{item.name}</dt>
167
+ <dd className="mt-2 text-sm leading-6 text-muted-foreground">{item.detail}</dd>
168
+ </div>
169
+ ))}
170
+ </dl>
171
+ </div>
172
+ </section>
173
+
174
+ <section
175
+ aria-labelledby="start-heading"
176
+ className="mx-auto w-full max-w-6xl px-4 py-16 sm:px-6 sm:py-20 lg:px-8"
177
+ >
178
+ <h2 className="text-2xl font-semibold tracking-tight" id="start-heading">
179
+ Up and running in three steps
180
+ </h2>
181
+ <ol className="mt-8 grid max-w-2xl gap-5">
182
+ {steps.map((step, index) => (
183
+ <li className="flex items-start gap-4" key={step}>
184
+ <span
185
+ aria-hidden="true"
186
+ className="grid size-7 shrink-0 place-items-center rounded-full border text-xs font-medium text-muted-foreground"
187
+ >
188
+ {index + 1}
189
+ </span>
190
+ <p className="pt-0.5 text-sm leading-6">{step}</p>
191
+ </li>
192
+ ))}
193
+ </ol>
194
+ <div className="mt-8">
195
+ <Button
196
+ render={<a href={SETUP_URL} rel="noreferrer" target="_blank" />}
197
+ variant="outline"
198
+ >
199
+ Read the full setup guide <ArrowUpRight aria-hidden="true" />
200
+ </Button>
201
+ </div>
202
+ </section>
203
+ </main>
204
+
205
+ <footer className="border-t">
206
+ <div className="mx-auto flex max-w-6xl flex-col items-center justify-between gap-3 px-4 py-5 text-xs text-muted-foreground sm:flex-row sm:px-6 lg:px-8">
207
+ <span>
208
+ © {new Date().getFullYear()} {siteConfig.name}. Licensed under Apache-2.0.
209
+ </span>
210
+ <div className="flex items-center gap-5">
211
+ <a
212
+ className="transition-colors hover:text-foreground"
213
+ href={SETUP_URL}
214
+ rel="noreferrer"
215
+ target="_blank"
216
+ >
217
+ Documentation
218
+ </a>
219
+ <a
220
+ className="inline-flex items-center gap-1.5 transition-colors hover:text-foreground"
221
+ href={REPO_URL}
222
+ rel="noreferrer"
223
+ target="_blank"
224
+ >
225
+ <Github aria-hidden="true" className="size-3.5" />
226
+ Contribute on GitHub
227
+ </a>
228
+ </div>
229
+ </div>
230
+ </footer>
231
+ </div>
232
+ );
233
+ }