@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,6 @@
1
+ import { clsx, type ClassValue } from "clsx";
2
+ import { twMerge } from "tailwind-merge";
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs));
6
+ }
@@ -0,0 +1,5 @@
1
+ import { createRoot } from "react-dom/client";
2
+ import App from "./App.tsx";
3
+ import "./index.css";
4
+
5
+ createRoot(document.getElementById("root")!).render(<App />);
@@ -0,0 +1,408 @@
1
+ import { useState, useEffect } from 'react';
2
+ import { useNavigate, useSearchParams } from 'react-router-dom';
3
+ import { supabase } from '@/integrations/supabase/client';
4
+ import { useAuth } from '@/contexts/AuthContext';
5
+ import { Button } from '@/components/ui/button';
6
+ import { Input } from '@/components/ui/input';
7
+ import { Label } from '@/components/ui/label';
8
+ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
9
+ import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
10
+ import { useToast } from '@/hooks/use-toast';
11
+ import { LogOut } from 'lucide-react';
12
+ import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
13
+
14
+ const Auth = () => {
15
+ const [loginEmail, setLoginEmail] = useState('');
16
+ const [loginPassword, setLoginPassword] = useState('');
17
+ const [magicLinkEmail, setMagicLinkEmail] = useState('');
18
+ const [resetEmail, setResetEmail] = useState('');
19
+ const [newPassword, setNewPassword] = useState('');
20
+ const [confirmPassword, setConfirmPassword] = useState('');
21
+ const [loading, setLoading] = useState(false);
22
+ const [magicLinkSent, setMagicLinkSent] = useState(false);
23
+ const [resetEmailSent, setResetEmailSent] = useState(false);
24
+ const [showResetForm, setShowResetForm] = useState(false);
25
+ const [isPasswordUpdateMode, setIsPasswordUpdateMode] = useState(false);
26
+ const [showModal, setShowModal] = useState(false);
27
+ const navigate = useNavigate();
28
+ const { toast } = useToast();
29
+ const { user, session, tenantId, setTenantId } = useAuth();
30
+ const [searchParams] = useSearchParams();
31
+
32
+ useEffect(() => {
33
+ const checkPasswordReset = async () => {
34
+ const isReset = searchParams.get('reset') === 'true';
35
+ if (isReset) {
36
+ const { data: { session } } = await supabase.auth.getSession();
37
+ if (session) {
38
+ setIsPasswordUpdateMode(true);
39
+ }
40
+ }
41
+ };
42
+ checkPasswordReset();
43
+ }, [searchParams]);
44
+
45
+
46
+ const handleLogout = async () => {
47
+ setLoading(true);
48
+ const { error } = await supabase.auth.signOut();
49
+ if (error) {
50
+ toast({
51
+ title: 'Error',
52
+ description: error.message,
53
+ variant: 'destructive',
54
+ });
55
+ } else {
56
+ toast({
57
+ title: 'Signed out',
58
+ description: 'You have been signed out successfully.',
59
+ });
60
+ }
61
+ setLoading(false);
62
+ };
63
+
64
+ const handleLogin = async (e: React.FormEvent) => {
65
+ e.preventDefault();
66
+ setLoading(true);
67
+
68
+ const { error } = await supabase.auth.signInWithPassword({
69
+ email: loginEmail,
70
+ password: loginPassword,
71
+ });
72
+
73
+ if (error) {
74
+ toast({
75
+ title: 'Error',
76
+ description: error.message,
77
+ variant: 'destructive',
78
+ });
79
+ } else {
80
+ navigate('/');
81
+ }
82
+ setLoading(false);
83
+ };
84
+
85
+ const handleMagicLink = async (e: React.FormEvent) => {
86
+ e.preventDefault();
87
+ setLoading(true);
88
+
89
+ const { error } = await supabase.auth.signInWithOtp({
90
+ email: magicLinkEmail,
91
+ options: {
92
+ emailRedirectTo: `${window.location.origin}/`,
93
+ },
94
+ });
95
+
96
+ if (error) {
97
+ toast({
98
+ title: 'Error',
99
+ description: error.message,
100
+ variant: 'destructive',
101
+ });
102
+ } else {
103
+ setMagicLinkSent(true);
104
+ toast({
105
+ title: 'Login link sent',
106
+ description: 'Please check your email and click the link to sign in.',
107
+ });
108
+ }
109
+ setLoading(false);
110
+ };
111
+
112
+ const handlePasswordReset = async (e: React.FormEvent) => {
113
+ e.preventDefault();
114
+ setLoading(true);
115
+
116
+ const { error } = await supabase.auth.resetPasswordForEmail(resetEmail, {
117
+ redirectTo: `${window.location.origin}/auth?reset=true`,
118
+ });
119
+
120
+ if (error) {
121
+ toast({
122
+ title: 'Error',
123
+ description: error.message,
124
+ variant: 'destructive',
125
+ });
126
+ } else {
127
+ setResetEmailSent(true);
128
+ toast({
129
+ title: 'Email sent',
130
+ description: 'Please check your email for the password reset link.',
131
+ });
132
+ }
133
+ setLoading(false);
134
+ };
135
+
136
+ const handleUpdatePassword = async (e: React.FormEvent) => {
137
+ e.preventDefault();
138
+
139
+ if (newPassword !== confirmPassword) {
140
+ toast({
141
+ title: 'Error',
142
+ description: 'Passwords do not match.',
143
+ variant: 'destructive',
144
+ });
145
+ return;
146
+ }
147
+
148
+ if (newPassword.length < 6) {
149
+ toast({
150
+ title: 'Error',
151
+ description: 'Password must be at least 6 characters long.',
152
+ variant: 'destructive',
153
+ });
154
+ return;
155
+ }
156
+
157
+ setLoading(true);
158
+
159
+ const { error } = await supabase.auth.updateUser({
160
+ password: newPassword,
161
+ });
162
+
163
+ if (error) {
164
+ toast({
165
+ title: 'Error',
166
+ description: error.message,
167
+ variant: 'destructive',
168
+ });
169
+ } else {
170
+ toast({
171
+ title: 'Password updated',
172
+ description: 'Your password has been changed successfully. You will be redirected to the home page.',
173
+ });
174
+ setIsPasswordUpdateMode(false);
175
+ navigate('/');
176
+ }
177
+ setLoading(false);
178
+ };
179
+
180
+
181
+ return (
182
+ <div className="min-h-screen bg-background flex items-center justify-center p-4">
183
+ <Card className="w-full max-w-md shadow-[var(--shadow-medium)]">
184
+ <CardHeader className="space-y-4">
185
+ <div className="flex justify-center">
186
+ <img
187
+ src="/logo.png"
188
+ alt="App Logo"
189
+ className=" w-auto object-contain"
190
+ />
191
+ </div>
192
+ <div className="text-center">
193
+ <CardTitle>{isPasswordUpdateMode ? 'Set New Password' : ''}</CardTitle>
194
+ </div>
195
+ {user && !isPasswordUpdateMode && (
196
+ <div className="flex flex-col items-center gap-3 pt-2 border-t">
197
+ <p className="text-sm text-muted-foreground">
198
+ Signed in as: <strong>{user.email}</strong>
199
+ </p>
200
+ <div className="flex gap-2">
201
+ <Button
202
+ variant="default"
203
+ size="sm"
204
+ onClick={() => navigate('/')}
205
+ className="gap-2"
206
+ >
207
+ Start
208
+ </Button>
209
+ <Button
210
+ variant="outline"
211
+ size="sm"
212
+ onClick={handleLogout}
213
+ disabled={loading}
214
+ className="gap-2"
215
+ >
216
+ <LogOut className="h-4 w-4" />
217
+ {loading ? 'Logging out...' : 'Logout'}
218
+ </Button>
219
+ </div>
220
+ </div>
221
+ )}
222
+ </CardHeader>
223
+ <CardContent>
224
+ {isPasswordUpdateMode ? (
225
+ <form onSubmit={handleUpdatePassword} className="space-y-4">
226
+ <div className="space-y-2">
227
+ <Label htmlFor="new-password">New Password</Label>
228
+ <Input
229
+ id="new-password"
230
+ type="password"
231
+ placeholder="At least 6 characters"
232
+ value={newPassword}
233
+ onChange={(e) => setNewPassword(e.target.value)}
234
+ required
235
+ />
236
+ </div>
237
+ <div className="space-y-2">
238
+ <Label htmlFor="confirm-password">Confirm password</Label>
239
+ <Input
240
+ id="confirm-password"
241
+ type="password"
242
+ placeholder="Confirm password"
243
+ value={confirmPassword}
244
+ onChange={(e) => setConfirmPassword(e.target.value)}
245
+ required
246
+ />
247
+ </div>
248
+ <Button type="submit" className="w-full" disabled={loading}>
249
+ {loading ? 'Saving...' : 'Save Password'}
250
+ </Button>
251
+ </form>
252
+ ) : (
253
+ <Tabs defaultValue="login" className="w-full" onValueChange={(value) => {
254
+ if (value === 'signup') {
255
+ navigate('/register');
256
+ }
257
+ setMagicLinkSent(false);
258
+ }}>
259
+ <TabsList className="grid w-full grid-cols-3">
260
+ <TabsTrigger value="login">Login</TabsTrigger>
261
+ <TabsTrigger value="magic">Login Link</TabsTrigger>
262
+ <TabsTrigger value="signup">Register</TabsTrigger>
263
+ </TabsList>
264
+
265
+ <TabsContent value="login">
266
+ {showResetForm ? (
267
+ resetEmailSent ? (
268
+ <div className="space-y-4 text-center py-4">
269
+ <div className="text-sm text-muted-foreground">
270
+ A password reset link has been sent to <strong>{resetEmail}</strong>.
271
+ Please check your email.
272
+ </div>
273
+ <Button
274
+ variant="outline"
275
+ className="w-full"
276
+ onClick={() => {
277
+ setShowResetForm(false);
278
+ setResetEmailSent(false);
279
+ setResetEmail('');
280
+ }}
281
+ >
282
+ Back to login
283
+ </Button>
284
+ </div>
285
+ ) : (
286
+ <form onSubmit={handlePasswordReset} className="space-y-4">
287
+ <div className="space-y-2">
288
+ <Label htmlFor="reset-email">Email</Label>
289
+ <Input
290
+ id="reset-email"
291
+ type="email"
292
+ placeholder="your@email.com"
293
+ value={resetEmail}
294
+ onChange={(e) => setResetEmail(e.target.value)}
295
+ required
296
+ />
297
+ </div>
298
+ <div className="text-sm text-muted-foreground">
299
+ We will send you a link to reset your password.
300
+ </div>
301
+ <Button type="submit" className="w-full" disabled={loading}>
302
+ {loading ? 'Sending...' : 'Send link'}
303
+ </Button>
304
+ <Button
305
+ type="button"
306
+ variant="ghost"
307
+ className="w-full"
308
+ onClick={() => setShowResetForm(false)}
309
+ >
310
+ Back to login
311
+ </Button>
312
+ </form>
313
+ )
314
+ ) : (
315
+ <form onSubmit={handleLogin} className="space-y-4">
316
+ <div className="space-y-2">
317
+ <Label htmlFor="login-email">Email</Label>
318
+ <Input
319
+ id="login-email"
320
+ type="email"
321
+ placeholder="your@email.com"
322
+ value={loginEmail}
323
+ onChange={(e) => setLoginEmail(e.target.value)}
324
+ required
325
+ />
326
+ </div>
327
+ <div className="space-y-2">
328
+ <Label htmlFor="login-password">Password</Label>
329
+ <Input
330
+ id="login-password"
331
+ type="password"
332
+ value={loginPassword}
333
+ onChange={(e) => setLoginPassword(e.target.value)}
334
+ required
335
+ />
336
+ </div>
337
+ <Button type="submit" className="w-full" disabled={loading}>
338
+ {loading ? 'Loading...' : 'Sign in'}
339
+ </Button>
340
+ <Button
341
+ type="button"
342
+ variant="link"
343
+ className="w-full text-sm"
344
+ onClick={() => setShowResetForm(true)}
345
+ >
346
+ Forgot password?
347
+ </Button>
348
+ </form>
349
+ )}
350
+ </TabsContent>
351
+
352
+ <TabsContent value="magic">
353
+ {magicLinkSent ? (
354
+ <div className="space-y-4 text-center py-4">
355
+ <div className="text-sm text-muted-foreground">
356
+ A login link has been sent to <strong>{magicLinkEmail}</strong>.
357
+ Please check your email and click the link to sign in.
358
+ </div>
359
+ <Button
360
+ variant="outline"
361
+ className="w-full"
362
+ onClick={() => {
363
+ setMagicLinkSent(false);
364
+ setMagicLinkEmail('');
365
+ }}
366
+ >
367
+ Send again
368
+ </Button>
369
+ </div>
370
+ ) : (
371
+ <form onSubmit={handleMagicLink} className="space-y-4">
372
+ <div className="space-y-2">
373
+ <Label htmlFor="magic-email">Email</Label>
374
+ <Input
375
+ id="magic-email"
376
+ type="email"
377
+ placeholder="your@email.com"
378
+ value={magicLinkEmail}
379
+ onChange={(e) => setMagicLinkEmail(e.target.value)}
380
+ required
381
+ />
382
+ </div>
383
+ <div className="text-sm text-muted-foreground">
384
+ We will send you a login link so you can sign in without a password.
385
+ </div>
386
+ <Button type="submit" className="w-full" disabled={loading}>
387
+ {loading ? 'Sending...' : 'Send login link'}
388
+ </Button>
389
+ </form>
390
+ )}
391
+ </TabsContent>
392
+ </Tabs>
393
+ )}
394
+ </CardContent>
395
+ </Card>
396
+
397
+ <Dialog open={showModal} onOpenChange={setShowModal}>
398
+ <DialogContent className="sm:max-w-md">
399
+ <DialogHeader>
400
+ <DialogTitle>Assigned Organizations</DialogTitle>
401
+ </DialogHeader>
402
+ </DialogContent>
403
+ </Dialog>
404
+ </div>
405
+ );
406
+ };
407
+
408
+ export default Auth;
@@ -0,0 +1,168 @@
1
+ import {DashboardLayout} from "@/components/layout/DashboardLayout";
2
+ import {StatCard} from "@/components/ui/stat-card";
3
+ import {Users, Calendar, ClipboardList} from "lucide-react";
4
+ import {mockClerks} from "@/data/mock-data";
5
+ import {Card, CardContent, CardHeader, CardTitle} from "@/components/ui/card";
6
+ import {Badge} from "@/components/ui/badge";
7
+ import {useActiveShiftsForDashboard} from "@/hooks/api/useShifts";
8
+ import {useActiveTasksForDashboard} from "@/hooks/api/useTasks";
9
+ import {useClerks} from "@/hooks/api/useClerks";
10
+ import {useUpcomingReservations} from "@/hooks/api/useReservations";
11
+ import {format} from "date-fns";
12
+ import {DashboardCalendar, CalendarEntry} from "@/components/calendar/Calendar";
13
+ import {useMemo, useCallback} from "react";
14
+ import {useApiContext} from "@/hooks/useApiContext";
15
+ import {registerNoShow, registerShowUp} from "@/lib/api";
16
+
17
+ export default function Dashboard() {
18
+ const ctx = useApiContext();
19
+ const {data: activeShifts = [], isLoading: shiftsLoading} = useActiveShiftsForDashboard();
20
+ const {data: activeTasks = [], isLoading: tasksLoading} = useActiveTasksForDashboard();
21
+ const {data: clerks = []} = useClerks();
22
+ const {data: upcomingReservations = []} = useUpcomingReservations();
23
+ const activeStaff = mockClerks.filter((c) => c.active).length;
24
+
25
+ const getClerkName = (clerkId: string) => {
26
+ const clerk = clerks.find((c) => c.clerkId === clerkId);
27
+ return clerk ? `${clerk.name} ${clerk.surname}` : clerkId;
28
+ };
29
+
30
+ const calendarEntries = useMemo<CalendarEntry[]>(() => {
31
+ return upcomingReservations.map((r) => ({
32
+ type: "reservation",
33
+ id: r.reservation_id,
34
+ title: r.name || r.reservation_id,
35
+ description: r.description,
36
+ start: r.start_date,
37
+ email: r.email,
38
+ phone: r.phone,
39
+ end: r.end_date,
40
+ showupRegistered: r.showup_registered,
41
+ }));
42
+ }, [upcomingReservations]);
43
+
44
+ const handleNoShow = useCallback((reservationId: string) => {
45
+ registerNoShow(reservationId, ctx).catch(console.error);
46
+ }, [ctx]);
47
+
48
+ const handleShowUp = useCallback((reservationId: string) => {
49
+ registerShowUp(reservationId, ctx).catch(console.error);
50
+ }, [ctx]);
51
+
52
+ return (
53
+ <DashboardLayout title="Overview" subtitle="Welcome back! Here is your summary.">
54
+ {/* Stats Grid */}
55
+ <div className="grid gap-6 md:grid-cols-2 lg:grid-cols-4">
56
+ <StatCard
57
+ title="Active Staff"
58
+ value={activeStaff}
59
+ icon={Users}
60
+ change={`${mockClerks.length - activeStaff} inactive`}
61
+ changeType="neutral"
62
+ />
63
+ </div>
64
+
65
+ {/* Quick Actions & Recent Activity */}
66
+ <div className="mt-8 grid gap-6 lg:grid-cols-2">
67
+ {/* Active Shifts */}
68
+ <Card className="animate-fade-in">
69
+ <CardHeader>
70
+ <CardTitle className="flex items-center gap-2 text-lg">
71
+ <Calendar className="h-5 w-5 text-primary"/>
72
+ Active Shifts Today
73
+ </CardTitle>
74
+ </CardHeader>
75
+ <CardContent>
76
+ <div className="space-y-4">
77
+ {shiftsLoading ? (
78
+ <p className="text-sm text-muted-foreground">Loading...</p>
79
+ ) : activeShifts.length === 0 ? (
80
+ <p className="text-sm text-muted-foreground">No active shifts</p>
81
+ ) : (
82
+ activeShifts.map((shift) => (
83
+ <div
84
+ key={shift.shiftId}
85
+ className="flex items-center justify-between rounded-lg border border-border bg-muted/30 p-4"
86
+ >
87
+ <div>
88
+ <p className="font-medium text-foreground">{shift.name}</p>
89
+ <p className="text-sm text-muted-foreground">{shift.fromTo}</p>
90
+ {shift.assignees.length > 0 && (
91
+ <p className="mt-1 text-xs text-muted-foreground">
92
+ {shift.assignees.map(getClerkName).join(", ")}
93
+ </p>
94
+ )}
95
+ </div>
96
+ <Badge variant="secondary" className="bg-success/10 text-success">
97
+ Active
98
+ </Badge>
99
+ </div>
100
+ ))
101
+ )}
102
+ </div>
103
+ </CardContent>
104
+ </Card>
105
+ </div>
106
+
107
+ {/* Pending Tasks */}
108
+ <Card className="mt-6 animate-fade-in">
109
+ <CardHeader>
110
+ <CardTitle className="flex items-center gap-2 text-lg">
111
+ <ClipboardList className="h-5 w-5 text-primary"/>
112
+ Pending Tasks
113
+ </CardTitle>
114
+ </CardHeader>
115
+ <CardContent>
116
+ {tasksLoading ? (
117
+ <p className="text-sm text-muted-foreground">Loading...</p>
118
+ ) : activeTasks.length === 0 ? (
119
+ <p className="text-sm text-muted-foreground">No active tasks</p>
120
+ ) : (
121
+ <div className="overflow-x-auto">
122
+ <table className="w-full">
123
+ <thead>
124
+ <tr className="border-b border-border">
125
+ <th className="pb-3 text-left text-sm font-medium text-muted-foreground">Task</th>
126
+ <th className="pb-3 text-left text-sm font-medium text-muted-foreground">Assigned to</th>
127
+ <th className="pb-3 text-left text-sm font-medium text-muted-foreground">Due date</th>
128
+ <th className="pb-3 text-left text-sm font-medium text-muted-foreground">Recurrence</th>
129
+ </tr>
130
+ </thead>
131
+ <tbody>
132
+ {activeTasks.map((task) => (
133
+ <tr key={task.taskId} className="border-b border-border/50 last:border-0">
134
+ <td className="py-4">
135
+ <p className="font-medium text-foreground">{task.title}</p>
136
+ <p className="text-sm text-muted-foreground">{task.description}</p>
137
+ </td>
138
+ <td className="py-4 text-foreground">
139
+ {task.assignedClerk ? getClerkName(task.assignedClerk) : "Unassigned"}
140
+ </td>
141
+ <td className="py-4 text-foreground">
142
+ {task.date ? format(new Date(task.date), "MM/dd/yyyy") : "–"}
143
+ </td>
144
+ <td className="py-4">
145
+ {task.repeats ? (
146
+ <Badge variant="outline">{task.repeats}</Badge>
147
+ ) : (
148
+ <span className="text-sm text-muted-foreground">Once</span>
149
+ )}
150
+ </td>
151
+ </tr>
152
+ ))}
153
+ </tbody>
154
+ </table>
155
+ </div>
156
+ )}
157
+ </CardContent>
158
+ </Card>
159
+
160
+ <DashboardCalendar
161
+ entries={calendarEntries}
162
+ onAppointmentClick={() => {}}
163
+ onNoShow={handleNoShow}
164
+ onShowUp={handleShowUp}
165
+ />
166
+ </DashboardLayout>
167
+ );
168
+ }