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:
@@ -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 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 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 };
|
||||
}
|
||||
Reference in New Issue
Block a user