@nebulit/embuilder 0.1.39

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 (212) hide show
  1. package/README.md +254 -0
  2. package/dist/cli.d.ts +2 -0
  3. package/dist/cli.js +138 -0
  4. package/package.json +49 -0
  5. package/templates/.claude/hooks/QUICKSTART.md +256 -0
  6. package/templates/.claude/hooks/README.md +533 -0
  7. package/templates/.claude/hooks/analyze-commit.sh +22 -0
  8. package/templates/.claude/hooks/analyze-commit.ts +518 -0
  9. package/templates/.claude/hooks/analyzers/README.md +198 -0
  10. package/templates/.claude/hooks/analyzers/code-quality-checker.ts +154 -0
  11. package/templates/.claude/hooks/analyzers/code-quality.md +54 -0
  12. package/templates/.claude/hooks/analyzers/commit-blocker-example.ts.disabled +110 -0
  13. package/templates/.claude/hooks/analyzers/commit-policy.md +49 -0
  14. package/templates/.claude/hooks/analyzers/event-model-validator.md +49 -0
  15. package/templates/.claude/hooks/analyzers/event-model-validator.ts +169 -0
  16. package/templates/.claude/hooks/analyzers/example-logger.ts +70 -0
  17. package/templates/.claude/hooks/analyzers/slice-scope-validator.md +81 -0
  18. package/templates/.claude/hooks/check-review-result.sh +47 -0
  19. package/templates/.claude/hooks/prepare-review.sh +34 -0
  20. package/templates/.claude/hooks/review-agent-prompt.md +42 -0
  21. package/templates/.claude/hooks/run-review-agent.sh +124 -0
  22. package/templates/.claude/settings.local.json +37 -0
  23. package/templates/.claude/skills/help/README.md +84 -0
  24. package/templates/.claude/skills/help/SKILL.md +393 -0
  25. package/templates/.claude/skills/help/templates/demo-config.json +6753 -0
  26. package/templates/.claude/skills/sample-slices/SKILL.md +8 -0
  27. package/templates/.claude/skills/sample-slices/templates/.slices/Library/addbook/code-slice.json +124 -0
  28. package/templates/.claude/skills/sample-slices/templates/.slices/Library/addbook/slice.json +255 -0
  29. package/templates/.claude/skills/sample-slices/templates/.slices/Library/availablebooks/slice.json +107 -0
  30. package/templates/.claude/skills/sample-slices/templates/.slices/index.json +20 -0
  31. package/templates/.claude/skills/sample-slices/templates/Cart/additem/slice.json +979 -0
  32. package/templates/.claude/skills/sample-slices/templates/Cart/archiveitem/slice.json +529 -0
  33. package/templates/.claude/skills/sample-slices/templates/Cart/cartitems/slice.json +1072 -0
  34. package/templates/.claude/skills/sample-slices/templates/Cart/cartwithproducts/slice.json +394 -0
  35. package/templates/.claude/skills/sample-slices/templates/Cart/changedprices/slice.json +88 -0
  36. package/templates/.claude/skills/sample-slices/templates/Cart/changeinventory/slice.json +264 -0
  37. package/templates/.claude/skills/sample-slices/templates/Cart/changeprice/slice.json +308 -0
  38. package/templates/.claude/skills/sample-slices/templates/Cart/clearcart/slice.json +358 -0
  39. package/templates/.claude/skills/sample-slices/templates/Cart/inventories/slice.json +203 -0
  40. package/templates/.claude/skills/sample-slices/templates/Cart/publishcart/slice.json +876 -0
  41. package/templates/.claude/skills/sample-slices/templates/Cart/removeitem/slice.json +560 -0
  42. package/templates/.claude/skills/sample-slices/templates/Cart/submitcart/slice.json +708 -0
  43. package/templates/.claude/skills/sample-slices/templates/Cart/submittedcartdata/slice.json +399 -0
  44. package/templates/.claude/skills/sample-slices/templates/index.json +108 -0
  45. package/templates/.claude/skills/slice-automation/SKILL.md +49 -0
  46. package/templates/.claude/skills/slice-state-change/SKILL.md +369 -0
  47. package/templates/.claude/skills/slice-state-change/templates/AddLocation/AddLocation.test.ts.sample +76 -0
  48. package/templates/.claude/skills/slice-state-change/templates/AddLocation/AddLocationCommand.ts.sample +84 -0
  49. package/templates/.claude/skills/slice-state-change/templates/AddLocation/routes.ts.sample +73 -0
  50. package/templates/.claude/skills/slice-state-change/templates/README.md +46 -0
  51. package/templates/.claude/skills/slice-state-view/SKILL.md +336 -0
  52. package/templates/.claude/skills/slice-state-view/templates/Locations/Locations.test.ts.sample +84 -0
  53. package/templates/.claude/skills/slice-state-view/templates/Locations/LocationsProjection.ts.sample +50 -0
  54. package/templates/.claude/skills/slice-state-view/templates/Locations/routes.ts.sample +46 -0
  55. package/templates/.claude/skills/slice-state-view/templates/README.md +109 -0
  56. package/templates/.claude/skills/slice-state-view/templates/Tables/Tables.test.ts.sample +104 -0
  57. package/templates/.claude/skills/slice-state-view/templates/Tables/TablesProjection.ts.sample +59 -0
  58. package/templates/.claude/skills/slice-state-view/templates/Tables/routes.ts.sample +46 -0
  59. package/templates/.claude/skills/slice-state-view/templates/V2__tables.sql +7 -0
  60. package/templates/.claude/skills/slice-state-view/templates/V8__locations.sql +7 -0
  61. package/templates/.claude/skills/test-analyzer/SKILL.md +373 -0
  62. package/templates/.claude/skills/test-analyzer/examples/specification-format.md +143 -0
  63. package/templates/.claude/skills/test-analyzer/examples/state-change-example.md +111 -0
  64. package/templates/.claude/skills/test-analyzer/examples/state-view-example.md +122 -0
  65. package/templates/AGENTS.md +110 -0
  66. package/templates/Claude.md +58 -0
  67. package/templates/README.md +178 -0
  68. package/templates/backend/.env +9 -0
  69. package/templates/backend/BACKEND_AUTH_SETUP.md +183 -0
  70. package/templates/backend/SWAGGER.md +213 -0
  71. package/templates/backend/eslint.config.mjs +31 -0
  72. package/templates/backend/flyway.conf +17 -0
  73. package/templates/backend/package.json +44 -0
  74. package/templates/backend/prd.json.example +64 -0
  75. package/templates/backend/public/assets/images/banner.png +0 -0
  76. package/templates/backend/public/assets/logo.png +0 -0
  77. package/templates/backend/public/file.svg +4 -0
  78. package/templates/backend/public/globe.svg +12 -0
  79. package/templates/backend/public/next.svg +6 -0
  80. package/templates/backend/public/vercel.svg +3 -0
  81. package/templates/backend/public/window.svg +5 -0
  82. package/templates/backend/server.ts +129 -0
  83. package/templates/backend/setup-env.sh +50 -0
  84. package/templates/backend/src/common/assertions.ts +6 -0
  85. package/templates/backend/src/common/db.ts +1 -0
  86. package/templates/backend/src/common/loadPostgresEventstore.ts +16 -0
  87. package/templates/backend/src/common/parseEndpoint.ts +51 -0
  88. package/templates/backend/src/common/replay.ts +9 -0
  89. package/templates/backend/src/common/routes.ts +19 -0
  90. package/templates/backend/src/common/testHelpers.ts +53 -0
  91. package/templates/backend/src/core/readmodel.ts +28 -0
  92. package/templates/backend/src/core/types.ts +26 -0
  93. package/templates/backend/src/process/process.ts +53 -0
  94. package/templates/backend/src/supabase/LoginHandler.ts +36 -0
  95. package/templates/backend/src/supabase/ProtectedPageProps.ts +21 -0
  96. package/templates/backend/src/supabase/README.md +171 -0
  97. package/templates/backend/src/supabase/api.ts +63 -0
  98. package/templates/backend/src/supabase/authMiddleware.ts +53 -0
  99. package/templates/backend/src/supabase/component.ts +12 -0
  100. package/templates/backend/src/supabase/requireUser.ts +72 -0
  101. package/templates/backend/src/supabase/serverProps.ts +25 -0
  102. package/templates/backend/src/supabase/staticProps.ts +10 -0
  103. package/templates/backend/src/swagger.ts +34 -0
  104. package/templates/backend/src/util/assertions.ts +6 -0
  105. package/templates/backend/supabase/config.toml +295 -0
  106. package/templates/backend/supabase/migrations/20260121155918593_catalogentries.sql.sample +23 -0
  107. package/templates/backend/supabase/seed.sql +1 -0
  108. package/templates/backend/tsconfig.json +31 -0
  109. package/templates/frontend/.env.development +3 -0
  110. package/templates/frontend/AGENTS.md +7 -0
  111. package/templates/frontend/README.md +73 -0
  112. package/templates/frontend/components.json +20 -0
  113. package/templates/frontend/eslint.config.js +26 -0
  114. package/templates/frontend/index.html +18 -0
  115. package/templates/frontend/package-lock.json +8347 -0
  116. package/templates/frontend/package.json +94 -0
  117. package/templates/frontend/postcss.config.js +6 -0
  118. package/templates/frontend/public/favicon.ico +0 -0
  119. package/templates/frontend/public/logo.png +0 -0
  120. package/templates/frontend/public/placeholder.svg +1 -0
  121. package/templates/frontend/public/robots.txt +14 -0
  122. package/templates/frontend/src/App.css +42 -0
  123. package/templates/frontend/src/App.tsx +47 -0
  124. package/templates/frontend/src/components/NavLink.tsx +28 -0
  125. package/templates/frontend/src/components/ProtectedRoute.tsx +24 -0
  126. package/templates/frontend/src/components/calendar/Calendar.tsx +302 -0
  127. package/templates/frontend/src/components/layout/DashboardLayout.tsx +21 -0
  128. package/templates/frontend/src/components/layout/Header.tsx +45 -0
  129. package/templates/frontend/src/components/layout/Sidebar.tsx +82 -0
  130. package/templates/frontend/src/components/tables/ReservationTemplates.tsx +189 -0
  131. package/templates/frontend/src/components/ui/accordion.tsx +52 -0
  132. package/templates/frontend/src/components/ui/alert-dialog.tsx +104 -0
  133. package/templates/frontend/src/components/ui/alert.tsx +43 -0
  134. package/templates/frontend/src/components/ui/aspect-ratio.tsx +5 -0
  135. package/templates/frontend/src/components/ui/avatar.tsx +38 -0
  136. package/templates/frontend/src/components/ui/badge.tsx +29 -0
  137. package/templates/frontend/src/components/ui/breadcrumb.tsx +90 -0
  138. package/templates/frontend/src/components/ui/button.tsx +47 -0
  139. package/templates/frontend/src/components/ui/calendar.tsx +54 -0
  140. package/templates/frontend/src/components/ui/card.tsx +43 -0
  141. package/templates/frontend/src/components/ui/carousel.tsx +224 -0
  142. package/templates/frontend/src/components/ui/chart.tsx +303 -0
  143. package/templates/frontend/src/components/ui/checkbox.tsx +26 -0
  144. package/templates/frontend/src/components/ui/collapsible.tsx +9 -0
  145. package/templates/frontend/src/components/ui/command.tsx +132 -0
  146. package/templates/frontend/src/components/ui/context-menu.tsx +178 -0
  147. package/templates/frontend/src/components/ui/dialog.tsx +95 -0
  148. package/templates/frontend/src/components/ui/drawer.tsx +87 -0
  149. package/templates/frontend/src/components/ui/dropdown-menu.tsx +179 -0
  150. package/templates/frontend/src/components/ui/form.tsx +129 -0
  151. package/templates/frontend/src/components/ui/hover-card.tsx +27 -0
  152. package/templates/frontend/src/components/ui/input-otp.tsx +61 -0
  153. package/templates/frontend/src/components/ui/input.tsx +22 -0
  154. package/templates/frontend/src/components/ui/label.tsx +17 -0
  155. package/templates/frontend/src/components/ui/menubar.tsx +207 -0
  156. package/templates/frontend/src/components/ui/navigation-menu.tsx +120 -0
  157. package/templates/frontend/src/components/ui/pagination.tsx +81 -0
  158. package/templates/frontend/src/components/ui/popover.tsx +29 -0
  159. package/templates/frontend/src/components/ui/progress.tsx +23 -0
  160. package/templates/frontend/src/components/ui/radio-group.tsx +36 -0
  161. package/templates/frontend/src/components/ui/resizable.tsx +37 -0
  162. package/templates/frontend/src/components/ui/scroll-area.tsx +38 -0
  163. package/templates/frontend/src/components/ui/select.tsx +143 -0
  164. package/templates/frontend/src/components/ui/separator.tsx +20 -0
  165. package/templates/frontend/src/components/ui/sheet.tsx +107 -0
  166. package/templates/frontend/src/components/ui/sidebar.tsx +637 -0
  167. package/templates/frontend/src/components/ui/skeleton.tsx +7 -0
  168. package/templates/frontend/src/components/ui/slider.tsx +23 -0
  169. package/templates/frontend/src/components/ui/sonner.tsx +27 -0
  170. package/templates/frontend/src/components/ui/stat-card.tsx +44 -0
  171. package/templates/frontend/src/components/ui/switch.tsx +27 -0
  172. package/templates/frontend/src/components/ui/table.tsx +72 -0
  173. package/templates/frontend/src/components/ui/tabs.tsx +53 -0
  174. package/templates/frontend/src/components/ui/textarea.tsx +21 -0
  175. package/templates/frontend/src/components/ui/toast.tsx +111 -0
  176. package/templates/frontend/src/components/ui/toaster.tsx +24 -0
  177. package/templates/frontend/src/components/ui/toggle-group.tsx +49 -0
  178. package/templates/frontend/src/components/ui/toggle.tsx +37 -0
  179. package/templates/frontend/src/components/ui/tooltip.tsx +28 -0
  180. package/templates/frontend/src/components/ui/use-toast.ts +3 -0
  181. package/templates/frontend/src/contexts/AuthContext.tsx +94 -0
  182. package/templates/frontend/src/contexts/RefreshContext.tsx +236 -0
  183. package/templates/frontend/src/hooks/api/index.ts +2 -0
  184. package/templates/frontend/src/hooks/api/useLocations.ts +15 -0
  185. package/templates/frontend/src/hooks/use-mobile.tsx +19 -0
  186. package/templates/frontend/src/hooks/use-toast.ts +186 -0
  187. package/templates/frontend/src/hooks/useApiContext.ts +11 -0
  188. package/templates/frontend/src/index.css +118 -0
  189. package/templates/frontend/src/integrations/supabase/client.ts +9 -0
  190. package/templates/frontend/src/lib/api-client.ts +136 -0
  191. package/templates/frontend/src/lib/api.ts +1028 -0
  192. package/templates/frontend/src/lib/utils.ts +6 -0
  193. package/templates/frontend/src/main.tsx +5 -0
  194. package/templates/frontend/src/pages/Auth.tsx +408 -0
  195. package/templates/frontend/src/pages/Dashboard.tsx +168 -0
  196. package/templates/frontend/src/pages/Menus.tsx +224 -0
  197. package/templates/frontend/src/pages/NotFound.tsx +24 -0
  198. package/templates/frontend/src/pages/Register.tsx +285 -0
  199. package/templates/frontend/src/test/example.test.ts +0 -0
  200. package/templates/frontend/src/test/setup.ts +15 -0
  201. package/templates/frontend/src/types/index.ts +8 -0
  202. package/templates/frontend/src/vite-env.d.ts +1 -0
  203. package/templates/frontend/tailwind.config.ts +101 -0
  204. package/templates/frontend/tsconfig.app.json +31 -0
  205. package/templates/frontend/tsconfig.json +16 -0
  206. package/templates/frontend/tsconfig.node.json +22 -0
  207. package/templates/frontend/vite.config.ts +21 -0
  208. package/templates/frontend/vitest.config.ts +16 -0
  209. package/templates/init.sh +1 -0
  210. package/templates/prompt.md +139 -0
  211. package/templates/ralph.sh +120 -0
  212. package/templates/server.mjs +505 -0
