diff --git a/FRONTEND_ISSUES.md b/FRONTEND_ISSUES.md index e0daede..0edf6a5 100644 --- a/FRONTEND_ISSUES.md +++ b/FRONTEND_ISSUES.md @@ -25,7 +25,7 @@ ## Still Needs Frontend Polish -- Dashboard inline i18n ternaries remain (~60 as of M30). Systematic extraction to `i18n.t()` keys is an ongoing task. +- Dashboard inline i18n ternaries: shared chrome strings extracted to `dashboard.*` keys (demo notices, save/delete failures, delete aria-labels, zone/blocked-day copy). ~56 `cs() ? ...` remain project-wide, of which the dashboard remainder is demo fixture names, `cs-CZ`/`en-US` locale tags, and the `zón`/`locations` plural — all correct inline (Czech plurals need count-aware forms, not a single key). - Registration cannot be fully customer-tested locally until Neon Auth environment variables are configured. ## Fixed This Session (platform audit pass) diff --git a/apps/frontend/src/providers/i18n-provider.tsx b/apps/frontend/src/providers/i18n-provider.tsx index 2d487d7..5645472 100644 --- a/apps/frontend/src/providers/i18n-provider.tsx +++ b/apps/frontend/src/providers/i18n-provider.tsx @@ -551,6 +551,23 @@ const dictionaries = { "dashboard.notification.reminder": "Připomínka: rezervace u", "dashboard.notification.upgrade": "Blížíte se limitu plánu", "dashboard.notification.trialEnding": "Vaše zkušební doba končí za 3 dny", + "dashboard.demo.noSave": "V demo režimu nelze ukládat.", + "dashboard.demo.noDelete": "V demo režimu nelze mazat.", + "dashboard.demo.noModify": "V demo režimu nelze měnit rezervace.", + "dashboard.action.delete": "Smazat", + "dashboard.loading": "Načítání…", + "dashboard.saveFailed": "Uložení selhalo.", + "dashboard.deleteFailed": "Smazání selhalo.", + "dashboard.availability.hint": "Nastavte dny a časy, kdy přijímáte rezervace.", + "dashboard.blockedDay.hint": "Dny, kdy nejsou dostupné rezervace.", + "dashboard.blockedDay.empty": "Zatím žádné blokované dny.", + "dashboard.blockedDay.placeholder": "např. Státní svátek", + "dashboard.blockedDay.date": "Datum", + "dashboard.day.closed": "Zavřeno", + "dashboard.day.open": "Otevřeno", + "dashboard.zone.addFirst": "Přidejte první zónu nebo místnost.", + "dashboard.zone.limitHint": "Váš plán umožňuje pouze omezený počet lokací. Pro více lokací upgradujte.", + "dashboard.zone.saveFailed": "Uložení zóny selhalo.", "booking.title": "Rezervace", "booking.body": "Vyberte dostupný termín, doplňte kontaktní údaje a potvrzení přijde e-mailem.", "booking.slots": "Dostupné termíny", @@ -1190,6 +1207,23 @@ const dictionaries = { "dashboard.notification.reminder": "Reminder: booking at", "dashboard.notification.upgrade": "You're nearing your plan limit", "dashboard.notification.trialEnding": "Your trial ends in 3 days", + "dashboard.demo.noSave": "Cannot save in demo mode.", + "dashboard.demo.noDelete": "Cannot delete in demo mode.", + "dashboard.demo.noModify": "Cannot modify bookings in demo mode.", + "dashboard.action.delete": "Delete", + "dashboard.loading": "Loading…", + "dashboard.saveFailed": "Save failed.", + "dashboard.deleteFailed": "Delete failed.", + "dashboard.availability.hint": "Set the days and times you accept bookings.", + "dashboard.blockedDay.hint": "Days when bookings are unavailable.", + "dashboard.blockedDay.empty": "No blocked days yet.", + "dashboard.blockedDay.placeholder": "e.g. Public holiday", + "dashboard.blockedDay.date": "Date", + "dashboard.day.closed": "Closed", + "dashboard.day.open": "Open", + "dashboard.zone.addFirst": "Add your first location or room.", + "dashboard.zone.limitHint": "Your plan only allows a limited number of locations. Upgrade for more.", + "dashboard.zone.saveFailed": "Failed to save location.", "booking.title": "Book a visit", "booking.body": "Choose an available time, add your contact details, and receive confirmation by email.", "booking.slots": "Available times", diff --git a/apps/frontend/src/routes/dashboard/services-page.tsx b/apps/frontend/src/routes/dashboard/services-page.tsx index 3a76a1b..9d0a79b 100644 --- a/apps/frontend/src/routes/dashboard/services-page.tsx +++ b/apps/frontend/src/routes/dashboard/services-page.tsx @@ -141,7 +141,7 @@ function ServicesPage() { const duration = parseInt(form().durationMinutes, 10); if (!name || !duration || duration <= 0) return; if (!bearer || bearer.startsWith("demo.")) { - data.setDemoNotice(cs() ? "V demo režimu nelze ukládat." : "Cannot save in demo mode."); + data.setDemoNotice(data.i18n.t("dashboard.demo.noSave")); return; } setSaving(true); @@ -180,7 +180,7 @@ function ServicesPage() { const handleDelete = async (id: string) => { const bearer = data.token(); if (!bearer || bearer.startsWith("demo.")) { - data.setDemoNotice(cs() ? "V demo režimu nelze mazat." : "Cannot delete in demo mode."); + data.setDemoNotice(data.i18n.t("dashboard.demo.noDelete")); return; } try { @@ -287,7 +287,7 @@ function ServicesPage() { setIntake(i(), { required: e.currentTarget.checked })} class="rounded accent-accent" /> {data.i18n.t("dashboard.service.intakeRequired")} - + )} @@ -304,7 +304,7 @@ function ServicesPage() { {cs() ? "Načítání…" : "Loading…"} +
{data.i18n.t("dashboard.loading")}
}> 0} fallback={
@@ -336,7 +336,7 @@ function ServicesPage() { -
diff --git a/apps/frontend/src/routes/dashboard/staff-page.tsx b/apps/frontend/src/routes/dashboard/staff-page.tsx index e281d04..e468efa 100644 --- a/apps/frontend/src/routes/dashboard/staff-page.tsx +++ b/apps/frontend/src/routes/dashboard/staff-page.tsx @@ -114,7 +114,7 @@ function StaffPage() { const name = form().displayName.trim(); if (!name) return; if (!bearer || bearer.startsWith("demo.")) { - data.setDemoNotice(cs() ? "V demo režimu nelze ukládat." : "Cannot save in demo mode."); + data.setDemoNotice(data.i18n.t("dashboard.demo.noSave")); return; } setSaving(true); @@ -146,7 +146,7 @@ function StaffPage() { const handleToggleActive = async (member: any) => { const bearer = data.token(); if (!bearer || bearer.startsWith("demo.")) { - data.setDemoNotice(cs() ? "V demo režimu nelze ukládat." : "Cannot save in demo mode."); + data.setDemoNotice(data.i18n.t("dashboard.demo.noSave")); return; } try { @@ -164,7 +164,7 @@ function StaffPage() { const handleDelete = async (id: string) => { const bearer = data.token(); if (!bearer || bearer.startsWith("demo.")) { - data.setDemoNotice(cs() ? "V demo režimu nelze mazat." : "Cannot delete in demo mode."); + data.setDemoNotice(data.i18n.t("dashboard.demo.noDelete")); return; } try { @@ -254,7 +254,7 @@ function StaffPage() {
{cs() ? "Načítání…" : "Loading…"} +
{data.i18n.t("dashboard.loading")}
}> 0} fallback={
@@ -294,7 +294,7 @@ function StaffPage() { -
diff --git a/apps/frontend/src/routes/dashboard/zones-page.tsx b/apps/frontend/src/routes/dashboard/zones-page.tsx index 16adfc9..d03f5f2 100644 --- a/apps/frontend/src/routes/dashboard/zones-page.tsx +++ b/apps/frontend/src/routes/dashboard/zones-page.tsx @@ -203,7 +203,7 @@ function ZonesPage() { const handlePromote = async (bookingId: string, allowOverCapacity: boolean) => { const bearer = data.token(); if (!bearer || bearer.startsWith("demo.")) { - data.setDemoNotice(cs() ? "V demo režimu nelze měnit rezervace." : "Cannot modify bookings in demo mode."); + data.setDemoNotice(data.i18n.t("dashboard.demo.noModify")); return; } setPromoteBusy(bookingId); @@ -237,7 +237,7 @@ function ZonesPage() { if (!newZoneName().trim()) return; if (!canAddLocation()) { setShowUpgradePrompt(true); setShowAddZone(false); return; } if (!bearer || bearer.startsWith("demo.")) { - data.setDemoNotice(cs() ? "V demo režimu nelze ukládat." : "Cannot save in demo mode."); + data.setDemoNotice(data.i18n.t("dashboard.demo.noSave")); return; } try { @@ -248,25 +248,25 @@ function ZonesPage() { setNewZoneName(""); setNewZoneType("room"); setNewZoneCapacity(10); setShowAddZone(false); void refetchLocations(); } catch { - data.setDemoNotice(cs() ? "Uložení zóny selhalo." : "Failed to save location."); + data.setDemoNotice(data.i18n.t("dashboard.zone.saveFailed")); } }; const handleDeleteZone = async (id: string) => { const bearer = data.token(); - if (!bearer || bearer.startsWith("demo.")) { data.setDemoNotice(cs() ? "V demo režimu nelze mazat." : "Cannot delete in demo mode."); return; } + if (!bearer || bearer.startsWith("demo.")) { data.setDemoNotice(data.i18n.t("dashboard.demo.noDelete")); return; } try { await (apiClient as any).DELETE(`/v1/catalog/locations/${id}`, authHeaders()); void refetchLocations(); } catch { - data.setDemoNotice(cs() ? "Smazání selhalo." : "Delete failed."); + data.setDemoNotice(data.i18n.t("dashboard.deleteFailed")); } }; const handleAddBlockedDay = async () => { const bearer = data.token(); if (!newBlockedDate()) return; - if (!bearer || bearer.startsWith("demo.")) { data.setDemoNotice(cs() ? "V demo režimu nelze ukládat." : "Cannot save in demo mode."); return; } + if (!bearer || bearer.startsWith("demo.")) { data.setDemoNotice(data.i18n.t("dashboard.demo.noSave")); return; } setBlockedSaving(true); try { await (apiClient as any).POST("/v1/catalog/blocked-days", { @@ -274,14 +274,14 @@ function ZonesPage() { body: { date: new Date(`${newBlockedDate()}T00:00:00`).toISOString(), type: "closure", - reason: newBlockedReason().trim() || (cs() ? "Zavřeno" : "Closed"), + reason: newBlockedReason().trim() || (data.i18n.t("dashboard.day.closed")), ...(newBlockedStaffId() ? { staffId: newBlockedStaffId() } : {}), }, }); setNewBlockedDate(""); setNewBlockedReason(""); setNewBlockedStaffId(""); void refetchBlocked(); } catch { - data.setDemoNotice(cs() ? "Uložení selhalo." : "Save failed."); + data.setDemoNotice(data.i18n.t("dashboard.saveFailed")); } finally { setBlockedSaving(false); } @@ -298,7 +298,7 @@ function ZonesPage() { const handleSaveHours = async (day: { dayOfWeek: number; open: string; close: string; isOpen: boolean }) => { const bearer = data.token(); - if (!bearer || bearer.startsWith("demo.")) { data.setDemoNotice(cs() ? "V demo režimu nelze ukládat." : "Cannot save in demo mode."); return; } + if (!bearer || bearer.startsWith("demo.")) { data.setDemoNotice(data.i18n.t("dashboard.demo.noSave")); return; } setHoursSaving(day.dayOfWeek); try { const scope = hoursScope(); @@ -309,7 +309,7 @@ function ZonesPage() { }); void refetchHours(); } catch { - data.setDemoNotice(cs() ? "Uložení selhalo." : "Save failed."); + data.setDemoNotice(data.i18n.t("dashboard.saveFailed")); } finally { setHoursSaving(null); } @@ -322,7 +322,7 @@ function ZonesPage() { const handleSaveTemplate = async () => { const bearer = data.token(); if (!newTemplateTitle().trim()) return; - if (!bearer || bearer.startsWith("demo.")) { data.setDemoNotice(cs() ? "V demo režimu nelze ukládat." : "Cannot save in demo mode."); return; } + if (!bearer || bearer.startsWith("demo.")) { data.setDemoNotice(data.i18n.t("dashboard.demo.noSave")); return; } setTemplateSaving(true); try { const body = { title: newTemplateTitle().trim(), durationMinutes: newTemplateDuration(), capacity: newTemplateCapacity() }; @@ -335,7 +335,7 @@ function ZonesPage() { resetTemplateForm(); void refetchTemplates(); void refetchSessions(); } catch { - data.setDemoNotice(cs() ? "Uložení selhalo." : "Save failed."); + data.setDemoNotice(data.i18n.t("dashboard.saveFailed")); } finally { setTemplateSaving(false); } @@ -350,19 +350,19 @@ function ZonesPage() { const handleDeleteTemplate = async (id: string) => { const bearer = data.token(); - if (!bearer || bearer.startsWith("demo.")) { data.setDemoNotice(cs() ? "V demo režimu nelze mazat." : "Cannot delete in demo mode."); return; } + if (!bearer || bearer.startsWith("demo.")) { data.setDemoNotice(data.i18n.t("dashboard.demo.noDelete")); return; } try { await (apiClient as any).DELETE(`/v1/catalog/class-templates/${id}`, authHeaders()); void refetchTemplates(); void refetchSessions(); } catch { - data.setDemoNotice(cs() ? "Smazání selhalo." : "Delete failed."); + data.setDemoNotice(data.i18n.t("dashboard.deleteFailed")); } }; const handleAddSession = async () => { const bearer = data.token(); if (!newSessionTemplateId() || !newSessionStartsAt()) return; - if (!bearer || bearer.startsWith("demo.")) { data.setDemoNotice(cs() ? "V demo režimu nelze ukládat." : "Cannot save in demo mode."); return; } + if (!bearer || bearer.startsWith("demo.")) { data.setDemoNotice(data.i18n.t("dashboard.demo.noSave")); return; } setSessionSaving(true); try { await (apiClient as any).POST("/v1/catalog/class-sessions", { @@ -376,7 +376,7 @@ function ZonesPage() { setNewSessionTemplateId(""); setNewSessionLocationId(""); setNewSessionStartsAt(""); void refetchSessions(); } catch { - data.setDemoNotice(cs() ? "Uložení selhalo." : "Save failed."); + data.setDemoNotice(data.i18n.t("dashboard.saveFailed")); } finally { setSessionSaving(false); } @@ -384,19 +384,19 @@ function ZonesPage() { const handleDeleteSession = async (id: string) => { const bearer = data.token(); - if (!bearer || bearer.startsWith("demo.")) { data.setDemoNotice(cs() ? "V demo režimu nelze mazat." : "Cannot delete in demo mode."); return; } + if (!bearer || bearer.startsWith("demo.")) { data.setDemoNotice(data.i18n.t("dashboard.demo.noDelete")); return; } try { await (apiClient as any).DELETE(`/v1/catalog/class-sessions/${id}`, authHeaders()); void refetchSessions(); } catch { - data.setDemoNotice(cs() ? "Smazání selhalo." : "Delete failed."); + data.setDemoNotice(data.i18n.t("dashboard.deleteFailed")); } }; const handleAddSchedule = async () => { const bearer = data.token(); if (!newSchedTemplateId() || !newSchedStartsLocal()) return; - if (!bearer || bearer.startsWith("demo.")) { data.setDemoNotice(cs() ? "V demo režimu nelze ukládat." : "Cannot save in demo mode."); return; } + if (!bearer || bearer.startsWith("demo.")) { data.setDemoNotice(data.i18n.t("dashboard.demo.noSave")); return; } setSchedSaving(true); try { await (apiClient as any).POST("/v1/catalog/class-schedules", { @@ -412,7 +412,7 @@ function ZonesPage() { void refetchSchedules(); void refetchSessions(); } catch { - data.setDemoNotice(cs() ? "Uložení selhalo." : "Save failed."); + data.setDemoNotice(data.i18n.t("dashboard.saveFailed")); } finally { setSchedSaving(false); } @@ -431,7 +431,7 @@ function ZonesPage() { const bearer = data.token(); const id = editingSchedId(); if (!id) return; - if (!bearer || bearer.startsWith("demo.")) { data.setDemoNotice(cs() ? "V demo režimu nelze ukládat." : "Cannot save in demo mode."); return; } + if (!bearer || bearer.startsWith("demo.")) { data.setDemoNotice(data.i18n.t("dashboard.demo.noSave")); return; } setSchedEditSaving(true); try { await (apiClient as any).PATCH(`/v1/catalog/class-schedules/${id}`, { @@ -448,7 +448,7 @@ function ZonesPage() { void refetchSchedules(); void refetchSessions(); } catch { - data.setDemoNotice(cs() ? "Uložení selhalo." : "Save failed."); + data.setDemoNotice(data.i18n.t("dashboard.saveFailed")); } finally { setSchedEditSaving(false); } @@ -462,7 +462,7 @@ function ZonesPage() { const handleSaveCapacity = async (session: any, allowOverCapacity: boolean) => { const bearer = data.token(); - if (!bearer || bearer.startsWith("demo.")) { data.setDemoNotice(cs() ? "V demo režimu nelze ukládat." : "Cannot save in demo mode."); return; } + if (!bearer || bearer.startsWith("demo.")) { data.setDemoNotice(data.i18n.t("dashboard.demo.noSave")); return; } setCapSaving(true); try { const response = await (apiClient as any).PATCH(`/v1/catalog/class-sessions/${session.id}`, { @@ -477,7 +477,7 @@ function ZonesPage() { setCapOverbookConfirm(null); void refetchSessions(); } catch { - data.setDemoNotice(cs() ? "Uložení selhalo." : "Save failed."); + data.setDemoNotice(data.i18n.t("dashboard.saveFailed")); } finally { setCapSaving(false); } @@ -485,12 +485,12 @@ function ZonesPage() { const handleDeleteSchedule = async (id: string) => { const bearer = data.token(); - if (!bearer || bearer.startsWith("demo.")) { data.setDemoNotice(cs() ? "V demo režimu nelze mazat." : "Cannot delete in demo mode."); return; } + if (!bearer || bearer.startsWith("demo.")) { data.setDemoNotice(data.i18n.t("dashboard.demo.noDelete")); return; } try { await (apiClient as any).DELETE(`/v1/catalog/class-schedules/${id}`, authHeaders()); void refetchSchedules(); } catch { - data.setDemoNotice(cs() ? "Smazání selhalo." : "Delete failed."); + data.setDemoNotice(data.i18n.t("dashboard.deleteFailed")); } }; @@ -558,7 +558,7 @@ function ZonesPage() { 0} fallback={

{data.i18n.t("dashboard.empty.title")}

-

{cs() ? "Přidejte první zónu nebo místnost." : "Add your first location or room."}

+

{data.i18n.t("dashboard.zone.addFirst")}

}>
@@ -571,7 +571,7 @@ function ZonesPage() {

{typeLabel(zone.type)} • {data.i18n.t("dashboard.zone.capacity")}: {zone.capacity}

- @@ -585,12 +585,12 @@ function ZonesPage() {

{data.i18n.t("dashboard.zone.blockedDays")}

-

{cs() ? "Dny, kdy nejsou dostupné rezervace." : "Days when bookings are unavailable."}

+

{data.i18n.t("dashboard.blockedDay.hint")}

- setNewBlockedDate(e.currentTarget.value)} /> - setNewBlockedReason(e.currentTarget.value)} placeholder={cs() ? "např. Státní svátek" : "e.g. Public holiday"} /> + setNewBlockedDate(e.currentTarget.value)} /> + setNewBlockedReason(e.currentTarget.value)} placeholder={data.i18n.t("dashboard.blockedDay.placeholder")} /> setField({ isOpen: e.currentTarget.checked, open: e.currentTarget.checked ? (day.open || "09:00") : day.open, close: e.currentTarget.checked ? (day.close || "18:00") : day.close })} class="rounded border-border accent-[#c25e3a]" /> - {cs() ? "Otevřeno" : "Open"} + {data.i18n.t("dashboard.day.open")}
@@ -808,7 +808,7 @@ function ZonesPage() {
- +
} @@ -927,7 +927,7 @@ function ZonesPage() { - +
@@ -1045,7 +1045,7 @@ function ZonesPage() {

{data.i18n.t("dashboard.locationLimitReached")}

-

{cs() ? "Váš plán umožňuje pouze omezený počet lokací. Pro více lokací upgradujte." : "Your plan only allows a limited number of locations. Upgrade for more."}

+

{data.i18n.t("dashboard.zone.limitHint")}