create-lego-one 2.0.9 → 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,102 @@
1
+ // PocketBase field types
2
+ export interface BoolField {
3
+ name: string;
4
+ type: 'bool';
5
+ required?: boolean;
6
+ default?: boolean;
7
+ }
8
+
9
+ export interface NumberField {
10
+ name: string;
11
+ type: 'number';
12
+ required?: boolean;
13
+ min?: number;
14
+ max?: number;
15
+ default?: number;
16
+ }
17
+
18
+ export interface TextField {
19
+ name: string;
20
+ type: 'text';
21
+ required?: boolean;
22
+ min?: number;
23
+ max?: number;
24
+ default?: string;
25
+ pattern?: string;
26
+ unique?: boolean;
27
+ }
28
+
29
+ export interface EmailField {
30
+ name: string;
31
+ type: 'email';
32
+ required?: boolean;
33
+ }
34
+
35
+ export interface UrlField {
36
+ name: string;
37
+ type: 'url';
38
+ required?: boolean;
39
+ }
40
+
41
+ export interface DateField {
42
+ name: string;
43
+ type: 'date';
44
+ required?: boolean;
45
+ }
46
+
47
+ export interface AutodateField {
48
+ name: string;
49
+ type: 'autodate';
50
+ required?: boolean;
51
+ }
52
+
53
+ export interface SelectField {
54
+ name: string;
55
+ type: 'select';
56
+ required?: boolean;
57
+ values: string[];
58
+ default?: string;
59
+ }
60
+
61
+ export interface JsonField {
62
+ name: string;
63
+ type: 'json';
64
+ required?: boolean;
65
+ }
66
+
67
+ export interface RelationField {
68
+ name: string;
69
+ type: 'relation';
70
+ required?: boolean;
71
+ maxSelect?: number;
72
+ collectionId: string;
73
+ cascadeDelete?: boolean;
74
+ }
75
+
76
+ export type Field =
77
+ | BoolField
78
+ | NumberField
79
+ | TextField
80
+ | EmailField
81
+ | UrlField
82
+ | DateField
83
+ | AutodateField
84
+ | SelectField
85
+ | JsonField
86
+ | RelationField;
87
+
88
+ // Collection definition
89
+ export interface Collection {
90
+ type: 'base' | 'auth' | 'view';
91
+ name: string;
92
+ listRule?: string | null;
93
+ viewRule?: string | null;
94
+ createRule?: string | null;
95
+ updateRule?: string | null;
96
+ deleteRule?: string | null;
97
+ fields: Field[];
98
+ indexes?: string[];
99
+ }
100
+
101
+ // Migration function type
102
+ export type Migration = () => Collection | Collection[];
@@ -0,0 +1,26 @@
1
+ import { defineRuntimeConfig } from '@modern-js/runtime';
2
+
3
+ // Development vs Production configuration
4
+ const isDev = import.meta.env.MODE === 'development';
5
+
6
+ export default defineRuntimeConfig({
7
+ masterApp: {
8
+ apps: [
9
+ {
10
+ name: '@lego/plugin-dashboard',
11
+ // Dev: Separate server │ Prod: Bundled via dynamic import
12
+ entry: isDev
13
+ ? 'http://localhost:3001'
14
+ : () => import('@lego/plugin-dashboard'),
15
+ activeWhen: '/dashboard',
16
+ },
17
+ {
18
+ name: '@lego/plugin-todo',
19
+ entry: isDev
20
+ ? 'http://localhost:3002'
21
+ : () => import('@lego/plugin-todo'),
22
+ activeWhen: '/todos',
23
+ },
24
+ ],
25
+ },
26
+ });
@@ -0,0 +1,9 @@
1
+ declare module '@lego/plugin-dashboard' {
2
+ const App: React.ComponentType;
3
+ export { App };
4
+ }
5
+
6
+ declare module '@lego/plugin-todo' {
7
+ const App: React.ComponentType;
8
+ export { App };
9
+ }
@@ -0,0 +1,30 @@
1
+ import { createContext, useContext, ReactNode } from 'react';
2
+ import { getPocketBase, getPocketBaseAdmin, resetPocketBase } from '../lib/pocketbase';
3
+ import type PocketBase from 'pocketbase';
4
+
5
+ const PocketBaseContext = createContext<PocketBase | null>(null);
6
+
7
+ export function PocketBaseProvider({ children }: { children: ReactNode }) {
8
+ const pb = getPocketBase();
9
+
10
+ return (
11
+ <PocketBaseContext.Provider value={pb}>{children}</PocketBaseContext.Provider>
12
+ );
13
+ }
14
+
15
+ export function usePocketBase(): PocketBase {
16
+ const pb = useContext(PocketBaseContext);
17
+
18
+ if (!pb) {
19
+ throw new Error('usePocketBase must be used within PocketBaseProvider');
20
+ }
21
+
22
+ return pb;
23
+ }
24
+
25
+ export function usePocketBaseAdmin() {
26
+ return getPocketBaseAdmin();
27
+ }
28
+
29
+ // For testing
30
+ export { resetPocketBase as resetPocketBaseForTesting };
@@ -0,0 +1,6 @@
1
+ import { Outlet } from '@modern-js/runtime/router';
2
+ import { Shell } from '../layout/Shell';
3
+
4
+ export default function PluginWrapper() {
5
+ return <Shell><Outlet /></Shell>;
6
+ }
@@ -0,0 +1,41 @@
1
+ import { useGlobalKernelState } from '../kernel/shared-state';
2
+ import { Skeleton } from '../kernel/components/ui/skeleton';
3
+
4
+ export default function DashboardPluginRoute() {
5
+ const { isAuthenticated, isLoading } = useGlobalKernelState();
6
+
7
+ if (isLoading) {
8
+ return (
9
+ <div className="space-y-6">
10
+ <Skeleton className="h-8 w-64" />
11
+ <div className="grid gap-6 md:grid-cols-3">
12
+ <Skeleton className="h-32" />
13
+ <Skeleton className="h-32" />
14
+ <Skeleton className="h-32" />
15
+ </div>
16
+ <Skeleton className="h-64" />
17
+ </div>
18
+ );
19
+ }
20
+
21
+ if (!isAuthenticated) {
22
+ return (
23
+ <div className="text-center">
24
+ <h1 className="text-2xl font-bold">Authentication Required</h1>
25
+ <p className="mt-2 text-muted-foreground">
26
+ Please sign in to access the dashboard.
27
+ </p>
28
+ </div>
29
+ );
30
+ }
31
+
32
+ // Placeholder - the actual plugin will be rendered by Garfish
33
+ return (
34
+ <div className="text-center">
35
+ <h1 className="text-2xl font-bold">Dashboard</h1>
36
+ <p className="mt-2 text-muted-foreground">
37
+ The Dashboard plugin is not yet implemented.
38
+ </p>
39
+ </div>
40
+ );
41
+ }
@@ -0,0 +1,93 @@
1
+ import { Link } from '@modern-js/runtime/router';
2
+ import { ArrowRight, LayoutDashboard, CheckSquare, Shield } from 'lucide-react';
3
+ import { useGlobalKernelState } from '../kernel/shared-state';
4
+
5
+ export default function HomePage() {
6
+ const { isAuthenticated, user } = useGlobalKernelState();
7
+
8
+ return (
9
+ <div className="mx-auto max-w-4xl">
10
+ <div className="space-y-6">
11
+ {/* Hero */}
12
+ <div className="rounded-xl border bg-card p-8 text-center">
13
+ <h1 className="text-4xl font-bold tracking-tight">Welcome to Lego-One</h1>
14
+ <p className="mt-4 text-lg text-muted-foreground">
15
+ A modular SaaS boilerplate with microkernel architecture
16
+ </p>
17
+
18
+ {isAuthenticated ? (
19
+ <div className="mt-8">
20
+ <p className="text-muted-foreground">
21
+ Welcome back, {user?.name || 'User'}!
22
+ </p>
23
+ </div>
24
+ ) : (
25
+ <div className="mt-8 flex justify-center gap-4">
26
+ <Link
27
+ to="/login"
28
+ className="inline-flex items-center gap-2 rounded-lg bg-primary px-6 py-3 font-medium text-primary-foreground hover:bg-primary/90"
29
+ >
30
+ Sign In
31
+ <ArrowRight className="h-4 w-4" />
32
+ </Link>
33
+ </div>
34
+ )}
35
+ </div>
36
+
37
+ {/* Features */}
38
+ <div className="grid gap-6 md:grid-cols-3">
39
+ <div className="rounded-xl border bg-card p-6">
40
+ <div className="flex h-12 w-12 items-center justify-center rounded-lg bg-primary/10 text-primary">
41
+ <LayoutDashboard className="h-6 w-6" />
42
+ </div>
43
+ <h3 className="mt-4 text-lg font-semibold">Dashboard Plugin</h3>
44
+ <p className="mt-2 text-sm text-muted-foreground">
45
+ Overview of your account with stats and quick actions
46
+ </p>
47
+ <Link
48
+ to="/dashboard"
49
+ className="mt-4 inline-flex items-center text-sm font-medium text-primary hover:underline"
50
+ >
51
+ View Dashboard
52
+ <ArrowRight className="ml-1 h-4 w-4" />
53
+ </Link>
54
+ </div>
55
+
56
+ <div className="rounded-xl border bg-card p-6">
57
+ <div className="flex h-12 w-12 items-center justify-center rounded-lg bg-primary/10 text-primary">
58
+ <CheckSquare className="h-6 w-6" />
59
+ </div>
60
+ <h3 className="mt-4 text-lg font-semibold">Todo Plugin</h3>
61
+ <p className="mt-2 text-sm text-muted-foreground">
62
+ Manage your tasks with this example plugin
63
+ </p>
64
+ <Link
65
+ to="/todos"
66
+ className="mt-4 inline-flex items-center text-sm font-medium text-primary hover:underline"
67
+ >
68
+ View Todos
69
+ <ArrowRight className="ml-1 h-4 w-4" />
70
+ </Link>
71
+ </div>
72
+
73
+ <div className="rounded-xl border bg-card p-6">
74
+ <div className="flex h-12 w-12 items-center justify-center rounded-lg bg-primary/10 text-primary">
75
+ <Shield className="h-6 w-6" />
76
+ </div>
77
+ <h3 className="mt-4 text-lg font-semibold">Multi-Tenancy</h3>
78
+ <p className="mt-2 text-sm text-muted-foreground">
79
+ Built-in support for organizations and RBAC
80
+ </p>
81
+ <Link
82
+ to="/settings"
83
+ className="mt-4 inline-flex items-center text-sm font-medium text-primary hover:underline"
84
+ >
85
+ Manage Settings
86
+ <ArrowRight className="ml-1 h-4 w-4" />
87
+ </Link>
88
+ </div>
89
+ </div>
90
+ </div>
91
+ </div>
92
+ );
93
+ }
@@ -0,0 +1,36 @@
1
+ import { Navigate } from '@modern-js/runtime/router';
2
+ import { LoginForm } from '../kernel/auth/components/LoginForm';
3
+ import { useAuth } from '../kernel/auth/hooks';
4
+
5
+ export default function LoginPage() {
6
+ const { isAuthenticated } = useAuth();
7
+
8
+ if (isAuthenticated) {
9
+ return <Navigate to="/dashboard" replace />;
10
+ }
11
+
12
+ return (
13
+ <div className="flex min-h-[calc(100vh-4rem)] items-center justify-center p-6">
14
+ <div className="w-full max-w-md space-y-6">
15
+ {/* Logo and branding */}
16
+ <div className="text-center">
17
+ <div className="mx-auto flex h-12 w-12 items-center justify-center rounded-lg bg-primary text-primary-foreground text-xl font-bold">
18
+ L
19
+ </div>
20
+ <h1 className="mt-4 text-2xl font-bold">Lego-One</h1>
21
+ <p className="mt-2 text-sm text-muted-foreground">
22
+ Sign in to access your account
23
+ </p>
24
+ </div>
25
+
26
+ {/* Login form */}
27
+ <LoginForm />
28
+
29
+ {/* Help text */}
30
+ <div className="text-center text-sm text-muted-foreground">
31
+ <p>Default credentials: admin@example.com / admin123</p>
32
+ </div>
33
+ </div>
34
+ </div>
35
+ );
36
+ }
@@ -0,0 +1,52 @@
1
+ import { z } from 'zod';
2
+ import { pluginConfigSchema } from './kernel/plugins/schemas';
3
+ import type { PluginConfig } from './kernel/plugins/types';
4
+
5
+ /**
6
+ * SaaS configuration file
7
+ *
8
+ * This file controls which plugins are enabled and their configuration.
9
+ * Admin can override this via UI (stored in PocketBase).
10
+ */
11
+
12
+ const saasConfigSchema = z.object({
13
+ plugins: z.array(z.object({
14
+ name: z.string(),
15
+ enabled: z.boolean(),
16
+ })),
17
+ settings: z.record(z.any()).optional(),
18
+ });
19
+
20
+ export type SaaSConfig = z.infer<typeof saasConfigSchema>;
21
+
22
+ /**
23
+ * Default SaaS configuration
24
+ * Modify this to enable/disable plugins by default
25
+ */
26
+ export const saasConfig: SaaSConfig = {
27
+ plugins: [
28
+ {
29
+ name: '@lego/plugin-dashboard',
30
+ enabled: true,
31
+ },
32
+ {
33
+ name: '@lego/plugin-todo',
34
+ enabled: true,
35
+ },
36
+ ],
37
+ settings: {
38
+ // Global settings
39
+ allowPublicSignup: false,
40
+ defaultOrganizationRequired: true,
41
+ },
42
+ };
43
+
44
+ /**
45
+ * Validate config on load
46
+ */
47
+ export function validateConfig(config: unknown): SaaSConfig {
48
+ return saasConfigSchema.parse(config);
49
+ }
50
+
51
+ // Export for type safety
52
+ export default saasConfig;
@@ -0,0 +1,65 @@
1
+ import '@testing-library/jest-dom';
2
+ import { cleanup } from '@testing-library/react';
3
+ import { afterEach, vi } from 'vitest';
4
+
5
+ // Cleanup after each test
6
+ afterEach(() => {
7
+ cleanup();
8
+ });
9
+
10
+ // Mock PocketBase
11
+ vi.mock('pocketbase', () => ({
12
+ default: vi.fn().mockImplementation(() => ({
13
+ collection: vi.fn(),
14
+ send: vi.fn(),
15
+ records: {
16
+ subscribe: vi.fn(),
17
+ },
18
+ })),
19
+ }));
20
+
21
+ // Mock window bridge
22
+ global.window.__LEGO_KERNEL_STATE__ = {
23
+ user: null,
24
+ organization: null,
25
+ isAuthenticated: false,
26
+ };
27
+
28
+ global.window.__LEGO_CHANNEL_BUS__ = {
29
+ publish: vi.fn(),
30
+ subscribe: vi.fn(),
31
+ };
32
+
33
+ // Mock IntersectionObserver
34
+ global.IntersectionObserver = class IntersectionObserver {
35
+ constructor() {}
36
+ disconnect() {}
37
+ observe() {}
38
+ takeRecords() {
39
+ return [];
40
+ }
41
+ unobserve() {}
42
+ } as any;
43
+
44
+ // Mock ResizeObserver
45
+ global.ResizeObserver = class ResizeObserver {
46
+ constructor() {}
47
+ disconnect() {}
48
+ observe() {}
49
+ unobserve() {}
50
+ } as any;
51
+
52
+ // Mock matchMedia
53
+ Object.defineProperty(window, 'matchMedia', {
54
+ writable: true,
55
+ value: vi.fn().mockImplementation((query) => ({
56
+ matches: false,
57
+ media: query,
58
+ onchange: null,
59
+ addListener: vi.fn(),
60
+ removeListener: vi.fn(),
61
+ addEventListener: vi.fn(),
62
+ removeEventListener: vi.fn(),
63
+ dispatchEvent: vi.fn(),
64
+ })),
65
+ });
@@ -0,0 +1,69 @@
1
+ import { ReactElement } from 'react';
2
+ import { render, RenderOptions } from '@testing-library/react';
3
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
4
+ import { BrowserRouter } from '@modern-js/runtime/router';
5
+
6
+ // Test wrappers
7
+ const createTestQueryClient = () =>
8
+ new QueryClient({
9
+ defaultOptions: {
10
+ queries: {
11
+ retry: false,
12
+ },
13
+ mutations: {
14
+ retry: false,
15
+ },
16
+ },
17
+ });
18
+
19
+ interface AllTheProvidersProps {
20
+ children: React.ReactNode;
21
+ }
22
+
23
+ function AllTheProviders({ children }: AllTheProvidersProps) {
24
+ const queryClient = createTestQueryClient();
25
+ return (
26
+ <QueryClientProvider client={queryClient}>
27
+ <BrowserRouter>{children}</BrowserRouter>
28
+ </QueryClientProvider>
29
+ );
30
+ }
31
+
32
+ function customRender(ui: ReactElement, options?: Omit<RenderOptions, 'wrapper'>) {
33
+ return render(ui, { wrapper: AllTheProviders, ...options });
34
+ }
35
+
36
+ // Mock data generators
37
+ export const mockUser = {
38
+ id: 'test-user-id',
39
+ email: 'test@example.com',
40
+ name: 'Test User',
41
+ };
42
+
43
+ export const mockOrganization = {
44
+ id: 'test-org-id',
45
+ name: 'Test Organization',
46
+ slug: 'test-org',
47
+ };
48
+
49
+ export const mockAuthState = {
50
+ user: mockUser,
51
+ organization: mockOrganization,
52
+ isAuthenticated: true,
53
+ };
54
+
55
+ export function setupMockState() {
56
+ global.window.__LEGO_KERNEL_STATE__ = mockAuthState;
57
+ }
58
+
59
+ export function clearMockState() {
60
+ global.window.__LEGO_KERNEL_STATE__ = {
61
+ user: null,
62
+ organization: null,
63
+ isAuthenticated: false,
64
+ };
65
+ }
66
+
67
+ // Re-export everything from RTL
68
+ export * from '@testing-library/react';
69
+ export { customRender as render };
@@ -0,0 +1,19 @@
1
+ /// <reference types="vitest/globals" />
2
+
3
+ import type { User, Organization } from '../kernel/shared-state/types';
4
+
5
+ declare global {
6
+ interface Window {
7
+ __LEGO_KERNEL_STATE__?: {
8
+ user?: User | null;
9
+ organization?: Organization | null;
10
+ isAuthenticated: boolean;
11
+ };
12
+ __LEGO_CHANNEL_BUS__?: {
13
+ publish: (channel: string, message: any) => void;
14
+ subscribe: (channel: string, callback: (data: any) => void) => () => void;
15
+ };
16
+ }
17
+ }
18
+
19
+ export {};
@@ -0,0 +1,16 @@
1
+ /// <reference types="vite/client" />
2
+
3
+ interface ImportMetaEnv {
4
+ readonly VITE_POCKETBASE_URL: string;
5
+ readonly VITE_POCKETBASE_ADMIN_EMAIL: string;
6
+ readonly VITE_POCKETBASE_ADMIN_PASSWORD: string;
7
+ readonly VITE_SEED_ADMIN_EMAIL: string;
8
+ readonly VITE_SEED_ADMIN_PASSWORD: string;
9
+ readonly VITE_SEED_ADMIN_NAME: string;
10
+ readonly VITE_SEED_ORG_NAME: string;
11
+ readonly MODE: string;
12
+ }
13
+
14
+ interface ImportMeta {
15
+ readonly env: ImportMetaEnv;
16
+ }
@@ -0,0 +1,77 @@
1
+ import type { Config } from 'tailwindcss';
2
+
3
+ const config: Config = {
4
+ darkMode: ['class'],
5
+ content: [
6
+ './src/**/*.{js,ts,jsx,tsx,mdx}',
7
+ '../../packages/plugins/@lego/*/src/**/*.{js,ts,jsx,tsx,mdx}',
8
+ ],
9
+ theme: {
10
+ container: {
11
+ center: true,
12
+ padding: '2rem',
13
+ screens: {
14
+ '2xl': '1400px',
15
+ },
16
+ },
17
+ extend: {
18
+ colors: {
19
+ border: 'hsl(var(--border))',
20
+ input: 'hsl(var(--input))',
21
+ ring: 'hsl(var(--ring))',
22
+ background: 'hsl(var(--background))',
23
+ foreground: 'hsl(var(--foreground))',
24
+ primary: {
25
+ DEFAULT: 'hsl(var(--primary))',
26
+ foreground: 'hsl(var(--primary-foreground))',
27
+ },
28
+ secondary: {
29
+ DEFAULT: 'hsl(var(--secondary))',
30
+ foreground: 'hsl(var(--secondary-foreground))',
31
+ },
32
+ destructive: {
33
+ DEFAULT: 'hsl(var(--destructive))',
34
+ foreground: 'hsl(var(--destructive-foreground))',
35
+ },
36
+ muted: {
37
+ DEFAULT: 'hsl(var(--muted))',
38
+ foreground: 'hsl(var(--muted-foreground))',
39
+ },
40
+ accent: {
41
+ DEFAULT: 'hsl(var(--accent))',
42
+ foreground: 'hsl(var(--accent-foreground))',
43
+ },
44
+ popover: {
45
+ DEFAULT: 'hsl(var(--popover))',
46
+ foreground: 'hsl(var(--popover-foreground))',
47
+ },
48
+ card: {
49
+ DEFAULT: 'hsl(var(--card))',
50
+ foreground: 'hsl(var(--card-foreground))',
51
+ },
52
+ },
53
+ borderRadius: {
54
+ lg: 'var(--radius)',
55
+ md: 'calc(var(--radius) - 2px)',
56
+ sm: 'calc(var(--radius) - 4px)',
57
+ },
58
+ keyframes: {
59
+ 'accordion-down': {
60
+ from: { height: '0' },
61
+ to: { height: 'var(--radix-accordion-content-height)' },
62
+ },
63
+ 'accordion-up': {
64
+ from: { height: 'var(--radix-accordion-content-height)' },
65
+ to: { height: '0' },
66
+ },
67
+ },
68
+ animation: {
69
+ 'accordion-down': 'accordion-down 0.2s ease-out',
70
+ 'accordion-up': 'accordion-up 0.2s ease-out',
71
+ },
72
+ },
73
+ },
74
+ plugins: [require('tailwindcss-animate')],
75
+ };
76
+
77
+ export default config;
@@ -0,0 +1,19 @@
1
+ {
2
+ "extends": "@modern-js/tsconfig/base.json",
3
+ "compilerOptions": {
4
+ "jsx": "react-jsx",
5
+ "strict": true,
6
+ "esModuleInterop": true,
7
+ "skipLibCheck": true,
8
+ "module": "esnext",
9
+ "moduleResolution": "bundler",
10
+ "resolveJsonModule": true,
11
+ "isolatedModules": true,
12
+ "paths": {
13
+ "@/*": ["./src/*"],
14
+ "@lego/kernel/*": ["./src/kernel/*"]
15
+ }
16
+ },
17
+ "include": ["src"],
18
+ "exclude": ["node_modules", "dist", "**/__tests__", "**/*.test.ts", "**/*.test.tsx", "**/*.spec.ts", "**/*.spec.tsx", "src/test"]
19
+ }