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>
This commit is contained in:
@@ -235,6 +235,29 @@
|
|||||||
}
|
}
|
||||||
.dash-root .view { position: relative; z-index: 1; }
|
.dash-root .view { position: relative; z-index: 1; }
|
||||||
|
|
||||||
|
/* ---- sidebar (premium nav) ---- */
|
||||||
|
.dash-root .dash-sidebar { width: 236px; flex: 0 0 236px; background: linear-gradient(180deg, color-mix(in srgb, var(--orange) 4%, var(--sidebar)) 0%, var(--sidebar) 30%); }
|
||||||
|
.dash-root .dash-brand { padding: 20px 18px 16px; }
|
||||||
|
.dash-root .dash-brand .bk { font-size: 15.5px; font-weight: 800; letter-spacing: -0.01em; }
|
||||||
|
.dash-root .dash-nav { padding: 4px 12px 14px; }
|
||||||
|
.dash-root .nav-section { margin-bottom: 4px; }
|
||||||
|
.dash-root .nav-section + .nav-section { border-top: 1px solid var(--border); padding-top: 4px; }
|
||||||
|
.dash-root .nav-group { font-size: 10px; font-weight: 800; letter-spacing: 0.14em; text-transform: uppercase; color: var(--faint); padding: 13px 10px 7px; }
|
||||||
|
.dash-root .nav-item { display: flex; align-items: center; gap: 11px; width: 100%; padding: 9px 11px; border-radius: 10px; border: 0; background: none; cursor: pointer; color: var(--muted); font-family: inherit; font-size: 13px; font-weight: 500; text-align: left; transition: background 0.14s, color 0.14s, transform 0.12s; }
|
||||||
|
.dash-root .nav-item svg { color: var(--faint); flex: 0 0 auto; transition: color 0.14s; }
|
||||||
|
.dash-root .nav-item-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||||
|
.dash-root .nav-item:hover { background: var(--panel-2); color: var(--text); }
|
||||||
|
.dash-root .nav-item:hover svg { color: var(--text-2); }
|
||||||
|
.dash-root .nav-item.active { background: var(--grad-brand); color: #1a1206; font-weight: 700; box-shadow: var(--glow-orange); }
|
||||||
|
.dash-root .nav-item.active svg { color: #1a1206; }
|
||||||
|
|
||||||
|
/* ---- coming-soon placeholder ---- */
|
||||||
|
.dash-root .coming-soon { display: flex; flex-direction: column; align-items: center; text-align: center; padding: 56px 24px; gap: 8px; }
|
||||||
|
.dash-root .coming-soon-ic { width: 76px; height: 76px; border-radius: 22px; display: grid; place-items: center; color: var(--orange); background: color-mix(in srgb, var(--orange) 12%, transparent); margin-bottom: 8px; }
|
||||||
|
.dash-root .coming-soon h3 { font-size: 18px; font-weight: 800; }
|
||||||
|
.dash-root .coming-soon p { font-size: 13px; color: var(--muted); max-width: 440px; line-height: 1.55; }
|
||||||
|
.dash-root .coming-soon-actions { display: flex; gap: 10px; margin-top: 14px; flex-wrap: wrap; justify-content: center; }
|
||||||
|
|
||||||
/* ---- animations ---- */
|
/* ---- animations ---- */
|
||||||
@keyframes ds-fade { from { opacity: 0; } to { opacity: 1; } }
|
@keyframes ds-fade { from { opacity: 0; } to { opacity: 1; } }
|
||||||
@keyframes ds-pop { from { opacity: 0; transform: translateY(10px) scale(0.985); } to { opacity: 1; transform: none; } }
|
@keyframes ds-pop { from { opacity: 0; transform: translateY(10px) scale(0.985); } to { opacity: 1; transform: none; } }
|
||||||
@@ -340,7 +363,7 @@
|
|||||||
.dash-root .link-inline { background: none; border: 0; color: var(--orange); font-size: 12px; font-weight: 600; cursor: pointer; font-family: inherit; }
|
.dash-root .link-inline { background: none; border: 0; color: var(--orange); font-size: 12px; font-weight: 600; cursor: pointer; font-family: inherit; }
|
||||||
|
|
||||||
/* ---- Segmented / Tabs ---- */
|
/* ---- Segmented / Tabs ---- */
|
||||||
.dash-root .ds-segmented { display: inline-flex; gap: 3px; padding: 4px; background: var(--panel-2); border: 1px solid var(--border); border-radius: 12px; flex-wrap: wrap; }
|
.dash-root .ds-segmented { display: inline-flex; gap: 3px; padding: 4px; background: var(--panel-2); border: 1px solid var(--border); border-radius: 12px; flex-wrap: wrap; margin-bottom: 20px; }
|
||||||
.dash-root .ds-segmented .seg { display: inline-flex; align-items: center; gap: 6px; height: 32px; padding: 0 14px; border: 0; border-radius: 9px; background: none; color: var(--muted); font-family: inherit; font-size: 12.5px; font-weight: 600; cursor: pointer; transition: 0.14s; }
|
.dash-root .ds-segmented .seg { display: inline-flex; align-items: center; gap: 6px; height: 32px; padding: 0 14px; border: 0; border-radius: 9px; background: none; color: var(--muted); font-family: inherit; font-size: 12.5px; font-weight: 600; cursor: pointer; transition: 0.14s; }
|
||||||
.dash-root .ds-segmented .seg:hover { color: var(--text); }
|
.dash-root .ds-segmented .seg:hover { color: var(--text); }
|
||||||
.dash-root .ds-segmented .seg.active { background: var(--panel); color: var(--text); box-shadow: var(--card-hi), 0 4px 12px -7px rgba(0,0,0,0.5); }
|
.dash-root .ds-segmented .seg.active { background: var(--panel); color: var(--text); box-shadow: var(--card-hi), 0 4px 12px -7px rgba(0,0,0,0.5); }
|
||||||
@@ -524,7 +547,73 @@
|
|||||||
.dash-root .tl-dot.ev-resolved, .dash-root .tl-dot.ev-closed { background: color-mix(in srgb, var(--green) 18%, transparent); color: var(--green); }
|
.dash-root .tl-dot.ev-resolved, .dash-root .tl-dot.ev-closed { background: color-mix(in srgb, var(--green) 18%, transparent); color: var(--green); }
|
||||||
.dash-root .tl-body { padding-top: 2px; } .dash-root .tl-title { font-size: 13px; font-weight: 600; } .dash-root .tl-meta { font-size: 11.5px; color: var(--muted); margin-top: 1px; } .dash-root .tl-time { font-size: 11px; color: var(--faint); margin-top: 2px; }
|
.dash-root .tl-body { padding-top: 2px; } .dash-root .tl-title { font-size: 13px; font-weight: 600; } .dash-root .tl-meta { font-size: 11.5px; color: var(--muted); margin-top: 1px; } .dash-root .tl-time { font-size: 11px; color: var(--faint); margin-top: 2px; }
|
||||||
|
|
||||||
/* ---- Support: channel cards ---- */
|
/* ---- Support: Bento hub ---- */
|
||||||
|
.dash-root .support-bento {
|
||||||
|
display: grid; gap: 16px; grid-template-columns: repeat(4, 1fr);
|
||||||
|
grid-template-areas:
|
||||||
|
"chat chat team team"
|
||||||
|
"chat chat team team"
|
||||||
|
"ticket call email help"
|
||||||
|
"recent recent recent recent";
|
||||||
|
}
|
||||||
|
.dash-root .bento-tile { border: 1px solid var(--border); border-radius: 18px; background: var(--card-grad); box-shadow: var(--card-hi); padding: 20px; }
|
||||||
|
.dash-root .bento-tile-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 14px; }
|
||||||
|
.dash-root .bento-tile-head h3 { font-size: 15px; font-weight: 700; }
|
||||||
|
|
||||||
|
/* hero live-chat tile */
|
||||||
|
.dash-root .bento-chat { grid-area: chat; position: relative; overflow: hidden; display: flex; flex-direction: column; justify-content: space-between; gap: 20px; min-height: 268px; text-align: left; cursor: pointer; font-family: inherit; border: 0; color: #1a1206; background: linear-gradient(135deg, #ffb43c 0%, #fda913 45%, #fd6d13 100%); box-shadow: var(--glow-orange); transition: transform 0.18s, box-shadow 0.18s; }
|
||||||
|
.dash-root .bento-chat:hover { transform: translateY(-3px); box-shadow: 0 26px 54px -20px color-mix(in srgb, var(--orange) 85%, #000); }
|
||||||
|
.dash-root .bento-chat::after { content: ""; position: absolute; right: -70px; top: -70px; width: 260px; height: 260px; border-radius: 50%; background: radial-gradient(circle, rgba(255,255,255,0.28), transparent 68%); pointer-events: none; }
|
||||||
|
.dash-root .bento-chat::before { content: ""; position: absolute; left: -40px; bottom: -80px; width: 220px; height: 220px; border-radius: 50%; background: radial-gradient(circle, rgba(26,18,6,0.12), transparent 70%); pointer-events: none; }
|
||||||
|
.dash-root .bento-chat-top { display: flex; justify-content: space-between; align-items: center; gap: 10px; position: relative; z-index: 1; font-size: 12px; font-weight: 600; flex-wrap: wrap; }
|
||||||
|
.dash-root .bento-chat-badge { display: inline-flex; align-items: center; gap: 6px; background: rgba(26,18,6,0.14); padding: 6px 11px; border-radius: 99px; }
|
||||||
|
.dash-root .bento-chat-badge .ds-statusdot { background: #15461f; }
|
||||||
|
.dash-root .bento-chat-wait { display: inline-flex; align-items: center; gap: 5px; color: rgba(26,18,6,0.72); }
|
||||||
|
.dash-root .bento-chat-mid { position: relative; z-index: 1; }
|
||||||
|
.dash-root .bento-chat-mid h2 { font-size: 28px; font-weight: 800; letter-spacing: -0.02em; line-height: 1.12; }
|
||||||
|
.dash-root .bento-chat-mid p { font-size: 13px; margin-top: 9px; color: rgba(26,18,6,0.8); max-width: 360px; line-height: 1.5; }
|
||||||
|
.dash-root .bento-chat-foot { display: flex; align-items: center; justify-content: space-between; gap: 14px; position: relative; z-index: 1; flex-wrap: wrap; }
|
||||||
|
.dash-root .bento-avstack { display: flex; align-items: center; }
|
||||||
|
.dash-root .bento-avstack .ds-avatar { margin-left: -9px; box-shadow: 0 0 0 2px #fd8a13; border-radius: 50%; }
|
||||||
|
.dash-root .bento-avstack .ds-avatar:first-child { margin-left: 0; }
|
||||||
|
.dash-root .bento-av-more { margin-left: 11px; font-size: 12px; font-weight: 700; }
|
||||||
|
.dash-root .bento-chat-cta { display: inline-flex; align-items: center; gap: 8px; background: #1a1206; color: #fff; padding: 12px 20px; border-radius: 12px; font-size: 13.5px; font-weight: 700; transition: transform 0.14s; }
|
||||||
|
.dash-root .bento-chat:hover .bento-chat-cta { transform: translateX(3px); }
|
||||||
|
|
||||||
|
/* team tile */
|
||||||
|
.dash-root .bento-team { grid-area: team; display: flex; flex-direction: column; }
|
||||||
|
.dash-root .bento-team .team-list { overflow-y: auto; }
|
||||||
|
|
||||||
|
/* mini tiles */
|
||||||
|
.dash-root .bento-mini { position: relative; overflow: hidden; text-align: left; cursor: pointer; font-family: inherit; display: flex; flex-direction: column; gap: 5px; min-height: 150px; transition: transform 0.18s, box-shadow 0.18s, border-color 0.18s; }
|
||||||
|
.dash-root .bento-mini::before { content: ""; position: absolute; top: -45%; right: -25%; width: 150px; height: 150px; border-radius: 50%; background: radial-gradient(circle, color-mix(in srgb, var(--accent) 24%, transparent), transparent 70%); opacity: 0; transition: opacity 0.25s; pointer-events: none; }
|
||||||
|
.dash-root .bento-mini:hover { transform: translateY(-3px); border-color: color-mix(in srgb, var(--accent) 50%, transparent); box-shadow: var(--card-hi), 0 16px 36px -22px color-mix(in srgb, var(--accent) 75%, #000); }
|
||||||
|
.dash-root .bento-mini:hover::before { opacity: 1; }
|
||||||
|
.dash-root .bento-mini-ic { width: 44px; height: 44px; border-radius: 13px; display: grid; place-items: center; color: #fff; background: linear-gradient(135deg, var(--accent), color-mix(in srgb, var(--accent) 55%, #000)); box-shadow: 0 8px 18px -10px color-mix(in srgb, var(--accent) 80%, transparent); margin-bottom: 8px; }
|
||||||
|
.dash-root .bento-mini-title { font-size: 14.5px; font-weight: 700; }
|
||||||
|
.dash-root .bento-mini-desc { font-size: 12px; color: var(--muted); flex: 1; line-height: 1.45; }
|
||||||
|
.dash-root .bento-mini-meta { display: flex; align-items: center; justify-content: space-between; gap: 6px; font-size: 11.5px; color: var(--accent); font-weight: 600; margin-top: 8px; }
|
||||||
|
|
||||||
|
/* recent tickets */
|
||||||
|
.dash-root .bento-recent { grid-area: recent; }
|
||||||
|
.dash-root .bento-ticket-row { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; }
|
||||||
|
.dash-root .bento-ticket { text-align: left; cursor: pointer; font-family: inherit; padding: 14px; border-radius: 14px; border: 1px solid var(--border); background: var(--panel-2); display: flex; flex-direction: column; gap: 7px; transition: 0.14s; }
|
||||||
|
.dash-root .bento-ticket:hover { border-color: var(--border-2); background: var(--panel-3); transform: translateY(-2px); }
|
||||||
|
.dash-root .bento-ticket-top { display: flex; align-items: center; justify-content: space-between; gap: 6px; }
|
||||||
|
.dash-root .bento-ticket-subj { font-size: 13px; font-weight: 600; line-height: 1.35; }
|
||||||
|
.dash-root .bento-ticket-meta { display: flex; align-items: center; gap: 5px; font-size: 11px; color: var(--faint); }
|
||||||
|
|
||||||
|
@media (max-width: 1180px) {
|
||||||
|
.dash-root .support-bento { grid-template-columns: repeat(2, 1fr); grid-template-areas: "chat chat" "team team" "ticket call" "email help" "recent recent"; }
|
||||||
|
.dash-root .bento-ticket-row { grid-template-columns: repeat(2, 1fr); }
|
||||||
|
.dash-root .bento-team .team-list { max-height: none; }
|
||||||
|
}
|
||||||
|
@media (max-width: 680px) {
|
||||||
|
.dash-root .support-bento { grid-template-columns: 1fr; grid-template-areas: "chat" "team" "ticket" "call" "email" "help" "recent"; }
|
||||||
|
.dash-root .bento-ticket-row { grid-template-columns: 1fr; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Support: channel cards (legacy) ---- */
|
||||||
.dash-root .channel-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
|
.dash-root .channel-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
|
||||||
.dash-root .channel-card { text-align: left; position: relative; overflow: hidden; padding: 20px; border-radius: 18px; border: 1px solid var(--border); background: var(--card-grad); box-shadow: var(--card-hi); cursor: pointer; font-family: inherit; transition: transform 0.18s, box-shadow 0.18s, border-color 0.18s; display: flex; flex-direction: column; gap: 6px; }
|
.dash-root .channel-card { text-align: left; position: relative; overflow: hidden; padding: 20px; border-radius: 18px; border: 1px solid var(--border); background: var(--card-grad); box-shadow: var(--card-hi); cursor: pointer; font-family: inherit; transition: transform 0.18s, box-shadow 0.18s, border-color 0.18s; display: flex; flex-direction: column; gap: 6px; }
|
||||||
.dash-root .channel-card::before { content: ""; position: absolute; top: -45%; right: -20%; width: 190px; height: 190px; border-radius: 50%; background: radial-gradient(circle, color-mix(in srgb, var(--accent) 26%, transparent), transparent 70%); opacity: 0; transition: opacity 0.25s; pointer-events: none; }
|
.dash-root .channel-card::before { content: ""; position: absolute; top: -45%; right: -20%; width: 190px; height: 190px; border-radius: 50%; background: radial-gradient(circle, color-mix(in srgb, var(--accent) 26%, transparent), transparent 70%); opacity: 0; transition: opacity 0.25s; pointer-events: none; }
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
// fully clickable without a backend. Ready-to-paste shapes.
|
// fully clickable without a backend. Ready-to-paste shapes.
|
||||||
//
|
//
|
||||||
// Anchored to the same demo identity used by the auth portal:
|
// Anchored to the same demo identity used by the auth portal:
|
||||||
// Rajesh Kumar Sharma · Allotment YEA-654321 · Plot 181.
|
// James Carter · Allotment YEA-654321 · House 181.
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
/* ---------------------------------------------------------- */
|
/* ---------------------------------------------------------- */
|
||||||
@@ -38,7 +38,7 @@ export type User = {
|
|||||||
avatarGradient: string;
|
avatarGradient: string;
|
||||||
// property / allotment
|
// property / allotment
|
||||||
allotmentNo: string;
|
allotmentNo: string;
|
||||||
plot: string; // verify-to-reveal key
|
plot: string; // house number — verify-to-reveal key
|
||||||
sector: string;
|
sector: string;
|
||||||
pocket: string;
|
pocket: string;
|
||||||
category: string;
|
category: string;
|
||||||
@@ -59,10 +59,10 @@ export type User = {
|
|||||||
|
|
||||||
export const user: User = {
|
export const user: User = {
|
||||||
id: "usr_LUP_00181",
|
id: "usr_LUP_00181",
|
||||||
name: "Rajesh Kumar Sharma",
|
name: "James Carter",
|
||||||
firstName: "Rajesh",
|
firstName: "James",
|
||||||
lastName: "Sharma",
|
lastName: "Carter",
|
||||||
initials: "RS",
|
initials: "JC",
|
||||||
role: "Property Owner",
|
role: "Property Owner",
|
||||||
avatarGradient: "linear-gradient(135deg,#fda913,#fd6d13)",
|
avatarGradient: "linear-gradient(135deg,#fda913,#fd6d13)",
|
||||||
allotmentNo: "YEA-654321",
|
allotmentNo: "YEA-654321",
|
||||||
@@ -73,8 +73,8 @@ export const user: User = {
|
|||||||
size: "300 sq.m",
|
size: "300 sq.m",
|
||||||
isAllottee: true,
|
isAllottee: true,
|
||||||
relationship: "Self",
|
relationship: "Self",
|
||||||
allotteeName: "Rajesh Kumar Sharma",
|
allotteeName: "James Carter",
|
||||||
email: { label: "Email", masked: maskEmail("rajesh.sharma@example.com"), full: "rajesh.sharma@example.com" },
|
email: { label: "Email", masked: maskEmail("james.carter@example.com"), full: "james.carter@example.com" },
|
||||||
mobile: { label: "Mobile", masked: "+91 ••••• ••012", full: "+91 98765 43012" },
|
mobile: { label: "Mobile", masked: "+91 ••••• ••012", full: "+91 98765 43012" },
|
||||||
whatsapp: { label: "WhatsApp", masked: "+91 ••••• ••012", full: "+91 98765 43012" },
|
whatsapp: { label: "WhatsApp", masked: "+91 ••••• ••012", full: "+91 98765 43012" },
|
||||||
pan: { label: "PAN", masked: "•••••••• 12F", full: "ABCPS1234F" },
|
pan: { label: "PAN", masked: "•••••••• 12F", full: "ABCPS1234F" },
|
||||||
@@ -170,8 +170,8 @@ export const contactPrefs: NotifCategory[] = [
|
|||||||
|
|
||||||
export type ContactDestination = { id: string; channel: "email" | "sms" | "whatsapp"; label: string; value: string; verified: boolean; primary: boolean };
|
export type ContactDestination = { id: string; channel: "email" | "sms" | "whatsapp"; label: string; value: string; verified: boolean; primary: boolean };
|
||||||
export const contactDestinations: ContactDestination[] = [
|
export const contactDestinations: ContactDestination[] = [
|
||||||
{ id: "e1", channel: "email", label: "Personal email", value: "rajesh.sharma@example.com", verified: true, primary: true },
|
{ id: "e1", channel: "email", label: "Personal email", value: "james.carter@example.com", verified: true, primary: true },
|
||||||
{ id: "e2", channel: "email", label: "Work email", value: "r.sharma@acme.co.in", verified: false, primary: false },
|
{ 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: "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 },
|
{ id: "w1", channel: "whatsapp", label: "WhatsApp", value: "+91 98765 43012", verified: true, primary: true },
|
||||||
];
|
];
|
||||||
@@ -290,10 +290,10 @@ export type SupportAgent = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const supportTeam: SupportAgent[] = [
|
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: "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: "Arjun Mehta", initials: "AM", role: "Support Specialist", team: "Billing", gradient: "linear-gradient(135deg,#285ef0,#09b9c6)", status: "online", rating: 4.8 },
|
{ 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: "Sara Khan", initials: "SK", role: "Technical Support", team: "Portal & Login", gradient: "linear-gradient(135deg,#9036e9,#285ef0)", status: "away", rating: 4.7 },
|
{ 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: "Vikram Rao", initials: "VR", role: "Field Coordinator", team: "Inspections", gradient: "linear-gradient(135deg,#14bc83,#09b9c6)", status: "offline", rating: 4.9 },
|
{ 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.
|
// Category → department routing + attachment rules for the New Ticket form.
|
||||||
@@ -345,8 +345,8 @@ export const tickets: Ticket[] = [
|
|||||||
timeline: [
|
timeline: [
|
||||||
{ status: "created", label: "Ticket created", at: "24 Jun, 10:12 AM", by: "You" },
|
{ 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: "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: "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: "Priya Nair" },
|
{ 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" },
|
{ status: "in-progress", label: "You re-uploaded the document", at: "28 Jun, 04:22 PM", by: "You" },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -362,8 +362,8 @@ export const tickets: Ticket[] = [
|
|||||||
agentId: "a3",
|
agentId: "a3",
|
||||||
timeline: [
|
timeline: [
|
||||||
{ status: "created", label: "Ticket created", at: "18 Jun, 08:40 PM", by: "You" },
|
{ 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: "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: "Sara Khan" },
|
{ status: "resolved", label: "OTP delivery restored", at: "19 Jun, 10:15 AM", by: "Sophie Turner" },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -378,8 +378,8 @@ export const tickets: Ticket[] = [
|
|||||||
agentId: "a2",
|
agentId: "a2",
|
||||||
timeline: [
|
timeline: [
|
||||||
{ status: "created", label: "Ticket created", at: "12 Jun, 02:10 PM", by: "You" },
|
{ 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: "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: "Arjun Mehta" },
|
{ status: "awaiting-customer", label: "Need bank reference for the second debit", at: "26 Jun, 11:00 AM", by: "Liam Foster" },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -394,8 +394,8 @@ export const tickets: Ticket[] = [
|
|||||||
agentId: "a4",
|
agentId: "a4",
|
||||||
timeline: [
|
timeline: [
|
||||||
{ status: "created", label: "Ticket created", at: "02 Jun, 06:20 PM", by: "You" },
|
{ 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: "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: "Vikram Rao" },
|
{ 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" },
|
{ status: "closed", label: "Inspection completed", at: "06 Jun, 01:30 PM", by: "System" },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -426,8 +426,8 @@ export const supportThreads: SupportThread[] = [
|
|||||||
typing: true,
|
typing: true,
|
||||||
updatedAt: "2 min ago",
|
updatedAt: "2 min ago",
|
||||||
messages: [
|
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: "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 Priya. The earlier scan was blurry so I uploaded a sharper one.", at: "11:42 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: "m3", from: "agent", text: "Thanks, I can see the new scan now. Reviewing it with the verification team.", at: "11:43 AM" },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -478,7 +478,7 @@ export const helpTopics: HelpTopic[] = [
|
|||||||
accent: "var(--orange)",
|
accent: "var(--orange)",
|
||||||
count: 3,
|
count: 3,
|
||||||
articles: [
|
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: "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: "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." },
|
{ 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." },
|
||||||
],
|
],
|
||||||
@@ -530,7 +530,7 @@ export const helpTopics: HelpTopic[] = [
|
|||||||
export type Rule = { n: number; title: string; detail: string; tag: string; tagColor: string };
|
export type Rule = { n: number; title: string; detail: string; tag: string; tagColor: string };
|
||||||
|
|
||||||
export const rules: Rule[] = [
|
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: 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: 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: 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: 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)" },
|
||||||
|
|||||||
@@ -1,28 +1,23 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Dashboard shell — keeps the sidebar + header and swaps the
|
// Dashboard shell — premium dark theme. Keeps the sidebar +
|
||||||
// inner content between the Profile, Support and Rules views.
|
// header and swaps the inner content. Profile / Support / Rules
|
||||||
|
// render real views; other workspace modules show a placeholder.
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { Sidebar } from "./sidebar";
|
import { Sidebar, NAV_ITEMS } from "./sidebar";
|
||||||
import { Topbar } from "./topbar";
|
import { Topbar } from "./topbar";
|
||||||
import { ToastProvider } from "./ui";
|
import { ToastProvider, PageHead, Btn, Icon } from "./ui";
|
||||||
import { Profile } from "./profile";
|
import { Profile } from "./profile";
|
||||||
import { Support } from "./support";
|
import { Support } from "./support";
|
||||||
import { Rules } from "./rules";
|
import { Rules } from "./rules";
|
||||||
import "../../app/dashboard/dashboard.css";
|
import "../../app/dashboard/dashboard.css";
|
||||||
|
|
||||||
const HEADINGS: Record<string, { title: string; subtitle: string }> = {
|
|
||||||
profile: { title: "Profile", subtitle: "Your account, identity and preferences" },
|
|
||||||
support: { title: "Support Center", subtitle: "Get help, track requests and find answers" },
|
|
||||||
rules: { title: "Rules Checklist", subtitle: "The 13 rules behind Profile & Support" },
|
|
||||||
};
|
|
||||||
|
|
||||||
export function Dashboard() {
|
export function Dashboard() {
|
||||||
const [theme, setTheme] = useState<"dark" | "light">("dark");
|
const [theme, setTheme] = useState<"dark" | "light">("dark");
|
||||||
const [active, setActive] = useState("profile");
|
const [active, setActive] = useState("dashboard");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Sync the persisted theme from localStorage (an external system) on mount.
|
// Sync the persisted theme from localStorage (an external system) on mount.
|
||||||
@@ -33,21 +28,44 @@ export function Dashboard() {
|
|||||||
setTheme((t) => { const n = t === "dark" ? "light" : "dark"; try { localStorage.setItem("lup_dash_theme", n); } catch {} return n; });
|
setTheme((t) => { const n = t === "dark" ? "light" : "dark"; try { localStorage.setItem("lup_dash_theme", n); } catch {} return n; });
|
||||||
}
|
}
|
||||||
|
|
||||||
const head = HEADINGS[active] ?? HEADINGS.profile;
|
const item = NAV_ITEMS.find((i) => i.key === active);
|
||||||
|
const title = item?.label ?? "Dashboard";
|
||||||
|
const subtitle = item?.subtitle ?? "Workspace module";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="dash-root" data-theme={theme}>
|
<div className="dash-root" data-theme={theme}>
|
||||||
<Sidebar active={active} onSelect={setActive} />
|
<Sidebar active={active} onSelect={setActive} />
|
||||||
<div className="dash-main">
|
<div className="dash-main">
|
||||||
<Topbar theme={theme} onToggle={toggle} title={head.title} subtitle={head.subtitle} />
|
<Topbar theme={theme} onToggle={toggle} title={title} subtitle={subtitle} />
|
||||||
<div className="dash-content">
|
<div className="dash-content">
|
||||||
<ToastProvider>
|
<ToastProvider>
|
||||||
{active === "profile" && <Profile />}
|
{active === "profile" ? <Profile />
|
||||||
{active === "support" && <Support />}
|
: active === "support" ? <Support />
|
||||||
{active === "rules" && <Rules />}
|
: active === "rules" ? <Rules />
|
||||||
|
: <ComingSoon title={title} icon={item?.icon ?? "dashboard"} onGo={setActive} />}
|
||||||
</ToastProvider>
|
</ToastProvider>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Placeholder for workspace modules that don't have a view yet — keeps the
|
||||||
|
// full navigation coherent and points users to the live sections.
|
||||||
|
function ComingSoon({ title, icon, onGo }: { title: string; icon: string; onGo: (k: string) => void }) {
|
||||||
|
return (
|
||||||
|
<div className="view">
|
||||||
|
<PageHead eyebrow="Workspace" title={title} subtitle="This module is part of the LynkedUp Pro workspace." icon={icon} />
|
||||||
|
<div className="card coming-soon">
|
||||||
|
<span className="coming-soon-ic"><Icon name={icon} size={34} /></span>
|
||||||
|
<h3>{title} is coming soon</h3>
|
||||||
|
<p>This area is reserved for the {title} module. In the meantime, the account experience is fully built out.</p>
|
||||||
|
<div className="coming-soon-actions">
|
||||||
|
<Btn icon="user" onClick={() => onGo("profile")}>Open Profile</Btn>
|
||||||
|
<Btn variant="outline" icon="chat" onClick={() => onGo("support")}>Support Center</Btn>
|
||||||
|
<Btn variant="ghost" icon="check-circle" onClick={() => onGo("rules")}>Rules</Btn>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -79,14 +79,14 @@ function ProfileHeaderCard() {
|
|||||||
<div className="profile-hero-role">{user.role} · Member since {user.memberSince}</div>
|
<div className="profile-hero-role">{user.role} · Member since {user.memberSince}</div>
|
||||||
<div className="profile-hero-chips">
|
<div className="profile-hero-chips">
|
||||||
<span className="hero-chip"><Icon name="pin" size={13} /> {user.sector} · {user.pocket}</span>
|
<span className="hero-chip"><Icon name="pin" size={13} /> {user.sector} · {user.pocket}</span>
|
||||||
<span className="hero-chip"><Icon name="key" size={13} /> Allotment {user.allotmentNo}</span>
|
<span className="hero-chip"><Icon name="owners" size={13} /> {user.category}</span>
|
||||||
<span className="hero-chip accent"><Icon name="check" size={13} /> Plot {user.plot}</span>
|
<span className="hero-chip accent"><Icon name="check" size={13} /> House {user.plot}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="profile-hero-stats">
|
<div className="profile-hero-stats">
|
||||||
<HeroStat value={user.category} label="Category" />
|
<HeroStat value={user.category} label="Category" />
|
||||||
<HeroStat value={user.size} label="Plot size" />
|
<HeroStat value={user.size} label="House size" />
|
||||||
<HeroStat value="33%" label="KYC complete" accent="var(--orange)" />
|
<HeroStat value="33%" label="KYC complete" accent="var(--orange)" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -116,33 +116,27 @@ function PersonalInfoTab() {
|
|||||||
|
|
||||||
// editable contact values (live-updating after OTP confirmation)
|
// editable contact values (live-updating after OTP confirmation)
|
||||||
const [contacts, setContacts] = useState({ email: user.email.full, mobile: user.mobile.full, whatsapp: user.whatsapp.full });
|
const [contacts, setContacts] = useState({ email: user.email.full, mobile: user.mobile.full, whatsapp: user.whatsapp.full });
|
||||||
// editable account-holder fields
|
|
||||||
const [editId, setEditId] = useState(false);
|
|
||||||
const [holder, setHolder] = useState({ name: user.name, relationship: user.relationship });
|
|
||||||
const [holderDraft, setHolderDraft] = useState(holder);
|
|
||||||
|
|
||||||
function verify() {
|
function verify() {
|
||||||
if (plot.trim() === user.plot) { setVerified(true); setErr(""); push({ tone: "success", title: "Verified", desc: "Your full details are now visible and editable." }); }
|
if (plot.trim() === user.plot) { setVerified(true); setErr(""); push({ tone: "success", title: "Verified", desc: "Your full details are now visible and editable." }); }
|
||||||
else setErr(`That doesn't match the plot number on file (hint: it's ${user.plot}).`);
|
else setErr(`That doesn't match the house number on file (hint: it's ${user.plot}).`);
|
||||||
}
|
}
|
||||||
|
|
||||||
function mask(kind: ContactKind, full: string) {
|
function mask(kind: ContactKind, full: string) {
|
||||||
return kind === "email" ? maskEmail(full) : maskPhone(full);
|
return kind === "email" ? maskEmail(full) : maskPhone(full);
|
||||||
}
|
}
|
||||||
|
|
||||||
const rows: { key: ContactKind | "pan" | "aadhaar"; label: string; full: string; masked: string; editable: boolean }[] = [
|
const contactRows: { key: ContactKind; label: string; full: string; masked: string }[] = [
|
||||||
{ key: "email", label: "Email", full: contacts.email, masked: mask("email", contacts.email), editable: true },
|
{ key: "email", label: "Email", full: contacts.email, masked: mask("email", contacts.email) },
|
||||||
{ key: "mobile", label: "Mobile", full: contacts.mobile, masked: mask("mobile", contacts.mobile), editable: true },
|
{ key: "mobile", label: "Mobile", full: contacts.mobile, masked: mask("mobile", contacts.mobile) },
|
||||||
{ key: "whatsapp", label: "WhatsApp", full: contacts.whatsapp, masked: mask("whatsapp", contacts.whatsapp), editable: true },
|
{ key: "whatsapp", label: "WhatsApp", full: contacts.whatsapp, masked: mask("whatsapp", contacts.whatsapp) },
|
||||||
{ key: "pan", label: "PAN", full: user.pan.full, masked: user.pan.masked, editable: false },
|
|
||||||
{ key: "aadhaar", label: "Aadhaar", full: user.aadhaar.full, masked: user.aadhaar.masked, editable: false },
|
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid-2">
|
<div className="grid-2">
|
||||||
<div className="card">
|
<div className="card">
|
||||||
<div className="card-head">
|
<div className="card-head">
|
||||||
<h3>Personal details</h3>
|
<h3>Contact details</h3>
|
||||||
{verified
|
{verified
|
||||||
? <Pill tone="green"><Icon name="eye" size={13} /> Revealed</Pill>
|
? <Pill tone="green"><Icon name="eye" size={13} /> Revealed</Pill>
|
||||||
: <Pill tone="orange"><Icon name="eye-off" size={13} /> Masked</Pill>}
|
: <Pill tone="orange"><Icon name="eye-off" size={13} /> Masked</Pill>}
|
||||||
@@ -153,10 +147,10 @@ function PersonalInfoTab() {
|
|||||||
<div className="reveal-gate-ic"><Icon name="lock" size={20} /></div>
|
<div className="reveal-gate-ic"><Icon name="lock" size={20} /></div>
|
||||||
<div className="reveal-gate-txt">
|
<div className="reveal-gate-txt">
|
||||||
<strong>Verify to reveal & edit</strong>
|
<strong>Verify to reveal & edit</strong>
|
||||||
<span>Sensitive details are masked. Enter your plot number to unmask and edit them.</span>
|
<span>Sensitive details are masked. Enter your house number to unmask and edit them.</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="reveal-gate-form">
|
<div className="reveal-gate-form">
|
||||||
<input className="ds-input" placeholder="Plot number" value={plot} maxLength={6}
|
<input className="ds-input" placeholder="House number" value={plot} maxLength={6}
|
||||||
onChange={(e) => { setPlot(e.target.value.replace(/\D/g, "")); setErr(""); }}
|
onChange={(e) => { setPlot(e.target.value.replace(/\D/g, "")); setErr(""); }}
|
||||||
onKeyDown={(e) => e.key === "Enter" && verify()} />
|
onKeyDown={(e) => e.key === "Enter" && verify()} />
|
||||||
<Btn icon="check" onClick={verify}>Reveal</Btn>
|
<Btn icon="check" onClick={verify}>Reveal</Btn>
|
||||||
@@ -166,15 +160,14 @@ function PersonalInfoTab() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<dl className="kv-list">
|
<dl className="kv-list">
|
||||||
{rows.map((f) => (
|
{contactRows.map((f) => (
|
||||||
<div className="kv" key={f.key}>
|
<div className="kv" key={f.key}>
|
||||||
<dt>{f.label}</dt>
|
<dt>{f.label}</dt>
|
||||||
<dd className={verified ? "kv-edit" : "masked"}>
|
<dd className={verified ? "kv-edit" : "masked"}>
|
||||||
{verified ? f.full : f.masked}
|
{verified ? f.full : f.masked}
|
||||||
{verified && f.editable && (
|
{verified && (
|
||||||
<button className="kv-edit-btn" aria-label={`Edit ${f.label}`} onClick={() => setChange(f.key as ContactKind)}><Icon name="edit" size={13} /></button>
|
<button className="kv-edit-btn" aria-label={`Edit ${f.label}`} onClick={() => setChange(f.key)}><Icon name="edit" size={13} /></button>
|
||||||
)}
|
)}
|
||||||
{verified && !f.editable && <span className="kv-lock"><Icon name="lock" size={12} /> via KYC</span>}
|
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
@@ -189,40 +182,26 @@ function PersonalInfoTab() {
|
|||||||
|
|
||||||
<div className="card">
|
<div className="card">
|
||||||
<div className="card-head">
|
<div className="card-head">
|
||||||
<h3>Allottee & account holder</h3>
|
<h3>Account & identity</h3>
|
||||||
{!editId
|
{verified
|
||||||
? <Btn variant="outline" size="sm" icon="edit" onClick={() => { setHolderDraft(holder); setEditId(true); }}>Edit</Btn>
|
? <Pill tone="green"><Icon name="eye" size={13} /> Revealed</Pill>
|
||||||
: <div className="row gap-2"><Btn variant="ghost" size="sm" onClick={() => setEditId(false)}>Cancel</Btn><Btn size="sm" icon="check" onClick={() => { setHolder(holderDraft); setEditId(false); push({ tone: "success", title: "Details saved" }); }}>Save</Btn></div>}
|
: <Pill tone="muted"><Icon name="lock" size={12} /> Protected</Pill>}
|
||||||
</div>
|
</div>
|
||||||
{user.isAllottee ? (
|
<dl className="kv-list">
|
||||||
<div className="callout tone-green">
|
<div className="kv"><dt>Full name</dt><dd>{user.name}</dd></div>
|
||||||
<Icon name="check-circle" size={18} />
|
<div className="kv"><dt>Role</dt><dd>{user.role}</dd></div>
|
||||||
<div><strong>You are the allottee.</strong><span>The account holder and the allottee are the same person ({holder.relationship}).</span></div>
|
<div className="kv"><dt>Member since</dt><dd>{user.memberSince}</dd></div>
|
||||||
|
<div className="kv"><dt>Account status</dt><dd><Pill tone="green"><StatusDot status="online" /> Active</Pill></dd></div>
|
||||||
|
<div className="kv">
|
||||||
|
<dt>PAN</dt>
|
||||||
|
<dd className={verified ? "kv-edit" : "masked"}>{verified ? user.pan.full : user.pan.masked}{verified && <span className="kv-lock"><Icon name="lock" size={12} /> via KYC</span>}</dd>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
<div className="kv">
|
||||||
<div className="callout tone-orange">
|
<dt>Aadhaar</dt>
|
||||||
<Icon name="info" size={18} />
|
<dd className={verified ? "kv-edit" : "masked"}>{verified ? user.aadhaar.full : user.aadhaar.masked}{verified && <span className="kv-lock"><Icon name="lock" size={12} /> via KYC</span>}</dd>
|
||||||
<div><strong>Acting on behalf of the allottee.</strong><span>Account holder differs from the allottee — actions are logged against you.</span></div>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
</dl>
|
||||||
{editId ? (
|
<p className="muted-note"><Icon name="info" size={13} /> PAN & Aadhaar are managed under the KYC tab and can't be edited here.</p>
|
||||||
<div className="edit-grid">
|
|
||||||
<Field label="Account holder name"><input className="ds-input" value={holderDraft.name} onChange={(e) => setHolderDraft((d) => ({ ...d, name: e.target.value }))} /></Field>
|
|
||||||
<Field label="Relationship to allottee">
|
|
||||||
<select className="ds-select" value={holderDraft.relationship} onChange={(e) => setHolderDraft((d) => ({ ...d, relationship: e.target.value }))}>
|
|
||||||
{["Self", "Spouse", "Son / Daughter", "Authorized Representative", "Power of Attorney"].map((r) => <option key={r} value={r}>{r}</option>)}
|
|
||||||
</select>
|
|
||||||
</Field>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<dl className="kv-list">
|
|
||||||
<div className="kv"><dt>Allottee name</dt><dd>{user.allotteeName}</dd></div>
|
|
||||||
<div className="kv"><dt>Account holder</dt><dd>{holder.name}</dd></div>
|
|
||||||
<div className="kv"><dt>Relationship</dt><dd><Pill tone="blue">{holder.relationship}</Pill></dd></div>
|
|
||||||
<div className="kv"><dt>Allotment no.</dt><dd>{user.allotmentNo}</dd></div>
|
|
||||||
<div className="kv"><dt>Property</dt><dd>{user.sector} · {user.pocket} · Plot {user.plot}</dd></div>
|
|
||||||
</dl>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ChangeContactModal
|
<ChangeContactModal
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Rules Checklist — the 1–13 business rules in one place.
|
// Rules Checklist — the 1–13 business rules in one place.
|
||||||
// (plot 181 · OTP to registered mobile · KYC two-docs ·
|
// (house 181 · OTP to registered mobile · KYC two-docs ·
|
||||||
// password policy · ticket lifecycle · …)
|
// password policy · ticket lifecycle · …)
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
|
|||||||
@@ -4,29 +4,61 @@ import { ChevronsUpDown } from "lucide-react";
|
|||||||
import { Icon } from "./ui";
|
import { Icon } from "./ui";
|
||||||
import { user } from "./account-data";
|
import { user } from "./account-data";
|
||||||
|
|
||||||
type Item = { key: string; label: string; icon: string };
|
export type NavItem = { key: string; label: string; icon: string; subtitle?: string };
|
||||||
|
export type NavGroup = { title: string; items: NavItem[] };
|
||||||
|
|
||||||
const GROUPS: { title: string; items: Item[] }[] = [
|
// Full workspace navigation. The Account group items (profile/support/rules)
|
||||||
|
// render real views; the rest are workspace modules shown via a placeholder.
|
||||||
|
export const NAV_GROUPS: NavGroup[] = [
|
||||||
{
|
{
|
||||||
title: "Account",
|
title: "Workspace",
|
||||||
items: [
|
items: [
|
||||||
{ key: "profile", label: "Profile", icon: "user" },
|
{ key: "dashboard", label: "Dashboard", icon: "dashboard", subtitle: "Welcome back — your territory at a glance" },
|
||||||
|
{ key: "owners", label: "Owners Box", icon: "owners" },
|
||||||
|
{ key: "projects", label: "Projects", icon: "projects" },
|
||||||
|
{ key: "leads", label: "Leads", icon: "leads" },
|
||||||
|
{ key: "verify", label: "Lead Verification", icon: "verify" },
|
||||||
|
{ key: "pipeline", label: "Pipeline", icon: "pipeline" },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Help & Support",
|
title: "Workspace",
|
||||||
items: [
|
items: [
|
||||||
{ key: "support", label: "Support Center", icon: "chat" },
|
{ key: "dispatch", label: "LynkDispatch", icon: "dispatch" },
|
||||||
|
{ key: "storm", label: "Storm Intel", icon: "storm" },
|
||||||
|
{ key: "territory", label: "Territory Map", icon: "territory" },
|
||||||
|
{ key: "procanvas", label: "ProCanvas", icon: "procanvas" },
|
||||||
|
{ key: "estimates", label: "Estimates", icon: "estimates" },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Reference",
|
title: "Team",
|
||||||
items: [
|
items: [
|
||||||
{ key: "rules", label: "Rules Checklist", icon: "check-circle" },
|
{ key: "schedule", label: "Team Schedule", icon: "schedule" },
|
||||||
|
{ key: "leaderboard", label: "Leaderboard", icon: "leaderboard" },
|
||||||
|
{ key: "subtasks", label: "Subcontractor Tasks", icon: "subtasks" },
|
||||||
|
{ key: "people", label: "People", icon: "people" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Team",
|
||||||
|
items: [
|
||||||
|
{ key: "settings", label: "Org Settings", icon: "settings" },
|
||||||
|
{ key: "ai", label: "AI Assistant", icon: "ai" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Profile & Documents",
|
||||||
|
items: [
|
||||||
|
{ key: "profile", label: "Profile", icon: "user", subtitle: "Your account, identity and preferences" },
|
||||||
|
{ key: "support", label: "Support Center", icon: "chat", subtitle: "Get help, track requests and find answers" },
|
||||||
|
{ key: "rules", label: "Rules Checklist", icon: "check-circle", subtitle: "The 13 rules behind Profile & Support" },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const NAV_ITEMS: NavItem[] = NAV_GROUPS.flatMap((g) => g.items);
|
||||||
|
|
||||||
export function Sidebar({ active, onSelect }: { active: string; onSelect: (k: string) => void }) {
|
export function Sidebar({ active, onSelect }: { active: string; onSelect: (k: string) => void }) {
|
||||||
return (
|
return (
|
||||||
<aside className="dash-sidebar">
|
<aside className="dash-sidebar">
|
||||||
@@ -37,13 +69,13 @@ export function Sidebar({ active, onSelect }: { active: string; onSelect: (k: st
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<nav className="dash-nav">
|
<nav className="dash-nav">
|
||||||
{GROUPS.map((g, gi) => (
|
{NAV_GROUPS.map((g, gi) => (
|
||||||
<div key={gi}>
|
<div className="nav-section" key={gi}>
|
||||||
<div className="nav-group">{g.title}</div>
|
<div className="nav-group">{g.title}</div>
|
||||||
{g.items.map((it) => (
|
{g.items.map((it) => (
|
||||||
<button key={it.key} className={`nav-item ${active === it.key ? "active" : ""}`} onClick={() => onSelect(it.key)}>
|
<button key={it.key} className={`nav-item ${active === it.key ? "active" : ""}`} onClick={() => onSelect(it.key)}>
|
||||||
<Icon name={it.icon} size={18} />
|
<Icon name={it.icon} size={18} />
|
||||||
{it.label}
|
<span className="nav-item-label">{it.label}</span>
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ export function Support() {
|
|||||||
<Segmented options={SECTIONS} value={section} onChange={setSection} />
|
<Segmented options={SECTIONS} value={section} onChange={setSection} />
|
||||||
|
|
||||||
<div className="view-body">
|
<div className="view-body">
|
||||||
{section === "overview" && <Overview onChannel={onChannel} />}
|
{section === "overview" && <Overview onChannel={onChannel} onSection={setSection} />}
|
||||||
{section === "messages" && <MessageCenter />}
|
{section === "messages" && <MessageCenter />}
|
||||||
{section === "new" && <NewTicket onDone={() => setSection("tickets")} />}
|
{section === "new" && <NewTicket onDone={() => setSection("tickets")} />}
|
||||||
{section === "tickets" && <MyTickets />}
|
{section === "tickets" && <MyTickets />}
|
||||||
@@ -79,32 +79,41 @@ export function Support() {
|
|||||||
/* Overview — channel cards + support team */
|
/* Overview — channel cards + support team */
|
||||||
/* ============================================================ */
|
/* ============================================================ */
|
||||||
|
|
||||||
function Overview({ onChannel }: { onChannel: (id: string) => void }) {
|
const MINI_AREA: Record<string, string> = { ticket: "ticket", callback: "call", email: "email", help: "help" };
|
||||||
return (
|
|
||||||
<div className="grid-side">
|
|
||||||
<div>
|
|
||||||
<div className="channel-grid">
|
|
||||||
{supportChannels.map((c) => (
|
|
||||||
<button key={c.id} className="channel-card" style={{ ["--accent" as string]: c.accent }} onClick={() => onChannel(c.id)}>
|
|
||||||
<span className="channel-ic"><Icon name={c.icon} size={22} /></span>
|
|
||||||
<div className="channel-status"><StatusDot status={c.status} /> {c.status === "online" ? "Available" : c.status}</div>
|
|
||||||
<div className="channel-title">{c.title}</div>
|
|
||||||
<div className="channel-desc">{c.desc}</div>
|
|
||||||
<div className="channel-foot">
|
|
||||||
<span className="channel-meta">{c.meta}</span>
|
|
||||||
<span className="channel-cta">{c.action} <Icon name="arrow" size={14} /></span>
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="card">
|
function Overview({ onChannel, onSection }: { onChannel: (id: string) => void; onSection: (s: string) => void }) {
|
||||||
<div className="card-head"><h3>Your support team</h3><Pill tone="green"><StatusDot status="online" /> 2 online</Pill></div>
|
const online = supportTeam.filter((a) => a.status === "online");
|
||||||
|
const mini = supportChannels.filter((c) => ["ticket", "callback", "email", "help"].includes(c.id));
|
||||||
|
const recent = ticketSeed.slice(0, 4);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="support-bento">
|
||||||
|
{/* Hero — live chat */}
|
||||||
|
<button className="bento-tile bento-chat" onClick={() => onChannel("chat")}>
|
||||||
|
<div className="bento-chat-top">
|
||||||
|
<span className="bento-chat-badge"><StatusDot status="online" /> Agents online now</span>
|
||||||
|
<span className="bento-chat-wait"><Icon name="clock" size={13} /> Avg wait < 2 min</span>
|
||||||
|
</div>
|
||||||
|
<div className="bento-chat-mid">
|
||||||
|
<h2>Need a hand?<br />Chat with us live.</h2>
|
||||||
|
<p>Instant help from a real support specialist — no queues, no bots, no waiting on hold.</p>
|
||||||
|
</div>
|
||||||
|
<div className="bento-chat-foot">
|
||||||
|
<div className="bento-avstack">
|
||||||
|
{online.map((a) => <Avatar key={a.id} initials={a.initials} gradient={a.gradient} size={36} />)}
|
||||||
|
<span className="bento-av-more">+{online.length} online</span>
|
||||||
|
</div>
|
||||||
|
<span className="bento-chat-cta">Start chat <Icon name="arrow" size={16} /></span>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* Support team */}
|
||||||
|
<div className="bento-tile bento-team">
|
||||||
|
<div className="bento-tile-head"><h3>Your support team</h3><Pill tone="green">{online.length} online</Pill></div>
|
||||||
<div className="team-list">
|
<div className="team-list">
|
||||||
{supportTeam.map((a) => (
|
{supportTeam.map((a) => (
|
||||||
<div className="team-row" key={a.id}>
|
<div className="team-row" key={a.id}>
|
||||||
<Avatar initials={a.initials} gradient={a.gradient} size={42} status={a.status} />
|
<Avatar initials={a.initials} gradient={a.gradient} size={40} status={a.status} />
|
||||||
<div className="team-txt">
|
<div className="team-txt">
|
||||||
<div className="team-name">{a.name}</div>
|
<div className="team-name">{a.name}</div>
|
||||||
<div className="team-role">{a.role}</div>
|
<div className="team-role">{a.role}</div>
|
||||||
@@ -115,6 +124,30 @@ function Overview({ onChannel }: { onChannel: (id: string) => void }) {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Mini channel tiles */}
|
||||||
|
{mini.map((c) => (
|
||||||
|
<button key={c.id} className="bento-tile bento-mini" style={{ ["--accent" as string]: c.accent, gridArea: MINI_AREA[c.id] }} onClick={() => onChannel(c.id)}>
|
||||||
|
<span className="bento-mini-ic"><Icon name={c.icon} size={20} /></span>
|
||||||
|
<div className="bento-mini-title">{c.title}</div>
|
||||||
|
<div className="bento-mini-desc">{c.desc}</div>
|
||||||
|
<div className="bento-mini-meta"><span>{c.meta}</span> <Icon name="arrow" size={14} /></div>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{/* Recent tickets */}
|
||||||
|
<div className="bento-tile bento-recent">
|
||||||
|
<div className="bento-tile-head"><h3>Recent tickets</h3><button className="link-inline" onClick={() => onSection("tickets")}>View all</button></div>
|
||||||
|
<div className="bento-ticket-row">
|
||||||
|
{recent.map((t) => (
|
||||||
|
<button key={t.id} className="bento-ticket" onClick={() => onSection("tickets")}>
|
||||||
|
<div className="bento-ticket-top"><span className="t-id">{t.id}</span><Pill tone={STATUS_META[t.status].tone}>{STATUS_META[t.status].label}</Pill></div>
|
||||||
|
<div className="bento-ticket-subj">{t.subject}</div>
|
||||||
|
<div className="bento-ticket-meta"><Icon name="clock" size={12} /> {t.updatedAt} · {t.department}</div>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,10 @@ import {
|
|||||||
Lock, CreditCard, User, Bell, Eye, EyeOff, Camera, Upload, Plus, Star, Send,
|
Lock, CreditCard, User, Bell, Eye, EyeOff, Camera, Upload, Plus, Star, Send,
|
||||||
Paperclip, Clock, MapPin, Monitor, Smartphone, Tablet, LogOut, ArrowRight,
|
Paperclip, Clock, MapPin, Monitor, Smartphone, Tablet, LogOut, ArrowRight,
|
||||||
Info, Check, X, Search, ChevronDown, ChevronRight, Trash2, Globe,
|
Info, Check, X, Search, ChevronDown, ChevronRight, Trash2, Globe,
|
||||||
CheckCircle2, KeyRound, Pencil, Copy, RefreshCw, AlertTriangle, type LucideIcon,
|
CheckCircle2, KeyRound, Pencil, Copy, RefreshCw, AlertTriangle,
|
||||||
|
LayoutDashboard, Building2, FolderKanban, UserPlus, BadgeCheck, Filter,
|
||||||
|
Truck, CloudLightning, Map as MapIcon, PenTool, Calculator, CalendarDays,
|
||||||
|
Trophy, ListChecks, Users, Settings, Sparkles, type LucideIcon,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
|
|
||||||
/* ---------------------------------------------------------- */
|
/* ---------------------------------------------------------- */
|
||||||
@@ -38,6 +41,12 @@ const ICONS: Record<string, LucideIcon> = {
|
|||||||
"check-circle": CheckCircle2, key: KeyRound, edit: Pencil, copy: Copy,
|
"check-circle": CheckCircle2, key: KeyRound, edit: Pencil, copy: Copy,
|
||||||
refresh: RefreshCw, alert: AlertTriangle, privacy: ShieldCheck, devices: Monitor,
|
refresh: RefreshCw, alert: AlertTriangle, privacy: ShieldCheck, devices: Monitor,
|
||||||
desktop: Monitor,
|
desktop: Monitor,
|
||||||
|
// sidebar / workspace nav
|
||||||
|
dashboard: LayoutDashboard, owners: Building2, projects: FolderKanban,
|
||||||
|
leads: UserPlus, verify: BadgeCheck, pipeline: Filter, dispatch: Truck,
|
||||||
|
storm: CloudLightning, territory: MapIcon, procanvas: PenTool,
|
||||||
|
estimates: Calculator, schedule: CalendarDays, leaderboard: Trophy,
|
||||||
|
subtasks: ListChecks, people: Users, settings: Settings, ai: Sparkles,
|
||||||
};
|
};
|
||||||
|
|
||||||
export function Icon({ name, size = 18, className, strokeWidth = 2 }: { name: string; size?: number; className?: string; strokeWidth?: number }) {
|
export function Icon({ name, size = 18, className, strokeWidth = 2 }: { name: string; size?: number; className?: string; strokeWidth?: number }) {
|
||||||
|
|||||||
@@ -27,10 +27,10 @@ export const MASKED_WA = "+91 ••••• ••012";
|
|||||||
|
|
||||||
/* ---- demo account "on file" ---- */
|
/* ---- demo account "on file" ---- */
|
||||||
export const DEMO_USER = {
|
export const DEMO_USER = {
|
||||||
name: "Rajesh Kumar Sharma",
|
name: "James Carter",
|
||||||
firstName: "Rajesh",
|
firstName: "James",
|
||||||
lastName: "Sharma",
|
lastName: "Carter",
|
||||||
initials: "RS",
|
initials: "JC",
|
||||||
allotmentNo: "YEA-654321",
|
allotmentNo: "YEA-654321",
|
||||||
sector: "Sector 18",
|
sector: "Sector 18",
|
||||||
pocket: "Pocket B",
|
pocket: "Pocket B",
|
||||||
@@ -38,7 +38,7 @@ export const DEMO_USER = {
|
|||||||
category: "Residential",
|
category: "Residential",
|
||||||
size: "300 sq.m",
|
size: "300 sq.m",
|
||||||
maskedMobile: MASKED_PHONE,
|
maskedMobile: MASKED_PHONE,
|
||||||
email: "rajesh.sharma@example.com",
|
email: "james.carter@example.com",
|
||||||
relationship: "Self",
|
relationship: "Self",
|
||||||
isAllottee: true,
|
isAllottee: true,
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user