chati-dev 4.3.1 → 4.4.0
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/README.md +18 -13
- package/bin/chati.js +12 -4
- package/framework/agents/build/dev.md +5 -1
- package/framework/agents/discover/greenfield-wu.md +16 -1
- package/framework/agents/plan/tasks.md +31 -0
- package/framework/agents/plan/ux-brand-architect.md +21 -2
- package/framework/agents/plan/ux-component-engineer.md +10 -0
- package/framework/agents/quality/qa-implementation.md +1 -1
- package/framework/agents/quality/qa-planning.md +1 -1
- package/framework/agents/quality/qa-visual.md +24 -21
- package/framework/config.yaml +5 -4
- package/framework/constitution.md +58 -11
- package/framework/context/governance.md +12 -10
- package/framework/context/root.md +2 -2
- package/framework/data/entity-registry.yaml +12 -4
- package/framework/data/qa-rubrics.yaml +335 -0
- package/framework/domains/constitution.yaml +3 -3
- package/framework/executors/json-validate.js +80 -0
- package/framework/executors/npm-script.js +72 -0
- package/framework/executors/path-check.js +67 -0
- package/framework/executors/registry.yaml +36 -0
- package/framework/hooks/advance-trigger.js +47 -3
- package/framework/hooks/constitution-guard.js +9 -4
- package/framework/hooks/git-push-authority.js +113 -0
- package/framework/hooks/license-guard.js +59 -9
- package/framework/hooks/model-governance.js +40 -17
- package/framework/hooks/prism-engine.js +78 -11
- package/framework/hooks/session-digest.js +12 -3
- package/framework/hooks/settings.json +7 -3
- package/framework/hooks/style-guard.js +52 -3
- package/framework/hooks/team-quality-gate.js +43 -5
- package/framework/i18n/en.yaml +3 -3
- package/framework/i18n/es.yaml +3 -3
- package/framework/i18n/fr.yaml +3 -3
- package/framework/i18n/pt.yaml +3 -3
- package/framework/intelligence/confidence.yaml +85 -6
- package/framework/intelligence/context-engine.md +9 -5
- package/framework/intelligence/decision-engine.md +1 -1
- package/framework/orchestrator/chati-router.js +1 -1
- package/framework/orchestrator/chati-update.md +19 -3
- package/framework/orchestrator/chati.md +19 -1
- package/framework/quality-gates/planning-gate.md +2 -0
- package/framework/scaffold/motion-premium/scaffold.yaml +2 -1
- package/framework/scaffold/motion-premium-3d/components/webgl/WebGLContext.tsx.template +1 -1
- package/framework/scaffold/saas-dashboard/README.md +58 -0
- package/framework/scaffold/saas-dashboard/app/dashboard/page.tsx.template +546 -0
- package/framework/scaffold/saas-dashboard/app/globals.css.template +746 -0
- package/framework/scaffold/saas-dashboard/app/layout.tsx.template +192 -0
- package/framework/scaffold/saas-dashboard/app/shell.css.template +1070 -0
- package/framework/scaffold/saas-dashboard/components/data/Chart.tsx.template +725 -0
- package/framework/scaffold/saas-dashboard/components/data/DataTable.tsx.template +623 -0
- package/framework/scaffold/saas-dashboard/components/data/DataTable.types.ts.template +104 -0
- package/framework/scaffold/saas-dashboard/components/data/DataTablePagination.tsx.template +234 -0
- package/framework/scaffold/saas-dashboard/components/data/SkeletonBlock.tsx.template +111 -0
- package/framework/scaffold/saas-dashboard/components/data/StatCard.tsx.template +393 -0
- package/framework/scaffold/saas-dashboard/components/data/StatCardGrid.tsx.template +121 -0
- package/framework/scaffold/saas-dashboard/components/feedback/EmptyState.tsx.template +281 -0
- package/framework/scaffold/saas-dashboard/components/interaction/CommandPalette.tsx.template +767 -0
- package/framework/scaffold/saas-dashboard/components/interaction/Field.tsx.template +351 -0
- package/framework/scaffold/saas-dashboard/components/interaction/Form.tsx.template +424 -0
- package/framework/scaffold/saas-dashboard/components/primitives/Badge.tsx.template +193 -0
- package/framework/scaffold/saas-dashboard/components/primitives/Button.tsx.template +261 -0
- package/framework/scaffold/saas-dashboard/components/primitives/Card.tsx.template +287 -0
- package/framework/scaffold/saas-dashboard/components/primitives/Input.tsx.template +310 -0
- package/framework/scaffold/saas-dashboard/components/primitives/Spinner.tsx.template +135 -0
- package/framework/scaffold/saas-dashboard/components/primitives/index.ts.template +60 -0
- package/framework/scaffold/saas-dashboard/components/shell/AppShell.tsx.template +174 -0
- package/framework/scaffold/saas-dashboard/components/shell/Breadcrumb.tsx.template +186 -0
- package/framework/scaffold/saas-dashboard/components/shell/Container.tsx.template +86 -0
- package/framework/scaffold/saas-dashboard/components/shell/NavItem.tsx.template +147 -0
- package/framework/scaffold/saas-dashboard/components/shell/Sidebar.tsx.template +378 -0
- package/framework/scaffold/saas-dashboard/components/shell/SidebarContext.tsx.template +169 -0
- package/framework/scaffold/saas-dashboard/components/shell/ThemeToggle.tsx.template +128 -0
- package/framework/scaffold/saas-dashboard/components/shell/Topbar.tsx.template +301 -0
- package/framework/scaffold/saas-dashboard/lib/brand.ts.template +324 -0
- package/framework/scaffold/saas-dashboard/scaffold.yaml +225 -0
- package/framework/schemas/session.schema.json +5 -0
- package/framework/schemas/task.schema.json +9 -0
- package/framework/scripts/visual-qa.js +87 -1
- package/framework/tasks/orchestrator-health.md +4 -4
- package/framework/tasks/qa-impl-verdict.md +11 -5
- package/framework/templates/qa-gate-tmpl.yaml +24 -11
- package/package.json +3 -3
- package/src/config/context-file-generator.js +0 -6
- package/src/dashboard/renderer.js +0 -36
- package/src/executors/runner.js +204 -0
- package/src/installer/core.js +2 -1
- package/src/installer/templates.js +5 -6
- package/src/intelligence/registry-manager.js +1 -1
- package/src/license/client.js +9 -3
- package/src/license/commands.js +12 -2
- package/src/license/machine-id.js +42 -1
- package/src/memory/gotchas.js +58 -40
- package/src/memory/magic-docs.js +1 -1
- package/src/memory/session-digest.js +9 -4
- package/src/orchestrator/cli.js +174 -14
- package/src/orchestrator/doctor.js +98 -16
- package/src/orchestrator/pipeline-manager.js +44 -22
- package/src/orchestrator/session-manager.js +56 -8
- package/src/telemetry/sender.js +7 -19
- package/src/terminal/run-team.js +3 -3
- package/src/upgrade/tracked-files-detector.js +34 -16
- package/src/utils/feature-flags.js +1 -1
- package/src/utils/flatten-entities.js +4 -40
- package/src/utils/schema-validator.js +0 -14
- package/src/wizard/i18n.js +3 -3
- package/src/api/index.js +0 -120
- package/src/autonomy/autonomous-gate.js +0 -294
- package/src/autonomy/build-loop.js +0 -281
- package/src/autonomy/build-state.js +0 -286
- package/src/autonomy/cause-analyzer.js +0 -177
- package/src/autonomy/escalation.js +0 -214
- package/src/autonomy/index.js +0 -51
- package/src/autonomy/mode-manager.js +0 -225
- package/src/autonomy/mode-suggester.js +0 -283
- package/src/autonomy/progress-reporter.js +0 -275
- package/src/autonomy/safety-net.js +0 -370
- package/src/config/agent-customizer.js +0 -231
- package/src/decision/analyzer.js +0 -291
- package/src/decision/engine.js +0 -250
- package/src/decision/index.js +0 -38
- package/src/decision/registry-healer.js +0 -468
- package/src/decision/registry-updater.js +0 -339
- package/src/extensions/loader.js +0 -145
- package/src/extensions/registry.js +0 -134
- package/src/gates/circuit-breaker.js +0 -151
- package/src/gates/g1-planning-complete.js +0 -154
- package/src/gates/g2-qa-planning.js +0 -156
- package/src/gates/g3-implementation.js +0 -215
- package/src/gates/g4-qa-implementation.js +0 -240
- package/src/gates/g5-deploy-ready.js +0 -181
- package/src/gates/gate-base.js +0 -185
- package/src/gates/index.js +0 -46
- package/src/health/auto-fix.js +0 -216
- package/src/health/engine.js +0 -246
- package/src/merger/semantic-merger.js +0 -292
- package/src/preview/detector.js +0 -238
- package/src/preview/index.js +0 -20
- package/src/preview/launcher.js +0 -235
- package/src/preview/log-buffer.js +0 -103
- package/src/quality/metrics-collector.js +0 -281
- package/src/quality/test-runner.js +0 -366
- package/src/tasks/executor.js +0 -195
- package/src/tasks/index.js +0 -4
- package/src/tasks/loader.js +0 -210
- package/src/tasks/router.js +0 -182
- package/src/utils/event-bus.js +0 -126
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# saas-dashboard scaffold preset
|
|
2
|
+
|
|
3
|
+
A premium SaaS dashboard and app-shell starting point. Where the motion-premium
|
|
4
|
+
presets target animated showcase sites, this preset targets the build-me-a-tool
|
|
5
|
+
category: a data-dense application shell that looks designed, not generated.
|
|
6
|
+
|
|
7
|
+
## What it ships
|
|
8
|
+
|
|
9
|
+
29 templates across foundation, primitives, shell, data, interaction, and a
|
|
10
|
+
composed sample page:
|
|
11
|
+
|
|
12
|
+
- **Foundation**: `app/globals.css` (the full token system, fluid type scale,
|
|
13
|
+
reduced-motion handling, the `xl` breakpoint override), `lib/brand.ts` (typed
|
|
14
|
+
tokens), and the single `Container` authority for max-width and padding.
|
|
15
|
+
- **Primitives**: `Button`, `Card`, `Input`, `Badge`, `Spinner`, each with full
|
|
16
|
+
resting, hover, active, disabled, and focus states.
|
|
17
|
+
- **Shell**: `AppShell`, `Sidebar` (collapsible, keyboard-accessible, responsive
|
|
18
|
+
drawer), `Topbar` (search trigger, breadcrumb, user menu), `NavItem`,
|
|
19
|
+
`ThemeToggle`, `Breadcrumb`.
|
|
20
|
+
- **Data**: `DataTable` (typed, sortable, sticky header, selection, empty and
|
|
21
|
+
loading states), `StatCard` and `StatCardGrid` (KPI with delta semantics and a
|
|
22
|
+
sparkline), `Chart` (a self-contained SVG area chart, no chart dependency).
|
|
23
|
+
- **Interaction**: `CommandPalette` (Cmd+K, fuzzy filter, keyboard nav, hand
|
|
24
|
+
rolled, no extra dependency), `Field` and `Form` (labeled inputs, validation
|
|
25
|
+
states, never placeholder-only labels).
|
|
26
|
+
- **Feedback**: `EmptyState`.
|
|
27
|
+
- **Sample**: `app/dashboard/page.tsx` composes the shell, stat cards, a chart,
|
|
28
|
+
and a data table to demonstrate the preset.
|
|
29
|
+
|
|
30
|
+
## Design system
|
|
31
|
+
|
|
32
|
+
- **Color**: a calibrated dark palette built from a single neutral hue with even
|
|
33
|
+
perceptual luminance steps, a 6-color data-viz series, and a designed light
|
|
34
|
+
theme via `[data-theme="light"]` (never `filter: invert`). Every color is a
|
|
35
|
+
CSS custom property; components never carry a raw hex.
|
|
36
|
+
- **Type**: a 1.25 modular scale with fluid `clamp()` values, role-differentiated
|
|
37
|
+
line-heights, and optical tracking.
|
|
38
|
+
- **Space**: a 4px base unit tuned for data density.
|
|
39
|
+
- **Motion**: cubic-bezier easing matched to the metaphor, a duration scale, and
|
|
40
|
+
full reduced-motion support. No linear easing, no `transition: all`.
|
|
41
|
+
|
|
42
|
+
## Quality contract
|
|
43
|
+
|
|
44
|
+
This preset is built to clear the QA-Visual rubric
|
|
45
|
+
(`chati.dev/data/qa-rubrics.yaml`) on every dimension: layout, typography, color,
|
|
46
|
+
spacing, interaction, motion, accessibility (WCAG 2.1 AA), and performance
|
|
47
|
+
(animate only `transform` and `opacity`).
|
|
48
|
+
|
|
49
|
+
## Dependencies
|
|
50
|
+
|
|
51
|
+
`lucide-react` (icons) and `framer-motion` (micro-interactions). The chart and
|
|
52
|
+
the command palette are hand-rolled, so there is no chart library and no command
|
|
53
|
+
menu dependency.
|
|
54
|
+
|
|
55
|
+
## Brandable
|
|
56
|
+
|
|
57
|
+
The accent color (`ACCENT_HEX`, `ACCENT_HOVER_HEX`) is resolved at apply time
|
|
58
|
+
from your brand source. The neutral and semantic scales are the designed system.
|
|
@@ -0,0 +1,546 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* app/dashboard/page.tsx
|
|
3
|
+
* SaaS Dashboard Preset -- D_interaction phase (composed sample page)
|
|
4
|
+
*
|
|
5
|
+
* WHY THIS EXISTS
|
|
6
|
+
* ---------------
|
|
7
|
+
* A preset with only individual components is hard to evaluate. Teams adopting
|
|
8
|
+
* a scaffold need to see the components working together in a real layout:
|
|
9
|
+
* how tokens compose, how the grid breathes, how the type scale creates
|
|
10
|
+
* hierarchy across sections, and how the motion system feels at dashboard
|
|
11
|
+
* density.
|
|
12
|
+
*
|
|
13
|
+
* This file is the "show your work" page -- the proof that every design
|
|
14
|
+
* decision in the spec resolves coherently at the page level. It is ALSO
|
|
15
|
+
* the first thing a new developer opens, so it doubles as the definitive
|
|
16
|
+
* usage example for every component in the preset.
|
|
17
|
+
*
|
|
18
|
+
* LAYOUT ARCHITECTURE
|
|
19
|
+
* -------------------
|
|
20
|
+
* Section 1: StatCardGrid -- 4 KPI cards across, auto-fill responsive.
|
|
21
|
+
* Section 2: 2-column asymmetric grid -- Chart (wider) + secondary
|
|
22
|
+
* metric card (narrower). The asymmetry creates visual weight:
|
|
23
|
+
* the chart is the primary insight, the secondary card is context.
|
|
24
|
+
* Section 3: Full-width DataTable with realistic sample columns and
|
|
25
|
+
* pagination. Demonstrates sort indicators, status badges, and
|
|
26
|
+
* the empty state pattern.
|
|
27
|
+
*
|
|
28
|
+
* SPACING RATIONALE
|
|
29
|
+
* -----------------
|
|
30
|
+
* Sections are separated by --spacing-section (32px). Within sections,
|
|
31
|
+
* components are separated by --spacing-card-gap (20px). This two-level
|
|
32
|
+
* rhythm creates a clear hierarchy: sections are distinct regions,
|
|
33
|
+
* components within a section are related.
|
|
34
|
+
*
|
|
35
|
+
* The page wrapper uses padding-top to account for the sticky topbar
|
|
36
|
+
* (--spacing-topbar-h = 48px). This is set in AppShell's main scroll area,
|
|
37
|
+
* not here -- but it is documented here because the visual consequence
|
|
38
|
+
* (content not hiding behind topbar) is visible at this level.
|
|
39
|
+
*
|
|
40
|
+
* DATA
|
|
41
|
+
* ----
|
|
42
|
+
* All data is static TypeScript constants defined at the bottom of this
|
|
43
|
+
* file. No API calls, no SWR/React Query, no suspense boundaries for
|
|
44
|
+
* the sake of this demo. Replace with your data fetching layer.
|
|
45
|
+
*
|
|
46
|
+
* TOKEN USAGE
|
|
47
|
+
* -----------
|
|
48
|
+
* All spacing via CSS custom properties. No raw px values in JSX styles.
|
|
49
|
+
* Colors via semantic token layer (--color-text-*, --color-bg-*, etc.).
|
|
50
|
+
*/
|
|
51
|
+
|
|
52
|
+
'use client';
|
|
53
|
+
|
|
54
|
+
import { Container } from '@/components/shell/Container';
|
|
55
|
+
import { StatCard } from '@/components/data/StatCard';
|
|
56
|
+
import { StatCardGrid } from '@/components/data/StatCardGrid';
|
|
57
|
+
import { Chart } from '@/components/data/Chart';
|
|
58
|
+
import { DataTable } from '@/components/data/DataTable';
|
|
59
|
+
import { EmptyState } from '@/components/feedback/EmptyState';
|
|
60
|
+
import { Badge } from '@/components/primitives/Badge';
|
|
61
|
+
import { Button } from '@/components/primitives/Button';
|
|
62
|
+
import {
|
|
63
|
+
DollarSign,
|
|
64
|
+
Users,
|
|
65
|
+
TrendingUp,
|
|
66
|
+
Activity,
|
|
67
|
+
FileText,
|
|
68
|
+
Plus,
|
|
69
|
+
} from 'lucide-react';
|
|
70
|
+
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
// Mock data
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
|
|
75
|
+
const revenueSparkline = [12, 18, 14, 22, 19, 27, 24, 31, 28, 35, 33, 42];
|
|
76
|
+
const userSparkline = [340, 382, 360, 410, 395, 440, 420, 475, 460, 510, 490, 540];
|
|
77
|
+
const conversionSparkline = [3.1, 3.4, 3.2, 3.7, 3.5, 3.9, 3.8, 4.1, 3.9, 4.3, 4.2, 4.6];
|
|
78
|
+
const mrrSparkline = [8200, 8600, 8400, 9100, 8900, 9600, 9400, 10200, 9900, 10800, 10500, 11200];
|
|
79
|
+
|
|
80
|
+
const areaChartData = [
|
|
81
|
+
{ month: 'Jan', revenue: 12400, users: 340 },
|
|
82
|
+
{ month: 'Feb', revenue: 15200, users: 382 },
|
|
83
|
+
{ month: 'Mar', revenue: 13800, users: 360 },
|
|
84
|
+
{ month: 'Apr', revenue: 18100, users: 410 },
|
|
85
|
+
{ month: 'May', revenue: 16900, users: 395 },
|
|
86
|
+
{ month: 'Jun', revenue: 21400, users: 440 },
|
|
87
|
+
{ month: 'Jul', revenue: 19800, users: 420 },
|
|
88
|
+
{ month: 'Aug', revenue: 24200, users: 475 },
|
|
89
|
+
{ month: 'Sep', revenue: 22100, users: 460 },
|
|
90
|
+
{ month: 'Oct', revenue: 27800, users: 510 },
|
|
91
|
+
{ month: 'Nov', revenue: 25400, users: 490 },
|
|
92
|
+
{ month: 'Dec', revenue: 31600, users: 540 },
|
|
93
|
+
];
|
|
94
|
+
|
|
95
|
+
const chartSeries = [
|
|
96
|
+
{ key: 'revenue', label: 'Revenue', color: 'var(--color-viz-1)' },
|
|
97
|
+
{ key: 'users', label: 'Active Users', color: 'var(--color-viz-2)' },
|
|
98
|
+
];
|
|
99
|
+
|
|
100
|
+
export type TransactionRow = {
|
|
101
|
+
id: string;
|
|
102
|
+
customer: string;
|
|
103
|
+
email: string;
|
|
104
|
+
amount: number;
|
|
105
|
+
status: 'completed' | 'pending' | 'failed';
|
|
106
|
+
date: string;
|
|
107
|
+
plan: string;
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const transactions: TransactionRow[] = [
|
|
111
|
+
{ id: 'TXN-001', customer: 'Aria Nakamura', email: 'aria@synth.io', amount: 299, status: 'completed', date: '2026-06-07', plan: 'Pro' },
|
|
112
|
+
{ id: 'TXN-002', customer: 'Luca Ferreira', email: 'luca@vela.co', amount: 99, status: 'completed', date: '2026-06-07', plan: 'Starter' },
|
|
113
|
+
{ id: 'TXN-003', customer: 'Mira Osei', email: 'mira@datavault.com', amount: 899, status: 'pending', date: '2026-06-06', plan: 'Enterprise' },
|
|
114
|
+
{ id: 'TXN-004', customer: 'Dev Patel', email: 'dev@cortex.ai', amount: 299, status: 'completed', date: '2026-06-06', plan: 'Pro' },
|
|
115
|
+
{ id: 'TXN-005', customer: 'Soren Lindqvist', email: 'soren@nordic.se', amount: 99, status: 'failed', date: '2026-06-05', plan: 'Starter' },
|
|
116
|
+
{ id: 'TXN-006', customer: 'Yuki Tanaka', email: 'yuki@flux.jp', amount: 299, status: 'completed', date: '2026-06-05', plan: 'Pro' },
|
|
117
|
+
{ id: 'TXN-007', customer: 'Chiara Russo', email: 'chiara@bloom.it', amount: 899, status: 'completed', date: '2026-06-04', plan: 'Enterprise' },
|
|
118
|
+
{ id: 'TXN-008', customer: 'Omar Hassan', email: 'omar@pulse.ae', amount: 99, status: 'pending', date: '2026-06-04', plan: 'Starter' },
|
|
119
|
+
];
|
|
120
|
+
|
|
121
|
+
const tableColumns = [
|
|
122
|
+
{
|
|
123
|
+
key: 'customer' as const,
|
|
124
|
+
header: 'Customer',
|
|
125
|
+
render: (row: TransactionRow) => (
|
|
126
|
+
<div>
|
|
127
|
+
<div
|
|
128
|
+
style={{
|
|
129
|
+
fontSize: 'var(--type-small)',
|
|
130
|
+
fontWeight: 500,
|
|
131
|
+
color: 'var(--color-text-primary)',
|
|
132
|
+
}}
|
|
133
|
+
>
|
|
134
|
+
{row.customer}
|
|
135
|
+
</div>
|
|
136
|
+
<div
|
|
137
|
+
style={{
|
|
138
|
+
fontSize: 'var(--type-caption)',
|
|
139
|
+
color: 'var(--color-text-muted)',
|
|
140
|
+
marginTop: '2px',
|
|
141
|
+
}}
|
|
142
|
+
>
|
|
143
|
+
{row.email}
|
|
144
|
+
</div>
|
|
145
|
+
</div>
|
|
146
|
+
),
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
key: 'plan' as const,
|
|
150
|
+
header: 'Plan',
|
|
151
|
+
render: (row: TransactionRow) => (
|
|
152
|
+
<Badge
|
|
153
|
+
variant={
|
|
154
|
+
row.plan === 'Enterprise'
|
|
155
|
+
? 'accent'
|
|
156
|
+
: row.plan === 'Pro'
|
|
157
|
+
? 'info'
|
|
158
|
+
: 'default'
|
|
159
|
+
}
|
|
160
|
+
size="sm"
|
|
161
|
+
>
|
|
162
|
+
{row.plan}
|
|
163
|
+
</Badge>
|
|
164
|
+
),
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
key: 'amount' as const,
|
|
168
|
+
header: 'Amount',
|
|
169
|
+
render: (row: TransactionRow) => (
|
|
170
|
+
<span
|
|
171
|
+
style={{
|
|
172
|
+
fontFamily: 'var(--font-mono)',
|
|
173
|
+
fontSize: 'var(--type-mono)',
|
|
174
|
+
color: 'var(--color-text-primary)',
|
|
175
|
+
fontWeight: 500,
|
|
176
|
+
}}
|
|
177
|
+
>
|
|
178
|
+
${row.amount.toLocaleString()}
|
|
179
|
+
</span>
|
|
180
|
+
),
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
key: 'status' as const,
|
|
184
|
+
header: 'Status',
|
|
185
|
+
render: (row: TransactionRow) => (
|
|
186
|
+
<Badge
|
|
187
|
+
variant={
|
|
188
|
+
row.status === 'completed'
|
|
189
|
+
? 'success'
|
|
190
|
+
: row.status === 'pending'
|
|
191
|
+
? 'warning'
|
|
192
|
+
: 'danger'
|
|
193
|
+
}
|
|
194
|
+
dot
|
|
195
|
+
size="sm"
|
|
196
|
+
>
|
|
197
|
+
{row.status.charAt(0).toUpperCase() + row.status.slice(1)}
|
|
198
|
+
</Badge>
|
|
199
|
+
),
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
key: 'date' as const,
|
|
203
|
+
header: 'Date',
|
|
204
|
+
render: (row: TransactionRow) => (
|
|
205
|
+
<span
|
|
206
|
+
style={{
|
|
207
|
+
fontSize: 'var(--type-caption)',
|
|
208
|
+
color: 'var(--color-text-muted)',
|
|
209
|
+
fontFamily: 'var(--font-mono)',
|
|
210
|
+
}}
|
|
211
|
+
>
|
|
212
|
+
{row.date}
|
|
213
|
+
</span>
|
|
214
|
+
),
|
|
215
|
+
},
|
|
216
|
+
];
|
|
217
|
+
|
|
218
|
+
// ---------------------------------------------------------------------------
|
|
219
|
+
// Page
|
|
220
|
+
// ---------------------------------------------------------------------------
|
|
221
|
+
|
|
222
|
+
export default function DashboardPage() {
|
|
223
|
+
return (
|
|
224
|
+
<Container>
|
|
225
|
+
{/* Page header */}
|
|
226
|
+
<header
|
|
227
|
+
style={{
|
|
228
|
+
paddingTop: 'var(--spacing-section)',
|
|
229
|
+
paddingBottom: 'var(--spacing-section)',
|
|
230
|
+
borderBottom: '1px solid var(--color-border-default)',
|
|
231
|
+
marginBottom: 'var(--spacing-section)',
|
|
232
|
+
}}
|
|
233
|
+
>
|
|
234
|
+
<div
|
|
235
|
+
style={{
|
|
236
|
+
display: 'flex',
|
|
237
|
+
alignItems: 'flex-start',
|
|
238
|
+
justifyContent: 'space-between',
|
|
239
|
+
gap: 'var(--spacing-card-gap)',
|
|
240
|
+
flexWrap: 'wrap',
|
|
241
|
+
}}
|
|
242
|
+
>
|
|
243
|
+
<div>
|
|
244
|
+
<h1
|
|
245
|
+
style={{
|
|
246
|
+
fontSize: 'var(--type-h1)',
|
|
247
|
+
lineHeight: 'var(--type-h1-lh)',
|
|
248
|
+
letterSpacing: 'var(--type-h1-ls)',
|
|
249
|
+
fontWeight: 700,
|
|
250
|
+
color: 'var(--color-text-primary)',
|
|
251
|
+
margin: 0,
|
|
252
|
+
}}
|
|
253
|
+
>
|
|
254
|
+
Dashboard
|
|
255
|
+
</h1>
|
|
256
|
+
<p
|
|
257
|
+
style={{
|
|
258
|
+
fontSize: 'var(--type-small)',
|
|
259
|
+
color: 'var(--color-text-secondary)',
|
|
260
|
+
margin: 'var(--space-2) 0 0',
|
|
261
|
+
}}
|
|
262
|
+
>
|
|
263
|
+
Welcome back. Here is what is happening with your product.
|
|
264
|
+
</p>
|
|
265
|
+
</div>
|
|
266
|
+
|
|
267
|
+
{/* Header actions */}
|
|
268
|
+
<div style={{ display: 'flex', gap: 'var(--space-3)', alignItems: 'center' }}>
|
|
269
|
+
<Button variant="secondary" iconLeft={<FileText size={16} aria-hidden="true" />}>
|
|
270
|
+
Export report
|
|
271
|
+
</Button>
|
|
272
|
+
<Button variant="primary" iconLeft={<Plus size={16} aria-hidden="true" />}>
|
|
273
|
+
New transaction
|
|
274
|
+
</Button>
|
|
275
|
+
</div>
|
|
276
|
+
</div>
|
|
277
|
+
</header>
|
|
278
|
+
|
|
279
|
+
{/* Section 1: KPI stat cards */}
|
|
280
|
+
<section aria-label="Key performance indicators">
|
|
281
|
+
<StatCardGrid>
|
|
282
|
+
<StatCard
|
|
283
|
+
label="Total Revenue"
|
|
284
|
+
value="$142,840"
|
|
285
|
+
delta="+11.2%"
|
|
286
|
+
previousValue="$128,400"
|
|
287
|
+
unit="USD"
|
|
288
|
+
icon={<DollarSign size={20} aria-hidden="true" />}
|
|
289
|
+
trend="up"
|
|
290
|
+
sparklineData={revenueSparkline}
|
|
291
|
+
/>
|
|
292
|
+
<StatCard
|
|
293
|
+
label="Active Users"
|
|
294
|
+
value="5,420"
|
|
295
|
+
delta="+10.8%"
|
|
296
|
+
previousValue="4,890"
|
|
297
|
+
unit="users"
|
|
298
|
+
icon={<Users size={20} aria-hidden="true" />}
|
|
299
|
+
trend="up"
|
|
300
|
+
sparklineData={userSparkline}
|
|
301
|
+
/>
|
|
302
|
+
<StatCard
|
|
303
|
+
label="Conversion Rate"
|
|
304
|
+
value="4.6%"
|
|
305
|
+
delta="+0.5pp"
|
|
306
|
+
previousValue="4.1%"
|
|
307
|
+
unit="percent"
|
|
308
|
+
icon={<TrendingUp size={20} aria-hidden="true" />}
|
|
309
|
+
trend="up"
|
|
310
|
+
sparklineData={conversionSparkline}
|
|
311
|
+
/>
|
|
312
|
+
<StatCard
|
|
313
|
+
label="Monthly Recurring Revenue"
|
|
314
|
+
value="$11,200"
|
|
315
|
+
delta="+6.7%"
|
|
316
|
+
previousValue="$10,500"
|
|
317
|
+
unit="MRR"
|
|
318
|
+
icon={<Activity size={20} aria-hidden="true" />}
|
|
319
|
+
trend="up"
|
|
320
|
+
sparklineData={mrrSparkline}
|
|
321
|
+
/>
|
|
322
|
+
</StatCardGrid>
|
|
323
|
+
</section>
|
|
324
|
+
|
|
325
|
+
{/* Section 2: Chart + secondary metric (asymmetric 2-col grid) */}
|
|
326
|
+
<section
|
|
327
|
+
aria-label="Revenue and users trend"
|
|
328
|
+
style={{ marginTop: 'var(--spacing-section)' }}
|
|
329
|
+
>
|
|
330
|
+
<div
|
|
331
|
+
style={{
|
|
332
|
+
display: 'grid',
|
|
333
|
+
gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 340px), 1fr))',
|
|
334
|
+
gap: 'var(--spacing-card-gap)',
|
|
335
|
+
alignItems: 'start',
|
|
336
|
+
}}
|
|
337
|
+
>
|
|
338
|
+
{/* Area chart -- primary insight, wider column */}
|
|
339
|
+
<div
|
|
340
|
+
style={{
|
|
341
|
+
gridColumn: 'span 2',
|
|
342
|
+
// On wide viewports this naturally spans 2 of 3 grid columns
|
|
343
|
+
// On narrow viewports (single column) it fills full width
|
|
344
|
+
}}
|
|
345
|
+
>
|
|
346
|
+
<Chart
|
|
347
|
+
data={areaChartData}
|
|
348
|
+
series={chartSeries}
|
|
349
|
+
xKey="month"
|
|
350
|
+
ariaLabel="Revenue and subscriptions over the last 12 months"
|
|
351
|
+
formatX={(v: unknown) => String(v)}
|
|
352
|
+
formatY={(v: number) =>
|
|
353
|
+
v > 999 ? `$${(v / 1000).toFixed(0)}k` : String(v)
|
|
354
|
+
}
|
|
355
|
+
height={280}
|
|
356
|
+
/>
|
|
357
|
+
</div>
|
|
358
|
+
|
|
359
|
+
{/* Secondary metric card -- narrower, contextual */}
|
|
360
|
+
<div
|
|
361
|
+
style={{
|
|
362
|
+
backgroundColor: 'var(--color-bg-raised)',
|
|
363
|
+
border: '1px solid var(--color-border-default)',
|
|
364
|
+
borderRadius: 'var(--radius-lg)',
|
|
365
|
+
padding: 'var(--spacing-card-pad)',
|
|
366
|
+
boxShadow: 'var(--shadow-1)',
|
|
367
|
+
}}
|
|
368
|
+
>
|
|
369
|
+
<h2
|
|
370
|
+
style={{
|
|
371
|
+
fontSize: 'var(--type-h4)',
|
|
372
|
+
lineHeight: 'var(--type-h4-lh)',
|
|
373
|
+
letterSpacing: 'var(--type-h4-ls)',
|
|
374
|
+
fontWeight: 600,
|
|
375
|
+
color: 'var(--color-text-primary)',
|
|
376
|
+
margin: '0 0 var(--space-5)',
|
|
377
|
+
}}
|
|
378
|
+
>
|
|
379
|
+
Plan breakdown
|
|
380
|
+
</h2>
|
|
381
|
+
|
|
382
|
+
<ul
|
|
383
|
+
style={{
|
|
384
|
+
listStyle: 'none',
|
|
385
|
+
margin: 0,
|
|
386
|
+
padding: 0,
|
|
387
|
+
display: 'flex',
|
|
388
|
+
flexDirection: 'column',
|
|
389
|
+
gap: 'var(--space-4)',
|
|
390
|
+
}}
|
|
391
|
+
>
|
|
392
|
+
{[
|
|
393
|
+
{ label: 'Enterprise', count: 12, pct: 18, color: 'var(--color-viz-1)' },
|
|
394
|
+
{ label: 'Pro', count: 38, pct: 56, color: 'var(--color-viz-2)' },
|
|
395
|
+
{ label: 'Starter', count: 18, pct: 26, color: 'var(--color-viz-3)' },
|
|
396
|
+
].map((plan) => (
|
|
397
|
+
<li key={plan.label}>
|
|
398
|
+
<div
|
|
399
|
+
style={{
|
|
400
|
+
display: 'flex',
|
|
401
|
+
justifyContent: 'space-between',
|
|
402
|
+
marginBottom: 'var(--space-1)',
|
|
403
|
+
}}
|
|
404
|
+
>
|
|
405
|
+
<span
|
|
406
|
+
style={{
|
|
407
|
+
fontSize: 'var(--type-small)',
|
|
408
|
+
color: 'var(--color-text-secondary)',
|
|
409
|
+
}}
|
|
410
|
+
>
|
|
411
|
+
{plan.label}
|
|
412
|
+
</span>
|
|
413
|
+
<span
|
|
414
|
+
style={{
|
|
415
|
+
fontSize: 'var(--type-caption)',
|
|
416
|
+
fontFamily: 'var(--font-mono)',
|
|
417
|
+
color: 'var(--color-text-muted)',
|
|
418
|
+
}}
|
|
419
|
+
>
|
|
420
|
+
{plan.count} ({plan.pct}%)
|
|
421
|
+
</span>
|
|
422
|
+
</div>
|
|
423
|
+
{/* Progress bar -- animates only transform, not width */}
|
|
424
|
+
<div
|
|
425
|
+
style={{
|
|
426
|
+
height: '6px',
|
|
427
|
+
borderRadius: 'var(--radius-pill)',
|
|
428
|
+
backgroundColor: 'var(--color-bg-sunken)',
|
|
429
|
+
overflow: 'hidden',
|
|
430
|
+
}}
|
|
431
|
+
role="presentation"
|
|
432
|
+
aria-hidden="true"
|
|
433
|
+
>
|
|
434
|
+
<div
|
|
435
|
+
style={{
|
|
436
|
+
height: '100%',
|
|
437
|
+
width: '100%',
|
|
438
|
+
borderRadius: 'var(--radius-pill)',
|
|
439
|
+
backgroundColor: plan.color,
|
|
440
|
+
transformOrigin: 'left center',
|
|
441
|
+
transform: `scaleX(${plan.pct / 100})`,
|
|
442
|
+
transition: `transform var(--duration-layout) var(--ease-out)`,
|
|
443
|
+
}}
|
|
444
|
+
/>
|
|
445
|
+
</div>
|
|
446
|
+
</li>
|
|
447
|
+
))}
|
|
448
|
+
</ul>
|
|
449
|
+
|
|
450
|
+
<div
|
|
451
|
+
style={{
|
|
452
|
+
marginTop: 'var(--space-6)',
|
|
453
|
+
paddingTop: 'var(--space-5)',
|
|
454
|
+
borderTop: '1px solid var(--color-border-default)',
|
|
455
|
+
display: 'flex',
|
|
456
|
+
justifyContent: 'space-between',
|
|
457
|
+
alignItems: 'center',
|
|
458
|
+
}}
|
|
459
|
+
>
|
|
460
|
+
<span
|
|
461
|
+
style={{
|
|
462
|
+
fontSize: 'var(--type-caption)',
|
|
463
|
+
color: 'var(--color-text-muted)',
|
|
464
|
+
}}
|
|
465
|
+
>
|
|
466
|
+
Total customers
|
|
467
|
+
</span>
|
|
468
|
+
<span
|
|
469
|
+
style={{
|
|
470
|
+
fontSize: 'var(--type-h3)',
|
|
471
|
+
lineHeight: 'var(--type-h3-lh)',
|
|
472
|
+
fontWeight: 700,
|
|
473
|
+
color: 'var(--color-text-primary)',
|
|
474
|
+
fontFamily: 'var(--font-mono)',
|
|
475
|
+
}}
|
|
476
|
+
>
|
|
477
|
+
68
|
|
478
|
+
</span>
|
|
479
|
+
</div>
|
|
480
|
+
</div>
|
|
481
|
+
</div>
|
|
482
|
+
</section>
|
|
483
|
+
|
|
484
|
+
{/* Section 3: DataTable */}
|
|
485
|
+
<section
|
|
486
|
+
aria-label="Recent transactions"
|
|
487
|
+
style={{
|
|
488
|
+
marginTop: 'var(--spacing-section)',
|
|
489
|
+
paddingBottom: 'var(--spacing-section)',
|
|
490
|
+
}}
|
|
491
|
+
>
|
|
492
|
+
<div
|
|
493
|
+
style={{
|
|
494
|
+
marginBottom: 'var(--space-5)',
|
|
495
|
+
display: 'flex',
|
|
496
|
+
alignItems: 'center',
|
|
497
|
+
justifyContent: 'space-between',
|
|
498
|
+
gap: 'var(--space-4)',
|
|
499
|
+
}}
|
|
500
|
+
>
|
|
501
|
+
<div>
|
|
502
|
+
<h2
|
|
503
|
+
style={{
|
|
504
|
+
fontSize: 'var(--type-h3)',
|
|
505
|
+
lineHeight: 'var(--type-h3-lh)',
|
|
506
|
+
letterSpacing: 'var(--type-h3-ls)',
|
|
507
|
+
fontWeight: 600,
|
|
508
|
+
color: 'var(--color-text-primary)',
|
|
509
|
+
margin: 0,
|
|
510
|
+
}}
|
|
511
|
+
>
|
|
512
|
+
Recent transactions
|
|
513
|
+
</h2>
|
|
514
|
+
<p
|
|
515
|
+
style={{
|
|
516
|
+
fontSize: 'var(--type-caption)',
|
|
517
|
+
color: 'var(--color-text-muted)',
|
|
518
|
+
margin: 'var(--space-1) 0 0',
|
|
519
|
+
}}
|
|
520
|
+
>
|
|
521
|
+
{transactions.length} transactions this week
|
|
522
|
+
</p>
|
|
523
|
+
</div>
|
|
524
|
+
</div>
|
|
525
|
+
|
|
526
|
+
<DataTable
|
|
527
|
+
caption="Recent transactions"
|
|
528
|
+
columns={tableColumns}
|
|
529
|
+
data={transactions}
|
|
530
|
+
emptyState={
|
|
531
|
+
<EmptyState
|
|
532
|
+
icon={<FileText size={28} aria-hidden="true" />}
|
|
533
|
+
title="No transactions yet"
|
|
534
|
+
description="When customers complete purchases, they will appear here."
|
|
535
|
+
primaryAction={{
|
|
536
|
+
label: 'Create test transaction',
|
|
537
|
+
onClick: () => {},
|
|
538
|
+
icon: <Plus size={16} aria-hidden="true" />,
|
|
539
|
+
}}
|
|
540
|
+
/>
|
|
541
|
+
}
|
|
542
|
+
/>
|
|
543
|
+
</section>
|
|
544
|
+
</Container>
|
|
545
|
+
);
|
|
546
|
+
}
|