// ============================================================ // LynkedUp Pro — Account / Profile / Support mock data (D). // Everything here is a client-side mock so the screens are // fully clickable without a backend. Ready-to-paste shapes. // // Anchored to the same demo identity used by the auth portal: // James Carter · Allotment YEA-654321 · House 181. // ============================================================ /* ---------------------------------------------------------- */ /* helpers */ /* ---------------------------------------------------------- */ export function maskEmail(email: string): string { const [user = "", domain = ""] = email.split("@"); if (!domain) return email; if (user.length <= 2) return `${user[0] ?? ""}••@${domain}`; return `${user[0]}${"•".repeat(Math.max(4, user.length - 2))}${user[user.length - 1]}@${domain}`; } export function maskPhone(phone: string): string { const tail = phone.replace(/\D/g, "").slice(-2); return `+91 ••••• ••${tail}`; } /* ---------------------------------------------------------- */ /* USER — the signed-in account holder */ /* ---------------------------------------------------------- */ export type RevealField = { label: string; masked: string; full: string }; export type User = { id: string; name: string; firstName: string; lastName: string; initials: string; role: string; avatarGradient: string; // property / allotment allotmentNo: string; plot: string; // house number — verify-to-reveal key sector: string; pocket: string; category: string; size: string; // allottee vs account holder isAllottee: boolean; relationship: string; // "Self" when account holder IS allottee allotteeName: string; // contact (masked until verified) email: RevealField; mobile: RevealField; whatsapp: RevealField; pan: RevealField; aadhaar: RevealField; memberSince: string; status: "active" | "suspended"; }; export const user: User = { id: "usr_LUP_00181", name: "James Carter", firstName: "James", lastName: "Carter", initials: "JC", role: "Property Owner", avatarGradient: "linear-gradient(135deg,#fda913,#fd6d13)", allotmentNo: "YEA-654321", plot: "181", sector: "Sector 18", pocket: "Pocket B", category: "Residential", size: "300 sq.m", isAllottee: true, relationship: "Self", allotteeName: "James Carter", email: { label: "Email", masked: maskEmail("james.carter@example.com"), full: "james.carter@example.com" }, mobile: { label: "Mobile", masked: "+91 ••••• ••012", full: "+91 98765 43012" }, whatsapp: { label: "WhatsApp", masked: "+91 ••••• ••012", full: "+91 98765 43012" }, pan: { label: "PAN", masked: "•••••••• 12F", full: "ABCPS1234F" }, aadhaar: { label: "Aadhaar", masked: "•••• •••• 9012", full: "4321 8765 9012" }, memberSince: "March 2021", status: "active", }; /* ---------------------------------------------------------- */ /* COUNTRY CODES */ /* ---------------------------------------------------------- */ export type CountryCode = { code: string; flag: string; name: string; digits: number; example: string }; export const countryCodes: CountryCode[] = [ { code: "+91", flag: "🇮🇳", name: "India", digits: 10, example: "98765 43210" }, { code: "+1", flag: "🇺🇸", name: "United States", digits: 10, example: "201 555 0123" }, { code: "+44", flag: "🇬🇧", name: "United Kingdom", digits: 10, example: "7400 123456" }, { code: "+971", flag: "🇦🇪", name: "UAE", digits: 9, example: "50 123 4567" }, { code: "+65", flag: "🇸🇬", name: "Singapore", digits: 8, example: "8123 4567" }, { code: "+61", flag: "🇦🇺", name: "Australia", digits: 9, example: "412 345 678" }, ]; /* ---------------------------------------------------------- */ /* KYC */ /* ---------------------------------------------------------- */ export type KycUse = "id" | "address" | "any"; export type KycDocType = { id: string; label: string; use: KycUse; // what this document can prove hint: string; formats: string; // accepted attachment formats maxMb: number; }; // Note rule: an ID document and an ADDRESS document must be two // DIFFERENT document types. A passport (use:"any") may satisfy // either slot but not both at once. export const kycDocTypes: KycDocType[] = [ { id: "aadhaar", label: "Aadhaar Card", use: "any", hint: "Front & back, masked first 8 digits", formats: "PDF, JPG, PNG", maxMb: 5 }, { id: "pan", label: "PAN Card", use: "id", hint: "Clear photo of the front", formats: "PDF, JPG, PNG", maxMb: 5 }, { id: "passport", label: "Passport", use: "any", hint: "Photo page, must be valid", formats: "PDF, JPG, PNG", maxMb: 5 }, { id: "voter", label: "Voter ID", use: "id", hint: "Front & back", formats: "PDF, JPG, PNG", maxMb: 5 }, { id: "dl", label: "Driving Licence", use: "id", hint: "Front & back, not expired", formats: "PDF, JPG, PNG", maxMb: 5 }, { id: "utility", label: "Utility Bill", use: "address", hint: "Electricity / water, < 3 months old", formats: "PDF, JPG, PNG", maxMb: 8 }, { id: "bank", label: "Bank Statement", use: "address", hint: "First page with address, < 3 months", formats: "PDF", maxMb: 8 }, { id: "rent", label: "Registered Rent Agreement", use: "address", hint: "All pages, registered copy", formats: "PDF", maxMb: 10 }, ]; export type KycSlot = { key: "photo" | "id" | "address"; title: string; required: boolean; docId: string | null; // chosen kycDocTypes id fileName: string | null; status: "missing" | "uploaded" | "verified" | "rejected"; note?: string; }; export const kyc: { completion: number; slots: KycSlot[] } = { completion: 33, slots: [ { key: "photo", title: "Live Photo / Selfie", required: true, docId: null, fileName: "selfie_rajesh.jpg", status: "verified", note: "Face match passed" }, { key: "id", title: "Identity Proof", required: true, docId: "pan", fileName: "pan_front.pdf", status: "uploaded", note: "Under review" }, { key: "address", title: "Address Proof", required: true, docId: null, fileName: null, status: "missing" }, ], }; /* ---------------------------------------------------------- */ /* NOTIFICATIONS — channels + per-destination matrix */ /* ---------------------------------------------------------- */ export type NotifCategory = { id: string; label: string; desc: string; locked?: boolean; // statutory / security — cannot disable email: boolean; sms: boolean; whatsapp: boolean; push: boolean; }; export const contactPrefs: NotifCategory[] = [ { id: "security", label: "Security & sign-in alerts", desc: "New device, password and 2FA changes", locked: true, email: true, sms: true, whatsapp: false, push: true }, { id: "kyc", label: "KYC & verification", desc: "Document status and re-verification requests", email: true, sms: true, whatsapp: true, push: true }, { id: "tickets", label: "Support tickets", desc: "Replies, status changes and resolutions", email: true, sms: false, whatsapp: true, push: true }, { id: "billing", label: "Billing & dues", desc: "Invoices, payment reminders and receipts", email: true, sms: true, whatsapp: false, push: false }, { id: "inspections", label: "Inspections & schedule", desc: "Upcoming visits and rescheduling", email: true, sms: false, whatsapp: true, push: true }, { id: "marketing", label: "Offers & product news", desc: "Promotions, surveys and newsletters", email: false, sms: false, whatsapp: false, push: false }, ]; export type ContactDestination = { id: string; channel: "email" | "sms" | "whatsapp"; label: string; value: string; verified: boolean; primary: boolean }; export const contactDestinations: ContactDestination[] = [ { id: "e1", channel: "email", label: "Personal email", value: "james.carter@example.com", verified: true, primary: true }, { id: "e2", channel: "email", label: "Work email", value: "j.carter@acme.co", verified: false, primary: false }, { id: "s1", channel: "sms", label: "Registered mobile", value: "+91 98765 43012", verified: true, primary: true }, { id: "w1", channel: "whatsapp", label: "WhatsApp", value: "+91 98765 43012", verified: true, primary: true }, ]; export const contactTimezones = [ "Asia/Kolkata (IST · GMT+5:30)", "Asia/Dubai (GST · GMT+4:00)", "Europe/London (GMT/BST)", "America/New_York (ET)", "Asia/Singapore (SGT · GMT+8:00)", "Australia/Sydney (AET)", ]; // Contact-time windows the user is willing to be reached in. export type ContactTime = { id: string; label: string; range: string; enabled: boolean }; export const contactTimes: ContactTime[] = [ { id: "morning", label: "Morning", range: "08:00 – 12:00", enabled: true }, { id: "afternoon", label: "Afternoon", range: "12:00 – 16:00", enabled: true }, { id: "evening", label: "Evening", range: "16:00 – 20:00", enabled: true }, { id: "night", label: "Night", range: "20:00 – 22:00", enabled: false }, ]; /* ---------------------------------------------------------- */ /* SECURITY — login activity / devices */ /* ---------------------------------------------------------- */ export type LoginActivity = { id: string; device: string; os: string; kind: "desktop" | "mobile" | "tablet"; browser: string; location: string; ip: string; current: boolean; trusted: boolean; lastActive: string; event: "sign-in" | "password-change" | "2fa-enabled" | "new-device" | "sign-out"; }; export const loginActivity: LoginActivity[] = [ { id: "d1", device: "MacBook Pro 14\"", os: "macOS 15.2", kind: "desktop", browser: "Chrome 132", location: "Noida, IN", ip: "49.36.x.x", current: true, trusted: true, lastActive: "Active now", event: "sign-in" }, { id: "d2", device: "iPhone 15", os: "iOS 18.3", kind: "mobile", browser: "Safari", location: "Noida, IN", ip: "49.36.x.x", current: false, trusted: true, lastActive: "2 hours ago", event: "sign-in" }, { id: "d3", device: "Windows PC", os: "Windows 11", kind: "desktop", browser: "Edge 131", location: "Gurugram, IN", ip: "182.71.x.x", current: false, trusted: false, lastActive: "Yesterday, 9:14 PM", event: "new-device" }, { id: "d4", device: "iPad Air", os: "iPadOS 18", kind: "tablet", browser: "Safari", location: "Dubai, AE", ip: "94.205.x.x", current: false, trusted: false, lastActive: "3 days ago", event: "sign-in" }, { id: "d5", device: "MacBook Pro 14\"", os: "macOS 15.2", kind: "desktop", browser: "Chrome 132", location: "Noida, IN", ip: "49.36.x.x", current: false, trusted: true, lastActive: "5 days ago", event: "password-change" }, ]; /* ---------------------------------------------------------- */ /* PRIVACY & CONSENT */ /* ---------------------------------------------------------- */ export type ConsentItem = { id: string; label: string; desc: string; granted: boolean; locked?: boolean }; export type ConsentGroup = { id: string; title: string; items: ConsentItem[] }; export const consentGroups: ConsentGroup[] = [ { id: "essential", title: "Essential & statutory", items: [ { id: "tos", label: "Terms of Use & service agreement", desc: "Required to operate your account.", granted: true, locked: true }, { id: "kyc-process", label: "Identity verification processing", desc: "Process KYC documents to meet regulatory obligations.", granted: true, locked: true }, ], }, { id: "operational", title: "Operational data sharing", items: [ { id: "authority", label: "Share records with the authority", desc: "Sync allotment & inspection records with the development authority.", granted: true }, { id: "processors", label: "Trusted processors", desc: "Allow vetted vendors to process data on our behalf.", granted: true }, ], }, { id: "marketing", title: "Marketing & analytics", items: [ { id: "analytics", label: "Product analytics", desc: "Help improve the portal with anonymous usage data.", granted: true }, { id: "personalize", label: "Personalised recommendations", desc: "Tailor content based on your activity.", granted: false }, { id: "promos", label: "Promotional messages", desc: "Offers, surveys and partner news.", granted: false }, ], }, ]; /* ---------------------------------------------------------- */ /* SUPPORT — channels, team, tickets, threads, help */ /* ---------------------------------------------------------- */ export type SupportChannel = { id: string; title: string; desc: string; icon: string; // Icon name accent: string; // token var meta: string; // SLA / availability action: string; // CTA label status: "online" | "busy" | "offline"; }; export const supportChannels: SupportChannel[] = [ { id: "chat", title: "Live Chat", desc: "Instant help from a support agent", icon: "chat", accent: "var(--orange)", meta: "Avg. wait < 2 min", action: "Start chat", status: "online" }, { id: "ticket", title: "Raise a Ticket", desc: "Track a request end-to-end", icon: "ticket", accent: "var(--blue)", meta: "First reply in 4 hrs", action: "New ticket", status: "online" }, { id: "callback", title: "Request Callback", desc: "We'll call you in your time window", icon: "phone", accent: "var(--green)", meta: "Within working hours", action: "Schedule", status: "online" }, { id: "email", title: "Email Support", desc: "care@lynkeduppro.com", icon: "mail", accent: "var(--purple)", meta: "Reply in 1 business day", action: "Send email", status: "online" }, { id: "help", title: "Help Center", desc: "Guides, FAQs and how-tos", icon: "book", accent: "var(--cyan)", meta: "120+ articles", action: "Browse", status: "online" }, ]; export type SupportAgent = { id: string; name: string; initials: string; role: string; team: string; gradient: string; status: "online" | "away" | "offline"; rating: number; }; export const supportTeam: SupportAgent[] = [ { id: "a1", name: "Emma Wilson", initials: "EW", role: "Senior Support Lead", team: "Accounts & KYC", gradient: "linear-gradient(135deg,#fda913,#fd6d13)", status: "online", rating: 4.9 }, { id: "a2", name: "Liam Foster", initials: "LF", role: "Support Specialist", team: "Billing", gradient: "linear-gradient(135deg,#285ef0,#09b9c6)", status: "online", rating: 4.8 }, { id: "a3", name: "Sophie Turner", initials: "ST", role: "Technical Support", team: "Portal & Login", gradient: "linear-gradient(135deg,#9036e9,#285ef0)", status: "away", rating: 4.7 }, { id: "a4", name: "Noah Mitchell", initials: "NM", role: "Field Coordinator", team: "Inspections", gradient: "linear-gradient(135deg,#14bc83,#09b9c6)", status: "offline", rating: 4.9 }, ]; // Category → department routing + attachment rules for the New Ticket form. export type TicketCategory = { id: string; label: string; department: string; sla: string; requiresAttachment: boolean; attachmentNote: string; }; export const ticketCategories: TicketCategory[] = [ { id: "kyc", label: "KYC / Document verification", department: "Accounts & KYC", sla: "4 business hours", requiresAttachment: true, attachmentNote: "Attach the document in question (PDF/JPG/PNG, ≤ 8 MB)." }, { id: "login", label: "Login / Account access", department: "Portal & Login", sla: "2 business hours", requiresAttachment: false, attachmentNote: "Screenshots optional (PNG/JPG, ≤ 5 MB)." }, { id: "billing", label: "Billing & payments", department: "Billing", sla: "1 business day", requiresAttachment: true, attachmentNote: "Attach invoice or receipt (PDF, ≤ 8 MB)." }, { id: "allotment", label: "Allotment / Property records", department: "Accounts & KYC", sla: "1 business day", requiresAttachment: false, attachmentNote: "Allotment letter optional (PDF, ≤ 10 MB)." }, { id: "inspection", label: "Inspection / Site visit", department: "Inspections", sla: "1 business day", requiresAttachment: false, attachmentNote: "Site photos optional (JPG/PNG, ≤ 10 MB each)." }, { id: "other", label: "Something else", department: "General Support", sla: "1 business day", requiresAttachment: false, attachmentNote: "Attachments optional (≤ 8 MB)." }, ]; export type TicketStatus = "open" | "in-progress" | "awaiting-customer" | "resolved" | "closed"; export type TicketEvent = { status: TicketStatus | "created"; label: string; at: string; by: string }; export type Ticket = { id: string; subject: string; category: string; // TicketCategory label department: string; priority: "low" | "medium" | "high" | "urgent"; status: TicketStatus; createdAt: string; updatedAt: string; agentId: string | null; timeline: TicketEvent[]; }; export const tickets: Ticket[] = [ { id: "LUP-48217", subject: "Aadhaar re-upload not reflecting", category: "KYC / Document verification", department: "Accounts & KYC", priority: "high", status: "in-progress", createdAt: "24 Jun 2026", updatedAt: "28 Jun 2026", agentId: "a1", timeline: [ { status: "created", label: "Ticket created", at: "24 Jun, 10:12 AM", by: "You" }, { status: "open", label: "Routed to Accounts & KYC", at: "24 Jun, 10:13 AM", by: "System" }, { status: "in-progress", label: "Emma Wilson picked up the ticket", at: "24 Jun, 11:40 AM", by: "Emma Wilson" }, { status: "awaiting-customer", label: "Requested a clearer scan", at: "25 Jun, 09:05 AM", by: "Emma Wilson" }, { status: "in-progress", label: "You re-uploaded the document", at: "28 Jun, 04:22 PM", by: "You" }, ], }, { id: "LUP-47980", subject: "Unable to receive login OTP on mobile", category: "Login / Account access", department: "Portal & Login", priority: "urgent", status: "resolved", createdAt: "18 Jun 2026", updatedAt: "19 Jun 2026", agentId: "a3", timeline: [ { status: "created", label: "Ticket created", at: "18 Jun, 08:40 PM", by: "You" }, { status: "in-progress", label: "Sophie Turner investigating SMS gateway", at: "18 Jun, 09:02 PM", by: "Sophie Turner" }, { status: "resolved", label: "OTP delivery restored", at: "19 Jun, 10:15 AM", by: "Sophie Turner" }, ], }, { id: "LUP-47512", subject: "Duplicate maintenance invoice for June", category: "Billing & payments", department: "Billing", priority: "medium", status: "awaiting-customer", createdAt: "12 Jun 2026", updatedAt: "26 Jun 2026", agentId: "a2", timeline: [ { status: "created", label: "Ticket created", at: "12 Jun, 02:10 PM", by: "You" }, { status: "in-progress", label: "Liam Foster reviewing billing run", at: "12 Jun, 03:30 PM", by: "Liam Foster" }, { status: "awaiting-customer", label: "Need bank reference for the second debit", at: "26 Jun, 11:00 AM", by: "Liam Foster" }, ], }, { id: "LUP-46330", subject: "Reschedule roof inspection visit", category: "Inspection / Site visit", department: "Inspections", priority: "low", status: "closed", createdAt: "02 Jun 2026", updatedAt: "06 Jun 2026", agentId: "a4", timeline: [ { status: "created", label: "Ticket created", at: "02 Jun, 06:20 PM", by: "You" }, { status: "in-progress", label: "Noah Mitchell coordinating slot", at: "03 Jun, 10:00 AM", by: "Noah Mitchell" }, { status: "resolved", label: "Visit moved to 09 Jun, 11 AM", at: "04 Jun, 04:45 PM", by: "Noah Mitchell" }, { status: "closed", label: "Inspection completed", at: "06 Jun, 01:30 PM", by: "System" }, ], }, ]; // Message Center — inbox threads + messages (with a typing flag). export type ChatMsg = { id: string; from: "me" | "agent"; text: string; at: string }; export type SupportThread = { id: string; agentId: string; subject: string; preview: string; unread: number; pinned: boolean; typing: boolean; updatedAt: string; messages: ChatMsg[]; }; export const supportThreads: SupportThread[] = [ { id: "t1", agentId: "a1", subject: "KYC — Aadhaar re-upload", preview: "Thanks, I can see the new scan now…", unread: 2, pinned: true, typing: true, updatedAt: "2 min ago", messages: [ { id: "m1", from: "agent", text: "Hi James, this is Emma from Accounts & KYC. I'm looking into ticket LUP-48217.", at: "11:40 AM" }, { id: "m2", from: "me", text: "Thanks Emma. The earlier scan was blurry so I uploaded a sharper one.", at: "11:42 AM" }, { id: "m3", from: "agent", text: "Thanks, I can see the new scan now. Reviewing it with the verification team.", at: "11:43 AM" }, ], }, { id: "t2", agentId: "a2", subject: "Billing — duplicate invoice", preview: "Could you share the bank reference number?", unread: 0, pinned: false, typing: false, updatedAt: "Yesterday", messages: [ { id: "m1", from: "agent", text: "Hello! I'm reviewing the June billing run for the duplicate charge.", at: "3:30 PM" }, { id: "m2", from: "agent", text: "Could you share the bank reference number for the second debit?", at: "3:32 PM" }, ], }, { id: "t3", agentId: "a3", subject: "Login — OTP not received", preview: "Glad it's working now. Closing this out.", unread: 0, pinned: false, typing: false, updatedAt: "19 Jun", messages: [ { id: "m1", from: "me", text: "I stopped getting the OTP on my registered mobile last night.", at: "8:40 PM" }, { id: "m2", from: "agent", text: "There was a gateway issue in your region — fixed now. Please retry.", at: "10:14 AM" }, { id: "m3", from: "me", text: "Working now, thank you!", at: "10:20 AM" }, { id: "m4", from: "agent", text: "Glad it's working now. Closing this out.", at: "10:21 AM" }, ], }, ]; /* ---------------------------------------------------------- */ /* HELP CENTER */ /* ---------------------------------------------------------- */ export type HelpArticle = { q: string; a: string }; export type HelpTopic = { id: string; title: string; icon: string; accent: string; count: number; articles: HelpArticle[] }; export const helpTopics: HelpTopic[] = [ { id: "getting-started", title: "Getting started", icon: "rocket", accent: "var(--orange)", count: 3, articles: [ { q: "How do I verify my property details?", a: "Open Profile → Personal Info and click ‘Verify to reveal’. Enter your house number (e.g. 181) to unmask and confirm your records." }, { q: "What does ‘allottee’ mean if I'm a representative?", a: "If you manage the property on someone's behalf, the account holder and the allottee differ. Your relationship is shown on the Personal Info tab." }, { q: "How do I update my profile photo?", a: "Use the camera button on your profile header card. JPG or PNG up to 5 MB." }, ], }, { id: "kyc", title: "KYC & verification", icon: "shield", accent: "var(--blue)", count: 4, articles: [ { q: "Which documents do I need for KYC?", a: "A live photo (mandatory), one identity proof and one address proof. The identity and address documents must be two different document types." }, { q: "Can I use Aadhaar for both ID and address?", a: "No. ID and address proofs must be different document types. Aadhaar can fill either slot, but not both at the same time." }, { q: "How long does verification take?", a: "Most documents are reviewed within 4 business hours. You'll be notified on your registered channels." }, { q: "My document was rejected — what now?", a: "Open the rejected slot, read the reason, and re-upload a clearer copy. A support ticket is created automatically for high-priority cases." }, ], }, { id: "security", title: "Login & security", icon: "lock", accent: "var(--purple)", count: 4, articles: [ { q: "How do I enable two-factor authentication?", a: "Profile → Security → Two-factor. Enabling 2FA requires both your password and a one-time code for confirmation." }, { q: "I'm not receiving my OTP", a: "OTPs are sent to your registered mobile. Check signal and spam filters; if it persists, raise a ticket under Login / Account access." }, { q: "What is the password policy?", a: "At least 8 characters with upper & lower case, a number and a special character — and no common patterns like ‘1234’ or ‘password’." }, { q: "How do I sign out other devices?", a: "Profile → Devices shows every active session. Use ‘Sign out’ on any device, or ‘Sign out all other devices’." }, ], }, { id: "billing", title: "Billing & payments", icon: "card", accent: "var(--green)", count: 3, articles: [ { q: "Where can I download invoices?", a: "Invoices are emailed to your primary email and available under Billing. Receipts are issued within minutes of payment." }, { q: "I was charged twice", a: "Raise a Billing ticket and attach the invoice/receipt. Keep your bank reference handy to speed up the reconciliation." }, { q: "How do I change my billing email?", a: "Add a destination under Notifications and mark it primary for the Billing category in the per-destination matrix." }, ], }, ]; /* ---------------------------------------------------------- */ /* RULES — the 1–13 business-rules checklist */ /* ---------------------------------------------------------- */ export type Rule = { n: number; title: string; detail: string; tag: string; tagColor: string }; export const rules: Rule[] = [ { n: 1, title: "Verify-to-reveal", detail: "Masked personal details (email, mobile, PAN, Aadhaar) stay hidden until the account holder confirms their house number. For this account the house number is 181.", tag: "Profile", tagColor: "var(--orange)" }, { n: 2, title: "OTP goes to the registered mobile", detail: "Any sensitive change — email, mobile or password — sends a one-time code to the registered mobile number, never to the new/unverified destination.", tag: "Security", tagColor: "var(--blue)" }, { n: 3, title: "Allottee ≠ account holder", detail: "When the relationship is not ‘Self’, the account holder is a representative and the allottee's name is shown separately. Actions are logged against the account holder.", tag: "Profile", tagColor: "var(--orange)" }, { n: 4, title: "KYC needs two different documents", detail: "Identity proof and address proof must be two different document types. A single document (e.g. Aadhaar) cannot satisfy both slots at once.", tag: "KYC", tagColor: "var(--purple)" }, { n: 5, title: "Live photo is mandatory", detail: "A live photo / selfie is required for KYC completion in addition to the ID and address proofs. Completion percentage never reaches 100% without it.", tag: "KYC", tagColor: "var(--purple)" }, { n: 6, title: "Password policy", detail: "Minimum 8 characters with upper & lower case, a number and a special character. Common patterns like ‘1234’, ‘qwerty’ or ‘password’ are rejected.", tag: "Security", tagColor: "var(--blue)" }, { n: 7, title: "2FA requires password + OTP", detail: "Turning two-factor authentication on or off requires re-entering the password and confirming a one-time code. Authenticator apps and login alerts build on this.", tag: "Security", tagColor: "var(--blue)" }, { n: 8, title: "Ticket lifecycle", detail: "Tickets move Open → In Progress → Awaiting Customer → Resolved → Closed. Every transition is timestamped and attributed in the status timeline.", tag: "Support", tagColor: "var(--green)" }, { n: 9, title: "Category → department routing", detail: "Each ticket category auto-routes to the right department (e.g. KYC → Accounts & KYC) with its own SLA shown before submission.", tag: "Support", tagColor: "var(--green)" }, { n: 10, title: "Attachment rules", detail: "Some categories require an attachment; each defines accepted formats and a maximum file size. The form blocks submission until required attachments are present.", tag: "Support", tagColor: "var(--green)" }, { n: 11, title: "Contact-time windows + timezone", detail: "Callbacks and proactive messages are only sent inside the contact-time windows the user enables, interpreted in their selected timezone.", tag: "Notifications", tagColor: "var(--cyan)" }, { n: 12, title: "Statutory consent is locked", detail: "Essential and statutory consents (Terms of Use, KYC processing) cannot be switched off while the account is active. Marketing consents and sub-toggles are fully optional.", tag: "Privacy", tagColor: "var(--red)" }, { n: 13, title: "New-device login alerts", detail: "Signing in from an untrusted device raises a security alert on the registered channels and lists the device under Devices for review or sign-out.", tag: "Security", tagColor: "var(--blue)" }, ]; /* ---------------------------------------------------------- */ /* password strength (shared with Security tab) */ /* ---------------------------------------------------------- */ export type Strength = { score: number; label: "Weak" | "Fair" | "Good" | "Strong"; checks: { ok: boolean; label: string }[] }; export function passwordStrength(pw: string): Strength { const checks = [ { ok: pw.length >= 8, label: "At least 8 characters" }, { ok: /[a-z]/.test(pw) && /[A-Z]/.test(pw), label: "Upper & lower case" }, { ok: /\d/.test(pw), label: "A number" }, { ok: /[^A-Za-z0-9]/.test(pw), label: "A special character" }, ]; const passed = checks.filter((c) => c.ok).length; const weakList = ["1234", "abcd", "password", "qwerty", "0000"]; const isWeak = weakList.some((w) => pw.toLowerCase().includes(w)); const score = isWeak ? Math.min(passed, 1) : passed; const label = score <= 1 ? "Weak" : score === 2 ? "Fair" : score === 3 ? "Good" : "Strong"; return { score, label, checks }; }