@@ -0,0 +1,236 @@
1
+ import { createContext, useContext, useState, useCallback, ReactNode, useEffect } from 'react';
2
+ import { supabase } from '@/integrations/supabase/client';
3
+ import { useAuth } from '@/contexts/AuthContext';
4
+
5
+ type RefreshScope = 'all' | 'appointments' | 'tasks' | 'documents' | 'cases' | string;
6
+
7
+ interface RefreshContextType {
8
+ /**
9
+ * Trigger a refresh for specific scope(s) or all components
10
+ */
11
+ triggerRefresh: (scope?: RefreshScope | RefreshScope[]) => void;
12
+
13
+ /**
14
+ * Subscribe to refresh events for a specific scope
15
+ * Returns true when a refresh is triggered for this scope
16
+ */
17
+ shouldRefresh: (scope: RefreshScope) => boolean;
18
+
19
+ /**
20
+ * Acknowledge that a component has completed its refresh
21
+ */
22
+ acknowledgeRefresh: (scope: RefreshScope) => void;
23
+
24
+ /**
25
+ * Enable/disable Supabase Realtime auto-refresh
26
+ */
27
+ realtimeEnabled: boolean;
28
+ setRealtimeEnabled: (enabled: boolean) => void;
29
+ }
30
+
31
+ const RefreshContext = createContext<RefreshContextType | undefined>(undefined);
32
+
33
+ interface RefreshProviderProps {
34
+ children: ReactNode;
35
+ /**
36
+ * Enable Supabase Realtime auto-refresh by default
37
+ */
38
+ enableRealtime?: boolean;
39
+ }
40
+
41
+ export function RefreshProvider({ children, enableRealtime = false }: RefreshProviderProps) {
42
+ const [activeRefreshes, setActiveRefreshes] = useState<Set<RefreshScope>>(new Set());
43
+ const [realtimeEnabled, setRealtimeEnabled] = useState(enableRealtime);
44
+ const { user } = useAuth();
45
+
46
+ // Log provider initialization
47
+ useEffect(() => {
48
+ console.log('🚀 [PROVIDER] RefreshProvider initialized:', {
49
+ realtimeEnabled,
50
+ hasUser: !!user?.id,
51
+ userId: user?.id
52
+ });
53
+ }, [realtimeEnabled, user?.id]);
54
+
55
+ const triggerRefresh = useCallback((scope: RefreshScope | RefreshScope[] = 'all') => {
56
+ const scopes = Array.isArray(scope) ? scope : [scope];
57
+ console.log('🔄 [TRIGGER] triggerRefresh called with scope(s):', scopes);
58
+
59
+ setActiveRefreshes(prev => {
60
+ const next = new Set(prev);
61
+ const prevSize = next.size;
62
+ scopes.forEach(s => next.add(s));
63
+ // 'all' scope triggers everything
64
+ if (scopes.includes('all')) {
65
+ next.add('all');
66
+ }
67
+ console.log('🔄 [TRIGGER] Active refreshes updated:', {
68
+ previous: Array.from(prev),
69
+ current: Array.from(next),
70
+ added: next.size - prevSize
71
+ });
72
+ return next;
73
+ });
74
+ }, []);
75
+
76
+ const shouldRefresh = useCallback((scope: RefreshScope): boolean => {
77
+ const result = activeRefreshes.has(scope) || activeRefreshes.has('all');
78
+ return result;
79
+ }, [activeRefreshes]);
80
+
81
+ const acknowledgeRefresh = useCallback((scope: RefreshScope) => {
82
+ setActiveRefreshes(prev => {
83
+ const next = new Set(prev);
84
+ next.delete(scope);
85
+ // If no specific scopes left and 'all' was set, clear it
86
+ if (next.size === 1 && next.has('all')) {
87
+ next.delete('all');
88
+ }
89
+ console.log('✅ [ACK] Active refreshes after acknowledgment:', {
90
+ scope,
91
+ previous: Array.from(prev),
92
+ current: Array.from(next)
93
+ });
94
+ return next;
95
+ });
96
+ }, []);
97
+
98
+ // Supabase Realtime integration
99
+ useEffect(() => {
100
+ if (!realtimeEnabled || !user?.id) {
101
+ console.log('📡 [REALTIME] Skipping subscription:', {
102
+ realtimeEnabled,
103
+ hasUserId: !!user?.id
104
+ });
105
+ return;
106
+ }
107
+
108
+ console.log('📡 [REALTIME] Subscribing to Supabase Realtime for user:', user.id);
109
+
110
+ // Channel name can be anything; using 'ui-refresh' makes intent clear
111
+ const channel = supabase.channel(`ui-refresh:${user.id}`);
112
+
113
+ channel
114
+ .on(
115
+ 'postgres_changes',
116
+ {
117
+ event: 'INSERT',
118
+ schema: 'public', // your schema
119
+ table: 'ui_refresh_events',
120
+ filter: undefined
121
+ },
122
+ (payload) => {
123
+ // The inserted row is in payload.new
124
+ const scope = payload.new?.scope || 'all';
125
+
126
+ console.log('📡 [REALTIME] DB refresh event received:', {
127
+ payload,
128
+ scope,
129
+ timestamp: new Date().toISOString()
130
+ });
131
+
132
+ triggerRefresh(scope);
133
+ }
134
+ )
135
+ .subscribe((status) => {
136
+ console.log('📡 [REALTIME] Subscription status changed:', {
137
+ status,
138
+ channelName: `ui-refresh:${user.id}`,
139
+ timestamp: new Date().toISOString()
140
+ });
141
+
142
+ if (status === 'SUBSCRIBED') {
143
+ console.log(`✅ [REALTIME] Successfully subscribed to channel: ui-refresh:${user.id}`);
144
+ } else if (status === 'CHANNEL_ERROR') {
145
+ console.error('❌ [REALTIME] Failed to subscribe to realtime channel');
146
+ }
147
+ });
148
+
149
+ // Cleanup on unmount
150
+ return () => {
151
+ console.log(`🔌 [REALTIME] Unsubscribing from channel: ui-refresh:${user.id}`);
152
+ supabase.removeChannel(channel);
153
+ };
154
+ }, [realtimeEnabled, user?.id, triggerRefresh]);
155
+
156
+ return (
157
+ <RefreshContext.Provider
158
+ value={{
159
+ triggerRefresh,
160
+ shouldRefresh,
161
+ acknowledgeRefresh,
162
+ realtimeEnabled,
163
+ setRealtimeEnabled,
164
+ }}
165
+ >
166
+ {children}
167
+ </RefreshContext.Provider>
168
+ );
169
+ }
170
+
171
+ /**
172
+ * Hook to trigger refreshes from any component
173
+ */
174
+ export function useRefreshTrigger() {
175
+ const context = useContext(RefreshContext);
176
+ if (!context) {
177
+ throw new Error('useRefreshTrigger must be used within RefreshProvider');
178
+ }
179
+
180
+ console.log('🔌 [TRIGGER_HOOK] Component using useRefreshTrigger hook');
181
+
182
+ return {
183
+ triggerRefresh: context.triggerRefresh,
184
+ realtimeEnabled: context.realtimeEnabled,
185
+ setRealtimeEnabled: context.setRealtimeEnabled,
186
+ };
187
+ }
188
+
189
+ /**
190
+ * Hook for components to subscribe to refresh events
191
+ *
192
+ * @param scope - The scope this component listens to (e.g., 'appointments', 'tasks', or 'all')
193
+ * @param onRefresh - Async function to call when refresh is triggered
194
+ *
195
+ * @example
196
+ * ```tsx
197
+ * function AppointmentsList() {
198
+ * const { refetch } = useQuery(...);
199
+ *
200
+ * useRefreshSubscription('appointments', refetch);
201
+ *
202
+ * return <div>...</div>;
203
+ * }
204
+ * ```
205
+ */
206
+ export function useRefreshSubscription(
207
+ scope: RefreshScope,
208
+ onRefresh: () => Promise<void> | void
209
+ ) {
210
+ const context = useContext(RefreshContext);
211
+ if (!context) {
212
+ throw new Error('useRefreshSubscription must be used within RefreshProvider');
213
+ }
214
+
215
+ const { shouldRefresh, acknowledgeRefresh } = context;
216
+
217
+ useEffect(() => {
218
+
219
+ console.log(`{1}should refresh ${scope}`)
220
+ if (shouldRefresh(scope)) {
221
+ console.log(`{1}yes`)
222
+
223
+ const doRefresh = async () => {
224
+ try {
225
+ await onRefresh();
226
+ } catch (error) {
227
+ console.error('❌ [SUBSCRIPTION] Refresh failed for scope:', scope, error);
228
+ } finally {
229
+ acknowledgeRefresh(scope);
230
+ }
231
+ };
232
+
233
+ doRefresh();
234
+ }
235
+ }, [shouldRefresh, scope, onRefresh, acknowledgeRefresh]);
236
+ }
@@ -0,0 +1,2 @@
1
+ // API Hooks - Central export
2
+ export * from "./useLocations";
@@ -0,0 +1,15 @@
1
+ /*import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
2
+ import { useApiContext } from "@/hooks/useApiContext";
3
+ import * as api from "@/lib/api";
4
+
5
+ export function useRegisterRestaurant() {
6
+ const queryClient = useQueryClient();
7
+ const ctx = useApiContext();
8
+
9
+ return useMutation({
10
+ mutationFn: (params: api.RegisterRestaurantParams) => api.registerRestaurant(params, ctx),
11
+ onSuccess: () => {
12
+ queryClient.invalidateQueries({ queryKey: ["restaurants"] });
13
+ },
14
+ });
15
+ }*/
@@ -0,0 +1,19 @@
1
+ import * as React from "react";
2
+
3
+ const MOBILE_BREAKPOINT = 768;
4
+
5
+ export function useIsMobile() {
6
+ const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined);
7
+
8
+ React.useEffect(() => {
9
+ const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
10
+ const onChange = () => {
11
+ setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
12
+ };
13
+ mql.addEventListener("change", onChange);
14
+ setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
15
+ return () => mql.removeEventListener("change", onChange);
16
+ }, []);
17
+
18
+ return !!isMobile;
19
+ }
@@ -0,0 +1,186 @@
1
+ import * as React from "react";
2
+
3
+ import type { ToastActionElement, ToastProps } from "@/components/ui/toast";
4
+
5
+ const TOAST_LIMIT = 1;
6
+ const TOAST_REMOVE_DELAY = 1000000;
7
+
8
+ type ToasterToast = ToastProps & {
9
+ id: string;
10
+ title?: React.ReactNode;
11
+ description?: React.ReactNode;
12
+ action?: ToastActionElement;
13
+ };
14
+
15
+ const actionTypes = {
16
+ ADD_TOAST: "ADD_TOAST",
17
+ UPDATE_TOAST: "UPDATE_TOAST",
18
+ DISMISS_TOAST: "DISMISS_TOAST",
19
+ REMOVE_TOAST: "REMOVE_TOAST",
20
+ } as const;
21
+
22
+ let count = 0;
23
+
24
+ function genId() {
25
+ count = (count + 1) % Number.MAX_SAFE_INTEGER;
26
+ return count.toString();
27
+ }
28
+
29
+ type ActionType = typeof actionTypes;
30
+
31
+ type Action =
32
+ | {
33
+ type: ActionType["ADD_TOAST"];
34
+ toast: ToasterToast;
35
+ }
36
+ | {
37
+ type: ActionType["UPDATE_TOAST"];
38
+ toast: Partial<ToasterToast>;
39
+ }
40
+ | {
41
+ type: ActionType["DISMISS_TOAST"];
42
+ toastId?: ToasterToast["id"];
43
+ }
44
+ | {
45
+ type: ActionType["REMOVE_TOAST"];
46
+ toastId?: ToasterToast["id"];
47
+ };
48
+
49
+ interface State {
50
+ toasts: ToasterToast[];
51
+ }
52
+
53
+ const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>();
54
+
55
+ const addToRemoveQueue = (toastId: string) => {
56
+ if (toastTimeouts.has(toastId)) {
57
+ return;
58
+ }
59
+
60
+ const timeout = setTimeout(() => {
61
+ toastTimeouts.delete(toastId);
62
+ dispatch({
63
+ type: "REMOVE_TOAST",
64
+ toastId: toastId,
65
+ });
66
+ }, TOAST_REMOVE_DELAY);
67
+
68
+ toastTimeouts.set(toastId, timeout);
69
+ };
70
+
71
+ export const reducer = (state: State, action: Action): State => {
72
+ switch (action.type) {
73
+ case "ADD_TOAST":
74
+ return {
75
+ ...state,
76
+ toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
77
+ };
78
+
79
+ case "UPDATE_TOAST":
80
+ return {
81
+ ...state,
82
+ toasts: state.toasts.map((t) => (t.id === action.toast.id ? { ...t, ...action.toast } : t)),
83
+ };
84
+
85
+ case "DISMISS_TOAST": {
86
+ const { toastId } = action;
87
+
88
+ // ! Side effects ! - This could be extracted into a dismissToast() action,
89
+ // but I'll keep it here for simplicity
90
+ if (toastId) {
91
+ addToRemoveQueue(toastId);
92
+ } else {
93
+ state.toasts.forEach((toast) => {
94
+ addToRemoveQueue(toast.id);
95
+ });
96
+ }
97
+
98
+ return {
99
+ ...state,
100
+ toasts: state.toasts.map((t) =>
101
+ t.id === toastId || toastId === undefined
102
+ ? {
103
+ ...t,
104
+ open: false,
105
+ }
106
+ : t,
107
+ ),
108
+ };
109
+ }
110
+ case "REMOVE_TOAST":
111
+ if (action.toastId === undefined) {
112
+ return {
113
+ ...state,
114
+ toasts: [],
115
+ };
116
+ }
117
+ return {
118
+ ...state,
119
+ toasts: state.toasts.filter((t) => t.id !== action.toastId),
120
+ };
121
+ }
122
+ };
123
+
124
+ const listeners: Array<(state: State) => void> = [];
125
+
126
+ let memoryState: State = { toasts: [] };
127
+
128
+ function dispatch(action: Action) {
129
+ memoryState = reducer(memoryState, action);
130
+ listeners.forEach((listener) => {
131
+ listener(memoryState);
132
+ });
133
+ }
134
+
135
+ type Toast = Omit<ToasterToast, "id">;
136
+
137
+ function toast({ ...props }: Toast) {
138
+ const id = genId();
139
+
140
+ const update = (props: ToasterToast) =>
141
+ dispatch({
142
+ type: "UPDATE_TOAST",
143
+ toast: { ...props, id },
144
+ });
145
+ const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id });
146
+
147
+ dispatch({
148
+ type: "ADD_TOAST",
149
+ toast: {
150
+ ...props,
151
+ id,
152
+ open: true,
153
+ onOpenChange: (open) => {
154
+ if (!open) dismiss();
155
+ },
156
+ },
157
+ });
158
+
159
+ return {
160
+ id: id,
161
+ dismiss,
162
+ update,
163
+ };
164
+ }
165
+
166
+ function useToast() {
167
+ const [state, setState] = React.useState<State>(memoryState);
168
+
169
+ React.useEffect(() => {
170
+ listeners.push(setState);
171
+ return () => {
172
+ const index = listeners.indexOf(setState);
173
+ if (index > -1) {
174
+ listeners.splice(index, 1);
175
+ }
176
+ };
177
+ }, [state]);
178
+
179
+ return {
180
+ ...state,
181
+ toast,
182
+ dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }),
183
+ };
184
+ }
185
+
186
+ export { useToast, toast };
@@ -0,0 +1,11 @@
1
+ import { useAuth } from "@/contexts/AuthContext";
2
+ import { ApiContext } from "@/lib/api-client";
3
+
4
+ export function useApiContext(): ApiContext {
5
+ const { session, user, tenantId } = useAuth();
6
+ return {
7
+ token: session?.access_token ?? "",
8
+ tenantId: tenantId ?? undefined,
9
+ userId: user?.id ?? undefined,
10
+ };
11
+ }
@@ -0,0 +1,118 @@
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
4
+
5
+ @layer base {
6
+ :root {
7
+ /* Warm hospitality palette */
8
+ --background: 40 33% 98%;
9
+ --foreground: 20 20% 15%;
10
+
11
+ --card: 40 30% 99%;
12
+ --card-foreground: 20 20% 15%;
13
+
14
+ --popover: 40 30% 99%;
15
+ --popover-foreground: 20 20% 15%;
16
+
17
+ /* Warm terracotta coral */
18
+ --primary: 14 70% 54%;
19
+ --primary-foreground: 40 33% 98%;
20
+
21
+ /* Soft sage */
22
+ --secondary: 150 20% 92%;
23
+ --secondary-foreground: 150 30% 25%;
24
+
25
+ --muted: 40 20% 94%;
26
+ --muted-foreground: 20 10% 45%;
27
+
28
+ /* Sage accent */
29
+ --accent: 150 25% 88%;
30
+ --accent-foreground: 150 35% 20%;
31
+
32
+ --destructive: 0 72% 51%;
33
+ --destructive-foreground: 0 0% 100%;
34
+
35
+ --border: 40 20% 88%;
36
+ --input: 40 20% 88%;
37
+ --ring: 14 70% 54%;
38
+
39
+ --radius: 0.625rem;
40
+
41
+ /* Sidebar - warm neutral */
42
+ --sidebar-background: 20 15% 12%;
43
+ --sidebar-foreground: 40 20% 90%;
44
+ --sidebar-primary: 14 70% 60%;
45
+ --sidebar-primary-foreground: 40 33% 98%;
46
+ --sidebar-accent: 20 15% 18%;
47
+ --sidebar-accent-foreground: 40 20% 90%;
48
+ --sidebar-border: 20 15% 20%;
49
+ --sidebar-ring: 14 70% 54%;
50
+
51
+ /* Custom tokens */
52
+ --success: 150 50% 40%;
53
+ --success-foreground: 0 0% 100%;
54
+ --warning: 38 92% 50%;
55
+ --warning-foreground: 20 20% 15%;
56
+ }
57
+
58
+ .dark {
59
+ --background: 20 15% 8%;
60
+ --foreground: 40 20% 95%;
61
+
62
+ --card: 20 15% 10%;
63
+ --card-foreground: 40 20% 95%;
64
+
65
+ --popover: 20 15% 10%;
66
+ --popover-foreground: 40 20% 95%;
67
+
68
+ --primary: 14 70% 58%;
69
+ --primary-foreground: 20 15% 8%;
70
+
71
+ --secondary: 150 15% 18%;
72
+ --secondary-foreground: 150 20% 85%;
73
+
74
+ --muted: 20 15% 18%;
75
+ --muted-foreground: 40 15% 60%;
76
+
77
+ --accent: 150 20% 20%;
78
+ --accent-foreground: 150 20% 90%;
79
+
80
+ --destructive: 0 62% 45%;
81
+ --destructive-foreground: 0 0% 100%;
82
+
83
+ --border: 20 15% 20%;
84
+ --input: 20 15% 20%;
85
+ --ring: 14 70% 58%;
86
+
87
+ --sidebar-background: 20 15% 6%;
88
+ --sidebar-foreground: 40 20% 90%;
89
+ --sidebar-primary: 14 70% 60%;
90
+ --sidebar-primary-foreground: 20 15% 8%;
91
+ --sidebar-accent: 20 15% 12%;
92
+ --sidebar-accent-foreground: 40 20% 90%;
93
+ --sidebar-border: 20 15% 15%;
94
+ --sidebar-ring: 14 70% 58%;
95
+
96
+ --success: 150 50% 45%;
97
+ --success-foreground: 0 0% 100%;
98
+ --warning: 38 92% 55%;
99
+ --warning-foreground: 20 20% 10%;
100
+ }
101
+ }
102
+
103
+ @layer base {
104
+ * {
105
+ @apply border-border;
106
+ }
107
+
108
+ body {
109
+ @apply bg-background text-foreground antialiased;
110
+ font-feature-settings: "rlig" 1, "calt" 1;
111
+ }
112
+ }
113
+
114
+ @layer utilities {
115
+ .glass-card {
116
+ @apply bg-card/80 backdrop-blur-sm border border-border/50;
117
+ }
118
+ }
@@ -0,0 +1,9 @@
1
+ import { createClient } from '@supabase/supabase-js';
2
+
3
+ // Supabase project URL
4
+ const supabaseUrl = import.meta.env.VITE_SUPABASE_URL || '';
5
+
6
+ // Publishable key (safe to expose in the front-end)
7
+ const supabasePublishableKey = import.meta.env.VITE_SUPABASE_PUBLISHABLE_KEY || '';
8
+
9
+ export const supabase = createClient(supabaseUrl, supabasePublishableKey);