create-bluecopa-react-app 1.0.41 → 1.0.43

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.
Files changed (102) hide show
  1. package/README.md +7 -5
  2. package/package.json +1 -1
  3. package/templates/latest/.claude/settings.local.json +56 -0
  4. package/templates/latest/.env.example +8 -0
  5. package/templates/latest/Agent.md +598 -775
  6. package/templates/latest/CLAUDE.md +759 -0
  7. package/templates/latest/README.md +11 -2
  8. package/templates/latest/app/app.css +292 -85
  9. package/templates/latest/app/app.tsx +48 -39
  10. package/templates/latest/app/components/bluecopa-logo.tsx +20 -0
  11. package/templates/latest/app/components/charts/bar-chart.tsx +132 -0
  12. package/templates/latest/app/components/charts/base-chart.tsx +149 -0
  13. package/templates/latest/app/components/charts/chart-provider.tsx +71 -0
  14. package/templates/latest/app/components/charts/chart-theme.ts +262 -0
  15. package/templates/latest/app/components/charts/chart-utils.ts +142 -0
  16. package/templates/latest/app/components/charts/donut-chart.tsx +110 -0
  17. package/templates/latest/app/components/charts/index.ts +5 -0
  18. package/templates/latest/app/components/layouts/app-layout.tsx +22 -0
  19. package/templates/latest/app/components/layouts/app-sidebar.tsx +88 -0
  20. package/templates/latest/app/components/layouts/nav-main.tsx +50 -0
  21. package/templates/latest/app/components/layouts/nav-user.tsx +38 -0
  22. package/templates/latest/app/components/layouts/site-header.tsx +93 -0
  23. package/templates/latest/app/components/loading-screen.tsx +41 -0
  24. package/templates/latest/app/components/ui/ag-grid-table.tsx +79 -0
  25. package/templates/latest/app/components/ui/button.tsx +23 -23
  26. package/templates/latest/app/components/ui/card.tsx +20 -20
  27. package/templates/latest/app/components/ui/dropdown-menu.tsx +54 -49
  28. package/templates/latest/app/components/ui/input.tsx +8 -8
  29. package/templates/latest/app/components/ui/label.tsx +8 -8
  30. package/templates/latest/app/components/ui/separator.tsx +7 -7
  31. package/templates/latest/app/components/ui/sheet.tsx +43 -32
  32. package/templates/latest/app/components/ui/sidebar.tsx +240 -235
  33. package/templates/latest/app/components/ui/skeleton.tsx +4 -4
  34. package/templates/latest/app/components/ui/sonner.tsx +6 -9
  35. package/templates/latest/app/components/ui/tabs.tsx +15 -15
  36. package/templates/latest/app/components/ui/tooltip.tsx +18 -12
  37. package/templates/latest/app/constants/index.ts +31 -0
  38. package/templates/latest/app/contexts/app-context.tsx +201 -0
  39. package/templates/latest/app/hooks/use-mobile.ts +13 -12
  40. package/templates/latest/app/main.tsx +1 -1
  41. package/templates/latest/app/pages/dashboard.tsx +246 -0
  42. package/templates/latest/app/pages/payments.tsx +182 -0
  43. package/templates/latest/app/pages/settings.tsx +128 -0
  44. package/templates/latest/app/routes/index.tsx +19 -0
  45. package/templates/latest/app/single-spa.tsx +69 -186
  46. package/templates/latest/app/types/index.ts +37 -0
  47. package/templates/latest/app/utils/ag-grid-datasource.ts +63 -0
  48. package/templates/latest/app/utils/ag-grid-license.ts +12 -0
  49. package/templates/latest/app/utils/ag-grid-theme.ts +9 -0
  50. package/templates/latest/app/utils/component-style.ts +7 -0
  51. package/templates/latest/app/utils/style-drivers.ts +24 -0
  52. package/templates/latest/app/utils/utils.ts +10 -0
  53. package/templates/latest/components.json +3 -3
  54. package/templates/latest/index.html +30 -2
  55. package/templates/latest/package-lock.json +2717 -955
  56. package/templates/latest/package.json +19 -21
  57. package/templates/latest/preview/index.html +125 -285
  58. package/templates/latest/public/favicon.svg +1 -0
  59. package/templates/latest/vite.config.ts +2 -8
  60. package/templates/latest/app/components/app-sidebar.tsx +0 -182
  61. package/templates/latest/app/components/chart-area-interactive.tsx +0 -290
  62. package/templates/latest/app/components/data-table.tsx +0 -807
  63. package/templates/latest/app/components/nav-documents.tsx +0 -92
  64. package/templates/latest/app/components/nav-main.tsx +0 -40
  65. package/templates/latest/app/components/nav-secondary.tsx +0 -42
  66. package/templates/latest/app/components/nav-user.tsx +0 -111
  67. package/templates/latest/app/components/section-cards.tsx +0 -102
  68. package/templates/latest/app/components/site-header.tsx +0 -28
  69. package/templates/latest/app/components/ui/avatar.tsx +0 -53
  70. package/templates/latest/app/components/ui/badge.tsx +0 -46
  71. package/templates/latest/app/components/ui/breadcrumb.tsx +0 -109
  72. package/templates/latest/app/components/ui/chart.tsx +0 -352
  73. package/templates/latest/app/components/ui/checkbox.tsx +0 -30
  74. package/templates/latest/app/components/ui/drawer.tsx +0 -139
  75. package/templates/latest/app/components/ui/select.tsx +0 -183
  76. package/templates/latest/app/components/ui/table.tsx +0 -117
  77. package/templates/latest/app/components/ui/toggle-group.tsx +0 -73
  78. package/templates/latest/app/components/ui/toggle.tsx +0 -47
  79. package/templates/latest/app/data/data.json +0 -614
  80. package/templates/latest/app/data/mock-payments.json +0 -122
  81. package/templates/latest/app/data/mock-transactions.json +0 -128
  82. package/templates/latest/app/hooks/use-bluecopa-user.ts +0 -37
  83. package/templates/latest/app/lib/utils.ts +0 -6
  84. package/templates/latest/app/routes/apitest.tsx +0 -2118
  85. package/templates/latest/app/routes/comments.tsx +0 -588
  86. package/templates/latest/app/routes/dashboard.tsx +0 -36
  87. package/templates/latest/app/routes/payments.tsx +0 -342
  88. package/templates/latest/app/routes/statements.tsx +0 -493
  89. package/templates/latest/app/routes/websocket.tsx +0 -450
  90. package/templates/latest/app/routes.tsx +0 -22
  91. package/templates/latest/dist/assets/__federation_expose_App-D-lv9y21.js +0 -161
  92. package/templates/latest/dist/assets/__federation_fn_import-CzfA7kmP.js +0 -438
  93. package/templates/latest/dist/assets/__federation_shared_react-Bp6HVBS4.js +0 -16
  94. package/templates/latest/dist/assets/__federation_shared_react-dom-BCcRGiYp.js +0 -17
  95. package/templates/latest/dist/assets/client-Dms8K6Dw.js +0 -78879
  96. package/templates/latest/dist/assets/index-BrhXrqF7.js +0 -60
  97. package/templates/latest/dist/assets/index-BzNimew1.js +0 -69
  98. package/templates/latest/dist/assets/index-DMFtQdNS.js +0 -412
  99. package/templates/latest/dist/assets/remoteEntry.css +0 -3996
  100. package/templates/latest/dist/assets/remoteEntry.js +0 -88
  101. package/templates/latest/dist/favicon.ico +0 -0
  102. package/templates/latest/dist/index.html +0 -19
