create-lego-one 2.0.10 → 2.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +179 -0
- package/dist/index.cjs.map +1 -1
- package/package.json +5 -3
- package/template/.cursor/rules/rules.mdc +639 -0
- package/template/.dockerignore +58 -0
- package/template/.env.example +18 -0
- package/template/.eslintignore +5 -0
- package/template/.eslintrc.js +28 -0
- package/template/.prettierignore +6 -0
- package/template/.prettierrc +11 -0
- package/template/CLAUDE.md +634 -0
- package/template/Dockerfile +67 -0
- package/template/PROMPT.md +457 -0
- package/template/README.md +325 -0
- package/template/docker-compose.yml +48 -0
- package/template/docker-entrypoint.sh +23 -0
- package/template/docs/checkpoints/.template.md +64 -0
- package/template/docs/checkpoints/framework/01-infrastructure-setup.md +132 -0
- package/template/docs/checkpoints/framework/02-pocketbase-setup.md +155 -0
- package/template/docs/checkpoints/framework/03-host-kernel.md +170 -0
- package/template/docs/checkpoints/framework/04-auth-system.md +163 -0
- package/template/docs/checkpoints/framework/phase-05-multitenancy-rbac.md +223 -0
- package/template/docs/checkpoints/framework/phase-06-ui-components.md +260 -0
- package/template/docs/checkpoints/framework/phase-07-communication-system.md +276 -0
- package/template/docs/checkpoints/framework/phase-08-plugin-system.md +91 -0
- package/template/docs/checkpoints/framework/phase-09-dashboard-plugin.md +111 -0
- package/template/docs/checkpoints/framework/phase-10-todo-plugin.md +169 -0
- package/template/docs/checkpoints/framework/phase-11-testing.md +264 -0
- package/template/docs/checkpoints/framework/phase-12-deployment.md +294 -0
- package/template/docs/checkpoints/framework/phase-13-documentation.md +312 -0
- package/template/docs/framework/plans/00-index.md +164 -0
- package/template/docs/framework/plans/01-infrastructure-setup.md +855 -0
- package/template/docs/framework/plans/02-pocketbase-setup.md +1374 -0
- package/template/docs/framework/plans/03-host-kernel.md +1518 -0
- package/template/docs/framework/plans/04-auth-system.md +1466 -0
- package/template/docs/framework/plans/05-multitenancy-rbac.md +1527 -0
- package/template/docs/framework/plans/06-ui-components.md +1478 -0
- package/template/docs/framework/plans/07-communication-system.md +1106 -0
- package/template/docs/framework/plans/08-plugin-system.md +1179 -0
- package/template/docs/framework/plans/09-dashboard-plugin.md +1137 -0
- package/template/docs/framework/plans/10-todo-plugin.md +1343 -0
- package/template/docs/framework/plans/11-testing.md +935 -0
- package/template/docs/framework/plans/12-deployment.md +896 -0
- package/template/docs/framework/prompts/0-boilerplate-modernjs.md +151 -0
- package/template/docs/framework/research/00-modernjs-audit.md +488 -0
- package/template/docs/framework/research/01-system-blueprint.md +721 -0
- package/template/docs/framework/research/02-data-migration-protocol.md +699 -0
- package/template/docs/framework/research/03-host-setup.md +714 -0
- package/template/docs/framework/research/04-plugin-architecture.md +645 -0
- package/template/docs/framework/research/05-slot-injection-pattern.md +671 -0
- package/template/docs/framework/research/06-cli-strategy.md +615 -0
- package/template/docs/framework/research/07-deployment.md +629 -0
- package/template/docs/framework/research/README.md +282 -0
- package/template/docs/framework/setup/00-index.md +210 -0
- package/template/docs/framework/setup/01-framework-structure.md +308 -0
- package/template/docs/framework/setup/02-development-workflow.md +405 -0
- package/template/docs/framework/setup/03-environment-setup.md +215 -0
- package/template/docs/framework/setup/04-kernel-architecture.md +499 -0
- package/template/docs/framework/setup/05-plugin-system.md +620 -0
- package/template/docs/framework/setup/06-communication-patterns.md +451 -0
- package/template/docs/framework/setup/07-plugin-development.md +582 -0
- package/template/docs/framework/setup/08-component-library.md +658 -0
- package/template/docs/framework/setup/09-data-integration.md +609 -0
- package/template/docs/framework/setup/10-auth-rbac.md +497 -0
- package/template/docs/framework/setup/11-hooks-api.md +393 -0
- package/template/docs/framework/setup/12-components-api.md +665 -0
- package/template/docs/framework/setup/13-deployment-guide.md +566 -0
- package/template/docs/framework/setup/README.md +548 -0
- package/template/host/e2e/auth.spec.ts +38 -0
- package/template/host/e2e/layout.spec.ts +38 -0
- package/template/host/modern.config.ts +19 -0
- package/template/host/package.json +71 -0
- package/template/host/playwright.config.ts +34 -0
- package/template/host/postcss.config.mjs +6 -0
- package/template/host/src/App.tsx +6 -0
- package/template/host/src/bootstrap.tsx +74 -0
- package/template/host/src/global.css +59 -0
- package/template/host/src/index.ts +2 -0
- package/template/host/src/kernel/__tests__/lib-utils.test.ts +32 -0
- package/template/host/src/kernel/__tests__/rbac-hooks.test.tsx +114 -0
- package/template/host/src/kernel/__tests__/rbac-utils.test.ts +108 -0
- package/template/host/src/kernel/auth/ProtectedRoute.tsx +41 -0
- package/template/host/src/kernel/auth/components/LoginForm.tsx +97 -0
- package/template/host/src/kernel/auth/components/LogoutButton.tsx +79 -0
- package/template/host/src/kernel/auth/hooks.ts +174 -0
- package/template/host/src/kernel/auth/index.ts +5 -0
- package/template/host/src/kernel/auth/schemas.ts +27 -0
- package/template/host/src/kernel/auth/service.ts +197 -0
- package/template/host/src/kernel/auth/types.ts +36 -0
- package/template/host/src/kernel/channels/ChannelBus.ts +181 -0
- package/template/host/src/kernel/channels/ChannelProvider.tsx +57 -0
- package/template/host/src/kernel/channels/events.ts +27 -0
- package/template/host/src/kernel/channels/hooks.ts +168 -0
- package/template/host/src/kernel/channels/index.ts +6 -0
- package/template/host/src/kernel/channels/integrations/ToastIntegration.tsx +60 -0
- package/template/host/src/kernel/channels/plugin-hooks.ts +72 -0
- package/template/host/src/kernel/channels/types.ts +112 -0
- package/template/host/src/kernel/components/__tests__/Badge.test.tsx +35 -0
- package/template/host/src/kernel/components/__tests__/Button.test.tsx +63 -0
- package/template/host/src/kernel/components/__tests__/Input.test.tsx +64 -0
- package/template/host/src/kernel/components/index.ts +32 -0
- package/template/host/src/kernel/components/ui/alert.tsx +58 -0
- package/template/host/src/kernel/components/ui/avatar.tsx +47 -0
- package/template/host/src/kernel/components/ui/badge.tsx +35 -0
- package/template/host/src/kernel/components/ui/button.tsx +50 -0
- package/template/host/src/kernel/components/ui/card.tsx +78 -0
- package/template/host/src/kernel/components/ui/dialog.tsx +116 -0
- package/template/host/src/kernel/components/ui/dropdown-menu.tsx +192 -0
- package/template/host/src/kernel/components/ui/index.ts +7 -0
- package/template/host/src/kernel/components/ui/input.tsx +24 -0
- package/template/host/src/kernel/components/ui/label.tsx +21 -0
- package/template/host/src/kernel/components/ui/popover.tsx +28 -0
- package/template/host/src/kernel/components/ui/progress.tsx +25 -0
- package/template/host/src/kernel/components/ui/scroll-area.tsx +45 -0
- package/template/host/src/kernel/components/ui/select.tsx +155 -0
- package/template/host/src/kernel/components/ui/separator.tsx +28 -0
- package/template/host/src/kernel/components/ui/skeleton.tsx +15 -0
- package/template/host/src/kernel/components/ui/switch.tsx +26 -0
- package/template/host/src/kernel/components/ui/table.tsx +116 -0
- package/template/host/src/kernel/components/ui/tabs.tsx +52 -0
- package/template/host/src/kernel/components/ui/toast.tsx +126 -0
- package/template/host/src/kernel/components/ui/toaster.tsx +34 -0
- package/template/host/src/kernel/components/ui/tooltip.tsx +27 -0
- package/template/host/src/kernel/components/ui/use-toast.ts +183 -0
- package/template/host/src/kernel/index.ts +48 -0
- package/template/host/src/kernel/lib/cn.ts +1 -0
- package/template/host/src/kernel/lib/utils.ts +36 -0
- package/template/host/src/kernel/plugins/Slot.tsx +41 -0
- package/template/host/src/kernel/plugins/SlotProvider.tsx +88 -0
- package/template/host/src/kernel/plugins/index.ts +23 -0
- package/template/host/src/kernel/plugins/loader.ts +122 -0
- package/template/host/src/kernel/plugins/schemas.ts +54 -0
- package/template/host/src/kernel/plugins/store.ts +185 -0
- package/template/host/src/kernel/plugins/types.ts +103 -0
- package/template/host/src/kernel/providers/PocketBaseProvider.tsx +70 -0
- package/template/host/src/kernel/providers/QueryProvider.tsx +28 -0
- package/template/host/src/kernel/providers/ThemeProvider.tsx +25 -0
- package/template/host/src/kernel/providers/index.ts +3 -0
- package/template/host/src/kernel/rbac/components/OrganizationSelector.tsx +69 -0
- package/template/host/src/kernel/rbac/components/PermissionGate.tsx +43 -0
- package/template/host/src/kernel/rbac/hooks.ts +379 -0
- package/template/host/src/kernel/rbac/index.ts +6 -0
- package/template/host/src/kernel/rbac/service.ts +504 -0
- package/template/host/src/kernel/rbac/types.ts +164 -0
- package/template/host/src/kernel/rbac/utils.ts +34 -0
- package/template/host/src/kernel/shared-state/bridge.ts +31 -0
- package/template/host/src/kernel/shared-state/index.ts +3 -0
- package/template/host/src/kernel/shared-state/store.ts +62 -0
- package/template/host/src/kernel/shared-state/types.ts +60 -0
- package/template/host/src/kernel/use-migrations.ts +72 -0
- package/template/host/src/layout/MobileMenu.tsx +61 -0
- package/template/host/src/layout/Shell.tsx +42 -0
- package/template/host/src/layout/Sidebar.tsx +178 -0
- package/template/host/src/layout/Topbar.tsx +50 -0
- package/template/host/src/layout/index.ts +4 -0
- package/template/host/src/lib/pocketbase/client.ts +38 -0
- package/template/host/src/lib/pocketbase/collections/audit_logs.ts +87 -0
- package/template/host/src/lib/pocketbase/collections/index.ts +19 -0
- package/template/host/src/lib/pocketbase/collections/organizations.ts +63 -0
- package/template/host/src/lib/pocketbase/collections/permissions.ts +57 -0
- package/template/host/src/lib/pocketbase/collections/roles.ts +55 -0
- package/template/host/src/lib/pocketbase/collections/todos.ts +74 -0
- package/template/host/src/lib/pocketbase/collections/user_roles.ts +57 -0
- package/template/host/src/lib/pocketbase/collections/users.ts +43 -0
- package/template/host/src/lib/pocketbase/index.ts +5 -0
- package/template/host/src/lib/pocketbase/migrations.ts +44 -0
- package/template/host/src/lib/pocketbase/seed/permissions.ts +8 -0
- package/template/host/src/lib/pocketbase/seed/roles.ts +22 -0
- package/template/host/src/lib/pocketbase/seed.ts +113 -0
- package/template/host/src/lib/pocketbase/types.ts +102 -0
- package/template/host/src/modern.runtime.ts +26 -0
- package/template/host/src/plugins.d.ts +9 -0
- package/template/host/src/providers/PocketBaseProvider.tsx +30 -0
- package/template/host/src/routes/_.tsx +6 -0
- package/template/host/src/routes/dashboard._.tsx +41 -0
- package/template/host/src/routes/index.tsx +93 -0
- package/template/host/src/routes/login.tsx +36 -0
- package/template/host/src/saas.config.ts +52 -0
- package/template/host/src/test/setup.ts +65 -0
- package/template/host/src/test/utils.tsx +69 -0
- package/template/host/src/test/vitest-globals.d.ts +19 -0
- package/template/host/src/vite-env.d.ts +16 -0
- package/template/host/tailwind.config.ts +77 -0
- package/template/host/tsconfig.json +19 -0
- package/template/host/vitest.config.ts +30 -0
- package/template/nginx.conf +72 -0
- package/template/package.json +44 -0
- package/template/packages/plugins/@lego/plugin-dashboard/modern.config.ts +19 -0
- package/template/packages/plugins/@lego/plugin-dashboard/package.json +35 -0
- package/template/packages/plugins/@lego/plugin-dashboard/postcss.config.mjs +6 -0
- package/template/packages/plugins/@lego/plugin-dashboard/src/App.tsx +27 -0
- package/template/packages/plugins/@lego/plugin-dashboard/src/components/ActivityFeed.tsx +63 -0
- package/template/packages/plugins/@lego/plugin-dashboard/src/components/QuickActionSlot.tsx +11 -0
- package/template/packages/plugins/@lego/plugin-dashboard/src/components/QuickActions.tsx +68 -0
- package/template/packages/plugins/@lego/plugin-dashboard/src/components/SidebarWidget.tsx +35 -0
- package/template/packages/plugins/@lego/plugin-dashboard/src/components/StatCard.tsx +47 -0
- package/template/packages/plugins/@lego/plugin-dashboard/src/global.css +24 -0
- package/template/packages/plugins/@lego/plugin-dashboard/src/hooks/useChannelIntegration.ts +43 -0
- package/template/packages/plugins/@lego/plugin-dashboard/src/hooks/useDashboardStats.ts +65 -0
- package/template/packages/plugins/@lego/plugin-dashboard/src/hooks/usePocketBase.ts +47 -0
- package/template/packages/plugins/@lego/plugin-dashboard/src/hooks/useRecentActivity.ts +55 -0
- package/template/packages/plugins/@lego/plugin-dashboard/src/lib/utils.ts +6 -0
- package/template/packages/plugins/@lego/plugin-dashboard/src/pages/DashboardPage.tsx +105 -0
- package/template/packages/plugins/@lego/plugin-dashboard/src/plugin.config.ts +121 -0
- package/template/packages/plugins/@lego/plugin-dashboard/src/plugin.ts +18 -0
- package/template/packages/plugins/@lego/plugin-dashboard/src/vite-env.d.ts +32 -0
- package/template/packages/plugins/@lego/plugin-dashboard/tailwind.config.ts +35 -0
- package/template/packages/plugins/@lego/plugin-dashboard/tsconfig.json +18 -0
- package/template/packages/plugins/@lego/plugin-todo/modern.config.ts +18 -0
- package/template/packages/plugins/@lego/plugin-todo/package.json +41 -0
- package/template/packages/plugins/@lego/plugin-todo/postcss.config.mjs +6 -0
- package/template/packages/plugins/@lego/plugin-todo/src/App.tsx +12 -0
- package/template/packages/plugins/@lego/plugin-todo/src/components/SidebarWidget.tsx +16 -0
- package/template/packages/plugins/@lego/plugin-todo/src/components/TodoDialog.tsx +55 -0
- package/template/packages/plugins/@lego/plugin-todo/src/components/TodoFilters.tsx +79 -0
- package/template/packages/plugins/@lego/plugin-todo/src/components/TodoForm.tsx +94 -0
- package/template/packages/plugins/@lego/plugin-todo/src/components/TodoItem.tsx +121 -0
- package/template/packages/plugins/@lego/plugin-todo/src/components/TodoList.tsx +41 -0
- package/template/packages/plugins/@lego/plugin-todo/src/components/index.ts +6 -0
- package/template/packages/plugins/@lego/plugin-todo/src/global.css +59 -0
- package/template/packages/plugins/@lego/plugin-todo/src/hooks/useCreateTodo.ts +62 -0
- package/template/packages/plugins/@lego/plugin-todo/src/hooks/useDeleteTodo.ts +46 -0
- package/template/packages/plugins/@lego/plugin-todo/src/hooks/usePocketBase.ts +38 -0
- package/template/packages/plugins/@lego/plugin-todo/src/hooks/useTodos.ts +64 -0
- package/template/packages/plugins/@lego/plugin-todo/src/hooks/useUpdateTodo.ts +35 -0
- package/template/packages/plugins/@lego/plugin-todo/src/index.tsx +5 -0
- package/template/packages/plugins/@lego/plugin-todo/src/lib/utils.ts +20 -0
- package/template/packages/plugins/@lego/plugin-todo/src/pages/TodoPage.tsx +89 -0
- package/template/packages/plugins/@lego/plugin-todo/src/plugin.config.ts +104 -0
- package/template/packages/plugins/@lego/plugin-todo/src/plugin.ts +13 -0
- package/template/packages/plugins/@lego/plugin-todo/src/schemas.ts +37 -0
- package/template/packages/plugins/@lego/plugin-todo/src/types.ts +42 -0
- package/template/packages/plugins/@lego/plugin-todo/src/vite-env.d.ts +31 -0
- package/template/packages/plugins/@lego/plugin-todo/tailwind.config.ts +51 -0
- package/template/packages/plugins/@lego/plugin-todo/tsconfig.json +18 -0
- package/template/pnpm-workspace.yaml +4 -0
- package/template/pocketbase/CHANGELOG.md +911 -0
- package/template/pocketbase/LICENSE.md +17 -0
- package/template/scripts/create-plugin.js +221 -0
- package/template/scripts/deploy.sh +56 -0
- package/template/tsconfig.base.json +26 -0
- package/template/tsconfig.json +8 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { useQuery } from '@tanstack/react-query';
|
|
2
|
+
import { usePocketBase } from './usePocketBase';
|
|
3
|
+
|
|
4
|
+
export interface Activity {
|
|
5
|
+
id: string;
|
|
6
|
+
action: string;
|
|
7
|
+
resource: string;
|
|
8
|
+
userId: string;
|
|
9
|
+
userName?: string;
|
|
10
|
+
createdAt: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Fetch recent activity feed
|
|
15
|
+
*/
|
|
16
|
+
export function useRecentActivity(limit = 10) {
|
|
17
|
+
const pb = usePocketBase();
|
|
18
|
+
|
|
19
|
+
return useQuery({
|
|
20
|
+
queryKey: ['dashboard', 'activity', limit],
|
|
21
|
+
queryFn: async (): Promise<Activity[]> => {
|
|
22
|
+
if (!pb) {
|
|
23
|
+
throw new Error('PocketBase not initialized');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Get current organization from host state
|
|
27
|
+
const kernelState = (window as any).__LEGO_KERNEL_STATE__;
|
|
28
|
+
const state = kernelState?.useGlobalKernelState?.getState();
|
|
29
|
+
|
|
30
|
+
if (!state?.organization?.id) {
|
|
31
|
+
return [];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const orgId = state.organization.id;
|
|
35
|
+
|
|
36
|
+
// Fetch recent audit logs
|
|
37
|
+
const result = await pb.collection('audit_logs').getList(1, limit, {
|
|
38
|
+
filter: `organizationId = "${orgId}"`,
|
|
39
|
+
sort: '-created',
|
|
40
|
+
expand: 'userId',
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
return result.items.map((item: any) => ({
|
|
44
|
+
id: item.id,
|
|
45
|
+
action: item.action,
|
|
46
|
+
resource: item.resource,
|
|
47
|
+
userId: item.userId,
|
|
48
|
+
userName: item.expand?.userId?.name || item.expand?.userId?.email || 'Unknown',
|
|
49
|
+
createdAt: item.created,
|
|
50
|
+
}));
|
|
51
|
+
},
|
|
52
|
+
enabled: !!pb,
|
|
53
|
+
refetchInterval: 30000, // Refresh every 30 seconds
|
|
54
|
+
});
|
|
55
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
|
+
import { useDashboardStats } from '../hooks/useDashboardStats';
|
|
3
|
+
import { useRecentActivity } from '../hooks/useRecentActivity';
|
|
4
|
+
import { StatCard } from '../components/StatCard';
|
|
5
|
+
import { ActivityFeed } from '../components/ActivityFeed';
|
|
6
|
+
import { QuickActions } from '../components/QuickActions';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Dashboard Page
|
|
10
|
+
*
|
|
11
|
+
* Main dashboard page showing:
|
|
12
|
+
* - Organization statistics
|
|
13
|
+
* - Recent activity feed
|
|
14
|
+
* - Quick action buttons
|
|
15
|
+
*/
|
|
16
|
+
export default function DashboardPage() {
|
|
17
|
+
const { data: stats, isLoading: statsLoading } = useDashboardStats();
|
|
18
|
+
const { data: activities, isLoading: activityLoading } = useRecentActivity();
|
|
19
|
+
|
|
20
|
+
// Publish toast helper
|
|
21
|
+
const publishToast = (data: { type: string; title: string; description?: string }) => {
|
|
22
|
+
const channelBus = (window as any).__LEGO_CHANNEL_BUS__;
|
|
23
|
+
if (channelBus) {
|
|
24
|
+
channelBus.publish('lego:toast', { channel: 'lego:toast', data });
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// Notify that plugin is ready
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
const channelBus = (window as any).__LEGO_CHANNEL_BUS__;
|
|
31
|
+
if (channelBus) {
|
|
32
|
+
channelBus.publish('lego:plugin:ready', {
|
|
33
|
+
channel: 'lego:plugin:ready',
|
|
34
|
+
data: { name: '@lego/plugin-dashboard', version: '1.0.0' }
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}, []);
|
|
38
|
+
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
if (stats) {
|
|
41
|
+
console.log('[Dashboard] Stats loaded:', stats);
|
|
42
|
+
}
|
|
43
|
+
}, [stats]);
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<div className="space-y-6">
|
|
47
|
+
{/* Header */}
|
|
48
|
+
<div>
|
|
49
|
+
<h1 className="text-3xl font-bold tracking-tight">Dashboard</h1>
|
|
50
|
+
<p className="text-muted-foreground">
|
|
51
|
+
Welcome to your organization dashboard
|
|
52
|
+
</p>
|
|
53
|
+
</div>
|
|
54
|
+
|
|
55
|
+
{/* Statistics Grid */}
|
|
56
|
+
{statsLoading ? (
|
|
57
|
+
<div className="grid gap-4 md:grid-cols-3">
|
|
58
|
+
{[1, 2, 3].map((i) => (
|
|
59
|
+
<div key={i} className="h-32 animate-pulse rounded-lg bg-muted" />
|
|
60
|
+
))}
|
|
61
|
+
</div>
|
|
62
|
+
) : stats ? (
|
|
63
|
+
<div className="grid gap-4 md:grid-cols-3">
|
|
64
|
+
<StatCard
|
|
65
|
+
title="Total Users"
|
|
66
|
+
value={stats.totalUsers}
|
|
67
|
+
icon="Users"
|
|
68
|
+
description="Active users in organization"
|
|
69
|
+
trend={{ value: 12, direction: 'up' }}
|
|
70
|
+
/>
|
|
71
|
+
<StatCard
|
|
72
|
+
title="Active Todos"
|
|
73
|
+
value={stats.activeTodos}
|
|
74
|
+
icon="CheckSquare"
|
|
75
|
+
description="Tasks in progress"
|
|
76
|
+
trend={{ value: 8, direction: 'up' }}
|
|
77
|
+
/>
|
|
78
|
+
<StatCard
|
|
79
|
+
title="Completed Todos"
|
|
80
|
+
value={stats.completedTodos}
|
|
81
|
+
icon="CheckCircle"
|
|
82
|
+
description="Tasks completed"
|
|
83
|
+
trend={{ value: 5, direction: 'down' }}
|
|
84
|
+
/>
|
|
85
|
+
</div>
|
|
86
|
+
) : null}
|
|
87
|
+
|
|
88
|
+
<div className="grid gap-6 md:grid-cols-2">
|
|
89
|
+
{/* Quick Actions */}
|
|
90
|
+
<QuickActions onToast={publishToast} />
|
|
91
|
+
|
|
92
|
+
{/* Recent Activity */}
|
|
93
|
+
{activityLoading ? (
|
|
94
|
+
<div className="space-y-4">
|
|
95
|
+
{[1, 2, 3, 4, 5].map((i) => (
|
|
96
|
+
<div key={i} className="h-16 animate-pulse rounded-lg bg-muted" />
|
|
97
|
+
))}
|
|
98
|
+
</div>
|
|
99
|
+
) : (
|
|
100
|
+
<ActivityFeed activities={activities || []} />
|
|
101
|
+
)}
|
|
102
|
+
</div>
|
|
103
|
+
</div>
|
|
104
|
+
);
|
|
105
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import type { ReactNode, ComponentType } from 'react';
|
|
2
|
+
import { SidebarWidget } from './components/SidebarWidget';
|
|
3
|
+
import { QuickActionSlot } from './components/QuickActionSlot';
|
|
4
|
+
|
|
5
|
+
// Local type definitions for plugin config
|
|
6
|
+
interface PluginManifest {
|
|
7
|
+
name: string;
|
|
8
|
+
version: string;
|
|
9
|
+
displayName: string;
|
|
10
|
+
description: string;
|
|
11
|
+
author: string;
|
|
12
|
+
permissions: string[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface SlotInjection {
|
|
16
|
+
slot: string;
|
|
17
|
+
component: ReactNode | ComponentType<any>;
|
|
18
|
+
order?: number;
|
|
19
|
+
props?: Record<string, unknown>;
|
|
20
|
+
condition?: () => boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface RouteConfig {
|
|
24
|
+
path: string;
|
|
25
|
+
component: () => Promise<{ default: ComponentType<any> }>;
|
|
26
|
+
protected?: boolean;
|
|
27
|
+
permissions?: string[];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface PluginSetting {
|
|
31
|
+
key: string;
|
|
32
|
+
type: 'boolean' | 'string' | 'number' | 'select';
|
|
33
|
+
label: string;
|
|
34
|
+
description?: string;
|
|
35
|
+
defaultValue?: unknown;
|
|
36
|
+
options?: { label: string; value: string }[];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface PluginConfig {
|
|
40
|
+
manifest: PluginManifest;
|
|
41
|
+
enabled: boolean;
|
|
42
|
+
slots: SlotInjection[];
|
|
43
|
+
routes?: RouteConfig[];
|
|
44
|
+
settings?: PluginSetting[];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Dashboard plugin configuration
|
|
49
|
+
*
|
|
50
|
+
* This config defines:
|
|
51
|
+
* - Plugin metadata (manifest)
|
|
52
|
+
* - Slot injections (content injected into host layout)
|
|
53
|
+
* - Routes (plugin pages)
|
|
54
|
+
* - Settings (configurable by admin)
|
|
55
|
+
*/
|
|
56
|
+
export const pluginConfig: PluginConfig = {
|
|
57
|
+
manifest: {
|
|
58
|
+
name: '@lego/plugin-dashboard',
|
|
59
|
+
version: '1.0.0',
|
|
60
|
+
displayName: 'Dashboard',
|
|
61
|
+
description: 'Organization dashboard with stats, activity, and quick actions',
|
|
62
|
+
author: 'Lego-One',
|
|
63
|
+
permissions: ['organizations.read', 'audit_logs.read'],
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
enabled: true,
|
|
67
|
+
|
|
68
|
+
// Slot injections - add content to host layout
|
|
69
|
+
slots: [
|
|
70
|
+
{
|
|
71
|
+
slot: 'sidebar:nav',
|
|
72
|
+
component: SidebarWidget,
|
|
73
|
+
order: 50, // After Home, before Todos
|
|
74
|
+
props: {
|
|
75
|
+
to: '/dashboard',
|
|
76
|
+
icon: 'LayoutDashboard',
|
|
77
|
+
label: 'Dashboard',
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
slot: 'dashboard:widgets',
|
|
82
|
+
component: QuickActionSlot,
|
|
83
|
+
order: 100,
|
|
84
|
+
},
|
|
85
|
+
],
|
|
86
|
+
|
|
87
|
+
// Plugin routes
|
|
88
|
+
routes: [
|
|
89
|
+
{
|
|
90
|
+
path: '/dashboard',
|
|
91
|
+
component: () => import('./pages/DashboardPage').then(m => ({ default: m.default })),
|
|
92
|
+
protected: true,
|
|
93
|
+
permissions: ['organizations.read'],
|
|
94
|
+
},
|
|
95
|
+
],
|
|
96
|
+
|
|
97
|
+
// Plugin settings (shown in admin UI)
|
|
98
|
+
settings: [
|
|
99
|
+
{
|
|
100
|
+
key: 'showStats',
|
|
101
|
+
type: 'boolean',
|
|
102
|
+
label: 'Show Statistics',
|
|
103
|
+
description: 'Display organization statistics on dashboard',
|
|
104
|
+
defaultValue: true,
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
key: 'showActivity',
|
|
108
|
+
type: 'boolean',
|
|
109
|
+
label: 'Show Recent Activity',
|
|
110
|
+
description: 'Display recent activity feed',
|
|
111
|
+
defaultValue: true,
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
key: 'activityLimit',
|
|
115
|
+
type: 'number',
|
|
116
|
+
label: 'Activity Feed Limit',
|
|
117
|
+
description: 'Number of activity items to show',
|
|
118
|
+
defaultValue: 10,
|
|
119
|
+
},
|
|
120
|
+
],
|
|
121
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin entry point
|
|
3
|
+
*
|
|
4
|
+
* This file is the main entry point for the Dashboard plugin.
|
|
5
|
+
* It exports the plugin config and the root component.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { pluginConfig } from './plugin.config';
|
|
9
|
+
import DashboardApp from './App';
|
|
10
|
+
|
|
11
|
+
export default {
|
|
12
|
+
config: pluginConfig,
|
|
13
|
+
App: DashboardApp,
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
// Also export for convenience
|
|
17
|
+
export { pluginConfig };
|
|
18
|
+
export { default as App } from './App';
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/// <reference types="vite/client" />
|
|
2
|
+
|
|
3
|
+
declare global {
|
|
4
|
+
interface ImportMetaEnv {
|
|
5
|
+
readonly VITE_POCKETBASE_URL: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
interface ImportMeta {
|
|
9
|
+
readonly env: ImportMetaEnv;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface Window {
|
|
13
|
+
__LEGO_KERNEL_STATE__?: {
|
|
14
|
+
useGlobalKernelState: {
|
|
15
|
+
getState: () => {
|
|
16
|
+
token?: string;
|
|
17
|
+
organization?: { id: string };
|
|
18
|
+
user?: any;
|
|
19
|
+
};
|
|
20
|
+
subscribe: (callback: (state: any) => void) => () => void;
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
__LEGO_CHANNEL_BUS__?: {
|
|
24
|
+
publish: (channel: string, data: any) => void;
|
|
25
|
+
subscribe: (channel: string, callback: (data: any) => void) => () => void;
|
|
26
|
+
unsubscribe: (channel: string, callback: (data: any) => void) => void;
|
|
27
|
+
unsubscribeAll: (channel: string) => void;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export {};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { Config } from 'tailwindcss';
|
|
2
|
+
|
|
3
|
+
const config: Config = {
|
|
4
|
+
darkMode: ['class'],
|
|
5
|
+
content: [
|
|
6
|
+
'./src/**/*.{js,ts,jsx,tsx,mdx}',
|
|
7
|
+
],
|
|
8
|
+
theme: {
|
|
9
|
+
extend: {
|
|
10
|
+
colors: {
|
|
11
|
+
border: 'hsl(var(--border))',
|
|
12
|
+
background: 'hsl(var(--background))',
|
|
13
|
+
foreground: 'hsl(var(--foreground))',
|
|
14
|
+
primary: {
|
|
15
|
+
DEFAULT: 'hsl(var(--primary))',
|
|
16
|
+
foreground: 'hsl(var(--primary-foreground))',
|
|
17
|
+
},
|
|
18
|
+
muted: {
|
|
19
|
+
DEFAULT: 'hsl(var(--muted))',
|
|
20
|
+
foreground: 'hsl(var(--muted-foreground))',
|
|
21
|
+
},
|
|
22
|
+
card: {
|
|
23
|
+
DEFAULT: 'hsl(var(--card))',
|
|
24
|
+
foreground: 'hsl(var(--card-foreground))',
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
borderRadius: {
|
|
28
|
+
lg: 'var(--radius)',
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
plugins: [],
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export default config;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"jsx": "react-jsx",
|
|
4
|
+
"strict": true,
|
|
5
|
+
"esModuleInterop": true,
|
|
6
|
+
"skipLibCheck": true,
|
|
7
|
+
"module": "esnext",
|
|
8
|
+
"moduleResolution": "bundler",
|
|
9
|
+
"resolveJsonModule": true,
|
|
10
|
+
"isolatedModules": true,
|
|
11
|
+
"types": ["vite/client"],
|
|
12
|
+
"paths": {
|
|
13
|
+
"@/*": ["./src/*"]
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"include": ["src"],
|
|
17
|
+
"exclude": ["node_modules", "dist"]
|
|
18
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { appTools, defineConfig } from '@modern-js/app-tools';
|
|
2
|
+
import { garfishPlugin } from '@modern-js/plugin-garfish';
|
|
3
|
+
|
|
4
|
+
export default defineConfig({
|
|
5
|
+
dev: {
|
|
6
|
+
port: 3002,
|
|
7
|
+
},
|
|
8
|
+
|
|
9
|
+
runtime: {
|
|
10
|
+
router: true,
|
|
11
|
+
},
|
|
12
|
+
|
|
13
|
+
deploy: {
|
|
14
|
+
microFrontend: true,
|
|
15
|
+
},
|
|
16
|
+
|
|
17
|
+
plugins: [appTools(), garfishPlugin()],
|
|
18
|
+
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lego/plugin-todo",
|
|
3
|
+
"version": "2.0.13",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "modern dev",
|
|
8
|
+
"build": "modern build",
|
|
9
|
+
"start": "modern start",
|
|
10
|
+
"lint": "eslint src --ext .ts,.tsx",
|
|
11
|
+
"typecheck": "tsc --noEmit"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@hookform/resolvers": "^3.9.0",
|
|
15
|
+
"@modern-js/runtime": "^2.70.0",
|
|
16
|
+
"@radix-ui/react-dialog": "^1.1.2",
|
|
17
|
+
"@radix-ui/react-label": "^2.1.0",
|
|
18
|
+
"@radix-ui/react-slot": "^1.1.0",
|
|
19
|
+
"@tanstack/react-query": "^5.62.0",
|
|
20
|
+
"class-variance-authority": "^0.7.0",
|
|
21
|
+
"clsx": "^2.1.1",
|
|
22
|
+
"garfish": "^1.19.0",
|
|
23
|
+
"lucide-react": "^0.468.0",
|
|
24
|
+
"pocketbase": "^0.22.0",
|
|
25
|
+
"react": "^18.3.1",
|
|
26
|
+
"react-dom": "^18.3.1",
|
|
27
|
+
"react-hook-form": "^7.53.0",
|
|
28
|
+
"tailwind-merge": "^2.5.4",
|
|
29
|
+
"zod": "^3.23.8"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@modern-js/app-tools": "^2.70.0",
|
|
33
|
+
"@modern-js/plugin-garfish": "^2.70.0",
|
|
34
|
+
"@types/react": "^18.3.12",
|
|
35
|
+
"@types/react-dom": "^18.3.1",
|
|
36
|
+
"autoprefixer": "^10.4.20",
|
|
37
|
+
"postcss": "^8.4.49",
|
|
38
|
+
"tailwindcss": "^3.4.15",
|
|
39
|
+
"typescript": "^5.6.3"
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { CheckSquare } from 'lucide-react';
|
|
2
|
+
import { useNavigate } from '@modern-js/runtime/router';
|
|
3
|
+
|
|
4
|
+
export function SidebarWidget() {
|
|
5
|
+
const navigate = useNavigate();
|
|
6
|
+
|
|
7
|
+
return (
|
|
8
|
+
<button
|
|
9
|
+
onClick={() => navigate('/todo')}
|
|
10
|
+
className="flex w-full items-center gap-3 rounded-md px-3 py-2 text-sm font-medium text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground"
|
|
11
|
+
>
|
|
12
|
+
<CheckSquare className="h-4 w-4" />
|
|
13
|
+
<span>Todo</span>
|
|
14
|
+
</button>
|
|
15
|
+
);
|
|
16
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import * as Dialog from '@radix-ui/react-dialog';
|
|
2
|
+
import { X } from 'lucide-react';
|
|
3
|
+
import { TodoForm } from './TodoForm';
|
|
4
|
+
import { TodoFormInput } from '../schemas';
|
|
5
|
+
import { cn } from '../lib/utils';
|
|
6
|
+
|
|
7
|
+
interface TodoDialogProps {
|
|
8
|
+
open: boolean;
|
|
9
|
+
onOpenChange: (open: boolean) => void;
|
|
10
|
+
onSubmit: (data: TodoFormInput) => void;
|
|
11
|
+
isLoading?: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function TodoDialog({
|
|
15
|
+
open,
|
|
16
|
+
onOpenChange,
|
|
17
|
+
onSubmit,
|
|
18
|
+
isLoading,
|
|
19
|
+
}: TodoDialogProps) {
|
|
20
|
+
return (
|
|
21
|
+
<Dialog.Root open={open} onOpenChange={onOpenChange}>
|
|
22
|
+
<Dialog.Portal>
|
|
23
|
+
<Dialog.Overlay className="fixed inset-0 bg-black/50 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0" />
|
|
24
|
+
<Dialog.Content
|
|
25
|
+
className={cn(
|
|
26
|
+
'fixed left-1/2 top-1/2 z-50 w-full max-w-md -translate-x-1/2 -translate-y-1/2',
|
|
27
|
+
'rounded-lg border bg-background p-6 shadow-lg',
|
|
28
|
+
'data-[state=open]:animate-in data-[state=closed]:animate-out',
|
|
29
|
+
'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
|
|
30
|
+
'data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95',
|
|
31
|
+
'data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%]',
|
|
32
|
+
'data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%]'
|
|
33
|
+
)}
|
|
34
|
+
>
|
|
35
|
+
<div className="flex items-center justify-between">
|
|
36
|
+
<Dialog.Title className="text-lg font-semibold">
|
|
37
|
+
Create New Todo
|
|
38
|
+
</Dialog.Title>
|
|
39
|
+
<Dialog.Close
|
|
40
|
+
disabled={isLoading}
|
|
41
|
+
className="rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none"
|
|
42
|
+
>
|
|
43
|
+
<X className="h-4 w-4" />
|
|
44
|
+
<span className="sr-only">Close</span>
|
|
45
|
+
</Dialog.Close>
|
|
46
|
+
</div>
|
|
47
|
+
|
|
48
|
+
<div className="mt-4">
|
|
49
|
+
<TodoForm onSubmit={onSubmit} isLoading={isLoading} />
|
|
50
|
+
</div>
|
|
51
|
+
</Dialog.Content>
|
|
52
|
+
</Dialog.Portal>
|
|
53
|
+
</Dialog.Root>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { Search, Filter } from 'lucide-react';
|
|
2
|
+
import { TodoFilters as TodoFiltersType } from '../types';
|
|
3
|
+
import { cn } from '../lib/utils';
|
|
4
|
+
|
|
5
|
+
interface TodoFiltersProps {
|
|
6
|
+
filters: TodoFiltersType;
|
|
7
|
+
onFiltersChange: (filters: TodoFiltersType) => void;
|
|
8
|
+
todoCount: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function TodoFilters({
|
|
12
|
+
filters,
|
|
13
|
+
onFiltersChange,
|
|
14
|
+
todoCount,
|
|
15
|
+
}: TodoFiltersProps) {
|
|
16
|
+
return (
|
|
17
|
+
<div className="space-y-4">
|
|
18
|
+
{/* Search */}
|
|
19
|
+
<div className="relative">
|
|
20
|
+
<Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
|
|
21
|
+
<input
|
|
22
|
+
type="text"
|
|
23
|
+
placeholder="Search todos..."
|
|
24
|
+
value={filters.search || ''}
|
|
25
|
+
onChange={(e) =>
|
|
26
|
+
onFiltersChange({ ...filters, search: e.target.value })
|
|
27
|
+
}
|
|
28
|
+
className="w-full rounded-md border border-input bg-background pl-10 pr-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
|
|
29
|
+
/>
|
|
30
|
+
</div>
|
|
31
|
+
|
|
32
|
+
{/* Filter Controls */}
|
|
33
|
+
<div className="flex flex-wrap gap-2">
|
|
34
|
+
{/* Status Filter */}
|
|
35
|
+
<div className="flex items-center gap-2 rounded-md border bg-background p-1">
|
|
36
|
+
<Filter className="h-4 w-4 text-muted-foreground" />
|
|
37
|
+
<select
|
|
38
|
+
value={filters.status || 'all'}
|
|
39
|
+
onChange={(e) =>
|
|
40
|
+
onFiltersChange({
|
|
41
|
+
...filters,
|
|
42
|
+
status: e.target.value as TodoFiltersType['status'],
|
|
43
|
+
})
|
|
44
|
+
}
|
|
45
|
+
className="bg-transparent text-sm focus:outline-none"
|
|
46
|
+
>
|
|
47
|
+
<option value="all">All Status</option>
|
|
48
|
+
<option value="active">Active</option>
|
|
49
|
+
<option value="completed">Completed</option>
|
|
50
|
+
</select>
|
|
51
|
+
</div>
|
|
52
|
+
|
|
53
|
+
{/* Priority Filter */}
|
|
54
|
+
<div className="flex items-center gap-2 rounded-md border bg-background p-1">
|
|
55
|
+
<select
|
|
56
|
+
value={filters.priority || 'all'}
|
|
57
|
+
onChange={(e) =>
|
|
58
|
+
onFiltersChange({
|
|
59
|
+
...filters,
|
|
60
|
+
priority: e.target.value as TodoFiltersType['priority'],
|
|
61
|
+
})
|
|
62
|
+
}
|
|
63
|
+
className="bg-transparent text-sm focus:outline-none"
|
|
64
|
+
>
|
|
65
|
+
<option value="all">All Priority</option>
|
|
66
|
+
<option value="low">Low</option>
|
|
67
|
+
<option value="medium">Medium</option>
|
|
68
|
+
<option value="high">High</option>
|
|
69
|
+
</select>
|
|
70
|
+
</div>
|
|
71
|
+
|
|
72
|
+
{/* Results Count */}
|
|
73
|
+
<div className="ml-auto flex items-center text-sm text-muted-foreground">
|
|
74
|
+
{todoCount} {todoCount === 1 ? 'todo' : 'todos'}
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
);
|
|
79
|
+
}
|