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,185 @@
1
+ import { create } from 'zustand';
2
+ import { devtools, persist } from 'zustand/middleware';
3
+ import type { PluginState, PluginConfig } from './types';
4
+ import { saasConfig } from '../../saas.config';
5
+
6
+ interface PluginStore {
7
+ // State
8
+ plugins: Record<string, PluginState>;
9
+ isLoading: boolean;
10
+
11
+ // Actions
12
+ registerPlugin: (config: PluginConfig) => void;
13
+ unregisterPlugin: (name: string) => void;
14
+ enablePlugin: (name: string) => void;
15
+ disablePlugin: (name: string) => void;
16
+ updatePluginSettings: (name: string, settings: Record<string, unknown>) => void;
17
+ setPluginLoaded: (name: string, loaded: boolean) => void;
18
+ setPluginError: (name: string, error: string) => void;
19
+ initializeFromConfig: () => void;
20
+ }
21
+
22
+ /**
23
+ * Get default plugin settings from manifest
24
+ */
25
+ function getDefaultSettings(config: PluginConfig): Record<string, unknown> {
26
+ const settings: Record<string, unknown> = {};
27
+
28
+ if (config.settings) {
29
+ for (const setting of config.settings) {
30
+ if (setting.defaultValue !== undefined) {
31
+ settings[setting.key] = setting.defaultValue;
32
+ }
33
+ }
34
+ }
35
+
36
+ return settings;
37
+ }
38
+
39
+ /**
40
+ * Create plugin store
41
+ */
42
+ export const usePluginStore = create<PluginStore>()(
43
+ devtools(
44
+ persist(
45
+ (set, get) => ({
46
+ plugins: {},
47
+ isLoading: false,
48
+
49
+ registerPlugin: (config) => {
50
+ const { name } = config.manifest;
51
+ const { enabled } = config;
52
+
53
+ set((state) => ({
54
+ plugins: {
55
+ ...state.plugins,
56
+ [name]: {
57
+ id: name,
58
+ manifest: config.manifest,
59
+ enabled,
60
+ loaded: false,
61
+ settings: getDefaultSettings(config),
62
+ },
63
+ },
64
+ }));
65
+ },
66
+
67
+ unregisterPlugin: (name) => {
68
+ set((state) => {
69
+ const plugins = { ...state.plugins };
70
+ delete plugins[name];
71
+ return { plugins };
72
+ });
73
+ },
74
+
75
+ enablePlugin: (name) => {
76
+ set((state) => ({
77
+ plugins: {
78
+ ...state.plugins,
79
+ [name]: {
80
+ ...state.plugins[name],
81
+ enabled: true,
82
+ },
83
+ },
84
+ }));
85
+ },
86
+
87
+ disablePlugin: (name) => {
88
+ set((state) => ({
89
+ plugins: {
90
+ ...state.plugins,
91
+ [name]: {
92
+ ...state.plugins[name],
93
+ enabled: false,
94
+ loaded: false,
95
+ },
96
+ },
97
+ }));
98
+ },
99
+
100
+ updatePluginSettings: (name, settings) => {
101
+ set((state) => ({
102
+ plugins: {
103
+ ...state.plugins,
104
+ [name]: {
105
+ ...state.plugins[name],
106
+ settings: {
107
+ ...state.plugins[name].settings,
108
+ ...settings,
109
+ },
110
+ },
111
+ },
112
+ }));
113
+ },
114
+
115
+ setPluginLoaded: (name, loaded) => {
116
+ set((state) => ({
117
+ plugins: {
118
+ ...state.plugins,
119
+ [name]: {
120
+ ...state.plugins[name],
121
+ loaded,
122
+ },
123
+ },
124
+ }));
125
+ },
126
+
127
+ setPluginError: (name, error) => {
128
+ set((state) => ({
129
+ plugins: {
130
+ ...state.plugins,
131
+ [name]: {
132
+ ...state.plugins[name],
133
+ error,
134
+ loaded: false,
135
+ },
136
+ },
137
+ }));
138
+ },
139
+
140
+ initializeFromConfig: () => {
141
+ const { plugins } = saasConfig;
142
+
143
+ set((state) => {
144
+ const newPlugins = { ...state.plugins };
145
+
146
+ for (const plugin of plugins) {
147
+ const existing = newPlugins[plugin.name];
148
+
149
+ if (!existing) {
150
+ // Create placeholder for plugin that will be loaded
151
+ newPlugins[plugin.name] = {
152
+ id: plugin.name,
153
+ manifest: {
154
+ name: plugin.name,
155
+ version: '0.0.0',
156
+ displayName: plugin.name,
157
+ description: '',
158
+ },
159
+ enabled: plugin.enabled,
160
+ loaded: false,
161
+ settings: {},
162
+ };
163
+ } else {
164
+ // Update enabled state from config
165
+ newPlugins[plugin.name] = {
166
+ ...existing,
167
+ enabled: plugin.enabled,
168
+ };
169
+ }
170
+ }
171
+
172
+ return { plugins: newPlugins };
173
+ });
174
+ },
175
+ }),
176
+ {
177
+ name: 'lego-plugin-store',
178
+ partialize: (state) => ({
179
+ plugins: state.plugins,
180
+ }),
181
+ }
182
+ ),
183
+ { name: 'LegoPluginStore' }
184
+ )
185
+ );
@@ -0,0 +1,103 @@
1
+ import type { ReactNode } from 'react';
2
+
3
+ /**
4
+ * Slot names where plugins can inject content
5
+ */
6
+ export enum SlotName {
7
+ SIDEBAR_TOP = 'sidebar:top',
8
+ SIDEBAR_NAV = 'sidebar:nav',
9
+ SIDEBAR_BOTTOM = 'sidebar:bottom',
10
+ TOPBAR_LEFT = 'topbar:left',
11
+ TOPBAR_RIGHT = 'topbar:right',
12
+ TOPBAR_CENTER = 'topbar:center',
13
+ SETTINGS_MENU = 'settings:menu',
14
+ DASHBOARD_WIDGETS = 'dashboard:widgets',
15
+ }
16
+
17
+ /**
18
+ * Slot injection configuration
19
+ */
20
+ export interface SlotInjection {
21
+ slot: SlotName;
22
+ component: ReactNode | React.ComponentType;
23
+ order?: number; // Lower = higher priority
24
+ props?: Record<string, unknown>;
25
+ condition?: () => boolean; // Conditional rendering
26
+ }
27
+
28
+ /**
29
+ * Plugin manifest metadata
30
+ */
31
+ export interface PluginManifest {
32
+ name: string; // e.g., '@lego/plugin-dashboard'
33
+ version: string;
34
+ displayName: string;
35
+ description: string;
36
+ author?: string;
37
+ icon?: string;
38
+ permissions?: string[]; // Required permissions
39
+ }
40
+
41
+ /**
42
+ * Plugin configuration
43
+ */
44
+ export interface PluginConfig {
45
+ manifest: PluginManifest;
46
+ enabled: boolean;
47
+ slots: SlotInjection[];
48
+ routes?: RouteConfig[];
49
+ settings?: PluginSetting[];
50
+ }
51
+
52
+ /**
53
+ * Plugin route configuration
54
+ */
55
+ export interface RouteConfig {
56
+ path: string;
57
+ component: React.ComponentType;
58
+ protected?: boolean;
59
+ permissions?: string[];
60
+ }
61
+
62
+ /**
63
+ * Plugin setting for admin UI
64
+ */
65
+ export interface PluginSetting {
66
+ key: string;
67
+ type: 'boolean' | 'string' | 'number' | 'select';
68
+ label: string;
69
+ description?: string;
70
+ defaultValue?: unknown;
71
+ options?: { label: string; value: string }[];
72
+ }
73
+
74
+ /**
75
+ * Plugin state
76
+ */
77
+ export interface PluginState {
78
+ id: string;
79
+ manifest: PluginManifest;
80
+ enabled: boolean;
81
+ loaded: boolean;
82
+ error?: string;
83
+ settings: Record<string, unknown>;
84
+ }
85
+
86
+ /**
87
+ * Plugin registry
88
+ */
89
+ export interface PluginRegistry {
90
+ plugins: Map<string, PluginConfig>;
91
+ enabled: Set<string>;
92
+ slots: Map<SlotName, SlotInjection[]>;
93
+ }
94
+
95
+ /**
96
+ * Plugin info from host config
97
+ */
98
+ export interface HostPluginInfo {
99
+ name: string;
100
+ enabled: boolean;
101
+ entry: string | (() => Promise<unknown>);
102
+ activeWhen: string;
103
+ }
@@ -0,0 +1,70 @@
1
+ import { createContext, useContext, useEffect, useState } from 'react';
2
+ import PocketBase from 'pocketbase';
3
+ import { useGlobalKernelState } from '../shared-state';
4
+ import { initializeFirstOrganization } from '../rbac/utils';
5
+
6
+ const PocketBaseContext = createContext<PocketBase | null>(null);
7
+
8
+ export function PocketBaseProvider({ children }: { children: React.ReactNode }) {
9
+ const [pb, setPb] = useState<PocketBase | null>(null);
10
+ const { token, clearAuth, setOrganization } = useGlobalKernelState();
11
+
12
+ useEffect(() => {
13
+ // Initialize PocketBase client
14
+ const client = new PocketBase(import.meta.env.VITE_POCKETBASE_URL || 'http://127.0.0.1:8090');
15
+
16
+ // Load stored token
17
+ const storedToken = localStorage.getItem('pocketbase_auth');
18
+ if (storedToken) {
19
+ client.authStore.save(storedToken, null);
20
+ }
21
+
22
+ setPb(client);
23
+
24
+ // Set up auth cleanup on token invalidation
25
+ client.authStore.onChange(async (token, model) => {
26
+ if (!token) {
27
+ clearAuth();
28
+ localStorage.removeItem('pocketbase_auth');
29
+ setOrganization(null);
30
+ } else {
31
+ localStorage.setItem('pocketbase_auth', token);
32
+
33
+ // Initialize organization on login
34
+ if (model && !model.organizationId) {
35
+ const org = await initializeFirstOrganization(client, model.id);
36
+ if (org) {
37
+ setOrganization(org);
38
+ }
39
+ }
40
+ }
41
+ });
42
+
43
+ return () => {
44
+ // Cleanup on unmount
45
+ };
46
+ }, [clearAuth, setOrganization]);
47
+
48
+ // Update auth store when token changes in state
49
+ useEffect(() => {
50
+ if (pb && token && pb.authStore.token !== token) {
51
+ pb.authStore.save(token, null);
52
+ }
53
+ }, [pb, token]);
54
+
55
+ if (!pb) {
56
+ return null; // or a loading spinner
57
+ }
58
+
59
+ return (
60
+ <PocketBaseContext.Provider value={pb}>{children}</PocketBaseContext.Provider>
61
+ );
62
+ }
63
+
64
+ export function usePocketBase() {
65
+ const context = useContext(PocketBaseContext);
66
+ if (!context) {
67
+ throw new Error('usePocketBase must be used within PocketBaseProvider');
68
+ }
69
+ return context;
70
+ }
@@ -0,0 +1,28 @@
1
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
2
+ import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
3
+ import { useState, type ReactNode } from 'react';
4
+
5
+ export function QueryProvider({ children }: { children: ReactNode }) {
6
+ const [queryClient] = useState(
7
+ () =>
8
+ new QueryClient({
9
+ defaultOptions: {
10
+ queries: {
11
+ staleTime: 1000 * 60 * 5, // 5 minutes
12
+ gcTime: 1000 * 60 * 30, // 30 minutes (formerly cacheTime)
13
+ retry: 1,
14
+ refetchOnWindowFocus: false,
15
+ },
16
+ },
17
+ })
18
+ );
19
+
20
+ return (
21
+ <QueryClientProvider client={queryClient}>
22
+ {children}
23
+ {import.meta.env.MODE === 'development' && (
24
+ <ReactQueryDevtools initialIsOpen={false} />
25
+ )}
26
+ </QueryClientProvider>
27
+ );
28
+ }
@@ -0,0 +1,25 @@
1
+ import { useEffect } from 'react';
2
+ import { useGlobalKernelState, type Theme } from '../shared-state';
3
+
4
+ export function ThemeProvider({ children }: { children: React.ReactNode }) {
5
+ const { theme } = useGlobalKernelState();
6
+
7
+ useEffect(() => {
8
+ const root = window.document.documentElement;
9
+ root.classList.remove('light', 'dark');
10
+
11
+ let effectiveTheme: 'light' | 'dark' = 'light';
12
+
13
+ if (theme === 'system') {
14
+ effectiveTheme = window.matchMedia('(prefers-color-scheme: dark)').matches
15
+ ? 'dark'
16
+ : 'light';
17
+ } else {
18
+ effectiveTheme = theme;
19
+ }
20
+
21
+ root.classList.add(effectiveTheme);
22
+ }, [theme]);
23
+
24
+ return <>{children}</>;
25
+ }
@@ -0,0 +1,3 @@
1
+ export * from './PocketBaseProvider';
2
+ export * from './QueryProvider';
3
+ export * from './ThemeProvider';
@@ -0,0 +1,69 @@
1
+ import { useCurrentOrganization, useOrganizations } from '../hooks';
2
+ import {
3
+ DropdownMenu,
4
+ DropdownMenuContent,
5
+ DropdownMenuItem,
6
+ DropdownMenuLabel,
7
+ DropdownMenuSeparator,
8
+ DropdownMenuTrigger,
9
+ } from '../../components/ui/dropdown-menu';
10
+ import { Button } from '../../components/ui/button';
11
+ import { Building2, ChevronDown, Loader2, Plus } from 'lucide-react';
12
+ import { useNavigate } from '@modern-js/runtime/router';
13
+
14
+ export function OrganizationSelector() {
15
+ const { organization, setCurrentOrganization } = useCurrentOrganization();
16
+ const { data: organizations, isLoading } = useOrganizations();
17
+ const navigate = useNavigate();
18
+
19
+ const handleSwitch = (orgId: string) => {
20
+ const org = organizations?.find(o => o.id === orgId);
21
+ if (org) {
22
+ setCurrentOrganization(org);
23
+ }
24
+ };
25
+
26
+ const handleCreate = () => {
27
+ navigate('/settings/organizations/new');
28
+ };
29
+
30
+ return (
31
+ <DropdownMenu>
32
+ <DropdownMenuTrigger asChild>
33
+ <Button variant="outline" className="gap-2">
34
+ <Building2 className="h-4 w-4" />
35
+ {isLoading ? (
36
+ <Loader2 className="h-4 w-4 animate-spin" />
37
+ ) : organization ? (
38
+ <span className="max-w-[150px] truncate">{organization.name}</span>
39
+ ) : (
40
+ <span>Select Organization</span>
41
+ )}
42
+ <ChevronDown className="h-4 w-4" />
43
+ </Button>
44
+ </DropdownMenuTrigger>
45
+ <DropdownMenuContent className="w-56" align="start">
46
+ <DropdownMenuLabel>Organizations</DropdownMenuLabel>
47
+ <DropdownMenuSeparator />
48
+ {organizations?.map((org) => (
49
+ <DropdownMenuItem
50
+ key={org.id}
51
+ onClick={() => handleSwitch(org.id)}
52
+ className={organization?.id === org.id ? 'bg-accent' : ''}
53
+ >
54
+ <Building2 className="mr-2 h-4 w-4" />
55
+ <span className="flex-1 truncate">{org.name}</span>
56
+ {organization?.id === org.id && (
57
+ <span className="text-xs text-muted-foreground">Current</span>
58
+ )}
59
+ </DropdownMenuItem>
60
+ ))}
61
+ <DropdownMenuSeparator />
62
+ <DropdownMenuItem onClick={handleCreate}>
63
+ <Plus className="mr-2 h-4 w-4" />
64
+ Create Organization
65
+ </DropdownMenuItem>
66
+ </DropdownMenuContent>
67
+ </DropdownMenu>
68
+ );
69
+ }
@@ -0,0 +1,43 @@
1
+ import { useRequirePermission } from '../hooks';
2
+ import type { ResourceType, ActionType } from '../types';
3
+ import { Alert, AlertDescription } from '../../components/ui/alert';
4
+ import { Lock } from 'lucide-react';
5
+
6
+ interface PermissionGateProps {
7
+ resource: ResourceType;
8
+ action: ActionType;
9
+ children: React.ReactNode;
10
+ fallback?: React.ReactNode;
11
+ }
12
+
13
+ export function PermissionGate({
14
+ resource,
15
+ action,
16
+ children,
17
+ fallback,
18
+ }: PermissionGateProps) {
19
+ const { hasPermission, isLoading } = useRequirePermission(resource, action);
20
+
21
+ if (isLoading) {
22
+ return (
23
+ <div className="flex min-h-[200px] items-center justify-center">
24
+ <div className="text-sm text-muted-foreground">Checking permissions...</div>
25
+ </div>
26
+ );
27
+ }
28
+
29
+ if (!hasPermission) {
30
+ return (
31
+ fallback || (
32
+ <Alert variant="destructive">
33
+ <Lock className="h-4 w-4" />
34
+ <AlertDescription>
35
+ You don't have permission to {action} {resource}.
36
+ </AlertDescription>
37
+ </Alert>
38
+ )
39
+ );
40
+ }
41
+
42
+ return <>{children}</>;
43
+ }