Phase 7
This commit is contained in:
189 files changed
+9873
-655
No files matched your search
@@ -0,0 +1,41 @@
|
||||
"use server";
|
||||
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { auth } from "@/auth";
|
||||
import { prisma } from "@/lib/db/prisma";
|
||||
import { markAllNotificationsRead, markNotificationRead } from "@/services/notifications/notification.service";
|
||||
|
||||
export interface MarkReadActionResult {
|
||||
success: boolean;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared across all three notification-centre pages (customer/staff/owner)
|
||||
* — ownership is enforced by recipientUserId matching the session, exactly
|
||||
* the same rule regardless of which role is asking (see
|
||||
* NotificationService.markNotificationRead's own scoped WHERE clause), so
|
||||
* one action file covers all of them rather than three near-identical
|
||||
* copies.
|
||||
*/
|
||||
export async function markNotificationReadAction(notificationId: string): Promise<MarkReadActionResult> {
|
||||
const session = await auth();
|
||||
if (!session?.user) return { success: false, error: "You must be signed in." };
|
||||
|
||||
await markNotificationRead(prisma, { notificationId, userId: session.user.id });
|
||||
revalidatePath("/notifications");
|
||||
revalidatePath("/staff/notifications");
|
||||
revalidatePath("/owner/notifications");
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
export async function markAllNotificationsReadAction(): Promise<MarkReadActionResult> {
|
||||
const session = await auth();
|
||||
if (!session?.user) return { success: false, error: "You must be signed in." };
|
||||
|
||||
await markAllNotificationsRead(prisma, session.user.id);
|
||||
revalidatePath("/notifications");
|
||||
revalidatePath("/staff/notifications");
|
||||
revalidatePath("/owner/notifications");
|
||||
return { success: true };
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
"use server";
|
||||
|
||||
import { auth } from "@/auth";
|
||||
import { NotificationType } from "@/generated/prisma/enums";
|
||||
import { prisma } from "@/lib/db/prisma";
|
||||
import { setNotificationPreference } from "@/services/notifications/notification.service";
|
||||
import type { PreferenceCellState } from "@/features/staff/components/notification-preferences-form";
|
||||
|
||||
export interface PreferencesActionResult {
|
||||
success: boolean;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* The customer-side counterpart to features/staff/actions/profile.ts's
|
||||
* saveNotificationPreferencesAction — same shape, but scoped to the
|
||||
* session's own customer account and the one preference-controlled
|
||||
* customer-facing type (BOOKING_REMINDER; see notifications/types.ts).
|
||||
*/
|
||||
export async function saveCustomerNotificationPreferencesAction(
|
||||
cells: PreferenceCellState[],
|
||||
): Promise<PreferencesActionResult> {
|
||||
const session = await auth();
|
||||
if (!session?.user) return { success: false, error: "You must be signed in." };
|
||||
|
||||
for (const cell of cells) {
|
||||
await setNotificationPreference(prisma, {
|
||||
userId: session.user.id,
|
||||
type: NotificationType.BOOKING_REMINDER,
|
||||
channel: cell.channel,
|
||||
isEnabled: cell.isEnabled,
|
||||
});
|
||||
}
|
||||
return { success: true };
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
"use server";
|
||||
|
||||
import { auth } from "@/auth";
|
||||
import { prisma } from "@/lib/db/prisma";
|
||||
import { getPrimaryBusiness } from "@/lib/business/get-primary-business";
|
||||
import { subscribeToPush, unsubscribeFromPush } from "@/services/notifications/push-subscription.service";
|
||||
|
||||
export interface PushActionResult {
|
||||
success: boolean;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registered users only (staff, owner, signed-in customer) — a guest has
|
||||
* no session to attribute the subscription to. A guest subscribing right
|
||||
* after a guest booking is a real, documented gap for a future pass (see
|
||||
* docs/phase-7-summary.md, "Known limitations"), not an oversight.
|
||||
*/
|
||||
export async function subscribeToPushAction(input: {
|
||||
endpoint: string;
|
||||
p256dhKey: string;
|
||||
authKey: string;
|
||||
deviceLabel?: string;
|
||||
}): Promise<PushActionResult> {
|
||||
const session = await auth();
|
||||
if (!session?.user) return { success: false, error: "You must be signed in." };
|
||||
|
||||
const [business, customerProfile] = await Promise.all([
|
||||
getPrimaryBusiness(),
|
||||
prisma.customerProfile.findUnique({ where: { userId: session.user.id } }),
|
||||
]);
|
||||
|
||||
await subscribeToPush(prisma, {
|
||||
businessId: business.id,
|
||||
userId: session.user.id,
|
||||
customerId: customerProfile?.id,
|
||||
endpoint: input.endpoint,
|
||||
p256dhKey: input.p256dhKey,
|
||||
authKey: input.authKey,
|
||||
deviceLabel: input.deviceLabel,
|
||||
});
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
export async function unsubscribeFromPushAction(endpoint: string): Promise<PushActionResult> {
|
||||
const session = await auth();
|
||||
if (!session?.user) return { success: false, error: "You must be signed in." };
|
||||
|
||||
await unsubscribeFromPush(prisma, { endpoint, ownerUserId: session.user.id });
|
||||
return { success: true };
|
||||
}
|
||||
Reference in new issue
Block a user