227 lines
7.7 KiB
TypeScript
Raw Normal View History

/**
* 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<NotificationTypeValue, NotificationTemplate> = {
[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>
): 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<typeof notificationSchema>;
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<typeof createNotificationRequestSchema>;
export const notificationListResponseSchema = z.object({
notifications: z.array(notificationSchema),
unreadCount: z.number(),
total: z.number(),
});
export type NotificationListResponse = z.infer<typeof notificationListResponseSchema>;
export const notificationUnreadCountResponseSchema = z.object({
count: z.number(),
});
export type NotificationUnreadCountResponse = z.infer<typeof notificationUnreadCountResponseSchema>;
/**
* 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<typeof notificationQuerySchema>;