# Directory Structure This document describes the module organization and folder conventions for the frontend application. ## Overview ``` app/ # Next.js App Router ├── (marketing)/ # Public marketing pages (i18n) │ └── [locale]/ # Locale-based routing └── (app)/ # Protected application routes └── app/ # Main application routes modules/ # Feature modules ├── [feature]/ # Feature module │ ├── components/ # UI components │ ├── hooks/ # Custom hooks │ ├── context/ # React Context │ ├── lib/ # Utilities and data transforms │ └── types/ # Frontend view model types ├── shared/ # Shared components across features └── ui/ # UI component library middleware.ts # Authentication & routing middleware ``` ## Module Structure ### Feature Module Pattern Each feature module should follow this structure: ``` modules/dashboard/ ├── components/ │ ├── DashboardHeader.tsx │ ├── StatsCard.tsx │ ├── ActivityFeed.tsx │ └── index.ts # Barrel export ├── hooks/ │ ├── useDashboardStats.ts │ ├── useActivityFeed.ts │ └── index.ts ├── context/ │ ├── DashboardContext.tsx │ └── index.ts ├── lib/ │ ├── formatters.ts # Data formatting utilities │ ├── transformers.ts # API response transformers │ └── constants.ts # Feature-specific constants ├── types/ │ └── index.ts # View model types └── index.ts # Public API of the module ``` ### Component Organization ``` components/ ├── [ComponentName].tsx # Main component file ├── [ComponentName].test.tsx # Unit tests (if applicable) └── index.ts # Barrel export ``` ### Hooks Organization ``` hooks/ ├── useFeatureData.ts # Data fetching hooks ├── useFeatureActions.ts # Mutation hooks ├── useFeatureState.ts # Local state hooks └── index.ts ``` ## Shared Modules ### `modules/shared/` Components and utilities shared across multiple features: ``` shared/ ├── components/ │ ├── Layout/ # Layout components │ ├── Navigation/ # Navigation components │ ├── DataTable/ # Reusable data tables │ └── Forms/ # Form components ├── hooks/ │ ├── useUser.ts # Current user hook │ ├── useOrganization.ts # Organization context │ └── usePermissions.ts # Permission checks └── lib/ ├── api.ts # API client configuration └── utils.ts # Shared utilities ``` ### `modules/ui/` Low-level UI components (design system): ``` ui/ ├── Button/ ├── Input/ ├── Select/ ├── Dialog/ ├── Toast/ └── ... ``` ## Naming Conventions ### Files | Type | Convention | Example | |------|------------|---------| | Components | PascalCase | `UserProfile.tsx` | | Hooks | camelCase with `use` prefix | `useUserProfile.ts` | | Context | PascalCase with `Context` suffix | `UserContext.tsx` | | Utilities | camelCase | `formatDate.ts` | | Constants | camelCase or SCREAMING_SNAKE_CASE | `constants.ts` | | Types | PascalCase | `types.ts` or `UserTypes.ts` | ### Exports Use barrel exports (`index.ts`) for clean imports: ```typescript // modules/dashboard/components/index.ts export { DashboardHeader } from './DashboardHeader'; export { StatsCard } from './StatsCard'; export { ActivityFeed } from './ActivityFeed'; ``` ```typescript // Usage import { DashboardHeader, StatsCard } from '@/modules/dashboard/components'; ``` ## Route-Module Mapping Routes in `app/(app)/` should map to modules in `modules/`: ``` app/(app)/ ├── dashboard/ │ └── page.tsx -> modules/dashboard/ ├── users/ │ ├── page.tsx -> modules/users/ │ └── [id]/ │ └── page.tsx -> modules/users/ (detail view) ├── settings/ │ └── page.tsx -> modules/settings/ └── orders/ ├── page.tsx -> modules/orders/ └── [id]/ └── page.tsx -> modules/orders/ (detail view) ``` ## Scenario: Organization-Scoped Workspace Routes ### 1. Scope / Trigger - Trigger: adding or changing protected app workspace routes, app-shell navigation, breadcrumbs, login redirects, organization switching, or settings pagination links. - The current workspace URL must include the active organization slug when an organization is selected. ### 2. Signatures - Canonical workspace route: `/app/[organizationSlug]/`. - Legacy-compatible route: `/app/` may remain available while old links are migrated. - Shared helpers live in `modules/shared/lib/workspace-routing.ts`: - `getWorkspaceHref(organizationSlug: string | undefined, path?: string): string` - `getWorkspacePathFromPathname(pathname: string): string` - `getWorkspaceSlugFromPathname(pathname: string): string | undefined` - `isWorkspacePathActive(pathname: string, itemPath: string): boolean` ### 3. Contracts - `navGroups` stores workspace-local paths such as `/dashboard`, `/settings/users`, not full `/app/...` hrefs. - `AppShell` passes the current `organization.slug` into navigation and logo links. - `AppSidebarNav`, `AppBreadcrumbs`, settings search forms, and pagination must generate hrefs with `getWorkspaceHref(...)`. - The `[organizationSlug]` layout must reject slug/session mismatches by redirecting to the active session organization workspace. - Organization slugs cannot use reserved first-level workspace section keys such as `dashboard`, `settings`, `elders`, or `beds`. ### 4. Validation & Error Matrix - Missing active organization slug -> helpers fall back to legacy `/app/`. - URL slug differs from `getCurrentAuthContext().organization.slug` -> redirect to `getWorkspaceHref(activeSlug, "/dashboard")`. - New or updated organization slug equals a reserved workspace key -> API returns validation failure. - A page-level redirect inside protected routes -> use `getWorkspaceHref(context.organization?.slug, targetPath)`. ### 5. Good/Base/Bad Cases - Good: organization switch calls `POST /api/auth/organization`, then navigates to the same workspace path under the returned active organization slug. - Base: legacy `/app/dashboard` remains renderable for compatibility, but new app-shell links point to `/app/{slug}/dashboard`. - Bad: hard-code `/app/settings/users` in table forms or breadcrumbs; it drops users out of the organization-scoped workspace. - Bad: infer tenant from `localStorage`; active organization remains server-session state. ### 6. Tests Required - `pnpm lint` - `pnpm type-check` - `pnpm build` - Browser assertions: - login/setup lands on `/app/{activeOrg.slug}/dashboard` - sidebar links preserve the active organization slug - settings search and pagination preserve the active organization slug - switching organization moves the URL to the new slug while preserving the workspace path - mismatched `/app/{wrongSlug}/...` redirects to the active organization workspace ### 7. Wrong vs Correct #### Wrong ```tsx 用户管理 ``` #### Correct ```tsx 用户管理 ``` ## Import Path Aliases Configure in `tsconfig.json`: ```json { "compilerOptions": { "paths": { "@/*": ["./src/*"], "@/modules/*": ["./modules/*"], "@/components/*": ["./components/*"], "@/lib/*": ["./lib/*"] } } } ``` ## Best Practices 1. **Colocation**: Keep related files close together 2. **Single Responsibility**: Each module should have one clear purpose 3. **Explicit Dependencies**: Import what you need, avoid implicit globals 4. **Barrel Exports**: Use `index.ts` for public APIs 5. **Private by Default**: Only export what needs to be shared ## Anti-Patterns to Avoid - Deeply nested folder structures (max 3-4 levels) - Circular dependencies between modules - Mixing feature code with shared utilities - Importing internal module files directly (use barrel exports) - Creating "utils" folders that become dumping grounds