@donotdev/cli 0.0.5 → 0.0.7
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/dependencies-matrix.json +76 -34
- package/dist/bin/commands/build.js +165 -161
- package/dist/bin/commands/bump.js +172 -160
- package/dist/bin/commands/cacheout.js +163 -157
- package/dist/bin/commands/create-app.js +205 -163
- package/dist/bin/commands/create-project.js +176 -161
- package/dist/bin/commands/deploy.js +480 -472
- package/dist/bin/commands/dev.js +164 -158
- package/dist/bin/commands/emu.js +164 -158
- package/dist/bin/commands/format.js +163 -157
- package/dist/bin/commands/lint.js +166 -157
- package/dist/bin/commands/make-admin.d.ts +11 -0
- package/dist/bin/commands/make-admin.d.ts.map +1 -0
- package/dist/bin/commands/make-admin.js +12 -0
- package/dist/bin/commands/make-admin.js.map +1 -0
- package/dist/bin/commands/preview.js +164 -158
- package/dist/bin/commands/sync-secrets.js +164 -158
- package/dist/bin/commands/wai.d.ts +11 -0
- package/dist/bin/commands/wai.d.ts.map +1 -0
- package/dist/bin/commands/wai.js +12 -0
- package/dist/bin/commands/wai.js.map +1 -0
- package/dist/bin/dndev.js +24 -8
- package/dist/bin/donotdev.js +24 -8
- package/dist/index.js +557 -514
- package/package.json +1 -1
- package/templates/app-demo/index.html.example +4 -0
- package/templates/app-demo/src/App.tsx.example +28 -10
- package/templates/app-demo/src/config/app.ts.example +68 -0
- package/templates/app-next/src/app/ClientLayout.tsx.example +4 -3
- package/templates/app-next/src/app/layout.tsx.example +17 -25
- package/templates/app-next/src/config/app.ts.example +75 -48
- package/templates/app-next/src/globals.css.example +10 -7
- package/templates/app-next/src/locales/dndev_en.json.example +68 -0
- package/templates/app-next/src/pages/locales/example_en.json.example +5 -0
- package/templates/app-vite/index.html.example +71 -34
- package/templates/app-vite/src/config/app.ts.example +75 -47
- package/templates/app-vite/src/globals.css.example +14 -6
- package/templates/app-vite/src/locales/dndev_en.json.example +68 -0
- package/templates/app-vite/src/pages/FormPageExample.tsx.example +152 -0
- package/templates/app-vite/src/pages/HomePage.tsx.example +81 -134
- package/templates/app-vite/src/pages/ListPageExample.tsx.example +88 -0
- package/templates/functions-firebase/README.md.example +25 -0
- package/templates/functions-firebase/build.mjs.example +8 -1
- package/templates/functions-firebase/functions-firebase/build.mjs.example +8 -1
- package/templates/functions-firebase/functions-firebase/src/index.ts.example +19 -25
- package/templates/functions-firebase/functions.config.js.example +35 -0
- package/templates/functions-firebase/tsconfig.json.example +3 -13
- package/templates/functions-vercel/tsconfig.json.example +1 -13
- package/templates/root-consumer/entities/ExampleEntity.ts.example +223 -0
- package/templates/root-consumer/entities/demo.ts.example +562 -0
- package/templates/root-consumer/entities/index.ts.example +15 -0
- package/templates/root-consumer/firebase.json.example +1 -1
- package/templates/root-consumer/guides/{AGENT_START_HERE.md.example → dndev/AGENT_START_HERE.md.example} +22 -0
- package/templates/root-consumer/guides/{COMPONENTS_ADV.md.example → dndev/COMPONENTS_ADV.md.example} +456 -360
- package/templates/root-consumer/guides/{COMPONENTS_ATOMIC.md.example → dndev/COMPONENTS_ATOMIC.md.example} +42 -0
- package/templates/root-consumer/guides/dndev/COMPONENTS_CRUD.md.example +231 -0
- package/templates/root-consumer/guides/{INDEX.md.example → dndev/INDEX.md.example} +3 -0
- package/templates/root-consumer/guides/{SETUP_APP_CONFIG.md.example → dndev/SETUP_APP_CONFIG.md.example} +5 -2
- package/templates/root-consumer/guides/{SETUP_AUTH.md.example → dndev/SETUP_AUTH.md.example} +30 -0
- package/templates/root-consumer/guides/{SETUP_BILLING.md.example → dndev/SETUP_BILLING.md.example} +44 -4
- package/templates/root-consumer/guides/dndev/SETUP_CRUD.md.example +473 -0
- package/templates/root-consumer/guides/dndev/SETUP_FUNCTIONS.md.example +116 -0
- package/templates/root-consumer/guides/{SETUP_PAGES.md.example → dndev/SETUP_PAGES.md.example} +17 -0
- package/templates/root-consumer/guides/dndev/SETUP_PWA.md.example +213 -0
- package/templates/root-consumer/guides/dndev/USE_ROUTING.md.example +503 -0
- package/templates/root-consumer/guides/wai-way/WAI_WAY_CLI.md.example +404 -0
- package/templates/root-consumer/guides/wai-way/agents/architect.md.example +78 -0
- package/templates/root-consumer/guides/wai-way/agents/builder.md.example +87 -0
- package/templates/root-consumer/guides/wai-way/agents/extractor.md.example +325 -0
- package/templates/root-consumer/guides/wai-way/agents/polisher.md.example +100 -0
- package/templates/root-consumer/guides/wai-way/blueprints/0_brainstorm.md.example +281 -0
- package/templates/root-consumer/guides/wai-way/blueprints/1_scaffold.md.example +77 -0
- package/templates/root-consumer/guides/wai-way/blueprints/2_entities.md.example +104 -0
- package/templates/root-consumer/guides/wai-way/blueprints/3_compose.md.example +124 -0
- package/templates/root-consumer/guides/wai-way/blueprints/4_configure.md.example +165 -0
- package/templates/root-consumer/guides/wai-way/context_map.json.example +95 -0
- package/templates/root-consumer/guides/wai-way/entity_patterns.md.example +840 -0
- package/templates/root-consumer/guides/wai-way/page_patterns.md.example +686 -0
- package/templates/root-consumer/guides/wai-way/presets_guide.md.example +217 -0
- package/templates/root-consumer/guides/wai-way/spec_template.md.example +312 -0
- package/templates/root-consumer/vercel.json.example +315 -20
- package/templates/app-demo/src/Routes.tsx.example +0 -20
- package/templates/app-vite/src/Routes.tsx.example +0 -16
- package/templates/app-vite/src/pages/locales/README.md.example +0 -1
- package/templates/functions-firebase/functions-firebase/src/crud/createEntity.ts.example +0 -19
- package/templates/functions-firebase/functions-firebase/src/crud/deleteEntity.ts.example +0 -14
- package/templates/functions-firebase/functions-firebase/src/crud/getEntity.ts.example +0 -14
- package/templates/functions-firebase/functions-firebase/src/crud/index.ts.example +0 -12
- package/templates/functions-firebase/functions-firebase/src/crud/listEntities.ts.example +0 -14
- package/templates/functions-firebase/functions-firebase/src/crud/updateEntity.ts.example +0 -14
- package/templates/root-consumer/guides/COMPONENTS_CRUD.md.example +0 -70
- package/templates/root-consumer/guides/SETUP_FUNCTIONS.md.example +0 -62
- /package/templates/root-consumer/guides/{COMPONENTS_UI.md.example → dndev/COMPONENTS_UI.md.example} +0 -0
- /package/templates/root-consumer/guides/{ENV_SETUP.md.example → dndev/ENV_SETUP.md.example} +0 -0
- /package/templates/root-consumer/guides/{SETUP_I18N.md.example → dndev/SETUP_I18N.md.example} +0 -0
- /package/templates/root-consumer/guides/{SETUP_LAYOUTS.md.example → dndev/SETUP_LAYOUTS.md.example} +0 -0
- /package/templates/root-consumer/guides/{SETUP_OAUTH.md.example → dndev/SETUP_OAUTH.md.example} +0 -0
- /package/templates/root-consumer/guides/{SETUP_THEMES.md.example → dndev/SETUP_THEMES.md.example} +0 -0
- /package/templates/root-consumer/guides/{advanced → dndev/advanced}/APP_CHECK.md.example +0 -0
- /package/templates/root-consumer/guides/{advanced → dndev/advanced}/COOKIE_REFERENCE.md.example +0 -0
- /package/templates/root-consumer/guides/{advanced → dndev/advanced}/EMULATORS.md.example +0 -0
- /package/templates/root-consumer/guides/{advanced → dndev/advanced}/VERSION_CONTROL.md.example +0 -0
|
@@ -0,0 +1,686 @@
|
|
|
1
|
+
# Page Pattern Catalog
|
|
2
|
+
|
|
3
|
+
> **Don't invent page structures. Copy patterns, customize content.**
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Pattern Index
|
|
8
|
+
|
|
9
|
+
| Pattern | Use For | Key Components |
|
|
10
|
+
|---------|---------|----------------|
|
|
11
|
+
| [Dashboard](#dashboard) | Main logged-in view, stats, quick actions | Section, Grid, Card, ActionCard |
|
|
12
|
+
| [List Page](#list-page) | Entity CRUD table | EntityList |
|
|
13
|
+
| [Form Page](#form-page) | Entity create/edit | EntityFormRenderer |
|
|
14
|
+
| [Landing/Home](#landinghome) | Marketing, conversion | HeroSection, Section, Card, CTA |
|
|
15
|
+
| [Pricing](#pricing) | Plan comparison | PricingTable, Section |
|
|
16
|
+
| [Settings](#settings) | User preferences | Section, form components |
|
|
17
|
+
| [Profile](#profile) | User profile display/edit | Section, Card, Avatar |
|
|
18
|
+
| [Detail Page](#detail-page) | Single item display (non-edit) | Section, Card, Grid |
|
|
19
|
+
| [Analytics](#analytics) | Charts and metrics | Section, Grid, Card, charts |
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Dashboard
|
|
24
|
+
|
|
25
|
+
**Main logged-in view with KPIs, quick actions, alerts.**
|
|
26
|
+
|
|
27
|
+
```tsx
|
|
28
|
+
import { useEffect, useState } from 'react';
|
|
29
|
+
import { PageContainer, Loader, useNavigate } from '@donotdev/ui';
|
|
30
|
+
import { Section, Card, Grid, Stack, Button, Text, Badge } from '@donotdev/components';
|
|
31
|
+
import type { PageMeta } from '@donotdev/core';
|
|
32
|
+
import { ChartBar, Plus, ArrowRight } from 'lucide-react';
|
|
33
|
+
|
|
34
|
+
export const NAMESPACE = 'dashboard';
|
|
35
|
+
|
|
36
|
+
export const meta: PageMeta = {
|
|
37
|
+
namespace: NAMESPACE,
|
|
38
|
+
icon: <ChartBar />,
|
|
39
|
+
auth: { required: true },
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export default function DashboardPage() {
|
|
43
|
+
const navigate = useNavigate();
|
|
44
|
+
const [metrics, setMetrics] = useState<any>(null);
|
|
45
|
+
const [loading, setLoading] = useState(true);
|
|
46
|
+
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
// Fetch dashboard metrics from API/function
|
|
49
|
+
fetchMetrics().then(setMetrics).finally(() => setLoading(false));
|
|
50
|
+
}, []);
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<PageContainer>
|
|
54
|
+
{/* Quick Actions */}
|
|
55
|
+
<Section title="Quick Actions" gridCols={[2, 2, 4, 4]}>
|
|
56
|
+
<Card
|
|
57
|
+
clickable
|
|
58
|
+
onClick={() => navigate('/items/new')}
|
|
59
|
+
icon={Plus}
|
|
60
|
+
title="Add New Item"
|
|
61
|
+
/>
|
|
62
|
+
{/* More action cards... */}
|
|
63
|
+
</Section>
|
|
64
|
+
|
|
65
|
+
{/* KPI Cards */}
|
|
66
|
+
<Section title="Overview" gridCols={[1, 2, 3, 3]}>
|
|
67
|
+
<Card variant="glass" title="Total Items">
|
|
68
|
+
{loading ? <Loader /> : (
|
|
69
|
+
<Text level="h2">{metrics?.totalItems ?? 0}</Text>
|
|
70
|
+
)}
|
|
71
|
+
</Card>
|
|
72
|
+
{/* More KPI cards... */}
|
|
73
|
+
</Section>
|
|
74
|
+
|
|
75
|
+
{/* Alerts/Actions Needed */}
|
|
76
|
+
{metrics?.needsAttention > 0 && (
|
|
77
|
+
<Section title="Needs Attention">
|
|
78
|
+
<Card variant="secondary">
|
|
79
|
+
<Stack direction="row" justify="between" align="center">
|
|
80
|
+
<Text>{metrics.needsAttention} items need review</Text>
|
|
81
|
+
<Button variant="outline" onClick={() => navigate('/items?filter=attention')}>
|
|
82
|
+
View <ArrowRight size={16} />
|
|
83
|
+
</Button>
|
|
84
|
+
</Stack>
|
|
85
|
+
</Card>
|
|
86
|
+
</Section>
|
|
87
|
+
)}
|
|
88
|
+
</PageContainer>
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
**Key patterns:**
|
|
94
|
+
- Quick actions at top (cards with onClick)
|
|
95
|
+
- KPI cards in grid
|
|
96
|
+
- Alert/attention sections with actions
|
|
97
|
+
- Loading states with `<Loader />`
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## List Page
|
|
102
|
+
|
|
103
|
+
**Entity CRUD table with search, filter, pagination.**
|
|
104
|
+
|
|
105
|
+
```tsx
|
|
106
|
+
import { Car } from 'lucide-react';
|
|
107
|
+
import { EntityList } from '@donotdev/crud';
|
|
108
|
+
import { useAuth } from '@donotdev/auth';
|
|
109
|
+
import type { PageMeta } from '@donotdev/core';
|
|
110
|
+
import { PageContainer } from '@donotdev/ui';
|
|
111
|
+
import { carEntity } from 'entities/car';
|
|
112
|
+
|
|
113
|
+
export const NAMESPACE = 'cars';
|
|
114
|
+
|
|
115
|
+
export const meta: PageMeta = {
|
|
116
|
+
namespace: NAMESPACE,
|
|
117
|
+
auth: { required: true, role: 'admin' },
|
|
118
|
+
route: '/cars',
|
|
119
|
+
icon: <Car />,
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
export default function CarsListPage() {
|
|
123
|
+
const user = useAuth('user');
|
|
124
|
+
|
|
125
|
+
return (
|
|
126
|
+
<PageContainer>
|
|
127
|
+
<EntityList
|
|
128
|
+
entity={carEntity}
|
|
129
|
+
userRole={user?.role}
|
|
130
|
+
/>
|
|
131
|
+
</PageContainer>
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
**Key patterns:**
|
|
137
|
+
- Minimal code - `EntityList` handles everything
|
|
138
|
+
- Pass `userRole` for column filtering
|
|
139
|
+
- Route matches collection (e.g., `/cars` for `cars` collection)
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## Form Page
|
|
144
|
+
|
|
145
|
+
**Entity create/edit form.**
|
|
146
|
+
|
|
147
|
+
```tsx
|
|
148
|
+
import { useEffect, useState } from 'react';
|
|
149
|
+
import { PageContainer, Link, useNavigate } from '@donotdev/ui';
|
|
150
|
+
import { Section, Button, Alert } from '@donotdev/components';
|
|
151
|
+
import { EntityFormRenderer, useCrud } from '@donotdev/crud';
|
|
152
|
+
import { carEntity } from 'entities/car';
|
|
153
|
+
import { useTranslation } from '@donotdev/core';
|
|
154
|
+
import type { PageMeta } from '@donotdev/core';
|
|
155
|
+
import { useParams } from '@donotdev/ui/routing';
|
|
156
|
+
|
|
157
|
+
export const NAMESPACE = 'car';
|
|
158
|
+
|
|
159
|
+
export const meta: PageMeta = {
|
|
160
|
+
namespace: NAMESPACE,
|
|
161
|
+
route: '/cars/:id',
|
|
162
|
+
auth: { required: true, role: 'admin' },
|
|
163
|
+
hideFromMenu: true, // Don't show in nav
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
export default function CarPage() {
|
|
167
|
+
const { t } = useTranslation(NAMESPACE);
|
|
168
|
+
const { id } = useParams<{ id: string }>();
|
|
169
|
+
const navigate = useNavigate();
|
|
170
|
+
const isNew = id === 'new';
|
|
171
|
+
|
|
172
|
+
const { get, add, update, error } = useCrud(carEntity);
|
|
173
|
+
const [data, setData] = useState<any>(null);
|
|
174
|
+
|
|
175
|
+
// Fetch existing data for edit mode
|
|
176
|
+
useEffect(() => {
|
|
177
|
+
if (!isNew && id) {
|
|
178
|
+
get(id).then(setData);
|
|
179
|
+
}
|
|
180
|
+
}, [id, isNew, get]);
|
|
181
|
+
|
|
182
|
+
const handleSubmit = async (formData: any) => {
|
|
183
|
+
if (isNew) {
|
|
184
|
+
add(formData);
|
|
185
|
+
} else if (id) {
|
|
186
|
+
update(id, formData);
|
|
187
|
+
}
|
|
188
|
+
navigate('/cars');
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
// Error state
|
|
192
|
+
if (!isNew && error) {
|
|
193
|
+
return (
|
|
194
|
+
<PageContainer>
|
|
195
|
+
<Section title="Error">
|
|
196
|
+
<Alert variant="error" description="Failed to load data" />
|
|
197
|
+
<Link path="/cars">
|
|
198
|
+
<Button variant="outline">Back to list</Button>
|
|
199
|
+
</Link>
|
|
200
|
+
</Section>
|
|
201
|
+
</PageContainer>
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return (
|
|
206
|
+
<PageContainer>
|
|
207
|
+
<Section title={isNew ? 'Create Car' : 'Edit Car'}>
|
|
208
|
+
{isNew ? (
|
|
209
|
+
<EntityFormRenderer
|
|
210
|
+
entity={carEntity}
|
|
211
|
+
operation="create"
|
|
212
|
+
onSubmit={handleSubmit}
|
|
213
|
+
defaultValues={{ status: 'draft' }}
|
|
214
|
+
submitText="Create"
|
|
215
|
+
/>
|
|
216
|
+
) : (
|
|
217
|
+
<EntityFormRenderer
|
|
218
|
+
entity={carEntity}
|
|
219
|
+
operation="edit"
|
|
220
|
+
onSubmit={handleSubmit}
|
|
221
|
+
defaultValues={data}
|
|
222
|
+
submitText="Update"
|
|
223
|
+
/>
|
|
224
|
+
)}
|
|
225
|
+
</Section>
|
|
226
|
+
</PageContainer>
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
**Key patterns:**
|
|
232
|
+
- Route: `/[collection]/:id` where `id` can be "new"
|
|
233
|
+
- `hideFromMenu: true` - form pages don't show in nav
|
|
234
|
+
- Check `isNew` to determine create vs edit
|
|
235
|
+
- Don't mount form until data loaded (edit mode)
|
|
236
|
+
- Optimistic: navigate immediately, don't await
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
## Landing/Home
|
|
241
|
+
|
|
242
|
+
**Marketing page with hero, features, CTA.**
|
|
243
|
+
|
|
244
|
+
```tsx
|
|
245
|
+
import { PageContainer, useNavigate } from '@donotdev/ui';
|
|
246
|
+
import { HeroSection, Section, Card, Grid, Button } from '@donotdev/components';
|
|
247
|
+
import type { PageMeta } from '@donotdev/core';
|
|
248
|
+
import { Home } from 'lucide-react';
|
|
249
|
+
|
|
250
|
+
export const NAMESPACE = 'home';
|
|
251
|
+
|
|
252
|
+
export const meta: PageMeta = {
|
|
253
|
+
namespace: NAMESPACE,
|
|
254
|
+
icon: <Home />,
|
|
255
|
+
// No auth - public page
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
export default function HomePage() {
|
|
259
|
+
const navigate = useNavigate();
|
|
260
|
+
|
|
261
|
+
return (
|
|
262
|
+
<PageContainer>
|
|
263
|
+
<HeroSection
|
|
264
|
+
title="Your App Name"
|
|
265
|
+
subtitle="One-line value proposition"
|
|
266
|
+
primaryAction={{
|
|
267
|
+
label: "Get Started",
|
|
268
|
+
onClick: () => navigate('/signup'),
|
|
269
|
+
}}
|
|
270
|
+
secondaryAction={{
|
|
271
|
+
label: "Learn More",
|
|
272
|
+
onClick: () => navigate('/about'),
|
|
273
|
+
}}
|
|
274
|
+
/>
|
|
275
|
+
|
|
276
|
+
<Section title="Features">
|
|
277
|
+
<Grid cols={[1, 2, 3, 3]} gap="large">
|
|
278
|
+
<Card
|
|
279
|
+
title="Feature One"
|
|
280
|
+
content="Brief description of the feature and its benefit."
|
|
281
|
+
/>
|
|
282
|
+
<Card
|
|
283
|
+
title="Feature Two"
|
|
284
|
+
content="Brief description of the feature and its benefit."
|
|
285
|
+
/>
|
|
286
|
+
<Card
|
|
287
|
+
title="Feature Three"
|
|
288
|
+
content="Brief description of the feature and its benefit."
|
|
289
|
+
/>
|
|
290
|
+
</Grid>
|
|
291
|
+
</Section>
|
|
292
|
+
|
|
293
|
+
<Section title="How It Works">
|
|
294
|
+
<Grid cols={[1, 1, 3, 3]} gap="medium">
|
|
295
|
+
<Card title="1. Sign Up" content="Create your account in seconds." />
|
|
296
|
+
<Card title="2. Configure" content="Set up your preferences." />
|
|
297
|
+
<Card title="3. Launch" content="Start using immediately." />
|
|
298
|
+
</Grid>
|
|
299
|
+
</Section>
|
|
300
|
+
|
|
301
|
+
<Section>
|
|
302
|
+
<Card variant="primary" align="center">
|
|
303
|
+
<Text level="h2">Ready to get started?</Text>
|
|
304
|
+
<Button size="large" onClick={() => navigate('/signup')}>
|
|
305
|
+
Start Free Trial
|
|
306
|
+
</Button>
|
|
307
|
+
</Card>
|
|
308
|
+
</Section>
|
|
309
|
+
</PageContainer>
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
**Key patterns:**
|
|
315
|
+
- `HeroSection` at top with CTAs
|
|
316
|
+
- Feature grid with `Card` components
|
|
317
|
+
- Final CTA section
|
|
318
|
+
- HARDCODE strings first, i18n later
|
|
319
|
+
|
|
320
|
+
---
|
|
321
|
+
|
|
322
|
+
## Pricing
|
|
323
|
+
|
|
324
|
+
**Plan comparison page.**
|
|
325
|
+
|
|
326
|
+
```tsx
|
|
327
|
+
import { PageContainer, useNavigate } from '@donotdev/ui';
|
|
328
|
+
import { Section, PricingTable } from '@donotdev/components';
|
|
329
|
+
import type { PageMeta } from '@donotdev/core';
|
|
330
|
+
import { DollarSign } from 'lucide-react';
|
|
331
|
+
|
|
332
|
+
export const NAMESPACE = 'pricing';
|
|
333
|
+
|
|
334
|
+
export const meta: PageMeta = {
|
|
335
|
+
namespace: NAMESPACE,
|
|
336
|
+
icon: <DollarSign />,
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
export default function PricingPage() {
|
|
340
|
+
const navigate = useNavigate();
|
|
341
|
+
|
|
342
|
+
const plans = [
|
|
343
|
+
{
|
|
344
|
+
name: 'Free',
|
|
345
|
+
price: 0,
|
|
346
|
+
period: 'forever',
|
|
347
|
+
features: ['5 projects', 'Basic support', 'Community access'],
|
|
348
|
+
cta: { label: 'Get Started', onClick: () => navigate('/signup') },
|
|
349
|
+
},
|
|
350
|
+
{
|
|
351
|
+
name: 'Pro',
|
|
352
|
+
price: 29,
|
|
353
|
+
period: 'month',
|
|
354
|
+
features: ['Unlimited projects', 'Priority support', 'Advanced analytics'],
|
|
355
|
+
cta: { label: 'Start Trial', onClick: () => navigate('/signup?plan=pro') },
|
|
356
|
+
highlighted: true,
|
|
357
|
+
},
|
|
358
|
+
{
|
|
359
|
+
name: 'Enterprise',
|
|
360
|
+
price: 99,
|
|
361
|
+
period: 'month',
|
|
362
|
+
features: ['Everything in Pro', 'Dedicated support', 'Custom integrations'],
|
|
363
|
+
cta: { label: 'Contact Sales', onClick: () => navigate('/contact') },
|
|
364
|
+
},
|
|
365
|
+
];
|
|
366
|
+
|
|
367
|
+
return (
|
|
368
|
+
<PageContainer>
|
|
369
|
+
<Section title="Choose Your Plan" align="center">
|
|
370
|
+
<PricingTable plans={plans} />
|
|
371
|
+
</Section>
|
|
372
|
+
</PageContainer>
|
|
373
|
+
);
|
|
374
|
+
}
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
---
|
|
378
|
+
|
|
379
|
+
## Settings
|
|
380
|
+
|
|
381
|
+
**User preferences with grouped sections.**
|
|
382
|
+
|
|
383
|
+
```tsx
|
|
384
|
+
import { PageContainer } from '@donotdev/ui';
|
|
385
|
+
import { Section, Card, Stack, Switch, Select, Button } from '@donotdev/components';
|
|
386
|
+
import type { PageMeta } from '@donotdev/core';
|
|
387
|
+
import { Settings as SettingsIcon } from 'lucide-react';
|
|
388
|
+
|
|
389
|
+
export const NAMESPACE = 'settings';
|
|
390
|
+
|
|
391
|
+
export const meta: PageMeta = {
|
|
392
|
+
namespace: NAMESPACE,
|
|
393
|
+
icon: <SettingsIcon />,
|
|
394
|
+
auth: { required: true },
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
export default function SettingsPage() {
|
|
398
|
+
return (
|
|
399
|
+
<PageContainer>
|
|
400
|
+
<Section title="Appearance">
|
|
401
|
+
<Card>
|
|
402
|
+
<Stack direction="row" justify="between" align="center">
|
|
403
|
+
<Text>Theme</Text>
|
|
404
|
+
<Select
|
|
405
|
+
options={[
|
|
406
|
+
{ value: 'light', label: 'Light' },
|
|
407
|
+
{ value: 'dark', label: 'Dark' },
|
|
408
|
+
{ value: 'system', label: 'System' },
|
|
409
|
+
]}
|
|
410
|
+
defaultValue="system"
|
|
411
|
+
/>
|
|
412
|
+
</Stack>
|
|
413
|
+
</Card>
|
|
414
|
+
</Section>
|
|
415
|
+
|
|
416
|
+
<Section title="Notifications">
|
|
417
|
+
<Card>
|
|
418
|
+
<Stack direction="column" gap="medium">
|
|
419
|
+
<Stack direction="row" justify="between" align="center">
|
|
420
|
+
<Text>Email notifications</Text>
|
|
421
|
+
<Switch defaultChecked />
|
|
422
|
+
</Stack>
|
|
423
|
+
<Stack direction="row" justify="between" align="center">
|
|
424
|
+
<Text>Push notifications</Text>
|
|
425
|
+
<Switch />
|
|
426
|
+
</Stack>
|
|
427
|
+
</Stack>
|
|
428
|
+
</Card>
|
|
429
|
+
</Section>
|
|
430
|
+
|
|
431
|
+
<Section title="Danger Zone">
|
|
432
|
+
<Card variant="destructive">
|
|
433
|
+
<Stack direction="row" justify="between" align="center">
|
|
434
|
+
<Text>Delete account</Text>
|
|
435
|
+
<Button variant="destructive">Delete</Button>
|
|
436
|
+
</Stack>
|
|
437
|
+
</Card>
|
|
438
|
+
</Section>
|
|
439
|
+
</PageContainer>
|
|
440
|
+
);
|
|
441
|
+
}
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
---
|
|
445
|
+
|
|
446
|
+
## Profile
|
|
447
|
+
|
|
448
|
+
**User profile display with edit.**
|
|
449
|
+
|
|
450
|
+
```tsx
|
|
451
|
+
import { PageContainer } from '@donotdev/ui';
|
|
452
|
+
import { Section, Card, Stack, Text, Avatar, Button } from '@donotdev/components';
|
|
453
|
+
import { useAuth } from '@donotdev/auth';
|
|
454
|
+
import type { PageMeta } from '@donotdev/core';
|
|
455
|
+
import { User } from 'lucide-react';
|
|
456
|
+
|
|
457
|
+
export const NAMESPACE = 'profile';
|
|
458
|
+
|
|
459
|
+
export const meta: PageMeta = {
|
|
460
|
+
namespace: NAMESPACE,
|
|
461
|
+
icon: <User />,
|
|
462
|
+
auth: { required: true },
|
|
463
|
+
};
|
|
464
|
+
|
|
465
|
+
export default function ProfilePage() {
|
|
466
|
+
const user = useAuth('user');
|
|
467
|
+
|
|
468
|
+
return (
|
|
469
|
+
<PageContainer>
|
|
470
|
+
<Section>
|
|
471
|
+
<Card>
|
|
472
|
+
<Stack direction="row" gap="large" align="center">
|
|
473
|
+
<Avatar
|
|
474
|
+
src={user?.photoURL}
|
|
475
|
+
fallback={user?.displayName?.[0] || 'U'}
|
|
476
|
+
size="large"
|
|
477
|
+
/>
|
|
478
|
+
<Stack direction="column" gap="tight">
|
|
479
|
+
<Text level="h2">{user?.displayName || 'User'}</Text>
|
|
480
|
+
<Text variant="muted">{user?.email}</Text>
|
|
481
|
+
</Stack>
|
|
482
|
+
</Stack>
|
|
483
|
+
</Card>
|
|
484
|
+
</Section>
|
|
485
|
+
|
|
486
|
+
<Section title="Account Details">
|
|
487
|
+
<Card>
|
|
488
|
+
<Stack direction="column" gap="medium">
|
|
489
|
+
<Stack direction="row" justify="between">
|
|
490
|
+
<Text variant="muted">Email</Text>
|
|
491
|
+
<Text>{user?.email}</Text>
|
|
492
|
+
</Stack>
|
|
493
|
+
<Stack direction="row" justify="between">
|
|
494
|
+
<Text variant="muted">Member since</Text>
|
|
495
|
+
<Text>{user?.createdAt?.toLocaleDateString()}</Text>
|
|
496
|
+
</Stack>
|
|
497
|
+
<Stack direction="row" justify="between">
|
|
498
|
+
<Text variant="muted">Role</Text>
|
|
499
|
+
<Text>{user?.role || 'user'}</Text>
|
|
500
|
+
</Stack>
|
|
501
|
+
</Stack>
|
|
502
|
+
</Card>
|
|
503
|
+
</Section>
|
|
504
|
+
|
|
505
|
+
<Section>
|
|
506
|
+
<Button variant="outline">Edit Profile</Button>
|
|
507
|
+
</Section>
|
|
508
|
+
</PageContainer>
|
|
509
|
+
);
|
|
510
|
+
}
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
---
|
|
514
|
+
|
|
515
|
+
## Detail Page
|
|
516
|
+
|
|
517
|
+
**Single item display (read-only).**
|
|
518
|
+
|
|
519
|
+
```tsx
|
|
520
|
+
import { useEffect, useState } from 'react';
|
|
521
|
+
import { PageContainer, Loader, useNavigate } from '@donotdev/ui';
|
|
522
|
+
import { Section, Card, Grid, Stack, Text, Button, Badge } from '@donotdev/components';
|
|
523
|
+
import { useCrud } from '@donotdev/crud';
|
|
524
|
+
import { productEntity } from 'entities/product';
|
|
525
|
+
import type { PageMeta } from '@donotdev/core';
|
|
526
|
+
import { useParams } from '@donotdev/ui/routing';
|
|
527
|
+
|
|
528
|
+
export const NAMESPACE = 'product-detail';
|
|
529
|
+
|
|
530
|
+
export const meta: PageMeta = {
|
|
531
|
+
namespace: NAMESPACE,
|
|
532
|
+
route: '/products/:id',
|
|
533
|
+
hideFromMenu: true,
|
|
534
|
+
};
|
|
535
|
+
|
|
536
|
+
export default function ProductDetailPage() {
|
|
537
|
+
const { id } = useParams<{ id: string }>();
|
|
538
|
+
const navigate = useNavigate();
|
|
539
|
+
const { get } = useCrud(productEntity);
|
|
540
|
+
const [product, setProduct] = useState<any>(null);
|
|
541
|
+
const [loading, setLoading] = useState(true);
|
|
542
|
+
|
|
543
|
+
useEffect(() => {
|
|
544
|
+
if (id) {
|
|
545
|
+
get(id).then(setProduct).finally(() => setLoading(false));
|
|
546
|
+
}
|
|
547
|
+
}, [id, get]);
|
|
548
|
+
|
|
549
|
+
if (loading) return <PageContainer><Loader /></PageContainer>;
|
|
550
|
+
if (!product) return <PageContainer><Text>Product not found</Text></PageContainer>;
|
|
551
|
+
|
|
552
|
+
return (
|
|
553
|
+
<PageContainer>
|
|
554
|
+
<Section>
|
|
555
|
+
<Grid cols={[1, 1, 2, 2]} gap="large">
|
|
556
|
+
{/* Image */}
|
|
557
|
+
<Card>
|
|
558
|
+
<img src={product.image} alt={product.name} />
|
|
559
|
+
</Card>
|
|
560
|
+
|
|
561
|
+
{/* Details */}
|
|
562
|
+
<Stack direction="column" gap="medium">
|
|
563
|
+
<Text level="h1">{product.name}</Text>
|
|
564
|
+
<Badge>{product.category}</Badge>
|
|
565
|
+
<Text level="h2">${product.price}</Text>
|
|
566
|
+
<Text>{product.description}</Text>
|
|
567
|
+
<Button onClick={() => navigate(`/admin/products/${id}`)}>
|
|
568
|
+
Edit
|
|
569
|
+
</Button>
|
|
570
|
+
</Stack>
|
|
571
|
+
</Grid>
|
|
572
|
+
</Section>
|
|
573
|
+
</PageContainer>
|
|
574
|
+
);
|
|
575
|
+
}
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
---
|
|
579
|
+
|
|
580
|
+
## Analytics
|
|
581
|
+
|
|
582
|
+
**Charts and metrics display.**
|
|
583
|
+
|
|
584
|
+
```tsx
|
|
585
|
+
import { useEffect, useState } from 'react';
|
|
586
|
+
import { PageContainer, Loader } from '@donotdev/ui';
|
|
587
|
+
import { Section, Card, Grid, Stack, Text } from '@donotdev/components';
|
|
588
|
+
import type { PageMeta } from '@donotdev/core';
|
|
589
|
+
import { TrendingUp } from 'lucide-react';
|
|
590
|
+
|
|
591
|
+
export const NAMESPACE = 'analytics';
|
|
592
|
+
|
|
593
|
+
export const meta: PageMeta = {
|
|
594
|
+
namespace: NAMESPACE,
|
|
595
|
+
icon: <TrendingUp />,
|
|
596
|
+
auth: { required: true, role: 'admin' },
|
|
597
|
+
};
|
|
598
|
+
|
|
599
|
+
export default function AnalyticsPage() {
|
|
600
|
+
const [data, setData] = useState<any>(null);
|
|
601
|
+
const [loading, setLoading] = useState(true);
|
|
602
|
+
|
|
603
|
+
useEffect(() => {
|
|
604
|
+
fetchAnalytics().then(setData).finally(() => setLoading(false));
|
|
605
|
+
}, []);
|
|
606
|
+
|
|
607
|
+
if (loading) return <PageContainer><Loader /></PageContainer>;
|
|
608
|
+
|
|
609
|
+
return (
|
|
610
|
+
<PageContainer>
|
|
611
|
+
{/* Summary Cards */}
|
|
612
|
+
<Section gridCols={[2, 4, 4, 4]}>
|
|
613
|
+
<Card variant="glass">
|
|
614
|
+
<Stack direction="column" gap="tight">
|
|
615
|
+
<Text variant="muted">Total Revenue</Text>
|
|
616
|
+
<Text level="h2">${data?.revenue?.toLocaleString()}</Text>
|
|
617
|
+
</Stack>
|
|
618
|
+
</Card>
|
|
619
|
+
<Card variant="glass">
|
|
620
|
+
<Stack direction="column" gap="tight">
|
|
621
|
+
<Text variant="muted">Orders</Text>
|
|
622
|
+
<Text level="h2">{data?.orders}</Text>
|
|
623
|
+
</Stack>
|
|
624
|
+
</Card>
|
|
625
|
+
{/* More cards... */}
|
|
626
|
+
</Section>
|
|
627
|
+
|
|
628
|
+
{/* Charts */}
|
|
629
|
+
<Section title="Revenue Over Time">
|
|
630
|
+
<Card>
|
|
631
|
+
{/* Your chart component here */}
|
|
632
|
+
<div style={{ height: 300 }}>Chart placeholder</div>
|
|
633
|
+
</Card>
|
|
634
|
+
</Section>
|
|
635
|
+
|
|
636
|
+
<Section title="Top Products" gridCols={[1, 2, 2, 2]}>
|
|
637
|
+
{data?.topProducts?.map((product: any) => (
|
|
638
|
+
<Card key={product.id}>
|
|
639
|
+
<Stack direction="row" justify="between">
|
|
640
|
+
<Text>{product.name}</Text>
|
|
641
|
+
<Text level="h4">{product.sales} sales</Text>
|
|
642
|
+
</Stack>
|
|
643
|
+
</Card>
|
|
644
|
+
))}
|
|
645
|
+
</Section>
|
|
646
|
+
</PageContainer>
|
|
647
|
+
);
|
|
648
|
+
}
|
|
649
|
+
```
|
|
650
|
+
|
|
651
|
+
---
|
|
652
|
+
|
|
653
|
+
## PageMeta Reference
|
|
654
|
+
|
|
655
|
+
Every page exports `meta: PageMeta`:
|
|
656
|
+
|
|
657
|
+
```tsx
|
|
658
|
+
export const meta: PageMeta = {
|
|
659
|
+
namespace: 'page-name', // i18n namespace, required
|
|
660
|
+
icon: <IconComponent />, // lucide-react icon, shown in nav
|
|
661
|
+
|
|
662
|
+
// Auth options
|
|
663
|
+
auth: true, // Requires login
|
|
664
|
+
auth: { required: true }, // Same as above
|
|
665
|
+
auth: { required: true, role: 'admin' }, // Requires specific role
|
|
666
|
+
|
|
667
|
+
// Route options
|
|
668
|
+
route: '/custom-path', // Override auto-generated route
|
|
669
|
+
route: '/items/:id', // Dynamic route
|
|
670
|
+
|
|
671
|
+
// Navigation options
|
|
672
|
+
hideFromMenu: true, // Don't show in sidebar/nav
|
|
673
|
+
};
|
|
674
|
+
```
|
|
675
|
+
|
|
676
|
+
---
|
|
677
|
+
|
|
678
|
+
## Usage
|
|
679
|
+
|
|
680
|
+
1. **Find pattern** that matches your page type
|
|
681
|
+
2. **Copy** the page component
|
|
682
|
+
3. **Customize** content, entity, routes
|
|
683
|
+
4. **Update** PageMeta for auth/nav requirements
|
|
684
|
+
5. **HARDCODE** strings first, i18n later
|
|
685
|
+
|
|
686
|
+
**Do NOT invent new page structures unless none fit.**
|