From 0a4f45918897590e3f6600c4452c26e37e920b4a Mon Sep 17 00:00:00 2001 From: Noa Virellia Date: Fri, 2 Jan 2026 15:58:17 +0800 Subject: [PATCH] feat(client): profile-wip Signed-off-by: Noa Virellia --- client/bun.lock | 26 +++++ client/package.json | 4 + client/src/components/checkin/qr-dialog.tsx | 24 ++-- client/src/components/hoc/with-fallback.tsx | 2 +- client/src/components/profile/form.tsx | 104 ++++++++++++++++++ .../src/components/profile/main-profile.tsx | 34 ++++++ .../components/{ => sidebar}/app-sidebar.tsx | 36 +----- .../src/components/{ => sidebar}/nav-main.tsx | 0 .../{ => sidebar}/nav-secondary.tsx | 19 ++-- .../src/components/{ => sidebar}/nav-user.tsx | 4 +- client/src/components/site-header.tsx | 13 ++- client/src/components/ui/field.tsx | 2 +- client/src/hooks/data/useUserInfo.ts | 1 + client/src/lib/navData.ts | 21 ++++ client/src/routeTree.gen.ts | 23 +++- client/src/routes/_sidebarLayout.tsx | 2 +- client/src/routes/_sidebarLayout/profile.tsx | 14 +++ client/vite.config.ts | 2 +- 18 files changed, 272 insertions(+), 59 deletions(-) create mode 100644 client/src/components/profile/form.tsx create mode 100644 client/src/components/profile/main-profile.tsx rename client/src/components/{ => sidebar}/app-sidebar.tsx (62%) rename client/src/components/{ => sidebar}/nav-main.tsx (100%) rename client/src/components/{ => sidebar}/nav-secondary.tsx (63%) rename client/src/components/{ => sidebar}/nav-user.tsx (97%) create mode 100644 client/src/lib/navData.ts create mode 100644 client/src/routes/_sidebarLayout/profile.tsx diff --git a/client/bun.lock b/client/bun.lock index 694b39a..d1536c3 100644 --- a/client/bun.lock +++ b/client/bun.lock @@ -9,6 +9,7 @@ "@dnd-kit/modifiers": "^9.0.0", "@dnd-kit/sortable": "^10.0.0", "@dnd-kit/utilities": "^3.2.2", + "@hookform/resolvers": "^5.2.2", "@marsidev/react-turnstile": "^1.4.0", "@radix-ui/react-avatar": "^1.1.11", "@radix-ui/react-checkbox": "^1.3.3", @@ -24,11 +25,13 @@ "@radix-ui/react-tooltip": "^1.2.8", "@tabler/icons-react": "^3.36.0", "@tailwindcss/vite": "^4.1.18", + "@tanstack/react-form": "^1.27.7", "@tanstack/react-query": "^5.90.12", "@tanstack/react-router": "^1.141.6", "@tanstack/react-router-devtools": "^1.141.6", "@tanstack/react-table": "^8.21.3", "@tanstack/zod-adapter": "^1.143.4", + "@tanstack/zod-form-adapter": "^0.42.1", "axios": "^1.13.2", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", @@ -39,6 +42,7 @@ "qrcode": "^1.5.4", "react": "^19.2.0", "react-dom": "^19.2.0", + "react-hook-form": "^7.69.0", "recharts": "2.15.4", "sonner": "^2.0.7", "tailwind-merge": "^3.4.0", @@ -254,6 +258,8 @@ "@floating-ui/utils": ["@floating-ui/utils@0.2.10", "", {}, "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ=="], + "@hookform/resolvers": ["@hookform/resolvers@5.2.2", "", { "dependencies": { "@standard-schema/utils": "^0.3.0" }, "peerDependencies": { "react-hook-form": "^7.55.0" } }, "sha512-A/IxlMLShx3KjV/HeTcTfaMxdwy690+L/ZADoeaTltLx+CVuzkeVIPuybK3jrRfw7YZnmdKsVVHAlEPIAEUNlA=="], + "@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="], "@humanfs/node": ["@humanfs/node@0.16.7", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.4.0" } }, "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ=="], @@ -406,6 +412,8 @@ "@sindresorhus/base62": ["@sindresorhus/base62@1.0.0", "", {}, "sha512-TeheYy0ILzBEI/CO55CP6zJCSdSWeRtGnHy8U8dWSUH4I68iqTsy7HkMktR4xakThc9jotkPQUXT4ITdbV7cHA=="], + "@standard-schema/utils": ["@standard-schema/utils@0.3.0", "", {}, "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g=="], + "@stylistic/eslint-plugin": ["@stylistic/eslint-plugin@5.6.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.0", "@typescript-eslint/types": "^8.47.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "estraverse": "^5.3.0", "picomatch": "^4.0.3" }, "peerDependencies": { "eslint": ">=9.0.0" } }, "sha512-JCs+MqoXfXrRPGbGmho/zGS/jMcn3ieKl/A8YImqib76C8kjgZwq5uUFzc30lJkMvcchuRn6/v8IApLxli3Jyw=="], "@svgr/babel-plugin-add-jsx-attribute": ["@svgr/babel-plugin-add-jsx-attribute@8.0.0", "", { "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g=="], @@ -466,12 +474,20 @@ "@tailwindcss/vite": ["@tailwindcss/vite@4.1.18", "", { "dependencies": { "@tailwindcss/node": "4.1.18", "@tailwindcss/oxide": "4.1.18", "tailwindcss": "4.1.18" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7" } }, "sha512-jVA+/UpKL1vRLg6Hkao5jldawNmRo7mQYrZtNHMIVpLfLhDml5nMRUo/8MwoX2vNXvnaXNNMedrMfMugAVX1nA=="], + "@tanstack/devtools-event-client": ["@tanstack/devtools-event-client@0.4.0", "", {}, "sha512-RPfGuk2bDZgcu9bAJodvO2lnZeHuz4/71HjZ0bGb/SPg8+lyTA+RLSKQvo7fSmPSi8/vcH3aKQ8EM9ywf1olaw=="], + "@tanstack/eslint-plugin-query": ["@tanstack/eslint-plugin-query@5.91.2", "", { "dependencies": { "@typescript-eslint/utils": "^8.44.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0" } }, "sha512-UPeWKl/Acu1IuuHJlsN+eITUHqAaa9/04geHHPedY8siVarSaWprY0SVMKrkpKfk5ehRT7+/MZ5QwWuEtkWrFw=="], + "@tanstack/form-core": ["@tanstack/form-core@1.27.7", "", { "dependencies": { "@tanstack/devtools-event-client": "^0.4.0", "@tanstack/pacer-lite": "^0.1.1", "@tanstack/store": "^0.7.7" } }, "sha512-nvogpyE98fhb0NDw1Bf2YaCH+L7ZIUgEpqO9TkHucDn6zg3ni521boUpv0i8HKIrmmFwDYjWZoCnrgY4HYWTkw=="], + "@tanstack/history": ["@tanstack/history@1.141.0", "", {}, "sha512-LS54XNyxyTs5m/pl1lkwlg7uZM3lvsv2FIIV1rsJgnfwVCnI+n4ZGZ2CcjNT13BPu/3hPP+iHmliBSscJxW5FQ=="], + "@tanstack/pacer-lite": ["@tanstack/pacer-lite@0.1.1", "", {}, "sha512-y/xtNPNt/YeyoVxE/JCx+T7yjEzpezmbb+toK8DDD1P4m7Kzs5YR956+7OKexG3f8aXgC3rLZl7b1V+yNUSy5w=="], + "@tanstack/query-core": ["@tanstack/query-core@5.90.12", "", {}, "sha512-T1/8t5DhV/SisWjDnaiU2drl6ySvsHj1bHBCWNXd+/T+Hh1cf6JodyEYMd5sgwm+b/mETT4EV3H+zCVczCU5hg=="], + "@tanstack/react-form": ["@tanstack/react-form@1.27.7", "", { "dependencies": { "@tanstack/form-core": "1.27.7", "@tanstack/react-store": "^0.8.0" }, "peerDependencies": { "react": "^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-xTg4qrUY0fuLaSnkATLZcK3BWlnwLp7IuAb6UTbZKngiDEvvDCNTvVvHgPlgef1O2qN4klZxInRyRY6oEkXZ2A=="], + "@tanstack/react-query": ["@tanstack/react-query@5.90.12", "", { "dependencies": { "@tanstack/query-core": "5.90.12" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-graRZspg7EoEaw0a8faiUASCyJrqjKPdqJ9EwuDRUF9mEYJ1YPczI9H+/agJ0mOJkPCJDk0lsz5QTrLZ/jQ2rg=="], "@tanstack/react-router": ["@tanstack/react-router@1.141.6", "", { "dependencies": { "@tanstack/history": "1.141.0", "@tanstack/react-store": "^0.8.0", "@tanstack/router-core": "1.141.6", "isbot": "^5.1.22", "tiny-invariant": "^1.3.3", "tiny-warning": "^1.0.3" }, "peerDependencies": { "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0" } }, "sha512-qWFxi2D6eGc1L03RzUuhyEOplZ7Q6q62YOl7Of9Y0q4YjwQwxRm4zxwDVtvUIoy4RLVCpqp5UoE+Nxv2PY9trg=="], @@ -500,6 +516,8 @@ "@tanstack/zod-adapter": ["@tanstack/zod-adapter@1.143.4", "", { "peerDependencies": { "@tanstack/react-router": ">=1.43.2", "zod": "^3.23.8" } }, "sha512-yrdxNCKPaMjIXM5ZFf3jWNtGlOEZWh2nPdN5NQagkOrYK/l87SZRASB/vFerBXupXPaXvEL8C0qzb894trHW5w=="], + "@tanstack/zod-form-adapter": ["@tanstack/zod-form-adapter@0.42.1", "", { "dependencies": { "@tanstack/form-core": "0.42.1" }, "peerDependencies": { "zod": "^3.x" } }, "sha512-hPRM0lawVKP64yurW4c6KHZH6altMo2MQN14hfi+GMBTKjO9S7bW1x5LPZ5cayoJE3mBvdlahpSGT5rYZtSbXQ=="], + "@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="], "@types/babel__generator": ["@types/babel__generator@7.27.0", "", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg=="], @@ -1238,6 +1256,8 @@ "react-dom": ["react-dom@19.2.3", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.3" } }, "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg=="], + "react-hook-form": ["react-hook-form@7.69.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17 || ^18 || ^19" } }, "sha512-yt6ZGME9f4F6WHwevrvpAjh42HMvocuSnSIHUGycBqXIJdhqGSPQzTpGF+1NLREk/58IdPxEMfPcFCjlMhclGw=="], + "react-is": ["react-is@18.3.1", "", {}, "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="], "react-refresh": ["react-refresh@0.18.0", "", {}, "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw=="], @@ -1540,10 +1560,14 @@ "@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + "@tanstack/form-core/@tanstack/store": ["@tanstack/store@0.7.7", "", {}, "sha512-xa6pTan1bcaqYDS9BDpSiS63qa6EoDkPN9RsRaxHuDdVDNntzq3xNwR5YKTU/V3SkSyC9T4YVOPh2zRQN0nhIQ=="], + "@tanstack/router-generator/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], "@tanstack/router-plugin/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], + "@tanstack/zod-form-adapter/@tanstack/form-core": ["@tanstack/form-core@0.42.1", "", { "dependencies": { "@tanstack/store": "^0.7.0" } }, "sha512-jTU0jyHqFceujdtPNv3jPVej1dTqBwa8TYdIyWB5BCwRVUBZEp1PiYEBkC9r92xu5fMpBiKc+JKud3eeVjuMiA=="], + "@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], "@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], @@ -1634,6 +1658,8 @@ "@radix-ui/react-visually-hidden/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], + "@tanstack/zod-form-adapter/@tanstack/form-core/@tanstack/store": ["@tanstack/store@0.7.7", "", {}, "sha512-xa6pTan1bcaqYDS9BDpSiS63qa6EoDkPN9RsRaxHuDdVDNntzq3xNwR5YKTU/V3SkSyC9T4YVOPh2zRQN0nhIQ=="], + "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], "cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], diff --git a/client/package.json b/client/package.json index 93048b6..8607862 100644 --- a/client/package.json +++ b/client/package.json @@ -14,6 +14,7 @@ "@dnd-kit/modifiers": "^9.0.0", "@dnd-kit/sortable": "^10.0.0", "@dnd-kit/utilities": "^3.2.2", + "@hookform/resolvers": "^5.2.2", "@marsidev/react-turnstile": "^1.4.0", "@radix-ui/react-avatar": "^1.1.11", "@radix-ui/react-checkbox": "^1.3.3", @@ -29,11 +30,13 @@ "@radix-ui/react-tooltip": "^1.2.8", "@tabler/icons-react": "^3.36.0", "@tailwindcss/vite": "^4.1.18", + "@tanstack/react-form": "^1.27.7", "@tanstack/react-query": "^5.90.12", "@tanstack/react-router": "^1.141.6", "@tanstack/react-router-devtools": "^1.141.6", "@tanstack/react-table": "^8.21.3", "@tanstack/zod-adapter": "^1.143.4", + "@tanstack/zod-form-adapter": "^0.42.1", "axios": "^1.13.2", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", @@ -44,6 +47,7 @@ "qrcode": "^1.5.4", "react": "^19.2.0", "react-dom": "^19.2.0", + "react-hook-form": "^7.69.0", "recharts": "2.15.4", "sonner": "^2.0.7", "tailwind-merge": "^3.4.0", diff --git a/client/src/components/checkin/qr-dialog.tsx b/client/src/components/checkin/qr-dialog.tsx index dd6af35..f4bb318 100644 --- a/client/src/components/checkin/qr-dialog.tsx +++ b/client/src/components/checkin/qr-dialog.tsx @@ -38,20 +38,20 @@ function QrSection({ eventId, enabled }: { eventId: string; enabled: boolean }) const { data } = useCheckinCode(eventId, enabled); return data ? ( - <> -
- -
- -
- {data.data.checkin_code} + <> +
+
- - - ) + +
+ {data.data.checkin_code} +
+
+ + ) : ( - - ); + + ); } function QrSectionSkeleton() { diff --git a/client/src/components/hoc/with-fallback.tsx b/client/src/components/hoc/with-fallback.tsx index e4e8679..5d4c0e6 100644 --- a/client/src/components/hoc/with-fallback.tsx +++ b/client/src/components/hoc/with-fallback.tsx @@ -14,7 +14,7 @@ export function withFallback

( }; Wrapped.displayName = `withFallback(${Component.displayName! || Component.name || 'Component' - })`; + })`; return Wrapped; } diff --git a/client/src/components/profile/form.tsx b/client/src/components/profile/form.tsx new file mode 100644 index 0000000..e312468 --- /dev/null +++ b/client/src/components/profile/form.tsx @@ -0,0 +1,104 @@ +import { + useForm, +} from '@tanstack/react-form'; +import { + toast, +} from 'sonner'; +import { + z, +} from 'zod'; +import { + Button, +} from '@/components/ui/button'; +import { + Field, + FieldError, + FieldLabel, +} from '@/components/ui/field'; +import { + Input, +} from '@/components/ui/input'; + +const formSchema = z.object({ + email: z.string(), + nickname: z.string().min(1), + subtitle: z.string().min(1), +}); + +export default function SettingsForm() { + const form = useForm({ + defaultValues: { + email: '', + nickname: '', + subtitle: '', + }, + validators: { + onBlur: formSchema, + }, + onSubmit: async ({ + value, + }) => { + try { + toast( + {JSON.stringify(value, null, 2)}, + ); + } + catch (error) { + console.error('Form submission error', error); + toast.error('Failed to submit the form. Please try again.'); + } + }, + }); + + return ( +

{ + e.preventDefault(); + e.stopPropagation(); + void form.handleSubmit(); + }} + className="space-y-3 max-w-5xl mr-auto py-10" + > + + Email + form.setFieldValue('email', e.target.value)} + /> + + + + + 昵称 + form.setFieldValue('nickname', e.target.value)} + /> + + + + + 副标题 + form.setFieldValue('subtitle', e.target.value)} + /> + + + + +
+ ); +} diff --git a/client/src/components/profile/main-profile.tsx b/client/src/components/profile/main-profile.tsx new file mode 100644 index 0000000..02e5e1d --- /dev/null +++ b/client/src/components/profile/main-profile.tsx @@ -0,0 +1,34 @@ +import { Mail } from 'lucide-react'; +import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; +import { Button } from '@/components/ui/button'; +import { useUserInfo } from '@/hooks/data/useUserInfo'; + +export function MainProfile() { + const { data: user } = useUserInfo(); + return ( +
+
+ + + CN + +
+ + +
+
+ +
+
+ + {user.email} +
+
+
+ {/* Bio */} +
+
+ ); +} diff --git a/client/src/components/app-sidebar.tsx b/client/src/components/sidebar/app-sidebar.tsx similarity index 62% rename from client/src/components/app-sidebar.tsx rename to client/src/components/sidebar/app-sidebar.tsx index 801d55c..0d1a945 100644 --- a/client/src/components/app-sidebar.tsx +++ b/client/src/components/sidebar/app-sidebar.tsx @@ -1,12 +1,7 @@ -import { - IconDashboard, - IconSettings, -} from '@tabler/icons-react'; import * as React from 'react'; - import NixOSLogo from '@/assets/nixos.svg?react'; -import { NavMain } from '@/components/nav-main'; -import { NavSecondary } from '@/components/nav-secondary'; +import { NavMain } from '@/components/sidebar/nav-main'; +import { NavSecondary } from '@/components/sidebar/nav-secondary'; import { Sidebar, SidebarContent, @@ -16,30 +11,9 @@ import { SidebarMenuButton, SidebarMenuItem, } from '@/components/ui/sidebar'; +import { navData } from '@/lib/navData'; import { NavUser } from './nav-user'; -const data = { - user: { - name: 'shadcn', - email: 'm@example.com', - avatar: '/avatars/shadcn.jpg', - }, - navMain: [ - { - title: '工作台', - url: '/', - icon: IconDashboard, - }, - ], - navSecondary: [ - { - title: '设置', - url: '#', - icon: IconSettings, - }, - ], -}; - export function AppSidebar({ ...props }: React.ComponentProps) { return ( @@ -59,8 +33,8 @@ export function AppSidebar({ ...props }: React.ComponentProps) { - - + + diff --git a/client/src/components/nav-main.tsx b/client/src/components/sidebar/nav-main.tsx similarity index 100% rename from client/src/components/nav-main.tsx rename to client/src/components/sidebar/nav-main.tsx diff --git a/client/src/components/nav-secondary.tsx b/client/src/components/sidebar/nav-secondary.tsx similarity index 63% rename from client/src/components/nav-secondary.tsx rename to client/src/components/sidebar/nav-secondary.tsx index e9cf33b..b0da89c 100644 --- a/client/src/components/nav-secondary.tsx +++ b/client/src/components/sidebar/nav-secondary.tsx @@ -1,8 +1,9 @@ 'use client'; import type { Icon } from '@tabler/icons-react'; -import * as React from 'react'; +import { Link } from '@tanstack/react-router'; +import * as React from 'react'; import { SidebarGroup, SidebarGroupContent, @@ -27,12 +28,16 @@ export function NavSecondary({ {items.map(item => ( - - - - {item.title} - - + + {({ isActive }) => { + return ( + + + {item.title} + + ); + }} + ))} diff --git a/client/src/components/nav-user.tsx b/client/src/components/sidebar/nav-user.tsx similarity index 97% rename from client/src/components/nav-user.tsx rename to client/src/components/sidebar/nav-user.tsx index 9f6c5dd..8356344 100644 --- a/client/src/components/nav-user.tsx +++ b/client/src/components/sidebar/nav-user.tsx @@ -24,8 +24,8 @@ import { } from '@/components/ui/sidebar'; import { useUserInfo } from '@/hooks/data/useUserInfo'; import { useLogout } from '@/hooks/useLogout'; -import { withFallback } from './hoc/with-fallback'; -import { Skeleton } from './ui/skeleton'; +import { withFallback } from '../hoc/with-fallback'; +import { Skeleton } from '../ui/skeleton'; function NavUser_() { const { isMobile } = useSidebar(); diff --git a/client/src/components/site-header.tsx b/client/src/components/site-header.tsx index 96ead56..9ecc44b 100644 --- a/client/src/components/site-header.tsx +++ b/client/src/components/site-header.tsx @@ -1,7 +1,18 @@ +import { useRouterState } from '@tanstack/react-router'; import { Separator } from '@/components/ui/separator'; import { SidebarTrigger } from '@/components/ui/sidebar'; +import { navData } from '@/lib/navData'; export function SiteHeader() { + const pathname = useRouterState({ select: state => state.location.pathname }); + const allNavItems = [...navData.navMain, ...navData.navSecondary]; + const currentTitle + = allNavItems.find(item => + item.url === '/' + ? pathname === '/' + : pathname.startsWith(item.url), + )?.title ?? '工作台'; + return (
@@ -10,7 +21,7 @@ export function SiteHeader() { orientation="vertical" className="mx-2 data-[orientation=vertical]:h-4" /> -

工作台

+

{currentTitle}

); diff --git a/client/src/components/ui/field.tsx b/client/src/components/ui/field.tsx index 5678a47..9392f6b 100644 --- a/client/src/components/ui/field.tsx +++ b/client/src/components/ui/field.tsx @@ -190,7 +190,7 @@ function FieldError({ }: React.ComponentProps<'div'> & { errors?: Array<{ message?: string } | undefined>; }) { - const content = useMemo(async () => { + const content = useMemo(() => { if (children) { return children; } diff --git a/client/src/hooks/data/useUserInfo.ts b/client/src/hooks/data/useUserInfo.ts index b688e7c..c45e453 100644 --- a/client/src/hooks/data/useUserInfo.ts +++ b/client/src/hooks/data/useUserInfo.ts @@ -17,5 +17,6 @@ export function useUserInfo() { >('/user/info'); return response.data; }, + staleTime: 10 * 60 * 1000, }); } diff --git a/client/src/lib/navData.ts b/client/src/lib/navData.ts new file mode 100644 index 0000000..7266d47 --- /dev/null +++ b/client/src/lib/navData.ts @@ -0,0 +1,21 @@ +import { + IconDashboard, + IconUser, +} from '@tabler/icons-react'; + +export const navData = { + navMain: [ + { + title: '工作台', + url: '/', + icon: IconDashboard, + }, + ], + navSecondary: [ + { + title: '个人资料', + url: '/profile', + icon: IconUser, + }, + ], +}; diff --git a/client/src/routeTree.gen.ts b/client/src/routeTree.gen.ts index fa081d6..dcada6a 100644 --- a/client/src/routeTree.gen.ts +++ b/client/src/routeTree.gen.ts @@ -13,6 +13,7 @@ import { Route as MagicLinkSentRouteImport } from './routes/magicLinkSent' import { Route as LoginRouteImport } from './routes/login' import { Route as SidebarLayoutRouteImport } from './routes/_sidebarLayout' import { Route as SidebarLayoutIndexRouteImport } from './routes/_sidebarLayout/index' +import { Route as SidebarLayoutProfileRouteImport } from './routes/_sidebarLayout/profile' const MagicLinkSentRoute = MagicLinkSentRouteImport.update({ id: '/magicLinkSent', @@ -33,15 +34,22 @@ const SidebarLayoutIndexRoute = SidebarLayoutIndexRouteImport.update({ path: '/', getParentRoute: () => SidebarLayoutRoute, } as any) +const SidebarLayoutProfileRoute = SidebarLayoutProfileRouteImport.update({ + id: '/profile', + path: '/profile', + getParentRoute: () => SidebarLayoutRoute, +} as any) export interface FileRoutesByFullPath { '/login': typeof LoginRoute '/magicLinkSent': typeof MagicLinkSentRoute + '/profile': typeof SidebarLayoutProfileRoute '/': typeof SidebarLayoutIndexRoute } export interface FileRoutesByTo { '/login': typeof LoginRoute '/magicLinkSent': typeof MagicLinkSentRoute + '/profile': typeof SidebarLayoutProfileRoute '/': typeof SidebarLayoutIndexRoute } export interface FileRoutesById { @@ -49,18 +57,20 @@ export interface FileRoutesById { '/_sidebarLayout': typeof SidebarLayoutRouteWithChildren '/login': typeof LoginRoute '/magicLinkSent': typeof MagicLinkSentRoute + '/_sidebarLayout/profile': typeof SidebarLayoutProfileRoute '/_sidebarLayout/': typeof SidebarLayoutIndexRoute } export interface FileRouteTypes { fileRoutesByFullPath: FileRoutesByFullPath - fullPaths: '/login' | '/magicLinkSent' | '/' + fullPaths: '/login' | '/magicLinkSent' | '/profile' | '/' fileRoutesByTo: FileRoutesByTo - to: '/login' | '/magicLinkSent' | '/' + to: '/login' | '/magicLinkSent' | '/profile' | '/' id: | '__root__' | '/_sidebarLayout' | '/login' | '/magicLinkSent' + | '/_sidebarLayout/profile' | '/_sidebarLayout/' fileRoutesById: FileRoutesById } @@ -100,14 +110,23 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof SidebarLayoutIndexRouteImport parentRoute: typeof SidebarLayoutRoute } + '/_sidebarLayout/profile': { + id: '/_sidebarLayout/profile' + path: '/profile' + fullPath: '/profile' + preLoaderRoute: typeof SidebarLayoutProfileRouteImport + parentRoute: typeof SidebarLayoutRoute + } } } interface SidebarLayoutRouteChildren { + SidebarLayoutProfileRoute: typeof SidebarLayoutProfileRoute SidebarLayoutIndexRoute: typeof SidebarLayoutIndexRoute } const SidebarLayoutRouteChildren: SidebarLayoutRouteChildren = { + SidebarLayoutProfileRoute: SidebarLayoutProfileRoute, SidebarLayoutIndexRoute: SidebarLayoutIndexRoute, } diff --git a/client/src/routes/_sidebarLayout.tsx b/client/src/routes/_sidebarLayout.tsx index 5ab31c7..e282a5e 100644 --- a/client/src/routes/_sidebarLayout.tsx +++ b/client/src/routes/_sidebarLayout.tsx @@ -1,5 +1,5 @@ import { createFileRoute, Outlet } from '@tanstack/react-router'; -import { AppSidebar } from '@/components/app-sidebar'; +import { AppSidebar } from '@/components/sidebar/app-sidebar'; import { SiteHeader } from '@/components/site-header'; import { SidebarInset, SidebarProvider } from '@/components/ui/sidebar'; diff --git a/client/src/routes/_sidebarLayout/profile.tsx b/client/src/routes/_sidebarLayout/profile.tsx new file mode 100644 index 0000000..f28284e --- /dev/null +++ b/client/src/routes/_sidebarLayout/profile.tsx @@ -0,0 +1,14 @@ +import { createFileRoute } from '@tanstack/react-router'; +import { MainProfile } from '@/components/profile/main-profile'; + +export const Route = createFileRoute('/_sidebarLayout/profile')({ + component: RouteComponent, +}); + +function RouteComponent() { + return ( +
+ +
+ ); +} diff --git a/client/vite.config.ts b/client/vite.config.ts index f52b72c..71ce399 100644 --- a/client/vite.config.ts +++ b/client/vite.config.ts @@ -23,7 +23,7 @@ export default defineConfig({ }, server: { proxy: { - '/api': 'http://10.0.0.10:8000', + '/api': 'http://10.0.0.250:8000', }, host: '0.0.0.0', port: 5173,