Replace dashboard with Profile, Support Center & Rules modules

- Remove old dashboard stats/charts content; keep sidebar + header shell
  which now routes between Profile, Support Center and Rules views.
- Profile: hero card + 6 tabs (Personal Info with verify-to-reveal +
  editable contacts via OTP, KYC two-doc rule + completion %, Security
  password policy + 2FA, Notifications matrix + contact windows, Privacy
  consents, Devices + activity timeline).
- Support Center: 5 channel cards, support team, Message Center with
  typing, New Ticket category->department routing + attachment rules,
  My Tickets + status-timeline modal, Live Chat handshake, Callback/Email
  modals, Help Center search + accordion.
- Rules: the 1-13 business-rules checklist.
- Shared design system (Icon, Avatar, PageHead, Pill, Toggle, OtpField,
  Modal, Toast, Field, Tabs) + full mock data in account-data.ts.
- Premium dark visual pass: brand gradients, depth, glow accents, pill tabs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-29 21:52:34 +05:30
parent 5ff655d786
commit 3ab6768c1c
13 changed files with 2663 additions and 512 deletions
+566
View File
@@ -0,0 +1,566 @@
// ============================================================
// 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:
// Rajesh Kumar Sharma · Allotment YEA-654321 · Plot 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; // 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: "Rajesh Kumar Sharma",
firstName: "Rajesh",
lastName: "Sharma",
initials: "RS",
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: "Rajesh Kumar Sharma",
email: { label: "Email", masked: maskEmail("rajesh.sharma@example.com"), full: "rajesh.sharma@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: "rajesh.sharma@example.com", verified: true, primary: true },
{ id: "e2", channel: "email", label: "Work email", value: "r.sharma@acme.co.in", 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: "Priya Nair", initials: "PN", role: "Senior Support Lead", team: "Accounts & KYC", gradient: "linear-gradient(135deg,#fda913,#fd6d13)", status: "online", rating: 4.9 },
{ id: "a2", name: "Arjun Mehta", initials: "AM", role: "Support Specialist", team: "Billing", gradient: "linear-gradient(135deg,#285ef0,#09b9c6)", status: "online", rating: 4.8 },
{ id: "a3", name: "Sara Khan", initials: "SK", role: "Technical Support", team: "Portal & Login", gradient: "linear-gradient(135deg,#9036e9,#285ef0)", status: "away", rating: 4.7 },
{ id: "a4", name: "Vikram Rao", initials: "VR", 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: "Priya Nair picked up the ticket", at: "24 Jun, 11:40 AM", by: "Priya Nair" },
{ status: "awaiting-customer", label: "Requested a clearer scan", at: "25 Jun, 09:05 AM", by: "Priya Nair" },
{ 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: "Sara Khan investigating SMS gateway", at: "18 Jun, 09:02 PM", by: "Sara Khan" },
{ status: "resolved", label: "OTP delivery restored", at: "19 Jun, 10:15 AM", by: "Sara Khan" },
],
},
{
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: "Arjun Mehta reviewing billing run", at: "12 Jun, 03:30 PM", by: "Arjun Mehta" },
{ status: "awaiting-customer", label: "Need bank reference for the second debit", at: "26 Jun, 11:00 AM", by: "Arjun Mehta" },
],
},
{
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: "Vikram Rao coordinating slot", at: "03 Jun, 10:00 AM", by: "Vikram Rao" },
{ status: "resolved", label: "Visit moved to 09 Jun, 11 AM", at: "04 Jun, 04:45 PM", by: "Vikram Rao" },
{ 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 Rajesh, this is Priya from Accounts & KYC. I'm looking into ticket LUP-48217.", at: "11:40 AM" },
{ id: "m2", from: "me", text: "Thanks Priya. 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 plot 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 plot number. For this account the plot 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 };
}