Add optional cancellation message for notifications
This commit is contained in:
@@ -51,6 +51,9 @@ export function PlanDashboard({ plan, dict, settingsDict, lang }: PlanDashboardP
|
|||||||
const [sitterName, setSitterName] = useState("")
|
const [sitterName, setSitterName] = useState("")
|
||||||
const [bookingType, setBookingType] = useState<"SITTER" | "OWNER_HOME">("SITTER")
|
const [bookingType, setBookingType] = useState<"SITTER" | "OWNER_HOME">("SITTER")
|
||||||
const [isDialogOpen, setIsDialogOpen] = useState(false)
|
const [isDialogOpen, setIsDialogOpen] = useState(false)
|
||||||
|
const [isCancelDialogOpen, setIsCancelDialogOpen] = useState(false)
|
||||||
|
const [bookingToCancel, setBookingToCancel] = useState<number | null>(null)
|
||||||
|
const [cancelReason, setCancelReason] = useState("")
|
||||||
const [isSubmitting, setIsSubmitting] = useState(false)
|
const [isSubmitting, setIsSubmitting] = useState(false)
|
||||||
|
|
||||||
const dateLocale = lang === "de" ? de : enUS
|
const dateLocale = lang === "de" ? de : enUS
|
||||||
@@ -92,14 +95,25 @@ export function PlanDashboard({ plan, dict, settingsDict, lang }: PlanDashboardP
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleCancel = async (bookingId: number) => {
|
const handleCancelClick = (bookingId: number) => {
|
||||||
if (!confirm(dict.cancelConfirm)) return
|
setBookingToCancel(bookingId)
|
||||||
|
setCancelReason("")
|
||||||
|
setIsCancelDialogOpen(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleConfirmCancel = async () => {
|
||||||
|
if (!bookingToCancel) return
|
||||||
|
|
||||||
|
setIsSubmitting(true)
|
||||||
try {
|
try {
|
||||||
await deleteBooking(bookingId, plan.id, lang)
|
await deleteBooking(bookingToCancel, plan.id, lang, cancelReason)
|
||||||
toast.success(dict.cancelSuccess)
|
toast.success(dict.cancelSuccess)
|
||||||
|
setIsCancelDialogOpen(false)
|
||||||
|
setBookingToCancel(null)
|
||||||
} catch {
|
} catch {
|
||||||
toast.error(dict.cancelError)
|
toast.error(dict.cancelError)
|
||||||
|
} finally {
|
||||||
|
setIsSubmitting(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,7 +182,7 @@ export function PlanDashboard({ plan, dict, settingsDict, lang }: PlanDashboardP
|
|||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
className="h-6 w-6 -mr-2 -mt-2 opacity-50 hover:opacity-100 text-destructive"
|
className="h-6 w-6 -mr-2 -mt-2 opacity-50 hover:opacity-100 text-destructive"
|
||||||
onClick={() => handleCancel(booking.id)}
|
onClick={() => handleCancelClick(booking.id)}
|
||||||
>
|
>
|
||||||
<X className="w-4 h-4" />
|
<X className="w-4 h-4" />
|
||||||
<span className="sr-only">Remove</span>
|
<span className="sr-only">Remove</span>
|
||||||
@@ -245,6 +259,40 @@ export function PlanDashboard({ plan, dict, settingsDict, lang }: PlanDashboardP
|
|||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Cancellation Dialog */}
|
||||||
|
<Dialog open={isCancelDialogOpen} onOpenChange={setIsCancelDialogOpen}>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>{dict.cancelTitle}</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
|
{dict.cancelConfirm}
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
|
||||||
|
{plan.webhookUrl && (
|
||||||
|
<div className="grid gap-2 py-4">
|
||||||
|
<Label htmlFor="reason">{dict.cancelMessageLabel}</Label>
|
||||||
|
<Input
|
||||||
|
id="reason"
|
||||||
|
value={cancelReason}
|
||||||
|
onChange={(e) => setCancelReason(e.target.value)}
|
||||||
|
placeholder={dict.cancelMessagePlaceholder}
|
||||||
|
autoFocus
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<DialogFooter>
|
||||||
|
<Button variant="outline" onClick={() => setIsCancelDialogOpen(false)} disabled={isSubmitting}>
|
||||||
|
{dict.cancel}
|
||||||
|
</Button>
|
||||||
|
<Button variant="destructive" onClick={handleConfirmCancel} disabled={isSubmitting}>
|
||||||
|
{isSubmitting ? dict.saving : dict.cancelSubmit}
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ export async function createBooking(planId: string, date: Date, name: string, ty
|
|||||||
revalidatePath(`/${lang}/dashboard/${planId}`)
|
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 dict = await getDictionary(lang as any)
|
||||||
|
|
||||||
const booking = await prisma.booking.findUnique({
|
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 planUrl = `${protocol}://${host}/${lang}/dashboard/${planId}`
|
||||||
|
|
||||||
const dateStr = booking.date.toLocaleDateString(lang)
|
const dateStr = booking.date.toLocaleDateString(lang)
|
||||||
|
const messageDisplay = reason ? `\nMessage: ${reason}` : ""
|
||||||
const message = dict.notifications.cancellation
|
const message = dict.notifications.cancellation
|
||||||
.replace("{name}", booking.sitterName || "Someone")
|
.replace("{name}", booking.sitterName || "Someone")
|
||||||
.replace("{date}", dateStr)
|
.replace("{date}", dateStr)
|
||||||
.replace("{url}", planUrl)
|
.replace("{url}", planUrl)
|
||||||
|
.replace("{message}", messageDisplay)
|
||||||
|
|
||||||
await sendNotification(booking.plan.webhookUrl, message)
|
await sendNotification(booking.plan.webhookUrl, message)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,11 @@
|
|||||||
"bookedSuccess": "Termin gebucht!",
|
"bookedSuccess": "Termin gebucht!",
|
||||||
"bookError": "Buchung fehlgeschlagen. Vielleicht war jemand schneller?",
|
"bookError": "Buchung fehlgeschlagen. Vielleicht war jemand schneller?",
|
||||||
"cancelConfirm": "Bist du sicher, dass du diesen Eintrag entfernen möchtest?",
|
"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",
|
"cancelSuccess": "Eintrag entfernt",
|
||||||
"cancelError": "Entfernen fehlgeschlagen",
|
"cancelError": "Entfernen fehlgeschlagen",
|
||||||
"noInstructions": "Noch keine spezifischen Instruktionen hinterlegt."
|
"noInstructions": "Noch keine spezifischen Instruktionen hinterlegt."
|
||||||
@@ -64,7 +69,7 @@
|
|||||||
"notifications": {
|
"notifications": {
|
||||||
"ownerHome": "🏠 BESITZER ZU HAUSE: Markiert für den {date}.\nPlan: {url}",
|
"ownerHome": "🏠 BESITZER ZU HAUSE: Markiert für den {date}.\nPlan: {url}",
|
||||||
"newBooking": "✅ NEUE BUCHUNG: {name} sittet am {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}"
|
"instructionsUpdated": "📝 UPDATE: Die Katzen-Instruktionen wurden geändert.\nPlan: {url}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -36,6 +36,11 @@
|
|||||||
"bookedSuccess": "Spot booked!",
|
"bookedSuccess": "Spot booked!",
|
||||||
"bookError": "Failed to book spot. Maybe it was just taken?",
|
"bookError": "Failed to book spot. Maybe it was just taken?",
|
||||||
"cancelConfirm": "Are you sure you want to remove this entry?",
|
"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",
|
"cancelSuccess": "Entry removed",
|
||||||
"cancelError": "Failed to remove entry",
|
"cancelError": "Failed to remove entry",
|
||||||
"noInstructions": "No specific instructions provided yet."
|
"noInstructions": "No specific instructions provided yet."
|
||||||
@@ -64,7 +69,7 @@
|
|||||||
"notifications": {
|
"notifications": {
|
||||||
"ownerHome": "🏠 OWNER HOME: Marked for {date}.\nPlan: {url}",
|
"ownerHome": "🏠 OWNER HOME: Marked for {date}.\nPlan: {url}",
|
||||||
"newBooking": "✅ NEW BOOKING: {name} is sitting on {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}"
|
"instructionsUpdated": "📝 UPDATED: Cat instructions have been modified.\nPlan: {url}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
BIN
prisma/dev.db
BIN
prisma/dev.db
Binary file not shown.
Reference in New Issue
Block a user