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,183 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
const TOAST_LIMIT = 1;
|
|
4
|
+
const TOAST_REMOVE_DELAY = 1000000;
|
|
5
|
+
|
|
6
|
+
type ToasterToast = {
|
|
7
|
+
id: string;
|
|
8
|
+
title?: React.ReactNode;
|
|
9
|
+
description?: React.ReactNode;
|
|
10
|
+
action?: React.ReactNode;
|
|
11
|
+
variant?: 'default' | 'destructive';
|
|
12
|
+
open?: boolean;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const actionTypes = {
|
|
16
|
+
ADD_TOAST: 'ADD_TOAST',
|
|
17
|
+
UPDATE_TOAST: 'UPDATE_TOAST',
|
|
18
|
+
DISMISS_TOAST: 'DISMISS_TOAST',
|
|
19
|
+
REMOVE_TOAST: 'REMOVE_TOAST',
|
|
20
|
+
} as const;
|
|
21
|
+
|
|
22
|
+
let count = 0;
|
|
23
|
+
|
|
24
|
+
function genId() {
|
|
25
|
+
count = (count + 1) % Number.MAX_SAFE_INTEGER;
|
|
26
|
+
return count.toString();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
type ActionType = typeof actionTypes;
|
|
30
|
+
|
|
31
|
+
type Action =
|
|
32
|
+
| {
|
|
33
|
+
type: ActionType['ADD_TOAST'];
|
|
34
|
+
toast: ToasterToast;
|
|
35
|
+
}
|
|
36
|
+
| {
|
|
37
|
+
type: ActionType['UPDATE_TOAST'];
|
|
38
|
+
toast: Partial<ToasterToast>;
|
|
39
|
+
}
|
|
40
|
+
| {
|
|
41
|
+
type: ActionType['DISMISS_TOAST'];
|
|
42
|
+
toastId?: ToasterToast['id'];
|
|
43
|
+
}
|
|
44
|
+
| {
|
|
45
|
+
type: ActionType['REMOVE_TOAST'];
|
|
46
|
+
toastId?: ToasterToast['id'];
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
interface State {
|
|
50
|
+
toasts: ToasterToast[];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>();
|
|
54
|
+
|
|
55
|
+
const addToRemoveQueue = (toastId: string) => {
|
|
56
|
+
if (toastTimeouts.has(toastId)) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const timeout = setTimeout(() => {
|
|
61
|
+
toastTimeouts.delete(toastId);
|
|
62
|
+
dispatch({
|
|
63
|
+
type: 'REMOVE_TOAST',
|
|
64
|
+
toastId: toastId,
|
|
65
|
+
});
|
|
66
|
+
}, TOAST_REMOVE_DELAY);
|
|
67
|
+
|
|
68
|
+
toastTimeouts.set(toastId, timeout);
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export const reducer = (state: State, action: Action): State => {
|
|
72
|
+
switch (action.type) {
|
|
73
|
+
case 'ADD_TOAST':
|
|
74
|
+
return {
|
|
75
|
+
...state,
|
|
76
|
+
toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
case 'UPDATE_TOAST':
|
|
80
|
+
return {
|
|
81
|
+
...state,
|
|
82
|
+
toasts: state.toasts.map((t) =>
|
|
83
|
+
t.id === action.toast.id ? { ...t, ...action.toast } : t
|
|
84
|
+
),
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
case 'DISMISS_TOAST': {
|
|
88
|
+
const { toastId } = action;
|
|
89
|
+
|
|
90
|
+
if (toastId) {
|
|
91
|
+
addToRemoveQueue(toastId);
|
|
92
|
+
} else {
|
|
93
|
+
state.toasts.forEach((toast) => {
|
|
94
|
+
addToRemoveQueue(toast.id);
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
...state,
|
|
100
|
+
toasts: state.toasts.map((t) =>
|
|
101
|
+
t.id === toastId || toastId === undefined
|
|
102
|
+
? {
|
|
103
|
+
...t,
|
|
104
|
+
open: false,
|
|
105
|
+
}
|
|
106
|
+
: t
|
|
107
|
+
),
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
case 'REMOVE_TOAST':
|
|
111
|
+
if (action.toastId === undefined) {
|
|
112
|
+
return {
|
|
113
|
+
...state,
|
|
114
|
+
toasts: [],
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
return {
|
|
118
|
+
...state,
|
|
119
|
+
toasts: state.toasts.filter((t) => t.id !== action.toastId),
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const listeners: Array<(state: State) => void> = [];
|
|
125
|
+
|
|
126
|
+
let memoryState: State = { toasts: [] };
|
|
127
|
+
|
|
128
|
+
function dispatch(action: Action) {
|
|
129
|
+
memoryState = reducer(memoryState, action);
|
|
130
|
+
listeners.forEach((listener) => {
|
|
131
|
+
listener(memoryState);
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
type Toast = Omit<ToasterToast, 'id'>;
|
|
136
|
+
|
|
137
|
+
function toast({ ...props }: Toast) {
|
|
138
|
+
const id = genId();
|
|
139
|
+
|
|
140
|
+
const update = (props: ToasterToast) =>
|
|
141
|
+
dispatch({
|
|
142
|
+
type: 'UPDATE_TOAST',
|
|
143
|
+
toast: { ...props, id },
|
|
144
|
+
});
|
|
145
|
+
const dismiss = () => dispatch({ type: 'DISMISS_TOAST', toastId: id });
|
|
146
|
+
|
|
147
|
+
dispatch({
|
|
148
|
+
type: 'ADD_TOAST',
|
|
149
|
+
toast: {
|
|
150
|
+
...props,
|
|
151
|
+
id,
|
|
152
|
+
open: true,
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
id: id,
|
|
158
|
+
dismiss,
|
|
159
|
+
update,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function useToast() {
|
|
164
|
+
const [state, setState] = React.useState<State>(memoryState);
|
|
165
|
+
|
|
166
|
+
React.useEffect(() => {
|
|
167
|
+
listeners.push(setState);
|
|
168
|
+
return () => {
|
|
169
|
+
const index = listeners.indexOf(setState);
|
|
170
|
+
if (index > -1) {
|
|
171
|
+
listeners.splice(index, 1);
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
}, [state]);
|
|
175
|
+
|
|
176
|
+
return {
|
|
177
|
+
...state,
|
|
178
|
+
toast,
|
|
179
|
+
dismiss: (toastId?: string) => dispatch({ type: 'DISMISS_TOAST', toastId }),
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export { useToast, toast };
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// Re-export all kernel modules
|
|
2
|
+
export * from './shared-state';
|
|
3
|
+
export * from './providers';
|
|
4
|
+
export * from './lib/utils';
|
|
5
|
+
|
|
6
|
+
// UI Components
|
|
7
|
+
export * from './components/index';
|
|
8
|
+
|
|
9
|
+
// Channels
|
|
10
|
+
export * from './channels';
|
|
11
|
+
|
|
12
|
+
// Plugins
|
|
13
|
+
export * from './plugins';
|
|
14
|
+
|
|
15
|
+
// Auth
|
|
16
|
+
export type { LoginCredentials, RegisterData, AuthResponse, AuthError } from './auth/types';
|
|
17
|
+
export { AuthService } from './auth/service';
|
|
18
|
+
export { useAuth, useCurrentUser, useRequireAuth } from './auth/hooks';
|
|
19
|
+
export { ProtectedRoute } from './auth/ProtectedRoute';
|
|
20
|
+
|
|
21
|
+
// RBAC
|
|
22
|
+
export type {
|
|
23
|
+
Organization,
|
|
24
|
+
CreateOrganizationData,
|
|
25
|
+
UpdateOrganizationData,
|
|
26
|
+
Role,
|
|
27
|
+
CreateRoleData,
|
|
28
|
+
UpdateRoleData,
|
|
29
|
+
Permission,
|
|
30
|
+
UserRole,
|
|
31
|
+
AssignRoleData,
|
|
32
|
+
UserWithRoles,
|
|
33
|
+
CreateUserRequest,
|
|
34
|
+
UpdateUserRequest,
|
|
35
|
+
PermissionCheck,
|
|
36
|
+
ResourceType,
|
|
37
|
+
ActionType,
|
|
38
|
+
} from './rbac/types';
|
|
39
|
+
export { RBACService } from './rbac/service';
|
|
40
|
+
export {
|
|
41
|
+
useCurrentOrganization,
|
|
42
|
+
useOrganizations,
|
|
43
|
+
useOrganizationRoles,
|
|
44
|
+
useHasPermission,
|
|
45
|
+
useRequirePermission,
|
|
46
|
+
} from './rbac/hooks';
|
|
47
|
+
export { PermissionGate } from './rbac/components/PermissionGate';
|
|
48
|
+
export { OrganizationSelector } from './rbac/components/OrganizationSelector';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { cn } from './utils';
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { type ClassValue, clsx } from 'clsx';
|
|
2
|
+
import { twMerge } from 'tailwind-merge';
|
|
3
|
+
|
|
4
|
+
export function cn(...inputs: ClassValue[]) {
|
|
5
|
+
return twMerge(clsx(inputs));
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function getInitials(name: string): string {
|
|
9
|
+
return name
|
|
10
|
+
.split(' ')
|
|
11
|
+
.map((n) => n[0])
|
|
12
|
+
.join('')
|
|
13
|
+
.toUpperCase()
|
|
14
|
+
.slice(0, 2);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function formatDate(date: string | Date): string {
|
|
18
|
+
return new Intl.DateTimeFormat('en-US', {
|
|
19
|
+
month: 'short',
|
|
20
|
+
day: 'numeric',
|
|
21
|
+
year: 'numeric',
|
|
22
|
+
}).format(new Date(date));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function formatRelativeTime(date: string | Date): string {
|
|
26
|
+
const now = new Date();
|
|
27
|
+
const past = new Date(date);
|
|
28
|
+
const diffInSeconds = Math.floor((now.getTime() - past.getTime()) / 1000);
|
|
29
|
+
|
|
30
|
+
if (diffInSeconds < 60) return 'just now';
|
|
31
|
+
if (diffInSeconds < 3600) return `${Math.floor(diffInSeconds / 60)}m ago`;
|
|
32
|
+
if (diffInSeconds < 86400) return `${Math.floor(diffInSeconds / 3600)}h ago`;
|
|
33
|
+
if (diffInSeconds < 604800) return `${Math.floor(diffInSeconds / 86400)}d ago`;
|
|
34
|
+
|
|
35
|
+
return formatDate(date);
|
|
36
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { useSlots } from './SlotProvider';
|
|
2
|
+
import type { SlotName, SlotInjection } from './types';
|
|
3
|
+
|
|
4
|
+
interface SlotProps {
|
|
5
|
+
name: SlotName;
|
|
6
|
+
children?: React.ReactNode; // Fallback content
|
|
7
|
+
className?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Slot - Component that renders injected content from plugins
|
|
12
|
+
*
|
|
13
|
+
* Plugins can register content to be rendered in specific slots
|
|
14
|
+
* throughout the host layout (sidebar, topbar, etc.)
|
|
15
|
+
*/
|
|
16
|
+
export function Slot({ name, children, className }: SlotProps) {
|
|
17
|
+
const { getSlotContent } = useSlots();
|
|
18
|
+
|
|
19
|
+
const content = getSlotContent(name);
|
|
20
|
+
|
|
21
|
+
if (!content || content.length === 0) {
|
|
22
|
+
return <>{children}</>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<div className={className}>
|
|
27
|
+
{content.map((item: SlotInjection, index: number) => (
|
|
28
|
+
<div key={`${item.slot}-${index}`} className="slot-item">
|
|
29
|
+
{item.condition === undefined || item.condition() ? (
|
|
30
|
+
typeof item.component === 'function' ? (
|
|
31
|
+
<item.component {...(item.props || {})} />
|
|
32
|
+
) : (
|
|
33
|
+
item.component
|
|
34
|
+
)
|
|
35
|
+
) : null}
|
|
36
|
+
</div>
|
|
37
|
+
))}
|
|
38
|
+
{children}
|
|
39
|
+
</div>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { createContext, useContext, ReactNode } from 'react';
|
|
2
|
+
import type { SlotInjection, SlotName } from './types';
|
|
3
|
+
import { usePluginStore } from './store';
|
|
4
|
+
import { pluginLoader } from './loader';
|
|
5
|
+
|
|
6
|
+
interface SlotContextValue {
|
|
7
|
+
getSlotContent: (name: SlotName) => SlotInjection[];
|
|
8
|
+
registerSlot: (name: SlotName, injection: SlotInjection) => void;
|
|
9
|
+
unregisterSlot: (name: SlotName, component: ReactNode) => void;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const SlotContext = createContext<SlotContextValue | null>(null);
|
|
13
|
+
|
|
14
|
+
interface SlotProviderProps {
|
|
15
|
+
children: ReactNode;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* SlotProvider - Provides slot injection system to the app
|
|
20
|
+
*
|
|
21
|
+
* This provider:
|
|
22
|
+
* 1. Collects slot injections from all enabled plugins
|
|
23
|
+
* 2. Sorts them by order
|
|
24
|
+
* 3. Provides getSlotContent function for Slot components
|
|
25
|
+
*/
|
|
26
|
+
export function SlotProvider({ children }: SlotProviderProps) {
|
|
27
|
+
const plugins = usePluginStore((state) => state.plugins);
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Get all content for a slot from enabled plugins
|
|
31
|
+
*/
|
|
32
|
+
const getSlotContent = (name: SlotName): SlotInjection[] => {
|
|
33
|
+
const injections: SlotInjection[] = [];
|
|
34
|
+
|
|
35
|
+
// Collect injections from all enabled plugins
|
|
36
|
+
for (const plugin of Object.values(plugins)) {
|
|
37
|
+
if (!plugin.enabled || !plugin.loaded) continue;
|
|
38
|
+
|
|
39
|
+
const config = pluginLoader.getPluginConfig(plugin.id);
|
|
40
|
+
if (!config) continue;
|
|
41
|
+
|
|
42
|
+
for (const slot of config.slots) {
|
|
43
|
+
if (slot.slot === name) {
|
|
44
|
+
injections.push(slot);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Sort by order (lower = higher priority = first)
|
|
50
|
+
return injections.sort((a, b) => (a.order || 100) - (b.order || 100));
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Register a slot injection (for dynamic registration)
|
|
55
|
+
*/
|
|
56
|
+
const registerSlot = (name: SlotName, injection: SlotInjection) => {
|
|
57
|
+
// This would be used for runtime slot registration
|
|
58
|
+
// For now, slots are registered via plugin config
|
|
59
|
+
console.log('[SlotProvider] Register slot:', name);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Unregister a slot injection
|
|
64
|
+
*/
|
|
65
|
+
const unregisterSlot = (name: SlotName, component: ReactNode) => {
|
|
66
|
+
console.log('[SlotProvider] Unregister slot:', name);
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<SlotContext.Provider value={{ getSlotContent, registerSlot, unregisterSlot }}>
|
|
71
|
+
{children}
|
|
72
|
+
</SlotContext.Provider>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Hook to access slot context
|
|
78
|
+
*/
|
|
79
|
+
export function useSlots() {
|
|
80
|
+
const context = useContext(SlotContext);
|
|
81
|
+
if (!context) {
|
|
82
|
+
throw new Error('useSlots must be used within SlotProvider');
|
|
83
|
+
}
|
|
84
|
+
return context;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Export SlotContext for use in Slot component
|
|
88
|
+
export { SlotContext };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin System - Barrel Export
|
|
3
|
+
*
|
|
4
|
+
* Exports all plugin system functionality for use throughout the host application
|
|
5
|
+
* and for plugin developers.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// Types and interfaces
|
|
9
|
+
export * from './types';
|
|
10
|
+
|
|
11
|
+
// Zod schemas
|
|
12
|
+
export * from './schemas';
|
|
13
|
+
|
|
14
|
+
// Zustand store
|
|
15
|
+
export * from './store';
|
|
16
|
+
|
|
17
|
+
// Plugin loader
|
|
18
|
+
export * from './loader';
|
|
19
|
+
|
|
20
|
+
// Slot components
|
|
21
|
+
export * from './Slot';
|
|
22
|
+
export * from './SlotProvider';
|
|
23
|
+
export { useSlots } from './SlotProvider';
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import Garfish from 'garfish';
|
|
2
|
+
import type { PluginConfig, PluginManifest } from './types';
|
|
3
|
+
import { usePluginStore } from './store';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Plugin loader - handles dynamic loading of plugins
|
|
7
|
+
*/
|
|
8
|
+
class PluginLoader {
|
|
9
|
+
private garfishInstance: typeof Garfish | null = null;
|
|
10
|
+
private loadedPlugins = new Map<string, PluginConfig>();
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Initialize loader with Garfish instance
|
|
14
|
+
*/
|
|
15
|
+
initialize(garfish: typeof Garfish): void {
|
|
16
|
+
this.garfishInstance = garfish;
|
|
17
|
+
console.log('[PluginLoader] Initialized');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Load a plugin dynamically
|
|
22
|
+
*/
|
|
23
|
+
async loadPlugin(entry: string | (() => Promise<unknown>)): Promise<PluginConfig | null> {
|
|
24
|
+
if (!this.garfishInstance) {
|
|
25
|
+
console.error('[PluginLoader] Garfish not initialized');
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
let module: any;
|
|
31
|
+
|
|
32
|
+
if (typeof entry === 'function') {
|
|
33
|
+
// Dynamic import (production)
|
|
34
|
+
module = await entry();
|
|
35
|
+
} else {
|
|
36
|
+
// URL load (development)
|
|
37
|
+
// In dev, plugins run on separate servers
|
|
38
|
+
// We just track them, actual loading is handled by Garfish
|
|
39
|
+
console.log(`[PluginLoader] Dev mode: Plugin at ${entry}`);
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Extract plugin config from module
|
|
44
|
+
const config = module.default?.config || module.config;
|
|
45
|
+
|
|
46
|
+
if (!config) {
|
|
47
|
+
console.error('[PluginLoader] Plugin has no config export');
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Validate config
|
|
52
|
+
this.loadedPlugins.set(config.manifest.name, config);
|
|
53
|
+
|
|
54
|
+
console.log(`[PluginLoader] Loaded plugin: ${config.manifest.name}`);
|
|
55
|
+
|
|
56
|
+
return config;
|
|
57
|
+
} catch (error) {
|
|
58
|
+
console.error('[PluginLoader] Failed to load plugin:', error);
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Load all configured plugins
|
|
65
|
+
*/
|
|
66
|
+
async loadPlugins(plugins: Array<{
|
|
67
|
+
name: string;
|
|
68
|
+
entry: string | (() => Promise<unknown>);
|
|
69
|
+
}>): Promise<void> {
|
|
70
|
+
const store = usePluginStore.getState();
|
|
71
|
+
|
|
72
|
+
for (const plugin of plugins) {
|
|
73
|
+
const state = store.plugins[plugin.name];
|
|
74
|
+
|
|
75
|
+
if (!state || !state.enabled) {
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
const config = await this.loadPlugin(plugin.entry);
|
|
81
|
+
|
|
82
|
+
if (config) {
|
|
83
|
+
store.registerPlugin(config);
|
|
84
|
+
store.setPluginLoaded(plugin.name, true);
|
|
85
|
+
}
|
|
86
|
+
} catch (error: any) {
|
|
87
|
+
store.setPluginError(plugin.name, error.message);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Get loaded plugin config
|
|
94
|
+
*/
|
|
95
|
+
getPluginConfig(name: string): PluginConfig | undefined {
|
|
96
|
+
return this.loadedPlugins.get(name);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Get all loaded plugins
|
|
101
|
+
*/
|
|
102
|
+
getAllPlugins(): PluginConfig[] {
|
|
103
|
+
return Array.from(this.loadedPlugins.values());
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Check if plugin is loaded
|
|
108
|
+
*/
|
|
109
|
+
isLoaded(name: string): boolean {
|
|
110
|
+
return this.loadedPlugins.has(name);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Export singleton
|
|
115
|
+
export const pluginLoader = new PluginLoader();
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Initialize plugin loader (call from bootstrap)
|
|
119
|
+
*/
|
|
120
|
+
export function initializePluginLoader(garfish: typeof Garfish): void {
|
|
121
|
+
pluginLoader.initialize(garfish);
|
|
122
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { SlotName } from './types';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Slot injection schema
|
|
6
|
+
*/
|
|
7
|
+
export const slotInjectionSchema = z.object({
|
|
8
|
+
slot: z.nativeEnum(SlotName),
|
|
9
|
+
component: z.any(), // React component
|
|
10
|
+
order: z.number().optional().default(100),
|
|
11
|
+
props: z.record(z.any()).optional(),
|
|
12
|
+
condition: z.function().optional(),
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Plugin manifest schema
|
|
17
|
+
*/
|
|
18
|
+
export const pluginManifestSchema = z.object({
|
|
19
|
+
name: z.string().regex(/^@lego\/plugin-/, 'Plugin name must start with @lego/plugin-'),
|
|
20
|
+
version: z.string().regex(/^\d+\.\d+\.\d+$/, 'Version must be semver'),
|
|
21
|
+
displayName: z.string().min(1).max(50),
|
|
22
|
+
description: z.string().max(500),
|
|
23
|
+
author: z.string().optional(),
|
|
24
|
+
icon: z.string().optional(),
|
|
25
|
+
permissions: z.array(z.string()).optional(),
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Plugin config schema
|
|
30
|
+
*/
|
|
31
|
+
export const pluginConfigSchema = z.object({
|
|
32
|
+
manifest: pluginManifestSchema,
|
|
33
|
+
enabled: z.boolean().default(true),
|
|
34
|
+
slots: z.array(slotInjectionSchema).default([]),
|
|
35
|
+
routes: z.array(z.object({
|
|
36
|
+
path: z.string(),
|
|
37
|
+
component: z.any(),
|
|
38
|
+
protected: z.boolean().optional(),
|
|
39
|
+
permissions: z.array(z.string()).optional(),
|
|
40
|
+
})).optional(),
|
|
41
|
+
settings: z.array(z.object({
|
|
42
|
+
key: z.string(),
|
|
43
|
+
type: z.enum(['boolean', 'string', 'number', 'select']),
|
|
44
|
+
label: z.string(),
|
|
45
|
+
description: z.string().optional(),
|
|
46
|
+
defaultValue: z.any(),
|
|
47
|
+
options: z.array(z.object({
|
|
48
|
+
label: z.string(),
|
|
49
|
+
value: z.string(),
|
|
50
|
+
})).optional(),
|
|
51
|
+
})).optional(),
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
export type PluginConfigInput = z.infer<typeof pluginConfigSchema>;
|