Skip to content

Commit f5c1154

Browse files
committed
feat: integrate Zustand for state management and session handling
- Added AppStoreProvider and app-store for managing application state. - Implemented initial state fetching with getInitialAppState. - Enhanced theme management with responsive theme application. - Updated AppProviders to include AppStoreProvider. - Introduced useAppStore hooks for accessing state and actions. - Added session management with authentication state. - Improved theme handling with cookie persistence and system preference detection. - Refactored theme utility functions and CSS for better theme support. - Updated router and root component to utilize new state management. - Added TypeScript types for session data and application state.
1 parent 7a31914 commit f5c1154

30 files changed

Lines changed: 961 additions & 104 deletions

.env.development

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
DATABASE_URL=postgres://postgres:postgres@localhost:5432/postgres
22

3-
# Better Auth
4-
BETTER_AUTH_SECRET=developmentauthsecretmin32characetersrequired
3+
SESSION_SECRET=please_change_this_to_a_secure_random_string_with_at_least_32_characters
54

65
# Like: CF_CONNECTING_IP
76
X_FORWARDED_FOR=

.env.example

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
DATABASE_URL=
22

3+
SESSION_SECRET=please_change_this_to_a_secure_random_string_with_at_least_32_characters
4+
35
# Like: CF_CONNECTING_IP
46
X_FORWARDED_FOR=
57

