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.
Files changed (171) hide show
  1. package/dist/index.cjs +145 -0
  2. package/dist/index.cjs.map +1 -1
  3. package/package.json +5 -3
  4. package/template/host/e2e/auth.spec.ts +38 -0
  5. package/template/host/e2e/layout.spec.ts +38 -0
  6. package/template/host/modern.config.ts +19 -0
  7. package/template/host/package.json +71 -0
  8. package/template/host/playwright.config.ts +34 -0
  9. package/template/host/postcss.config.mjs +6 -0
  10. package/template/host/src/App.tsx +6 -0
  11. package/template/host/src/bootstrap.tsx +74 -0
  12. package/template/host/src/global.css +59 -0
  13. package/template/host/src/index.ts +2 -0
  14. package/template/host/src/kernel/__tests__/lib-utils.test.ts +32 -0
  15. package/template/host/src/kernel/__tests__/rbac-hooks.test.tsx +114 -0
  16. package/template/host/src/kernel/__tests__/rbac-utils.test.ts +108 -0
  17. package/template/host/src/kernel/auth/ProtectedRoute.tsx +41 -0
  18. package/template/host/src/kernel/auth/components/LoginForm.tsx +97 -0
  19. package/template/host/src/kernel/auth/components/LogoutButton.tsx +79 -0
  20. package/template/host/src/kernel/auth/hooks.ts +174 -0
  21. package/template/host/src/kernel/auth/index.ts +5 -0
  22. package/template/host/src/kernel/auth/schemas.ts +27 -0
  23. package/template/host/src/kernel/auth/service.ts +197 -0
  24. package/template/host/src/kernel/auth/types.ts +36 -0
  25. package/template/host/src/kernel/channels/ChannelBus.ts +181 -0
  26. package/template/host/src/kernel/channels/ChannelProvider.tsx +57 -0
  27. package/template/host/src/kernel/channels/events.ts +27 -0
  28. package/template/host/src/kernel/channels/hooks.ts +168 -0
  29. package/template/host/src/kernel/channels/index.ts +6 -0
  30. package/template/host/src/kernel/channels/integrations/ToastIntegration.tsx +60 -0
  31. package/template/host/src/kernel/channels/plugin-hooks.ts +72 -0
  32. package/template/host/src/kernel/channels/types.ts +112 -0
  33. package/template/host/src/kernel/components/__tests__/Badge.test.tsx +35 -0
  34. package/template/host/src/kernel/components/__tests__/Button.test.tsx +63 -0
  35. package/template/host/src/kernel/components/__tests__/Input.test.tsx +64 -0
  36. package/template/host/src/kernel/components/index.ts +32 -0
  37. package/template/host/src/kernel/components/ui/alert.tsx +58 -0
  38. package/template/host/src/kernel/components/ui/avatar.tsx +47 -0
  39. package/template/host/src/kernel/components/ui/badge.tsx +35 -0
  40. package/template/host/src/kernel/components/ui/button.tsx +50 -0
  41. package/template/host/src/kernel/components/ui/card.tsx +78 -0
  42. package/template/host/src/kernel/components/ui/dialog.tsx +116 -0
  43. package/template/host/src/kernel/components/ui/dropdown-menu.tsx +192 -0
  44. package/template/host/src/kernel/components/ui/index.ts +7 -0
  45. package/template/host/src/kernel/components/ui/input.tsx +24 -0
  46. package/template/host/src/kernel/components/ui/label.tsx +21 -0
  47. package/template/host/src/kernel/components/ui/popover.tsx +28 -0
  48. package/template/host/src/kernel/components/ui/progress.tsx +25 -0
  49. package/template/host/src/kernel/components/ui/scroll-area.tsx +45 -0
  50. package/template/host/src/kernel/components/ui/select.tsx +155 -0
  51. package/template/host/src/kernel/components/ui/separator.tsx +28 -0
  52. package/template/host/src/kernel/components/ui/skeleton.tsx +15 -0
  53. package/template/host/src/kernel/components/ui/switch.tsx +26 -0
  54. package/template/host/src/kernel/components/ui/table.tsx +116 -0
  55. package/template/host/src/kernel/components/ui/tabs.tsx +52 -0
  56. package/template/host/src/kernel/components/ui/toast.tsx +126 -0
  57. package/template/host/src/kernel/components/ui/toaster.tsx +34 -0
  58. package/template/host/src/kernel/components/ui/tooltip.tsx +27 -0
  59. package/template/host/src/kernel/components/ui/use-toast.ts +183 -0
  60. package/template/host/src/kernel/index.ts +48 -0
  61. package/template/host/src/kernel/lib/cn.ts +1 -0
  62. package/template/host/src/kernel/lib/utils.ts +36 -0
  63. package/template/host/src/kernel/plugins/Slot.tsx +41 -0
  64. package/template/host/src/kernel/plugins/SlotProvider.tsx +88 -0
  65. package/template/host/src/kernel/plugins/index.ts +23 -0
  66. package/template/host/src/kernel/plugins/loader.ts +122 -0
  67. package/template/host/src/kernel/plugins/schemas.ts +54 -0
  68. package/template/host/src/kernel/plugins/store.ts +185 -0
  69. package/template/host/src/kernel/plugins/types.ts +103 -0
  70. package/template/host/src/kernel/providers/PocketBaseProvider.tsx +70 -0
  71. package/template/host/src/kernel/providers/QueryProvider.tsx +28 -0
  72. package/template/host/src/kernel/providers/ThemeProvider.tsx +25 -0
  73. package/template/host/src/kernel/providers/index.ts +3 -0
  74. package/template/host/src/kernel/rbac/components/OrganizationSelector.tsx +69 -0
  75. package/template/host/src/kernel/rbac/components/PermissionGate.tsx +43 -0
  76. package/template/host/src/kernel/rbac/hooks.ts +379 -0
  77. package/template/host/src/kernel/rbac/index.ts +6 -0
  78. package/template/host/src/kernel/rbac/service.ts +504 -0
  79. package/template/host/src/kernel/rbac/types.ts +164 -0
  80. package/template/host/src/kernel/rbac/utils.ts +34 -0
  81. package/template/host/src/kernel/shared-state/bridge.ts +31 -0
  82. package/template/host/src/kernel/shared-state/index.ts +3 -0
  83. package/template/host/src/kernel/shared-state/store.ts +62 -0
  84. package/template/host/src/kernel/shared-state/types.ts +60 -0
  85. package/template/host/src/kernel/use-migrations.ts +72 -0
  86. package/template/host/src/layout/MobileMenu.tsx +61 -0
  87. package/template/host/src/layout/Shell.tsx +42 -0
  88. package/template/host/src/layout/Sidebar.tsx +178 -0
  89. package/template/host/src/layout/Topbar.tsx +50 -0
  90. package/template/host/src/layout/index.ts +4 -0
  91. package/template/host/src/lib/pocketbase/client.ts +38 -0
  92. package/template/host/src/lib/pocketbase/collections/audit_logs.ts +87 -0
  93. package/template/host/src/lib/pocketbase/collections/index.ts +19 -0
  94. package/template/host/src/lib/pocketbase/collections/organizations.ts +63 -0
  95. package/template/host/src/lib/pocketbase/collections/permissions.ts +57 -0
  96. package/template/host/src/lib/pocketbase/collections/roles.ts +55 -0
  97. package/template/host/src/lib/pocketbase/collections/todos.ts +74 -0
  98. package/template/host/src/lib/pocketbase/collections/user_roles.ts +57 -0
  99. package/template/host/src/lib/pocketbase/collections/users.ts +43 -0
  100. package/template/host/src/lib/pocketbase/index.ts +5 -0
  101. package/template/host/src/lib/pocketbase/migrations.ts +44 -0
  102. package/template/host/src/lib/pocketbase/seed/permissions.ts +8 -0
  103. package/template/host/src/lib/pocketbase/seed/roles.ts +22 -0
  104. package/template/host/src/lib/pocketbase/seed.ts +113 -0
  105. package/template/host/src/lib/pocketbase/types.ts +102 -0
  106. package/template/host/src/modern.runtime.ts +26 -0
  107. package/template/host/src/plugins.d.ts +9 -0
  108. package/template/host/src/providers/PocketBaseProvider.tsx +30 -0
  109. package/template/host/src/routes/_.tsx +6 -0
  110. package/template/host/src/routes/dashboard._.tsx +41 -0
  111. package/template/host/src/routes/index.tsx +93 -0
  112. package/template/host/src/routes/login.tsx +36 -0
  113. package/template/host/src/saas.config.ts +52 -0
  114. package/template/host/src/test/setup.ts +65 -0
  115. package/template/host/src/test/utils.tsx +69 -0
  116. package/template/host/src/test/vitest-globals.d.ts +19 -0
  117. package/template/host/src/vite-env.d.ts +16 -0
  118. package/template/host/tailwind.config.ts +77 -0
  119. package/template/host/tsconfig.json +19 -0
  120. package/template/host/vitest.config.ts +30 -0
  121. package/template/package.json +44 -0
  122. package/template/packages/plugins/@lego/plugin-dashboard/modern.config.ts +19 -0
  123. package/template/packages/plugins/@lego/plugin-dashboard/package.json +35 -0
  124. package/template/packages/plugins/@lego/plugin-dashboard/postcss.config.mjs +6 -0
  125. package/template/packages/plugins/@lego/plugin-dashboard/src/App.tsx +27 -0
  126. package/template/packages/plugins/@lego/plugin-dashboard/src/components/ActivityFeed.tsx +63 -0
  127. package/template/packages/plugins/@lego/plugin-dashboard/src/components/QuickActionSlot.tsx +11 -0
  128. package/template/packages/plugins/@lego/plugin-dashboard/src/components/QuickActions.tsx +68 -0
  129. package/template/packages/plugins/@lego/plugin-dashboard/src/components/SidebarWidget.tsx +35 -0
  130. package/template/packages/plugins/@lego/plugin-dashboard/src/components/StatCard.tsx +47 -0
  131. package/template/packages/plugins/@lego/plugin-dashboard/src/global.css +24 -0
  132. package/template/packages/plugins/@lego/plugin-dashboard/src/hooks/useChannelIntegration.ts +43 -0
  133. package/template/packages/plugins/@lego/plugin-dashboard/src/hooks/useDashboardStats.ts +65 -0
  134. package/template/packages/plugins/@lego/plugin-dashboard/src/hooks/usePocketBase.ts +47 -0
  135. package/template/packages/plugins/@lego/plugin-dashboard/src/hooks/useRecentActivity.ts +55 -0
  136. package/template/packages/plugins/@lego/plugin-dashboard/src/lib/utils.ts +6 -0
  137. package/template/packages/plugins/@lego/plugin-dashboard/src/pages/DashboardPage.tsx +105 -0
  138. package/template/packages/plugins/@lego/plugin-dashboard/src/plugin.config.ts +121 -0
  139. package/template/packages/plugins/@lego/plugin-dashboard/src/plugin.ts +18 -0
  140. package/template/packages/plugins/@lego/plugin-dashboard/src/vite-env.d.ts +32 -0
  141. package/template/packages/plugins/@lego/plugin-dashboard/tailwind.config.ts +35 -0
  142. package/template/packages/plugins/@lego/plugin-dashboard/tsconfig.json +18 -0
  143. package/template/packages/plugins/@lego/plugin-todo/modern.config.ts +18 -0
  144. package/template/packages/plugins/@lego/plugin-todo/package.json +41 -0
  145. package/template/packages/plugins/@lego/plugin-todo/postcss.config.mjs +6 -0
  146. package/template/packages/plugins/@lego/plugin-todo/src/App.tsx +12 -0
  147. package/template/packages/plugins/@lego/plugin-todo/src/components/SidebarWidget.tsx +16 -0
  148. package/template/packages/plugins/@lego/plugin-todo/src/components/TodoDialog.tsx +55 -0
  149. package/template/packages/plugins/@lego/plugin-todo/src/components/TodoFilters.tsx +79 -0
  150. package/template/packages/plugins/@lego/plugin-todo/src/components/TodoForm.tsx +94 -0
  151. package/template/packages/plugins/@lego/plugin-todo/src/components/TodoItem.tsx +121 -0
  152. package/template/packages/plugins/@lego/plugin-todo/src/components/TodoList.tsx +41 -0
  153. package/template/packages/plugins/@lego/plugin-todo/src/components/index.ts +6 -0
  154. package/template/packages/plugins/@lego/plugin-todo/src/global.css +59 -0
  155. package/template/packages/plugins/@lego/plugin-todo/src/hooks/useCreateTodo.ts +62 -0
  156. package/template/packages/plugins/@lego/plugin-todo/src/hooks/useDeleteTodo.ts +46 -0
  157. package/template/packages/plugins/@lego/plugin-todo/src/hooks/usePocketBase.ts +38 -0
  158. package/template/packages/plugins/@lego/plugin-todo/src/hooks/useTodos.ts +64 -0
  159. package/template/packages/plugins/@lego/plugin-todo/src/hooks/useUpdateTodo.ts +35 -0
  160. package/template/packages/plugins/@lego/plugin-todo/src/index.tsx +5 -0
  161. package/template/packages/plugins/@lego/plugin-todo/src/lib/utils.ts +20 -0
  162. package/template/packages/plugins/@lego/plugin-todo/src/pages/TodoPage.tsx +89 -0
  163. package/template/packages/plugins/@lego/plugin-todo/src/plugin.config.ts +104 -0
  164. package/template/packages/plugins/@lego/plugin-todo/src/plugin.ts +13 -0
  165. package/template/packages/plugins/@lego/plugin-todo/src/schemas.ts +37 -0
  166. package/template/packages/plugins/@lego/plugin-todo/src/types.ts +42 -0
  167. package/template/packages/plugins/@lego/plugin-todo/src/vite-env.d.ts +31 -0
  168. package/template/packages/plugins/@lego/plugin-todo/tailwind.config.ts +51 -0
  169. package/template/packages/plugins/@lego/plugin-todo/tsconfig.json +18 -0
  170. package/template/pnpm-workspace.yaml +4 -0
  171. 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,3 @@
1
+ export * from './types';
2
+ export * from './store';
3
+ export * from './bridge';
@@ -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,4 @@
1
+ export * from './Shell';
2
+ export * from './Sidebar';
3
+ export * from './Topbar';
4
+ export * from './MobileMenu';
@@ -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
+ }