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.
- package/dist/index.cjs +145 -0
- package/dist/index.cjs.map +1 -1
- package/package.json +5 -3
- package/template/host/e2e/auth.spec.ts +38 -0
- package/template/host/e2e/layout.spec.ts +38 -0
- package/template/host/modern.config.ts +19 -0
- package/template/host/package.json +71 -0
- package/template/host/playwright.config.ts +34 -0
- package/template/host/postcss.config.mjs +6 -0
- package/template/host/src/App.tsx +6 -0
- package/template/host/src/bootstrap.tsx +74 -0
- package/template/host/src/global.css +59 -0
- package/template/host/src/index.ts +2 -0
- package/template/host/src/kernel/__tests__/lib-utils.test.ts +32 -0
- package/template/host/src/kernel/__tests__/rbac-hooks.test.tsx +114 -0
- package/template/host/src/kernel/__tests__/rbac-utils.test.ts +108 -0
- package/template/host/src/kernel/auth/ProtectedRoute.tsx +41 -0
- package/template/host/src/kernel/auth/components/LoginForm.tsx +97 -0
- package/template/host/src/kernel/auth/components/LogoutButton.tsx +79 -0
- package/template/host/src/kernel/auth/hooks.ts +174 -0
- package/template/host/src/kernel/auth/index.ts +5 -0
- package/template/host/src/kernel/auth/schemas.ts +27 -0
- package/template/host/src/kernel/auth/service.ts +197 -0
- package/template/host/src/kernel/auth/types.ts +36 -0
- package/template/host/src/kernel/channels/ChannelBus.ts +181 -0
- package/template/host/src/kernel/channels/ChannelProvider.tsx +57 -0
- package/template/host/src/kernel/channels/events.ts +27 -0
- package/template/host/src/kernel/channels/hooks.ts +168 -0
- package/template/host/src/kernel/channels/index.ts +6 -0
- package/template/host/src/kernel/channels/integrations/ToastIntegration.tsx +60 -0
- package/template/host/src/kernel/channels/plugin-hooks.ts +72 -0
- package/template/host/src/kernel/channels/types.ts +112 -0
- package/template/host/src/kernel/components/__tests__/Badge.test.tsx +35 -0
- package/template/host/src/kernel/components/__tests__/Button.test.tsx +63 -0
- package/template/host/src/kernel/components/__tests__/Input.test.tsx +64 -0
- package/template/host/src/kernel/components/index.ts +32 -0
- package/template/host/src/kernel/components/ui/alert.tsx +58 -0
- package/template/host/src/kernel/components/ui/avatar.tsx +47 -0
- package/template/host/src/kernel/components/ui/badge.tsx +35 -0
- package/template/host/src/kernel/components/ui/button.tsx +50 -0
- package/template/host/src/kernel/components/ui/card.tsx +78 -0
- package/template/host/src/kernel/components/ui/dialog.tsx +116 -0
- package/template/host/src/kernel/components/ui/dropdown-menu.tsx +192 -0
- package/template/host/src/kernel/components/ui/index.ts +7 -0
- package/template/host/src/kernel/components/ui/input.tsx +24 -0
- package/template/host/src/kernel/components/ui/label.tsx +21 -0
- package/template/host/src/kernel/components/ui/popover.tsx +28 -0
- package/template/host/src/kernel/components/ui/progress.tsx +25 -0
- package/template/host/src/kernel/components/ui/scroll-area.tsx +45 -0
- package/template/host/src/kernel/components/ui/select.tsx +155 -0
- package/template/host/src/kernel/components/ui/separator.tsx +28 -0
- package/template/host/src/kernel/components/ui/skeleton.tsx +15 -0
- package/template/host/src/kernel/components/ui/switch.tsx +26 -0
- package/template/host/src/kernel/components/ui/table.tsx +116 -0
- package/template/host/src/kernel/components/ui/tabs.tsx +52 -0
- package/template/host/src/kernel/components/ui/toast.tsx +126 -0
- package/template/host/src/kernel/components/ui/toaster.tsx +34 -0
- package/template/host/src/kernel/components/ui/tooltip.tsx +27 -0
- package/template/host/src/kernel/components/ui/use-toast.ts +183 -0
- package/template/host/src/kernel/index.ts +48 -0
- package/template/host/src/kernel/lib/cn.ts +1 -0
- package/template/host/src/kernel/lib/utils.ts +36 -0
- package/template/host/src/kernel/plugins/Slot.tsx +41 -0
- package/template/host/src/kernel/plugins/SlotProvider.tsx +88 -0
- package/template/host/src/kernel/plugins/index.ts +23 -0
- package/template/host/src/kernel/plugins/loader.ts +122 -0
- package/template/host/src/kernel/plugins/schemas.ts +54 -0
- package/template/host/src/kernel/plugins/store.ts +185 -0
- package/template/host/src/kernel/plugins/types.ts +103 -0
- package/template/host/src/kernel/providers/PocketBaseProvider.tsx +70 -0
- package/template/host/src/kernel/providers/QueryProvider.tsx +28 -0
- package/template/host/src/kernel/providers/ThemeProvider.tsx +25 -0
- package/template/host/src/kernel/providers/index.ts +3 -0
- package/template/host/src/kernel/rbac/components/OrganizationSelector.tsx +69 -0
- package/template/host/src/kernel/rbac/components/PermissionGate.tsx +43 -0
- package/template/host/src/kernel/rbac/hooks.ts +379 -0
- package/template/host/src/kernel/rbac/index.ts +6 -0
- package/template/host/src/kernel/rbac/service.ts +504 -0
- package/template/host/src/kernel/rbac/types.ts +164 -0
- package/template/host/src/kernel/rbac/utils.ts +34 -0
- package/template/host/src/kernel/shared-state/bridge.ts +31 -0
- package/template/host/src/kernel/shared-state/index.ts +3 -0
- package/template/host/src/kernel/shared-state/store.ts +62 -0
- package/template/host/src/kernel/shared-state/types.ts +60 -0
- package/template/host/src/kernel/use-migrations.ts +72 -0
- package/template/host/src/layout/MobileMenu.tsx +61 -0
- package/template/host/src/layout/Shell.tsx +42 -0
- package/template/host/src/layout/Sidebar.tsx +178 -0
- package/template/host/src/layout/Topbar.tsx +50 -0
- package/template/host/src/layout/index.ts +4 -0
- package/template/host/src/lib/pocketbase/client.ts +38 -0
- package/template/host/src/lib/pocketbase/collections/audit_logs.ts +87 -0
- package/template/host/src/lib/pocketbase/collections/index.ts +19 -0
- package/template/host/src/lib/pocketbase/collections/organizations.ts +63 -0
- package/template/host/src/lib/pocketbase/collections/permissions.ts +57 -0
- package/template/host/src/lib/pocketbase/collections/roles.ts +55 -0
- package/template/host/src/lib/pocketbase/collections/todos.ts +74 -0
- package/template/host/src/lib/pocketbase/collections/user_roles.ts +57 -0
- package/template/host/src/lib/pocketbase/collections/users.ts +43 -0
- package/template/host/src/lib/pocketbase/index.ts +5 -0
- package/template/host/src/lib/pocketbase/migrations.ts +44 -0
- package/template/host/src/lib/pocketbase/seed/permissions.ts +8 -0
- package/template/host/src/lib/pocketbase/seed/roles.ts +22 -0
- package/template/host/src/lib/pocketbase/seed.ts +113 -0
- package/template/host/src/lib/pocketbase/types.ts +102 -0
- package/template/host/src/modern.runtime.ts +26 -0
- package/template/host/src/plugins.d.ts +9 -0
- package/template/host/src/providers/PocketBaseProvider.tsx +30 -0
- package/template/host/src/routes/_.tsx +6 -0
- package/template/host/src/routes/dashboard._.tsx +41 -0
- package/template/host/src/routes/index.tsx +93 -0
- package/template/host/src/routes/login.tsx +36 -0
- package/template/host/src/saas.config.ts +52 -0
- package/template/host/src/test/setup.ts +65 -0
- package/template/host/src/test/utils.tsx +69 -0
- package/template/host/src/test/vitest-globals.d.ts +19 -0
- package/template/host/src/vite-env.d.ts +16 -0
- package/template/host/tailwind.config.ts +77 -0
- package/template/host/tsconfig.json +19 -0
- package/template/host/vitest.config.ts +30 -0
- package/template/package.json +44 -0
- package/template/packages/plugins/@lego/plugin-dashboard/modern.config.ts +19 -0
- package/template/packages/plugins/@lego/plugin-dashboard/package.json +35 -0
- package/template/packages/plugins/@lego/plugin-dashboard/postcss.config.mjs +6 -0
- package/template/packages/plugins/@lego/plugin-dashboard/src/App.tsx +27 -0
- package/template/packages/plugins/@lego/plugin-dashboard/src/components/ActivityFeed.tsx +63 -0
- package/template/packages/plugins/@lego/plugin-dashboard/src/components/QuickActionSlot.tsx +11 -0
- package/template/packages/plugins/@lego/plugin-dashboard/src/components/QuickActions.tsx +68 -0
- package/template/packages/plugins/@lego/plugin-dashboard/src/components/SidebarWidget.tsx +35 -0
- package/template/packages/plugins/@lego/plugin-dashboard/src/components/StatCard.tsx +47 -0
- package/template/packages/plugins/@lego/plugin-dashboard/src/global.css +24 -0
- package/template/packages/plugins/@lego/plugin-dashboard/src/hooks/useChannelIntegration.ts +43 -0
- package/template/packages/plugins/@lego/plugin-dashboard/src/hooks/useDashboardStats.ts +65 -0
- package/template/packages/plugins/@lego/plugin-dashboard/src/hooks/usePocketBase.ts +47 -0
- package/template/packages/plugins/@lego/plugin-dashboard/src/hooks/useRecentActivity.ts +55 -0
- package/template/packages/plugins/@lego/plugin-dashboard/src/lib/utils.ts +6 -0
- package/template/packages/plugins/@lego/plugin-dashboard/src/pages/DashboardPage.tsx +105 -0
- package/template/packages/plugins/@lego/plugin-dashboard/src/plugin.config.ts +121 -0
- package/template/packages/plugins/@lego/plugin-dashboard/src/plugin.ts +18 -0
- package/template/packages/plugins/@lego/plugin-dashboard/src/vite-env.d.ts +32 -0
- package/template/packages/plugins/@lego/plugin-dashboard/tailwind.config.ts +35 -0
- package/template/packages/plugins/@lego/plugin-dashboard/tsconfig.json +18 -0
- package/template/packages/plugins/@lego/plugin-todo/modern.config.ts +18 -0
- package/template/packages/plugins/@lego/plugin-todo/package.json +41 -0
- package/template/packages/plugins/@lego/plugin-todo/postcss.config.mjs +6 -0
- package/template/packages/plugins/@lego/plugin-todo/src/App.tsx +12 -0
- package/template/packages/plugins/@lego/plugin-todo/src/components/SidebarWidget.tsx +16 -0
- package/template/packages/plugins/@lego/plugin-todo/src/components/TodoDialog.tsx +55 -0
- package/template/packages/plugins/@lego/plugin-todo/src/components/TodoFilters.tsx +79 -0
- package/template/packages/plugins/@lego/plugin-todo/src/components/TodoForm.tsx +94 -0
- package/template/packages/plugins/@lego/plugin-todo/src/components/TodoItem.tsx +121 -0
- package/template/packages/plugins/@lego/plugin-todo/src/components/TodoList.tsx +41 -0
- package/template/packages/plugins/@lego/plugin-todo/src/components/index.ts +6 -0
- package/template/packages/plugins/@lego/plugin-todo/src/global.css +59 -0
- package/template/packages/plugins/@lego/plugin-todo/src/hooks/useCreateTodo.ts +62 -0
- package/template/packages/plugins/@lego/plugin-todo/src/hooks/useDeleteTodo.ts +46 -0
- package/template/packages/plugins/@lego/plugin-todo/src/hooks/usePocketBase.ts +38 -0
- package/template/packages/plugins/@lego/plugin-todo/src/hooks/useTodos.ts +64 -0
- package/template/packages/plugins/@lego/plugin-todo/src/hooks/useUpdateTodo.ts +35 -0
- package/template/packages/plugins/@lego/plugin-todo/src/index.tsx +5 -0
- package/template/packages/plugins/@lego/plugin-todo/src/lib/utils.ts +20 -0
- package/template/packages/plugins/@lego/plugin-todo/src/pages/TodoPage.tsx +89 -0
- package/template/packages/plugins/@lego/plugin-todo/src/plugin.config.ts +104 -0
- package/template/packages/plugins/@lego/plugin-todo/src/plugin.ts +13 -0
- package/template/packages/plugins/@lego/plugin-todo/src/schemas.ts +37 -0
- package/template/packages/plugins/@lego/plugin-todo/src/types.ts +42 -0
- package/template/packages/plugins/@lego/plugin-todo/src/vite-env.d.ts +31 -0
- package/template/packages/plugins/@lego/plugin-todo/tailwind.config.ts +51 -0
- package/template/packages/plugins/@lego/plugin-todo/tsconfig.json +18 -0
- package/template/pnpm-workspace.yaml +4 -0
- package/template/tsconfig.json +8 -0
|
@@ -0,0 +1,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,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,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
|
+
}
|