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.
- package/README.md +3 -1
- package/dist/index.js +106 -105
- package/package.json +2 -2
- package/template/base/apps/web-base/src/index.css +1 -1
- package/template/base/apps/web-tanstack-router/src/components/mode-toggle.tsx +37 -0
- package/template/base/apps/web-tanstack-router/src/components/theme-provider.tsx +73 -0
- package/template/base/apps/web-tanstack-router/src/routes/__root.tsx +1 -2
- package/template/base/apps/web-tanstack-start/app.config.ts +17 -0
- package/template/base/apps/web-tanstack-start/package.json +52 -0
- package/template/base/apps/web-tanstack-start/public/robots.txt +3 -0
- package/template/base/apps/web-tanstack-start/src/api.ts +6 -0
- package/template/base/apps/web-tanstack-start/src/client.tsx +8 -0
- package/template/base/apps/web-tanstack-start/src/components/header.tsx +27 -0
- package/template/base/apps/web-tanstack-start/src/index.css +135 -0
- package/template/base/apps/web-tanstack-start/src/router.tsx +70 -0
- package/template/base/apps/web-tanstack-start/src/routes/__root.tsx +68 -0
- package/template/base/apps/web-tanstack-start/src/routes/index.tsx +88 -0
- package/template/base/apps/web-tanstack-start/src/ssr.tsx +12 -0
- package/template/base/apps/web-tanstack-start/src/utils/trpc.ts +5 -0
- package/template/base/apps/web-tanstack-start/tsconfig.json +28 -0
- package/template/examples/ai/apps/web-tanstack-start/src/routes/ai.tsx +69 -0
- package/template/examples/todo/apps/web-tanstack-start/src/routes/todos.tsx +135 -0
- package/template/with-auth/apps/web-tanstack-router/src/utils/trpc.ts +39 -0
- package/template/with-auth/apps/web-tanstack-start/src/components/header.tsx +32 -0
- package/template/with-auth/apps/web-tanstack-start/src/components/sign-in-form.tsx +139 -0
- package/template/with-auth/apps/web-tanstack-start/src/components/sign-up-form.tsx +164 -0
- package/template/with-auth/apps/web-tanstack-start/src/components/user-menu.tsx +62 -0
- package/template/with-auth/apps/web-tanstack-start/src/router.tsx +76 -0
- package/template/with-auth/apps/web-tanstack-start/src/routes/dashboard.tsx +37 -0
- package/template/with-auth/apps/web-tanstack-start/src/routes/login.tsx +18 -0
- package/template/with-drizzle-sqlite/apps/server/src/db/schema/auth.ts +0 -6
- /package/template/base/apps/{web-base → web-react-router}/src/components/mode-toggle.tsx +0 -0
- /package/template/base/apps/{web-base → web-react-router}/src/components/theme-provider.tsx +0 -0
- /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.
|
|
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/
|
|
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/",
|
|
@@ -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
|
|
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,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,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
|
+
}
|