Files
lynkeduppro-crm/src/components/dashboard/account-data.ts
T
Goutam d7eb82f182 Redesign Support as bento hub, restore full sidebar, Western names
- Support Center overview rebuilt as a bento grid: large live-chat hero
  tile, support team tile, mini channel tiles and a recent-tickets strip.
- Restore full workspace sidebar (Workspace/Team/Profile groups) with a
  premium golden active state; unbuilt modules show a Coming Soon view.
- Use Western names across the UI (James Carter; Emma Wilson, Liam Foster,
  Sophie Turner, Noah Mitchell) incl. tickets, threads, portal demo user.
- Remove the Allottee card from Profile; add a clean Account & identity
  card and live-editable contacts.
- Relabel "Plot" -> "House number" everywhere; drop the allotment chip in
  favour of the property category.
- Premium visual pass: brand gradients, depth, glow accents, pill tabs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 22:19:07 +05:30

567 lines
30 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// ============================================================
// 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 113 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 };
}