/** * Notifications Schema * * Zod schemas and types for in-app notifications. */ import { z } from "zod"; // ============================================================================= // Enums // ============================================================================= export const NOTIFICATION_TYPE = { ELIGIBILITY_ELIGIBLE: "ELIGIBILITY_ELIGIBLE", ELIGIBILITY_INELIGIBLE: "ELIGIBILITY_INELIGIBLE", VERIFICATION_VERIFIED: "VERIFICATION_VERIFIED", VERIFICATION_REJECTED: "VERIFICATION_REJECTED", ORDER_APPROVED: "ORDER_APPROVED", ORDER_ACTIVATED: "ORDER_ACTIVATED", ORDER_FAILED: "ORDER_FAILED", CANCELLATION_SCHEDULED: "CANCELLATION_SCHEDULED", CANCELLATION_COMPLETE: "CANCELLATION_COMPLETE", PAYMENT_METHOD_EXPIRING: "PAYMENT_METHOD_EXPIRING", INVOICE_DUE: "INVOICE_DUE", SYSTEM_ANNOUNCEMENT: "SYSTEM_ANNOUNCEMENT", } as const; export type NotificationTypeValue = (typeof NOTIFICATION_TYPE)[keyof typeof NOTIFICATION_TYPE]; export const NOTIFICATION_SOURCE = { SALESFORCE: "SALESFORCE", WHMCS: "WHMCS", PORTAL: "PORTAL", SYSTEM: "SYSTEM", } as const; export type NotificationSourceValue = (typeof NOTIFICATION_SOURCE)[keyof typeof NOTIFICATION_SOURCE]; // ============================================================================= // Notification Templates // ============================================================================= export interface NotificationTemplate { type: NotificationTypeValue; title: string; message: string; actionUrl?: string; actionLabel?: string; priority: "low" | "medium" | "high"; } export const NOTIFICATION_TEMPLATES: Record = { [NOTIFICATION_TYPE.ELIGIBILITY_ELIGIBLE]: { type: NOTIFICATION_TYPE.ELIGIBILITY_ELIGIBLE, title: "Good news! Internet service is available", message: "We've confirmed internet service is available at your address. You can now select a plan and complete your order.", actionUrl: "/account/services/internet", actionLabel: "View Plans", priority: "high", }, [NOTIFICATION_TYPE.ELIGIBILITY_INELIGIBLE]: { type: NOTIFICATION_TYPE.ELIGIBILITY_INELIGIBLE, title: "Internet service not available", message: "Unfortunately, internet service is not currently available at your address. We'll notify you if this changes.", actionUrl: "/account/support", actionLabel: "Contact Support", priority: "high", }, [NOTIFICATION_TYPE.VERIFICATION_VERIFIED]: { type: NOTIFICATION_TYPE.VERIFICATION_VERIFIED, title: "ID verification complete", message: "Your identity has been verified. You can now complete your order.", actionUrl: "/account/order", actionLabel: "Continue Checkout", priority: "high", }, [NOTIFICATION_TYPE.VERIFICATION_REJECTED]: { type: NOTIFICATION_TYPE.VERIFICATION_REJECTED, title: "ID verification requires attention", message: "We couldn't verify your ID. Please review the feedback and resubmit.", actionUrl: "/account/settings/verification", actionLabel: "Resubmit", priority: "high", }, [NOTIFICATION_TYPE.ORDER_APPROVED]: { type: NOTIFICATION_TYPE.ORDER_APPROVED, title: "Order approved", message: "Your order has been approved and is being processed.", actionUrl: "/account/orders", actionLabel: "View Order", priority: "medium", }, [NOTIFICATION_TYPE.ORDER_ACTIVATED]: { type: NOTIFICATION_TYPE.ORDER_ACTIVATED, title: "Service activated", message: "Your service is now active and ready to use.", actionUrl: "/account/services", actionLabel: "View Service", priority: "high", }, [NOTIFICATION_TYPE.ORDER_FAILED]: { type: NOTIFICATION_TYPE.ORDER_FAILED, title: "Order requires attention", message: "There was an issue processing your order. Please contact support.", actionUrl: "/account/support", actionLabel: "Contact Support", priority: "high", }, [NOTIFICATION_TYPE.CANCELLATION_SCHEDULED]: { type: NOTIFICATION_TYPE.CANCELLATION_SCHEDULED, title: "Cancellation scheduled", message: "Your cancellation request has been received and scheduled.", actionUrl: "/account/services", actionLabel: "View Details", priority: "medium", }, [NOTIFICATION_TYPE.CANCELLATION_COMPLETE]: { type: NOTIFICATION_TYPE.CANCELLATION_COMPLETE, title: "Service cancelled", message: "Your service has been successfully cancelled.", actionUrl: "/account/services", actionLabel: "View Details", priority: "medium", }, [NOTIFICATION_TYPE.PAYMENT_METHOD_EXPIRING]: { type: NOTIFICATION_TYPE.PAYMENT_METHOD_EXPIRING, title: "Payment method expiring soon", message: "Your payment method is expiring soon. Please update it to avoid service interruption.", actionUrl: "/account/billing/payments", actionLabel: "Update Payment", priority: "high", }, [NOTIFICATION_TYPE.INVOICE_DUE]: { type: NOTIFICATION_TYPE.INVOICE_DUE, title: "Invoice due", message: "You have an invoice due. Please make a payment to keep your service active.", actionUrl: "/account/billing/invoices", actionLabel: "Pay Now", priority: "high", }, [NOTIFICATION_TYPE.SYSTEM_ANNOUNCEMENT]: { type: NOTIFICATION_TYPE.SYSTEM_ANNOUNCEMENT, title: "System announcement", message: "Important information about your service.", priority: "low", }, }; /** * Get notification template by type with optional overrides */ export function getNotificationTemplate( type: NotificationTypeValue, overrides?: Partial ): NotificationTemplate { const template = NOTIFICATION_TEMPLATES[type]; if (!template) { throw new Error(`Unknown notification type: ${type}`); } return { ...template, ...overrides }; } // ============================================================================= // Schemas // ============================================================================= export const notificationSchema = z.object({ id: z.string().uuid(), userId: z.string().uuid(), type: z.nativeEnum(NOTIFICATION_TYPE), title: z.string(), message: z.string().nullable(), actionUrl: z.string().nullable(), actionLabel: z.string().nullable(), source: z.nativeEnum(NOTIFICATION_SOURCE), sourceId: z.string().nullable(), read: z.boolean(), readAt: z.string().datetime().nullable(), dismissed: z.boolean(), createdAt: z.string().datetime(), expiresAt: z.string().datetime(), }); export type Notification = z.infer; export const createNotificationRequestSchema = z.object({ userId: z.string().uuid(), type: z.nativeEnum(NOTIFICATION_TYPE), title: z.string().optional(), message: z.string().optional(), actionUrl: z.string().optional(), actionLabel: z.string().optional(), source: z.nativeEnum(NOTIFICATION_SOURCE).optional(), sourceId: z.string().optional(), }); export type CreateNotificationRequest = z.infer; export const notificationListResponseSchema = z.object({ notifications: z.array(notificationSchema), unreadCount: z.number(), total: z.number(), }); export type NotificationListResponse = z.infer; export const notificationUnreadCountResponseSchema = z.object({ count: z.number(), }); export type NotificationUnreadCountResponse = z.infer; /** * Schema for notification query parameters */ export const notificationQuerySchema = z.object({ limit: z.coerce.number().int().min(1).max(100).optional().default(20), offset: z.coerce.number().int().nonnegative().optional().default(0), includeRead: z.coerce.boolean().optional().default(true), }); export type NotificationQuery = z.infer; // ============================================================================= // Route Param Schemas (BFF) // ============================================================================= export const notificationIdParamSchema = z.object({ id: z.string().uuid(), }); export type NotificationIdParam = z.infer;