@rashidee/co2 1.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +25449 -0
- package/drizzle/0000_remarkable_charles_xavier.sql +210 -0
- package/drizzle/0001_wise_wolf_cub.sql +13 -0
- package/drizzle/0002_secret_spyke.sql +16 -0
- package/drizzle/0003_workspace_config_singleton.sql +16 -0
- package/drizzle/0004_terminal_claim_v120.sql +11 -0
- package/drizzle/0005_brainstorm_v124.sql +10 -0
- package/drizzle/0006_project_v140.sql +6 -0
- package/drizzle/0007_prd_wizard_v124.sql +14 -0
- package/drizzle/0008_workflow_artifact_v126.sql +24 -0
- package/drizzle/0009_project_import_v150.sql +7 -0
- package/drizzle/0010_quality_v151.sql +3 -0
- package/drizzle/meta/0000_snapshot.json +1335 -0
- package/drizzle/meta/0001_snapshot.json +1416 -0
- package/drizzle/meta/0002_snapshot.json +1510 -0
- package/drizzle/meta/_journal.json +83 -0
- package/package.json +41 -0
- package/plugin/.claude-plugin/marketplace.json +36 -0
- package/plugin/.claude-plugin/plugin.json +22 -0
- package/plugin/README.md +625 -0
- package/plugin/SKILLS.md +350 -0
- package/plugin/co2.svg +5 -0
- package/plugin/commands/brainstorm-loop.md +129 -0
- package/plugin/skills/conductor-defect/SKILL.md +898 -0
- package/plugin/skills/conductor-feature-develop/SKILL.md +1383 -0
- package/plugin/skills/conductor-feature-develop/references/implementation-domain-template.md +131 -0
- package/plugin/skills/conductor-feature-develop/references/implementation-master-template.md +89 -0
- package/plugin/skills/conductor-feature-develop/references/playwright-setup.md +224 -0
- package/plugin/skills/conductor-feature-prepare/SKILL.md +604 -0
- package/plugin/skills/depgen-k8s/SKILL.md +468 -0
- package/plugin/skills/depgen-k8s/references/dockerfile-laravel.md +197 -0
- package/plugin/skills/depgen-k8s/references/dockerfile-nodejs.md +163 -0
- package/plugin/skills/depgen-k8s/references/dockerfile-spring-boot.md +174 -0
- package/plugin/skills/depgen-k8s/references/k8s-patterns.md +869 -0
- package/plugin/skills/mockgen-shadcn/SKILL.md +1067 -0
- package/plugin/skills/mockgen-shadcn/references/admin-layout-template.md +1467 -0
- package/plugin/skills/mockgen-shadcn/references/mockup-hub-template.md +498 -0
- package/plugin/skills/mockgen-shadcn/references/mockup-index-template.md +216 -0
- package/plugin/skills/mockgen-tailwind/SKILL.md +904 -0
- package/plugin/skills/mockgen-tailwind/references/admin-layout-template.md +720 -0
- package/plugin/skills/mockgen-tailwind/references/mockup-hub-template.md +498 -0
- package/plugin/skills/mockgen-tailwind/references/mockup-index-template.md +190 -0
- package/plugin/skills/modelgen-nosql/SKILL.md +617 -0
- package/plugin/skills/modelgen-nosql/references/model-extraction-methodology.md +497 -0
- package/plugin/skills/modelgen-nosql/references/nosql-design-guide.md +258 -0
- package/plugin/skills/modelgen-relational/SKILL.md +707 -0
- package/plugin/skills/modelgen-relational/references/model-extraction-methodology.md +499 -0
- package/plugin/skills/specgen-flutter-riverpod/SKILL.md +898 -0
- package/plugin/skills/specgen-flutter-riverpod/references/component-patterns.md +971 -0
- package/plugin/skills/specgen-flutter-riverpod/references/notification-patterns.md +457 -0
- package/plugin/skills/specgen-flutter-riverpod/references/routing-patterns.md +362 -0
- package/plugin/skills/specgen-flutter-riverpod/references/security-patterns.md +709 -0
- package/plugin/skills/specgen-flutter-riverpod/references/spec-template.md +1566 -0
- package/plugin/skills/specgen-flutter-riverpod/references/state-patterns.md +518 -0
- package/plugin/skills/specgen-flutter-riverpod/references/storage-patterns.md +370 -0
- package/plugin/skills/specgen-laravel-eloquent-bladehtmx/SKILL.md +984 -0
- package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/batch-patterns.md +359 -0
- package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/messaging-patterns.md +468 -0
- package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/modulith-patterns.md +461 -0
- package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/reporting-patterns.md +803 -0
- package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/security-patterns.md +501 -0
- package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/spec-template.md +1268 -0
- package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/web-patterns.md +301 -0
- package/plugin/skills/specgen-node-cli-web/SKILL.md +561 -0
- package/plugin/skills/specgen-node-cli-web/references/auth-patterns.md +293 -0
- package/plugin/skills/specgen-node-cli-web/references/cli-packaging-patterns.md +250 -0
- package/plugin/skills/specgen-node-cli-web/references/server-patterns.md +247 -0
- package/plugin/skills/specgen-node-cli-web/references/spec-template.md +401 -0
- package/plugin/skills/specgen-node-cli-web/references/testing-patterns.md +174 -0
- package/plugin/skills/specgen-react-mui/SKILL.md +789 -0
- package/plugin/skills/specgen-react-mui/references/component-patterns.md +1077 -0
- package/plugin/skills/specgen-react-mui/references/reporting-patterns.md +1998 -0
- package/plugin/skills/specgen-react-mui/references/routing-patterns.md +461 -0
- package/plugin/skills/specgen-react-mui/references/security-patterns.md +599 -0
- package/plugin/skills/specgen-react-mui/references/spec-template.md +1030 -0
- package/plugin/skills/specgen-react-mui/references/state-patterns.md +327 -0
- package/plugin/skills/specgen-react-tailwind/SKILL.md +859 -0
- package/plugin/skills/specgen-react-tailwind/references/component-patterns.md +1212 -0
- package/plugin/skills/specgen-react-tailwind/references/reporting-patterns.md +1529 -0
- package/plugin/skills/specgen-react-tailwind/references/routing-patterns.md +669 -0
- package/plugin/skills/specgen-react-tailwind/references/security-patterns.md +656 -0
- package/plugin/skills/specgen-react-tailwind/references/spec-template.md +1237 -0
- package/plugin/skills/specgen-react-tailwind/references/state-patterns.md +327 -0
- package/plugin/skills/specgen-sdk-java/SKILL.md +933 -0
- package/plugin/skills/specgen-sdk-java/references/api-extraction-patterns.md +247 -0
- package/plugin/skills/specgen-sdk-java/references/http-client-patterns.md +495 -0
- package/plugin/skills/specgen-sdk-java/references/packaging-patterns.md +366 -0
- package/plugin/skills/specgen-sdk-java/references/spec-template.md +956 -0
- package/plugin/skills/specgen-spring-jpa-jtehtmx/SKILL.md +1284 -0
- package/plugin/skills/specgen-spring-jpa-jtehtmx/references/batch-patterns.md +1224 -0
- package/plugin/skills/specgen-spring-jpa-jtehtmx/references/jasper-patterns.md +1638 -0
- package/plugin/skills/specgen-spring-jpa-jtehtmx/references/messaging-patterns.md +512 -0
- package/plugin/skills/specgen-spring-jpa-jtehtmx/references/modulith-patterns.md +354 -0
- package/plugin/skills/specgen-spring-jpa-jtehtmx/references/security-patterns.md +551 -0
- package/plugin/skills/specgen-spring-jpa-jtehtmx/references/spec-template.md +2502 -0
- package/plugin/skills/specgen-spring-jpa-jtehtmx/references/web-patterns.md +503 -0
- package/plugin/skills/specgen-spring-jpa-restapi/SKILL.md +990 -0
- package/plugin/skills/specgen-spring-jpa-restapi/references/batch-patterns.md +1224 -0
- package/plugin/skills/specgen-spring-jpa-restapi/references/messaging-patterns.md +512 -0
- package/plugin/skills/specgen-spring-jpa-restapi/references/modulith-patterns.md +279 -0
- package/plugin/skills/specgen-spring-jpa-restapi/references/reporting-patterns.md +1536 -0
- package/plugin/skills/specgen-spring-jpa-restapi/references/restapi-patterns.md +722 -0
- package/plugin/skills/specgen-spring-jpa-restapi/references/security-patterns.md +351 -0
- package/plugin/skills/specgen-spring-jpa-restapi/references/spec-template.md +553 -0
- package/plugin/skills/specgen-ts-cli/SKILL.md +861 -0
- package/plugin/skills/specgen-ts-cli/references/async-patterns.md +1101 -0
- package/plugin/skills/specgen-ts-cli/references/command-patterns.md +383 -0
- package/plugin/skills/specgen-ts-cli/references/config-patterns.md +430 -0
- package/plugin/skills/specgen-ts-cli/references/database-patterns.md +750 -0
- package/plugin/skills/specgen-ts-cli/references/packaging-patterns.md +426 -0
- package/plugin/skills/specgen-ts-cli/references/spec-template.md +1391 -0
- package/plugin/skills/specgen-ts-cli/references/testing-patterns.md +909 -0
- package/plugin/skills/testgen-functional/SKILL.md +1381 -0
- package/plugin/skills/tracegen-matrix/SKILL.md +243 -0
- package/plugin/skills/util-preparek8senv/SKILL.md +422 -0
- package/plugin/skills/util-projectinit/SKILL.md +307 -0
- package/plugin/skills/util-projectinit/reference/brainstorm-loop.md +129 -0
- package/plugin/skills/util-projectinit/reference/brainstorm-protocol.md +144 -0
- package/plugin/skills/util-projectinit/reference/brainstorm-setup-guide.md +153 -0
- package/plugin/skills/util-projectinit/reference/brainstorm-template-appdev.md +87 -0
- package/plugin/skills/util-projectinit/reference/co2-context-generation-guide.md +761 -0
- package/plugin/skills/util-projectsync/SKILL.md +501 -0
- package/plugin/skills/util-updprd/SKILL.md +324 -0
- package/plugin/skills/util-usanalyzer/SKILL.md +307 -0
- package/plugin/skills/util-ustagger/SKILL.md +154 -0
- package/static/assets/abnfDiagram-VRR7QNED-DvPuwyCV.js +1 -0
- package/static/assets/arc-DRKw7sjs.js +1 -0
- package/static/assets/architectureDiagram-ZJ3FMSHR-CaNcxUbC.js +36 -0
- package/static/assets/blockDiagram-677ZJIJ3-Demd4AOK.js +132 -0
- package/static/assets/c4Diagram-LMCZKHZV-PVKUfc2x.js +10 -0
- package/static/assets/channel-C2nVbuwv.js +1 -0
- package/static/assets/chunk-2Q5K7J3B-CExf8ww_.js +1 -0
- package/static/assets/chunk-32BRIVSS-CFPaoONF.js +1 -0
- package/static/assets/chunk-5VM5RSS4-DE2esM67.js +15 -0
- package/static/assets/chunk-EX3LRPZG-SON_YdDG.js +231 -0
- package/static/assets/chunk-JWPE2WC7-Qxah8LNr.js +1 -0
- package/static/assets/chunk-MOJQB5TN-BcWGQV3j.js +88 -0
- package/static/assets/chunk-RYQCIY6F-DkDj-OR5.js +1 -0
- package/static/assets/chunk-V7JOEXUC-BlI1Mrah.js +206 -0
- package/static/assets/chunk-VR4S4FIN-Agf__duj.js +1 -0
- package/static/assets/chunk-XXDRQBXY-B5CfAh85.js +1 -0
- package/static/assets/classDiagram-OUVF2IWQ-DXJIljsR.js +1 -0
- package/static/assets/classDiagram-v2-EOCWNBFH-DXJIljsR.js +1 -0
- package/static/assets/cose-bilkent-JH36ORCC-DO0cQkcy.js +1 -0
- package/static/assets/cynefin-VYW2F7L2-CfOnsnXc.js +178 -0
- package/static/assets/cynefinDiagram-TSTJHNR4-BJ8tULAv.js +62 -0
- package/static/assets/cytoscape.esm-DTSO7Bv0.js +331 -0
- package/static/assets/dagre-VKFMJZFB-DfjwzHqP.js +4 -0
- package/static/assets/defaultLocale-DX6XiGOO.js +1 -0
- package/static/assets/diagram-FQU43EPY-CndSOIIM.js +3 -0
- package/static/assets/diagram-G47NLZAW-Czf5hN0Z.js +24 -0
- package/static/assets/diagram-NH7WQ7WH-COAyojGh.js +24 -0
- package/static/assets/diagram-OA4YK3LP-H8PoiN2m.js +30 -0
- package/static/assets/diagram-WEI45ONY-DzcaVzjp.js +41 -0
- package/static/assets/ebnfDiagram-CCIWWBDH-DPazGYLJ.js +1 -0
- package/static/assets/erDiagram-Q63AITRT-Bo-fubgF.js +85 -0
- package/static/assets/flowDiagram-23GEKE2U-CSvxUDwh.js +156 -0
- package/static/assets/ganttDiagram-NO4QXBWP-DnBveWX6.js +292 -0
- package/static/assets/gitGraphDiagram-IHSO6WYX-B3rVL8c-.js +106 -0
- package/static/assets/graph-C9eacEi8.js +1 -0
- package/static/assets/index-LZSQkLE2.css +41 -0
- package/static/assets/index-y7XDSgMG.js +495 -0
- package/static/assets/infoDiagram-FWYZ7A6U-CvWV9yy2.js +2 -0
- package/static/assets/init-Gi6I4Gst.js +1 -0
- package/static/assets/ishikawaDiagram-FXEZZL3T-DF28a1N6.js +70 -0
- package/static/assets/journeyDiagram-5HDEW3XC-CB6CugYk.js +139 -0
- package/static/assets/kanban-definition-HUTT4EX6-ResSLF6d.js +89 -0
- package/static/assets/katex-C5jXJg4s.js +257 -0
- package/static/assets/layout-DEXfKzaS.js +1 -0
- package/static/assets/linear-CLO--ryk.js +1 -0
- package/static/assets/map-Czzmt4hB.js +1 -0
- package/static/assets/mindmap-definition-LN4V7U3C-BvW3EbxX.js +96 -0
- package/static/assets/ordinal-Cboi1Yqb.js +1 -0
- package/static/assets/pegDiagram-2B236MQR-BHCgqrrM.js +1 -0
- package/static/assets/pieDiagram-ENE6RG2P-DVmCQ5FA.js +39 -0
- package/static/assets/quadrantDiagram-ABIIQ3AL-B3e5KR52.js +7 -0
- package/static/assets/railroadDiagram-RFXS5EU6-Csv5ysLc.js +1 -0
- package/static/assets/requirementDiagram-TGXJPOKE-DOMVzvnB.js +84 -0
- package/static/assets/sankeyDiagram-HTMAVEWB-B1dcTOvy.js +40 -0
- package/static/assets/sequenceDiagram-DBY2YBRQ-BY414yeO.js +162 -0
- package/static/assets/sizeCapture-X5ZJPWSS-CuJfrfBN.js +1 -0
- package/static/assets/stateDiagram-2N3HPSRC-FX4rJkzV.js +1 -0
- package/static/assets/stateDiagram-v2-6OUMAXLB-BVufbO_H.js +1 -0
- package/static/assets/swimlanes-5IMT3BWC-Sfy58mmv.js +2 -0
- package/static/assets/swimlanesDiagram-G3AALYLV-wDCDw08H.js +8 -0
- package/static/assets/timeline-definition-FHXFAJF6-CmV7by8p.js +120 -0
- package/static/assets/vennDiagram-L72KCM5P-FqIyFZFt.js +34 -0
- package/static/assets/wardleyDiagram-EHGQE667-BZ06O45i.js +78 -0
- package/static/assets/xychartDiagram-FW5EYKEG-BOkffR06.js +7 -0
- package/static/index.html +24 -0
|
@@ -0,0 +1,461 @@
|
|
|
1
|
+
# Routing Patterns — React Router v7 + Feature-Based Architecture
|
|
2
|
+
|
|
3
|
+
This reference describes the React Router v7 conventions and feature-based directory
|
|
4
|
+
structure used in the spec. Include this content in Sections 4 and 7 of the generated
|
|
5
|
+
specification.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Feature-Based Directory Structure
|
|
10
|
+
|
|
11
|
+
Each PRD module maps to a `src/features/<module>/` folder. This co-locates everything
|
|
12
|
+
related to a feature (types, API, hooks, components, pages) so the coding agent can
|
|
13
|
+
implement one feature folder at a time.
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
src/
|
|
17
|
+
├── main.tsx # App entry — providers, ReactDOM.render
|
|
18
|
+
├── App.tsx # Root component — router, theme, notifications
|
|
19
|
+
├── router/
|
|
20
|
+
│ └── index.tsx # Route tree definition
|
|
21
|
+
├── lib/
|
|
22
|
+
│ ├── api/
|
|
23
|
+
│ │ ├── axiosInstance.ts # Axios instance with interceptors
|
|
24
|
+
│ │ └── authBridge.ts # [If Auth = Keycloak] Auth bridge for interceptors
|
|
25
|
+
│ ├── auth/
|
|
26
|
+
│ │ ├── oidcConfig.ts # [If Auth = Keycloak] oidc-client-ts config
|
|
27
|
+
│ │ ├── keycloakRoles.ts # [If Auth = Keycloak] Role extraction utilities
|
|
28
|
+
│ │ ├── useAuthUser.ts # [If Auth = Keycloak] Auth hook wrapper
|
|
29
|
+
│ │ └── roles.ts # Role constants
|
|
30
|
+
│ └── queryClient.ts # TanStack Query QueryClient instance
|
|
31
|
+
├── store/
|
|
32
|
+
│ ├── auth.store.ts # [If Auth = Local] Zustand auth store
|
|
33
|
+
│ └── ui.store.ts # Zustand UI store (sidebar, theme mode)
|
|
34
|
+
├── shared/
|
|
35
|
+
│ ├── layouts/
|
|
36
|
+
│ │ ├── DashboardLayout.tsx # Authenticated layout (sidebar + topbar + outlet)
|
|
37
|
+
│ │ ├── PublicLayout.tsx # Public layout (header/footer + outlet)
|
|
38
|
+
│ │ └── AuthLayout.tsx # Minimal centered layout for login/callback
|
|
39
|
+
│ ├── components/
|
|
40
|
+
│ │ ├── ProtectedRoute.tsx # Auth + role guard wrapper
|
|
41
|
+
│ │ ├── PageHeader.tsx # Page title + breadcrumbs + action buttons
|
|
42
|
+
│ │ ├── DataTable.tsx # Generic MUI Table with sorting/pagination
|
|
43
|
+
│ │ ├── ConfirmDialog.tsx # Generic confirmation Modal
|
|
44
|
+
│ │ ├── StatusChip.tsx # Colored MUI Chip for status badges
|
|
45
|
+
│ │ ├── FormDialog.tsx # Full-screen or modal form dialog wrapper
|
|
46
|
+
│ │ ├── ImageUpload.tsx # Drag-and-drop image upload with preview
|
|
47
|
+
│ │ ├── LoadingOverlay.tsx # Full-page CircularProgress overlay
|
|
48
|
+
│ │ ├── EmptyState.tsx # Empty list placeholder with icon + message
|
|
49
|
+
│ │ ├── ErrorBoundary.tsx # React ErrorBoundary with fallback UI
|
|
50
|
+
│ │ └── SearchInput.tsx # Debounced search text field
|
|
51
|
+
│ ├── hooks/
|
|
52
|
+
│ │ ├── useDebounce.ts # Generic debounce hook
|
|
53
|
+
│ │ ├── useNotification.ts # Toast notification Zustand hook
|
|
54
|
+
│ │ └── useConfirm.ts # Confirm dialog imperative hook
|
|
55
|
+
│ └── form/
|
|
56
|
+
│ ├── TextFieldController.tsx # RHF Controller wrapping MUI TextField
|
|
57
|
+
│ ├── SelectController.tsx # RHF Controller wrapping MUI Select
|
|
58
|
+
│ ├── DatePickerController.tsx # RHF Controller wrapping MUI X DatePicker
|
|
59
|
+
│ ├── SwitchController.tsx # RHF Controller wrapping MUI Switch
|
|
60
|
+
│ ├── AutocompleteController.tsx# RHF Controller wrapping MUI Autocomplete
|
|
61
|
+
│ └── RichTextController.tsx # [If RichText = yes] RHF Controller for Quill
|
|
62
|
+
├── features/
|
|
63
|
+
│ ├── auth/ # [If Auth != none] Auth feature
|
|
64
|
+
│ │ ├── pages/
|
|
65
|
+
│ │ │ ├── LoginPage.tsx # [If Auth = Local] Login form page
|
|
66
|
+
│ │ │ └── AuthCallbackPage.tsx # [If Auth = Keycloak] OIDC callback handler
|
|
67
|
+
│ │ └── index.ts
|
|
68
|
+
│ ├── {{module1}}/ # PRD module 1 (e.g., heroSection)
|
|
69
|
+
│ │ ├── api/
|
|
70
|
+
│ │ │ └── {{module1}}Api.ts # Axios API functions
|
|
71
|
+
│ │ ├── components/
|
|
72
|
+
│ │ │ ├── {{Module1}}List.tsx # List table/grid component
|
|
73
|
+
│ │ │ ├── {{Module1}}Form.tsx # Create/edit form
|
|
74
|
+
│ │ │ └── {{Module1}}Card.tsx # Card component (if applicable)
|
|
75
|
+
│ │ ├── hooks/
|
|
76
|
+
│ │ │ ├── use{{Module1}}s.ts # useQuery for list
|
|
77
|
+
│ │ │ ├── use{{Module1}}.ts # useQuery for single item
|
|
78
|
+
│ │ │ └── use{{Module1}}Mutations.ts # useMutation for CUD operations
|
|
79
|
+
│ │ ├── pages/
|
|
80
|
+
│ │ │ ├── {{Module1}}ListPage.tsx # List page component (lazy-loaded)
|
|
81
|
+
│ │ │ └── {{Module1}}FormPage.tsx # Create/edit page (lazy-loaded)
|
|
82
|
+
│ │ ├── store/
|
|
83
|
+
│ │ │ └── {{module1}}.store.ts # [If needed] Feature-level Zustand store
|
|
84
|
+
│ │ ├── {{module1}}.types.ts # TypeScript interfaces
|
|
85
|
+
│ │ ├── {{module1}}.schema.ts # Zod validation schemas
|
|
86
|
+
│ │ └── index.ts # Public API re-exports
|
|
87
|
+
│ └── {{module2}}/
|
|
88
|
+
│ └── (same structure)
|
|
89
|
+
├── theme/
|
|
90
|
+
│ ├── index.ts # MUI createTheme() with design tokens
|
|
91
|
+
│ ├── palette.ts # Color palette extracted from mockup
|
|
92
|
+
│ └── typography.ts # Font config extracted from mockup
|
|
93
|
+
└── types/
|
|
94
|
+
└── global.d.ts # Global type augmentations (e.g., import.meta.env)
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## Route Tree Definition
|
|
100
|
+
|
|
101
|
+
```tsx
|
|
102
|
+
// src/router/index.tsx
|
|
103
|
+
import { lazy, Suspense } from 'react';
|
|
104
|
+
import { createBrowserRouter, Navigate } from 'react-router-dom';
|
|
105
|
+
import { DashboardLayout } from '../shared/layouts/DashboardLayout';
|
|
106
|
+
import { PublicLayout } from '../shared/layouts/PublicLayout';
|
|
107
|
+
import { AuthLayout } from '../shared/layouts/AuthLayout';
|
|
108
|
+
import { ProtectedRoute } from '../shared/components/ProtectedRoute';
|
|
109
|
+
import { LoadingOverlay } from '../shared/components/LoadingOverlay';
|
|
110
|
+
import { Roles } from '../lib/auth/roles';
|
|
111
|
+
|
|
112
|
+
// Lazy-load all page components for code splitting
|
|
113
|
+
// [If Auth = Keycloak]
|
|
114
|
+
const AuthCallbackPage = lazy(() =>
|
|
115
|
+
import('../features/auth/pages/AuthCallbackPage').then((m) => ({
|
|
116
|
+
default: m.AuthCallbackPage,
|
|
117
|
+
})),
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
// [If Auth = Local]
|
|
121
|
+
const LoginPage = lazy(() =>
|
|
122
|
+
import('../features/auth/pages/LoginPage').then((m) => ({ default: m.LoginPage })),
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
// Dashboard / home page
|
|
126
|
+
const DashboardPage = lazy(() =>
|
|
127
|
+
import('../features/dashboard/pages/DashboardPage').then((m) => ({
|
|
128
|
+
default: m.DashboardPage,
|
|
129
|
+
})),
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
// Hero Section (example — replace with actual modules from PRD)
|
|
133
|
+
const HeroSectionListPage = lazy(() =>
|
|
134
|
+
import('../features/heroSection/pages/HeroSectionListPage').then((m) => ({
|
|
135
|
+
default: m.HeroSectionListPage,
|
|
136
|
+
})),
|
|
137
|
+
);
|
|
138
|
+
const HeroSectionFormPage = lazy(() =>
|
|
139
|
+
import('../features/heroSection/pages/HeroSectionFormPage').then((m) => ({
|
|
140
|
+
default: m.HeroSectionFormPage,
|
|
141
|
+
})),
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
// Fallback wrapper for lazy-loaded components
|
|
145
|
+
function SuspenseWrapper({ children }: { children: React.ReactNode }) {
|
|
146
|
+
return <Suspense fallback={<LoadingOverlay />}>{children}</Suspense>;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export const router = createBrowserRouter([
|
|
150
|
+
// ── Auth routes (public) ────────────────────────────────────────────
|
|
151
|
+
{
|
|
152
|
+
element: <AuthLayout />,
|
|
153
|
+
children: [
|
|
154
|
+
// [If Auth = Keycloak]
|
|
155
|
+
{
|
|
156
|
+
path: '/auth/callback',
|
|
157
|
+
element: (
|
|
158
|
+
<SuspenseWrapper>
|
|
159
|
+
<AuthCallbackPage />
|
|
160
|
+
</SuspenseWrapper>
|
|
161
|
+
),
|
|
162
|
+
},
|
|
163
|
+
// [If Auth = Local]
|
|
164
|
+
{
|
|
165
|
+
path: '/login',
|
|
166
|
+
element: (
|
|
167
|
+
<SuspenseWrapper>
|
|
168
|
+
<LoginPage />
|
|
169
|
+
</SuspenseWrapper>
|
|
170
|
+
),
|
|
171
|
+
},
|
|
172
|
+
],
|
|
173
|
+
},
|
|
174
|
+
|
|
175
|
+
// ── Protected / dashboard routes ────────────────────────────────────
|
|
176
|
+
{
|
|
177
|
+
element: (
|
|
178
|
+
<ProtectedRoute>
|
|
179
|
+
<DashboardLayout />
|
|
180
|
+
</ProtectedRoute>
|
|
181
|
+
),
|
|
182
|
+
children: [
|
|
183
|
+
{
|
|
184
|
+
index: true,
|
|
185
|
+
element: <Navigate to="/dashboard" replace />,
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
path: '/dashboard',
|
|
189
|
+
element: (
|
|
190
|
+
<SuspenseWrapper>
|
|
191
|
+
<DashboardPage />
|
|
192
|
+
</SuspenseWrapper>
|
|
193
|
+
),
|
|
194
|
+
},
|
|
195
|
+
|
|
196
|
+
// Hero Section — accessible by ADMIN and EDITOR
|
|
197
|
+
{
|
|
198
|
+
path: '/hero-section',
|
|
199
|
+
element: (
|
|
200
|
+
<ProtectedRoute requiredRoles={[Roles.ADMIN, Roles.EDITOR]}>
|
|
201
|
+
<SuspenseWrapper>
|
|
202
|
+
<HeroSectionListPage />
|
|
203
|
+
</SuspenseWrapper>
|
|
204
|
+
</ProtectedRoute>
|
|
205
|
+
),
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
path: '/hero-section/create',
|
|
209
|
+
element: (
|
|
210
|
+
<ProtectedRoute requiredRoles={[Roles.ADMIN, Roles.EDITOR]}>
|
|
211
|
+
<SuspenseWrapper>
|
|
212
|
+
<HeroSectionFormPage />
|
|
213
|
+
</SuspenseWrapper>
|
|
214
|
+
</ProtectedRoute>
|
|
215
|
+
),
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
path: '/hero-section/:id/edit',
|
|
219
|
+
element: (
|
|
220
|
+
<ProtectedRoute requiredRoles={[Roles.ADMIN, Roles.EDITOR]}>
|
|
221
|
+
<SuspenseWrapper>
|
|
222
|
+
<HeroSectionFormPage />
|
|
223
|
+
</SuspenseWrapper>
|
|
224
|
+
</ProtectedRoute>
|
|
225
|
+
),
|
|
226
|
+
},
|
|
227
|
+
|
|
228
|
+
// Add additional module routes following the same pattern...
|
|
229
|
+
],
|
|
230
|
+
},
|
|
231
|
+
|
|
232
|
+
// ── Error routes ────────────────────────────────────────────────────
|
|
233
|
+
{
|
|
234
|
+
path: '/unauthorized',
|
|
235
|
+
element: <div>You do not have permission to access this page.</div>,
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
path: '*',
|
|
239
|
+
element: <div>404 — Page not found.</div>,
|
|
240
|
+
},
|
|
241
|
+
]);
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
## DashboardLayout
|
|
247
|
+
|
|
248
|
+
```tsx
|
|
249
|
+
// src/shared/layouts/DashboardLayout.tsx
|
|
250
|
+
import { useState } from 'react';
|
|
251
|
+
import { Outlet } from 'react-router-dom';
|
|
252
|
+
import { Box, Toolbar } from '@mui/material';
|
|
253
|
+
import { AppTopBar } from '../components/AppTopBar';
|
|
254
|
+
import { AppSidebar } from '../components/AppSidebar';
|
|
255
|
+
|
|
256
|
+
const DRAWER_WIDTH = 240;
|
|
257
|
+
|
|
258
|
+
export function DashboardLayout() {
|
|
259
|
+
const [sidebarOpen, setSidebarOpen] = useState(true);
|
|
260
|
+
|
|
261
|
+
return (
|
|
262
|
+
<Box sx={{ display: 'flex', minHeight: '100vh' }}>
|
|
263
|
+
<AppTopBar
|
|
264
|
+
drawerWidth={DRAWER_WIDTH}
|
|
265
|
+
sidebarOpen={sidebarOpen}
|
|
266
|
+
onToggleSidebar={() => setSidebarOpen((prev) => !prev)}
|
|
267
|
+
/>
|
|
268
|
+
<AppSidebar
|
|
269
|
+
drawerWidth={DRAWER_WIDTH}
|
|
270
|
+
open={sidebarOpen}
|
|
271
|
+
/>
|
|
272
|
+
<Box
|
|
273
|
+
component="main"
|
|
274
|
+
sx={{
|
|
275
|
+
flexGrow: 1,
|
|
276
|
+
p: 3,
|
|
277
|
+
ml: sidebarOpen ? `${DRAWER_WIDTH}px` : 0,
|
|
278
|
+
transition: (theme) =>
|
|
279
|
+
theme.transitions.create('margin', {
|
|
280
|
+
easing: theme.transitions.easing.sharp,
|
|
281
|
+
duration: theme.transitions.duration.leavingScreen,
|
|
282
|
+
}),
|
|
283
|
+
}}
|
|
284
|
+
>
|
|
285
|
+
<Toolbar /> {/* Spacer for fixed AppBar */}
|
|
286
|
+
<Outlet />
|
|
287
|
+
</Box>
|
|
288
|
+
</Box>
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
---
|
|
294
|
+
|
|
295
|
+
## Sidebar Navigation
|
|
296
|
+
|
|
297
|
+
```typescript
|
|
298
|
+
// src/shared/components/navigationConfig.ts
|
|
299
|
+
import DashboardIcon from '@mui/icons-material/Dashboard';
|
|
300
|
+
import ImageIcon from '@mui/icons-material/Image';
|
|
301
|
+
import PeopleIcon from '@mui/icons-material/People';
|
|
302
|
+
import type { SvgIconComponent } from '@mui/icons-material';
|
|
303
|
+
import { Roles } from '../../lib/auth/roles';
|
|
304
|
+
|
|
305
|
+
export interface NavItem {
|
|
306
|
+
label: string;
|
|
307
|
+
path: string;
|
|
308
|
+
icon: SvgIconComponent;
|
|
309
|
+
/** If provided, user must have at least one of these roles to see this item */
|
|
310
|
+
requiredRoles?: string[];
|
|
311
|
+
children?: NavItem[];
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* All sidebar navigation items.
|
|
316
|
+
* Items with requiredRoles are hidden for users who lack those roles.
|
|
317
|
+
* Replace with actual modules from the PRD.md.
|
|
318
|
+
*/
|
|
319
|
+
export const navigationItems: NavItem[] = [
|
|
320
|
+
{
|
|
321
|
+
label: 'Dashboard',
|
|
322
|
+
path: '/dashboard',
|
|
323
|
+
icon: DashboardIcon,
|
|
324
|
+
// No requiredRoles = accessible to all authenticated users
|
|
325
|
+
},
|
|
326
|
+
{
|
|
327
|
+
label: 'Hero Section',
|
|
328
|
+
path: '/hero-section',
|
|
329
|
+
icon: ImageIcon,
|
|
330
|
+
requiredRoles: [Roles.ADMIN, Roles.EDITOR],
|
|
331
|
+
},
|
|
332
|
+
{
|
|
333
|
+
label: 'Users',
|
|
334
|
+
path: '/users',
|
|
335
|
+
icon: PeopleIcon,
|
|
336
|
+
requiredRoles: [Roles.ADMIN],
|
|
337
|
+
},
|
|
338
|
+
// Add additional nav items matching the mockup sidebar files...
|
|
339
|
+
];
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
```tsx
|
|
343
|
+
// src/shared/components/AppSidebar.tsx
|
|
344
|
+
import {
|
|
345
|
+
Drawer,
|
|
346
|
+
List,
|
|
347
|
+
ListItemButton,
|
|
348
|
+
ListItemIcon,
|
|
349
|
+
ListItemText,
|
|
350
|
+
Toolbar,
|
|
351
|
+
} from '@mui/material';
|
|
352
|
+
import { useNavigate, useLocation } from 'react-router-dom';
|
|
353
|
+
import { useAuthUser } from '../../lib/auth/useAuthUser';
|
|
354
|
+
import { navigationItems } from './navigationConfig';
|
|
355
|
+
|
|
356
|
+
interface AppSidebarProps {
|
|
357
|
+
drawerWidth: number;
|
|
358
|
+
open: boolean;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
export function AppSidebar({ drawerWidth, open }: AppSidebarProps) {
|
|
362
|
+
const { hasAnyRole } = useAuthUser();
|
|
363
|
+
const navigate = useNavigate();
|
|
364
|
+
const location = useLocation();
|
|
365
|
+
|
|
366
|
+
const visibleItems = navigationItems.filter(
|
|
367
|
+
(item) => !item.requiredRoles || hasAnyRole(item.requiredRoles),
|
|
368
|
+
);
|
|
369
|
+
|
|
370
|
+
return (
|
|
371
|
+
<Drawer
|
|
372
|
+
variant="persistent"
|
|
373
|
+
anchor="left"
|
|
374
|
+
open={open}
|
|
375
|
+
sx={{
|
|
376
|
+
width: drawerWidth,
|
|
377
|
+
flexShrink: 0,
|
|
378
|
+
'& .MuiDrawer-paper': { width: drawerWidth, boxSizing: 'border-box' },
|
|
379
|
+
}}
|
|
380
|
+
>
|
|
381
|
+
<Toolbar />
|
|
382
|
+
<List>
|
|
383
|
+
{visibleItems.map((item) => {
|
|
384
|
+
const Icon = item.icon;
|
|
385
|
+
const isActive = location.pathname.startsWith(item.path);
|
|
386
|
+
return (
|
|
387
|
+
<ListItemButton
|
|
388
|
+
key={item.path}
|
|
389
|
+
selected={isActive}
|
|
390
|
+
onClick={() => navigate(item.path)}
|
|
391
|
+
>
|
|
392
|
+
<ListItemIcon>
|
|
393
|
+
<Icon color={isActive ? 'primary' : 'inherit'} />
|
|
394
|
+
</ListItemIcon>
|
|
395
|
+
<ListItemText primary={item.label} />
|
|
396
|
+
</ListItemButton>
|
|
397
|
+
);
|
|
398
|
+
})}
|
|
399
|
+
</List>
|
|
400
|
+
</Drawer>
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
---
|
|
406
|
+
|
|
407
|
+
## Breadcrumb Pattern
|
|
408
|
+
|
|
409
|
+
```tsx
|
|
410
|
+
// src/shared/components/PageHeader.tsx
|
|
411
|
+
import { Box, Breadcrumbs, Link, Typography } from '@mui/material';
|
|
412
|
+
import { useNavigate } from 'react-router-dom';
|
|
413
|
+
|
|
414
|
+
export interface BreadcrumbItem {
|
|
415
|
+
label: string;
|
|
416
|
+
path?: string;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
interface PageHeaderProps {
|
|
420
|
+
title: string;
|
|
421
|
+
breadcrumbs?: BreadcrumbItem[];
|
|
422
|
+
actions?: React.ReactNode;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
export function PageHeader({ title, breadcrumbs, actions }: PageHeaderProps) {
|
|
426
|
+
const navigate = useNavigate();
|
|
427
|
+
|
|
428
|
+
return (
|
|
429
|
+
<Box sx={{ mb: 3 }}>
|
|
430
|
+
{breadcrumbs && (
|
|
431
|
+
<Breadcrumbs sx={{ mb: 1 }}>
|
|
432
|
+
{breadcrumbs.map((crumb, index) =>
|
|
433
|
+
crumb.path ? (
|
|
434
|
+
<Link
|
|
435
|
+
key={index}
|
|
436
|
+
component="button"
|
|
437
|
+
variant="body2"
|
|
438
|
+
onClick={() => navigate(crumb.path!)}
|
|
439
|
+
underline="hover"
|
|
440
|
+
color="inherit"
|
|
441
|
+
>
|
|
442
|
+
{crumb.label}
|
|
443
|
+
</Link>
|
|
444
|
+
) : (
|
|
445
|
+
<Typography key={index} variant="body2" color="text.primary">
|
|
446
|
+
{crumb.label}
|
|
447
|
+
</Typography>
|
|
448
|
+
),
|
|
449
|
+
)}
|
|
450
|
+
</Breadcrumbs>
|
|
451
|
+
)}
|
|
452
|
+
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
|
453
|
+
<Typography variant="h5" fontWeight={600}>
|
|
454
|
+
{title}
|
|
455
|
+
</Typography>
|
|
456
|
+
{actions && <Box sx={{ display: 'flex', gap: 1 }}>{actions}</Box>}
|
|
457
|
+
</Box>
|
|
458
|
+
</Box>
|
|
459
|
+
);
|
|
460
|
+
}
|
|
461
|
+
```
|