From 29888fd1ad254462c8b1dbc556435e7277dc27b4 Mon Sep 17 00:00:00 2001 From: zuiidea Date: Tue, 14 Apr 2026 19:12:48 +0800 Subject: [PATCH] refactor(with-lingui): move theme and session, merge mock utils - Theme hooks: tokenBuilders + useAppTheme under hooks; drop theme barrel - Session fetch helper under utils; update entry/login imports - Mock filter + avatar helpers in mocks/utils; avatars from API in UI - README tree updates Made-with: Cursor --- README.md | 7 +++---- apps/with-lingui/README.md | 7 +++---- apps/with-lingui/src/components/Layout/UserMenu/index.tsx | 4 +--- apps/with-lingui/src/{theme => hooks}/tokenBuilders.ts | 0 apps/with-lingui/src/{theme => hooks}/useAppTheme.ts | 4 ++++ apps/with-lingui/src/main.tsx | 2 +- apps/with-lingui/src/mocks/data.ts | 2 +- apps/with-lingui/src/mocks/handlers/user.ts | 2 +- apps/with-lingui/src/mocks/{filterUtils.ts => utils.ts} | 7 ++++++- apps/with-lingui/src/routes/__root.tsx | 2 +- apps/with-lingui/src/routes/_auth/users/index.tsx | 3 +-- apps/with-lingui/src/routes/login/index.tsx | 2 +- apps/with-lingui/src/theme/index.ts | 5 ----- apps/with-lingui/src/utils/avatarUrl.ts | 4 ---- apps/with-lingui/src/{auth => utils}/session.ts | 0 15 files changed, 23 insertions(+), 28 deletions(-) rename apps/with-lingui/src/{theme => hooks}/tokenBuilders.ts (100%) rename apps/with-lingui/src/{theme => hooks}/useAppTheme.ts (74%) rename apps/with-lingui/src/mocks/{filterUtils.ts => utils.ts} (80%) delete mode 100644 apps/with-lingui/src/theme/index.ts delete mode 100644 apps/with-lingui/src/utils/avatarUrl.ts rename apps/with-lingui/src/{auth => utils}/session.ts (100%) diff --git a/README.md b/README.md index 8c1aa73..39b57ab 100644 --- a/README.md +++ b/README.md @@ -104,6 +104,8 @@ src/ │ ├── UserMenu/ # User dropdown in sidebar: index.tsx + index.css │ └── Header/ # Top bar: index.tsx ├── hooks/ +│ ├── tokenBuilders.ts # Shared Ant Design token/config builders +│ ├── useAppTheme.ts # Theme selection hook (ConfigProvider) │ ├── usePermission.ts # Permission check hook │ └── useResourceCRUD.ts # Shared CRUD query/mutation wiring ├── utils/ @@ -116,7 +118,7 @@ src/ │ ├── browser.ts # MSW worker setup │ ├── createHandler.ts # Shared MSW success/error/delay helpers │ ├── data.ts # Mock seed data (users, menus) -│ ├── filterUtils.ts # Mock-only filtering and pagination helpers +│ ├── utils.ts # Mock-only helpers (filters, pagination, demo avatar URLs) │ └── handlers/ # Request handlers (auth, user CRUD) ├── routes/ # TanStack Router file-based routes │ ├── __root.tsx # Root layout (QueryClient, ConfigProvider, I18n) @@ -131,9 +133,6 @@ src/ │ ├── auth.ts # Auth store (tokens, user, menus, permissions) │ ├── createPersistentStore.ts # Shared persisted-store factory │ └── settings.ts # Settings store (darkMode, locale, sidebar) -├── theme/ -│ ├── tokenBuilders.ts # Shared Ant Design token/config builders -│ └── useAppTheme.ts # Theme selection hook └── main.tsx # Entry point (MSW init → React render) tests/ diff --git a/apps/with-lingui/README.md b/apps/with-lingui/README.md index 99a8fa4..ee2a31e 100644 --- a/apps/with-lingui/README.md +++ b/apps/with-lingui/README.md @@ -104,6 +104,8 @@ src/ │ ├── UserMenu/ # User dropdown in sidebar: index.tsx + index.css │ └── Header/ # Top bar: index.tsx ├── hooks/ +│ ├── tokenBuilders.ts # Shared Ant Design token/config builders +│ ├── useAppTheme.ts # Theme selection hook (ConfigProvider) │ ├── usePermission.ts # Permission check hook │ └── useResourceCRUD.ts # Shared CRUD query/mutation wiring ├── utils/ @@ -116,7 +118,7 @@ src/ │ ├── browser.ts # MSW worker setup │ ├── createHandler.ts # Shared MSW success/error/delay helpers │ ├── data.ts # Mock seed data (users, menus) -│ ├── filterUtils.ts # Mock-only filtering and pagination helpers +│ ├── utils.ts # Mock-only helpers (filters, pagination, demo avatar URLs) │ └── handlers/ # Request handlers (auth, user CRUD) ├── routes/ # TanStack Router file-based routes │ ├── __root.tsx # Root layout (QueryClient, ConfigProvider, I18n) @@ -131,9 +133,6 @@ src/ │ ├── auth.ts # Auth store (tokens, user, menus, permissions) │ ├── createPersistentStore.ts # Shared persisted-store factory │ └── settings.ts # Settings store (darkMode, locale, sidebar) -├── theme/ -│ ├── tokenBuilders.ts # Shared Ant Design token/config builders -│ └── useAppTheme.ts # Theme selection hook └── main.tsx # Entry point (MSW init → React render) tests/ diff --git a/apps/with-lingui/src/components/Layout/UserMenu/index.tsx b/apps/with-lingui/src/components/Layout/UserMenu/index.tsx index ebb2352..040a25c 100644 --- a/apps/with-lingui/src/components/Layout/UserMenu/index.tsx +++ b/apps/with-lingui/src/components/Layout/UserMenu/index.tsx @@ -1,7 +1,6 @@ import { Avatar, Button, Dropdown, Flex, Typography, theme } from "antd"; import type { MenuProps } from "antd"; import type { User } from "@/api/schemas"; -import { vercelAvatarUrl } from "@/utils/avatarUrl"; import { MoreVertical } from "lucide-react"; import "./index.css"; @@ -16,8 +15,7 @@ interface UserMenuProps { export function UserMenu({ collapsed, user, userMenuItems }: UserMenuProps) { const { token } = theme.useToken(); - const avatarSrc = - (user?.avatar ?? "").trim() || (user?.username ? vercelAvatarUrl(user.username) : undefined); + const avatarSrc = (user?.avatar ?? "").trim() || undefined; const avatarSize = collapsed ? 32 : token.controlHeight; const avatar = ( diff --git a/apps/with-lingui/src/theme/tokenBuilders.ts b/apps/with-lingui/src/hooks/tokenBuilders.ts similarity index 100% rename from apps/with-lingui/src/theme/tokenBuilders.ts rename to apps/with-lingui/src/hooks/tokenBuilders.ts diff --git a/apps/with-lingui/src/theme/useAppTheme.ts b/apps/with-lingui/src/hooks/useAppTheme.ts similarity index 74% rename from apps/with-lingui/src/theme/useAppTheme.ts rename to apps/with-lingui/src/hooks/useAppTheme.ts index f4fb596..504e61d 100644 --- a/apps/with-lingui/src/theme/useAppTheme.ts +++ b/apps/with-lingui/src/hooks/useAppTheme.ts @@ -3,6 +3,10 @@ import type { ConfigProviderProps } from "antd"; import { buildLightThemeConfig, buildDarkThemeConfig } from "./tokenBuilders"; import { useSettingsStore } from "@/stores/settings"; +/** + * Theme selection for ConfigProvider: light/dark share radius, font, control tweaks. + * @see https://ant.design/docs/react/customize-theme + */ export function useAppTheme(): ConfigProviderProps { const darkMode = useSettingsStore((s) => s.darkMode); diff --git a/apps/with-lingui/src/main.tsx b/apps/with-lingui/src/main.tsx index 7c9e2a2..966b779 100644 --- a/apps/with-lingui/src/main.tsx +++ b/apps/with-lingui/src/main.tsx @@ -7,7 +7,7 @@ import { messages as enMessages } from "./locales/en/messages.po"; import { messages as zhMessages } from "./locales/zh/messages.po"; import { useSettingsStore } from "./stores/settings"; import { useAuthStore } from "./stores/auth"; -import { fetchSessionAndApplyToStore } from "./auth/session"; +import { fetchSessionAndApplyToStore } from "./utils/session"; i18n.load("en", enMessages); i18n.load("zh", zhMessages); diff --git a/apps/with-lingui/src/mocks/data.ts b/apps/with-lingui/src/mocks/data.ts index ab6639e..22ed8e5 100644 --- a/apps/with-lingui/src/mocks/data.ts +++ b/apps/with-lingui/src/mocks/data.ts @@ -1,5 +1,5 @@ import type { User } from "@/api/schemas"; -import { vercelAvatarUrl } from "@/utils/avatarUrl"; +import { vercelAvatarUrl } from "./utils"; /** Demo logins look like real org accounts (first.last @ northstar.io). Index 0 stays `admin` for mock login. */ const MOCK_IDENTITIES: ReadonlyArray<[string, string]> = [ diff --git a/apps/with-lingui/src/mocks/handlers/user.ts b/apps/with-lingui/src/mocks/handlers/user.ts index b1c232e..97679c6 100644 --- a/apps/with-lingui/src/mocks/handlers/user.ts +++ b/apps/with-lingui/src/mocks/handlers/user.ts @@ -1,6 +1,6 @@ import { http } from "msw"; import { MOCK_USERS } from "../data"; -import { filterUsers, paginateList, parsePaginationParams } from "../filterUtils"; +import { filterUsers, paginateList, parsePaginationParams } from "../utils"; import { withDelay, successResponse, errorResponse, ERROR_CODES } from "../createHandler"; let users = [...MOCK_USERS]; diff --git a/apps/with-lingui/src/mocks/filterUtils.ts b/apps/with-lingui/src/mocks/utils.ts similarity index 80% rename from apps/with-lingui/src/mocks/filterUtils.ts rename to apps/with-lingui/src/mocks/utils.ts index 23dd35b..978fd16 100644 --- a/apps/with-lingui/src/mocks/filterUtils.ts +++ b/apps/with-lingui/src/mocks/utils.ts @@ -1,7 +1,12 @@ /** - * Common filtering utilities for mock user handlers. + * Mock-only helpers: demo avatars, list filtering, pagination. */ +/** Deterministic demo avatar URL from username (mock seed data only). */ +export function vercelAvatarUrl(username: string): string { + return `https://vercel.com/api/www/avatar?u=${encodeURIComponent(username)}`; +} + export interface User { id: string; username: string; diff --git a/apps/with-lingui/src/routes/__root.tsx b/apps/with-lingui/src/routes/__root.tsx index a9799ef..d195170 100644 --- a/apps/with-lingui/src/routes/__root.tsx +++ b/apps/with-lingui/src/routes/__root.tsx @@ -7,7 +7,7 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { i18n } from "@lingui/core"; import { I18nProvider } from "@lingui/react"; import { useSettingsStore } from "@/stores/settings"; -import { useAppTheme } from "@/theme"; +import { useAppTheme } from "@/hooks/useAppTheme"; import { NotFound } from "@/components/NotFound"; import "@/index.css"; diff --git a/apps/with-lingui/src/routes/_auth/users/index.tsx b/apps/with-lingui/src/routes/_auth/users/index.tsx index 732c0a6..1c9c9f4 100644 --- a/apps/with-lingui/src/routes/_auth/users/index.tsx +++ b/apps/with-lingui/src/routes/_auth/users/index.tsx @@ -13,7 +13,6 @@ import { DataTable } from "@/components/DataTable"; import { useResourceCRUD } from "@/hooks/useResourceCRUD"; import { Toolbar } from "./-Toolbar"; import { FormModal } from "./-FormModal"; -import { vercelAvatarUrl } from "@/utils/avatarUrl"; const UserSearchParamsSchema = z.object({ limit: z.number().int().positive().catch(100), @@ -188,7 +187,7 @@ function UsersPage() { sorter: true, sortOrder: search.sortField === "username" ? search.sortOrder : null, render: (_: unknown, record: User) => { - const src = record.avatar?.trim() || vercelAvatarUrl(record.username); + const src = (record.avatar ?? "").trim() || undefined; return ( diff --git a/apps/with-lingui/src/routes/login/index.tsx b/apps/with-lingui/src/routes/login/index.tsx index 03bdf42..e0b146b 100644 --- a/apps/with-lingui/src/routes/login/index.tsx +++ b/apps/with-lingui/src/routes/login/index.tsx @@ -8,7 +8,7 @@ import { useAuthStore } from "@/stores/auth"; import { useSettingsStore } from "@/stores/settings"; import { AUTH_ENDPOINTS } from "@/api/auth"; import { LoginRequestSchema, AuthTokensSchema } from "@/api/schemas"; -import { fetchSessionAndApplyToStore } from "@/auth/session"; +import { fetchSessionAndApplyToStore } from "@/utils/session"; import type { LoginRequest } from "@/api/schemas"; import { APP_BRAND_NAME, APP_FAVICON_SRC } from "@/utils/constants"; import { Languages } from "lucide-react"; diff --git a/apps/with-lingui/src/theme/index.ts b/apps/with-lingui/src/theme/index.ts deleted file mode 100644 index 6bbb534..0000000 --- a/apps/with-lingui/src/theme/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Theme entry: light/dark share radius, font, control tweaks; each sets primary/layout/border neutrals. - * @see https://ant.design/docs/react/customize-theme - */ -export { useAppTheme } from "./useAppTheme"; diff --git a/apps/with-lingui/src/utils/avatarUrl.ts b/apps/with-lingui/src/utils/avatarUrl.ts deleted file mode 100644 index 3bca086..0000000 --- a/apps/with-lingui/src/utils/avatarUrl.ts +++ /dev/null @@ -1,4 +0,0 @@ -/** Deterministic avatar image from username (Vercel avatar API). */ -export function vercelAvatarUrl(username: string): string { - return `https://vercel.com/api/www/avatar?u=${encodeURIComponent(username)}`; -} diff --git a/apps/with-lingui/src/auth/session.ts b/apps/with-lingui/src/utils/session.ts similarity index 100% rename from apps/with-lingui/src/auth/session.ts rename to apps/with-lingui/src/utils/session.ts