create-lego-one 2.0.12 → 2.0.14

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 (78) hide show
  1. package/dist/index.cjs +150 -15
  2. package/dist/index.cjs.map +1 -1
  3. package/package.json +1 -1
  4. package/template/.cursor/rules/rules.mdc +639 -0
  5. package/template/.dockerignore +58 -0
  6. package/template/.env.example +18 -0
  7. package/template/.eslintignore +5 -0
  8. package/template/.eslintrc.js +28 -0
  9. package/template/.prettierignore +6 -0
  10. package/template/.prettierrc +11 -0
  11. package/template/CLAUDE.md +634 -0
  12. package/template/Dockerfile +67 -0
  13. package/template/PROMPT.md +457 -0
  14. package/template/README.md +325 -0
  15. package/template/docker-compose.yml +48 -0
  16. package/template/docker-entrypoint.sh +23 -0
  17. package/template/docs/checkpoints/.template.md +64 -0
  18. package/template/docs/checkpoints/framework/01-infrastructure-setup.md +132 -0
  19. package/template/docs/checkpoints/framework/02-pocketbase-setup.md +155 -0
  20. package/template/docs/checkpoints/framework/03-host-kernel.md +170 -0
  21. package/template/docs/checkpoints/framework/04-auth-system.md +163 -0
  22. package/template/docs/checkpoints/framework/phase-05-multitenancy-rbac.md +223 -0
  23. package/template/docs/checkpoints/framework/phase-06-ui-components.md +260 -0
  24. package/template/docs/checkpoints/framework/phase-07-communication-system.md +276 -0
  25. package/template/docs/checkpoints/framework/phase-08-plugin-system.md +91 -0
  26. package/template/docs/checkpoints/framework/phase-09-dashboard-plugin.md +111 -0
  27. package/template/docs/checkpoints/framework/phase-10-todo-plugin.md +169 -0
  28. package/template/docs/checkpoints/framework/phase-11-testing.md +264 -0
  29. package/template/docs/checkpoints/framework/phase-12-deployment.md +294 -0
  30. package/template/docs/checkpoints/framework/phase-13-documentation.md +312 -0
  31. package/template/docs/framework/plans/00-index.md +164 -0
  32. package/template/docs/framework/plans/01-infrastructure-setup.md +855 -0
  33. package/template/docs/framework/plans/02-pocketbase-setup.md +1374 -0
  34. package/template/docs/framework/plans/03-host-kernel.md +1518 -0
  35. package/template/docs/framework/plans/04-auth-system.md +1466 -0
  36. package/template/docs/framework/plans/05-multitenancy-rbac.md +1527 -0
  37. package/template/docs/framework/plans/06-ui-components.md +1478 -0
  38. package/template/docs/framework/plans/07-communication-system.md +1106 -0
  39. package/template/docs/framework/plans/08-plugin-system.md +1179 -0
  40. package/template/docs/framework/plans/09-dashboard-plugin.md +1137 -0
  41. package/template/docs/framework/plans/10-todo-plugin.md +1343 -0
  42. package/template/docs/framework/plans/11-testing.md +935 -0
  43. package/template/docs/framework/plans/12-deployment.md +896 -0
  44. package/template/docs/framework/prompts/0-boilerplate-modernjs.md +151 -0
  45. package/template/docs/framework/research/00-modernjs-audit.md +488 -0
  46. package/template/docs/framework/research/01-system-blueprint.md +721 -0
  47. package/template/docs/framework/research/02-data-migration-protocol.md +699 -0
  48. package/template/docs/framework/research/03-host-setup.md +714 -0
  49. package/template/docs/framework/research/04-plugin-architecture.md +645 -0
  50. package/template/docs/framework/research/05-slot-injection-pattern.md +671 -0
  51. package/template/docs/framework/research/06-cli-strategy.md +615 -0
  52. package/template/docs/framework/research/07-deployment.md +629 -0
  53. package/template/docs/framework/research/README.md +282 -0
  54. package/template/docs/framework/setup/00-index.md +210 -0
  55. package/template/docs/framework/setup/01-framework-structure.md +308 -0
  56. package/template/docs/framework/setup/02-development-workflow.md +405 -0
  57. package/template/docs/framework/setup/03-environment-setup.md +215 -0
  58. package/template/docs/framework/setup/04-kernel-architecture.md +499 -0
  59. package/template/docs/framework/setup/05-plugin-system.md +620 -0
  60. package/template/docs/framework/setup/06-communication-patterns.md +451 -0
  61. package/template/docs/framework/setup/07-plugin-development.md +582 -0
  62. package/template/docs/framework/setup/08-component-library.md +658 -0
  63. package/template/docs/framework/setup/09-data-integration.md +609 -0
  64. package/template/docs/framework/setup/10-auth-rbac.md +497 -0
  65. package/template/docs/framework/setup/11-hooks-api.md +393 -0
  66. package/template/docs/framework/setup/12-components-api.md +665 -0
  67. package/template/docs/framework/setup/13-deployment-guide.md +566 -0
  68. package/template/docs/framework/setup/README.md +548 -0
  69. package/template/host/package.json +1 -1
  70. package/template/nginx.conf +72 -0
  71. package/template/package.json +1 -1
  72. package/template/packages/plugins/@lego/plugin-dashboard/package.json +1 -1
  73. package/template/packages/plugins/@lego/plugin-todo/package.json +1 -1
  74. package/template/pocketbase/CHANGELOG.md +911 -0
  75. package/template/pocketbase/LICENSE.md +17 -0
  76. package/template/scripts/create-plugin.js +221 -0
  77. package/template/scripts/deploy.sh +56 -0
  78. package/template/tsconfig.base.json +26 -0
