create-better-t-stack 1.7.1 → 1.8.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 (34) hide show
  1. package/README.md +3 -1
  2. package/dist/index.js +106 -105
  3. package/package.json +2 -2
  4. package/template/base/apps/web-base/src/index.css +1 -1
  5. package/template/base/apps/web-tanstack-router/src/components/mode-toggle.tsx +37 -0
  6. package/template/base/apps/web-tanstack-router/src/components/theme-provider.tsx +73 -0
  7. package/template/base/apps/web-tanstack-router/src/routes/__root.tsx +1 -2
  8. package/template/base/apps/web-tanstack-start/app.config.ts +17 -0
  9. package/template/base/apps/web-tanstack-start/package.json +52 -0
  10. package/template/base/apps/web-tanstack-start/public/robots.txt +3 -0
  11. package/template/base/apps/web-tanstack-start/src/api.ts +6 -0
  12. package/template/base/apps/web-tanstack-start/src/client.tsx +8 -0
  13. package/template/base/apps/web-tanstack-start/src/components/header.tsx +27 -0
  14. package/template/base/apps/web-tanstack-start/src/index.css +135 -0
  15. package/template/base/apps/web-tanstack-start/src/router.tsx +70 -0
  16. package/template/base/apps/web-tanstack-start/src/routes/__root.tsx +68 -0
  17. package/template/base/apps/web-tanstack-start/src/routes/index.tsx +88 -0
  18. package/template/base/apps/web-tanstack-start/src/ssr.tsx +12 -0
  19. package/template/base/apps/web-tanstack-start/src/utils/trpc.ts +5 -0
  20. package/template/base/apps/web-tanstack-start/tsconfig.json +28 -0
  21. package/template/examples/ai/apps/web-tanstack-start/src/routes/ai.tsx +69 -0
  22. package/template/examples/todo/apps/web-tanstack-start/src/routes/todos.tsx +135 -0
  23. package/template/with-auth/apps/web-tanstack-router/src/utils/trpc.ts +39 -0
  24. package/template/with-auth/apps/web-tanstack-start/src/components/header.tsx +32 -0
  25. package/template/with-auth/apps/web-tanstack-start/src/components/sign-in-form.tsx +139 -0
  26. package/template/with-auth/apps/web-tanstack-start/src/components/sign-up-form.tsx +164 -0
  27. package/template/with-auth/apps/web-tanstack-start/src/components/user-menu.tsx +62 -0
  28. package/template/with-auth/apps/web-tanstack-start/src/router.tsx +76 -0
  29. package/template/with-auth/apps/web-tanstack-start/src/routes/dashboard.tsx +37 -0
  30. package/template/with-auth/apps/web-tanstack-start/src/routes/login.tsx +18 -0
  31. package/template/with-drizzle-sqlite/apps/server/src/db/schema/auth.ts +0 -6
  32. /package/template/base/apps/{web-base → web-react-router}/src/components/mode-toggle.tsx +0 -0
  33. /package/template/base/apps/{web-base → web-react-router}/src/components/theme-provider.tsx +0 -0
  34. /package/template/with-auth/apps/{web-base → web-react-router}/src/utils/trpc.ts +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-better-t-stack",
3
- "version": "1.7.1",
3
+ "version": "1.8.1",
4
4
  "description": "A modern CLI tool for scaffolding end-to-end type-safe TypeScript projects with best practices and customizable configurations",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -39,7 +39,7 @@
39
39
  ],
40
40
  "repository": {
41
41
  "type": "git",
42
- "url": "git+https://github.com/better-t-stack/create-better-t-stack.git",
42
+ "url": "git+https://github.com/AmanVarshney01/create-better-t-stack.git",
43
43
  "directory": "apps/cli"
44
44
  },
45
45
  "homepage": "https://better-t-stack.pages.dev/",
@@ -1,7 +1,7 @@
1
1
  @import "tailwindcss";
2
2
  @import "tw-animate-css";
3
3
 
4
- @custom-variant dark (&:is(.dark *));
4
+ @custom-variant dark (&:where(.dark, .dark *));
5
5
 
