@nebulit/embuilder 0.1.45 → 0.1.46
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/package.json +1 -1
- package/templates/.claude/skills/slice-state-view/SKILL.md +0 -6
- package/templates/frontend/prompt.md +1 -1
- package/templates/frontend/setup-env.sh +43 -0
- package/templates/frontend/src/App.tsx +12 -24
- package/templates/frontend/src/pages/Dashboard.tsx +7 -144
- package/templates/.claude/skills/sample-slices/templates/.slices/Library/addbook/code-slice.json +0 -124
package/package.json
CHANGED
|
@@ -11,12 +11,6 @@ If the processors-array in the slice json is not empty. Treat this as an AUTOMAT
|
|
|
11
11
|
|
|
12
12
|
## Critical Requirements
|
|
13
13
|
|
|
14
|
-
### Restaurant ID Requirement
|
|
15
|
-
- **CRITICAL**: ALL database tables MUST have a `restaurant_id` column (snake_case)
|
|
16
|
-
- **CRITICAL**: ALL events MUST have `restaurantId` in their metadata (camelCase)
|
|
17
|
-
- **NEVER** use `locationId` or `location_id` - these are outdated and forbidden
|
|
18
|
-
- This ensures proper multi-tenancy and data isolation
|
|
19
|
-
|
|
20
14
|
## Implementation Steps
|
|
21
15
|
|
|
22
16
|
When creating a state-view slice, you MUST create the following files:
|
|
@@ -77,7 +77,7 @@ Slice C: { groupId: "123", sliceType: "STATE_CHANGE", title: "Cancel Event" }
|
|
|
77
77
|
```typescript
|
|
78
78
|
// AuthContext: user, session, restaurantId
|
|
79
79
|
import { useAuth } from "@/contexts/AuthContext";
|
|
80
|
-
const { user, session
|
|
80
|
+
const { user, session } = useAuth();
|
|
81
81
|
|
|
82
82
|
// ApiContext: token, restaurantId, userId (auto-injected into headers)
|
|
83
83
|
import { useApiContext } from "@/hooks/useApiContext";
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
prompt() {
|
|
5
|
+
local var_name="$1"
|
|
6
|
+
local prompt_text="$2"
|
|
7
|
+
local value
|
|
8
|
+
read -rp "$prompt_text: " value
|
|
9
|
+
if [[ -z "$value" ]]; then
|
|
10
|
+
echo "Error: $var_name cannot be empty." >&2
|
|
11
|
+
exit 1
|
|
12
|
+
fi
|
|
13
|
+
echo "$value"
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
prompt_secret() {
|
|
17
|
+
local var_name="$1"
|
|
18
|
+
local prompt_text="$2"
|
|
19
|
+
local value
|
|
20
|
+
read -rsp "$prompt_text: " value
|
|
21
|
+
echo "" >&2
|
|
22
|
+
if [[ -z "$value" ]]; then
|
|
23
|
+
echo "Error: $var_name cannot be empty." >&2
|
|
24
|
+
exit 1
|
|
25
|
+
fi
|
|
26
|
+
echo "$value"
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
echo "=== Supabase .env setup ==="
|
|
30
|
+
echo ""
|
|
31
|
+
|
|
32
|
+
PROJECT_ID=$(prompt SUPABASE_PROJECT_ID "Supabase Project ID")
|
|
33
|
+
DB_PASSWORD=$(prompt_secret SUPABASE_DB_PASSWORD "Database Password")
|
|
34
|
+
PUBLISHABLE_KEY=$(prompt_secret SUPABASE_PUBLISHABLE_KEY "Supabase Publishable Key")
|
|
35
|
+
#SECRET_KEY=$(prompt_secret SUPABASE_SECRET_KEY "Supabase Secret Key")
|
|
36
|
+
|
|
37
|
+
cat > .env.development <<EOF
|
|
38
|
+
VITE_SUPABASE_URL=https://${PROJECT_ID}.supabase.co
|
|
39
|
+
VITE_SUPABASE_PUBLISHABLE_KEY=${PUBLISHABLE_KEY}
|
|
40
|
+
VITE_SUPABASE_DB_URL=postgresql://postgres.${PROJECT_ID}:${DB_PASSWORD}@aws-1-eu-central-1.pooler.supabase.com:5432/postgres?prepareThreshold=0
|
|
41
|
+
|
|
42
|
+
echo ""
|
|
43
|
+
echo ".env.development created successfully."
|
|
@@ -6,12 +6,6 @@ import {AuthProvider} from "@/contexts/AuthContext";
|
|
|
6
6
|
import {QueryClient, QueryClientProvider} from "@tanstack/react-query";
|
|
7
7
|
import {BrowserRouter, Routes, Route} from "react-router-dom";
|
|
8
8
|
import Dashboard from "./pages/Dashboard";
|
|
9
|
-
import Tables from "./pages/Tables";
|
|
10
|
-
import Staff from "./pages/Staff";
|
|
11
|
-
import Shifts from "./pages/Shifts";
|
|
12
|
-
import Tasks from "./pages/Tasks";
|
|
13
|
-
import Menus from "./pages/Menus";
|
|
14
|
-
import Vacations from "./pages/Vacations";
|
|
15
9
|
import NotFound from "./pages/NotFound";
|
|
16
10
|
import Auth from "@/pages/Auth.tsx";
|
|
17
11
|
import Register from "@/pages/Register.tsx";
|
|
@@ -22,24 +16,18 @@ const queryClient = new QueryClient();
|
|
|
22
16
|
const App = () => (
|
|
23
17
|
<QueryClientProvider client={queryClient}>
|
|
24
18
|
<AuthProvider>
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
<Route path="/menus" element={<ProtectedRoute><Menus/></ProtectedRoute>}/>
|
|
38
|
-
<Route path="/vacations" element={<ProtectedRoute><Vacations/></ProtectedRoute>}/>
|
|
39
|
-
<Route path="*" element={<NotFound/>}/>
|
|
40
|
-
</Routes>
|
|
41
|
-
</BrowserRouter>
|
|
42
|
-
</TooltipProvider>
|
|
19
|
+
<TooltipProvider>
|
|
20
|
+
<Toaster/>
|
|
21
|
+
<Sonner/>
|
|
22
|
+
<BrowserRouter>
|
|
23
|
+
<Routes>
|
|
24
|
+
<Route path="/register" element={<Register/>}/>
|
|
25
|
+
<Route path="/auth" element={<Auth/>}/>
|
|
26
|
+
<Route path="/" element={<ProtectedRoute><Dashboard/></ProtectedRoute>}/>
|
|
27
|
+
<Route path="*" element={<NotFound/>}/>
|
|
28
|
+
</Routes>
|
|
29
|
+
</BrowserRouter>
|
|
30
|
+
</TooltipProvider>
|
|
43
31
|
</AuthProvider>
|
|
44
32
|
</QueryClientProvider>
|
|
45
33
|
);
|
|
@@ -1,168 +1,31 @@
|
|
|
1
1
|
import {DashboardLayout} from "@/components/layout/DashboardLayout";
|
|
2
|
-
import {
|
|
3
|
-
import {Users, Calendar, ClipboardList} from "lucide-react";
|
|
4
|
-
import {mockClerks} from "@/data/mock-data";
|
|
2
|
+
import {Calendar, ClipboardList} from "lucide-react";
|
|
5
3
|
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
4
|
import {useApiContext} from "@/hooks/useApiContext";
|
|
15
|
-
import {registerNoShow, registerShowUp} from "@/lib/api";
|
|
16
5
|
|
|
17
6
|
export default function Dashboard() {
|
|
18
7
|
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;
|
|
8
|
+
//const {data: activeShifts = [], isLoading: shiftsLoading} = useActiveShiftsForDashboard();
|
|
24
9
|
|
|
25
|
-
const
|
|
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) => {
|
|
10
|
+
/*const handleNoShow = useCallback((reservationId: string) => {
|
|
45
11
|
registerNoShow(reservationId, ctx).catch(console.error);
|
|
46
|
-
}, [ctx])
|
|
47
|
-
|
|
48
|
-
const handleShowUp = useCallback((reservationId: string) => {
|
|
49
|
-
registerShowUp(reservationId, ctx).catch(console.error);
|
|
50
|
-
}, [ctx]);
|
|
12
|
+
}, [ctx]);*/
|
|
51
13
|
|
|
52
14
|
return (
|
|
53
15
|
<DashboardLayout title="Overview" subtitle="Welcome back! Here is your summary.">
|
|
54
|
-
|
|
55
|
-
|
|
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 */}
|
|
16
|
+
<div></div>
|
|
17
|
+
{/* Quick Actions & Recent Activity
|
|
66
18
|
<div className="mt-8 grid gap-6 lg:grid-cols-2">
|
|
67
|
-
{/* Active Shifts */}
|
|
68
19
|
<Card className="animate-fade-in">
|
|
69
20
|
<CardHeader>
|
|
70
21
|
<CardTitle className="flex items-center gap-2 text-lg">
|
|
71
22
|
<Calendar className="h-5 w-5 text-primary"/>
|
|
72
|
-
Active Shifts Today
|
|
73
23
|
</CardTitle>
|
|
74
24
|
</CardHeader>
|
|
75
25
|
<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
26
|
</CardContent>
|
|
104
27
|
</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
|
-
/>
|
|
28
|
+
</div>*/}
|
|
166
29
|
</DashboardLayout>
|
|
167
30
|
);
|
|
168
31
|
}
|
package/templates/.claude/skills/sample-slices/templates/.slices/Library/addbook/code-slice.json
DELETED
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"id": "3458764661593137476",
|
|
3
|
-
"title": "slice: Add Book",
|
|
4
|
-
"specifications": [
|
|
5
|
-
{
|
|
6
|
-
"vertical": false,
|
|
7
|
-
"id": "GENERATED-SPEC-001",
|
|
8
|
-
"sliceName": "slice: Add Book",
|
|
9
|
-
"type": "COMMAND",
|
|
10
|
-
"title": "should create BookAdded event when adding a book",
|
|
11
|
-
"given": [],
|
|
12
|
-
"when": [
|
|
13
|
-
{
|
|
14
|
-
"id": "GENERATED-WHEN-001",
|
|
15
|
-
"linkedId": "3458764661591564785",
|
|
16
|
-
"title": "Add Book",
|
|
17
|
-
"type": "COMMAND",
|
|
18
|
-
"fields": [
|
|
19
|
-
{
|
|
20
|
-
"name": "title",
|
|
21
|
-
"type": "String",
|
|
22
|
-
"cardinality": "Single",
|
|
23
|
-
"example": "Harry Potter",
|
|
24
|
-
"subfields": [],
|
|
25
|
-
"idAttribute": false
|
|
26
|
-
},
|
|
27
|
-
{
|
|
28
|
-
"name": "author",
|
|
29
|
-
"type": "String",
|
|
30
|
-
"cardinality": "Single",
|
|
31
|
-
"example": "J.K. Rowling",
|
|
32
|
-
"subfields": [],
|
|
33
|
-
"idAttribute": false
|
|
34
|
-
},
|
|
35
|
-
{
|
|
36
|
-
"name": "description",
|
|
37
|
-
"type": "String",
|
|
38
|
-
"cardinality": "Single",
|
|
39
|
-
"example": "A magical adventure",
|
|
40
|
-
"subfields": [],
|
|
41
|
-
"idAttribute": false
|
|
42
|
-
},
|
|
43
|
-
{
|
|
44
|
-
"name": "isbn",
|
|
45
|
-
"type": "String",
|
|
46
|
-
"cardinality": "Single",
|
|
47
|
-
"example": "1234567890",
|
|
48
|
-
"subfields": [],
|
|
49
|
-
"idAttribute": false
|
|
50
|
-
},
|
|
51
|
-
{
|
|
52
|
-
"name": "user",
|
|
53
|
-
"type": "String",
|
|
54
|
-
"cardinality": "Single",
|
|
55
|
-
"example": "john@example.com",
|
|
56
|
-
"subfields": [],
|
|
57
|
-
"idAttribute": false
|
|
58
|
-
}
|
|
59
|
-
]
|
|
60
|
-
}
|
|
61
|
-
],
|
|
62
|
-
"then": [
|
|
63
|
-
{
|
|
64
|
-
"id": "GENERATED-THEN-001",
|
|
65
|
-
"linkedId": "3458764661586330620",
|
|
66
|
-
"title": "Book added",
|
|
67
|
-
"type": "SPEC_EVENT",
|
|
68
|
-
"fields": [
|
|
69
|
-
{
|
|
70
|
-
"name": "id",
|
|
71
|
-
"type": "String",
|
|
72
|
-
"cardinality": "Single",
|
|
73
|
-
"subfields": [],
|
|
74
|
-
"idAttribute": false,
|
|
75
|
-
"generated": true
|
|
76
|
-
},
|
|
77
|
-
{
|
|
78
|
-
"name": "title",
|
|
79
|
-
"type": "String",
|
|
80
|
-
"cardinality": "Single",
|
|
81
|
-
"example": "Harry Potter",
|
|
82
|
-
"subfields": [],
|
|
83
|
-
"idAttribute": false
|
|
84
|
-
},
|
|
85
|
-
{
|
|
86
|
-
"name": "author",
|
|
87
|
-
"type": "String",
|
|
88
|
-
"cardinality": "Single",
|
|
89
|
-
"example": "J.K. Rowling",
|
|
90
|
-
"subfields": [],
|
|
91
|
-
"idAttribute": false
|
|
92
|
-
},
|
|
93
|
-
{
|
|
94
|
-
"name": "description",
|
|
95
|
-
"type": "String",
|
|
96
|
-
"cardinality": "Single",
|
|
97
|
-
"example": "A magical adventure",
|
|
98
|
-
"subfields": [],
|
|
99
|
-
"idAttribute": false
|
|
100
|
-
},
|
|
101
|
-
{
|
|
102
|
-
"name": "isbn",
|
|
103
|
-
"type": "String",
|
|
104
|
-
"cardinality": "Single",
|
|
105
|
-
"example": "1234567890",
|
|
106
|
-
"subfields": [],
|
|
107
|
-
"idAttribute": false
|
|
108
|
-
},
|
|
109
|
-
{
|
|
110
|
-
"name": "user",
|
|
111
|
-
"type": "String",
|
|
112
|
-
"cardinality": "Single",
|
|
113
|
-
"example": "john@example.com",
|
|
114
|
-
"subfields": [],
|
|
115
|
-
"idAttribute": false
|
|
116
|
-
}
|
|
117
|
-
]
|
|
118
|
-
}
|
|
119
|
-
],
|
|
120
|
-
"comments": [],
|
|
121
|
-
"linkedId": "GENERATED-SPEC-001"
|
|
122
|
-
}
|
|
123
|
-
]
|
|
124
|
-
}
|