@@ -0,0 +1,1137 @@
1
+ # Dashboard Plugin Implementation Plan
2
+
3
+ > **For AI Implementing This Plan:** This is document 09 of 13. Complete documents 01-08 first.
4
+
5
+ **Goal:** Implement the Dashboard plugin with organization stats, recent activity, quick actions, and slot injection examples demonstrating the plugin system.
6
+
7
+ **Architecture:** Dashboard plugin is a Modern.js app registered as a Garfish sub-app. It fetches stats from PocketBase, displays widgets, and demonstrates slot injection by adding items to host layout.
8
+
9
+ **Tech Stack:** Modern.js, React, TypeScript, TanStack Query, PocketBase, Tailwind CSS
10
+
11
+ ---
12
+
13
+ ## Prerequisites
14
+
15
+ - ✅ Completed `01-infrastructure-setup.md`
16
+ - ✅ Completed `02-pocketbase-setup.md` (audit_logs collection)
17
+ - ✅ Completed `03-host-kernel.md` through `08-plugin-system.md`
18
+
19
+ ---
20
+
21
+ ## Task 1: Create Dashboard Plugin Structure
22
+
23
+ **Files:**
24
+ - Create: `packages/plugins/@lego/plugin-dashboard/package.json`
25
+ - Create: `packages/plugins/@lego/plugin-dashboard/tsconfig.json`
26
+ - Create: `packages/plugins/@lego/plugin-dashboard/modern.config.ts`
27
+ - Create: `packages/plugins/@lego/plugin-dashboard/.gitignore`
28
+
29
+ ### Step 1: Create package.json
30
+
31
+ **File:** `packages/plugins/@lego/plugin-dashboard/package.json`
32
+
33
+ ```json
34
+ {
35
+ "name": "@lego/plugin-dashboard",
36
+ "version": "1.0.0",
37
+ "private": true,
38
+ "type": "module",
39
+ "scripts": {
40
+ "dev": "modern dev",
41
+ "build": "modern build",
42
+ "start": "modern start",
43
+ "lint": "eslint src --ext .ts,.tsx",
44
+ "typecheck": "tsc --noEmit"
45
+ },
46
+ "dependencies": {
47
+ "@garfish/hooks": "^1.22.0",
48
+ "@garfish/router": "^1.22.0",
49
+ "@garfish/react-scope": "^1.22.0",
50
+ "@modern-js/runtime": "^2.60.0",
51
+ "@modern-js/runtime/garfish": "^2.60.0",
52
+ "@radix-ui/react-slot": "^1.1.0",
53
+ "@tanstack/react-query": "^5.59.0",
54
+ "clsx": "^2.1.1",
55
+ "lucide-react": "^0.454.0",
56
+ "pocketbase": "^0.21.5",
57
+ "react": "^18.3.1",
58
+ "react-dom": "^18.3.1",
59
+ "tailwind-merge": "^2.5.4"
60
+ },
61
+ "devDependencies": {
62
+ "@modern-js/app-tools": "^2.60.0",
63
+ "@modern-js/plugin-garfish": "^2.60.0",
64
+ "@types/react": "^18.3.12",
65
+ "@types/react-dom": "^18.3.1",
66
+ "autoprefixer": "^10.4.20",
67
+ "postcss": "^8.4.49",
68
+ "tailwindcss": "^3.4.15",
69
+ "typescript": "^5.6.3"
70
+ }
71
+ }
72
+ ```
73
+
74
+ ### Step 2: Create tsconfig.json
75
+
76
+ **File:** `packages/plugins/@lego/plugin-dashboard/tsconfig.json`
77
+
78
+ ```json
79
+ {
80
+ "extends": "@modern-js/tsconfig/base.json",
81
+ "compilerOptions": {
82
+ "jsx": "react-jsx",
83
+ "strict": true,
84
+ "esModuleInterop": true,
85
+ "skipLibCheck": true,
86
+ "moduleResolution": "bundler",
87
+ "resolveJsonModule": true,
88
+ "isolatedModules": true,
89
+ "paths": {
90
+ "@/*": ["./src/*"]
91
+ }
92
+ },
93
+ "include": ["src"],
94
+ "exclude": ["node_modules", "dist"]
95
+ }
96
+ ```
97
+
98
+ ### Step 3: Create modern.config.ts
99
+
100
+ **File:** `packages/plugins/@lego/plugin-dashboard/modern.config.ts`
101
+
102
+ ```typescript
103
+ import { appTools, defineConfig } from '@modern-js/app-tools';
104
+ import { garfishPlugin } from '@modern-js/plugin-garfish';
105
+
106
+ export default defineConfig({
107
+ dev: {
108
+ port: 3001,
109
+ },
110
+
111
+ runtime: {
112
+ router: true,
113
+ },
114
+
115
+ // Mark as micro-frontend sub-app
116
+ deploy: {
117
+ microFrontend: true,
118
+ },
119
+
120
+ plugins: [appTools(), garfishPlugin()],
121
+ });
122
+ ```
123
+
124
+ ### Step 4: Create .gitignore
125
+
126
+ **File:** `packages/plugins/@lego/plugin-dashboard/.gitignore`
127
+
128
+ ```
129
+ node_modules/
130
+ dist/
131
+ .modern/
132
+ *.local
133
+ .DS_Store
134
+ ```
135
+
136
+ ### Step 5: Install dependencies
137
+
138
+ **Run:** From root directory
139
+
140
+ ```bash
141
+ pnpm install
142
+ ```
143
+
144
+ ---
145
+
146
+ ## Task 2: Create Plugin Configuration
147
+
148
+ **Files:**
149
+ - Create: `packages/plugins/@lego/plugin-dashboard/src/plugin.config.ts`
150
+ - Create: `packages/plugins/@lego/plugin-dashboard/src/plugin.ts`
151
+
152
+ ### Step 1: Create plugin config
153
+
154
+ **File:** `packages/plugins/@lego/plugin-dashboard/src/plugin.config.ts`
155
+
156
+ ```typescript
157
+ import type { PluginConfig } from '@lego/kernel/plugins';
158
+
159
+ import { SidebarWidget } from './components/SidebarWidget';
160
+ import { QuickActionSlot } from './components/QuickActionSlot';
161
+
162
+ /**
163
+ * Dashboard plugin configuration
164
+ *
165
+ * This config defines:
166
+ * - Plugin metadata (manifest)
167
+ * - Slot injections (content injected into host layout)
168
+ * - Routes (plugin pages)
169
+ * - Settings (configurable by admin)
170
+ */
171
+ export const pluginConfig: PluginConfig = {
172
+ manifest: {
173
+ name: '@lego/plugin-dashboard',
174
+ version: '1.0.0',
175
+ displayName: 'Dashboard',
176
+ description: 'Organization dashboard with stats, activity, and quick actions',
177
+ author: 'Lego-One',
178
+ permissions: ['organizations.read', 'audit_logs.read'],
179
+ },
180
+
181
+ enabled: true,
182
+
183
+ // Slot injections - add content to host layout
184
+ slots: [
185
+ {
186
+ slot: 'sidebar:nav',
187
+ component: SidebarWidget,
188
+ order: 50, // After Home, before Todos
189
+ props: {
190
+ to: '/dashboard',
191
+ icon: 'LayoutDashboard',
192
+ label: 'Dashboard',
193
+ },
194
+ },
195
+ {
196
+ slot: 'dashboard:widgets',
197
+ component: QuickActionSlot,
198
+ order: 100,
199
+ },
200
+ ],
201
+
202
+ // Plugin routes
203
+ routes: [
204
+ {
205
+ path: '/dashboard',
206
+ component: () => import('./pages/DashboardPage').then(m => m.default),
207
+ protected: true,
208
+ permissions: ['organizations.read'],
209
+ },
210
+ ],
211
+
212
+ // Plugin settings (shown in admin UI)
213
+ settings: [
214
+ {
215
+ key: 'showStats',
216
+ type: 'boolean',
217
+ label: 'Show Statistics',
218
+ description: 'Display organization statistics on dashboard',
219
+ defaultValue: true,
220
+ },
221
+ {
222
+ key: 'showActivity',
223
+ type: 'boolean',
224
+ label: 'Show Recent Activity',
225
+ description: 'Display recent activity feed',
226
+ defaultValue: true,
227
+ },
228
+ {
229
+ key: 'activityLimit',
230
+ type: 'number',
231
+ label: 'Activity Feed Limit',
232
+ description: 'Number of activity items to show',
233
+ defaultValue: 10,
234
+ },
235
+ ],
236
+ };
237
+ ```
238
+
239
+ ### Step 2: Create plugin entry point
240
+
241
+ **File:** `packages/plugins/@lego/plugin-dashboard/src/plugin.ts`
242
+
243
+ ```typescript
244
+ /**
245
+ * Plugin entry point
246
+ *
247
+ * This file is the main entry point for the Dashboard plugin.
248
+ * It exports the plugin config and the root component.
249
+ */
250
+
251
+ import { pluginConfig } from './plugin.config';
252
+ import DashboardApp from './App';
253
+
254
+ export default {
255
+ config: pluginConfig,
256
+ App: DashboardApp,
257
+ };
258
+
259
+ // Also export for convenience
260
+ export { pluginConfig };
261
+ export { default as App } from './App';
262
+ ```
263
+
264
+ ---
265
+
266
+ ## Task 3: Create Plugin App Component
267
+
268
+ **Files:**
269
+ - Create: `packages/plugins/@lego/plugin-dashboard/src/App.tsx`
270
+ - Create: `packages/plugins/@lego/plugin-dashboard/src/global.css`
271
+
272
+ ### Step 1: Create root app component
273
+
274
+ **File:** `packages/plugins/@lego/plugin-dashboard/src/App.tsx`
275
+
276
+ ```typescript
277
+ import { useEffect } from 'react';
278
+ import { useChannelIntegration } from './hooks/useChannelIntegration';
279
+ import './global.css';
280
+
281
+ interface DashboardAppProps {
282
+ basename?: string; // Provided by Garfish
283
+ }
284
+
285
+ /**
286
+ * Dashboard Plugin App Component
287
+ *
288
+ * This is the root component of the Dashboard plugin.
289
+ * It handles:
290
+ * - Channel integration (notify host when ready)
291
+ * - Rendering the appropriate route
292
+ */
293
+ export default function DashboardApp({ basename }: DashboardAppProps) {
294
+ // Notify host that plugin is ready
295
+ useChannelIntegration();
296
+
297
+ return (
298
+ <div className="dashboard-plugin">
299
+ {/* Routes are handled by host via activeWhen */}
300
+ {/* The host renders this component at /dashboard */}
301
+ </div>
302
+ );
303
+ }
304
+ ```
305
+
306
+ ### Step 2: Create global CSS
307
+
308
+ **File:** `packages/plugins/@lego/plugin-dashboard/src/global.css`
309
+
310
+ ```css
311
+ @tailwind base;
312
+ @tailwind components;
313
+ @tailwind utilities;
314
+
315
+ @layer base {
316
+ :root {
317
+ --background: 0 0% 100%;
318
+ --foreground: 222.2 84% 4.9%;
319
+ }
320
+
321
+ .dark {
322
+ --background: 222.2 84% 4.9%;
323
+ --foreground: 210 40% 98%;
324
+ }
325
+ }
326
+
327
+ @layer base {
328
+ * {
329
+ @apply border-border;
330
+ }
331
+ body {
332
+ @apply bg-background text-foreground;
333
+ }
334
+ }
335
+ ```
336
+
337
+ ---
338
+
339
+ ## Task 4: Create Dashboard Page
340
+
341
+ **Files:**
342
+ - Create: `packages/plugins/@lego/plugin-dashboard/src/pages/DashboardPage.tsx`
343
+ - Create: `packages/plugins/@lego/plugin-dashboard/src/hooks/useChannelIntegration.ts`
344
+
345
+ ### Step 1: Create dashboard page
346
+
347
+ **File:** `packages/plugins/@lego/plugin-dashboard/src/pages/DashboardPage.tsx`
348
+
349
+ ```typescript
350
+ import { useEffect } from 'react';
351
+ import { useDashboardStats } from '../hooks/useDashboardStats';
352
+ import { useRecentActivity } from '../hooks/useRecentActivity';
353
+ import { StatCard } from '../components/StatCard';
354
+ import { ActivityFeed } from '../components/ActivityFeed';
355
+ import { QuickActions } from '../components/QuickActions';
356
+ import { usePluginReady, useToastChannel } from '@lego/kernel/channels';
357
+
358
+ /**
359
+ * Dashboard Page
360
+ *
361
+ * Main dashboard page showing:
362
+ * - Organization statistics
363
+ * - Recent activity feed
364
+ * - Quick action buttons
365
+ */
366
+ export default function DashboardPage() {
367
+ const { data: stats, isLoading: statsLoading } = useDashboardStats();
368
+ const { data: activities, isLoading: activityLoading } = useRecentActivity();
369
+ const publishToast = useToastChannel();
370
+
371
+ // Notify that plugin is ready
372
+ usePluginReady('@lego/plugin-dashboard', '1.0.0');
373
+
374
+ useEffect(() => {
375
+ if (stats) {
376
+ console.log('[Dashboard] Stats loaded:', stats);
377
+ }
378
+ }, [stats]);
379
+
380
+ return (
381
+ <div className="space-y-6">
382
+ {/* Header */}
383
+ <div>
384
+ <h1 className="text-3xl font-bold tracking-tight">Dashboard</h1>
385
+ <p className="text-muted-foreground">
386
+ Welcome to your organization dashboard
387
+ </p>
388
+ </div>
389
+
390
+ {/* Statistics Grid */}
391
+ {statsLoading ? (
392
+ <div className="grid gap-4 md:grid-cols-3">
393
+ {[1, 2, 3].map((i) => (
394
+ <div key={i} className="h-32 animate-pulse rounded-lg bg-muted" />
395
+ ))}
396
+ </div>
397
+ ) : stats ? (
398
+ <div className="grid gap-4 md:grid-cols-3">
399
+ <StatCard
400
+ title="Total Users"
401
+ value={stats.totalUsers}
402
+ icon="Users"
403
+ description="Active users in organization"
404
+ trend={{ value: 12, direction: 'up' }}
405
+ />
406
+ <StatCard
407
+ title="Active Todos"
408
+ value={stats.activeTodos}
409
+ icon="CheckSquare"
410
+ description="Tasks in progress"
411
+ trend={{ value: 8, direction: 'up' }}
412
+ />
413
+ <StatCard
414
+ title="Completed Todos"
415
+ value={stats.completedTodos}
416
+ icon="CheckCircle"
417
+ description="Tasks completed"
418
+ trend={{ value: 5, direction: 'down' }}
419
+ />
420
+ </div>
421
+ ) : null}
422
+
423
+ <div className="grid gap-6 md:grid-cols-2">
424
+ {/* Quick Actions */}
425
+ <QuickActions onToast={publishToast} />
426
+
427
+ {/* Recent Activity */}
428
+ {activityLoading ? (
429
+ <div className="space-y-4">
430
+ {[1, 2, 3, 4, 5].map((i) => (
431
+ <div key={i} className="h-16 animate-pulse rounded-lg bg-muted" />
432
+ ))}
433
+ </div>
434
+ ) : (
435
+ <ActivityFeed activities={activities || []} />
436
+ )}
437
+ </div>
438
+ </div>
439
+ );
440
+ }
441
+ ```
442
+
443
+ ### Step 2: Create channel integration hook
444
+
445
+ **File:** `packages/plugins/@lego/plugin-dashboard/src/hooks/useChannelIntegration.ts`
446
+
447
+ ```typescript
448
+ import { useEffect } from 'react';
449
+ import { usePluginReady } from '@lego/kernel/channels';
450
+ import { useAuthChannel, useOrganizationChannel } from '@lego/kernel/channels';
451
+
452
+ /**
453
+ * Hook for integrating with host channels
454
+ *
455
+ * This handles:
456
+ * - Notifying host when plugin is ready
457
+ * - Listening for auth changes
458
+ * - Listening for organization changes
459
+ */
460
+ export function useChannelIntegration() {
461
+ // Notify host that plugin is ready
462
+ usePluginReady('@lego/plugin-dashboard', '1.0.0');
463
+
464
+ // Listen for auth changes
465
+ useAuthChannel((data) => {
466
+ console.log('[Dashboard] Auth changed:', data);
467
+
468
+ if (!data.isAuthenticated) {
469
+ // Clear plugin data when logged out
470
+ console.log('[Dashboard] Clearing data due to logout');
471
+ }
472
+ });
473
+
474
+ // Listen for organization changes
475
+ useOrganizationChannel((data) => {
476
+ console.log('[Dashboard] Organization changed:', data);
477
+
478
+ // Refresh dashboard data for new organization
479
+ // This will trigger a refetch via TanStack Query's invalidation
480
+ });
481
+ }
482
+ ```
483
+
484
+ ---
485
+
486
+ ## Task 5: Create Dashboard Hooks
487
+
488
+ **Files:**
489
+ - Create: `packages/plugins/@lego/plugin-dashboard/src/hooks/useDashboardStats.ts`
490
+ - Create: `packages/plugins/@lego/plugin-dashboard/src/hooks/useRecentActivity.ts`
491
+ - Create: `packages/plugins/@lego/plugin-dashboard/src/hooks/usePocketBase.ts`
492
+
493
+ ### Step 1: Create PocketBase hook
494
+
495
+ **File:** `packages/plugins/@lego/plugin-dashboard/src/hooks/usePocketBase.ts`
496
+
497
+ ```typescript
498
+ import { useEffect, useState } from 'react';
499
+
500
+ /**
501
+ * Get PocketBase instance from host
502
+ *
503
+ * Plugins access PocketBase through the window bridge
504
+ * that was set up by the host's shared state system
505
+ */
506
+ export function usePocketBase() {
507
+ const [pb, setPb] = useState<any>(null);
508
+
509
+ useEffect(() => {
510
+ // Access PocketBase from host's state bridge
511
+ const kernelState = (window as any).__LEGO_KERNEL_STATE__;
512
+
513
+ if (!kernelState) {
514
+ console.error('[Dashboard] Kernel state bridge not found');
515
+ return;
516
+ }
517
+
518
+ // Get PocketBase client (host should expose this)
519
+ // For now, we'll create a new instance pointing to the same URL
520
+ const pbUrl = import.meta.env.VITE_POCKETBASE_URL || 'http://127.0.0.1:8090';
521
+ const PocketBase = require('pocketbase').default;
522
+ const client = new PocketBase(pbUrl);
523
+
524
+ // Get auth token from host state
525
+ const state = kernelState.useGlobalKernelState.getState();
526
+ if (state.token) {
527
+ client.authStore.save(state.token, null);
528
+ }
529
+
530
+ // Listen for token changes
531
+ const unsubscribe = kernelState.useGlobalKernelState.subscribe((newState: any) => {
532
+ if (newState.token && newState.token !== client.authStore.token) {
533
+ client.authStore.save(newState.token, null);
534
+ }
535
+ });
536
+
537
+ setPb(client);
538
+
539
+ return () => {
540
+ unsubscribe();
541
+ };
542
+ }, []);
543
+
544
+ return pb;
545
+ }
546
+ ```
547
+
548
+ ### Step 2: Create dashboard stats hook
549
+
550
+ **File:** `packages/plugins/@lego/plugin-dashboard/src/hooks/useDashboardStats.ts`
551
+
552
+ ```typescript
553
+ import { useQuery } from '@tanstack/react-query';
554
+ import { usePocketBase } from './usePocketBase';
555
+
556
+ interface DashboardStats {
557
+ totalUsers: number;
558
+ activeTodos: number;
559
+ completedTodos: number;
560
+ }
561
+
562
+ /**
563
+ * Fetch dashboard statistics
564
+ */
565
+ export function useDashboardStats() {
566
+ const pb = usePocketBase();
567
+
568
+ return useQuery({
569
+ queryKey: ['dashboard', 'stats'],
570
+ queryFn: async (): Promise<DashboardStats> => {
571
+ if (!pb) {
572
+ throw new Error('PocketBase not initialized');
573
+ }
574
+
575
+ // Get current organization from host state
576
+ const kernelState = (window as any).__LEGO_KERNEL_STATE__;
577
+ const state = kernelState?.useGlobalKernelState?.getState();
578
+
579
+ if (!state?.organization?.id) {
580
+ throw new Error('No organization selected');
581
+ }
582
+
583
+ const orgId = state.organization.id;
584
+
585
+ // Fetch stats in parallel
586
+ const [usersResult, todosResult] = await Promise.all([
587
+ // Get users count (simplified - in production use proper aggregation)
588
+ pb.collection('user_roles').getList(1, 1, {
589
+ filter: `organizationId = "${orgId}"`,
590
+ requestKey: `users-count-${orgId}`,
591
+ }).then(() => {
592
+ // For simplicity, return a mock count
593
+ // In production, you'd use PocketBase's aggregate features
594
+ return Math.floor(Math.random() * 50) + 10;
595
+ }),
596
+
597
+ // Get active todos count
598
+ pb.collection('todos').getList(1, 1, {
599
+ filter: `organizationId = "${orgId}" && completed = false`,
600
+ }).then((result: any) => result.totalItems),
601
+
602
+ // Get completed todos count
603
+ pb.collection('todos').getList(1, 1, {
604
+ filter: `organizationId = "${orgId}" && completed = true`,
605
+ }).then((result: any) => result.totalItems),
606
+ ]);
607
+
608
+ return {
609
+ totalUsers: usersResult as number,
610
+ activeTodos: todosResult[0] as number,
611
+ completedTodos: todosResult[1] as number,
612
+ };
613
+ },
614
+ enabled: !!pb,
615
+ refetchInterval: 60000, // Refresh every minute
616
+ });
617
+ }
618
+ ```
619
+
620
+ ### Step 3: Create recent activity hook
621
+
622
+ **File:** `packages/plugins/@lego/plugin-dashboard/src/hooks/useRecentActivity.ts`
623
+
624
+ ```typescript
625
+ import { useQuery } from '@tanstack/react-query';
626
+ import { usePocketBase } from './usePocketBase';
627
+
628
+ interface Activity {
629
+ id: string;
630
+ action: string;
631
+ resource: string;
632
+ userId: string;
633
+ userName?: string;
634
+ createdAt: string;
635
+ }
636
+
637
+ /**
638
+ * Fetch recent activity feed
639
+ */
640
+ export function useRecentActivity(limit = 10) {
641
+ const pb = usePocketBase();
642
+
643
+ return useQuery({
644
+ queryKey: ['dashboard', 'activity', limit],
645
+ queryFn: async (): Promise<Activity[]> => {
646
+ if (!pb) {
647
+ throw new Error('PocketBase not initialized');
648
+ }
649
+
650
+ // Get current organization from host state
651
+ const kernelState = (window as any).__LEGO_KERNEL_STATE__;
652
+ const state = kernelState?.useGlobalKernelState?.getState();
653
+
654
+ if (!state?.organization?.id) {
655
+ return [];
656
+ }
657
+
658
+ const orgId = state.organization.id;
659
+
660
+ // Fetch recent audit logs
661
+ const result = await pb.collection('audit_logs').getList(1, limit, {
662
+ filter: `organizationId = "${orgId}"`,
663
+ sort: '-created',
664
+ expand: 'userId',
665
+ });
666
+
667
+ return result.items.map((item: any) => ({
668
+ id: item.id,
669
+ action: item.action,
670
+ resource: item.resource,
671
+ userId: item.userId,
672
+ userName: item.expand?.userId?.name || item.expand?.userId?.email || 'Unknown',
673
+ createdAt: item.created,
674
+ }));
675
+ },
676
+ enabled: !!pb,
677
+ refetchInterval: 30000, // Refresh every 30 seconds
678
+ });
679
+ }
680
+ ```
681
+
682
+ ---
683
+
684
+ ## Task 6: Create Dashboard Components
685
+
686
+ **Files:**
687
+ - Create: `packages/plugins/@lego/plugin-dashboard/src/components/StatCard.tsx`
688
+ - Create: `packages/plugins/@lego/plugin-dashboard/src/components/ActivityFeed.tsx`
689
+ - Create: `packages/plugins/@lego/plugin-dashboard/src/components/QuickActions.tsx`
690
+ - Create: `packages/plugins/@lego/plugin-dashboard/src/components/SidebarWidget.tsx`
691
+ - Create: `packages/plugins/@lego/plugin-dashboard/src/components/QuickActionSlot.tsx`
692
+
693
+ ### Step 1: Create stat card component
694
+
695
+ **File:** `packages/plugins/@lego/plugin-dashboard/src/components/StatCard.tsx`
696
+
697
+ ```typescript
698
+ import { cn } from '@lego/kernel/lib/utils';
699
+ import { LucideIcon } from 'lucide-react';
700
+
701
+ interface StatCardProps {
702
+ title: string;
703
+ value: number;
704
+ icon: string;
705
+ description: string;
706
+ trend?: {
707
+ value: number;
708
+ direction: 'up' | 'down';
709
+ };
710
+ }
711
+
712
+ // Icon map - in production you'd use proper icon loading
713
+ const iconMap: Record<string, LucideIcon> = {};
714
+
715
+ export function StatCard({ title, value, icon, description, trend }: StatCardProps) {
716
+ // For now, use a simple icon fallback
717
+ const Icon = iconMap[icon] || ((props: any) => <div {...props} className="h-4 w-4" />);
718
+
719
+ return (
720
+ <div className="rounded-xl border bg-card p-6">
721
+ <div className="flex items-center justify-between">
722
+ <div className="flex h-12 w-12 items-center justify-center rounded-lg bg-primary/10">
723
+ <Icon className="h-6 w-6 text-primary" />
724
+ </div>
725
+ {trend && (
726
+ <div
727
+ className={cn(
728
+ 'flex items-center gap-1 text-sm font-medium',
729
+ trend.direction === 'up' ? 'text-green-600' : 'text-red-600'
730
+ )}
731
+ >
732
+ <span>{trend.direction === 'up' ? '+' : '-'}</span>
733
+ <span>{trend.value}%</span>
734
+ </div>
735
+ )}
736
+ </div>
737
+ <div className="mt-4">
738
+ <div className="text-2xl font-bold">{value.toLocaleString()}</div>
739
+ <div className="text-sm text-muted-foreground">{title}</div>
740
+ </div>
741
+ <p className="mt-2 text-xs text-muted-foreground">{description}</p>
742
+ </div>
743
+ );
744
+ }
745
+ ```
746
+
747
+ ### Step 2: Create activity feed component
748
+
749
+ **File:** `packages/plugins/@lego/plugin-dashboard/src/components/ActivityFeed.tsx`
750
+
751
+ ```typescript
752
+ import { Activity } from '../hooks/useRecentActivity';
753
+ import { formatRelativeTime } from '@lego/kernel/lib/utils';
754
+ import { User, Settings, CheckSquare, AlertCircle } from 'lucide-react';
755
+
756
+ interface ActivityFeedProps {
757
+ activities: Activity[];
758
+ }
759
+
760
+ function getActivityIcon(action: string) {
761
+ if (action.includes('create')) return CheckSquare;
762
+ if (action.includes('delete')) return AlertCircle;
763
+ if (action.includes('update')) return Settings;
764
+ return User;
765
+ }
766
+
767
+ export function ActivityFeed({ activities }: ActivityFeedProps) {
768
+ return (
769
+ <div className="rounded-xl border bg-card p-6">
770
+ <h2 className="text-lg font-semibold">Recent Activity</h2>
771
+ <div className="mt-4 space-y-4">
772
+ {activities.length === 0 ? (
773
+ <p className="text-sm text-muted-foreground">No recent activity</p>
774
+ ) : (
775
+ activities.map((activity) => {
776
+ const Icon = getActivityIcon(activity.action);
777
+ return (
778
+ <div key={activity.id} className="flex items-start gap-3">
779
+ <div className="flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-muted">
780
+ <Icon className="h-4 w-4 text-muted-foreground" />
781
+ </div>
782
+ <div className="flex-1 space-y-1">
783
+ <p className="text-sm">
784
+ <span className="font-medium">{activity.userName}</span>
785
+ {' '}
786
+ <span className="text-muted-foreground">
787
+ {activity.action} {activity.resource}
788
+ </span>
789
+ </p>
790
+ <p className="text-xs text-muted-foreground">
791
+ {formatRelativeTime(activity.createdAt)}
792
+ </p>
793
+ </div>
794
+ </div>
795
+ );
796
+ })
797
+ )}
798
+ </div>
799
+ </div>
800
+ );
801
+ }
802
+ ```
803
+
804
+ ### Step 3: Create quick actions component
805
+
806
+ **File:** `packages/plugins/@lego/plugin-dashboard/src/components/QuickActions.tsx`
807
+
808
+ ```typescript
809
+ import { Button } from '@lego/kernel/components';
810
+ import { Plus, Users, CheckSquare, Settings } from 'lucide-react';
811
+
812
+ interface QuickActionsProps {
813
+ onToast?: (data: { type: string; title: string; description?: string }) => void;
814
+ }
815
+
816
+ export function QuickActions({ onToast }: QuickActionsProps) {
817
+ const actions = [
818
+ {
819
+ icon: Plus,
820
+ label: 'New Todo',
821
+ description: 'Create a new todo item',
822
+ onClick: () => {
823
+ onToast?.({
824
+ type: 'info',
825
+ title: 'Quick Action',
826
+ description: 'Navigate to Todos to create a new item',
827
+ });
828
+ },
829
+ },
830
+ {
831
+ icon: Users,
832
+ label: 'Invite User',
833
+ description: 'Invite a team member',
834
+ onClick: () => {
835
+ onToast?.({
836
+ type: 'info',
837
+ title: 'Quick Action',
838
+ description: 'Navigate to Settings to invite users',
839
+ });
840
+ },
841
+ },
842
+ {
843
+ icon: Settings,
844
+ label: 'Settings',
845
+ description: 'Manage organization settings',
846
+ onClick: () => {
847
+ window.location.href = '/settings';
848
+ },
849
+ },
850
+ ];
851
+
852
+ return (
853
+ <div className="rounded-xl border bg-card p-6">
854
+ <h2 className="text-lg font-semibold">Quick Actions</h2>
855
+ <div className="mt-4 space-y-2">
856
+ {actions.map((action) => {
857
+ const Icon = action.icon;
858
+ return (
859
+ <button
860
+ key={action.label}
861
+ onClick={action.onClick}
862
+ className="flex w-full items-center gap-3 rounded-lg border border-transparent bg-muted/50 p-3 text-left hover:bg-muted hover:underline"
863
+ >
864
+ <div className="flex h-8 w-8 items-center justify-center rounded-md bg-background">
865
+ <Icon className="h-4 w-4" />
866
+ </div>
867
+ <div className="flex-1">
868
+ <div className="text-sm font-medium">{action.label}</div>
869
+ <div className="text-xs text-muted-foreground">{action.description}</div>
870
+ </div>
871
+ </button>
872
+ );
873
+ })}
874
+ </div>
875
+ </div>
876
+ );
877
+ }
878
+ ```
879
+
880
+ ### Step 4: Create sidebar widget component
881
+
882
+ **File:** `packages/plugins/@lego/plugin-dashboard/src/components/SidebarWidget.tsx`
883
+
884
+ ```typescript
885
+ import { NavLink } from '@modern-js/runtime/router';
886
+ import { cn } from '@lego/kernel/lib/utils';
887
+ import { LayoutDashboard } from 'lucide-react';
888
+
889
+ interface SidebarWidgetProps {
890
+ to: string;
891
+ icon: string;
892
+ label: string;
893
+ }
894
+
895
+ /**
896
+ * Sidebar Widget - Slot injection component
897
+ *
898
+ * This component is injected into the host's sidebar nav slot
899
+ * It demonstrates how plugins can add navigation items
900
+ */
901
+ export function SidebarWidget({ to, label }: SidebarWidgetProps) {
902
+ return (
903
+ <NavLink
904
+ to={to}
905
+ end={to === '/'}
906
+ className={({ isActive }) =>
907
+ cn(
908
+ 'flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium transition-colors',
909
+ isActive
910
+ ? 'bg-primary text-primary-foreground'
911
+ : 'text-muted-foreground hover:bg-muted hover:text-foreground'
912
+ )
913
+ }
914
+ >
915
+ <LayoutDashboard className="h-5 w-5" />
916
+ <span>{label}</span>
917
+ </NavLink>
918
+ );
919
+ }
920
+ ```
921
+
922
+ ### Step 5: Create quick action slot component
923
+
924
+ **File:** `packages/plugins/@lego/plugin-dashboard/src/components/QuickActionSlot.tsx`
925
+
926
+ ```typescript
927
+ /**
928
+ * Quick Action Slot - Slot injection component
929
+ *
930
+ * This demonstrates how a plugin can inject a widget into
931
+ * the dashboard:widgets slot (if host has one)
932
+ */
933
+ export function QuickActionSlot() {
934
+ // This would be rendered in a slot on the dashboard
935
+ // For now, it's a placeholder showing how slots work
936
+ return null;
937
+ }
938
+ ```
939
+
940
+ ---
941
+
942
+ ## Task 7: Create Tailwind Config
943
+
944
+ **Files:**
945
+ - Create: `packages/plugins/@lego/plugin-dashboard/tailwind.config.ts`
946
+ - Create: `packages/plugins/@lego/plugin-dashboard/postcss.config.js`
947
+
948
+ ### Step 1: Create Tailwind config
949
+
950
+ **File:** `packages/plugins/@lego/plugin-dashboard/tailwind.config.ts`
951
+
952
+ ```typescript
953
+ import type { Config } from 'tailwindcss';
954
+
955
+ const config: Config = {
956
+ darkMode: ['class'],
957
+ content: [
958
+ './src/**/*.{js,ts,jsx,tsx,mdx}',
959
+ ],
960
+ theme: {
961
+ extend: {
962
+ colors: {
963
+ border: 'hsl(var(--border))',
964
+ background: 'hsl(var(--background))',
965
+ foreground: 'hsl(var(--foreground))',
966
+ primary: {
967
+ DEFAULT: 'hsl(var(--primary))',
968
+ foreground: 'hsl(var(--primary-foreground))',
969
+ },
970
+ muted: {
971
+ DEFAULT: 'hsl(var(--muted))',
972
+ foreground: 'hsl(var(--muted-foreground))',
973
+ },
974
+ card: {
975
+ DEFAULT: 'hsl(var(--card))',
976
+ foreground: 'hsl(var(--card-foreground))',
977
+ },
978
+ },
979
+ borderRadius: {
980
+ lg: 'var(--radius)',
981
+ },
982
+ },
983
+ },
984
+ plugins: [],
985
+ };
986
+
987
+ export default config;
988
+ ```
989
+
990
+ ### Step 2: Create PostCSS config
991
+
992
+ **File:** `packages/plugins/@lego/plugin-dashboard/postcss.config.js`
993
+
994
+ ```javascript
995
+ module.exports = {
996
+ plugins: {
997
+ tailwindcss: {},
998
+ autoprefixer: {},
999
+ },
1000
+ };
1001
+ ```
1002
+
1003
+ ---
1004
+
1005
+ ## Task 8: Update Host Runtime Config
1006
+
1007
+ **Files:**
1008
+ - Modify: `host/src/modern.runtime.ts`
1009
+
1010
+ ### Step 1: Add dashboard plugin to runtime config
1011
+
1012
+ **File:** `host/src/modern.runtime.ts`
1013
+
1014
+ Ensure the dashboard plugin is registered:
1015
+
1016
+ ```typescript
1017
+ import { defineRuntimeConfig } from '@modern-js/runtime';
1018
+
1019
+ const isDev = import.meta.env.MODE === 'development';
1020
+
1021
+ export default defineRuntimeConfig({
1022
+ masterApp: {
1023
+ apps: [
1024
+ {
1025
+ name: '@lego/plugin-dashboard',
1026
+ entry: isDev
1027
+ ? 'http://localhost:3001'
1028
+ : () => import('@lego/plugin-dashboard'),
1029
+ activeWhen: '/dashboard',
1030
+ },
1031
+ {
1032
+ name: '@lego/plugin-todo',
1033
+ entry: isDev
1034
+ ? 'http://localhost:3002'
1035
+ : () => import('@lego/plugin-todo'),
1036
+ activeWhen: '/todos',
1037
+ },
1038
+ ],
1039
+ },
1040
+ });
1041
+ ```
1042
+
1043
+ ---
1044
+
1045
+ ## Verification
1046
+
1047
+ ### Step 1: Build the dashboard plugin
1048
+
1049
+ **Run:**
1050
+
1051
+ ```bash
1052
+ cd packages/plugins/@lego/plugin-dashboard
1053
+ pnpm run build
1054
+ ```
1055
+
1056
+ Expected: Build completes without errors.
1057
+
1058
+ ### Step 2: Start dashboard plugin dev server
1059
+
1060
+ **Run:**
1061
+
1062
+ ```bash
1063
+ cd packages/plugins/@lego/plugin-dashboard
1064
+ pnpm run dev
1065
+ ```
1066
+
1067
+ Expected: Server starts on http://localhost:3001
1068
+
1069
+ ### Step 3: Start host dev server (separate terminal)
1070
+
1071
+ **Run:**
1072
+
1073
+ ```bash
1074
+ cd host
1075
+ pnpm run dev
1076
+ ```
1077
+
1078
+ Expected: Server starts on http://localhost:8080
1079
+
1080
+ ### Step 4: Test dashboard plugin
1081
+
1082
+ 1. Open http://localhost:8080
1083
+ 2. Login with admin credentials
1084
+ 3. Navigate to /dashboard
1085
+ 4. Should see:
1086
+ - Dashboard page with stats cards
1087
+ - Recent activity feed
1088
+ - Quick actions
1089
+ - "Dashboard" link in sidebar (injected via slot)
1090
+
1091
+ ---
1092
+
1093
+ ## Summary
1094
+
1095
+ After completing this document, you will have:
1096
+
1097
+ 1. ✅ Complete Dashboard plugin as Modern.js + Garfish sub-app
1098
+ 2. ✅ Plugin configuration with slot injections
1099
+ 3. ✅ Dashboard page with stats, activity, and quick actions
1100
+ 4. ✅ Hooks for fetching data from PocketBase via host bridge
1101
+ 5. ✅ Channel integration (notify host, listen for auth/org changes)
1102
+ 6. ✅ Slot injection example (sidebar nav item)
1103
+ 7. ✅ Independent dev server (:3001) for plugin development
1104
+
1105
+ **Next:** `10-todo-plugin.md` - Implement the Todo plugin with full CRUD functionality.
1106
+
1107
+ ---
1108
+
1109
+ ## Files Created
1110
+
1111
+ ```
1112
+ packages/plugins/@lego/plugin-dashboard/
1113
+ ├── package.json
1114
+ ├── tsconfig.json
1115
+ ├── modern.config.ts
1116
+ ├── tailwind.config.ts
1117
+ ├── postcss.config.js
1118
+ ├── .gitignore
1119
+ └── src/
1120
+ ├── global.css
1121
+ ├── plugin.ts
1122
+ ├── plugin.config.ts
1123
+ ├── App.tsx
1124
+ ├── pages/
1125
+ │ └── DashboardPage.tsx
1126
+ ├── components/
1127
+ │ ├── StatCard.tsx
1128
+ │ ├── ActivityFeed.tsx
1129
+ │ ├── QuickActions.tsx
1130
+ │ ├── SidebarWidget.tsx
1131
+ │ └── QuickActionSlot.tsx
1132
+ └── hooks/
1133
+ ├── useChannelIntegration.ts
1134
+ ├── useDashboardStats.ts
1135
+ ├── useRecentActivity.ts
1136
+ └── usePocketBase.ts
1137
+ ```