6
6
  @theme {
7
7
  --font-sans: "Inter", ui-sans-serif, system-ui, sans-serif,
@@ -0,0 +1,37 @@
1
+ import { Moon, Sun } from "lucide-react";
2
+
3
+ import { Button } from "@/components/ui/button";
4
+ import {
5
+ DropdownMenu,
6
+ DropdownMenuContent,
7
+ DropdownMenuItem,
8
+ DropdownMenuTrigger,
9
+ } from "@/components/ui/dropdown-menu";
10
+ import { useTheme } from "@/components/theme-provider";
11
+
12
+ export function ModeToggle() {
13
+ const { setTheme } = useTheme();
14
+
15
+ return (
16
+ <DropdownMenu>
17
+ <DropdownMenuTrigger asChild>
18
+ <Button variant="outline" size="icon">
19
+ <Sun className="h-[1.2rem] w-[1.2rem] scale-100 rotate-0 transition-all dark:scale-0 dark:-rotate-90" />
20
+ <Moon className="absolute h-[1.2rem] w-[1.2rem] scale-0 rotate-90 transition-all dark:scale-100 dark:rotate-0" />
21
+ <span className="sr-only">Toggle theme</span>
22
+ </Button>
23
+ </DropdownMenuTrigger>
24
+ <DropdownMenuContent align="end">
25
+ <DropdownMenuItem onClick={() => setTheme("light")}>
26
+ Light
27
+ </DropdownMenuItem>
28
+ <DropdownMenuItem onClick={() => setTheme("dark")}>
29
+ Dark
30
+ </DropdownMenuItem>
31
+ <DropdownMenuItem onClick={() => setTheme("system")}>
32
+ System
33
+ </DropdownMenuItem>
34
+ </DropdownMenuContent>
35
+ </DropdownMenu>
36
+ );
37
+ }
@@ -0,0 +1,73 @@
1
+ import { createContext, useContext, useEffect, useState } from "react";
2
+
3
+ type Theme = "dark" | "light" | "system";
4
+
5
+ type ThemeProviderProps = {
6
+ children: React.ReactNode;
7
+ defaultTheme?: Theme;
8
+ storageKey?: string;
9
+ };
10
+
11
+ type ThemeProviderState = {
12
+ theme: Theme;
13
+ setTheme: (theme: Theme) => void;
14
+ };
15
+
16
+ const initialState: ThemeProviderState = {
17
+ theme: "system",
18
+ setTheme: () => null,
19
+ };
20
+
21
+ const ThemeProviderContext = createContext<ThemeProviderState>(initialState);
22
+
23
+ export function ThemeProvider({
24
+ children,
25
+ defaultTheme = "system",
26
+ storageKey = "vite-ui-theme",
27
+ ...props
28
+ }: ThemeProviderProps) {
29
+ const [theme, setTheme] = useState<Theme>(
30
+ () => (localStorage.getItem(storageKey) as Theme) || defaultTheme
31
+ );
32
+
33
+ useEffect(() => {
34
+ const root = window.document.documentElement;
35
+
36
+ root.classList.remove("light", "dark");
37
+
38
+ if (theme === "system") {
39
+ const systemTheme = window.matchMedia("(prefers-color-scheme: dark)")
40
+ .matches
41
+ ? "dark"
42
+ : "light";
43
+
44
+ root.classList.add(systemTheme);
45
+ return;
46
+ }
47
+
48
+ root.classList.add(theme);
49
+ }, [theme]);
50
+
51
+ const value = {
52
+ theme,
53
+ setTheme: (theme: Theme) => {
54
+ localStorage.setItem(storageKey, theme);
55
+ setTheme(theme);
56
+ },
57
+ };
58
+
59
+ return (
60
+ <ThemeProviderContext.Provider {...props} value={value}>
61
+ {children}
62
+ </ThemeProviderContext.Provider>
63
+ );
64
+ }
65
+
66
+ export const useTheme = () => {
67
+ const context = useContext(ThemeProviderContext);
68
+
69
+ if (context === undefined)
70
+ throw new Error("useTheme must be used within a ThemeProvider");
71
+
72
+ return context;
73
+ };
@@ -50,8 +50,7 @@ function RootComponent() {
50
50
  <ThemeProvider defaultTheme="dark" storageKey="vite-ui-theme">
51
51
  <div className="grid grid-rows-[auto_1fr] h-svh">
52
52
  <Header />
53
- {isFetching && <Loader />}
54
- <Outlet />
53
+ {isFetching ? <Loader /> : <Outlet />}
55
54
  </div>
56
55
  <Toaster richColors />
57
56
  </ThemeProvider>
@@ -0,0 +1,17 @@
1
+ import { defineConfig } from "@tanstack/react-start/config";
2
+ import viteTsConfigPaths from "vite-tsconfig-paths";
3
+ import tailwindcss from "@tailwindcss/vite";
4
+
5
+ export default defineConfig({
6
+ tsr: {
7
+ appDirectory: "src",
8
+ },
9
+ vite: {
10
+ plugins: [
11
+ viteTsConfigPaths({
12
+ projects: ["./tsconfig.json"],
13
+ }),
14
+ tailwindcss(),
15
+ ],
16
+ },
17
+ });
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "tanstack-start",
3
+ "private": true,
4
+ "type": "module",
5
+ "scripts": {
6
+ "start": "vinxi start",
7
+ "build": "vinxi build",
8
+ "serve": "vite preview",
9
+ "dev": "vinxi dev --port=3001"
10
+ },
11
+ "dependencies": {
12
+ "@radix-ui/react-checkbox": "^1.1.4",
13
+ "@radix-ui/react-dropdown-menu": "^2.1.6",
14
+ "@radix-ui/react-label": "^2.1.2",
15
+ "@radix-ui/react-slot": "^1.1.2",
16
+ "@tanstack/react-form": "^1.0.5",
17
+ "@tailwindcss/vite": "^4.0.6",
18
+ "@tanstack/react-query": "^5.71.10",
19
+ "@tanstack/react-router": "^1.114.3",
20
+ "@tanstack/react-router-with-query": "^1.114.3",
21
+ "@tanstack/react-start": "^1.114.3",
22
+ "@tanstack/router-plugin": "^1.114.3",
23
+ "@trpc/client": "^11.0.2",
24
+ "@trpc/server": "^11.0.2",
25
+ "@trpc/tanstack-react-query": "^11.0.2",
26
+ "class-variance-authority": "^0.7.1",
27
+ "clsx": "^2.1.1",
28
+ "lucide-react": "^0.473.0",
29
+ "next-themes": "^0.4.6",
30
+ "react": "^19.0.0",
31
+ "react-dom": "^19.0.0",
32
+ "sonner": "^2.0.3",
33
+ "tailwindcss": "^4.1.3",
34
+ "tailwind-merge": "^2.6.0",
35
+ "tw-animate-css": "^1.2.5",
36
+ "vinxi": "^0.5.3",
37
+ "vite-tsconfig-paths": "^5.1.4"
38
+ },
39
+ "devDependencies": {
40
+ "@tanstack/react-router-devtools": "^1.114.3",
41
+ "@tanstack/react-query-devtools": "^5.71.10",
42
+ "@testing-library/dom": "^10.4.0",
43
+ "@testing-library/react": "^16.2.0",
44
+ "@types/react": "^19.0.8",
45
+ "@types/react-dom": "^19.0.3",
46
+ "@vitejs/plugin-react": "^4.3.4",
47
+ "jsdom": "^26.0.0",
48
+ "typescript": "^5.7.2",
49
+ "vite": "^6.1.0",
50
+ "web-vitals": "^4.2.4"
51
+ }
52
+ }
@@ -0,0 +1,3 @@
1
+ # https://www.robotstxt.org/robotstxt.html
2
+ User-agent: *
3
+ Disallow:
@@ -0,0 +1,6 @@
1
+ import {
2
+ createStartAPIHandler,
3
+ defaultAPIFileRouteHandler,
4
+ } from "@tanstack/react-start/api";
5
+
6
+ export default createStartAPIHandler(defaultAPIFileRouteHandler);
@@ -0,0 +1,8 @@
1
+ import { StartClient } from "@tanstack/react-start";
2
+ import { hydrateRoot } from "react-dom/client";
3
+
4
+ import { createRouter } from "./router";
5
+
6
+ const router = createRouter();
7
+
8
+ hydrateRoot(document, <StartClient router={router} />);
@@ -0,0 +1,27 @@
1
+ import { Link } from "@tanstack/react-router";
2
+
3
+ export default function Header() {
4
+ const links = [
5
+ { to: "/", label: "Home" },
6
+ ];
7
+
8
+ return (
9
+ <div>
10
+ <div className="flex flex-row items-center justify-between px-2 py-1">
11
+ <nav className="flex gap-4 text-lg">
12
+ {links.map(({ to, label }) => (
13
+ <Link
14
+ key={to}
15
+ to={to}
16
+ activeProps={{ className: "font-bold" }}
17
+ activeOptions={{ exact: true }}
18
+ >
19
+ {label}
20
+ </Link>
21
+ ))}
22
+ </nav>
23
+ </div>
24
+ <hr />
25
+ </div>
26
+ );
27
+ }
@@ -0,0 +1,135 @@
1
+ @import "tailwindcss";
2
+ @import "tw-animate-css";
3
+
4
+ @custom-variant dark (&:where(.dark, .dark *));
5
+
6
+ @theme {
7
+ --font-sans:
8
+ "Inter", ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji",
9
+ "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
10
+ }
11
+
12
+ html,
13
+ body {
14
+ @apply bg-white dark:bg-gray-950;
15
+
16
+ @media (prefers-color-scheme: dark) {
17
+ color-scheme: dark;
18
+ }
19
+ }
20
+
21
+ :root {
22
+ --radius: 0.625rem;
23
+ --background: oklch(1 0 0);
24
+ --foreground: oklch(0.145 0 0);
25
+ --card: oklch(1 0 0);
26
+ --card-foreground: oklch(0.145 0 0);
27
+ --popover: oklch(1 0 0);
28
+ --popover-foreground: oklch(0.145 0 0);
29
+ --primary: oklch(0.205 0 0);
30
+ --primary-foreground: oklch(0.985 0 0);
31
+ --secondary: oklch(0.97 0 0);
32
+ --secondary-foreground: oklch(0.205 0 0);
33
+ --muted: oklch(0.97 0 0);
34
+ --muted-foreground: oklch(0.556 0 0);
35
+ --accent: oklch(0.97 0 0);
36
+ --accent-foreground: oklch(0.205 0 0);
37
+ --destructive: oklch(0.577 0.245 27.325);
38
+ --border: oklch(0.922 0 0);
39
+ --input: oklch(0.922 0 0);
40
+ --ring: oklch(0.708 0 0);
41
+ --chart-1: oklch(0.646 0.222 41.116);
42
+ --chart-2: oklch(0.6 0.118 184.704);
43
+ --chart-3: oklch(0.398 0.07 227.392);
44
+ --chart-4: oklch(0.828 0.189 84.429);
45
+ --chart-5: oklch(0.769 0.188 70.08);
46
+ --sidebar: oklch(0.985 0 0);
47
+ --sidebar-foreground: oklch(0.145 0 0);
48
+ --sidebar-primary: oklch(0.205 0 0);
49
+ --sidebar-primary-foreground: oklch(0.985 0 0);
50
+ --sidebar-accent: oklch(0.97 0 0);
51
+ --sidebar-accent-foreground: oklch(0.205 0 0);
52
+ --sidebar-border: oklch(0.922 0 0);
53
+ --sidebar-ring: oklch(0.708 0 0);
54
+ }
55
+
56
+ .dark {
57
+ --background: oklch(0.145 0 0);
58
+ --foreground: oklch(0.985 0 0);
59
+ --card: oklch(0.205 0 0);
60
+ --card-foreground: oklch(0.985 0 0);
61
+ --popover: oklch(0.205 0 0);
62
+ --popover-foreground: oklch(0.985 0 0);
63
+ --primary: oklch(0.922 0 0);
64
+ --primary-foreground: oklch(0.205 0 0);
65
+ --secondary: oklch(0.269 0 0);
66
+ --secondary-foreground: oklch(0.985 0 0);
67
+ --muted: oklch(0.269 0 0);
68
+ --muted-foreground: oklch(0.708 0 0);
69
+ --accent: oklch(0.269 0 0);
70
+ --accent-foreground: oklch(0.985 0 0);
71
+ --destructive: oklch(0.704 0.191 22.216);
72
+ --border: oklch(1 0 0 / 10%);
73
+ --input: oklch(1 0 0 / 15%);
74
+ --ring: oklch(0.556 0 0);
75
+ --chart-1: oklch(0.488 0.243 264.376);
76
+ --chart-2: oklch(0.696 0.17 162.48);
77
+ --chart-3: oklch(0.769 0.188 70.08);
78
+ --chart-4: oklch(0.627 0.265 303.9);
79
+ --chart-5: oklch(0.645 0.246 16.439);
80
+ --sidebar: oklch(0.205 0 0);
81
+ --sidebar-foreground: oklch(0.985 0 0);
82
+ --sidebar-primary: oklch(0.488 0.243 264.376);
83
+ --sidebar-primary-foreground: oklch(0.985 0 0);
84
+ --sidebar-accent: oklch(0.269 0 0);
85
+ --sidebar-accent-foreground: oklch(0.985 0 0);
86
+ --sidebar-border: oklch(1 0 0 / 10%);
87
+ --sidebar-ring: oklch(0.556 0 0);
88
+ }
89
+
90
+ @theme inline {
91
+ --radius-sm: calc(var(--radius) - 4px);
92
+ --radius-md: calc(var(--radius) - 2px);
93
+ --radius-lg: var(--radius);
94
+ --radius-xl: calc(var(--radius) + 4px);
95
+ --color-background: var(--background);
96
+ --color-foreground: var(--foreground);
97
+ --color-card: var(--card);
98
+ --color-card-foreground: var(--card-foreground);
99
+ --color-popover: var(--popover);
100
+ --color-popover-foreground: var(--popover-foreground);
101
+ --color-primary: var(--primary);
102
+ --color-primary-foreground: var(--primary-foreground);
103
+ --color-secondary: var(--secondary);
104
+ --color-secondary-foreground: var(--secondary-foreground);
105
+ --color-muted: var(--muted);
106
+ --color-muted-foreground: var(--muted-foreground);
107
+ --color-accent: var(--accent);
108
+ --color-accent-foreground: var(--accent-foreground);
109
+ --color-destructive: var(--destructive);
110
+ --color-border: var(--border);
111
+ --color-input: var(--input);
112
+ --color-ring: var(--ring);
113
+ --color-chart-1: var(--chart-1);
114
+ --color-chart-2: var(--chart-2);
115
+ --color-chart-3: var(--chart-3);
116
+ --color-chart-4: var(--chart-4);
117
+ --color-chart-5: var(--chart-5);
118
+ --color-sidebar: var(--sidebar);
119
+ --color-sidebar-foreground: var(--sidebar-foreground);
120
+ --color-sidebar-primary: var(--sidebar-primary);
121
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
122
+ --color-sidebar-accent: var(--sidebar-accent);
123
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
124
+ --color-sidebar-border: var(--sidebar-border);
125
+ --color-sidebar-ring: var(--sidebar-ring);
126
+ }
127
+
128
+ @layer base {
129
+ * {
130
+ @apply border-border outline-ring/50;
131
+ }
132
+ body {
133
+ @apply bg-background text-foreground;
134
+ }
135
+ }
@@ -0,0 +1,70 @@
1
+ import {
2
+ QueryCache,
3
+ QueryClient,
4
+ QueryClientProvider,
5
+ } from "@tanstack/react-query";
6
+ import { createRouter as createTanstackRouter } from "@tanstack/react-router";
7
+ import { createTRPCClient, httpBatchLink } from "@trpc/client";
8
+ import { createTRPCOptionsProxy } from "@trpc/tanstack-react-query";
9
+ import { toast } from "sonner";
10
+ import type { AppRouter } from "../../server/src/routers";
11
+ import Loader from "./components/loader";
12
+ import "./index.css";
13
+ import { routeTree } from "./routeTree.gen";
14
+ import { TRPCProvider } from "./utils/trpc";
15
+
16
+ export const queryClient = new QueryClient({
17
+ queryCache: new QueryCache({
18
+ onError: (error) => {
19
+ toast.error(error.message, {
20
+ action: {
21
+ label: "retry",
22
+ onClick: () => {
23
+ queryClient.invalidateQueries();
24
+ },
25
+ },
26
+ });
27
+ },
28
+ }),
29
+ defaultOptions: { queries: { staleTime: 60 * 1000 } },
30
+ });
31
+
32
+ const trpcClient = createTRPCClient<AppRouter>({
33
+ links: [
34
+ httpBatchLink({
35
+ url: `${import.meta.env.VITE_SERVER_URL}/trpc`,
36
+ }),
37
+ ],
38
+ });
39
+
40
+ const trpc = createTRPCOptionsProxy({
41
+ client: trpcClient,
42
+ queryClient: queryClient,
43
+ });
44
+
45
+ export const createRouter = () => {
46
+ const router = createTanstackRouter({
47
+ routeTree,
48
+ scrollRestoration: true,
49
+ defaultPreloadStaleTime: 0,
50
+ context: { trpc, queryClient },
51
+ defaultPendingComponent: () => <Loader />,
52
+ defaultNotFoundComponent: () => <div>Not Found</div>,
53
+ Wrap: ({ children }) => (
54
+ <QueryClientProvider client={queryClient}>
55
+ <TRPCProvider trpcClient={trpcClient} queryClient={queryClient}>
56
+ {children}
57
+ </TRPCProvider>
58
+ </QueryClientProvider>
59
+ ),
60
+ });
61
+
62
+ return router;
63
+ };
64
+
65
+ // Register the router instance for type safety
66
+ declare module "@tanstack/react-router" {
67
+ interface Register {
68
+ router: ReturnType<typeof createRouter>;
69
+ }
70
+ }
@@ -0,0 +1,68 @@
1
+ import { Toaster } from "@/components/ui/sonner";
2
+ import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
3
+ import {
4
+ HeadContent,
5
+ Outlet,
6
+ Scripts,
7
+ createRootRouteWithContext,
8
+ useRouterState,
9
+ } from "@tanstack/react-router";
10
+ import { TanStackRouterDevtools } from "@tanstack/react-router-devtools";
11
+ import Header from "../components/header";
12
+ import appCss from "../index.css?url";
13
+ import type { QueryClient } from "@tanstack/react-query";
14
+ import type { TRPCOptionsProxy } from "@trpc/tanstack-react-query";
15
+ import type { AppRouter } from "../../../server/src/routers";
16
+ import Loader from "@/components/loader";
17
+
18
+ export interface RouterAppContext {
19
+ trpc: TRPCOptionsProxy<AppRouter>;
20
+ queryClient: QueryClient;
21
+ }
22
+
23
+ export const Route = createRootRouteWithContext<RouterAppContext>()({
24
+ head: () => ({
25
+ meta: [
26
+ {
27
+ charSet: "utf-8",
28
+ },
29
+ {
30
+ name: "viewport",
31
+ content: "width=device-width, initial-scale=1",
32
+ },
33
+ {
34
+ title: "My App",
35
+ },
36
+ ],
37
+ links: [
38
+ {
39
+ rel: "stylesheet",
40
+ href: appCss,
41
+ },
42
+ ],
43
+ }),
44
+
45
+ component: RootDocument,
46
+ });
47
+
48
+ function RootDocument() {
49
+ const isFetching = useRouterState({ select: (s) => s.isLoading });
50
+
51
+ return (
52
+ <html lang="en" className="dark">
53
+ <head>
54
+ <HeadContent />
55
+ </head>
56
+ <body>
57
+ <div className="grid h-svh grid-rows-[auto_1fr]">
58
+ <Header />
59
+ {isFetching ? <Loader /> : <Outlet />}
60
+ </div>
61
+ <Toaster richColors />
62
+ <TanStackRouterDevtools position="bottom-left" />
63
+ <ReactQueryDevtools position="bottom" buttonPosition="bottom-right" />
64
+ <Scripts />
65
+ </body>
66
+ </html>
67
+ );
68
+ }
@@ -0,0 +1,88 @@
1
+ import { useTRPC } from "@/utils/trpc";
2
+ import { useQuery } from "@tanstack/react-query";
3
+ import { createFileRoute } from "@tanstack/react-router";
4
+
5
+ export const Route = createFileRoute("/")({
6
+ component: HomeComponent,
7
+ });
8
+
9
+ const TITLE_TEXT = `
10
+ ██████╗ ███████╗████████╗████████╗███████╗██████╗
11
+ ██╔══██╗██╔════╝╚══██╔══╝╚══██╔══╝██╔════╝██╔══██╗
12
+ ██████╔╝█████╗ ██║ ██║ █████╗ ██████╔╝
13
+ ██╔══██╗██╔══╝ ██║ ██║ ██╔══╝ ██╔══██╗
14
+ ██████╔╝███████╗ ██║ ██║ ███████╗██║ ██║
15
+ ╚═════╝ ╚══════╝ ╚═╝ ╚═╝ ╚══════╝╚═╝ ╚═╝
16
+
17
+ ████████╗ ███████╗████████╗ █████╗ ██████╗██╗ ██╗
18
+ ╚══██╔══╝ ██╔════╝╚══██╔══╝██╔══██╗██╔════╝██║ ██╔╝
19
+ ██║ ███████╗ ██║ ███████║██║ █████╔╝
20
+ ██║ ╚════██║ ██║ ██╔══██║██║ ██╔═██╗
21
+ ██║ ███████║ ██║ ██║ ██║╚██████╗██║ ██╗
22
+ ╚═╝ ╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝
23
+ `;
24
+
25
+ function HomeComponent() {
26
+ const trpc = useTRPC();
27
+ const healthCheck = useQuery(trpc.healthCheck.queryOptions());
28
+
29
+ return (
30
+ <div className="container mx-auto max-w-3xl px-4 py-2">
31
+ <pre className="overflow-x-auto font-mono text-sm">{TITLE_TEXT}</pre>
32
+ <div className="grid gap-6">
33
+ <section className="rounded-lg border p-4">
34
+ <h2 className="mb-2 font-medium">API Status</h2>
35
+ <div className="flex items-center gap-2">
36
+ <div
37
+ className={`h-2 w-2 rounded-full ${healthCheck.data ? "bg-green-500" : "bg-red-500"}`}
38
+ />
39
+ <span className="text-muted-foreground text-sm">
40
+ {healthCheck.isLoading
41
+ ? "Checking..."
42
+ : healthCheck.data
43
+ ? "Connected"
44
+ : "Disconnected"}
45
+ </span>
46
+ </div>
47
+ </section>
48
+
49
+ <section>
50
+ <h2 className="mb-3 font-medium">Core Features</h2>
51
+ <ul className="grid grid-cols-2 gap-3">
52
+ <FeatureItem
53
+ title="Type-Safe API"
54
+ description="End-to-end type safety with tRPC"
55
+ />
56
+ <FeatureItem
57
+ title="Modern React"
58
+ description="TanStack Router + TanStack Query"
59
+ />
60
+ <FeatureItem
61
+ title="Fast Backend"
62
+ description="Lightweight Hono server"
63
+ />
64
+ <FeatureItem
65
+ title="Beautiful UI"
66
+ description="TailwindCSS + shadcn/ui components"
67
+ />
68
+ </ul>
69
+ </section>
70
+ </div>
71
+ </div>
72
+ );
73
+ }
74
+
75
+ function FeatureItem({
76
+ title,
77
+ description,
78
+ }: {
79
+ title: string;
80
+ description: string;
81
+ }) {
82
+ return (
83
+ <li className="border-primary border-l-2 py-1 pl-3">
84
+ <h3 className="font-medium">{title}</h3>
85
+ <p className="text-muted-foreground text-sm">{description}</p>
86
+ </li>
87
+ );
88
+ }
@@ -0,0 +1,12 @@
1
+ import { getRouterManifest } from "@tanstack/react-start/router-manifest";
2
+ import {
3
+ createStartHandler,
4
+ defaultStreamHandler,
5
+ } from "@tanstack/react-start/server";
6
+
7
+ import { createRouter } from "./router";
8
+
9
+ export default createStartHandler({
10
+ createRouter,
11
+ getRouterManifest,
12
+ })(defaultStreamHandler);
@@ -0,0 +1,5 @@
1
+ import { createTRPCContext } from "@trpc/tanstack-react-query";
2
+ import type { AppRouter } from "../../../server/src/routers";
3
+
4
+ export const { TRPCProvider, useTRPC, useTRPCClient } =
5
+ createTRPCContext<AppRouter>();
@@ -0,0 +1,28 @@
1
+ {
2
+ "include": ["**/*.ts", "**/*.tsx"],
3
+ "compilerOptions": {
4
+ "target": "ES2022",
5
+ "jsx": "react-jsx",
6
+ "module": "ESNext",
7
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
8
+ "types": ["vite/client"],
9
+
10
+ /* Bundler mode */
11
+ "moduleResolution": "bundler",
12
+ "allowImportingTsExtensions": true,
13
+ "verbatimModuleSyntax": true,
14
+ "noEmit": true,
15
+
16
+ /* Linting */
17
+ "skipLibCheck": true,
18
+ "strict": true,
19
+ "noUnusedLocals": true,
20
+ "noUnusedParameters": true,
21
+ "noFallthroughCasesInSwitch": true,
22
+ "noUncheckedSideEffectImports": true,
23
+ "baseUrl": ".",
24
+ "paths": {
25
+ "@/*": ["./src/*"]
26
+ }
27
+ }
28
+ }