README.md

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -85,17 +85,17 @@ Open [http://localhost:3000](http://localhost:3000) to see the application.
8585

8686
## 📜 Development Commands
8787

88-
| Command | Description |
89-
| ------------------- | -------------------------------------- |
90-
| `bun dev` | Start development server on port 3000 |
91-
| `bun build` | Build the application for production |
92-
| `bun typecheck` | Run TypeScript type checking |
93-
| `bun lint` | Lint code using oxlint |
94-
| `bun format` | Format code using oxfmt |
95-
| `bun test` | Run tests with Vitest |
96-
| `bun db:generate` | Generate Drizzle migrations |
97-
| `bun db:migrate` | Apply Drizzle migrations |
98-
| `bun up` | Update dependencies to latest versions |
88+
| Command | Description |
89+
| ----------------- | -------------------------------------- |
90+
| `bun dev` | Start development server on port 3000 |
91+
| `bun build` | Build the application for production |
92+
| `bun typecheck` | Run TypeScript type checking |
93+
| `bun lint` | Lint code using oxlint |
94+
| `bun format` | Format code using oxfmt |
95+
| `bun test` | Run tests with Vitest |
96+
| `bun db:generate` | Generate Drizzle migrations |
97+
| `bun db:migrate` | Apply Drizzle migrations |
98+
| `bun up` | Update dependencies to latest versions |
9999

100100
## 🔐 Environment Variables
101101

bun.lock

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

conductor.json

Lines changed: 0 additions & 7 deletions
This file was deleted.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@
4545
"sonner": "^2.0.7",
4646
"tailwind-merge": "^3.5.0",
4747
"vaul": "^1.1.2",
48-
"zod": "^4.3.6"
48+
"zod": "^4.3.6",
49+
"zustand": "^5.0.11"
4950
},
5051
"devDependencies": {
5152
"@iconify-json/mingcute": "^1.2.7",

src/components/layouts/footer.tsx

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,26 @@
11
import React from "react";
2+
import { Separator } from "@/components/ui/separator";
23

34
export function Footer() {
45
return (
5-
<footer className="border-t py-6">
6-
<div className="container mx-auto px-4 text-center text-sm text-muted-foreground">
7-
<p>&copy; 2025 React Template. Built with modern stack.</p>
6+
<footer className="border-t border-border/70 bg-background/70">
7+
<div className="mx-auto flex max-w-6xl flex-col gap-4 px-4 py-8 sm:px-6 lg:flex-row lg:items-center lg:justify-between">
8+
<div className="space-y-1">
9+
<p className="font-medium">React Template</p>
10+
<p className="text-sm text-muted-foreground">
11+
为产品首页、后台原型和实验项目准备的现代化起点。
12+
</p>
13+
</div>
14+
15+
<div className="flex flex-wrap items-center gap-3 text-sm text-muted-foreground">
16+
<span>TanStack Start</span>
17+
<Separator orientation="vertical" className="hidden h-4 sm:block" />
18+
<span>shadcn/ui</span>
19+
<Separator orientation="vertical" className="hidden h-4 sm:block" />
20+
<span>Zustand</span>
21+
<Separator orientation="vertical" className="hidden h-4 sm:block" />
22+
<span>2026</span>
23+
</div>
824
</div>
925
</footer>
1026
);

src/components/layouts/header.tsx

Lines changed: 117 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,124 @@
1-
import { useRouteContext } from "@tanstack/react-router";
2-
import { Button } from "@/components/ui/button";
3-
import { setTheme } from "@/core/utils";
1+
import { startTransition } from "react";
2+
import { AiLaptopIcon, ArrowDown01Icon, Moon01Icon, Sun03Icon } from "@hugeicons/core-free-icons";
3+
import { HugeiconsIcon } from "@hugeicons/react";
4+
import { ThemeSchema, type Theme, cn } from "@/core/utils";
5+
import { Badge } from "@/components/ui/badge";
6+
import {
7+
DropdownMenu,
8+
DropdownMenuContent,
9+
DropdownMenuGroup,
10+
DropdownMenuLabel,
11+
DropdownMenuRadioGroup,
12+
DropdownMenuRadioItem,
13+
DropdownMenuSeparator,
14+
DropdownMenuTrigger,
15+
} from "@/components/ui/dropdown-menu";
16+
import { useSetThemeState, useThemeState } from "@/store";
417

5-
export function Header() {
6-
const router = useRouter();
7-
const { theme } = useRouteContext({ from: "__root__" });
18+
const themeOptions = [
19+
{
20+
label: "跟随系统",
21+
value: "system",
22+
description: "自动匹配设备外观",
23+
icon: AiLaptopIcon,
24+
},
25+
{
26+
label: "浅色",
27+
value: "light",
28+
description: "适合白天和演示场景",
29+
icon: Sun03Icon,
30+
},
31+
{
32+
label: "深色",
33+
value: "dark",
34+
description: "适合长时间编码阅读",
35+
icon: Moon01Icon,
36+
},
37+
] as const satisfies ReadonlyArray<{
38+
label: string;
39+
value: Theme;
40+
description: string;
41+
icon: typeof Sun03Icon;
42+
}>;
843

9-
const toggleTheme = useCallback(() => {
10-
setTheme(theme === "dark" ? "light" : "dark");
11-
router.invalidate();
12-
}, [theme, router]);
44+
export function Header() {
45+
const theme = useThemeState();
46+
const setTheme = useSetThemeState();
47+
const currentTheme = themeOptions.find((item) => item.value === theme) ?? themeOptions[0];
1348

1449
return (
15-
<header className="border-b">
16-
<div className="container mx-auto flex h-16 items-center justify-between px-4">
17-
<span className="font-bold text-xl">React Template</span>
18-
<Button variant="outline" size="sm" onClick={toggleTheme}>
19-
{theme === "dark" ? "Light" : "Dark"}
20-
</Button>
50+
<header className="sticky top-0 z-40 border-b border-border/70 bg-background/80 backdrop-blur-xl">
51+
<div className="mx-auto flex h-20 max-w-6xl items-center justify-between px-4 sm:px-6">
52+
<div className="flex items-center gap-4">
53+
<div className="flex size-11 items-center justify-center rounded-2xl border border-border/70 bg-card shadow-lg shadow-black/5 dark:shadow-black/20">
54+
<span className="font-semibold tracking-[0.18em] text-sm">RT</span>
55+
</div>
56+
57+
<div className="space-y-1">
58+
<div className="flex items-center gap-2">
59+
<span className="text-base font-semibold sm:text-lg">React Template</span>
60+
<Badge variant="outline" className="hidden rounded-full px-2.5 sm:inline-flex">
61+
Starter
62+
</Badge>
63+
</div>
64+
<p className="hidden text-sm text-muted-foreground sm:block">
65+
TanStack Start · shadcn/ui · Zustand
66+
</p>
67+
</div>
68+
</div>
69+
70+
<div className="flex items-center gap-2">
71+
<DropdownMenu>
72+
<DropdownMenuTrigger
73+
className={cn(
74+
"ring-foreground/10 bg-background hover:bg-muted inline-flex h-10 items-center gap-2 rounded-full border px-3 text-sm font-medium shadow-sm outline-none transition-colors",
75+
)}
76+
>
77+
<HugeiconsIcon icon={currentTheme.icon} strokeWidth={2} className="size-4" />
78+
<span className="hidden sm:inline">{currentTheme.label}</span>
79+
<HugeiconsIcon
80+
icon={ArrowDown01Icon}
81+
strokeWidth={2}
82+
className="size-4 text-muted-foreground"
83+
/>
84+
</DropdownMenuTrigger>
85+
86+
<DropdownMenuContent align="end" className="w-56">
87+
<DropdownMenuGroup>
88+
<DropdownMenuLabel>主题切换</DropdownMenuLabel>
89+
</DropdownMenuGroup>
90+
<DropdownMenuSeparator />
91+
<DropdownMenuRadioGroup
92+
value={theme}
93+
onValueChange={(value) => {
94+
startTransition(() => {
95+
setTheme(ThemeSchema.parse(value));
96+
});
97+
}}
98+
>
99+
{themeOptions.map((item) => (
100+
<DropdownMenuRadioItem
101+
key={item.value}
102+
value={item.value}
103+
className="flex items-start gap-3 py-2"
104+
>
105+
<HugeiconsIcon
106+
icon={item.icon}
107+
strokeWidth={2}
108+
className="mt-0.5 size-4 text-muted-foreground"
109+
/>
110+
<span className="space-y-0.5">
111+
<span className="block font-medium text-foreground">{item.label}</span>
112+
<span className="block text-xs text-muted-foreground">
113+
{item.description}
114+
</span>
115+
</span>
116+
</DropdownMenuRadioItem>
117+
))}
118+
</DropdownMenuRadioGroup>
119+
</DropdownMenuContent>
120+
</DropdownMenu>
121+
</div>
21122
</div>
22123
</header>
23124
);

src/components/providers/index.tsx

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
11
import React from "react";
22
import { QueryProvider } from "./query-provider";
33
import { AppUIProvider } from "./ui-provider";
4+
import { AppStoreProvider, type AppStoreSnapshot } from "@/store";
45

56
interface AppProvidersProps {
67
children: React.ReactNode;
8+
initialState: AppStoreSnapshot;
79
}
810

9-
export function AppProviders({ children }: AppProvidersProps) {
11+
export function AppProviders({ children, initialState }: AppProvidersProps) {
1012
return (
11-
<QueryProvider>
12-
<AppUIProvider>{children}</AppUIProvider>
13-
</QueryProvider>
13+
<AppStoreProvider initialState={initialState}>
14+
<QueryProvider>
15+
<AppUIProvider>{children}</AppUIProvider>
16+
</QueryProvider>
17+
</AppStoreProvider>
1418
);
1519
}

src/core/env/server-env.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ export const ServerEnvSchema = z.object({
55
// Database
66
DATABASE_URL: z.url(),
77

8+
// Secret
9+
SESSION_SECRET: z.string().min(32),
10+
811
// Reverse Proxy
912
X_FORWARDED_FOR: z.string().optional(),
1013

0 commit comments

Comments
 (0)