Enhance portal profile/support modules and add rebuild prompt doc
Add profile (verify-to-reveal, KYC rules, contact-change OTP flows, notifications/consent tabs, devices) and support (5 channels, message center, ticket routing, live chat, help center) updates, the geo BFF route, login auth flow, and a self-contained module rebuild prompt. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,353 @@
|
||||
# PROMPT — Rebuild the "Profile" + "Support Center" modules
|
||||
|
||||
> Copy everything below this line into the other tool/website. It is a complete, framework-agnostic spec. It describes a real allottee-portal demo for a government land authority (the example is "YEIDA — Yamuna Expressway Industrial Development Authority"). Build the **Profile** module and the **Support Center** module exactly as described. You may adapt the brand name, but keep every behaviour, rule, field, screen, state and copy-tone identical.
|
||||
|
||||
---
|
||||
|
||||
## 0. Tech + look & feel
|
||||
|
||||
- React (function components + hooks). Single-page feel; each module is one screen rendered inside an authenticated portal shell with a left sidebar and a content area.
|
||||
- Inline styles + a small set of utility CSS classes (list below). No component library — everything is hand-built.
|
||||
- Visual language: clean SaaS dashboard. White cards with `1px` light borders, rounded corners (`10–14px`), soft shadows, a green/navy authority palette, blue accents for interactive elements, amber for "pending/attention", red for errors, emerald-green for "success/verified".
|
||||
- Everything is a **front-end demo with mocked data** — no real backend. OTP verification is mocked: **entering `000000` "verifies" any OTP**. File uploads only capture the file name.
|
||||
- All money in INR (`₹`), dates as `DD Mon YYYY`, Indian number grouping.
|
||||
|
||||
### CSS utility classes / tokens to implement (or map to your own)
|
||||
Classes used throughout: `row` (flex row, center), `col` (flex column), `spread` (justify-between), `gap-2/3/4`, `card`, `card-pad`, `card-hover`, `card-pad`, `btn` + variants `btn-primary btn-outline btn-ghost btn-emerald btn-sm btn-block`, `icon-btn`, `input`, `input-icon` (icon-prefixed input wrapper), `select`, `field` (label + control group), `label`, `badge` + tones `badge-blue badge-green badge-amber badge-navy badge-gray badge-red`, `chip` (+`.active`), `tbl` (table), `progress` (bar), `muted`, `faint`, `num` (tabular numerals), `dot` (status dot), `sec-eyebrow`, `anim-fade-up`, `anim-fade-in`, `anim-pop`.
|
||||
|
||||
CSS variables (colors): `--ink, --ink-2, --muted, --faint, --line, --line-2, --bg, --bg-2, --blue-50/100/400/500/600/700, --navy-700/800, --emerald-50/500/600/700, --amber-50/200/500/600, --red-50/500/600, --sh-xs/sm/md/lg, --font-display`. Keyframe animations needed: `spin`, `pulseDot` (typing dots), `growBar`, fade/pop entrances.
|
||||
|
||||
### Shared primitives you must build first
|
||||
1. **`Icon({name, size})`** — an icon set. Names referenced: `user, mail, phone, lock, shield, bell, device, edit, check, checkCircle, x, plus, search, upload, download, file, documents, building, map, mapPin, plot, grid, layers, calendar, clock, info, alert, flag, bookmark, ticket, messages, message/chat, send, paperclip, sparkles, support, funnel, arrowR, chevR, chevD, chevU, refresh, home, transfer, payments, construction, users, trend, rupee, eye, sun, moon, settings`.
|
||||
2. **`Avatar({initials, size, src, style})`** — circular; shows image if `src`, else initials.
|
||||
3. **`PageHead({title, subtitle, children})`** — big H1 + muted subtitle, optional right-aligned action buttons.
|
||||
4. **`useToast()` → `[toastNode, toast(msg)]`** — bottom-right toast stack, auto-dismiss ~3.2s.
|
||||
5. **`Modal({title, sub, icon, onClose, children, footer, width=520})`** — centered overlay (`rgba(8,26,57,.55)` + blur), white rounded card, header with icon chip + title/sub + close X, scrollable body, optional footer row (right-aligned). Click backdrop to close; stop propagation on the card.
|
||||
6. **`Toggle({label, on, onChange})`** — pill switch (emerald when on).
|
||||
7. **`OtpField({label, sent, value, onChange, verified, onVerify})`** — 6-digit numeric input + "Verify" button (enabled at 6 digits). When verified: green border, check icon, "Verified ✓". Helper text: `OTP sent to {sent} · enter 000000 to verify (demo)`.
|
||||
|
||||
---
|
||||
|
||||
## 1. MOCK DATA (single source of truth)
|
||||
|
||||
Create one data object `D` with these fields. Keep the exact shapes; values can be tweaked but keep them realistic.
|
||||
|
||||
```js
|
||||
const D = {
|
||||
user: {
|
||||
name: 'Rajeev Malhotra', firstName: 'Rajeev', lastName: 'Malhotra', initials: 'RM',
|
||||
allotment: 'YEA-654321', sector: 'Sector 18', pocket: 'Pocket B', plot: '181',
|
||||
category: 'Residential', size: '300 sq.m',
|
||||
mobile: '+91 98xxxxxx12', email: 'r.malhotra@email.com', memberSince: 'Mar 2019',
|
||||
relationship: 'Self', // Self | Son | Daughter | Spouse | Relative | Authorized Representative
|
||||
isAllottee: true,
|
||||
allotteeFirstName: 'Rajeev', allotteeLastName: 'Malhotra',
|
||||
mailingAddress: { line1:'C-204, Orchid Residency, Sector 50', line2:'Near City Centre', city:'Noida', state:'Uttar Pradesh', pin:'201301' },
|
||||
},
|
||||
relationshipOptions: ['Self','Son','Daughter','Spouse','Relative','Authorized Representative'],
|
||||
|
||||
// ----- SUPPORT -----
|
||||
// Ticket categories drive dropdown routing. attach:true => attachment required to submit.
|
||||
ticketCategories: [
|
||||
{ id:'technical', label:'Technical Support', icon:'device', color:'blue', dept:'IT Helpdesk', sla:'Replies within 4 hrs',
|
||||
subs:['Login / OTP not working','Portal error or bug','Document upload failing','Page not loading','Other technical issue'] },
|
||||
{ id:'payment', label:'Payments & Challan', icon:'payments', color:'emerald', dept:'Payments Desk', sla:'Replies within 1 working day',
|
||||
subs:['Payment not reflecting','Challan / receipt not generated','Refund request','Dues / interest query','Failed online payment'] },
|
||||
{ id:'kyc', label:'KYC & Documents', icon:'documents', color:'navy', dept:'Records Cell', sla:'Replies within 2 working days', attach:true,
|
||||
subs:['KYC document rejected','Update / re-upload KYC','Lease deed correction','Certificate copy request','Name / detail correction'] },
|
||||
{ id:'construction',label:'Construction Approvals',icon:'construction', color:'amber', dept:'Construction Cell',sla:'Replies within 2 working days',
|
||||
subs:['Building plan query','Plan re-submission','Inspection / progress','Completion certificate','Map / setback / FAR query'] },
|
||||
{ id:'allotment', label:'Allotment & Transfer', icon:'transfer', color:'blue', dept:'Allotment Cell', sla:'Replies within 2 working days', attach:true,
|
||||
subs:['Allotment detail correction','Plot transfer / sale','Possession query','Mutation / succession','Address update'] },
|
||||
{ id:'general', label:'General / Other', icon:'messages', color:'navy', dept:'Customer Care', sla:'Replies within 1 working day',
|
||||
subs:['Account question','Feedback / suggestion','How do I…','Something else'] },
|
||||
],
|
||||
tickets: [ // "My Tickets" table rows
|
||||
{ id:'TKT-4821', subject:'Lease deed correction request', category:'Records & Documents', status:'Open', priority:'High', updated:'2h ago' },
|
||||
{ id:'TKT-4790', subject:'Challan not reflecting after UPI payment',category:'Payments & Challan', status:'Resolved', priority:'Medium', updated:'3d ago' },
|
||||
{ id:'TKT-4755', subject:'Building plan re-submission query', category:'Construction Approvals', status:'In Progress', priority:'Medium', updated:'5d ago' },
|
||||
],
|
||||
supportThreads: [ // secure Message Center inbox threads
|
||||
{ id:'MSG-4821', subject:'Lease deed correction request',
|
||||
agent:{ name:'Anjali Sharma', role:'Records Officer', initials:'AS', online:true }, unread:2, time:'2h ago',
|
||||
messages:[
|
||||
{from:'me', text:'There is a spelling error in the name on my registered lease deed. How can I get it corrected?', time:'Mon 10:12'},
|
||||
{from:'agent',text:'Hello Rajeev, thanks for reaching out. We can process a name-correction request against allotment YEA-654321. Could you share a self-attested ID proof?', time:'Mon 10:21'},
|
||||
{from:'me', text:'Sure, I have uploaded my Aadhaar in the Documents section.', time:'Mon 10:25'},
|
||||
{from:'agent',text:'Received. Your correction request is logged as TKT-4821 and forwarded to the Records cell. Expected TAT is 7 working days.', time:'Mon 10:31'},
|
||||
] },
|
||||
{ id:'MSG-4790', subject:'Challan not reflecting after UPI payment',
|
||||
agent:{ name:'Vikram Singh', role:'Payments Desk', initials:'VS', online:true }, unread:0, time:'Yesterday',
|
||||
messages:[
|
||||
{from:'me', text:'I paid ₹38,625 via UPI yesterday but the challan is not showing under Payments.', time:'Tue 16:40'},
|
||||
{from:'agent',text:'Apologies for the delay — UPI reconciliations can take up to 24 hours. I can see the payment now; challan CH-2026-0128-04 has been generated against your account.', time:'Tue 17:02'},
|
||||
{from:'me', text:'Great, I can see it now. Thank you!', time:'Tue 17:05'},
|
||||
] },
|
||||
{ id:'MSG-4755', subject:'Building plan re-submission query',
|
||||
agent:{ name:'Neha Gupta', role:'Construction Cell', initials:'NG', online:false }, unread:1, time:'5d ago',
|
||||
messages:[
|
||||
{from:'me', text:'My building plan was returned for revision. What changes are required?', time:'Thu 11:15'},
|
||||
{from:'agent',text:'The rear-side setback needs to be increased to 3m as per the updated FAR norms. Please revise and re-upload the architect-stamped plan.', time:'Thu 12:02'},
|
||||
] },
|
||||
],
|
||||
supportTeam: [
|
||||
{ name:'Aarav', role:'Chief AI Support', initials:'AI', online:true, ai:true, desc:'Instant answers, available 24/7' },
|
||||
{ name:'Anjali Sharma', role:'Records Officer', initials:'AS', online:true, desc:'Lease deed, allotment & records' },
|
||||
{ name:'Vikram Singh', role:'Payments Desk', initials:'VS', online:true, desc:'Challans, dues & refunds' },
|
||||
{ name:'Neha Gupta', role:'Construction Cell',initials:'NG', online:false, desc:'Building plans & approvals' },
|
||||
],
|
||||
helpTopics: [ // Help Center categories, each with expandable Q&A articles
|
||||
{ id:'payments', icon:'payments', color:'emerald', title:'Payments & Challan', desc:'Dues, e-challan, receipts & refunds', articles:[ /* {q,a} … */ ] },
|
||||
{ id:'kyc', icon:'documents', color:'navy', title:'KYC & Documents', desc:'KYC, lease deed & certificates', articles:[ /* … */ ] },
|
||||
{ id:'construction', icon:'construction',color:'amber', title:'Construction', desc:'Plans, approvals & progress', articles:[ /* … */ ] },
|
||||
{ id:'account', icon:'user', color:'blue', title:'Account & Profile', desc:'Login, contacts & security', articles:[ /* … */ ] },
|
||||
{ id:'property', icon:'building', color:'navy', title:'Property Verification',desc:'Allotment, plot & possession checks',articles:[ /* … */ ] },
|
||||
{ id:'technical', icon:'device', color:'blue', title:'Technical Support', desc:'Login, OTP, errors & the portal', articles:[ /* … */ ] },
|
||||
{ id:'documents', icon:'documents', color:'amber', title:'Documents & Uploads', desc:'Uploading, formats & retrieving files',articles:[ /* … */ ] },
|
||||
],
|
||||
contactTimes: [ // for callback "preferred time"
|
||||
{id:'morning',label:'Morning',hint:'9 AM – 12 PM',icon:'sun'},
|
||||
{id:'afternoon',label:'Afternoon',hint:'12 PM – 4 PM',icon:'sun'},
|
||||
{id:'evening',label:'Evening',hint:'4 PM – 8 PM',icon:'moon'},
|
||||
{id:'anytime',label:'Anytime',hint:'No preference',icon:'clock'},
|
||||
],
|
||||
countryCodes: [
|
||||
{code:'+91',label:'IN',name:'India',digits:10}, {code:'+971',label:'AE',name:'UAE',digits:9},
|
||||
{code:'+1',label:'US',name:'United States',digits:10}, {code:'+44',label:'UK',name:'United Kingdom',digits:10},
|
||||
{code:'+65',label:'SG',name:'Singapore',digits:8},
|
||||
],
|
||||
|
||||
// ----- PROFILE / KYC -----
|
||||
kycDocTypes: [ // proof: 'identity' | 'address' | 'photo'; alsoAddress, expires drive rules
|
||||
{id:'aadhaar', label:'Aadhaar', proof:'identity', alsoAddress:true, expires:false, hint:'Identity + address proof · does not expire'},
|
||||
{id:'pan', label:'PAN', proof:'identity', alsoAddress:false, expires:false, hint:'Identity proof · does not expire'},
|
||||
{id:'passport',label:'Passport', proof:'identity', alsoAddress:true, expires:true, hint:'Identity + address proof · has an expiry date'},
|
||||
{id:'dl', label:'Driving Licence', proof:'identity', alsoAddress:true, expires:true, hint:'Identity + address proof · has an expiry date'},
|
||||
{id:'utility', label:'Electricity Bill', proof:'address', alsoAddress:true, expires:true, hint:'Address proof · recent bill (within 3 months)'},
|
||||
{id:'water', label:'Water Bill', proof:'address', alsoAddress:true, expires:true, hint:'Address proof · recent bill (within 3 months)'},
|
||||
{id:'housetax',label:'House Tax Receipt',proof:'address', alsoAddress:true, expires:false, hint:'Address proof · latest receipt'},
|
||||
{id:'bankstmt',label:'Bank Statement', proof:'address', alsoAddress:true, expires:true, hint:'Address proof · recent statement showing your name & address'},
|
||||
{id:'photo', label:'Photograph', proof:'photo', alsoAddress:false, expires:false, hint:'Recent passport-size photograph'},
|
||||
],
|
||||
kyc: {
|
||||
status:'verified', verifiedOn:'12 Mar 2019', contractSigned:true, viaPOA:false,
|
||||
documents:[
|
||||
{type:'aadhaar', number:'•••• 4821', proof:'identity+address', status:'verified', expiry:null},
|
||||
{type:'pan', number:'•••• 234F', proof:'identity', status:'verified', expiry:null},
|
||||
{type:'passport',number:'•••• 6789', proof:'identity+address', status:'verified', expiry:'14 Aug 2028'},
|
||||
],
|
||||
},
|
||||
contactPrefs: {
|
||||
emails:[
|
||||
{id:'e1', value:'r.malhotra@email.com', primary:true, verified:true, owner:'holder'},
|
||||
{id:'e2', value:'rajeev.work@company.com', primary:false, verified:true, owner:'holder'},
|
||||
{id:'e3', value:'allottee.nri@email.com', primary:false, verified:true, owner:'allottee'},
|
||||
],
|
||||
phones:[
|
||||
{id:'p1', value:'+91 98xxxxxx12', primary:true, verified:true, owner:'holder'},
|
||||
{id:'p2', value:'+91 99xxxxxx77', primary:false, verified:false, owner:'holder'},
|
||||
{id:'p3', value:'+971 50xxxxxx89',primary:false, verified:true, owner:'allottee'},
|
||||
],
|
||||
marketing:{ newsletter:false, promotions:false, productUpdates:true },
|
||||
preferredTime:'anytime', preferredTz:'IST',
|
||||
consent:{ dataProcessing:true, profileVisibility:true, shareWithFamily:false, kycSharing:true,
|
||||
transferDisclosure:false, authorizedReps:false, analyticsCookies:false, thirdPartySharing:false,
|
||||
crossBorder:false, platformUpdates:true, marketingConsent:false },
|
||||
},
|
||||
contactTimezones:[
|
||||
{id:'IST',abbr:'IST',label:'India — IST',utc:'UTC+5:30'}, {id:'GST',abbr:'GST',label:'UAE / Gulf — GST',utc:'UTC+4:00'},
|
||||
{id:'GMT',abbr:'GMT',label:'UK — GMT',utc:'UTC+0:00'}, {id:'ET',abbr:'ET',label:'US Eastern — ET',utc:'UTC−5:00'},
|
||||
{id:'PT',abbr:'PT',label:'US Pacific — PT',utc:'UTC−8:00'}, {id:'SGT',abbr:'SGT',label:'Singapore — SGT',utc:'UTC+8:00'},
|
||||
],
|
||||
loginActivity:[
|
||||
{action:'Successful login', device:'MacBook Pro · Chrome', ip:'49.36.x.x', location:'Greater Noida, IN', time:'Today, 09:14', ok:true},
|
||||
{action:'Password changed', device:'MacBook Pro · Chrome', ip:'49.36.x.x', location:'Greater Noida, IN', time:'Yesterday, 18:02', ok:true},
|
||||
{action:'New device login', device:'iPhone 14 · YEIDA App',ip:'49.36.x.x', location:'Greater Noida, IN', time:'2 days ago', ok:true},
|
||||
{action:'Failed login attempt',device:'Unknown · Firefox', ip:'103.21.x.x',location:'Mumbai, IN', time:'4 days ago', ok:false},
|
||||
{action:'Login alert consent enabled',device:'Windows PC · Edge',ip:'157.32.x.x',location:'Delhi, IN', time:'5 days ago', ok:true},
|
||||
],
|
||||
};
|
||||
```
|
||||
|
||||
> For `helpTopics[].articles`, write 3–4 realistic `{q, a}` pairs per topic in the same helpful tone (e.g. Payments: "How do I generate an e-Challan?", KYC: "How do I update my KYC?" reminding ID & address must be two different docs, Account: "How do I change my email or mobile?" noting OTP goes to registered mobile, Property: "How do I verify my allotment details?" via verify-to-reveal, Technical: "I am not receiving my OTP", Documents: "Which file formats and sizes are accepted?" → JPG/PNG/PDF up to 10 MB). Each `{q,a}` becomes an accordion article.
|
||||
|
||||
A tone→color map used for category chips/cards:
|
||||
```js
|
||||
const TONE = { blue:['var(--blue-50)','var(--blue-600)'], emerald:['var(--emerald-50)','var(--emerald-700)'],
|
||||
navy:['#e9eefb','var(--navy-700)'], amber:['var(--amber-50)','var(--amber-600)'] };
|
||||
```
|
||||
|
||||
A persistent **user-photo store** + **profile-override store** backed by `localStorage` (so the uploaded avatar and edited name survive route changes): expose `useUserPhoto() → [photo, setPhoto]`, `readImageFile(file, cb)` (FileReader → data URL), `setUserProfile(patch)`, and `useUser(base)` which merges saved overrides over `D.user`.
|
||||
|
||||
---
|
||||
|
||||
## 2. SUPPORT CENTER module
|
||||
|
||||
A single `Support({ go, toast })` screen. `go(route)` navigates the portal (e.g. `go('assistant')` opens the AI assistant). Layout top-to-bottom:
|
||||
|
||||
### 2.1 Header
|
||||
`PageHead title="Support Center" subtitle="Raise tickets, chat with us, or browse the help center"` with a right-side **"Talk to AI"** outline button (calls `talkAI`).
|
||||
|
||||
### 2.2 Five channel cards (responsive grid, `minmax(180px,1fr)`)
|
||||
Each is a clickable card: icon chip (tone-colored), bold title, muted subtitle.
|
||||
1. **AI Assistant** — "Instant answers · available 24/7" — `sparkles`, blue → `talkAI` = `go('assistant')`.
|
||||
2. **Live Chat** — "Choose AI or a human officer" — `chat`, emerald → opens a **chat-choice modal**.
|
||||
3. **Request Callback** — "We'll call you at your preferred time" — `phone`, navy → opens **CallbackForm** (kind `call`).
|
||||
4. **Talk to Human Agent** — "Connect to a support officer" — `support`, amber → opens **LiveChatModal** (human).
|
||||
5. **Create Ticket** — "Title, details & attachments" — `ticket`, emerald → opens **NewTicketForm**.
|
||||
|
||||
**Chat-choice modal** (title "Start a live chat", sub "Who would you like to chat with?"): two big option rows — "Chat with AI Assistant" (→ talkAI) and "Chat with a Human Agent" (→ open LiveChatModal). Each row: icon chip + title + description + chevron.
|
||||
|
||||
### 2.3 "Your support team" card
|
||||
Header "Your support team." + green "● Support online" badge. 4-column grid of `supportTeam` cards: avatar (AI member gets a blue→navy gradient avatar + an "AI" badge) with an online green dot, name, role, description, and a full-width button — "Ask AI" (→ talkAI) for the AI member, else "Message" (→ toast "Message sent to {firstName}").
|
||||
|
||||
### 2.4 Message Center (`MessageCenter`)
|
||||
Secure two-pane messaging, **inbox + live thread**:
|
||||
- Card header: gradient icon chip, "Message Center", sub "Secure messaging with YEIDA support officers", green "● Support online" badge.
|
||||
- **Left inbox** (300px, scroll): "Inbox" + a total-unread pill (`N new`). Each thread row: agent avatar (with online dot), agent name + time, **subject** (truncated), last-message preview (prefix `You: ` if last from me), and a blue unread count bubble. Active thread has blue left border + blue-50 bg. Opening a thread zeroes its unread.
|
||||
- **Right thread** (470px tall flex column): header with agent avatar/name/role + online state + a gray badge with the thread id. Scrollable message list: my bubbles are blue, right-aligned, tail bottom-right; agent bubbles white with border + small avatar, tail bottom-left; each with a tiny timestamp. Auto-scroll to bottom on new messages.
|
||||
- **Composer**: paperclip (attach), text input (Enter to send), send button. On send: append my message (time "Just now"), show a 3-dot **typing** indicator (~1s), then append a canned agent reply: `Thank you for your message. {agentFirstName} from the {role} will review this and get back to you shortly.`
|
||||
|
||||
### 2.5 New Ticket form (`NewTicketForm`) — inline card, not a modal
|
||||
Header: ticket icon, "Raise a support ticket", sub "Pick a category so we route it to the right department — no need to call", close X. Fields:
|
||||
1. **Category** dropdown (from `ticketCategories`, shows `label`). When chosen, show a department chip (`cat.dept`) next to the label, and the select icon adopts the category icon/color. Helper line lists example categories.
|
||||
2. **Sub-reason** dropdown (appears after a category is chosen; options = `cat.subs`). Helper shows `{dept} · {sla}` with a clock icon.
|
||||
3. **Title** (text) and **Description** (textarea).
|
||||
4. **Attachments** — two upload buttons: "Attach screenshot" (`image/*`) and "Attach document" (`image/*,application/pdf`). Capture file name only. If `cat.attach === true`, mark **"· required"** and show an amber "Attach a document to submit" badge until something is attached.
|
||||
5. **Auto-routing note** (tone-colored): "This ticket is auto-assigned to **{dept}** — the right officer picks it up. {sla}."
|
||||
6. Footer: a left **"Talk to AI instead"** ghost button (→ talkAI), plus Cancel + **Submit ticket**. Submit is enabled only when category + sub-reason + title + description are filled **and** (if attachment required) something is attached. On submit → toast: `Ticket created & auto-routed to {dept} — the right officer will be assigned and you'll be notified`.
|
||||
|
||||
### 2.6 My Tickets table
|
||||
Card header "My Tickets" + hint "Click a ticket to check status & add an update". Table columns: Ticket id, Subject, Department (gray badge from `category`), Priority (red badge if "High" else gray), Status (badge tone via the status map below), Updated, chevron. Clicking a row opens **TicketDetailModal**.
|
||||
|
||||
**Ticket status tones (single source):** `Open→amber, In Progress→blue, Waiting for User→amber, Resolved→green, Closed→gray`.
|
||||
|
||||
**TicketDetailModal**: header = ticket id + subject. Body:
|
||||
- Badge row: status badge, category badge, priority badge (red if High), "Updated {updated}".
|
||||
- **Status timeline** — vertical stepper with fixed order `Open → In Progress → Waiting for User → Resolved → Closed`, each with a sub-label (Open="Logged and classified in the system", In Progress="Assigned to the handling officer", Waiting for User="Awaiting your reply or documents", Resolved="Resolution provided", Closed="Ticket closed"). Steps before current = done (emerald + check), current = blue with a "Current" badge, future = muted. Connector line emerald up to current.
|
||||
- If status is Resolved/Closed → show an emerald "This ticket is resolved…" note. Otherwise show an **"Add an update / reply"** textarea + Attach + **Post update** button (enabled when non-empty) → toast `Update added to {id}` and close.
|
||||
|
||||
### 2.7 Callback / Email form (`CallbackForm({kind})`) — a Modal, `kind` ∈ `call|email`
|
||||
- **Topic** dropdown (from `ticketCategories` labels) — required.
|
||||
- **call**: country-code select (with flag emoji from ISO label; map UK→GB) + mobile number input; **preferred date** (date input) + **preferred time (IST)** — a select of `contactTimes` plus a "Specific time…" option that swaps to a `<time>` input (with an X to revert to a window). Valid when topic set and ≥8 digits.
|
||||
- **email**: name, mobile (optional), email (required, validated). Valid when topic + name + valid email.
|
||||
- Optional reason/notes textarea.
|
||||
- On submit → a centered **thank-you** confirmation state inside the modal: green check, "Thank you!", and a personalized line: "Our team will contact you shortly **on {cc} {phone}** / **at {email}**" + (if call & specific) "at around {time}" / (if call & a window) "during your preferred {window} window" + (if topic) "regarding your {topic} query". "You'll also receive a confirmation by email." Footer Done.
|
||||
|
||||
### 2.8 Live Chat with a human (`LiveChatModal`) — ChatGPT-style, 840px modal
|
||||
- Left **conversations sidebar** (252px): a **"New chat"** button on top, then "Previous conversations" = the `supportThreads` (seeded as past convos) plus the current new chat. Each item: avatar (or a "+" chip for the new chat), title, time, last-message snippet.
|
||||
- Right **chat area**: header with the (human) agent avatar + online dot + role + state ("Online now" / "Connecting…"); thread id badge for past convos.
|
||||
- **New-chat handshake**: on starting a new chat run a timed sequence — `connecting` (spinner "Connecting you to support…", ~1s) → `waiting` ("Please wait — an agent will join shortly…") → typing dots → **joined**: insert agent message `Hi, this is {firstName} from the {role}. I've joined the chat — how can I help you today?` and show "{agent} joined the chat". The input is **disabled until joined**.
|
||||
- Pick the human agent as the one named "Anjali" from `supportThreads` (fallback first). Sending a message shows typing (~1.1s) then a canned reply: "Thanks for the details — let me check that for you right away." "New chat" resets the new conversation and restarts the handshake.
|
||||
|
||||
### 2.9 Help Center (`HelpCenter`) — Amazon/ServiceNow-style self-service, at the bottom
|
||||
- Gradient header with support icon, "Help Center", sub "Find answers fast — most questions are solved here without calling", and a **search box** ("Search help — e.g. challan, KYC, transfer fee, OTP").
|
||||
- **Search mode**: when the query is non-empty, flatten all topics' articles, filter by `(q + a)` containing the query, show "N results for "query"" and render each as an accordion (`Article`), first one open by default. Empty → "No articles matched — try rephrasing, or raise a ticket below."
|
||||
- **Browse mode**: left list of topics (icon chip in topic color, title, "{n} articles"); right pane shows the active topic's articles as single-open accordions.
|
||||
- Footer bar: "Didn't find your answer?" + **Ask AI** (→ talkAI) and **Raise a ticket** (→ open NewTicketForm).
|
||||
- **`Article`** = an accordion: question row (optional gray tag badge) + chevron; expands to the answer text.
|
||||
|
||||
---
|
||||
|
||||
## 3. PROFILE module
|
||||
|
||||
A single `Profile({ go })` screen. Owns a toast. Layout:
|
||||
|
||||
### 3.1 Profile header card
|
||||
Green radial-gradient banner (height ~100px). Overlapping avatar (84px; show uploaded photo if any, else initials) with a small blue camera/edit button to upload a photo (`image/*` → data URL via `readImageFile` → `setPhoto` + toast "Profile photo updated"). Beside it: user name (H2) and a sub line: `{isAllottee ? 'Allottee' : relationship + ' · on behalf of allottee'} · {allotment}`. Right side: green "🛡 KYC Verified" badge + navy "Member since {memberSince}" badge.
|
||||
|
||||
### 3.2 Left tab rail + content
|
||||
Tabs (icon + label): **Personal Info** (`user`), **KYC Verification** (`shield`), **Security** (`lock`), **Notifications** (`bell`), **Privacy & Consent** (`shield`), **Devices** (`device`). Active tab = blue-50 bg, blue text, bold. Content card re-mounts per tab (keyed) for a fade-in.
|
||||
|
||||
### 3.3 Personal Info tab (`PersonalTab`)
|
||||
Three sub-sections plus contact-change and verify-to-reveal flows.
|
||||
|
||||
**(a) Account Holder** — editable. Edit/Cancel/Save toggle in the header.
|
||||
- First Name, Last Name (editable only in edit mode).
|
||||
- Email Address + Mobile Number shown **read-only**, each with a **"Change with verification"** ghost button → opens **ChangeContactModal**.
|
||||
- Intro copy: "These are the **account holder's** details. Your name and contact (email & mobile) are editable — the allottee may be a different person (see below). Allotment identity fields are locked to authority records after verification."
|
||||
- Save → `setUserProfile({...})` (name, initials, email, relationship, isAllottee, allottee names) + toast "Personal information saved".
|
||||
|
||||
**(b) Allottee Details** — the allottee may differ from the account holder.
|
||||
- **Relationship to Allottee** select (`relationshipOptions`), editable in edit mode.
|
||||
- If relationship is **Self**: show "Allottee Name" = account-holder name, read-only, with note "You are the original allottee (same as account holder)."
|
||||
- If **not Self**: show Allottee First Name, Last Name, **Allottee Email**, **Allottee Mobile** (seed allottee email/phone from `contactPrefs` where `owner==='allottee'`), plus an info note: "Allottee ≠ account holder — registered as **{relationship}**. Their email & number can receive reminders (set in Notifications & Privacy)." Copy above: "The allottee may be someone other than the account holder — for example, the plot is in your spouse's name while you manage the account. The email and mobile above always belong to the account holder."
|
||||
|
||||
**(c) Allotment Identity** — **verify-to-reveal** (the key security pattern).
|
||||
- Fields: Allotment Number, Sector, Plot Number, Pocket — all locked/read-only.
|
||||
- Until verified they are **masked** with bullets (`•••••`, letter-spaced, muted) and each shows a lock icon. Section header shows a gray "Verification required" badge.
|
||||
- Show an amber banner: "Sensitive details are hidden — Verify your identity to view your allotment number, plot & sector." with a **"Verify to reveal"** button → opens **AllotmentVerifyModal**.
|
||||
- After verify: reveal real values, header badge turns green "Verified", toast "Allotment identity verified".
|
||||
|
||||
**AllotmentVerifyModal** (`expectedPlot`, `registeredMobile`): info box "For your security, your allotment number, plot & sector stay hidden until you confirm the plot number on record. A confirmation is logged against {registeredMobile}." + a **Plot Number** input. On "Verify & reveal": fake ~700ms lookup (spinner); if normalized input (strip spaces/hyphens, lowercase) equals `expectedPlot` → `onVerified()`, else inline error "That plot number doesn't match our records. Please try again." Helper: "Enter the plot number from your allotment (demo: 181)."
|
||||
|
||||
**ChangeContactModal** (`kind ∈ email|phone`, `current`, `registeredMobile`, `verifyOldEmailPolicy=false`): rules —
|
||||
- **Email**: identity is always confirmed by an **OTP sent to the registered MOBILE** (not the old email). Show a blue security note saying so. Steps inside a dashed box once the new email is valid: (1) "Verify identity (registered mobile)" OTP — always required; (2) "Confirm old email (policy)" OTP — only if `verifyOldEmailPolicy` true and not lost-access; (3) "Verify new email" OTP. A "I lost access to my old email" checkbox skips the old-email step (then show: identity is confirmed via registered mobile, change logged for review). When policy off, show "Old-email confirmation is currently not required (system-configurable by the Authority)."
|
||||
- **Mobile**: verify the **old number** then the **new number**; "I lost access to my old phone" checkbox skips the old-number step (only the new number is verified, change logged for review).
|
||||
- All OTPs use the `OtpField` (demo `000000`). Save enabled only when the new value is valid and the required OTP steps are verified. On save → update the field + toast "{Email|Mobile} updated & verified".
|
||||
|
||||
### 3.4 KYC Verification tab (`KycTab`)
|
||||
Collected "once you become a customer, after the allotment contract is signed". Intro: identity proof, address proof (incl. electricity/utility bill) and a **recent photograph are ALL mandatory** here (photo was optional at sign-up).
|
||||
- **Completion summary card**: count of verified requirements, "{n} items still pending" or "All KYC requirements complete", a big percentage + progress bar (amber if pending, emerald if complete). `verifiedOn` shown when fully verified.
|
||||
- If `kyc.viaPOA` → blue note about Power of Attorney.
|
||||
- **Inline upload panel** (always on screen, NOT a popup) — `KycUploadPanel`: pick document **type** from a 3-col grid of `kycDocTypes` (icon by proof type: photo→user, address→mapPin, else file), show the type's `hint`, then choose a file (`image/*,application/pdf`, name only) and **Upload document**. On upload: append a doc `{type, number:fileName, proof, status:'pending', expiry: expires?'To be read from document':null}` (proof becomes `identity+address` if the type is identity with `alsoAddress`), toast "{label} uploaded — pending verification".
|
||||
- **Requirements checklist** — three required categories: **Identity Proof**, **Address Proof** ("A different document from your identity proof — e.g. Electricity / Utility bill"), **Photograph**. Each shows status (Verified/green, Pending review/amber, Not uploaded/gray), a "Mandatory" badge, and an Upload/Re-upload button that scrolls to + preselects the upload panel's type. **Sort pending/missing first, verified last.**
|
||||
- **Rule: Identity proof and Address proof must be TWO DIFFERENT documents.** If the same document is relied on for both (e.g. Aadhaar for both) → red conflict banner: "Your identity and address proof are the same document. Please add a **different** document for address proof…". Otherwise a blue reminder "Identity proof and address proof must be two **different** documents." (Address requirement is only satisfied by an address-proof of a *different type* than the ID proof.)
|
||||
- **Documents on file** list: each row = type label, proof-category badge, masked number, expiry (amber "Expires {date}" if the type expires, else "No expiry"; none for photo), and a Verified/Pending badge. Verified sorted last.
|
||||
- **Acceptable documents** reference grid listing every `kycDocTypes` entry with its hint (clock icon if it expires, else green check).
|
||||
|
||||
### 3.5 Security tab (`SecurityTab`)
|
||||
Rows:
|
||||
- **Password** — "Last changed 3 months ago" → "Change" opens **ChangePasswordModal**.
|
||||
- **Two-Factor Authentication** — shows current method label ("OTP via mobile" / "Authenticator app" / "OTP via email") "enabled" → "Manage" opens **TwoFAModal**.
|
||||
- **Authenticator App** — verify a QR + code to enable app-based 2FA; shows a "Verified" badge once done → "Verify to set up" / "Re-verify".
|
||||
- **Login Alerts** toggle — "Email me on every new-device login" → toast on change.
|
||||
|
||||
**ChangePasswordModal**: current password + new + confirm. Password policy: **min 8 chars with uppercase, lowercase, a number AND a special char**. Show a live checklist (At least 8, Uppercase, Lowercase, Number, Special character) turning green as satisfied; red border on weak; "Passwords do not match" when confirm differs. Update enabled only when current filled, new is strong, and confirm matches → toast "Password updated".
|
||||
|
||||
**TwoFAModal**: choose method from SMS OTP / Authenticator App / Email OTP (radio cards). Authenticator shows a faux QR. **A change is only allowed when the CURRENT PASSWORD is entered AND an OTP/TP is verified** (demo `000000`) — otherwise Save stays blocked with the note "Change stays blocked until your current password is entered and the OTP is verified." Save → toast "2FA method saved" (or "Authenticator app verified & enabled" via the authenticator entry point).
|
||||
|
||||
### 3.6 Notifications tab (`NotifTab`) — "how we reach you" (vs Consent = "what is shared")
|
||||
A single card "Notification Preferences / How communication is sent to you", with:
|
||||
1. **Preferred channels** — high-level on/off pill switches for Email, SMS, WhatsApp, Phone Call, and an optional (dashed) Postal Mail.
|
||||
2. **Preferred contact time** — multi-select windows **Morning (9 AM–12 PM)**, **Afternoon (12 PM–4 PM)**, **Evening (4 PM–8 PM)** (selected = blue filled), an **Anytime** option (clears windows), and a **Timezone** select from `contactTimezones` (label + UTC). A live info line states which windows + timezone abbreviation will be used. (Optionally support a specific from–to time window.)
|
||||
3. **Mobile numbers table** — editable rows from `contactPrefs.phones`: number input, a **role** select (Primary / Secondary / Allottee), verified/unverified badge with inline **Verify** (→ `VerifyContactModal`), and per-row channel checkboxes for **SMS, WhatsApp, Call, OTP**. Add/remove numbers. Copy: "WhatsApp availability is detected from numbers added at registration — we won't ask again."
|
||||
4. **Email addresses** — editable rows from `contactPrefs.emails`: email input, role select (Primary/Secondary), a "Notify" checkbox, Verify (if unverified), remove. Add email.
|
||||
5. **Summary** info box: "The system will only contact you via **{channels}** during **{time}** ({tz}). Urgent security alerts may be sent at any time."
|
||||
6. Pointer line: "These are **notification** preferences (how we reach you). Manage **consent** … in the **Privacy & Consent** tab."
|
||||
|
||||
Notes: changing primary uses a radio per list (only one primary); setting role to Allottee tags `owner:'allottee'`. Use **plain render-helper functions, NOT nested components**, so inputs keep focus across re-renders.
|
||||
|
||||
**VerifyContactModal**: an `OtpField` (demo `000000`) to verify a single email/number; Done → mark verified + toast.
|
||||
|
||||
### 3.7 Privacy & Consent tab (`ConsentTab`) — its own tab, separate from notifications
|
||||
Intro: "**Consent** controls *what* data can be shared, stored or used — separate from your **Notification** preferences …which control *how* we reach you." A gray "Synced from authority records" badge.
|
||||
Data-driven consent list grouped into **Essential / Disclosure / Data & cookies / Communications**. Each item: title, description, and an emerald pill switch (or a locked "On" badge for required items). Items:
|
||||
- *Essential*: **Consent to data processing & storage** (locked on) — "Required to administer your allotment, payments & statutory notices."
|
||||
- *Disclosure*: Profile visible to handling officers; Share profile information with family members; Share information with authorised representatives (POA); KYC & document sharing; Disclose identity during a plot transfer.
|
||||
- *Data & cookies*: Analytics & preference cookies; Share with empanelled partners; Cross-border data transfer (NRI).
|
||||
- *Communications*: Allow platform updates & announcements; Receive marketing communications. When **marketing** is on, reveal granular toggles: Newsletter, Promotional emails, Product & service updates.
|
||||
Toggling any → toast "Consent preference saved". Footer note: "Withdrawing a consent stops the related processing going forward. Statutory data processing stays on while your allotment is active…"
|
||||
|
||||
### 3.8 Devices tab (`DevicesTab`)
|
||||
- **Connected Devices** list (hard-coded sample): e.g. "MacBook Pro · Chrome / Greater Noida · Active now / current", "iPhone 14 · YEIDA App / 2h ago", "Windows PC · Edge / Delhi · 5 days ago". Current device shows a green "This device" badge; others a red "Sign out" button → toast "Signed out of device".
|
||||
- **Recent Activity** timeline from `loginActivity`: each entry = a circular icon (emerald check if `ok`, else red alert), action, "{device} · {location}", and "IP {ip} · {time}", connected by a vertical line.
|
||||
|
||||
---
|
||||
|
||||
## 4. Behaviour rules checklist (don't miss these)
|
||||
1. **OTP is mocked**: `000000` verifies; the `OtpField` always says "enter 000000 to verify (demo)".
|
||||
2. **Verify-to-reveal**: allotment identity (allotment no., plot, sector, pocket) stays masked until the user confirms their **plot number** (demo `181`).
|
||||
3. **Email change** is authorised by an OTP to the **registered mobile**, never the old email; old-email confirmation is policy-gated and skippable via "lost access".
|
||||
4. **Mobile change** verifies old then new number; "lost access" skips the old-number step.
|
||||
5. **KYC**: identity proof and address proof must be **two different documents**; photograph is **mandatory at KYC** (was optional at registration). Aadhaar can serve identity+address but cannot be used for *both* requirements simultaneously.
|
||||
6. **Password policy**: ≥8 chars + upper + lower + number + special.
|
||||
7. **2FA change** requires current password **and** a verified OTP — otherwise blocked.
|
||||
8. **Ticket routing**: category → department + sub-reasons; some categories require an attachment; submit auto-routes (no "call for everything").
|
||||
9. **Ticket lifecycle**: Open → In Progress → Waiting for User → Resolved → Closed (timeline + badge tones above).
|
||||
10. **Support channels** are exactly five: AI Assistant, Live Chat (AI/human chooser), Request Callback, Talk to Human, Create Ticket.
|
||||
11. **Notifications ≠ Consent**: keep them in separate tabs with the cross-pointers.
|
||||
12. **Allottee may differ from account holder** (NRI/spouse case): per-contact `owner` of `holder|allottee`, allottee can have own email/phone, reminders can route to them.
|
||||
13. Everything writes to local React state (+ localStorage for photo/profile overrides) and surfaces a toast; no network calls.
|
||||
|
||||
Build it pixel-clean and production-looking, matching the SaaS-dashboard aesthetic described in §0. Use the exact copy and field names above.
|
||||
Reference in New Issue
Block a user