import { z } from 'zod'; import { looksLikeEmail, normalizePhoneNumber } from '@/lib/phone'; const emailSchema = z.string().trim().email(); export type ParsedEmailList = { validEmails: string[]; skipped: number; }; /** * Parses a free-form textarea of guest emails (comma and/or newline * separated) into a deduplicated, lowercased list of valid addresses, plus * a count of entries that didn't parse as a valid email. */ export function parseEmailList(raw: string): ParsedEmailList { const rawEntries = raw .split(/[\n,]/) .map((entry) => entry.trim()) .filter(Boolean); const validEmails = new Set(); let skipped = 0; for (const entry of rawEntries) { const result = emailSchema.safeParse(entry); if (result.success) { validEmails.add(result.data.toLowerCase()); } else { skipped++; } } return { validEmails: [...validEmails], skipped }; } export type ParsedGuestContactList = { emails: string[]; phones: string[]; skipped: number; }; /** * Like parseEmailList, but for a guest list textarea that accepts a mix of * emails and phone numbers - each entry is routed by whether it contains an * '@', then validated/normalized accordingly. Anything that fails either * check is counted as skipped rather than rejecting the whole batch. */ export function parseGuestContactList(raw: string): ParsedGuestContactList { const rawEntries = raw .split(/[\n,]/) .map((entry) => entry.trim()) .filter(Boolean); const emails = new Set(); const phones = new Set(); let skipped = 0; for (const entry of rawEntries) { if (looksLikeEmail(entry)) { const result = emailSchema.safeParse(entry); if (result.success) { emails.add(result.data.toLowerCase()); } else { skipped++; } } else { const normalized = normalizePhoneNumber(entry); if (normalized) { phones.add(normalized); } else { skipped++; } } } return { emails: [...emails], phones: [...phones], skipped }; }