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,31 @@
|
|
|
1
|
+
import { useGlobalKernelState } from './store';
|
|
2
|
+
import { registerPluginChannels } from '../channels/plugin-hooks';
|
|
3
|
+
|
|
4
|
+
// Declare window type for shared state
|
|
5
|
+
declare global {
|
|
6
|
+
interface Window {
|
|
7
|
+
__LEGO_KERNEL_STATE__?: {
|
|
8
|
+
useGlobalKernelState: typeof useGlobalKernelState;
|
|
9
|
+
};
|
|
10
|
+
__LEGO_PLUGIN_CHANNELS__?: {
|
|
11
|
+
toast: (data: any) => void;
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Register shared state to window for plugin access
|
|
17
|
+
export function registerSharedState() {
|
|
18
|
+
if (typeof window !== 'undefined') {
|
|
19
|
+
window.__LEGO_KERNEL_STATE__ = {
|
|
20
|
+
useGlobalKernelState,
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// Also register plugin channels helper
|
|
24
|
+
registerPluginChannels();
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Hook for plugins to access kernel state
|
|
29
|
+
export function useKernelState() {
|
|
30
|
+
return useGlobalKernelState;
|
|
31
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { create } from 'zustand';
|
|
2
|
+
import { devtools, persist } from 'zustand/middleware';
|
|
3
|
+
import type { GlobalKernelState, Theme, User, Organization, Toast } from './types';
|
|
4
|
+
|
|
5
|
+
const generateId = () => Math.random().toString(36).substring(2, 9);
|
|
6
|
+
|
|
7
|
+
export const useGlobalKernelState = create<GlobalKernelState>()(
|
|
8
|
+
devtools(
|
|
9
|
+
persist(
|
|
10
|
+
(set) => ({
|
|
11
|
+
// Initial state
|
|
12
|
+
user: null,
|
|
13
|
+
token: null,
|
|
14
|
+
isAuthenticated: false,
|
|
15
|
+
organization: null,
|
|
16
|
+
theme: 'system',
|
|
17
|
+
sidebarOpen: true,
|
|
18
|
+
mobileMenuOpen: false,
|
|
19
|
+
toasts: [],
|
|
20
|
+
isLoading: false,
|
|
21
|
+
|
|
22
|
+
// Auth actions
|
|
23
|
+
setUser: (user: User | null) => set({ user, isAuthenticated: !!user }),
|
|
24
|
+
setToken: (token: string | null) => set({ token }),
|
|
25
|
+
setOrganization: (org: Organization | null) => set({ organization: org }),
|
|
26
|
+
clearAuth: () => set({
|
|
27
|
+
user: null,
|
|
28
|
+
token: null,
|
|
29
|
+
isAuthenticated: false,
|
|
30
|
+
organization: null,
|
|
31
|
+
}),
|
|
32
|
+
|
|
33
|
+
// UI actions
|
|
34
|
+
setTheme: (theme: Theme) => set({ theme }),
|
|
35
|
+
toggleSidebar: () => set((state) => ({ sidebarOpen: !state.sidebarOpen })),
|
|
36
|
+
setSidebarOpen: (open: boolean) => set({ sidebarOpen: open }),
|
|
37
|
+
toggleMobileMenu: () => set((state) => ({ mobileMenuOpen: !state.mobileMenuOpen })),
|
|
38
|
+
setMobileMenuOpen: (open: boolean) => set({ mobileMenuOpen: open }),
|
|
39
|
+
|
|
40
|
+
// Toast actions
|
|
41
|
+
addToast: (toast: Omit<Toast, 'id'>) => set((state) => ({
|
|
42
|
+
toasts: [...state.toasts, { ...toast, id: generateId() }],
|
|
43
|
+
})),
|
|
44
|
+
removeToast: (id: string) => set((state) => ({
|
|
45
|
+
toasts: state.toasts.filter((t) => t.id !== id),
|
|
46
|
+
})),
|
|
47
|
+
|
|
48
|
+
// Loading actions
|
|
49
|
+
setIsLoading: (isLoading: boolean) => set({ isLoading }),
|
|
50
|
+
}),
|
|
51
|
+
{
|
|
52
|
+
name: 'lego-kernel-state',
|
|
53
|
+
partialize: (state) => ({
|
|
54
|
+
theme: state.theme,
|
|
55
|
+
sidebarOpen: state.sidebarOpen,
|
|
56
|
+
token: state.token,
|
|
57
|
+
}),
|
|
58
|
+
}
|
|
59
|
+
),
|
|
60
|
+
{ name: 'LegoKernelState' }
|
|
61
|
+
)
|
|
62
|
+
);
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
export interface User {
|
|
2
|
+
id: string;
|
|
3
|
+
email: string;
|
|
4
|
+
name: string;
|
|
5
|
+
avatar?: string;
|
|
6
|
+
role?: string;
|
|
7
|
+
organizationId?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface Organization {
|
|
11
|
+
id: string;
|
|
12
|
+
name: string;
|
|
13
|
+
slug: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface AuthState {
|
|
17
|
+
user: User | null;
|
|
18
|
+
token: string | null;
|
|
19
|
+
isAuthenticated: boolean;
|
|
20
|
+
organization: Organization | null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export type Theme = 'light' | 'dark' | 'system';
|
|
24
|
+
|
|
25
|
+
export interface UIState {
|
|
26
|
+
theme: Theme;
|
|
27
|
+
sidebarOpen: boolean;
|
|
28
|
+
mobileMenuOpen: boolean;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface Toast {
|
|
32
|
+
id: string;
|
|
33
|
+
title: string;
|
|
34
|
+
description?: string;
|
|
35
|
+
variant?: 'default' | 'destructive' | 'success';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface GlobalKernelState extends AuthState, UIState {
|
|
39
|
+
// Auth actions
|
|
40
|
+
setUser: (user: User | null) => void;
|
|
41
|
+
setToken: (token: string | null) => void;
|
|
42
|
+
setOrganization: (org: Organization | null) => void;
|
|
43
|
+
clearAuth: () => void;
|
|
44
|
+
|
|
45
|
+
// UI actions
|
|
46
|
+
setTheme: (theme: Theme) => void;
|
|
47
|
+
toggleSidebar: () => void;
|
|
48
|
+
setSidebarOpen: (open: boolean) => void;
|
|
49
|
+
toggleMobileMenu: () => void;
|
|
50
|
+
setMobileMenuOpen: (open: boolean) => void;
|
|
51
|
+
|
|
52
|
+
// Toast actions
|
|
53
|
+
toasts: Toast[];
|
|
54
|
+
addToast: (toast: Omit<Toast, 'id'>) => void;
|
|
55
|
+
removeToast: (id: string) => void;
|
|
56
|
+
|
|
57
|
+
// Loading state
|
|
58
|
+
isLoading: boolean;
|
|
59
|
+
setIsLoading: (loading: boolean) => void;
|
|
60
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import { getPocketBaseAdmin } from '../lib/pocketbase';
|
|
3
|
+
import { runMigrations } from '../lib/pocketbase/migrations';
|
|
4
|
+
import { collections } from '../lib/pocketbase/collections';
|
|
5
|
+
import { seedDatabase } from '../lib/pocketbase/seed';
|
|
6
|
+
|
|
7
|
+
interface MigrationState {
|
|
8
|
+
status: 'pending' | 'running' | 'success' | 'error';
|
|
9
|
+
message: string | null;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function useMigrations() {
|
|
13
|
+
const [state, setState] = useState<MigrationState>({
|
|
14
|
+
status: 'pending',
|
|
15
|
+
message: null,
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
let cancelled = false;
|
|
20
|
+
|
|
21
|
+
async function run() {
|
|
22
|
+
if (cancelled) return;
|
|
23
|
+
|
|
24
|
+
setState({ status: 'running', message: 'Running database migrations...' });
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
const pb = getPocketBaseAdmin();
|
|
28
|
+
|
|
29
|
+
// Authenticate as admin
|
|
30
|
+
await pb.admins.authWithPassword(
|
|
31
|
+
import.meta.env.VITE_POCKETBASE_ADMIN_EMAIL || 'admin@example.com',
|
|
32
|
+
import.meta.env.VITE_POCKETBASE_ADMIN_PASSWORD || 'admin123'
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
if (cancelled) return;
|
|
36
|
+
|
|
37
|
+
// Run migrations
|
|
38
|
+
await runMigrations(pb, collections);
|
|
39
|
+
|
|
40
|
+
if (cancelled) return;
|
|
41
|
+
|
|
42
|
+
// Seed database (only if in development or explicitly requested)
|
|
43
|
+
if (import.meta.env.VITE_SEED_ADMIN_EMAIL) {
|
|
44
|
+
setState({ status: 'running', message: 'Seeding database...' });
|
|
45
|
+
|
|
46
|
+
await seedDatabase(pb, {
|
|
47
|
+
adminEmail: import.meta.env.VITE_SEED_ADMIN_EMAIL,
|
|
48
|
+
adminPassword: import.meta.env.VITE_SEED_ADMIN_PASSWORD || 'admin123',
|
|
49
|
+
adminName: import.meta.env.VITE_SEED_ADMIN_NAME || 'Super Admin',
|
|
50
|
+
orgName: import.meta.env.VITE_SEED_ORG_NAME || 'Demo Organization',
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (cancelled) return;
|
|
55
|
+
|
|
56
|
+
setState({ status: 'success', message: 'Database ready!' });
|
|
57
|
+
} catch (error) {
|
|
58
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
59
|
+
setState({ status: 'error', message });
|
|
60
|
+
console.error('[Migrations] Error:', error);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
run();
|
|
65
|
+
|
|
66
|
+
return () => {
|
|
67
|
+
cancelled = true;
|
|
68
|
+
};
|
|
69
|
+
}, []);
|
|
70
|
+
|
|
71
|
+
return state;
|
|
72
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { NavLink } from '@modern-js/runtime/router';
|
|
2
|
+
import { useGlobalKernelState } from '../kernel/shared-state';
|
|
3
|
+
import {
|
|
4
|
+
Home,
|
|
5
|
+
LayoutDashboard,
|
|
6
|
+
Settings,
|
|
7
|
+
CheckSquare,
|
|
8
|
+
X,
|
|
9
|
+
} from 'lucide-react';
|
|
10
|
+
import { cn } from '../kernel/lib/utils';
|
|
11
|
+
|
|
12
|
+
const navItems = [
|
|
13
|
+
{ to: '/', icon: Home, label: 'Home' },
|
|
14
|
+
{ to: '/dashboard', icon: LayoutDashboard, label: 'Dashboard' },
|
|
15
|
+
{ to: '/todos', icon: CheckSquare, label: 'Todos' },
|
|
16
|
+
{ to: '/settings', icon: Settings, label: 'Settings' },
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
export function MobileMenu() {
|
|
20
|
+
const { mobileMenuOpen, setMobileMenuOpen } = useGlobalKernelState();
|
|
21
|
+
|
|
22
|
+
if (!mobileMenuOpen) return null;
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<div className="fixed inset-0 z-50 lg:hidden">
|
|
26
|
+
<div className="fixed inset-y-0 right-0 w-64 border-l bg-background p-6 shadow-lg">
|
|
27
|
+
<div className="flex items-center justify-between mb-8">
|
|
28
|
+
<h2 className="text-lg font-semibold">Menu</h2>
|
|
29
|
+
<button
|
|
30
|
+
onClick={() => setMobileMenuOpen(false)}
|
|
31
|
+
className="rounded-lg p-2 text-muted-foreground hover:bg-muted"
|
|
32
|
+
>
|
|
33
|
+
<X className="h-5 w-5" />
|
|
34
|
+
</button>
|
|
35
|
+
</div>
|
|
36
|
+
|
|
37
|
+
<nav className="space-y-1">
|
|
38
|
+
{navItems.map((item) => (
|
|
39
|
+
<NavLink
|
|
40
|
+
key={item.to}
|
|
41
|
+
to={item.to}
|
|
42
|
+
end={item.to === '/'}
|
|
43
|
+
onClick={() => setMobileMenuOpen(false)}
|
|
44
|
+
className={({ isActive }) =>
|
|
45
|
+
cn(
|
|
46
|
+
'flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium transition-colors',
|
|
47
|
+
isActive
|
|
48
|
+
? 'bg-primary text-primary-foreground'
|
|
49
|
+
: 'text-muted-foreground hover:bg-muted hover:text-foreground'
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
>
|
|
53
|
+
<item.icon className="h-5 w-5" />
|
|
54
|
+
{item.label}
|
|
55
|
+
</NavLink>
|
|
56
|
+
))}
|
|
57
|
+
</nav>
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { Outlet } from '@modern-js/runtime/router';
|
|
2
|
+
import { Sidebar } from './Sidebar';
|
|
3
|
+
import { Topbar } from './Topbar';
|
|
4
|
+
import { MobileMenu } from './MobileMenu';
|
|
5
|
+
import { useGlobalKernelState } from '../kernel/shared-state';
|
|
6
|
+
|
|
7
|
+
export function Shell({ children }: { children?: React.ReactNode }) {
|
|
8
|
+
const { sidebarOpen, mobileMenuOpen } = useGlobalKernelState();
|
|
9
|
+
|
|
10
|
+
return (
|
|
11
|
+
<div className="min-h-screen bg-background">
|
|
12
|
+
{/* Mobile menu overlay */}
|
|
13
|
+
{mobileMenuOpen && (
|
|
14
|
+
<div
|
|
15
|
+
className="fixed inset-0 z-40 bg-background/80 backdrop-blur-sm lg:hidden"
|
|
16
|
+
aria-hidden="true"
|
|
17
|
+
/>
|
|
18
|
+
)}
|
|
19
|
+
|
|
20
|
+
{/* Sidebar */}
|
|
21
|
+
<Sidebar />
|
|
22
|
+
|
|
23
|
+
{/* Main content area */}
|
|
24
|
+
<div
|
|
25
|
+
className={`transition-all duration-300 lg:pl-64 ${
|
|
26
|
+
sidebarOpen ? 'lg:pl-64' : 'lg:pl-16'
|
|
27
|
+
}`}
|
|
28
|
+
>
|
|
29
|
+
{/* Topbar */}
|
|
30
|
+
<Topbar />
|
|
31
|
+
|
|
32
|
+
{/* Page content */}
|
|
33
|
+
<main className="min-h-[calc(100vh-4rem)] p-6">
|
|
34
|
+
{children || <Outlet />}
|
|
35
|
+
</main>
|
|
36
|
+
</div>
|
|
37
|
+
|
|
38
|
+
{/* Mobile menu */}
|
|
39
|
+
<MobileMenu />
|
|
40
|
+
</div>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { Link, NavLink } from '@modern-js/runtime/router';
|
|
2
|
+
import { useGlobalKernelState } from '../kernel/shared-state';
|
|
3
|
+
import { Slot } from '../kernel/plugins/Slot';
|
|
4
|
+
import { SlotName } from '../kernel/plugins/types';
|
|
5
|
+
import {
|
|
6
|
+
Home,
|
|
7
|
+
LayoutDashboard,
|
|
8
|
+
Settings,
|
|
9
|
+
ChevronLeft,
|
|
10
|
+
ChevronRight,
|
|
11
|
+
CheckSquare,
|
|
12
|
+
} from 'lucide-react';
|
|
13
|
+
import { cn } from '../kernel/lib/utils';
|
|
14
|
+
|
|
15
|
+
const navItems = [
|
|
16
|
+
{ to: '/', icon: Home, label: 'Home' },
|
|
17
|
+
{ to: '/dashboard', icon: LayoutDashboard, label: 'Dashboard' },
|
|
18
|
+
{ to: '/todos', icon: CheckSquare, label: 'Todos' },
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
export function Sidebar() {
|
|
22
|
+
const { sidebarOpen, toggleSidebar, mobileMenuOpen } = useGlobalKernelState();
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<>
|
|
26
|
+
{/* Mobile sidebar */}
|
|
27
|
+
<aside
|
|
28
|
+
className={cn(
|
|
29
|
+
'fixed inset-y-0 left-0 z-50 w-64 transform border-r bg-card transition-transform duration-300 lg:hidden',
|
|
30
|
+
mobileMenuOpen ? 'translate-x-0' : '-translate-x-full'
|
|
31
|
+
)}
|
|
32
|
+
>
|
|
33
|
+
{/* Slot: sidebar top */}
|
|
34
|
+
<Slot name={SlotName.SIDEBAR_TOP} className="border-b p-4" />
|
|
35
|
+
|
|
36
|
+
<div className="flex h-16 items-center justify-between border-b px-6">
|
|
37
|
+
<Link to="/" className="flex items-center gap-2 font-semibold">
|
|
38
|
+
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-primary text-primary-foreground">
|
|
39
|
+
L
|
|
40
|
+
</div>
|
|
41
|
+
<span>Lego-One</span>
|
|
42
|
+
</Link>
|
|
43
|
+
</div>
|
|
44
|
+
|
|
45
|
+
{/* Slot: sidebar nav (before nav items) */}
|
|
46
|
+
<Slot name={SlotName.SIDEBAR_NAV} />
|
|
47
|
+
|
|
48
|
+
<nav className="space-y-1 p-4">
|
|
49
|
+
{navItems.map((item) => (
|
|
50
|
+
<NavLink
|
|
51
|
+
key={item.to}
|
|
52
|
+
to={item.to}
|
|
53
|
+
end={item.to === '/'}
|
|
54
|
+
className={({ isActive }) =>
|
|
55
|
+
cn(
|
|
56
|
+
'flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium transition-colors',
|
|
57
|
+
isActive
|
|
58
|
+
? 'bg-primary text-primary-foreground'
|
|
59
|
+
: 'text-muted-foreground hover:bg-muted hover:text-foreground'
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
>
|
|
63
|
+
<item.icon className="h-5 w-5" />
|
|
64
|
+
{item.label}
|
|
65
|
+
</NavLink>
|
|
66
|
+
))}
|
|
67
|
+
</nav>
|
|
68
|
+
|
|
69
|
+
{/* Slot: sidebar bottom */}
|
|
70
|
+
<Slot name={SlotName.SIDEBAR_BOTTOM} className="absolute bottom-0 left-0 right-0 border-t p-4" />
|
|
71
|
+
|
|
72
|
+
<div className="absolute bottom-0 left-0 right-0 border-t p-4">
|
|
73
|
+
<NavLink
|
|
74
|
+
to="/settings"
|
|
75
|
+
className={({ isActive }) =>
|
|
76
|
+
cn(
|
|
77
|
+
'flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium transition-colors',
|
|
78
|
+
isActive
|
|
79
|
+
? 'bg-primary text-primary-foreground'
|
|
80
|
+
: 'text-muted-foreground hover:bg-muted hover:text-foreground'
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
>
|
|
84
|
+
<Settings className="h-5 w-5" />
|
|
85
|
+
Settings
|
|
86
|
+
</NavLink>
|
|
87
|
+
</div>
|
|
88
|
+
</aside>
|
|
89
|
+
|
|
90
|
+
{/* Desktop sidebar */}
|
|
91
|
+
<aside
|
|
92
|
+
className={cn(
|
|
93
|
+
'fixed inset-y-0 left-0 z-30 hidden border-r bg-card transition-all duration-300 lg:block',
|
|
94
|
+
sidebarOpen ? 'w-64' : 'w-16'
|
|
95
|
+
)}
|
|
96
|
+
>
|
|
97
|
+
{/* Slot: sidebar top (desktop) */}
|
|
98
|
+
<Slot name={SlotName.SIDEBAR_TOP} className="border-b p-4" />
|
|
99
|
+
|
|
100
|
+
<div className="flex h-16 items-center justify-between border-b px-4">
|
|
101
|
+
{sidebarOpen ? (
|
|
102
|
+
<Link to="/" className="flex items-center gap-2 font-semibold">
|
|
103
|
+
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-primary text-primary-foreground">
|
|
104
|
+
L
|
|
105
|
+
</div>
|
|
106
|
+
<span>Lego-One</span>
|
|
107
|
+
</Link>
|
|
108
|
+
) : (
|
|
109
|
+
<Link to="/" className="flex items-center justify-center">
|
|
110
|
+
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-primary text-primary-foreground">
|
|
111
|
+
L
|
|
112
|
+
</div>
|
|
113
|
+
</Link>
|
|
114
|
+
)}
|
|
115
|
+
|
|
116
|
+
<button
|
|
117
|
+
onClick={toggleSidebar}
|
|
118
|
+
className="rounded-lg p-1.5 text-muted-foreground hover:bg-muted hover:text-foreground"
|
|
119
|
+
>
|
|
120
|
+
{sidebarOpen ? (
|
|
121
|
+
<ChevronLeft className="h-5 w-5" />
|
|
122
|
+
) : (
|
|
123
|
+
<ChevronRight className="h-5 w-5" />
|
|
124
|
+
)}
|
|
125
|
+
</button>
|
|
126
|
+
</div>
|
|
127
|
+
|
|
128
|
+
{/* Slot: sidebar nav (desktop) */}
|
|
129
|
+
<Slot name={SlotName.SIDEBAR_NAV} />
|
|
130
|
+
|
|
131
|
+
<nav className="space-y-1 p-2">
|
|
132
|
+
{navItems.map((item) => (
|
|
133
|
+
<NavLink
|
|
134
|
+
key={item.to}
|
|
135
|
+
to={item.to}
|
|
136
|
+
end={item.to === '/'}
|
|
137
|
+
className={({ isActive }) =>
|
|
138
|
+
cn(
|
|
139
|
+
'flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium transition-colors',
|
|
140
|
+
!sidebarOpen && 'justify-center',
|
|
141
|
+
isActive
|
|
142
|
+
? 'bg-primary text-primary-foreground'
|
|
143
|
+
: 'text-muted-foreground hover:bg-muted hover:text-foreground'
|
|
144
|
+
)
|
|
145
|
+
}
|
|
146
|
+
title={!sidebarOpen ? item.label : undefined}
|
|
147
|
+
>
|
|
148
|
+
<item.icon className="h-5 w-5 flex-shrink-0" />
|
|
149
|
+
{sidebarOpen && <span>{item.label}</span>}
|
|
150
|
+
</NavLink>
|
|
151
|
+
))}
|
|
152
|
+
</nav>
|
|
153
|
+
|
|
154
|
+
{/* Slot: sidebar bottom (desktop) */}
|
|
155
|
+
<Slot name={SlotName.SIDEBAR_BOTTOM} className="absolute bottom-0 left-0 right-0 border-t p-2" />
|
|
156
|
+
|
|
157
|
+
<div className="absolute bottom-0 left-0 right-0 border-t p-2">
|
|
158
|
+
<NavLink
|
|
159
|
+
to="/settings"
|
|
160
|
+
className={({ isActive }) =>
|
|
161
|
+
cn(
|
|
162
|
+
'flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium transition-colors',
|
|
163
|
+
!sidebarOpen && 'justify-center',
|
|
164
|
+
isActive
|
|
165
|
+
? 'bg-primary text-primary-foreground'
|
|
166
|
+
: 'text-muted-foreground hover:bg-muted hover:text-foreground'
|
|
167
|
+
)
|
|
168
|
+
}
|
|
169
|
+
title={!sidebarOpen ? 'Settings' : undefined}
|
|
170
|
+
>
|
|
171
|
+
<Settings className="h-5 w-5 flex-shrink-0" />
|
|
172
|
+
{sidebarOpen && <span>Settings</span>}
|
|
173
|
+
</NavLink>
|
|
174
|
+
</div>
|
|
175
|
+
</aside>
|
|
176
|
+
</>
|
|
177
|
+
);
|
|
178
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { useGlobalKernelState } from '../kernel/shared-state';
|
|
2
|
+
import { Menu, Bell } from 'lucide-react';
|
|
3
|
+
import { LogoutButton } from '../kernel/auth/components/LogoutButton';
|
|
4
|
+
import { useAuth } from '../kernel/auth/hooks';
|
|
5
|
+
import { OrganizationSelector } from '../kernel/rbac/components/OrganizationSelector';
|
|
6
|
+
import { Slot } from '../kernel/plugins/Slot';
|
|
7
|
+
import { SlotName } from '../kernel/plugins/types';
|
|
8
|
+
|
|
9
|
+
export function Topbar() {
|
|
10
|
+
const { toggleMobileMenu } = useGlobalKernelState();
|
|
11
|
+
const { isAuthenticated } = useAuth();
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<header className="sticky top-0 z-20 flex h-16 items-center gap-4 border-b bg-background px-6">
|
|
15
|
+
{/* Mobile menu button */}
|
|
16
|
+
<button
|
|
17
|
+
onClick={toggleMobileMenu}
|
|
18
|
+
className="lg:hidden rounded-lg p-2 text-muted-foreground hover:bg-muted"
|
|
19
|
+
>
|
|
20
|
+
<Menu className="h-5 w-5" />
|
|
21
|
+
</button>
|
|
22
|
+
|
|
23
|
+
{/* Slot: topbar left */}
|
|
24
|
+
<Slot name={SlotName.TOPBAR_LEFT} className="flex items-center gap-2" />
|
|
25
|
+
|
|
26
|
+
{/* Organization selector */}
|
|
27
|
+
{isAuthenticated && <OrganizationSelector />}
|
|
28
|
+
|
|
29
|
+
{/* Slot: topbar center */}
|
|
30
|
+
<Slot name={SlotName.TOPBAR_CENTER} className="flex-1" />
|
|
31
|
+
|
|
32
|
+
{/* Breadcrumb/spacer */}
|
|
33
|
+
<div className="flex-1" />
|
|
34
|
+
|
|
35
|
+
{/* Slot: topbar right */}
|
|
36
|
+
<Slot name={SlotName.TOPBAR_RIGHT} className="flex items-center gap-2" />
|
|
37
|
+
|
|
38
|
+
{/* Actions */}
|
|
39
|
+
<div className="flex items-center gap-2">
|
|
40
|
+
{isAuthenticated && (
|
|
41
|
+
<button className="rounded-lg p-2 text-muted-foreground hover:bg-muted">
|
|
42
|
+
<Bell className="h-5 w-5" />
|
|
43
|
+
</button>
|
|
44
|
+
)}
|
|
45
|
+
|
|
46
|
+
{isAuthenticated && <LogoutButton />}
|
|
47
|
+
</div>
|
|
48
|
+
</header>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import PocketBase from 'pocketbase';
|
|
2
|
+
|
|
3
|
+
let pbInstance: PocketBase | null = null;
|
|
4
|
+
|
|
5
|
+
export function getPocketBase(): PocketBase {
|
|
6
|
+
if (pbInstance) {
|
|
7
|
+
return pbInstance;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const pb = new PocketBase(import.meta.env.VITE_POCKETBASE_URL || 'http://127.0.0.1:8090');
|
|
11
|
+
|
|
12
|
+
// Auto-authenticate as admin for server-side operations
|
|
13
|
+
if (typeof window === 'undefined') {
|
|
14
|
+
pb.admins
|
|
15
|
+
.authWithPassword(
|
|
16
|
+
import.meta.env.VITE_POCKETBASE_ADMIN_EMAIL || 'admin@example.com',
|
|
17
|
+
import.meta.env.VITE_POCKETBASE_ADMIN_PASSWORD || 'admin123'
|
|
18
|
+
)
|
|
19
|
+
.catch((err) => {
|
|
20
|
+
console.error('Failed to authenticate with PocketBase:', err);
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
pbInstance = pb;
|
|
25
|
+
return pb;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function getPocketBaseAdmin(): PocketBase {
|
|
29
|
+
const pb = new PocketBase(import.meta.env.VITE_POCKETBASE_URL || 'http://127.0.0.1:8090');
|
|
30
|
+
|
|
31
|
+
// This must be called with admin credentials
|
|
32
|
+
return pb;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Reset singleton (useful for testing)
|
|
36
|
+
export function resetPocketBase(): void {
|
|
37
|
+
pbInstance = null;
|
|
38
|
+
}
|