diff --git a/apps/backend/migrations/00012_memberships_and_manage_token.sql b/apps/backend/migrations/00012_memberships_and_manage_token.sql index 013e2f5..c73f2f7 100644 --- a/apps/backend/migrations/00012_memberships_and_manage_token.sql +++ b/apps/backend/migrations/00012_memberships_and_manage_token.sql @@ -30,12 +30,13 @@ WHERE manage_token IS NULL; ALTER TABLE locations ADD COLUMN IF NOT EXISTS updated_at timestamptz NOT NULL DEFAULT now(); --- Demo tenant membership so demo-owner resolves to Studio Atelier. +-- Backfill memberships for every existing user linked via tenant_users. +-- Covers the seeded demo owner (users.neon_subject = 'demo-owner'). INSERT INTO tenant_memberships (tenant_id, user_neon_subject, role) -SELECT t.id, u.neon_subject, 'owner' -FROM tenants t -JOIN users u ON u.neon_subject = 'demo-owner' -WHERE t.slug = 'studio-atelier' +SELECT tu.tenant_id, u.neon_subject, tu.role +FROM tenant_users tu +JOIN users u ON u.id = tu.user_id +WHERE u.neon_subject IS NOT NULL ON CONFLICT (tenant_id, user_neon_subject) DO NOTHING; -- +goose Down diff --git a/apps/frontend/src/routes/dashboard/bookings-page.tsx b/apps/frontend/src/routes/dashboard/bookings-page.tsx index 5d8a0d1..946e729 100644 --- a/apps/frontend/src/routes/dashboard/bookings-page.tsx +++ b/apps/frontend/src/routes/dashboard/bookings-page.tsx @@ -72,15 +72,17 @@ function BookingsPage() { } setCreatingBooking(true); try { - await (apiClient as any).POST("/v1/catalog/bookings", { + const response = await (apiClient as any).POST("/v1/catalog/bookings", { headers: { Authorization: `Bearer ${bearer}` }, body: { customerName: newBookingCustomer(), customerEmail: newBookingEmail(), service: newBookingService(), startsAt: new Date(`${newBookingDate()}T${newBookingTime()}`).toISOString(), notes: newBookingNotes() } }); + if (response.error) throw new Error(typeof response.error === "string" ? response.error : "booking create failed"); setNewBookingCustomer(""); setNewBookingEmail(""); setNewBookingService(""); setNewBookingDate(""); setNewBookingTime(""); setNewBookingNotes(""); setShowNewBooking(false); + void data.refetchSummary(); } catch { data.setDemoNotice(data.i18n.locale() === "cs" ? "Vytvoření rezervace selhalo." : "Failed to create booking."); } finally { setCreatingBooking(false); } }; diff --git a/apps/frontend/src/routes/dashboard/dashboard-data.ts b/apps/frontend/src/routes/dashboard/dashboard-data.ts index f7bb7f2..fd829bc 100644 --- a/apps/frontend/src/routes/dashboard/dashboard-data.ts +++ b/apps/frontend/src/routes/dashboard/dashboard-data.ts @@ -94,6 +94,7 @@ export interface DashboardData { token: () => string | null | undefined; isDemoMode: () => boolean; resolvedSummary: () => any; + refetchSummary: () => void; resolvedBootstrap: () => any; resolvedBilling: () => any; normalizedKpis: () => any[]; @@ -150,7 +151,7 @@ export function DashboardDataProvider(props: { children: any }) { const isDemoMode = () => token()?.startsWith("demo.") ?? false; const demoData = createMemo(() => createDemoData(i18n)); - const [summary] = createResource(token, async (bearer) => { + const [summary, { refetch: refetchSummary }] = createResource(token, async (bearer) => { if (!bearer) return null; if (bearer.startsWith("demo.")) return demoData().summary; const response = await apiClient.GET("/v1/dashboard/summary", { headers: { Authorization: `Bearer ${bearer}` } }); @@ -286,6 +287,7 @@ export function DashboardDataProvider(props: { children: any }) { token: () => token(), isDemoMode, resolvedSummary, + refetchSummary, resolvedBootstrap, resolvedBilling, normalizedKpis,