feat: scope workspace routes by organization slug
This commit is contained in:
@@ -155,6 +155,71 @@ app/(app)/
|
||||
└── 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]/<workspacePath>`.
|
||||
- Legacy-compatible route: `/app/<workspacePath>` 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/<workspacePath>`.
|
||||
- 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
|
||||
<Link href="/app/settings/users">用户管理</Link>
|
||||
```
|
||||
|
||||
#### Correct
|
||||
|
||||
```tsx
|
||||
<Link href={getWorkspaceHref(organization.slug, "/settings/users")}>用户管理</Link>
|
||||
```
|
||||
|
||||
## Import Path Aliases
|
||||
|
||||
Configure in `tsconfig.json`:
|
||||
|
||||
Reference in New Issue
Block a user