From cc2687dad8b01f1b6ea35341a1a671df1d035b0a Mon Sep 17 00:00:00 2001 From: elpatron Date: Mon, 12 Jan 2026 21:42:52 +0100 Subject: [PATCH] Add optional cancellation message for notifications --- .../[planId]/_components/plan-dashboard.tsx | 56 ++++++++++++++++-- app/actions/booking.ts | 4 +- dictionaries/de.json | 7 ++- dictionaries/en.json | 7 ++- prisma/dev.db | Bin 40960 -> 40960 bytes 5 files changed, 67 insertions(+), 7 deletions(-) diff --git a/app/[lang]/dashboard/[planId]/_components/plan-dashboard.tsx b/app/[lang]/dashboard/[planId]/_components/plan-dashboard.tsx index d0495d9..43fa8fe 100644 --- a/app/[lang]/dashboard/[planId]/_components/plan-dashboard.tsx +++ b/app/[lang]/dashboard/[planId]/_components/plan-dashboard.tsx @@ -51,6 +51,9 @@ export function PlanDashboard({ plan, dict, settingsDict, lang }: PlanDashboardP const [sitterName, setSitterName] = useState("") const [bookingType, setBookingType] = useState<"SITTER" | "OWNER_HOME">("SITTER") const [isDialogOpen, setIsDialogOpen] = useState(false) + const [isCancelDialogOpen, setIsCancelDialogOpen] = useState(false) + const [bookingToCancel, setBookingToCancel] = useState(null) + const [cancelReason, setCancelReason] = useState("") const [isSubmitting, setIsSubmitting] = useState(false) const dateLocale = lang === "de" ? de : enUS @@ -92,14 +95,25 @@ export function PlanDashboard({ plan, dict, settingsDict, lang }: PlanDashboardP } } - const handleCancel = async (bookingId: number) => { - if (!confirm(dict.cancelConfirm)) return + const handleCancelClick = (bookingId: number) => { + setBookingToCancel(bookingId) + setCancelReason("") + setIsCancelDialogOpen(true) + } + const handleConfirmCancel = async () => { + if (!bookingToCancel) return + + setIsSubmitting(true) try { - await deleteBooking(bookingId, plan.id, lang) + await deleteBooking(bookingToCancel, plan.id, lang, cancelReason) toast.success(dict.cancelSuccess) + setIsCancelDialogOpen(false) + setBookingToCancel(null) } catch { toast.error(dict.cancelError) + } finally { + setIsSubmitting(false) } } @@ -168,7 +182,7 @@ export function PlanDashboard({ plan, dict, settingsDict, lang }: PlanDashboardP variant="ghost" size="icon" className="h-6 w-6 -mr-2 -mt-2 opacity-50 hover:opacity-100 text-destructive" - onClick={() => handleCancel(booking.id)} + onClick={() => handleCancelClick(booking.id)} > Remove @@ -245,6 +259,40 @@ export function PlanDashboard({ plan, dict, settingsDict, lang }: PlanDashboardP ) })} + + {/* Cancellation Dialog */} + + + + {dict.cancelTitle} + + {dict.cancelConfirm} + + + + {plan.webhookUrl && ( +
+ + setCancelReason(e.target.value)} + placeholder={dict.cancelMessagePlaceholder} + autoFocus + /> +
+ )} + + + + + +
+
) } diff --git a/app/actions/booking.ts b/app/actions/booking.ts index 736b124..27539d4 100644 --- a/app/actions/booking.ts +++ b/app/actions/booking.ts @@ -50,7 +50,7 @@ export async function createBooking(planId: string, date: Date, name: string, ty revalidatePath(`/${lang}/dashboard/${planId}`) } -export async function deleteBooking(bookingId: number, planId: string, lang: string = "en") { +export async function deleteBooking(bookingId: number, planId: string, lang: string = "en", reason?: string) { const dict = await getDictionary(lang as any) const booking = await prisma.booking.findUnique({ @@ -70,10 +70,12 @@ export async function deleteBooking(bookingId: number, planId: string, lang: str const planUrl = `${protocol}://${host}/${lang}/dashboard/${planId}` const dateStr = booking.date.toLocaleDateString(lang) + const messageDisplay = reason ? `\nMessage: ${reason}` : "" const message = dict.notifications.cancellation .replace("{name}", booking.sitterName || "Someone") .replace("{date}", dateStr) .replace("{url}", planUrl) + .replace("{message}", messageDisplay) await sendNotification(booking.plan.webhookUrl, message) } diff --git a/dictionaries/de.json b/dictionaries/de.json index 86f88db..dba55ec 100644 --- a/dictionaries/de.json +++ b/dictionaries/de.json @@ -36,6 +36,11 @@ "bookedSuccess": "Termin gebucht!", "bookError": "Buchung fehlgeschlagen. Vielleicht war jemand schneller?", "cancelConfirm": "Bist du sicher, dass du diesen Eintrag entfernen möchtest?", + "cancelTitle": "Termin stornieren", + "cancelMessageLabel": "Nachricht (optional)", + "cancelMessagePlaceholder": "Ich kann leider doch nicht...", + "cancelSubmit": "Stornierung bestätigen", + "cancel": "Abbrechen", "cancelSuccess": "Eintrag entfernt", "cancelError": "Entfernen fehlgeschlagen", "noInstructions": "Noch keine spezifischen Instruktionen hinterlegt." @@ -64,7 +69,7 @@ "notifications": { "ownerHome": "🏠 BESITZER ZU HAUSE: Markiert für den {date}.\nPlan: {url}", "newBooking": "✅ NEUE BUCHUNG: {name} sittet am {date}.\nPlan: {url}", - "cancellation": "🚨 ABSAGE: {name} hat die Buchung für den {date} gelöscht.\nPlan: {url}", + "cancellation": "🚨 ABSAGE: {name} hat die Buchung für den {date} gelöscht.{message}\nPlan: {url}", "instructionsUpdated": "📝 UPDATE: Die Katzen-Instruktionen wurden geändert.\nPlan: {url}" } } \ No newline at end of file diff --git a/dictionaries/en.json b/dictionaries/en.json index 343f80d..b5e368c 100644 --- a/dictionaries/en.json +++ b/dictionaries/en.json @@ -36,6 +36,11 @@ "bookedSuccess": "Spot booked!", "bookError": "Failed to book spot. Maybe it was just taken?", "cancelConfirm": "Are you sure you want to remove this entry?", + "cancelTitle": "Cancel Booking", + "cancelMessageLabel": "Message (optional)", + "cancelMessagePlaceholder": "I can't make it because...", + "cancelSubmit": "Confirm Cancellation", + "cancel": "Cancel", "cancelSuccess": "Entry removed", "cancelError": "Failed to remove entry", "noInstructions": "No specific instructions provided yet." @@ -64,7 +69,7 @@ "notifications": { "ownerHome": "🏠 OWNER HOME: Marked for {date}.\nPlan: {url}", "newBooking": "✅ NEW BOOKING: {name} is sitting on {date}.\nPlan: {url}", - "cancellation": "🚨 CANCELLATION: {name} removed their booking for {date}.\nPlan: {url}", + "cancellation": "🚨 CANCELLATION: {name} removed their booking for {date}.{message}\nPlan: {url}", "instructionsUpdated": "📝 UPDATED: Cat instructions have been modified.\nPlan: {url}" } } \ No newline at end of file diff --git a/prisma/dev.db b/prisma/dev.db index 81a509e9aaf261be145b2ff3c5e68e818f52478c..586e0406e3bd0c336abe77e932850123e1fcc2ba 100644 GIT binary patch delta 325 zcmZoTz|?SnX@WGP>_i!7M%j%CJN%h=zibv1xW#K_!O6~G&nhX&nw*=Rl$%pwS!e(R zxkkCh$rTw{7M4jCjI%F?88-MP7G;+f2YZHuxCSxK-u!-6?Pk5Wi-L@tlL{o5_{uj6 zD#Y^XXtM&1VkF8W&dKNN6gMBKb6^x==Z$6HKf>S0@5;}~cal$tHx}qLcV5#*BX%x( zQC3#o#(GZN&MQeRE`d0aarP&h$jP34wF2*L*EO23aoI~kHBS!omz->CC$!nCZx_%$ metic1$NWe6m-Bb>XY>2=>jPac$v@fGpF<352(SaCHUj{HQ)CeU delta 111 zcmV-#0FeKHzyg540+1U48Ic@A0U5DixKFbY5Ec%zE?&_Q0SJr