@@ -0,0 +1,759 @@
1
+ # CLAUDE.md — React MFE Boilerplate
2
+
3
+ This file provides guidance to Claude Code when working with the Bluecopa React MFE (Micro-Frontend) boilerplate.
4
+
5
+ ## Project Overview
6
+
7
+ A production-ready React microfrontend template for building apps that run inside the Bluecopa platform. Uses single-spa for MFE orchestration, Module Federation for runtime loading, and the Dream Light design system for UI.
8
+
9
+ ## Quick Start
10
+
11
+ ```bash
12
+ pnpm dev # Dev server on port 8080
13
+ pnpm build # TypeScript check + production build
14
+ pnpm start # Serve built MFE on port 3001
15
+ pnpm typecheck # TypeScript validation only
16
+ ```
17
+
18
+ ## Architecture
19
+
20
+ ### Entry Points
21
+
22
+ | File | Mode | Router | Use Case |
23
+ |------|------|--------|----------|
24
+ | `app/main.tsx` | Standalone | `BrowserRouter` | Local development |
25
+ | `app/single-spa.tsx` | MFE | `MemoryRouter` | Deployed inside Bluecopa shell |
26
+
27
+ ### MFE Props Interface
28
+
29
+ ```typescript
30
+ interface MfeProps {
31
+ apiBaseUrl?: string; // Bluecopa API URL
32
+ workspaceId?: string; // Workspace context
33
+ accessToken?: string; // JWT auth token
34
+ userId?: string; // Current user ID
35
+ basename?: string; // Router basename
36
+ }
37
+ ```
38
+
39
+ Props are passed from the host app via single-spa `customProps` or `manualMount()`.
40
+
41
+ ### Key Files
42
+
43
+ | File | Purpose |
44
+ |------|---------|
45
+ | `app/app.tsx` | Root component — QueryClient, ChartProvider, MFE config, AppProvider |
46
+ | `app/app.css` | Tailwind v4 config + Dream Light theme tokens |
47
+ | `app/contexts/app-context.tsx` | User, workspace settings, admin role context |
48
+ | `app/routes/index.tsx` | Route definitions (all wrapped in AppLayout) |
49
+ | `app/utils/utils.ts` | `cn()` utility with `copa` prefix support |
50
+ | `app/utils/component-style.ts` | Unprefixed style tokens (source of truth) |
51
+ | `app/utils/style-drivers.ts` | Auto-prefixes style tokens with `copa:` |
52
+ | `vite.config.ts` | Vite + Module Federation + Tailwind v4 |
53
+ | `components.json` | shadcn CLI config (prefix: `copa`) |
54
+
55
+ ### Directory Layout
56
+
57
+ ```
58
+ app/
59
+ ├── components/
60
+ │ ├── ui/ # shadcn/Dream Light components (prefixed)
61
+ │ ├── charts/ # ECharts-based chart components (BarChart, DonutChart, etc.)
62
+ │ └── layouts/ # AppLayout, AppSidebar, SiteHeader, NavMain, NavUser
63
+ ├── constants/ # Route paths, API defaults, query defaults, breakpoints
64
+ ├── contexts/ # AppContext (user, workspace, settings)
65
+ ├── hooks/ # use-mobile.ts (768px breakpoint)
66
+ ├── pages/ # Page components (dashboard, settings, etc.)
67
+ ├── routes/ # Route config
68
+ ├── types/ # MfeProps, AppUser, WorkspaceDataSettings
69
+ └── utils/ # cn(), style drivers
70
+ ```
71
+
72
+ ### Layout Structure
73
+
74
+ ```
75
+ AppLayout (SidebarProvider)
76
+ ├── AppSidebar (collapsible, Cmd/Ctrl+B toggle)
77
+ │ ├── NavMain (navigation items)
78
+ │ └── NavUser (user menu footer)
79
+ └── SidebarInset
80
+ ├── SiteHeader (breadcrumbs, user dropdown)
81
+ └── <Outlet /> (page content)
82
+ ```
83
+
84
+ ## Tailwind v4 + `copa:` Prefix — CRITICAL
85
+
86
+ All Tailwind classes MUST use the `copa:` prefix for MFE CSS isolation.
87
+
88
+ ### Prefix Order Rule
89
+
90
+ **Prefix ALWAYS comes first**, then variants, then utility:
91
+
92
+ ```
93
+ {prefix}:{variants}:{utility}
94
+ ```
95
+
96
+ ```tsx
97
+ // CORRECT
98
+ className="copa:flex copa:items-center copa:hover:bg-accent copa:md:block"
99
+ className="copa:group-data-[state=open]:block"
100
+
101
+ // WRONG — silently fails, no CSS generated, no errors
102
+ className="hover:copa:bg-accent" // ← variant before prefix
103
+ className="md:copa:block" // ← breakpoint before prefix
104
+ ```
105
+
106
+ ### CSS Architecture (app.css)
107
+
108
+ Three layers in order:
109
+
110
+ 1. **`@theme` (build-time)** — Resolved at compile time. Cannot use `var()` refs to runtime CSS variables. Must hardcode values.
111
+ 2. **`@theme inline` (runtime)** — Binds to CSS variables from `.mfe-root`. Used for dynamic radius, colors.
112
+ 3. **`.mfe-root` (scoped vars)** — All CSS variables scoped here, NOT `:root`. This prevents MFE style leaking.
113
+
114
+ ### cn() Utility
115
+
116
+ ```typescript
117
+ import { cn } from "~/utils/utils";
118
+
119
+ // Handles copa: prefix correctly via extendTailwindMerge({ prefix: "copa" })
120
+ <div className={cn("copa:flex copa:gap-2", isActive && "copa:bg-accent", className)} />
121
+ ```
122
+
123
+ ### Style Drivers
124
+
125
+ For reusable style tokens, define unprefixed in `component-style.ts`, consume prefixed from `style-drivers.ts`:
126
+
127
+ ```typescript
128
+ // component-style.ts (write unprefixed)
129
+ export const styles = {
130
+ card: "rounded-lg border bg-card text-card-foreground shadow-sm",
131
+ };
132
+
133
+ // style-drivers.ts (auto-prefixes)
134
+ // "rounded-lg border ..." → "copa:rounded-lg copa:border ..."
135
+
136
+ // Usage
137
+ import { prefixedStyles } from "~/utils/style-drivers";
138
+ <div className={prefixedStyles.card} />
139
+ ```
140
+
141
+ ## @bluecopa/react — API Integration
142
+
143
+ React Query hooks wrapping `@bluecopa/core`. All data fetching uses these hooks.
144
+
145
+ ### Setup Flow
146
+
147
+ 1. `copaSetConfig()` called in `app.tsx` useEffect with MFE props or env vars
148
+ 2. `QueryClientProvider` wraps the app (staleTime: 5min, retry: 1)
149
+ 3. Hooks become available in all child components
150
+
151
+ ### Configuration
152
+
153
+ ```typescript
154
+ import { copaSetConfig } from "@bluecopa/react";
155
+
156
+ copaSetConfig({
157
+ apiBaseUrl: "https://develop.bluecopa.com/api/v1",
158
+ workspaceId: "your-workspace-id",
159
+ accessToken: "your-jwt-token",
160
+ userId: "user-id",
161
+ });
162
+ ```
163
+
164
+ ### Environment Variables
165
+
166
+ ```bash
167
+ VITE_BLUECOPA_API_URL=https://develop.bluecopa.com/api/v1
168
+ VITE_BLUECOPA_WORKSPACE_ID=prod
169
+ ```
170
+
171
+ ### Available Hooks
172
+
173
+ #### User & Auth
174
+ - `useUser()` — Current user details
175
+ - `useGetAllUsers()` — All workspace users
176
+
177
+ #### Datasets & Tables
178
+ - `useDataset(id, { limit })` — Dataset records
179
+ - `useDatasetSample(id)` — Sample data
180
+ - `useRows(tableId, { filters, limit, offset, order, order_by })` — Supabase-style filtered rows
181
+ - `useInputTable(id, { limitParams, pageParams, sortParams })` — Input table data
182
+ - `useGetTableById(id)` — Table metadata
183
+ - `useInsertRow()` — Insert row (mutation, auto-invalidates)
184
+ - `useUpdateRow()` — Update row (mutation)
185
+ - `useDeleteRow()` — Delete row (mutation)
186
+
187
+ #### Metrics
188
+ - `useMetric(id)` — Metric data
189
+
190
+ #### Workbooks & Worksheets
191
+ - `useGetWorkbooksByType(type)` — Filter workbooks
192
+ - `useGetPublishedWorkbookById(id)` — Published workbook
193
+ - `useGetWorkbookDetails(id)` — Detailed info
194
+ - `useSaveWorkbook()` — Save (mutation)
195
+ - `usePublishWorkbook()` — Publish (mutation)
196
+ - `useGetWorksheets()` — All worksheets
197
+ - `useGetWorksheetsByType(type)` — Filter by type
198
+
199
+ #### Views & Runs
200
+ - `useGetViewById(id)` — View details
201
+ - `useGetViewsBySheetId(id)` — Views for sheet
202
+ - `useGetRunsByViewId(id)` — Run history
203
+ - `useGetRunResultById(id)` — Run result
204
+
205
+ #### Workflows
206
+ - `useTriggerWorkflow()` — Trigger execution (mutation)
207
+ - `useTriggerHttpWorkflow()` — HTTP trigger (mutation)
208
+ - `useGetWorkflowInstanceStatusById(id)` — Check status
209
+ - `useGetAllHttpTriggers()` — List triggers
210
+
211
+ #### Definitions
212
+ - `useRunDefinition()` — Execute definition (mutation)
213
+ - `useRunPublishedDefinition()` — Published definition (mutation)
214
+ - `useRunSampleDefinition()` — Sample definition (mutation)
215
+
216
+ #### Files
217
+ - `useFileUpload()` — Upload to S3 (mutation)
218
+ - `useFileDownload()` — Download file (mutation)
219
+ - `useGetFileUrlByFileId(id)` — Get file URL
220
+
221
+ #### Forms
222
+ - `useGetFormById(id)` — Form details
223
+ - `useGetFormSchema(id)` — Form schema
224
+ - `useGetFormData(id)` — Form data
225
+ - `useCreateOrUpdateForm()` — Create/update (mutation)
226
+
227
+ #### Chat & Comments
228
+ - `useCreateThread()` — Create thread (mutation)
229
+ - `useGetCommentsByThreadId(id)` — Thread comments
230
+ - `usePostComment()` — Post comment (mutation)
231
+ - `useUpdateComment()` / `useDeleteComment()` — Manage comments
232
+
233
+ #### Tasks & Inbox
234
+ - `useGetTaskDetails(id)` — Task details
235
+ - `useMarkTaskDone()` — Complete task (mutation)
236
+ - `useReassignTask()` — Reassign (mutation)
237
+ - `useGetAllInboxItems()` — Inbox items
238
+ - `useMarkItemAsRead()` / `useMarkItemAsUnread()` — Read state
239
+
240
+ #### Statements
241
+ - `useCreateStatementRun()` — Create run (mutation)
242
+ - `useGetStatementData(id)` — Statement data
243
+
244
+ #### Audit & Recon
245
+ - `useGetAuditLogs()` — Audit logs
246
+ - `useCreateAuditLog()` — Create entry (mutation)
247
+ - `useGetAllRecon()` — Reconciliations
248
+ - `useRunRecon()` — Run recon (mutation)
249
+
250
+ #### Pipelines
251
+ - `useGetAllTemplatedPipelines()` — Template pipelines
252
+
253
+ ### Re-exports
254
+
255
+ ```typescript
256
+ // Available from @bluecopa/react
257
+ export * as reactQuery from "@tanstack/react-query"; // QueryClient, useQuery, etc.
258
+ export * from "@bluecopa/core"; // copaSetConfig, copaApi, copaUtils
259
+ export { ReactQueryDevtools } from "@tanstack/react-query-devtools";
260
+ ```
261
+
262
+ ### Hook Usage Pattern
263
+
264
+ ```tsx
265
+ import { useDataset } from "@bluecopa/react";
266
+
267
+ function MyPage() {
268
+ const { data, isLoading, error, refetch } = useDataset("dataset-id", { limit: 100 });
269
+
270
+ if (isLoading) return <Skeleton />;
271
+ if (error) return <div>Error: {error.message}</div>;
272
+
273
+ // Parse response (handles multiple formats)
274
+ const rows = data?.records || data?.data || data?.rows || (Array.isArray(data) ? data : []);
275
+ return <DataTable data={rows} />;
276
+ }
277
+ ```
278
+
279
+ ## @bluecopa/core — API SDK
280
+
281
+ Low-level TypeScript SDK. Prefer `@bluecopa/react` hooks in components, use `copaApi` directly only when needed outside React.
282
+
283
+ ### Authentication
284
+
285
+ Custom header-based auth (not Bearer tokens):
286
+ - `X-COPA-TOKEN`: Access token
287
+ - `X-COPA-WORKSPACE-ID`: Workspace context
288
+
289
+ ### Direct API Usage
290
+
291
+ ```typescript
292
+ import { copaApi } from "@bluecopa/react";
293
+
294
+ const user = await copaApi.user.getLoggedInUserDetails();
295
+ const rows = await copaApi.inputTable.getRows("table-id", { limit: 50 });
296
+ await copaApi.inputTable.insertRow("table-id", { name: "New", value: 42 });
297
+ ```
298
+
299
+ ### WebSocket Integration
300
+
301
+ ```typescript
302
+ import { copaUtils } from "@bluecopa/react";
303
+
304
+ const ws = copaUtils.websocketUtils.WebsocketContextFactory.create("centrifugo", {
305
+ connectionUrl: "wss://...",
306
+ token: "...",
307
+ userId: "...",
308
+ });
309
+ ws.connect();
310
+ ws.bind("channel", "event", (data) => console.log(data));
311
+ ```
312
+
313
+ ## AppContext
314
+
315
+ Provides user data, workspace settings, and role info to all components.
316
+
317
+ ### Available Values
318
+
319
+ ```typescript
320
+ const {
321
+ user, // { userId, email, name, role, currentWorkspace, ... }
322
+ isLoading, // User fetch loading state
323
+ error, // User fetch error
324
+ refetch, // Refetch user data
325
+ isAdmin, // Derived: any team contains "admin"
326
+ workspaceIds, // User's workspace IDs
327
+ teams, // User's teams
328
+ users, // All workspace users
329
+ currentWorkspaceSettings, // { currency, timezone, dateFormat, fiscalYear, numberSettings, weekStartDay }
330
+ setCurrency, // Override currency
331
+ } = useAppContext();
332
+ ```
333
+
334
+ ### Workspace Settings Shape
335
+
336
+ ```typescript
337
+ {
338
+ currency: "USD",
339
+ timezone: "UTC",
340
+ dateFormat: "MM/DD/YYYY HH:mm:ss",
341
+ fiscalYear: { yearType: "calendar_year", startMonth?, startDate? },
342
+ numberSettings: { numberSystem: "intl", defaultPrecision: 2, nullPlaceholder? },
343
+ weekStartDay: "SUNDAY",
344
+ }
345
+ ```
346
+
347
+ ## Dream Light Design System
348
+
349
+ The UI design system. Source: `[https://blui.vercel.app/](https://blui.vercel.app/)`
350
+ Registry base URL: `https://blui.vercel.app/r/registry.json`
351
+
352
+ ### Adding Components
353
+
354
+ From either registry via shadcn CLI:
355
+
356
+ ```bash
357
+ # Dream Light / Bluecopa UI (preferred)
358
+ pnpm dlx shadcn@latest add @bluecopa-ui/button
359
+ pnpm dlx shadcn@latest add @bluecopa-ui/dialog
360
+ pnpm dlx shadcn@latest add @bluecopa-ui/data-table
361
+
362
+ # Standard shadcn/ui (fallback)
363
+ pnpm dlx shadcn@latest add button
364
+ ```
365
+
366
+ Registry URL: `https://blui.vercel.app/r/{name}.json`
367
+
368
+ ### Available Dream Light Components (70+)
369
+
370
+ **Inputs**: button, input, textarea, checkbox, radio-group, switch, select, select-advanced, combobox, number-input, pills, date-picker, date-range-picker, calendar, toggle
371
+
372
+ **Navigation**: tabs, horizontal-tabs, vertical-tabs, breadcrumb, pagination, dropdown-menu, command, sidebar, sidebar-with-icons
373
+
374
+ **Display**: text, badge, alert, avatar, toast, tooltip, spinner, loading-state, skeleton, empty-state, error-boundary, progress, kpi-card, ag-grid-table, data-table
375
+
376
+ **Containers**: card, section-card, dialog, sheet, side-panel, popover, accordion, scroll-area, separator, stack, kanban, tree-menu, topbar
377
+
378
+ **Layout**: scaffold, page-container, section, page-header
379
+
380
+ **Forms**: form-field, form-section, form-error-summary, label, search-box
381
+
382
+ **Advanced**: filter-builder, stepper, brand-logo, listing-view, listing-table, listing-header
383
+
384
+ ### Design Tokens
385
+
386
+ #### Colors (OKLCH)
387
+
388
+ ```css
389
+ --primary: #3548ff; /* Bluecopa blue */
390
+ --primary-foreground: oklch(100% 0 0); /* White on primary */
391
+ --background: oklch(0.975 0.005 280); /* Near-white */
392
+ --foreground: oklch(0.145 0 0); /* Near-black */
393
+ --destructive: oklch(0.577 0.245 27.325); /* Red */
394
+ --border: oklch(0.92 0.005 280); /* Light gray */
395
+ --muted: oklch(0.97 0.003 280);
396
+ --muted-foreground: oklch(0.556 0 0);
397
+ --accent: oklch(0.97 0.003 280);
398
+
399
+ /* Semantic status */
400
+ --status-success: oklch(0.72 0.19 142);
401
+ --status-warning: oklch(0.80 0.18 85);
402
+ --status-error: oklch(0.63 0.24 25);
403
+
404
+ /* Sidebar */
405
+ --sidebar: oklch(1 0 0); /* White */
406
+ --sidebar-foreground: oklch(0.145 0 0);
407
+ --sidebar-border: oklch(0.92 0.004 280);
408
+ ```
409
+
410
+ #### Typography Scale
411
+
412
+ ```css
413
+ /* Fluid type scale */
414
+ --text-xs: 0.702rem; /* Small captions */
415
+ --text-sm: 0.835rem; /* Body text, labels */
416
+ --text-md: 1.004rem; /* Prominent body */
417
+ --text-lg: 1.21rem; /* Subheadings */
418
+ --text-xl: 1.452rem; /* Section headers */
419
+ --text-2xl: 1.742rem; /* Large headings */
420
+ --text-3xl: 2.093rem;
421
+ --text-4xl: 2.505rem;
422
+ --text-5xl: 3.013rem; /* Hero display */
423
+
424
+ /* Heading tokens */
425
+ --text-h1: 3rem; /* 700 weight */
426
+ --text-h2: 2.25rem; /* 700 */
427
+ --text-h3: 1.875rem; /* 600 */
428
+ --text-h4: 1.5rem; /* 400 */
429
+ --text-h5: 1.25rem; /* 600 */
430
+ --text-h6: 1rem; /* 400 */
431
+
432
+ /* Font: Satoshi (sans-serif) */
433
+ ```
434
+
435
+ #### Custom Typography Utilities (defined in app.css)
436
+
437
+ ```
438
+ .text-page-title → 1.5rem, semibold
439
+ .text-section-title → 0.9375rem, semibold
440
+ .text-label → 0.75rem, uppercase, tracked
441
+ .text-value → 1.75rem, bold
442
+ .text-nav-section → 0.6875rem, uppercase, tracked
443
+ ```
444
+
445
+ #### Spacing
446
+
447
+ ```css
448
+ --radius: 1rem;
449
+ --card-radius: 1.25rem;
450
+ --rounded-input: 12px;
451
+
452
+ /* Motion */
453
+ --duration-fast: 100ms;
454
+ --duration-normal: 200ms;
455
+ --ease-default: cubic-bezier(0.4, 0, 0.2, 1);
456
+ ```
457
+
458
+ #### Custom Shadow Utilities
459
+
460
+ ```
461
+ .shadow-soft-xs → Subtle card shadow
462
+ .shadow-card → Multi-layer card shadow
463
+ .shadow-card-hover → Elevated hover state
464
+ ```
465
+
466
+ ### Chart Colors
467
+
468
+ ```css
469
+ --chart-1: oklch(0.55 0.15 255); /* Blue */
470
+ --chart-2: oklch(0.62 0.12 175); /* Teal */
471
+ --chart-3: oklch(0.70 0.13 80); /* Green */
472
+ --chart-4: oklch(0.55 0.14 295); /* Purple */
473
+ --chart-5: oklch(0.62 0.12 15); /* Red */
474
+ ```
475
+
476
+ ## Build System
477
+
478
+ ### Vite + Module Federation
479
+
480
+ ```
481
+ Build Output:
482
+ dist/assets/remoteEntry.js ← Module Federation entry (SystemJS format)
483
+ dist/assets/remoteEntry.css ← Single bundled CSS file
484
+ ```
485
+
486
+ - **Federation name**: `__copa_ext_app__react_route`
487
+ - **Exposed module**: `./App` → `./app/single-spa.tsx`
488
+ - **Shared**: `react`, `react-dom` (externalized in build)
489
+ - **Format**: SystemJS (for single-spa compatibility)
490
+ - **CSS**: Single file, no code splitting
491
+
492
+ ### TypeScript Config
493
+
494
+ - Path alias: `~/` → `./app/` (via `tsconfig.json` paths + `vite-tsconfig-paths`)
495
+ - Target: ES2020, JSX: react-jsx, strict mode
496
+
497
+ ### Docker
498
+
499
+ ```bash
500
+ docker build -t bluecopa-mfe .
501
+ # Serves dist/ via lightweight HTTP server
502
+ ```
503
+
504
+ ## MFE Integration
505
+
506
+ ### How the Host App Loads This MFE
507
+
508
+ ```javascript
509
+ // 1. Load Module Federation entry
510
+ System.import("__copa_ext_app__react_route/App");
511
+
512
+ // 2. Register with single-spa
513
+ registerApplication({
514
+ name: "bluecopa-preview",
515
+ app: () => System.import("__copa_ext_app__react_route/App"),
516
+ activeWhen: ["/app/external"],
517
+ customProps: {
518
+ apiBaseUrl: "https://develop.bluecopa.com/api/v1",
519
+ accessToken: "eyJ...",
520
+ workspaceId: "prod",
521
+ userId: "user-123",
522
+ },
523
+ });
524
+ ```
525
+
526
+ ### Single-spa Lifecycle
527
+
528
+ - **bootstrap**: No-op
529
+ - **mount**: Creates React root in `#single-spa-application:bluecopa-preview`, renders `<MemoryRouter><App {...props} /></MemoryRouter>`
530
+ - **unmount**: Calls `root.unmount()`
531
+ - **errorBoundary**: Catches errors, renders fallback UI
532
+
533
+ ### Manual Mount API (for non-single-spa hosts)
534
+
535
+ ```typescript
536
+ import { manualMount, manualUnmount } from "./single-spa";
537
+
538
+ await manualMount({
539
+ domElement: document.getElementById("mfe-container"),
540
+ apiBaseUrl: "https://...",
541
+ accessToken: "...",
542
+ workspaceId: "...",
543
+ userId: "...",
544
+ basename: "/app",
545
+ });
546
+ ```
547
+
548
+ ## Charts (ECharts)
549
+
550
+ Chart components live in `app/components/charts/` and are powered by Apache ECharts via `echarts-for-react`. The `<ChartProvider>` in `app.tsx` bridges CSS design tokens to the ECharts theme.
551
+
552
+ ### Architecture
553
+
554
+ | File | Purpose |
555
+ |------|---------|
556
+ | `chart-provider.tsx` | React context — registers ECharts theme from CSS tokens, auto-refreshes on theme change |
557
+ | `chart-theme.ts` | Reads `--chart-*` vars from `.mfe-root`, converts OKLCH to hex, builds ECharts theme object |
558
+ | `chart-utils.ts` | Composable option builders: `chartTooltip()`, `chartLegend()`, `chartGrid()`, `chartCategoryAxis()`, `chartValueAxis()`, `chartTitle()`, `chartAnimation()`, `chartColors()`, `formatChartValue()`, `formatChartCurrency()` |
559
+ | `base-chart.tsx` | Thin wrapper around `echarts-for-react` with loading/empty states, accessibility, responsive resize |
560
+ | `bar-chart.tsx` | Configurable bar chart (vertical/horizontal, stacked, click events, value labels) |
561
+ | `donut-chart.tsx` | Donut/pie chart with center text, legend, slice interactions |
562
+ | `index.ts` | Barrel exports |
563
+
564
+ ### Available Chart Components
565
+
566
+ #### BarChart
567
+
568
+ ```tsx
569
+ import { BarChart } from "~/components/charts";
570
+
571
+ <BarChart
572
+ data={{
573
+ categories: ["Jan", "Feb", "Mar"],
574
+ series: [{ name: "Revenue", data: [4200, 3800, 5100] }],
575
+ }}
576
+ height={280}
577
+ horizontal={false} // swap to horizontal bars
578
+ stacked={false} // stack multiple series
579
+ showValues={false} // show value labels on bars
580
+ showLegend={true} // auto-shows if >1 series
581
+ formatValue={(v) => `$${(v / 1000).toFixed(1)}K`}
582
+ onBarClick={({ name, seriesName, value }) => console.log(name, value)}
583
+ loading={false}
584
+ empty={false}
585
+ />
586
+ ```
587
+
588
+ #### DonutChart
589
+
590
+ ```tsx
591
+ import { DonutChart } from "~/components/charts";
592
+
593
+ <DonutChart
594
+ data={{
595
+ items: [
596
+ { name: "Payroll", value: 42 },
597
+ { name: "Operations", value: 28 },
598
+ { name: "Marketing", value: 16 },
599
+ ],
600
+ }}
601
+ height={280}
602
+ centerText="100%" // large text in donut center
603
+ centerSubText="Total" // smaller text below center
604
+ thickness={60} // donut ring thickness (%)
605
+ showLegend={true}
606
+ showLabels={false} // show labels on slices
607
+ onSliceClick={(params) => console.log(params)}
608
+ />
609
+ ```
610
+
611
+ #### BaseChart (escape hatch)
612
+
613
+ For custom ECharts configurations not covered by BarChart/DonutChart:
614
+
615
+ ```tsx
616
+ import { BaseChart } from "~/components/charts";
617
+ import { chartTooltip, chartGrid, chartCategoryAxis, chartValueAxis } from "~/components/charts/chart-utils";
618
+
619
+ <BaseChart
620
+ option={{
621
+ ...chartTooltip(),
622
+ ...chartGrid(),
623
+ xAxis: chartCategoryAxis(["A", "B", "C"]),
624
+ yAxis: chartValueAxis(),
625
+ series: [{ type: "line", data: [10, 20, 30], smooth: true }],
626
+ }}
627
+ height={300}
628
+ loading={false}
629
+ ariaLabel="Custom line chart"
630
+ />
631
+ ```
632
+
633
+ ### Chart Colors
634
+
635
+ Defined as `--chart-1` through `--chart-5` in `.mfe-root` (app.css). Hardcoded hex fallbacks in `chart-theme.ts` ensure colors render even if CSS vars aren't resolved:
636
+
637
+ | Token | OKLCH | Hex Fallback | Color |
638
+ |-------|-------|-------------|-------|
639
+ | `--chart-1` | `oklch(0.55 0.15 255)` | `#2563eb` | Blue |
640
+ | `--chart-2` | `oklch(0.62 0.12 175)` | `#0d9488` | Teal |
641
+ | `--chart-3` | `oklch(0.70 0.13 80)` | `#ca8a04` | Gold |
642
+ | `--chart-4` | `oklch(0.55 0.14 295)` | `#7c3aed` | Purple |
643
+ | `--chart-5` | `oklch(0.62 0.12 15)` | `#dc2626` | Red |
644
+
645
+ The theme auto-extends to 10 colors via tints/shades of the base 5.
646
+
647
+ ### Adding New Chart Types
648
+
649
+ To add a new chart type (e.g., LineChart, PieChart), follow the pattern in `bar-chart.tsx`:
650
+
651
+ 1. Create `app/components/charts/my-chart.tsx`
652
+ 2. Use `useMemo` to build an ECharts option from `chart-utils` helpers
653
+ 3. Render via `<BaseChart option={option} ... />`
654
+ 4. Export from `index.ts`
655
+
656
+ Source chart components from the Dream Light registry: `/Users/mahipat/projects/bluecopa/shadcn-ui-registry/src/components/charts/`
657
+
658
+ ## Toast Notifications
659
+
660
+ Uses [Sonner](https://sonner.emilkowal.dev/) for toast notifications. The `<Toaster />` component is rendered in `app.tsx`.
661
+
662
+ ### Usage
663
+
664
+ ```tsx
665
+ import { toast } from "sonner";
666
+
667
+ // Success
668
+ toast.success("Record saved", { description: "Changes applied." });
669
+
670
+ // Error
671
+ toast.error("Failed to save", { description: "Check your connection." });
672
+
673
+ // Warning
674
+ toast.warning("Unsaved changes");
675
+
676
+ // Info
677
+ toast.info("New data available");
678
+
679
+ // With action
680
+ toast("Item deleted", {
681
+ action: { label: "Undo", onClick: () => undoDelete() },
682
+ });
683
+
684
+ // Promise (loading → success/error)
685
+ toast.promise(saveData(), {
686
+ loading: "Saving...",
687
+ success: "Saved!",
688
+ error: "Failed to save",
689
+ });
690
+ ```
691
+
692
+ ### Toaster Config
693
+
694
+ The `<Toaster />` component from `~/components/ui/sonner` is pre-configured with Dream Light theme tokens. It renders in the app root (`app.tsx`) and is available globally — no additional setup needed.
695
+
696
+ ## Coding Conventions
697
+
698
+ ### Component Pattern
699
+
700
+ ```tsx
701
+ import { cn } from "~/utils/utils";
702
+
703
+ interface MyComponentProps {
704
+ className?: string;
705
+ children: React.ReactNode;
706
+ }
707
+
708
+ export function MyComponent({ className, children }: MyComponentProps) {
709
+ return (
710
+ <div className={cn("copa:flex copa:items-center copa:gap-2", className)}>
711
+ {children}
712
+ </div>
713
+ );
714
+ }
715
+ ```
716
+
717
+ ### Adding a New Page
718
+
719
+ 1. Create page in `app/pages/my-page.tsx`
720
+ 2. Add route in `app/routes/index.tsx` inside the `<Route element={<AppLayout />}>` group
721
+ 3. Add navigation item in `app/components/layouts/app-sidebar.tsx`
722
+
723
+ ### Adding a New UI Component
724
+
725
+ ```bash
726
+ # Preferred: Dream Light registry
727
+ pnpm dlx shadcn@latest add @bluecopa-ui/component-name
728
+
729
+ # Fallback: standard shadcn
730
+ pnpm dlx shadcn@latest add component-name
731
+ ```
732
+
733
+ Components install to `app/components/ui/` with `copa:` prefix pre-configured.
734
+
735
+ ### Icons
736
+
737
+ Use `lucide-react` or `@tabler/icons-react` (both available).
738
+
739
+ ### State Management
740
+
741
+ - **Server state**: `@bluecopa/react` hooks (React Query)
742
+ - **App context**: `useAppContext()` for user/workspace/settings
743
+ - **Local state**: `useState` / `useReducer`
744
+ - **No global state library** — keep it simple
745
+
746
+ ### Responsive Design
747
+
748
+ - Mobile breakpoint: 768px (`useIsMobile()` hook)
749
+ - Sidebar: Desktop = collapsible panel, Mobile = sheet overlay
750
+ - Use `copa:md:` prefix for desktop-only styles
751
+
752
+ ### Important Gotchas
753
+
754
+ 1. **Prefix order**: `copa:` MUST come before any variant. `copa:hover:bg-accent` not `hover:copa:bg-accent`
755
+ 2. **`@theme` is build-time**: Cannot reference `.mfe-root` CSS variables in `@theme` blocks. Use `@theme inline` for runtime bindings.
756
+ 3. **`.mfe-root` not `:root`**: All CSS variables scoped to `.mfe-root` for MFE isolation
757
+ 4. **Breakpoints with prefix**: Must explicitly define `--breakpoint-*` vars in `@theme` (Tailwind v4 doesn't auto-include them with prefix)
758
+ 5. **ReactQueryDevtools**: Only rendered in dev mode (`import.meta.env.DEV`)
759
+ 6. **Config before queries**: `copaSetConfig()` must complete before React Query hooks fire. The app gates rendering behind `isConfigured` state.