91 lines
2.6 KiB
TypeScript
91 lines
2.6 KiB
TypeScript
import { broadcastToUser } from "@/lib/realtime/bus";
|
|
|
|
type PayloadType = Awaited<ReturnType<typeof import("payload").getPayload>>;
|
|
|
|
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<void> {
|
|
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 "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";
|
|
}
|
|
}
|