create-lego-one 2.0.10 → 2.0.12
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 +145 -0
- package/dist/index.cjs.map +1 -1
- package/package.json +5 -3
- 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/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/tsconfig.json +8 -0
|
@@ -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.12",
|
|
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
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { useForm } from 'react-hook-form';
|
|
2
|
+
import { zodResolver } from '@hookform/resolvers/zod';
|
|
3
|
+
import { todoFormSchema, type TodoFormInput } from '../schemas';
|
|
4
|
+
import { cn } from '../lib/utils';
|
|
5
|
+
|
|
6
|
+
interface TodoFormProps {
|
|
7
|
+
onSubmit: (data: TodoFormInput) => void;
|
|
8
|
+
isLoading?: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function TodoForm({ onSubmit, isLoading }: TodoFormProps) {
|
|
12
|
+
const {
|
|
13
|
+
register,
|
|
14
|
+
handleSubmit,
|
|
15
|
+
setValue,
|
|
16
|
+
formState: { errors },
|
|
17
|
+
} = useForm<TodoFormInput>({
|
|
18
|
+
resolver: zodResolver(todoFormSchema),
|
|
19
|
+
defaultValues: {
|
|
20
|
+
priority: 'medium',
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
|
|
26
|
+
<div className="space-y-2">
|
|
27
|
+
<label htmlFor="title" className="text-sm font-medium">
|
|
28
|
+
Title *
|
|
29
|
+
</label>
|
|
30
|
+
<input
|
|
31
|
+
id="title"
|
|
32
|
+
placeholder="What needs to be done?"
|
|
33
|
+
{...register('title')}
|
|
34
|
+
className="w-full rounded-md border px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary"
|
|
35
|
+
/>
|
|
36
|
+
{errors.title && (
|
|
37
|
+
<p className="text-sm text-destructive">{errors.title.message}</p>
|
|
38
|
+
)}
|
|
39
|
+
</div>
|
|
40
|
+
|
|
41
|
+
<div className="space-y-2">
|
|
42
|
+
<label htmlFor="description" className="text-sm font-medium">
|
|
43
|
+
Description
|
|
44
|
+
</label>
|
|
45
|
+
<input
|
|
46
|
+
id="description"
|
|
47
|
+
placeholder="Add details (optional)"
|
|
48
|
+
{...register('description')}
|
|
49
|
+
className="w-full rounded-md border px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary"
|
|
50
|
+
/>
|
|
51
|
+
{errors.description && (
|
|
52
|
+
<p className="text-sm text-destructive">{errors.description.message}</p>
|
|
53
|
+
)}
|
|
54
|
+
</div>
|
|
55
|
+
|
|
56
|
+
<div className="space-y-2">
|
|
57
|
+
<label htmlFor="priority" className="text-sm font-medium">
|
|
58
|
+
Priority *
|
|
59
|
+
</label>
|
|
60
|
+
<select
|
|
61
|
+
{...register('priority')}
|
|
62
|
+
className="w-full rounded-md border px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary"
|
|
63
|
+
>
|
|
64
|
+
<option value="low">Low</option>
|
|
65
|
+
<option value="medium">Medium</option>
|
|
66
|
+
<option value="high">High</option>
|
|
67
|
+
</select>
|
|
68
|
+
{errors.priority && (
|
|
69
|
+
<p className="text-sm text-destructive">{errors.priority.message}</p>
|
|
70
|
+
)}
|
|
71
|
+
</div>
|
|
72
|
+
|
|
73
|
+
<div className="space-y-2">
|
|
74
|
+
<label htmlFor="dueDate" className="text-sm font-medium">
|
|
75
|
+
Due Date
|
|
76
|
+
</label>
|
|
77
|
+
<input
|
|
78
|
+
id="dueDate"
|
|
79
|
+
type="date"
|
|
80
|
+
{...register('dueDate')}
|
|
81
|
+
className="w-full rounded-md border px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary"
|
|
82
|
+
/>
|
|
83
|
+
</div>
|
|
84
|
+
|
|
85
|
+
<button
|
|
86
|
+
type="submit"
|
|
87
|
+
disabled={isLoading}
|
|
88
|
+
className="w-full rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 disabled:opacity-50"
|
|
89
|
+
>
|
|
90
|
+
{isLoading ? 'Creating...' : 'Create Todo'}
|
|
91
|
+
</button>
|
|
92
|
+
</form>
|
|
93
|
+
);
|
|
94
|
+
}
|