import { broadcastToUser } from "@/lib/realtime/bus"; type PayloadType = Awaited>; export interface NotifyInput { userId: number; type?: string; title: string; message?: string; link?: string; } /** Create an in-app notification for a user. Fire-and-forget: failures are logged, not thrown. */ export async function notifyUser(payload: PayloadType, input: NotifyInput): Promise { try { const target = await payload.findByID({ collection: "users", id: input.userId, depth: 0, overrideAccess: true, }); const prefs = ( target as { preferences?: { notifications?: { mutedAll?: boolean | null; mutedTypes?: (string | null)[] | null }; }; } ).preferences?.notifications; if (prefs?.mutedAll) return; if (input.type && prefs?.mutedTypes?.includes(input.type)) return; await payload.create({ collection: "user-notifications", data: { user: input.userId, type: input.type, title: input.title, message: input.message, link: input.link, read: false, }, overrideAccess: true, depth: 0, }); broadcastToUser("notification", input.userId, { type: input.type ?? null }); } catch (err) { console.error("[Notifications] Failed to create notification:", err); } } /** Short display label for a notification type, or the raw type when unknown. */ export function notificationLabel(type?: string | null): string { switch (type) { case "market:offer": return "New offer"; case "market:counter": return "Counteroffer"; case "market:accept": return "Offer accepted"; case "market:reject": return "Offer rejected"; case "market:withdrawn": return "Offer withdrawn"; case "market:closed": return "Negotiations closed"; case "market:sold": return "Listing sold"; case "market:expired": return "Listing expired"; case "award:granted": return "Award received"; case "account:discord-request": return "Discord request"; case "finance:deposit": return "Bank deposit"; case "finance:withdraw": return "Bank withdrawal"; case "finance:transfer": return "Bank transfer"; case "shipment:completed": return "Shipment completed"; case "ticket:created": return "New ticket"; case "ticket:assigned": return "Ticket assigned"; case "ticket:reply": return "New reply"; case "ticket:status": return "Ticket updated"; default: return type ?? "Notification"; } }