feat: implement pwa push notifications

This commit is contained in:
2026-01-13 09:03:46 +01:00
parent e7291951d8
commit 14430b275e
16 changed files with 547 additions and 50 deletions

View File

@@ -3,7 +3,7 @@
import prisma from "@/lib/prisma"
import { revalidatePath } from "next/cache"
import { headers } from "next/headers"
import { sendNotification } from "@/lib/notifications"
import { sendPlanNotification } from "@/lib/notifications"
import { getDictionary } from "@/get-dictionary"
export async function createBooking(planId: string, date: Date, name: string, type: "SITTER" | "OWNER_HOME" = "SITTER", lang: string = "en") {
@@ -34,7 +34,7 @@ export async function createBooking(planId: string, date: Date, name: string, ty
}
})
if (plan?.webhookUrl && plan.notifyAll) {
if (plan?.notifyAll) {
const host = (await headers()).get("host")
const protocol = host?.includes("localhost") ? "http" : "https"
const planUrl = `${protocol}://${host}/${lang}/dashboard/${planId}`
@@ -44,7 +44,7 @@ export async function createBooking(planId: string, date: Date, name: string, ty
? dict.notifications.ownerHome.replace("{date}", dateStr).replace("{url}", planUrl)
: dict.notifications.newBooking.replace("{name}", name).replace("{date}", dateStr).replace("{url}", planUrl)
await sendNotification(plan.webhookUrl, message)
await sendPlanNotification(planId, message, plan.webhookUrl)
}
revalidatePath(`/${lang}/dashboard/${planId}`)
@@ -64,7 +64,7 @@ export async function deleteBooking(bookingId: number, planId: string, lang: str
where: { id: bookingId }
})
if (booking.plan.webhookUrl) {
if (booking.plan.notifyAll) {
const host = (await headers()).get("host")
const protocol = host?.includes("localhost") ? "http" : "https"
const planUrl = `${protocol}://${host}/${lang}/dashboard/${planId}`
@@ -77,7 +77,7 @@ export async function deleteBooking(bookingId: number, planId: string, lang: str
.replace("{url}", planUrl)
.replace("{message}", messageDisplay)
await sendNotification(booking.plan.webhookUrl, message)
await sendPlanNotification(planId, message, booking.plan.webhookUrl)
}
revalidatePath(`/${lang}/dashboard/${planId}`)
@@ -98,7 +98,7 @@ export async function completeBooking(bookingId: number, planId: string, lang: s
data: { completedAt: new Date() }
})
if (booking.plan.webhookUrl) {
if (booking.plan.notifyAll) {
const host = (await headers()).get("host")
const protocol = host?.includes("localhost") ? "http" : "https"
const planUrl = `${protocol}://${host}/${lang}/dashboard/${planId}`
@@ -109,7 +109,7 @@ export async function completeBooking(bookingId: number, planId: string, lang: s
.replace("{date}", dateStr)
.replace("{url}", planUrl)
await sendNotification(booking.plan.webhookUrl, message)
await sendPlanNotification(planId, message, booking.plan.webhookUrl)
}
revalidatePath(`/${lang}/dashboard/${planId}`)

View File

@@ -3,7 +3,7 @@
import prisma from "@/lib/prisma"
import { revalidatePath } from "next/cache"
import { headers } from "next/headers"
import { sendNotification } from "@/lib/notifications"
import { sendPlanNotification } from "@/lib/notifications"
import { getDictionary } from "@/get-dictionary"
export async function updatePlan(
@@ -34,14 +34,15 @@ export async function updatePlan(
}
})
if (data.instructions && plan.webhookUrl && plan.notifyAll) {
if (data.instructions && plan.notifyAll) {
const host = (await headers()).get("host")
const protocol = host?.includes("localhost") ? "http" : "https"
const planUrl = `${protocol}://${host}/${lang}/dashboard/${planId}`
await sendNotification(
plan.webhookUrl,
dict.notifications.instructionsUpdated.replace("{url}", planUrl)
await sendPlanNotification(
planId,
dict.notifications.instructionsUpdated.replace("{url}", planUrl),
plan.webhookUrl
)
}

View File

@@ -0,0 +1,45 @@
"use server";
import prisma from "@/lib/prisma";
export async function subscribeUser(planId: string, subscription: any) {
if (!subscription || !subscription.endpoint || !subscription.keys) {
throw new Error("Invalid subscription object");
}
const { endpoint, keys: { p256dh, auth } } = subscription;
// Use upsert to handle updates gracefully
// If endpoint exists, update the planId. A device follows the last plan logged into (or explicitly subscribed to).
// Check existence first to decide logic (or just upsert)
// Actually, if we want to allow multiple plans per device, we need a different model.
// For now, simple model: One device -> One Plan.
await prisma.pushSubscription.upsert({
where: { endpoint },
create: {
planId,
endpoint,
p256dh,
auth,
},
update: {
planId,
p256dh,
auth,
}
});
return { success: true };
}
export async function unsubscribeUser(endpoint: string) {
if (!endpoint) return;
await prisma.pushSubscription.deleteMany({
where: { endpoint },
});
return { success: true };
}