mirror of
https://github.com/Dvorinka/Bookra.git
synced 2026-10-28 15:51:15 +00:00
fix: backfill memberships, handle booking create errors
- Migration 00012: populate tenant_memberships from tenant_users join users, not only the seeded demo owner (Codex P1) - bookings-page: check response.error so 4xx failures do not close the modal silently; refetch dashboard summary after create (Codex P2) Authored By: TDvorak <[email protected]> Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
f2f7439050
commit
50c8440a06
3 files changed
+12
-7
No files matched your search
@@ -30,12 +30,13 @@ WHERE manage_token IS NULL;
|
|||||||
ALTER TABLE locations
|
ALTER TABLE locations
|
||||||
ADD COLUMN IF NOT EXISTS updated_at timestamptz NOT NULL DEFAULT now();
|
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)
|
INSERT INTO tenant_memberships (tenant_id, user_neon_subject, role)
|
||||||
SELECT t.id, u.neon_subject, 'owner'
|
SELECT tu.tenant_id, u.neon_subject, tu.role
|
||||||
FROM tenants t
|
FROM tenant_users tu
|
||||||
JOIN users u ON u.neon_subject = 'demo-owner'
|
JOIN users u ON u.id = tu.user_id
|
||||||
WHERE t.slug = 'studio-atelier'
|
WHERE u.neon_subject IS NOT NULL
|
||||||
ON CONFLICT (tenant_id, user_neon_subject) DO NOTHING;
|
ON CONFLICT (tenant_id, user_neon_subject) DO NOTHING;
|
||||||
|
|
||||||
-- +goose Down
|
-- +goose Down
|
||||||
|
|||||||
@@ -72,15 +72,17 @@ function BookingsPage() {
|
|||||||
}
|
}
|
||||||
setCreatingBooking(true);
|
setCreatingBooking(true);
|
||||||
try {
|
try {
|
||||||
await (apiClient as any).POST("/v1/catalog/bookings", {
|
const response = await (apiClient as any).POST("/v1/catalog/bookings", {
|
||||||
headers: { Authorization: `Bearer ${bearer}` },
|
headers: { Authorization: `Bearer ${bearer}` },
|
||||||
body: {
|
body: {
|
||||||
customerName: newBookingCustomer(), customerEmail: newBookingEmail(), service: newBookingService(),
|
customerName: newBookingCustomer(), customerEmail: newBookingEmail(), service: newBookingService(),
|
||||||
startsAt: new Date(`${newBookingDate()}T${newBookingTime()}`).toISOString(), notes: newBookingNotes()
|
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("");
|
setNewBookingCustomer(""); setNewBookingEmail(""); setNewBookingService(""); setNewBookingDate(""); setNewBookingTime(""); setNewBookingNotes("");
|
||||||
setShowNewBooking(false);
|
setShowNewBooking(false);
|
||||||
|
void data.refetchSummary();
|
||||||
} catch { data.setDemoNotice(data.i18n.locale() === "cs" ? "Vytvoření rezervace selhalo." : "Failed to create booking."); }
|
} catch { data.setDemoNotice(data.i18n.locale() === "cs" ? "Vytvoření rezervace selhalo." : "Failed to create booking."); }
|
||||||
finally { setCreatingBooking(false); }
|
finally { setCreatingBooking(false); }
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -94,6 +94,7 @@ export interface DashboardData {
|
|||||||
token: () => string | null | undefined;
|
token: () => string | null | undefined;
|
||||||
isDemoMode: () => boolean;
|
isDemoMode: () => boolean;
|
||||||
resolvedSummary: () => any;
|
resolvedSummary: () => any;
|
||||||
|
refetchSummary: () => void;
|
||||||
resolvedBootstrap: () => any;
|
resolvedBootstrap: () => any;
|
||||||
resolvedBilling: () => any;
|
resolvedBilling: () => any;
|
||||||
normalizedKpis: () => any[];
|
normalizedKpis: () => any[];
|
||||||
@@ -150,7 +151,7 @@ export function DashboardDataProvider(props: { children: any }) {
|
|||||||
const isDemoMode = () => token()?.startsWith("demo.") ?? false;
|
const isDemoMode = () => token()?.startsWith("demo.") ?? false;
|
||||||
const demoData = createMemo(() => createDemoData(i18n));
|
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) return null;
|
||||||
if (bearer.startsWith("demo.")) return demoData().summary;
|
if (bearer.startsWith("demo.")) return demoData().summary;
|
||||||
const response = await apiClient.GET("/v1/dashboard/summary", { headers: { Authorization: `Bearer ${bearer}` } });
|
const response = await apiClient.GET("/v1/dashboard/summary", { headers: { Authorization: `Bearer ${bearer}` } });
|
||||||
@@ -286,6 +287,7 @@ export function DashboardDataProvider(props: { children: any }) {
|
|||||||
token: () => token(),
|
token: () => token(),
|
||||||
isDemoMode,
|
isDemoMode,
|
||||||
resolvedSummary,
|
resolvedSummary,
|
||||||
|
refetchSummary,
|
||||||
resolvedBootstrap,
|
resolvedBootstrap,
|
||||||
resolvedBilling,
|
resolvedBilling,
|
||||||
normalizedKpis,
|
normalizedKpis,
|
||||||
|
|||||||
Reference in new issue
Block a user