Wire user dashboard to real APIs — profile, credits, leads, requirements, jobs
- dashboard.tsx: fetch session for real user name/ID/role, fallback to
localStorage; show dashboard with role defaults when runtime config unavailable
- DashboardDesignPreview: add liveData prop; createResource for credits,
marketplace, lead requests, customer requirements, jobs, and profile
- Profile form: inputs now track state via profileFormData signal; pre-filled
from GET /api/${prefix}/profile/me; Save Changes PATCHes real endpoint
- Lead actions: Send Request POSTs to /api/${prefix}/leads/request; Cancel
DELETEs /api/${prefix}/leads/requests/{id}; both refetch after completion
- Requirement submit: POSTs to /api/customers/requirements then submits for approval
- Replace hardcoded "Alex" with real session name; credits from wallet balance API
- Fix launch.json PATH so npm is found in sh shell
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 21:13:54 +02:00
import { For , Show , createEffect , createMemo , createResource , createSignal } from 'solid-js' ;
2026-04-05 16:52:02 +02:00
import {
Award ,
Bell ,
BadgeCheck ,
Bookmark ,
BriefcaseBusiness ,
Calendar ,
Camera ,
CheckCircle2 ,
ChevronDown ,
ChevronUp ,
Clapperboard ,
Compass ,
Coins ,
Code2 ,
Dumbbell ,
Edit2 ,
Eye ,
FileText ,
GraduationCap ,
HandHelping ,
HeadphonesIcon ,
HelpCircle ,
Image ,
LayoutGrid ,
LogOut ,
MapPin ,
Megaphone ,
PenTool ,
RefreshCw ,
Rocket ,
Scissors ,
Search ,
Settings ,
Settings2 ,
ShieldCheck ,
Star ,
TrendingUp ,
UtensilsCrossed ,
User ,
UserCircle2 ,
Users ,
X ,
} from 'lucide-solid' ;
function titleCase ( value : string ) {
return String ( value || '' )
. replace ( /_/g , ' ' )
. replace ( /\b\w/g , ( c ) = > c . toUpperCase ( ) ) ;
}
const ORANGE_ICON_FILTER = 'invert(51%) sepia(86%) saturate(2445%) hue-rotate(353deg) brightness(101%) contrast(103%)' ;
function StatusBadge ( props : { status : 'ACTIVE' | 'INACTIVE' } ) {
const active = ( ) = > props . status === 'ACTIVE' ;
return (
< span style = { ` display:inline-flex;align-items:center;border-radius:9999px;border:1px solid ${ active ( ) ? '#FFD8C2' : '#D1D5DB' } ;background: ${ active ( ) ? '#FFF1EB' : '#F3F4F6' } ;color: ${ active ( ) ? '#FF5E13' : '#4B5563' } ;padding:2px 10px;font-size:12px;font-weight:500 ` } >
{ active ( ) ? 'Active' : 'Inactive' }
< / span >
) ;
}
function sidebarIcon ( label : string ) {
const key = String ( label || '' ) . toLowerCase ( ) . trim ( ) ;
if ( key . includes ( 'dashboard' ) ) return LayoutGrid ;
if ( key . includes ( 'portfolio' ) ) return Image ;
if ( key . includes ( 'profile' ) ) return UserCircle2 ;
if ( key . includes ( 'lead' ) ) return HandHelping ;
if ( key . includes ( 'response' ) ) return FileText ;
if ( key . includes ( 'credit' ) ) return Coins ;
if ( key . includes ( 'explore' ) ) return Compass ;
if ( key . includes ( 'verification' ) ) return BadgeCheck ;
if ( key . includes ( 'support' ) || key . includes ( 'help center' ) ) return HeadphonesIcon ;
if ( key === 'settings' ) return Settings2 ;
if ( key . includes ( 'switch role' ) || key . includes ( 'switch services' ) || key . includes ( 'switch service' ) ) return RefreshCw ;
if ( key . includes ( 'logout' ) ) return LogOut ;
if ( key . includes ( 'job' ) ) return BriefcaseBusiness ;
if ( key . includes ( 'application' ) ) return FileText ;
if ( key . includes ( 'shortlisted' ) ) return Users ;
if ( key . includes ( 'saved' ) ) return Bookmark ;
if ( key . includes ( 'requirement' ) ) return FileText ;
return LayoutGrid ;
}
type CustomerView = {
title : string ;
subtitle : string ;
tabs : string [ ] ;
cta? : string ;
} ;
type ProfileSpec = {
title : string ;
subtitle : string ;
tabs : string [ ] ;
tabFields : Record < string , string [ ] > ;
} ;
function normalizeRoleKey ( roleKey : string ) : string {
const key = String ( roleKey || '' ) . toUpperCase ( ) ;
if ( key . includes ( 'COMPANY' ) ) return 'COMPANY' ;
if ( key . includes ( 'CUSTOMER' ) ) return 'CUSTOMER' ;
if ( key . includes ( 'SERVICE_SEEKER' ) ) return 'CUSTOMER' ;
if ( key . includes ( 'JOB_SEEKER' ) || key . includes ( 'JOBSEEKER' ) ) return 'JOB_SEEKER' ;
if ( key . includes ( 'PHOTOGRAPHER' ) ) return 'PHOTOGRAPHER' ;
if ( key . includes ( 'MAKEUP' ) ) return 'MAKEUP_ARTIST' ;
if ( key . includes ( 'DEVELOPER' ) ) return 'DEVELOPER' ;
if ( key . includes ( 'VIDEO' ) ) return 'VIDEO_EDITOR' ;
if ( key . includes ( 'UGC' ) || ( key . includes ( 'CONTENT' ) && key . includes ( 'CREATOR' ) ) ) return 'UGC_CONTENT_CREATOR' ;
if ( key . includes ( 'GRAPHIC' ) ) return 'GRAPHIC_DESIGNER' ;
if ( key . includes ( 'SOCIAL' ) ) return 'SOCIAL_MEDIA_MANAGER' ;
if ( key . includes ( 'FITNESS' ) ) return 'FITNESS_TRAINER' ;
if ( key . includes ( 'TUTOR' ) ) return 'TUTOR' ;
if ( key . includes ( 'CATER' ) ) return 'CATERING_SERVICES' ;
return 'PROFESSIONAL' ;
}
const PROFILE_SPECS : Record < string , ProfileSpec > = {
CUSTOMER : {
title : 'Service Seeker Profile' ,
subtitle : 'Manage your personal details, service preferences, documents, and account settings.' ,
tabs : [ 'basic information' , 'documents' , 'settings' ] ,
tabFields : {
'basic information' : [ 'First Name' , 'Last Name' , 'Email Address' , 'Mobile Number' , 'Area' , 'Place' , 'PIN Code' , 'Service Category' ] ,
documents : [ 'Identity Proof' , 'Address Proof' ] ,
settings : [ 'Password & Login' , 'Notification Preferences' , 'Privacy Controls' ] ,
} ,
} ,
COMPANY : {
title : 'Company Profile' ,
subtitle : 'Configure organization details, hiring preferences, compliance documents, and settings.' ,
tabs : [ 'basic information' , 'documents' , 'settings' ] ,
tabFields : {
'basic information' : [ 'Company Name' , 'Company Email' , 'Company Phone' , 'City' , 'Area' , 'Place' , 'PIN Code' , 'Website URL' ] ,
documents : [ 'GST Certificate' , 'PAN Card' , 'Incorporation Certificate' ] ,
settings : [ 'Account Security' , 'Team Access' , 'Notification Preferences' ] ,
} ,
} ,
JOB_SEEKER : {
title : 'Job Seeker Profile' ,
subtitle : 'Maintain your career profile, resume, preferences, and verification docs.' ,
tabs : [ 'basic information' , 'documents' , 'settings' ] ,
tabFields : {
'basic information' : [ 'First Name' , 'Last Name' , 'Email Address' , 'Mobile Number' , 'Current Role' , 'Total Experience' , 'City' , 'Area' , 'Place' ] ,
documents : [ 'Identity Proof' , 'Address Proof' , 'Education Proof' ] ,
settings : [ 'Password & Login' , 'Notification Preferences' , 'Privacy Controls' ] ,
} ,
} ,
PHOTOGRAPHER : {
title : 'Photographer Profile' ,
subtitle : 'Manage your photography details, pricing, portfolio, and documents.' ,
tabs : [ 'basic information' , 'documents' , 'settings' ] ,
tabFields : {
'basic information' : [ 'First Name' , 'Last Name' , 'Email Address' , 'Mobile Number' , 'Gender' , 'Address Line 1' , 'Address Line 2 (Optional)' , 'City' , 'Area' , 'State' , 'PIN Code' ] ,
documents : [ 'Identity Proof' , 'Address Proof' , 'Portfolio Ownership Proof' ] ,
settings : [ 'Password & Login' , 'Notification Preferences' , 'Privacy Controls' ] ,
} ,
} ,
MAKEUP_ARTIST : {
title : 'Makeup Artist Profile' ,
subtitle : 'Manage makeup specialization, services, portfolio, and compliance documents.' ,
tabs : [ 'basic information' , 'documents' , 'settings' ] ,
tabFields : {
'basic information' : [ 'First Name' , 'Last Name' , 'Email Address' , 'Mobile Number' , 'Gender' , 'Address Line 1' , 'Address Line 2 (Optional)' , 'City' , 'Area' , 'State' , 'PIN Code' ] ,
documents : [ 'Identity Proof' , 'Address Proof' , 'Professional Certifications' ] ,
settings : [ 'Password & Login' , 'Notification Preferences' , 'Privacy Controls' ] ,
} ,
} ,
DEVELOPER : {
title : 'Developer Profile' ,
subtitle : 'Showcase technical profile, pricing models, portfolio projects, and documents.' ,
tabs : [ 'basic information' , 'documents' , 'settings' ] ,
tabFields : {
'basic information' : [ 'First Name' , 'Last Name' , 'Email Address' , 'Mobile Number' , 'Gender' , 'Address Line 1' , 'Address Line 2 (Optional)' , 'City' , 'Area' , 'State' , 'PIN Code' ] ,
documents : [ 'Identity Proof' , 'Address Proof' , 'Tax Document' ] ,
settings : [ 'Password & Login' , 'Notification Preferences' , 'Privacy Controls' ] ,
} ,
} ,
VIDEO_EDITOR : {
title : 'Video Editor Profile' ,
subtitle : 'Manage editing profile, services, portfolio, and verification documents.' ,
tabs : [ 'basic information' , 'documents' , 'settings' ] ,
tabFields : {
'basic information' : [ 'First Name' , 'Last Name' , 'Email Address' , 'Mobile Number' , 'Gender' , 'Address Line 1' , 'Address Line 2 (Optional)' , 'City' , 'Area' , 'State' , 'PIN Code' ] ,
documents : [ 'Identity Proof' , 'Address Proof' , 'Tax Document' ] ,
settings : [ 'Password & Login' , 'Notification Preferences' , 'Privacy Controls' ] ,
} ,
} ,
UGC_CONTENT_CREATOR : {
title : 'UGC Content Creator Profile' ,
subtitle : 'Manage your creator profile, content style, pricing, and portfolio deliverables.' ,
tabs : [ 'basic information' , 'documents' , 'settings' ] ,
tabFields : {
'basic information' : [ 'First Name' , 'Last Name' , 'Email Address' , 'Mobile Number' , 'Gender' , 'Address Line 1' , 'Address Line 2 (Optional)' , 'City' , 'Area' , 'State' , 'PIN Code' ] ,
documents : [ 'Identity Proof' , 'Address Proof' , 'Tax Document' ] ,
settings : [ 'Password & Login' , 'Notification Preferences' , 'Privacy Controls' ] ,
} ,
} ,
GRAPHIC_DESIGNER : {
title : 'Graphic Designer Profile' ,
subtitle : 'Manage design profile, service pricing, portfolio assets, and documents.' ,
tabs : [ 'basic information' , 'documents' , 'settings' ] ,
tabFields : {
'basic information' : [ 'First Name' , 'Last Name' , 'Email Address' , 'Mobile Number' , 'Gender' , 'Address Line 1' , 'Address Line 2 (Optional)' , 'City' , 'Area' , 'State' , 'PIN Code' ] ,
documents : [ 'Identity Proof' , 'Address Proof' , 'Tax Document' ] ,
settings : [ 'Password & Login' , 'Notification Preferences' , 'Privacy Controls' ] ,
} ,
} ,
SOCIAL_MEDIA_MANAGER : {
title : 'Social Media Manager Profile' ,
subtitle : 'Manage social profile details, service plans, case studies, and documents.' ,
tabs : [ 'basic information' , 'documents' , 'settings' ] ,
tabFields : {
'basic information' : [ 'First Name' , 'Last Name' , 'Email Address' , 'Mobile Number' , 'Gender' , 'Address Line 1' , 'Address Line 2 (Optional)' , 'City' , 'Area' , 'State' , 'PIN Code' ] ,
documents : [ 'Identity Proof' , 'Address Proof' , 'Tax Document' ] ,
settings : [ 'Password & Login' , 'Notification Preferences' , 'Privacy Controls' ] ,
} ,
} ,
FITNESS_TRAINER : {
title : 'Fitness Trainer Profile' ,
subtitle : 'Manage training details, plans, certifications, and profile settings.' ,
tabs : [ 'basic information' , 'documents' , 'settings' ] ,
tabFields : {
'basic information' : [ 'First Name' , 'Last Name' , 'Email Address' , 'Mobile Number' , 'Gender' , 'Address Line 1' , 'Address Line 2 (Optional)' , 'City' , 'Area' , 'State' , 'PIN Code' ] ,
documents : [ 'Identity Proof' , 'Address Proof' , 'Certification Proof' ] ,
settings : [ 'Password & Login' , 'Notification Preferences' , 'Privacy Controls' ] ,
} ,
} ,
TUTOR : {
title : 'Tutor Profile' ,
subtitle : 'Manage teaching details, subjects, pricing, documents, and settings.' ,
tabs : [ 'basic information' , 'documents' , 'settings' ] ,
tabFields : {
'basic information' : [ 'First Name' , 'Last Name' , 'Email Address' , 'Mobile Number' , 'Gender' , 'Address Line 1' , 'Address Line 2 (Optional)' , 'City' , 'Area' , 'State' , 'PIN Code' ] ,
documents : [ 'Identity Proof' , 'Address Proof' , 'Educational Proof' ] ,
settings : [ 'Password & Login' , 'Notification Preferences' , 'Privacy Controls' ] ,
} ,
} ,
CATERING_SERVICES : {
title : 'Catering Services Profile' ,
subtitle : 'Manage business details, menu packages, gallery, and compliance docs.' ,
tabs : [ 'basic information' , 'documents' , 'settings' ] ,
tabFields : {
'basic information' : [ 'Business Name' , 'Contact Person Name' , 'Email Address' , 'Mobile Number' , 'Gender' , 'Address Line 1' , 'Address Line 2 (Optional)' , 'City' , 'Area' , 'State' , 'PIN Code' ] ,
documents : [ 'Identity Proof' , 'Address Proof' , 'Food License' ] ,
settings : [ 'Password & Login' , 'Notification Preferences' , 'Privacy Controls' ] ,
} ,
} ,
PROFESSIONAL : {
title : 'Professional Profile' ,
subtitle : 'Manage professional details, pricing, portfolio, and account settings.' ,
tabs : [ 'basic information' , 'documents' , 'settings' ] ,
tabFields : {
'basic information' : [ 'Full Name' , 'Email Address' , 'Mobile Number' , 'Gender' , 'Address Line 1' , 'Address Line 2 (Optional)' , 'City' , 'Area' , 'State' , 'PIN Code' ] ,
documents : [ 'Identity Proof' , 'Address Proof' , 'Tax Document' ] ,
settings : [ 'Password & Login' , 'Notification Preferences' , 'Privacy Controls' ] ,
} ,
} ,
} ;
function profileSpecForRole ( roleKey : string ) : ProfileSpec {
const normalized = normalizeRoleKey ( roleKey ) ;
const base = PROFILE_SPECS [ normalized ] || PROFILE_SPECS . PROFESSIONAL ;
const existingDocuments = Array . isArray ( base . tabFields ? . documents ) ? base . tabFields . documents : [ ] ;
const documents = Array . from ( new Set ( [ 'Address' , 'Address Proof' , . . . existingDocuments ] ) ) ;
return {
. . . base ,
tabFields : {
. . . base . tabFields ,
documents ,
} ,
} ;
}
type PortfolioSpec = {
roleLabel : string ;
tabs : string [ ] ;
specialties : string [ ] ;
statsLabels : string [ ] ;
equipmentLabel : string ;
galleryTabLabel : string ;
experienceTabLabel : string ;
serviceTabLabel : string ;
faqItems : Array < { q : string ; a : string } > ;
packages : Array < { name : string ; price : string ; items : string [ ] } > ;
} ;
const PORTFOLIO_SPECS : Record < string , PortfolioSpec > = {
PHOTOGRAPHER : {
roleLabel : 'Photographer' ,
tabs : [ 'about' , 'services & pricing' , 'portfolio gallery' , 'experience & equipment' , 'testimonials' , 'faqs' ] ,
specialties : [ 'Wedding' , 'Pre-Wedding' , 'Candid' , 'Event' , 'Portrait' , 'Lifestyle' ] ,
statsLabels : [ 'Shoots Done' , 'Years Exp' , 'Verified Pro' , 'Last Delivery' , 'Will Travel' ] ,
equipmentLabel : 'Camera & Equipment' ,
galleryTabLabel : 'portfolio gallery' ,
experienceTabLabel : 'experience & equipment' ,
serviceTabLabel : 'services & pricing' ,
faqItems : [
{ q : 'Do you travel for shoots?' , a : 'Yes, I cover Pan-India and select international locations. Travel charges apply beyond 50km.' } ,
{ q : 'How long before I receive the final photos?' , a : 'Edited photos are delivered within 10– 14 working days after the shoot.' } ,
{ q : 'What equipment do you use?' , a : 'I shoot with Canon EOS R6 and Sony A7IV, with a full range of prime and zoom lenses.' } ,
{ q : 'Do you provide raw files?' , a : 'Raw files are not included by default. They can be added as an optional upgrade.' } ,
] ,
packages : [
{ name : 'Essential' , price : '₹15,000' , items : [ '4-hour shoot' , '100 edited photos' , 'Online gallery' , '1 location' ] } ,
{ name : 'Premium' , price : '₹28,000' , items : [ '8-hour shoot' , '250 edited photos' , 'Online gallery' , '2 locations' , 'Drone shots' ] } ,
{ name : 'Signature' , price : '₹50,000' , items : [ 'Full-day shoot' , '500 edited photos' , 'USB delivery' , '3 locations' , 'Drone + Reel' ] } ,
] ,
} ,
MAKEUP_ARTIST : {
roleLabel : 'Makeup Artist' ,
tabs : [ 'about' , 'services & pricing' , 'gallery' , 'experience & certifications' , 'testimonials' , 'faqs' ] ,
specialties : [ 'Bridal' , 'Party' , 'Editorial' , 'SFX' , 'Airbrush' , 'Natural' ] ,
statsLabels : [ 'Sessions Done' , 'Years Exp' , 'Verified Pro' , 'Last Booking' , 'Will Travel' ] ,
equipmentLabel : 'Kit & Products' ,
galleryTabLabel : 'gallery' ,
experienceTabLabel : 'experience & certifications' ,
serviceTabLabel : 'services & pricing' ,
faqItems : [
{ q : 'Do you do trials before the wedding?' , a : 'Yes, a bridal trial is mandatory. It helps finalise the look and ensures comfort on the big day.' } ,
{ q : 'Which brands do you use?' , a : 'I work with MAC, Huda Beauty, Fenty, and NARS for face. Products are selected based on skin type.' } ,
{ q : 'Do you travel for events?' , a : 'Yes. Travel is available with prior notice. Charges apply for locations beyond 30km.' } ,
{ q : 'How long does bridal makeup take?' , a : 'Typically 2.5– 3 hours for full bridal. Party makeup takes around 45– 60 minutes.' } ,
] ,
packages : [
{ name : 'Party' , price : '₹3,500' , items : [ 'Full face makeup' , 'Hair styling' , '45-min session' , 'Touch-up kit' ] } ,
{ name : 'Bridal' , price : '₹18,000' , items : [ 'Trial session' , 'Full bridal look' , 'HD & airbrush' , 'Touch-up on-site' ] } ,
{ name : 'Bridal Party' , price : '₹35,000' , items : [ 'Bride + 4 family' , 'Full day' , 'Airbrush finish' , 'On-site artist' ] } ,
] ,
} ,
TUTOR : {
roleLabel : 'Tutor' ,
tabs : [ 'about' , 'subjects & pricing' , 'student work' , 'qualifications' , 'testimonials' , 'faqs' ] ,
specialties : [ 'Mathematics' , 'Physics' , 'Chemistry' , 'Biology' , 'English' , 'Programming' ] ,
statsLabels : [ 'Students Taught' , 'Years Exp' , 'Verified Pro' , 'Next Slot' , 'Online OK' ] ,
equipmentLabel : 'Teaching Tools' ,
galleryTabLabel : 'student work' ,
experienceTabLabel : 'qualifications' ,
serviceTabLabel : 'subjects & pricing' ,
faqItems : [
{ q : 'Do you teach online or in-person?' , a : 'Both modes available. Online via Zoom or Google Meet. In-person in Chennai and surrounding areas.' } ,
{ q : 'What boards do you cover?' , a : 'CBSE, ICSE, State Board, IB, and A-Levels. I also prepare students for JEE and NEET.' } ,
{ q : 'How many students per batch?' , a : 'Group batches have max 5 students. Individual sessions are 1-on-1 for focused learning.' } ,
{ q : 'Do you provide study material?' , a : 'Yes. Custom notes, practice sheets, and mock tests are included in all plans.' } ,
] ,
packages : [
{ name : 'Crash Course' , price : '₹5,000/mo' , items : [ '10 sessions/month' , '1 subject' , 'Practice tests' , 'Online only' ] } ,
{ name : 'Core Plan' , price : '₹8,500/mo' , items : [ '16 sessions/month' , '2 subjects' , 'Study materials' , 'Doubt clearing' ] } ,
{ name : 'Intensive' , price : '₹14,000/mo' , items : [ '24 sessions/month' , '3 subjects' , 'Mock exams' , 'Progress reports' ] } ,
] ,
} ,
DEVELOPER : {
roleLabel : 'Developer' ,
tabs : [ 'about' , 'services & pricing' , 'projects' , 'tech stack & experience' , 'testimonials' , 'faqs' ] ,
specialties : [ 'Web Development' , 'Mobile Apps' , 'API / Backend' , 'UI/UX' , 'DevOps' , 'Consulting' ] ,
statsLabels : [ 'Projects Done' , 'Years Exp' , 'Verified Pro' , 'Last Delivery' , 'Remote OK' ] ,
equipmentLabel : 'Tech Stack' ,
galleryTabLabel : 'projects' ,
experienceTabLabel : 'tech stack & experience' ,
serviceTabLabel : 'services & pricing' ,
faqItems : [
{ q : 'Do you work on fixed price or hourly?' , a : 'Both models available. Small projects are fixed-price; larger engagements use hourly or milestone billing.' } ,
{ q : 'Do you sign NDAs?' , a : 'Yes. NDAs are standard for all client projects involving proprietary data or unreleased products.' } ,
{ q : 'Can you handle full-stack?' , a : 'Yes. I cover React/SolidJS frontend, Rust/Node backend, PostgreSQL databases, and cloud deployment.' } ,
{ q : 'What is your typical delivery time?' , a : 'Landing pages in 3– 5 days. Full apps depend on scope. Detailed estimate provided after briefing.' } ,
] ,
packages : [
{ name : 'Starter' , price : '₹20,000' , items : [ 'Landing page / MVP' , '5-day delivery' , '2 revisions' , 'Basic SEO' ] } ,
{ name : 'Business' , price : '₹65,000' , items : [ 'Full web app' , '3-week delivery' , 'API integration' , 'Admin panel' , 'Deployment' ] } ,
{ name : 'Enterprise' , price : 'Custom' , items : [ 'Complex systems' , 'Dedicated sprint' , 'Team collaboration' , 'SLA included' ] } ,
] ,
} ,
VIDEO_EDITOR : {
roleLabel : 'Video Editor' ,
tabs : [ 'about' , 'services & pricing' , 'showreel' , 'experience & tools' , 'testimonials' , 'faqs' ] ,
specialties : [ 'Wedding Films' , 'Corporate' , 'Music Videos' , 'Reels' , 'Documentary' , 'Product' ] ,
statsLabels : [ 'Videos Done' , 'Years Exp' , 'Verified Pro' , 'Last Delivery' , 'Remote OK' ] ,
equipmentLabel : 'Editing Suite' ,
galleryTabLabel : 'showreel' ,
experienceTabLabel : 'experience & tools' ,
serviceTabLabel : 'services & pricing' ,
faqItems : [
{ q : 'Which editing software do you use?' , a : 'Adobe Premiere Pro, DaVinci Resolve, and After Effects for motion graphics and colour grading.' } ,
{ q : 'What is your turnaround time?' , a : 'Short reels in 2– 3 days. Full event films take 10– 15 working days depending on footage length.' } ,
{ q : 'Do you accept raw footage from other cameras?' , a : 'Yes. Any format is accepted — I handle transcoding as part of the workflow.' } ,
{ q : 'How many revisions are included?' , a : '2 revisions are included in all packages. Additional revisions are charged at ₹500 per round.' } ,
] ,
packages : [
{ name : 'Reel Edit' , price : '₹3,000' , items : [ 'Up to 60s reel' , 'Music sync' , '2 revisions' , '48hr delivery' ] } ,
{ name : 'Event Film' , price : '₹12,000' , items : [ '5-min highlight' , 'Colour grade' , 'Titles + music' , '10-day delivery' ] } ,
{ name : 'Feature Film' , price : '₹28,000' , items : [ 'Full-length film' , 'Cinematic grade' , 'Motion graphics' , 'Multi-format export' ] } ,
] ,
} ,
UGC_CONTENT_CREATOR : {
roleLabel : 'UGC Content Creator' ,
tabs : [ 'about' , 'services & pricing' , 'content portfolio' , 'experience & tools' , 'testimonials' , 'faqs' ] ,
specialties : [ 'UGC Ads' , 'Product Demos' , 'Lifestyle UGC' , 'Voiceover' , 'Scripted Reels' , 'Performance Creatives' ] ,
statsLabels : [ 'Campaigns Done' , 'Years Exp' , 'Verified Pro' , 'Last Delivery' , 'Remote OK' ] ,
equipmentLabel : 'Creator Setup' ,
galleryTabLabel : 'content portfolio' ,
experienceTabLabel : 'experience & tools' ,
serviceTabLabel : 'services & pricing' ,
faqItems : [
{ q : 'Do you create scripts for UGC ads?' , a : 'Yes. Script ideation and hooks are included in campaign packages based on your brand goals.' } ,
{ q : 'Can you deliver platform-specific formats?' , a : 'Yes. I deliver optimized cuts for Instagram, YouTube Shorts, and TikTok formats.' } ,
{ q : 'Are raw files included?' , a : 'Raw files are optional and available as an add-on depending on package scope.' } ,
{ q : 'What is your turnaround time?' , a : 'Most UGC deliverables are completed in 3– 5 business days after brief and product receipt.' } ,
] ,
packages : [
{ name : 'Starter UGC' , price : '₹7,500' , items : [ '1 video creative' , 'Script + hook' , 'Vertical format' , '2 revisions' ] } ,
{ name : 'Growth UGC' , price : '₹18,000' , items : [ '3 video creatives' , 'Platform variations' , 'Usage rights' , '4 revisions' ] } ,
{ name : 'Scale UGC' , price : '₹35,000' , items : [ '6 video creatives' , 'A/B hook variants' , 'Ad-ready exports' , 'Priority delivery' ] } ,
] ,
} ,
GRAPHIC_DESIGNER : {
roleLabel : 'Graphic Designer' ,
tabs : [ 'about' , 'services & pricing' , 'portfolio' , 'experience & tools' , 'testimonials' , 'faqs' ] ,
specialties : [ 'Brand Identity' , 'UI/UX Design' , 'Print' , 'Social Media' , 'Motion' , 'Packaging' ] ,
statsLabels : [ 'Projects Done' , 'Years Exp' , 'Verified Pro' , 'Last Delivery' , 'Remote OK' ] ,
equipmentLabel : 'Design Tools' ,
galleryTabLabel : 'portfolio' ,
experienceTabLabel : 'experience & tools' ,
serviceTabLabel : 'services & pricing' ,
faqItems : [
{ q : 'What file formats do you deliver?' , a : 'Final files in AI, PDF, PNG, SVG, and any format required. Print-ready and web-optimised variants included.' } ,
{ q : 'Do you create brand guidelines?' , a : 'Yes. Brand identity packages include a full style guide covering colour, typography, and usage rules.' } ,
{ q : 'How many concepts do you provide initially?' , a : '3 initial concepts are presented for branding. UI/UX starts with wireframes before visual designs.' } ,
{ q : 'Do you handle printing coordination?' , a : 'Yes. I work with trusted print vendors and can manage end-to-end print production.' } ,
] ,
packages : [
{ name : 'Logo Pack' , price : '₹8,000' , items : [ '3 logo concepts' , '2 revisions' , 'All file formats' , 'Usage rights' ] } ,
{ name : 'Brand Kit' , price : '₹22,000' , items : [ 'Logo + palette' , 'Typography' , 'Brand guidelines' , 'Social templates' ] } ,
{ name : 'Full Identity' , price : '₹45,000' , items : [ 'Complete brand' , 'UI kit' , 'Print collateral' , 'Motion logo' ] } ,
] ,
} ,
SOCIAL_MEDIA_MANAGER : {
roleLabel : 'Social Media Manager' ,
tabs : [ 'about' , 'services & pricing' , 'case studies' , 'experience & tools' , 'testimonials' , 'faqs' ] ,
specialties : [ 'Instagram' , 'YouTube' , 'LinkedIn' , 'Twitter/X' , 'Facebook' , 'Content Strategy' ] ,
statsLabels : [ 'Brands Managed' , 'Years Exp' , 'Verified Pro' , 'Last Campaign' , 'Remote OK' ] ,
equipmentLabel : 'Platforms & Tools' ,
galleryTabLabel : 'case studies' ,
experienceTabLabel : 'experience & tools' ,
serviceTabLabel : 'services & pricing' ,
faqItems : [
{ q : 'Do you create the content too?' , a : 'Yes. Content creation — including copy, graphics, and reels — is included in all monthly retainer plans.' } ,
{ q : 'How do you measure results?' , a : 'Monthly reports covering reach, engagement rate, follower growth, and link clicks are shared via dashboard.' } ,
{ q : 'Do you handle paid ads?' , a : 'Yes. Meta and Google ad campaigns are available as add-ons with dedicated reporting.' } ,
{ q : 'What industries do you specialise in?' , a : 'Fashion, F&B, real estate, personal brands, and D2C e-commerce. Industry-specific content strategy provided.' } ,
] ,
packages : [
{ name : 'Starter' , price : '₹12,000/mo' , items : [ '2 platforms' , '12 posts/month' , 'Captions + hashtags' , 'Monthly report' ] } ,
{ name : 'Growth' , price : '₹22,000/mo' , items : [ '3 platforms' , '24 posts/month' , 'Reels + Stories' , 'Ad management' ] } ,
{ name : 'Premium' , price : '₹40,000/mo' , items : [ '5 platforms' , 'Daily posting' , 'Full content calendar' , 'Influencer outreach' ] } ,
] ,
} ,
FITNESS_TRAINER : {
roleLabel : 'Fitness Trainer' ,
tabs : [ 'about' , 'training plans' , 'client results' , 'certifications' , 'testimonials' , 'faqs' ] ,
specialties : [ 'Weight Loss' , 'Strength' , 'Yoga' , 'HIIT' , 'Nutrition' , 'Rehabilitation' ] ,
statsLabels : [ 'Clients Trained' , 'Years Exp' , 'Certified Pro' , 'Next Slot' , 'Online OK' ] ,
equipmentLabel : 'Certifications & Tools' ,
galleryTabLabel : 'client results' ,
experienceTabLabel : 'certifications' ,
serviceTabLabel : 'training plans' ,
faqItems : [
{ q : 'Do you offer online training?' , a : 'Yes. Online sessions via Zoom with personalised programs tracked through fitness apps.' } ,
{ q : 'Is nutrition guidance included?' , a : 'Basic nutrition guidance is included. A detailed meal plan is available as an add-on.' } ,
{ q : 'How long until I see results?' , a : 'Visible progress typically begins in 4– 6 weeks with consistent training and diet adherence.' } ,
{ q : 'Do you design custom workout plans?' , a : 'Yes. Every client gets a custom plan based on fitness level, goals, and equipment access.' } ,
] ,
packages : [
{ name : 'Starter' , price : '₹4,000/mo' , items : [ '8 sessions/month' , 'Workout plan' , 'WhatsApp check-ins' , 'Progress tracking' ] } ,
{ name : 'Transform' , price : '₹8,000/mo' , items : [ '16 sessions/month' , 'Custom diet plan' , 'Weekly reviews' , 'App access' ] } ,
{ name : 'Elite' , price : '₹15,000/mo' , items : [ 'Daily check-in' , 'Full nutrition plan' , 'Body composition tracking' , 'Priority access' ] } ,
] ,
} ,
CATERING_SERVICES : {
roleLabel : 'Catering Services' ,
tabs : [ 'about' , 'packages & pricing' , 'gallery' , 'experience & certifications' , 'testimonials' , 'faqs' ] ,
specialties : [ 'Weddings' , 'Corporate Events' , 'Private Parties' , 'Buffet' , 'Live Counters' , 'Theme Catering' ] ,
statsLabels : [ 'Events Done' , 'Years Active' , 'Certified' , 'Last Event' , 'Pan-India' ] ,
equipmentLabel : 'Equipment & Certifications' ,
galleryTabLabel : 'gallery' ,
experienceTabLabel : 'experience & certifications' ,
serviceTabLabel : 'packages & pricing' ,
faqItems : [
{ q : 'What is your minimum guest count?' , a : 'Minimum 50 guests for standard bookings. Intimate private dining available for 20+ guests.' } ,
{ q : 'Do you handle equipment setup?' , a : 'Yes. Full setup including chafing dishes, serving counters, and staff is included in all packages.' } ,
{ q : 'Are custom menus available?' , a : 'Absolutely. Menus are fully customisable — dietary restrictions, regional cuisines, and themed menus accommodated.' } ,
{ q : 'How far in advance should I book?' , a : 'At least 3– 4 weeks for events under 200 guests; 6– 8 weeks for large-scale events.' } ,
] ,
packages : [
{ name : 'Essential' , price : '₹350/plate' , items : [ '3-course menu' , 'Serving staff' , 'Basic setup' , 'Buffet style' ] } ,
{ name : 'Premium' , price : '₹650/plate' , items : [ '5-course menu' , 'Live counters' , 'Themed decor' , 'Dedicated manager' ] } ,
{ name : 'Royal' , price : '₹1,200/plate' , items : [ 'Custom menu' , 'Chef station' , 'Premium décor' , 'Full-day service' ] } ,
] ,
} ,
PROFESSIONAL : {
roleLabel : 'Professional' ,
tabs : [ 'about' , 'services & pricing' , 'portfolio' , 'experience' , 'testimonials' , 'faqs' ] ,
specialties : [ 'Primary Service' , 'Secondary Service' , 'Consulting' , 'Training' , 'Events' , 'Custom' ] ,
statsLabels : [ 'Projects Done' , 'Years Exp' , 'Verified Pro' , 'Last Delivery' , 'Remote OK' ] ,
equipmentLabel : 'Tools & Equipment' ,
galleryTabLabel : 'portfolio' ,
experienceTabLabel : 'experience' ,
serviceTabLabel : 'services & pricing' ,
faqItems : [
{ q : 'How do I get started?' , a : 'Send a requirement, review the professional\'s profile, and accept the request to receive their contact details.' } ,
{ q : 'What is your availability?' , a : 'Availability is updated regularly on the profile. Contact for specific date confirmation.' } ,
{ q : 'Do you provide a contract?' , a : 'Yes. A service agreement is shared before any project begins for mutual clarity.' } ,
{ q : 'What is your revision policy?' , a : 'Revisions are included as per the selected package. Additional revisions are billed separately.' } ,
] ,
packages : [
{ name : 'Basic' , price : 'From ₹5,000' , items : [ 'Core deliverables' , 'Standard timeline' , '1 revision' , 'Email support' ] } ,
{ name : 'Standard' , price : 'From ₹15,000' , items : [ 'Full scope' , 'Priority delivery' , '3 revisions' , 'Call support' ] } ,
{ name : 'Premium' , price : 'Custom' , items : [ 'Custom scope' , 'Dedicated support' , 'Unlimited revisions' , 'SLA' ] } ,
] ,
} ,
} ;
function portfolioSpecForRole ( roleKey : string ) : PortfolioSpec {
const normalized = normalizeRoleKey ( roleKey ) ;
return PORTFOLIO_SPECS [ normalized ] || PORTFOLIO_SPECS . PROFESSIONAL ;
}
function portfolioMediaConfig ( roleKey : string ) : {
mode : 'visual' | 'non_visual' ;
ctaLabel : string ;
items : string [ ] ;
} {
const normalized = normalizeRoleKey ( roleKey ) ;
if (
normalized === 'PHOTOGRAPHER'
|| normalized === 'MAKEUP_ARTIST'
|| normalized === 'VIDEO_EDITOR'
|| normalized === 'UGC_CONTENT_CREATOR'
|| normalized === 'GRAPHIC_DESIGNER'
|| normalized === 'SOCIAL_MEDIA_MANAGER'
|| normalized === 'CATERING_SERVICES'
Wire user dashboard to real APIs — profile, credits, leads, requirements, jobs
- dashboard.tsx: fetch session for real user name/ID/role, fallback to
localStorage; show dashboard with role defaults when runtime config unavailable
- DashboardDesignPreview: add liveData prop; createResource for credits,
marketplace, lead requests, customer requirements, jobs, and profile
- Profile form: inputs now track state via profileFormData signal; pre-filled
from GET /api/${prefix}/profile/me; Save Changes PATCHes real endpoint
- Lead actions: Send Request POSTs to /api/${prefix}/leads/request; Cancel
DELETEs /api/${prefix}/leads/requests/{id}; both refetch after completion
- Requirement submit: POSTs to /api/customers/requirements then submits for approval
- Replace hardcoded "Alex" with real session name; credits from wallet balance API
- Fix launch.json PATH so npm is found in sh shell
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 21:13:54 +02:00
|| normalized === 'DEVELOPER'
|| normalized === 'FITNESS_TRAINER'
2026-04-05 16:52:02 +02:00
) {
return {
mode : 'visual' ,
ctaLabel : 'Upload Images' ,
items : [ 'Campaign Shot' , 'Result Highlight' , 'Before/After' , 'Client Output' , 'Style Board' , 'Final Delivery' , 'Portfolio Select' , 'Recent Work' ] ,
} ;
}
return {
mode : 'non_visual' ,
ctaLabel : 'Add Work Entry' ,
items : [ 'Case Study Summary' , 'Outcome & Metrics' , 'Client Feedback Snippet' , 'Delivery Timeline' , 'Method & Process' , 'Proof of Work' ] ,
} ;
}
const PORTFOLIO_TESTIMONIALS = [
{ name : 'Priya S.' , rating : 5 , text : 'Absolutely brilliant work. The results exceeded every expectation. Highly recommended.' , category : 'Verified Booking' } ,
{ name : 'Rohan M.' , rating : 5 , text : 'Professional, punctual, and highly creative. Loved the final deliverables.' , category : 'Verified Booking' } ,
{ name : 'Ananya K.' , rating : 4 , text : 'Great communication throughout the project. Delivered on time with excellent quality.' , category : 'Verified Booking' } ,
{ name : 'Kiran T.' , rating : 5 , text : 'An absolute pleasure to work with. Will definitely hire again for our next project.' , category : 'Verified Booking' } ,
] ;
function customerViewFor ( sidebar : string , roleKey : string ) : CustomerView {
const key = String ( sidebar || '' ) . toLowerCase ( ) . trim ( ) ;
const role = normalizeRoleKey ( roleKey ) ;
const isProfessionalRole = role !== 'COMPANY' && role !== 'JOB_SEEKER' && role !== 'CUSTOMER' ;
if ( key === 'my dashboard' ) return { title : 'Service Seeker Dashboard Overview' , subtitle : 'Manage your requirements and track professional responses in real-time.' , tabs : [ 'overview' , 'recent requirements' , 'quick actions' ] , cta : 'Post New Requirement' } ;
if ( key === 'leads' ) return { title : 'Leads' , subtitle : 'Browse marketplace requirements and request contact access for current opportunities.' , tabs : [ ] , cta : 'Buy Credits' } ;
if ( key === 'jobs' ) {
if ( role === 'JOB_SEEKER' ) return { title : 'Jobs' , subtitle : 'Scroll through approved jobs, filter opportunities, and apply with your job seeker profile.' , tabs : [ 'all jobs' , 'recommended' , 'saved' , 'applied' , 'expiring soon' ] , cta : 'Post A Resume' } ;
return { title : 'Post Job' , subtitle : 'Create a job, submit it for approval, and publish it to attract the right candidates.' , tabs : [ ] , cta : 'Post New Job' } ;
}
if ( key === 'my requirements' ) return { title : 'My Requirements Management' , subtitle : 'Manage and track active service requests, drafts, and closed items.' , tabs : [ 'all requirements' , 'open' , 'closed' , 'drafts' ] , cta : 'Post New Requirement' } ;
if ( key === 'received responses' || key === 'my responses' ) {
if ( role !== 'CUSTOMER' ) return { title : 'My Responses' , subtitle : 'Track requested and pending leads with current request status.' , tabs : [ 'requested leads' , 'pending leads' ] } ;
return { title : 'Received Professional Responses' , subtitle : 'Review and manage professional applications for active requirements.' , tabs : [ 'all responses' , 'new' , 'shortlisted' , 'rejected' ] } ;
}
if ( key === 'shortlisted responses' ) return { title : 'Shortlisted Responses' , subtitle : 'Focus on high-intent responses and convert them to confirmed engagements.' , tabs : [ 'all shortlisted' , 'interview scheduled' , 'finalized' ] } ;
Wire user dashboard to real APIs — profile, credits, leads, requirements, jobs
- dashboard.tsx: fetch session for real user name/ID/role, fallback to
localStorage; show dashboard with role defaults when runtime config unavailable
- DashboardDesignPreview: add liveData prop; createResource for credits,
marketplace, lead requests, customer requirements, jobs, and profile
- Profile form: inputs now track state via profileFormData signal; pre-filled
from GET /api/${prefix}/profile/me; Save Changes PATCHes real endpoint
- Lead actions: Send Request POSTs to /api/${prefix}/leads/request; Cancel
DELETEs /api/${prefix}/leads/requests/{id}; both refetch after completion
- Requirement submit: POSTs to /api/customers/requirements then submits for approval
- Replace hardcoded "Alex" with real session name; credits from wallet balance API
- Fix launch.json PATH so npm is found in sh shell
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 21:13:54 +02:00
if ( key === 'applications' ) return { title : 'Applications' , subtitle : 'Review all candidate applications received for your active job postings.' , tabs : [ 'all applications' , 'shortlisted' , 'under review' , 'rejected' ] , cta : 'View Job Postings' } ;
if ( key === 'shortlisted candidates' ) return { title : 'Shortlisted Candidates' , subtitle : 'Manage candidates you have shortlisted across all job postings.' , tabs : [ 'shortlisted' , 'interview scheduled' , 'offer extended' ] } ;
if ( key === 'my applications' ) return { title : 'My Applications' , subtitle : 'Track the status of all jobs you have applied to.' , tabs : [ 'all' , 'under review' , 'shortlisted' , 'rejected' ] , cta : 'Browse Jobs' } ;
if ( key === 'saved jobs' ) return { title : 'Saved Jobs' , subtitle : 'Jobs you have bookmarked. Apply before they expire.' , tabs : [ 'saved' , 'expiring soon' ] , cta : 'Browse Jobs' } ;
2026-04-05 16:52:02 +02:00
if ( key . includes ( 'profile' ) ) {
const spec = profileSpecForRole ( roleKey ) ;
return { title : spec.title , subtitle : spec.subtitle , tabs : spec.tabs , cta : 'Save Changes' } ;
}
if ( key === 'my portfolio' ) {
if ( ! isProfessionalRole ) {
return { title : 'Portfolio Unavailable' , subtitle : 'My Portfolio is available only for professional roles.' , tabs : [ ] } ;
}
const spec = portfolioSpecForRole ( roleKey ) ;
return { title : ` ${ spec . roleLabel } Portfolio ` , subtitle : 'Manage your public portfolio, showcase your work, and control what service seekers see before they accept your contact request.' , tabs : spec.tabs , cta : 'Edit Portfolio' } ;
}
if ( key === 'credits' ) return { title : 'Credits & Billing' , subtitle : 'View credit balance, top-up packages, and transaction history.' , tabs : [ 'overview' , 'buy credits' , 'transactions' , 'usage history' ] , cta : 'Buy Credits' } ;
if ( key === 'explore nxtgauge' ) return { title : 'Explore Marketplace' , subtitle : 'Discover top-tier professionals and specialized services.' , tabs : [ ] } ;
if ( key === 'verification' ) return { title : 'Verification Portal' , subtitle : 'Track verification progress, documents, and updates.' , tabs : [ 'approval status' , 'documents' , 'activity' ] } ;
if ( key === 'help center' || key === 'support' ) return { title : 'Help Center' , subtitle : 'Get help, manage tickets, and contact support.' , tabs : [ ] } ;
if ( key === 'settings' ) return { title : 'Account & Privacy Settings' , subtitle : 'Configure account security and privacy preferences.' , tabs : [ 'account' , 'privacy' , 'notifications' ] } ;
if ( key === 'switch role' || key === 'switch services' || key === 'switch service' ) return { title : 'Service Switcher Portal' , subtitle : 'Switch to approved services without logging out.' , tabs : [ 'available services' , 'pending approvals' , 'onboarding' ] } ;
if ( key === 'logout' ) return { title : 'Logout Confirmation' , subtitle : 'Confirm before ending your current session.' , tabs : [ 'confirm logout' , 'cancel' ] } ;
return { title : 'Service Seeker Dashboard' , subtitle : 'Preview service seeker dashboard flow.' , tabs : [ 'overview' ] } ;
}
const REQUIREMENT_ROWS = [
{
id : '#REQ-9012' ,
title : 'Wedding Shoot in Goa' ,
summary : 'Traditional & drone coverage' ,
category : 'PHOTOGRAPHER' ,
budget : '₹50,000 - ₹80,000' ,
location : 'Goa (On-site)' ,
submission : 'Oct 24, 2023' ,
status : 'approved' ,
responseTag : 'Active (5 Responses)' ,
} ,
{
id : '#REQ-8945' ,
title : 'E-commerce Portal Redesign' ,
summary : 'Next.js + Tailwind CSS specialist' ,
category : 'DEVELOPER' ,
budget : '₹1,20,000 - ₹2,00,000' ,
location : 'Remote' ,
submission : 'Nov 02, 2023' ,
status : 'under review' ,
responseTag : 'In Review' ,
} ,
{
id : '#REQ-8832' ,
title : 'Corporate Brand Identity' ,
summary : 'Logo, stationery & guidelines' ,
category : 'UI DESIGNER' ,
budget : '₹35,000 Flat' ,
location : 'Mumbai (Hybrid)' ,
submission : 'Oct 15, 2023' ,
status : 'rejected' ,
responseTag : 'Closed' ,
} ,
{
id : '#REQ-9108' ,
title : 'Home Fitness Program' ,
summary : '12-week transformation plan' ,
category : 'FITNESS TRAINER' ,
budget : '₹18,000 - ₹28,000' ,
location : 'Chennai (Online + Offline)' ,
submission : 'Nov 08, 2023' ,
status : 'active' ,
responseTag : 'New (3 Responses)' ,
} ,
{
id : '#REQ-9121' ,
title : 'Product Launch Promo Video' ,
summary : 'Scripted edit + motion graphics' ,
category : 'VIDEO EDITOR' ,
budget : '₹40,000 - ₹70,000' ,
location : 'Remote' ,
submission : 'Nov 10, 2023' ,
status : 'draft' ,
responseTag : 'Draft' ,
} ,
{
id : '#REQ-8660' ,
title : 'Weekly Maths Coaching' ,
summary : 'Class 10 board-focused tutoring' ,
category : 'TUTOR' ,
budget : '₹8,000 - ₹14,000' ,
location : 'Pune (Online)' ,
submission : 'Sep 28, 2023' ,
status : 'closed' ,
responseTag : 'Completed' ,
} ,
] ;
const REQUIREMENT_ROLE_OPTIONS = [
{ key : 'PHOTOGRAPHER' , title : 'Photographer' , desc : 'Capture moments with professional event, portrait, or commercial photography.' , icon : '/sidebar-icons/photographer.svg' } ,
{ key : 'MAKEUP_ARTIST' , title : 'Makeup Artist' , desc : 'Professional styling for weddings, film, or editorial shoots.' , icon : '/sidebar-icons/makeup-artist.svg' } ,
{ key : 'TUTOR' , title : 'Tutor' , desc : 'Expert guidance for academic subjects, languages, or specialized skills.' , icon : '/sidebar-icons/tutor.svg' } ,
{ key : 'DEVELOPER' , title : 'Developer' , desc : 'Build websites, mobile apps, or enterprise software solutions.' , icon : '/sidebar-icons/developers.svg' } ,
{ key : 'VIDEO_EDITOR' , title : 'Video Editor' , desc : 'Professional post-production for YouTube, film, or social media content.' , icon : '/sidebar-icons/report.svg' } ,
{ key : 'UGC_CONTENT_CREATOR' , title : 'UGC Content Creator' , desc : 'Create user-generated content for ads, social campaigns, and product promotions.' , icon : '/sidebar-icons/report.svg' } ,
{ key : 'FITNESS_TRAINER' , title : 'Fitness Trainer' , desc : 'Personalized workout plans and health coaching for your goals.' , icon : '/sidebar-icons/users.svg' } ,
{ key : 'CATERING_SERVICES' , title : 'Catering Services' , desc : 'High-quality food services for events, corporate parties, and weddings.' , icon : '/sidebar-icons/order.svg' } ,
{ key : 'GRAPHIC_DESIGNER' , title : 'Graphic Designer' , desc : 'Creative branding, logo design, and visual marketing assets.' , icon : '/sidebar-icons/designation.svg' } ,
{ key : 'SOCIAL_MEDIA_MANAGER' , title : 'Social Media Manager' , desc : 'Strategic growth and content management for your online presence.' , icon : '/sidebar-icons/leads.svg' } ,
] ;
const REQUIREMENT_ROLE_DETAILS : Record < string , { title : string ; subtitle : string ; fields : string [ ] ; toggles : string [ ] ; styles : string [ ] } > = {
PHOTOGRAPHER : {
title : 'Photography Details' ,
subtitle : 'Tell us more about the shoot to get accurate quotes.' ,
fields : [ 'Event Type' , 'Shoot Type' , 'Event Date & Time' , 'Event Duration (Hours)' , 'Venue / Location' , 'Number of People' , 'Delivery Deadline' ] ,
toggles : [ 'Outdoor Shoot' , 'Drone Required' , 'Physical Album' ] ,
styles : [ 'Black & White' , 'Vintage' , 'Cinematic' , 'Fine Art' ] ,
} ,
MAKEUP_ARTIST : {
title : 'Makeup Service Details' ,
subtitle : 'Share look preferences and event details for precise proposals.' ,
fields : [ 'Event Type' , 'Makeup Category' , 'Event Date & Time' , 'Artists Required' , 'Venue / Location' , 'Skin Tone Preference' , 'Ready By Time' ] ,
toggles : [ 'Trial Session Required' , 'Hair Styling Included' , 'Travel Included' ] ,
styles : [ 'Natural' , 'Glam' , 'HD' , 'Airbrush' ] ,
} ,
TUTOR : {
title : 'Tutoring Requirement Details' ,
subtitle : 'Specify class details so tutors can send the right plan.' ,
fields : [ 'Subject' , 'Class / Grade' , 'Mode (Online / Offline)' , 'Sessions Per Week' , 'Preferred Start Date' , 'Student Location' , 'Exam Goal' ] ,
toggles : [ '1-on-1 Sessions' , 'Homework Support' , 'Weekly Progress Reports' ] ,
styles : [ 'Concept Focused' , 'Exam Focused' , 'Crash Course' , 'Practice Heavy' ] ,
} ,
DEVELOPER : {
title : 'Development Requirement Details' ,
subtitle : 'Add technical scope details for better estimates.' ,
fields : [ 'Project Type' , 'Platform' , 'Preferred Stack' , 'Project Duration' , 'Launch Deadline' , 'Team Size Needed' , 'Support Duration' ] ,
toggles : [ 'UI/UX Included' , 'API Development' , 'Post-launch Maintenance' ] ,
styles : [ 'MVP' , 'Scale-ready' , 'Enterprise' , 'Performance-first' ] ,
} ,
VIDEO_EDITOR : {
title : 'Video Editing Details' ,
subtitle : 'Tell us the output style and timeline for quick delivery.' ,
fields : [ 'Video Category' , 'Final Duration' , 'Footage Volume' , 'Delivery Date' , 'Editing Style' , 'Platform' , 'Revision Rounds' ] ,
toggles : [ 'Color Grading' , 'Subtitles' , 'Motion Graphics' ] ,
styles : [ 'Cinematic' , 'Social Reels' , 'Corporate' , 'Documentary' ] ,
} ,
UGC_CONTENT_CREATOR : {
title : 'UGC Campaign Details' ,
subtitle : 'Share campaign objective and deliverables for accurate creator proposals.' ,
fields : [ 'Campaign Goal' , 'Platform' , 'Deliverables Needed' , 'Brand Category' , 'Delivery Deadline' , 'Target Audience' , 'Usage Rights Duration' ] ,
toggles : [ 'Script Support' , 'Voiceover Needed' , 'Product Shipping Required' ] ,
styles : [ 'Product Demo' , 'Lifestyle UGC' , 'Hook-first Ads' , 'Testimonial Style' ] ,
} ,
FITNESS_TRAINER : {
title : 'Fitness Coaching Details' ,
subtitle : 'Share your goals and routine to match with the right coach.' ,
fields : [ 'Primary Goal' , 'Current Activity Level' , 'Preferred Mode' , 'Training Days Per Week' , 'Preferred Timings' , 'Health Conditions' , 'Goal Timeline' ] ,
toggles : [ 'Nutrition Plan' , 'Home Equipment Plan' , 'Weekly Tracking' ] ,
styles : [ 'Weight Loss' , 'Strength' , 'Mobility' , 'Athletic' ] ,
} ,
CATERING_SERVICES : {
title : 'Catering Requirement Details' ,
subtitle : 'Provide event and menu details to receive accurate quotes.' ,
fields : [ 'Event Type' , 'Cuisine Preference' , 'Guest Count' , 'Service Date' , 'Venue / Location' , 'Meal Slot' , 'Serving Style' ] ,
toggles : [ 'Live Counters' , 'Dessert Station' , 'Serving Staff Included' ] ,
styles : [ 'South Indian' , 'North Indian' , 'Continental' , 'Fusion' ] ,
} ,
GRAPHIC_DESIGNER : {
title : 'Design Requirement Details' ,
subtitle : 'Define your creative brief for better design proposals.' ,
fields : [ 'Project Type' , 'Brand Industry' , 'Deliverables Needed' , 'Deadline' , 'Target Audience' , 'Reference Links' , 'Output Formats' ] ,
toggles : [ 'Brand Guidelines' , 'Source Files' , 'Print-ready Assets' ] ,
styles : [ 'Minimal' , 'Bold' , 'Luxury' , 'Playful' ] ,
} ,
SOCIAL_MEDIA_MANAGER : {
title : 'Social Media Requirement Details' ,
subtitle : 'Tell us campaign goals and channels for the best fit.' ,
fields : [ 'Primary Goal' , 'Platforms' , 'Posting Frequency' , 'Campaign Duration' , 'Start Date' , 'Brand Category' , 'Monthly Budget' ] ,
toggles : [ 'Reels Production' , 'Ad Management' , 'Community Management' ] ,
styles : [ 'Growth Focused' , 'Brand Awareness' , 'Lead Generation' , 'Content-first' ] ,
} ,
} ;
const RESPONSE_ROWS = [
{ name : 'Julianne Vance' , status : 'new' , quote : '₹12,86,500' } ,
{ name : 'Marcus Holloway' , status : 'shortlisted' , quote : '₹6,80,600' } ,
{ name : 'Sarah Jenkins' , status : 'rejected' , quote : '₹4,15,000' } ,
{ name : 'David Wu' , status : 'new' , quote : '₹8,30,000' } ,
] ;
const COMPANY_JOB_STEPS = [ 'Job Basics' , 'Role & Requirements' , 'Compensation & Location' , 'Screening' , 'Review & Checkout' ] ;
const COMPANY_JOB_SKILLS = [ 'Cloud Architecture' , 'Kubernetes' , 'Go/Rust' , 'PostgreSQL' , 'CI/CD' , 'System Design' ] ;
const LEAD_MARKETPLACE_TABS = [ 'All Leads' , 'Recommended' ] ;
type LeadCardStatus = 'open' | 'requested' | 'unlocked' | 'closed' ;
const INITIAL_PRO_LEAD_CARDS = [
{
id : 'LD-29831' ,
title : 'Luxury Wedding Coverage - ECR' ,
category : 'Wedding Photography' ,
location : 'Chennai, India' ,
area : 'ECR' ,
dateRequired : 'Apr 14, 2026' ,
urgency : 'High / Immediate' ,
budget : '₹2,40,000' ,
budgetValue : 240000 ,
priceRange : '₹2,00,000 - ₹2,60,000' ,
cost : 25 ,
status : 'open' as LeadCardStatus ,
match : '98% match' ,
contactCount : 7 ,
maxContacts : 10 ,
} ,
{
id : 'LD-29745' ,
title : 'Editorial Fashion Shoot - Studio Series' ,
category : 'Fashion / Editorial' ,
location : 'Chennai, India' ,
area : 'Nungambakkam' ,
dateRequired : 'Apr 22, 2026' ,
urgency : 'Medium' ,
budget : '₹1,10,000' ,
budgetValue : 110000 ,
priceRange : '₹90,000 - ₹1,30,000' ,
cost : 25 ,
status : 'requested' as LeadCardStatus ,
match : '92% match' ,
contactCount : 9 ,
maxContacts : 10 ,
} ,
{
id : 'LD-29612' ,
title : 'Corporate Branding Shoot - OMR Campus' ,
category : 'Commercial Architecture' ,
location : 'Chennai, India' ,
area : 'Sholinganallur' ,
dateRequired : 'May 05, 2026' ,
urgency : 'Low / Flexible' ,
budget : '₹1,85,000' ,
budgetValue : 185000 ,
priceRange : '₹1,50,000 - ₹2,10,000' ,
cost : 25 ,
status : 'unlocked' as LeadCardStatus ,
match : '85% match' ,
contactCount : 10 ,
maxContacts : 10 ,
} ,
{
id : 'LD-29588' ,
title : 'Temple Wedding Documentary' ,
category : 'Traditional Wedding' ,
location : 'Chennai, India' ,
area : 'Mylapore' ,
dateRequired : 'Apr 30, 2026' ,
urgency : 'High / Immediate' ,
budget : '₹95,000' ,
budgetValue : 95000 ,
priceRange : '₹70,000 - ₹1,10,000' ,
cost : 25 ,
status : 'open' as LeadCardStatus ,
match : '90% match' ,
contactCount : 4 ,
maxContacts : 10 ,
} ,
{
id : 'LD-29477' ,
title : 'Product Photography - Electronics' ,
category : 'E-commerce' ,
location : 'Chennai, India' ,
area : 'T Nagar' ,
dateRequired : 'May 12, 2026' ,
urgency : 'Medium' ,
budget : '₹75,000' ,
budgetValue : 75000 ,
priceRange : '₹50,000 - ₹90,000' ,
cost : 25 ,
status : 'open' as LeadCardStatus ,
match : '88% match' ,
contactCount : 6 ,
maxContacts : 10 ,
} ,
{
id : 'LD-29340' ,
title : 'Corporate Leadership Portraits' ,
category : 'Corporate Portrait' ,
location : 'Chennai, India' ,
area : 'Guindy' ,
dateRequired : 'May 22, 2026' ,
urgency : 'Low / Flexible' ,
budget : '₹1,35,000' ,
budgetValue : 135000 ,
priceRange : '₹1,00,000 - ₹1,50,000' ,
cost : 25 ,
status : 'closed' as LeadCardStatus ,
match : '84% match' ,
contactCount : 10 ,
maxContacts : 10 ,
} ,
] ;
const JOB_SEEKER_OPEN_JOBS = [
{ id : 'JOB-1001' , title : 'Senior UX/UI Architect' , company : 'Design System Global' , location : 'San Francisco, CA (Remote)' , salary : '$160k - $210k' , exp : '5-7 Years Exp.' , type : 'Full-time' , tags : [ 'FIGMA' , 'REACT' , 'TOKEN STUDIO' ] , match : '98% Match' , posted : 'Posted 2 hours ago' } ,
{ id : 'JOB-1002' , title : 'Engineering Manager (Cloud Infrastructure)' , company : 'FinStream Tech' , location : 'London, UK (Hybrid)' , salary : '£120k - £150k' , exp : '8+ Years Exp.' , type : 'Hybrid' , tags : [ 'AWS' , 'KUBERNETES' , 'TERRAFORM' ] , match : '92% Match' , posted : 'Posted 5 hours ago' } ,
{ id : 'JOB-1003' , title : 'Head of Talent Acquisition' , company : 'GreenGrowth HR' , location : 'Remote (North America)' , salary : '$130k - $180k' , exp : '10+ Years Exp.' , type : 'Worldwide' , tags : [ 'RECRUITING' , 'STRATEGIC HR' ] , match : '85% Match' , posted : 'Posted 1 day ago' } ,
{ id : 'JOB-1004' , title : 'Senior Data Scientist (LLM Focus)' , company : 'Aether Intelligence' , location : 'New York, NY' , salary : '$200k - $250k' , exp : '4+ Years Exp.' , type : 'Fast Hire' , tags : [ 'PYTHON' , 'PYTORCH' , 'TRANSFORMERS' ] , match : '95% Match' , posted : 'Posted 3 hours ago' } ,
] ;
type JobBoardCard = ( typeof JOB_SEEKER_OPEN_JOBS ) [ number ] ;
type CompanyJobSubmissionStatus = 'VERIFICATION_PENDING' | 'VERIFIED' | 'APPROVAL_PENDING' | 'APPROVED_LIVE' ;
type CompanyJobSubmission = {
id : string ;
job : JobBoardCard ;
status : CompanyJobSubmissionStatus ;
} ;
type CompanyJobDraft = {
title : string ;
company : string ;
location : string ;
salary : string ;
exp : string ;
type : string ;
tags : string [ ] ;
match : string ;
posted : string ;
department : string ;
openings : string ;
} ;
type RequirementRow = ( typeof REQUIREMENT_ROWS ) [ number ] ;
const DEFAULT_COMPANY_JOB_DRAFT : CompanyJobDraft = {
title : 'Senior Technical Architect' ,
company : 'Your Company' ,
location : 'Remote' ,
salary : '$80,000 - $120,000' ,
exp : '8+ Years Exp.' ,
type : 'Full-time, Permanent' ,
tags : COMPANY_JOB_SKILLS.slice ( 0 , 3 ) ,
match : 'New' ,
posted : 'Posted just now' ,
department : 'Engineering & Infrastructure' ,
openings : '1' ,
} ;
const JOB_SEEKER_APPLIED_ROWS = [
{ id : 'APP-NX-8293' , title : 'Senior Product Designer' , company : 'Stripe' , location : 'Remote, USA' , status : 'Shortlisted' , note : 'Recruiter viewed 2h ago' } ,
{ id : 'APP-NX-7741' , title : 'Staff UX Engineer' , company : 'Airbnb' , location : 'San Francisco, CA' , status : 'Under Review' , note : 'In initial screening' } ,
{ id : 'APP-NX-9011' , title : 'Product Marketing Lead' , company : 'Notion' , location : 'New York, NY' , status : 'Applied' , note : 'Successfully submitted' } ,
{ id : 'APP-NX-5529' , title : 'Growth Specialist' , company : 'Meta' , location : 'Remote' , status : 'Not Selected' , note : 'Closed on Oct 10' } ,
] ;
const HELP_CENTER_CATEGORIES = [
{ title : 'Account & Login' , description : 'Trouble logging in? Manage your password and account access.' , articles : 24 , icon : '/sidebar-icons/users.svg' } ,
{ title : 'Profile & Verification' , description : 'How to get verified and complete your profile faster.' , articles : 18 , icon : '/sidebar-icons/approval.svg' } ,
{ title : 'Jobs & Applications' , description : 'Find work and manage your active project applications.' , articles : 32 , icon : '/sidebar-icons/jobs.svg' } ,
{ title : 'Leads & Responses' , description : 'Learn how to respond to incoming business leads.' , articles : 15 , icon : '/sidebar-icons/leads.svg' } ,
{ title : 'Credits & Payments' , description : 'Invoices, purchasing credits, and billing history.' , articles : 21 , icon : '/sidebar-icons/credits.svg' } ,
{ title : 'Settings & Security' , description : 'Manage data, sessions, and notification preferences.' , articles : 12 , icon : '/sidebar-icons/role.svg' } ,
{ title : 'Service Switching' , description : 'How to switch between approved service profiles.' , articles : 9 , icon : '/sidebar-icons/role.svg' } ,
{ title : 'Support & Tickets' , description : 'Track your support history and ticket updates.' , articles : 14 , icon : '/sidebar-icons/support.svg' } ,
] ;
const HELP_CENTER_ARTICLES = [
'How to verify your business account' ,
'Setting up two-factor authentication' ,
'Understanding lead credit balances' ,
'Troubleshooting mobile notifications' ,
'Connecting your bank account' ,
] ;
const HELP_CENTER_FAQS = [
{
q : 'What are lead credits?' ,
a : 'Lead credits are the internal currency on Nxtgauge. You use them to respond to new inquiries and business opportunities.' ,
} ,
{
q : 'How long does verification take?' ,
a : 'Most verifications complete within 24 to 72 hours, depending on document quality and review queue.' ,
} ,
{
q : 'Can I change my account service?' ,
a : 'Yes. Use Switch Services once your additional service registration is approved.' ,
} ,
{
q : 'What is the refund policy for credits?' ,
a : 'Credits follow platform policy and may be eligible for adjustment when billing issues are validated.' ,
} ,
] ;
const HELP_TICKET_ROWS = [
{ id : 'TCK-1042' , title : 'Verification clarification required' , status : 'Open' , updated : '2h ago' , priority : 'High' , lastMessage : 'Please share GST certificate copy.' } ,
{ id : 'TCK-1031' , title : 'Unable to see credit invoice' , status : 'In Progress' , updated : 'Yesterday' , priority : 'Medium' , lastMessage : 'Invoice regenerated and shared via email.' } ,
{ id : 'TCK-1007' , title : 'Need onboarding status update' , status : 'Resolved' , updated : '3 days ago' , priority : 'Low' , lastMessage : 'Profile approved successfully.' } ,
] as const ;
const HELP_TICKET_DETAILS : Record <
string ,
{
userMessage : string ;
adminMessage : string ;
requestedDocuments : string [ ] ;
receivedFiles : Array < { file : string ; state : 'Received' | 'Pending Upload' } > ;
}
> = {
'TCK-1042' : {
userMessage : 'Can you confirm if any additional document is required from my side?' ,
adminMessage : 'Your verification is active. Please upload the pending documents listed below to avoid delays.' ,
requestedDocuments : [ 'GST Certificate' , 'Authorization Letter' , 'Business PAN Copy' ] ,
receivedFiles : [
{ file : 'gst_certificate.pdf' , state : 'Received' } ,
{ file : 'authorization_letter.jpg' , state : 'Received' } ,
{ file : 'business_pan.pdf' , state : 'Pending Upload' } ,
] ,
} ,
'TCK-1031' : {
userMessage : 'I cannot find my latest credit invoice in billing history.' ,
adminMessage : 'Invoice has been regenerated. Please verify if the attached copy is visible on your billing page now.' ,
requestedDocuments : [ 'Invoice Month Confirmation' ] ,
receivedFiles : [ { file : 'invoice_month_confirmation.txt' , state : 'Received' } ] ,
} ,
'TCK-1007' : {
userMessage : 'Can I get an update on my onboarding review status?' ,
adminMessage : 'Your onboarding has been approved. No further action is pending from your side.' ,
requestedDocuments : [ ] ,
receivedFiles : [ ] ,
} ,
} ;
function roleIcon ( roleKey : string ) {
const key = String ( roleKey || '' ) . toLowerCase ( ) ;
if ( key . includes ( 'photo' ) ) return Camera ;
if ( key . includes ( 'makeup' ) ) return Scissors ;
if ( key . includes ( 'tutor' ) ) return GraduationCap ;
if ( key . includes ( 'developer' ) ) return Code2 ;
if ( key . includes ( 'video' ) ) return Clapperboard ;
if ( key . includes ( 'ugc' ) || ( key . includes ( 'content' ) && key . includes ( 'creator' ) ) ) return Clapperboard ;
if ( key . includes ( 'graphic' ) ) return PenTool ;
if ( key . includes ( 'social' ) ) return Megaphone ;
if ( key . includes ( 'cater' ) ) return UtensilsCrossed ;
if ( key . includes ( 'fitness' ) ) return Dumbbell ;
return BriefcaseBusiness ;
}
function roleIconAsset ( roleKey : string ) {
const key = String ( roleKey || '' ) . toLowerCase ( ) ;
if ( key . includes ( 'photo' ) ) return '/sidebar-icons/photographer.svg' ;
if ( key . includes ( 'makeup' ) ) return '/sidebar-icons/makeup-artist.svg' ;
if ( key . includes ( 'tutor' ) ) return '/sidebar-icons/tutor.svg' ;
if ( key . includes ( 'developer' ) ) return '/sidebar-icons/developers.svg' ;
if ( key . includes ( 'video' ) ) return '' ;
if ( key . includes ( 'ugc' ) || ( key . includes ( 'content' ) && key . includes ( 'creator' ) ) ) return '/sidebar-icons/report.svg' ;
if ( key . includes ( 'graphic' ) ) return '/sidebar-icons/designation.svg' ;
if ( key . includes ( 'social' ) ) return '/sidebar-icons/leads.svg' ;
if ( key . includes ( 'cater' ) ) return '' ;
if ( key . includes ( 'fitness' ) ) return '/sidebar-icons/users.svg' ;
if ( key . includes ( 'company' ) ) return '/sidebar-icons/company.svg' ;
return '/sidebar-icons/role.svg' ;
}
export default function DashboardDesignPreview ( props : {
status : 'ACTIVE' | 'INACTIVE' ;
sidebarItems : string [ ] ;
activeSidebar : string ;
onSidebarSelect : ( item : string ) = > void ;
tabs : string [ ] ;
activeTab : string ;
onTabSelect : ( item : string ) = > void ;
widgets : string [ ] ;
fields : string [ ] ;
mode ? : 'default' | 'customer_external' ;
roleKey? : string ;
exploreRoles? : Array < { key : string ; name : string } > ;
onOpenFullscreen ? : ( ) = > void ;
hidePreviewHeader? : boolean ;
Wire user dashboard to real APIs — profile, credits, leads, requirements, jobs
- dashboard.tsx: fetch session for real user name/ID/role, fallback to
localStorage; show dashboard with role defaults when runtime config unavailable
- DashboardDesignPreview: add liveData prop; createResource for credits,
marketplace, lead requests, customer requirements, jobs, and profile
- Profile form: inputs now track state via profileFormData signal; pre-filled
from GET /api/${prefix}/profile/me; Save Changes PATCHes real endpoint
- Lead actions: Send Request POSTs to /api/${prefix}/leads/request; Cancel
DELETEs /api/${prefix}/leads/requests/{id}; both refetch after completion
- Requirement submit: POSTs to /api/customers/requirements then submits for approval
- Replace hardcoded "Alex" with real session name; credits from wallet balance API
- Fix launch.json PATH so npm is found in sh shell
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 21:13:54 +02:00
liveData ? : { userName : string ; userId : string ; rolePrefix : string } ;
2026-04-05 16:52:02 +02:00
} ) {
const isProfessionalRoleKey = ( roleKey : string ) = > {
const role = normalizeRoleKey ( roleKey ) ;
return role !== 'COMPANY' && role !== 'JOB_SEEKER' && role !== 'CUSTOMER' ;
} ;
const normalizeTabKey = ( value : string ) = > String ( value || '' ) . trim ( ) . toLowerCase ( ) ;
const isCustomerExternalMode = createMemo ( ( ) = > props . mode === 'customer_external' ) ;
const previewSidebarItems = createMemo ( ( ) = > ( props . sidebarItems . length ? props . sidebarItems : [ 'My Dashboard' , 'My Profile' , 'Switch Services' , 'Logout' ] ) ) ;
const customerView = createMemo ( ( ) = > customerViewFor ( props . activeSidebar || previewSidebarItems ( ) [ 0 ] || 'My Dashboard' , props . roleKey || '' ) ) ;
const currentProfileSpec = createMemo ( ( ) = > profileSpecForRole ( props . roleKey || '' ) ) ;
const isJobSeekerRole = createMemo ( ( ) = > normalizeRoleKey ( props . roleKey || '' ) === 'JOB_SEEKER' ) ;
const activeTabKey = createMemo ( ( ) = > normalizeTabKey ( props . activeTab ) ) ;
const previewTabs = createMemo ( ( ) = > {
if ( isCustomerExternalMode ( ) ) return customerView ( ) . tabs ;
return props . tabs . length ? props . tabs : [ 'overview' ] ;
} ) ;
const resolvedTabKey = createMemo ( ( ) = > {
const tabs = previewTabs ( ) ;
const key = activeTabKey ( ) ;
return tabs . some ( ( item ) = > normalizeTabKey ( item ) === key ) ? key : normalizeTabKey ( tabs [ 0 ] || '' ) ;
} ) ;
const previewWidgets = createMemo ( ( ) = > ( props . widgets . length ? props . widgets : [ 'total_requirements' , 'open' , 'closed' , 'responses' , 'saved_pros' ] ) ) ;
const previewFields = createMemo ( ( ) = > ( props . fields . length ? props . fields : [ 'full_name' , 'email' , 'verification_status' , 'approval_status' ] ) ) ;
const customerKey = createMemo ( ( ) = > String ( props . activeSidebar || '' ) . toLowerCase ( ) . trim ( ) ) ;
const portfolioJobsCompletedPreview = 1 ;
const portfolioFeedbackCountPreview = 0 ;
const portfolioTestimonialsUnlocked = createMemo ( ( ) = > portfolioJobsCompletedPreview >= 3 && portfolioFeedbackCountPreview >= 2 ) ;
const exploreRoleCards = createMemo ( ( ) = > {
const roles = Array . isArray ( props . exploreRoles ) ? props . exploreRoles : [ ] ;
const professionalOnly = roles
. filter ( ( r ) = > String ( r ? . key || '' ) . trim ( ) )
. filter ( ( r ) = > {
const roleKey = String ( r ? . key || '' ) . toLowerCase ( ) ;
return ! roleKey . includes ( 'company' )
&& ! roleKey . includes ( 'job_seeker' )
&& ! roleKey . includes ( 'jobseeker' )
&& ! roleKey . includes ( 'customer' )
&& ! roleKey . includes ( 'service_seeker' ) ;
} )
. map ( ( role ) = > {
const roleKey = String ( role . key || '' ) . trim ( ) ;
const title = String ( role . name || '' ) . trim ( ) || titleCase ( roleKey . replace ( /_/g , ' ' ) . toLowerCase ( ) ) ;
const isCurrent = normalizeRoleKey ( props . roleKey || '' ) === normalizeRoleKey ( roleKey ) ;
return {
key : roleKey ,
title ,
subtitle : ` Apply and onboard for ${ title } to unlock more opportunities on Nxtgauge. ` ,
status : isCurrent ? 'Registered' : 'Available' ,
action : isCurrent ? 'Active' : 'Register' ,
iconAsset : roleIconAsset ( roleKey ) ,
Icon : roleIcon ( roleKey ) ,
} ;
} ) ;
const defaults = [
{ key : 'PHOTOGRAPHER' , title : 'Photographer' , subtitle : 'Capture professional moments and monetize your creative vision.' , iconAsset : '/sidebar-icons/photographer.svg' , Icon : Camera } ,
{ key : 'MAKEUP_ARTIST' , title : 'Makeup Artist' , subtitle : 'Offer beauty services for events and premium clientele.' , iconAsset : '/sidebar-icons/makeup-artist.svg' , Icon : Scissors } ,
{ key : 'TUTOR' , title : 'Tutor' , subtitle : 'Share expertise through remote and flexible teaching.' , iconAsset : '/sidebar-icons/tutor.svg' , Icon : GraduationCap } ,
{ key : 'DEVELOPER' , title : 'Developer' , subtitle : 'Build scalable products for high-growth businesses.' , iconAsset : '/sidebar-icons/developers.svg' , Icon : Code2 } ,
{ key : 'VIDEO_EDITOR' , title : 'Video Editor' , subtitle : 'Craft compelling stories with world-class editing.' , iconAsset : '/sidebar-icons/report.svg' , Icon : Clapperboard } ,
{ key : 'UGC_CONTENT_CREATOR' , title : 'UGC Content Creator' , subtitle : 'Create ad-ready user-generated content for brand campaigns.' , iconAsset : '/sidebar-icons/report.svg' , Icon : Clapperboard } ,
{ key : 'GRAPHIC_DESIGNER' , title : 'Graphic Designer' , subtitle : 'Design visual systems that brands remember.' , iconAsset : '/sidebar-icons/designation.svg' , Icon : PenTool } ,
{ key : 'SOCIAL_MEDIA_MANAGER' , title : 'Social Media Manager' , subtitle : 'Manage digital presence and audience growth.' , iconAsset : '/sidebar-icons/leads.svg' , Icon : Megaphone } ,
{ key : 'FITNESS_TRAINER' , title : 'Fitness Trainer' , subtitle : 'Coach clients on health and performance goals.' , iconAsset : '/sidebar-icons/users.svg' , Icon : Dumbbell } ,
{ key : 'CATERING_SERVICES' , title : 'Catering Services' , subtitle : 'Deliver high-quality catering for events and celebrations.' , iconAsset : '/sidebar-icons/order.svg' , Icon : UtensilsCrossed } ,
] ;
const merged = [ . . . professionalOnly ] ;
defaults . forEach ( ( item ) = > {
if ( ! merged . some ( ( row ) = > normalizeRoleKey ( row . key ) === normalizeRoleKey ( item . key ) ) ) {
const isCurrent = normalizeRoleKey ( props . roleKey || '' ) === normalizeRoleKey ( item . key ) ;
merged . push ( { . . . item , status : isCurrent ? 'Registered' : 'Available' , action : isCurrent ? 'Active' : 'Register' } ) ;
}
} ) ;
return merged . slice ( 0 , 10 ) ;
} ) ;
const [ dashboardWidgetOrder , setDashboardWidgetOrder ] = createSignal < string [ ] > ( [ ] ) ;
const [ draggingDashboardWidget , setDraggingDashboardWidget ] = createSignal < string | null > ( null ) ;
const [ helpCenterTab , setHelpCenterTab ] = createSignal < 'help_center' | 'my_tickets' | 'create_ticket' > ( 'help_center' ) ;
const [ myTicketsTab , setMyTicketsTab ] = createSignal < 'all_tickets' | 'view_ticket' > ( 'all_tickets' ) ;
const [ activeTicketId , setActiveTicketId ] = createSignal < string > ( 'TCK-1042' ) ;
const [ openFaqIndex , setOpenFaqIndex ] = createSignal < number | null > ( 0 ) ;
const [ ticketMessage , setTicketMessage ] = createSignal ( '' ) ;
const [ createTicketFiles , setCreateTicketFiles ] = createSignal < string [ ] > ( [ ] ) ;
const [ viewTicketFiles , setViewTicketFiles ] = createSignal < string [ ] > ( [ ] ) ;
Wire user dashboard to real APIs — profile, credits, leads, requirements, jobs
- dashboard.tsx: fetch session for real user name/ID/role, fallback to
localStorage; show dashboard with role defaults when runtime config unavailable
- DashboardDesignPreview: add liveData prop; createResource for credits,
marketplace, lead requests, customer requirements, jobs, and profile
- Profile form: inputs now track state via profileFormData signal; pre-filled
from GET /api/${prefix}/profile/me; Save Changes PATCHes real endpoint
- Lead actions: Send Request POSTs to /api/${prefix}/leads/request; Cancel
DELETEs /api/${prefix}/leads/requests/{id}; both refetch after completion
- Requirement submit: POSTs to /api/customers/requirements then submits for approval
- Replace hardcoded "Alex" with real session name; credits from wallet balance API
- Fix launch.json PATH so npm is found in sh shell
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 21:13:54 +02:00
const [ profileFormData , setProfileFormData ] = createSignal < Record < string , string > > ( { } ) ;
const [ profileSaving , setProfileSaving ] = createSignal ( false ) ;
const [ profileSaveStatus , setProfileSaveStatus ] = createSignal < 'idle' | 'saved' | 'error' > ( 'idle' ) ;
2026-04-05 16:52:02 +02:00
const [ profileSettingsTab , setProfileSettingsTab ] = createSignal < 'change_password' | 'notifications' | 'privacy' > ( 'change_password' ) ;
const [ showDeleteAccountModal , setShowDeleteAccountModal ] = createSignal ( false ) ;
const [ portfolioEditMode , setPortfolioEditMode ] = createSignal ( false ) ;
const [ portfolioTopTab , setPortfolioTopTab ] = createSignal < 'my_portfolio' | 'preview' > ( 'my_portfolio' ) ;
const [ profileApprovalState , setProfileApprovalState ] = createSignal < 'DRAFT' | 'SUBMITTED' | 'IN_REVIEW' | 'DOCUMENTS_REQUESTED' | 'APPROVED' | 'REJECTED' > ( 'DRAFT' ) ;
const [ portfolioApprovalState , setPortfolioApprovalState ] = createSignal < 'DRAFT' | 'SUBMITTED' | 'IN_REVIEW' | 'DOCUMENTS_REQUESTED' | 'APPROVED' | 'REJECTED' > ( 'DRAFT' ) ;
const [ portfolioSpecialties , setPortfolioSpecialties ] = createSignal < string [ ] > ( [ ] ) ;
const [ portfolioLanguages , setPortfolioLanguages ] = createSignal < string [ ] > ( [ ] ) ;
const [ portfolioServiceAreas , setPortfolioServiceAreas ] = createSignal < string [ ] > ( [ ] ) ;
const [ portfolioSpecialtyInput , setPortfolioSpecialtyInput ] = createSignal ( '' ) ;
const [ portfolioLanguageInput , setPortfolioLanguageInput ] = createSignal ( '' ) ;
const [ portfolioAreaInput , setPortfolioAreaInput ] = createSignal ( '' ) ;
const [ profileDocumentType , setProfileDocumentType ] = createSignal ( 'Aadhar Card' ) ;
const [ portfolioFormValues , setPortfolioFormValues ] = createSignal < Record < string , string > > ( { } ) ;
const [ portfolioFormErrors , setPortfolioFormErrors ] = createSignal < Record < string , string > > ( { } ) ;
const [ portfolioValidationNotice , setPortfolioValidationNotice ] = createSignal ( '' ) ;
const [ portfolioServices , setPortfolioServices ] = createSignal < Array < { name : string ; model : string ; price : string ; details : string } > > ( [ ] ) ;
const [ portfolioServiceDraft , setPortfolioServiceDraft ] = createSignal < { name : string ; model : string ; price : string ; details : string } > ( { name : '' , model : 'Flat' , price : '' , details : '' } ) ;
const [ portfolioExperiences , setPortfolioExperiences ] = createSignal < Array < { year : string ; title : string ; details : string } > > ( [ ] ) ;
const [ portfolioExperienceDraft , setPortfolioExperienceDraft ] = createSignal < { year : string ; title : string ; details : string } > ( { year : '' , title : '' , details : '' } ) ;
const [ portfolioDesignTools , setPortfolioDesignTools ] = createSignal < string [ ] > ( [ ] ) ;
const [ portfolioToolInput , setPortfolioToolInput ] = createSignal ( '' ) ;
const [ portfolioPhotos , setPortfolioPhotos ] = createSignal < string [ ] > ( [ ] ) ;
const [ requirementsView , setRequirementsView ] = createSignal < 'list' | 'new' | 'detail' > ( 'list' ) ;
const [ requirementsStep , setRequirementsStep ] = createSignal ( 1 ) ;
const [ requirementRows , setRequirementRows ] = createSignal < RequirementRow [ ] > ( REQUIREMENT_ROWS ) ;
const [ selectedRequirementRole , setSelectedRequirementRole ] = createSignal ( 'PHOTOGRAPHER' ) ;
const [ selectedRequirementId , setSelectedRequirementId ] = createSignal ( '#REQ-9012' ) ;
const [ jobPostView , setJobPostView ] = createSignal < 'form' | 'review' | 'success' > ( 'form' ) ;
const [ jobPostStep , setJobPostStep ] = createSignal ( 1 ) ;
const [ jobBoardJobs , setJobBoardJobs ] = createSignal < JobBoardCard [ ] > ( JOB_SEEKER_OPEN_JOBS ) ;
const [ companyJobSubmissions , setCompanyJobSubmissions ] = createSignal < CompanyJobSubmission [ ] > ( [ ] ) ;
const [ companyJobDraft ] = createSignal < CompanyJobDraft > ( DEFAULT_COMPANY_JOB_DRAFT ) ;
const [ jobSeekerScreen , setJobSeekerScreen ] = createSignal < 'list' | 'detail' | 'apply' > ( 'list' ) ;
const [ jobSeekerSelectedId , setJobSeekerSelectedId ] = createSignal ( JOB_SEEKER_OPEN_JOBS [ 0 ] ? . id || '' ) ;
const [ jobSeekerApplyStep , setJobSeekerApplyStep ] = createSignal ( 2 ) ;
const [ lastJobSeekerTabKey , setLastJobSeekerTabKey ] = createSignal ( '' ) ;
const [ leadCards , setLeadCards ] = createSignal ( INITIAL_PRO_LEAD_CARDS ) ;
const [ leadMarketplaceTab , setLeadMarketplaceTab ] = createSignal ( 'All Leads' ) ;
const [ leadSearch , setLeadSearch ] = createSignal ( '' ) ;
const [ leadAreaFilter , setLeadAreaFilter ] = createSignal ( 'All Areas' ) ;
const [ leadBudgetFilter , setLeadBudgetFilter ] = createSignal ( 'All Budgets' ) ;
const [ leadDateFilter , setLeadDateFilter ] = createSignal ( 'Any Date' ) ;
const [ leadSortFilter , setLeadSortFilter ] = createSignal ( 'Newest First' ) ;
const [ leadFiltersOpen , setLeadFiltersOpen ] = createSignal ( false ) ;
const [ leadSortOpen , setLeadSortOpen ] = createSignal ( false ) ;
const [ leadPage , setLeadPage ] = createSignal ( 1 ) ;
const [ activeLeadDetailId , setActiveLeadDetailId ] = createSignal ( '' ) ;
const [ leadContactConfirmId , setLeadContactConfirmId ] = createSignal ( '' ) ;
const [ activeResponseLeadId , setActiveResponseLeadId ] = createSignal ( '' ) ;
const [ responsesDetailMode , setResponsesDetailMode ] = createSignal ( false ) ;
const [ requestedSearch , setRequestedSearch ] = createSignal ( '' ) ;
const [ requestedStatusFilter , setRequestedStatusFilter ] = createSignal ( 'All Status' ) ;
const [ requestedSortFilter , setRequestedSortFilter ] = createSignal ( 'Newest First' ) ;
const [ requestedSortOpen , setRequestedSortOpen ] = createSignal ( false ) ;
const [ requestedFilterOpen , setRequestedFilterOpen ] = createSignal ( false ) ;
const [ requestedPage , setRequestedPage ] = createSignal ( 1 ) ;
const [ leadCredits , setLeadCredits ] = createSignal ( 250 ) ;
const [ leadRequestRows , setLeadRequestRows ] = createSignal ( [
{ id : 'LD-29745' , title : 'Editorial Fashion Shoot - Studio Series' , city : 'Nungambakkam, Chennai' , requestDate : 'Apr 02, 2026' , status : 'request_sent' , decisionDate : '--' } ,
{ id : 'LD-29612' , title : 'Corporate Branding Shoot - OMR Campus' , city : 'Sholinganallur, Chennai' , requestDate : 'Apr 01, 2026' , status : 'contact_unlocked' , decisionDate : 'Apr 01, 2026' } ,
{ id : 'LD-29588' , title : 'Temple Wedding Documentary' , city : 'Mylapore, Chennai' , requestDate : 'Mar 30, 2026' , status : 'rejected' , decisionDate : 'Mar 30, 2026' } ,
] as Array < { id : string ; title : string ; city : string ; requestDate : string ; status : 'request_sent' | 'approved' | 'contact_unlocked' | 'rejected' | 'expired_refunded' | 'cancelled_by_professional' ; decisionDate : string } > ) ;
const [ lastSidebarKey , setLastSidebarKey ] = createSignal ( '' ) ;
const [ profileSettingToggles , setProfileSettingToggles ] = createSignal < Record < string , boolean > > ( {
email_updates : true ,
in_app_alerts : true ,
verification_reminders : true ,
profile_visibility : true ,
data_sharing_consent : false ,
} ) ;
const toggleProfileSetting = ( key : string ) = > {
setProfileSettingToggles ( ( prev ) = > ( { . . . prev , [ key ] : ! prev [ key ] } ) ) ;
} ;
const submitCompanyJobForReview = ( ) = > {
const draft = companyJobDraft ( ) ;
const id = ` JOB-COMP- ${ Date . now ( ) } ` ;
const submittedJob : JobBoardCard = {
id ,
title : draft.title ,
company : draft.company ,
location : draft.location ,
salary : draft.salary ,
exp : draft.exp ,
type : draft . type ,
tags : draft.tags ,
match : draft.match ,
posted : draft.posted ,
} ;
setCompanyJobSubmissions ( ( prev ) = > [ { id , job : submittedJob , status : 'VERIFICATION_PENDING' } , . . . prev ] ) ;
setTimeout ( ( ) = > {
setCompanyJobSubmissions ( ( prev ) = > prev . map ( ( row ) = > ( row . id === id ? { . . . row , status : 'VERIFIED' } : row ) ) ) ;
setTimeout ( ( ) = > {
setCompanyJobSubmissions ( ( prev ) = > prev . map ( ( row ) = > ( row . id === id ? { . . . row , status : 'APPROVAL_PENDING' } : row ) ) ) ;
setTimeout ( ( ) = > {
setCompanyJobSubmissions ( ( prev ) = > prev . map ( ( row ) = > ( row . id === id ? { . . . row , status : 'APPROVED_LIVE' } : row ) ) ) ;
setJobBoardJobs ( ( prev ) = > [ submittedJob , . . . prev . filter ( ( job ) = > job . id !== id ) ] ) ;
setJobSeekerSelectedId ( id ) ;
} , 650 ) ;
} , 650 ) ;
} , 650 ) ;
} ;
const submitRequirementForReview = ( ) = > {
const roleKey = selectedRequirementRole ( ) ;
const roleLabel = titleCase ( roleKey . replace ( /_/g , ' ' ) . toLowerCase ( ) ) ;
Wire user dashboard to real APIs — profile, credits, leads, requirements, jobs
- dashboard.tsx: fetch session for real user name/ID/role, fallback to
localStorage; show dashboard with role defaults when runtime config unavailable
- DashboardDesignPreview: add liveData prop; createResource for credits,
marketplace, lead requests, customer requirements, jobs, and profile
- Profile form: inputs now track state via profileFormData signal; pre-filled
from GET /api/${prefix}/profile/me; Save Changes PATCHes real endpoint
- Lead actions: Send Request POSTs to /api/${prefix}/leads/request; Cancel
DELETEs /api/${prefix}/leads/requests/{id}; both refetch after completion
- Requirement submit: POSTs to /api/customers/requirements then submits for approval
- Replace hardcoded "Alex" with real session name; credits from wallet balance API
- Fix launch.json PATH so npm is found in sh shell
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 21:13:54 +02:00
if ( hasLive ( ) ) {
apiPost ( '/api/customers/requirements' , {
profession_key : roleKey ,
title : ` ${ roleLabel } Requirement ` ,
description : 'Submitted via dashboard' ,
location : 'Chennai' ,
budget_min : 10000000 ,
budget_max : 20000000 ,
} ) . then ( ( res : any ) = > res ? . json ? . ( ) . then ( ( r : any ) = > {
// After creating, submit for approval
if ( r ? . id ) apiPost ( ` /api/customers/requirements/ ${ r . id } /submit ` , { } ) ;
} ) ) . then ( ( ) = > refetchRequirementsLive ( ) ) ;
}
2026-04-05 16:52:02 +02:00
const id = ` #REQ- ${ Math . floor ( 9200 + Math . random ( ) * 899 ) } ` ;
const submission = new Date ( ) . toLocaleDateString ( 'en-US' , { month : 'short' , day : '2-digit' , year : 'numeric' } ) ;
const newRequirement : RequirementRow = {
id ,
title : ` ${ roleLabel } Requirement ` ,
summary : 'Submitted from customer requirement form' ,
category : roleKey ,
budget : '₹1,50,000 - ₹2,00,000' ,
location : 'Chennai (On-site)' ,
submission ,
status : 'under review' ,
responseTag : 'Verification Pending' ,
} ;
setRequirementRows ( ( prev ) = > [ newRequirement , . . . prev ] ) ;
setSelectedRequirementId ( id ) ;
setRequirementsView ( 'list' ) ;
setRequirementsStep ( 1 ) ;
setTimeout ( ( ) = > {
setRequirementRows ( ( prev ) = > prev . map ( ( row ) = > ( row . id === id ? { . . . row , status : 'approved' , responseTag : 'Approval Pending' } : row ) ) ) ;
setTimeout ( ( ) = > {
setRequirementRows ( ( prev ) = > prev . map ( ( row ) = > ( row . id === id ? { . . . row , status : 'active' , responseTag : 'Active (0 Responses)' } : row ) ) ) ;
setLeadCards ( ( prev ) = > {
const leadId = id . replace ( '#REQ' , 'LD' ) ;
if ( prev . some ( ( card ) = > card . id === leadId ) ) return prev ;
return [
{
id : leadId ,
title : newRequirement.title ,
category : roleLabel ,
location : 'Chennai, India' ,
area : 'Chennai' ,
dateRequired : 'May 30, 2026' ,
urgency : 'Medium' ,
budget : '₹1,50,000' ,
budgetValue : 150000 ,
priceRange : newRequirement.budget ,
cost : 25 ,
status : 'open' ,
match : '90% match' ,
contactCount : 0 ,
maxContacts : 10 ,
} ,
. . . prev ,
] ;
} ) ;
} , 650 ) ;
} , 650 ) ;
} ;
const isProfessionalRole = createMemo ( ( ) = > isProfessionalRoleKey ( props . roleKey || '' ) ) ;
const bothApprovalsApproved = createMemo ( ( ) = > {
if ( ! isProfessionalRole ( ) ) return profileApprovalState ( ) === 'APPROVED' ;
return profileApprovalState ( ) === 'APPROVED' && portfolioApprovalState ( ) === 'APPROVED' ;
} ) ;
const approvalTone = ( state : string ) = > {
if ( state === 'APPROVED' ) return { border : '#E5E7EB' , bg : '#F9FAFB' , text : '#374151' , label : 'Approved' } ;
if ( state === 'IN_REVIEW' || state === 'SUBMITTED' ) return { border : '#E5E7EB' , bg : '#F9FAFB' , text : '#374151' , label : 'In Review' } ;
if ( state === 'DOCUMENTS_REQUESTED' ) return { border : '#E5E7EB' , bg : '#F9FAFB' , text : '#374151' , label : 'Documents Requested' } ;
if ( state === 'REJECTED' ) return { border : '#E5E7EB' , bg : '#F9FAFB' , text : '#374151' , label : 'Rejected' } ;
return { border : '#E5E7EB' , bg : '#F9FAFB' , text : '#374151' , label : 'Draft' } ;
} ;
Wire user dashboard to real APIs — profile, credits, leads, requirements, jobs
- dashboard.tsx: fetch session for real user name/ID/role, fallback to
localStorage; show dashboard with role defaults when runtime config unavailable
- DashboardDesignPreview: add liveData prop; createResource for credits,
marketplace, lead requests, customer requirements, jobs, and profile
- Profile form: inputs now track state via profileFormData signal; pre-filled
from GET /api/${prefix}/profile/me; Save Changes PATCHes real endpoint
- Lead actions: Send Request POSTs to /api/${prefix}/leads/request; Cancel
DELETEs /api/${prefix}/leads/requests/{id}; both refetch after completion
- Requirement submit: POSTs to /api/customers/requirements then submits for approval
- Replace hardcoded "Alex" with real session name; credits from wallet balance API
- Fix launch.json PATH so npm is found in sh shell
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 21:13:54 +02:00
// ─── Live API integration (customer_external mode with liveData) ──────────
const hasLive = ( ) = > isCustomerExternalMode ( ) && ! ! props . liveData ;
const livePrefix = ( ) = > props . liveData ? . rolePrefix ? ? '' ;
const GW = '/api/gateway' ;
const apiFetch = ( path : string ) = >
fetch ( ` ${ GW } ${ path } ` , { credentials : 'include' } )
. then ( ( r ) = > ( r . ok ? r . json ( ) : null ) )
. catch ( ( ) = > null ) ;
const apiPost = ( path : string , body : unknown ) = >
fetch ( ` ${ GW } ${ path } ` , {
method : 'POST' ,
credentials : 'include' ,
headers : { 'Content-Type' : 'application/json' } ,
body : JSON.stringify ( body ) ,
} ) . catch ( ( ) = > null ) ;
const apiDelete = ( path : string ) = >
fetch ( ` ${ GW } ${ path } ` , { method : 'DELETE' , credentials : 'include' } ) . catch ( ( ) = > null ) ;
// Credits balance
const [ creditsResource ] = createResource (
( ) = > ( hasLive ( ) ? livePrefix ( ) : null ) ,
( prefix ) = > apiFetch ( ` /api/ ${ prefix } /wallet/balance ` ) ,
) ;
// Marketplace requirements (professionals)
const [ marketplaceResource ] = createResource (
( ) = > ( hasLive ( ) && isProfessionalRole ( ) ? livePrefix ( ) : null ) ,
( prefix ) = > apiFetch ( ` /api/ ${ prefix } /marketplace?limit=50 ` ) ,
) ;
// My lead requests (professionals)
const [ leadRequestsResource , { refetch : refetchLeadRequestsLive } ] = createResource (
( ) = > ( hasLive ( ) && isProfessionalRole ( ) ? livePrefix ( ) : null ) ,
( prefix ) = > apiFetch ( ` /api/ ${ prefix } /leads/requests/me?limit=50 ` ) ,
) ;
// Customer requirements
const [ requirementsResource , { refetch : refetchRequirementsLive } ] = createResource (
( ) = > ( hasLive ( ) && normalizeRoleKey ( props . roleKey ? ? '' ) === 'CUSTOMER' ? 'yes' : null ) ,
( ) = > apiFetch ( '/api/customers/requirements?limit=50' ) ,
) ;
// Jobs board
const [ jobsResource ] = createResource (
( ) = > {
const r = normalizeRoleKey ( props . roleKey ? ? '' ) ;
return hasLive ( ) && ( r === 'JOB_SEEKER' || r === 'COMPANY' ) ? r : null ;
} ,
( ) = > apiFetch ( '/api/jobs?limit=50' ) ,
) ;
// User profile (all roles)
const [ profileResource ] = createResource (
( ) = > ( hasLive ( ) ? livePrefix ( ) : null ) ,
( prefix ) = > apiFetch ( ` /api/ ${ prefix } /profile/me ` ) ,
) ;
// Sync resources → local signals
createEffect ( ( ) = > {
const d = creditsResource ( ) ;
if ( d != null && typeof d . balance === 'number' ) setLeadCredits ( d . balance ) ;
} ) ;
createEffect ( ( ) = > {
const d = marketplaceResource ( ) ;
if ( ! d ) return ;
const items : any [ ] = Array . isArray ( d . items ) ? d.items : Array.isArray ( d ) ? d : [ ] ;
if ( ! items . length ) return ;
setLeadCards ( items . map ( ( item : any ) = > ( {
id : String ( item . id ? ? item . requirement_id ? ? '' ) ,
title : String ( item . title ? ? item . description ? ? 'Requirement' ) ,
category : String ( item . category ? ? item . profession_key ? ? props . roleKey ? ? '' ) ,
location : String ( item . location ? ? item . city ? ? 'India' ) ,
area : String ( item . area ? ? item . locality ? ? '' ) ,
dateRequired : item.required_by
? new Date ( item . required_by ) . toLocaleDateString ( 'en-US' , { month : 'short' , day : '2-digit' , year : 'numeric' } )
: 'TBD' ,
urgency : item.urgency === 'HIGH' ? 'High' : item . urgency === 'MEDIUM' ? 'Medium' : 'Low' ,
budget : item.budget_min != null
? ` ₹ ${ Math . round ( item . budget_min / 100 ) . toLocaleString ( 'en-IN' ) } - ₹ ${ Math . round ( ( item . budget_max ? ? item . budget_min ) / 100 ) . toLocaleString ( 'en-IN' ) } `
: '₹0' ,
budgetValue : Number ( item . budget_max ? ? item . budget_min ? ? 0 ) / 100 ,
priceRange : item.budget_min != null
? ` ₹ ${ Math . round ( item . budget_min / 100 ) . toLocaleString ( 'en-IN' ) } - ₹ ${ Math . round ( ( item . budget_max ? ? item . budget_min ) / 100 ) . toLocaleString ( 'en-IN' ) } `
: '₹0' ,
cost : 25 ,
status : 'open' as const ,
match : '80% match' ,
contactCount : Number ( item . contact_count ? ? 0 ) ,
maxContacts : Number ( item . max_contacts ? ? 10 ) ,
} ) ) ) ;
} ) ;
createEffect ( ( ) = > {
const d = leadRequestsResource ( ) ;
if ( ! d ) return ;
const items : any [ ] = Array . isArray ( d . items ) ? d.items : Array.isArray ( d ) ? d : [ ] ;
if ( ! items . length ) return ;
const statusMap : Record < string , any > = {
PENDING : 'request_sent' , CONTACT_UNLOCKED : 'contact_unlocked' ,
REJECTED : 'rejected' , CANCELLED : 'cancelled_by_professional' , EXPIRED : 'expired_refunded' ,
} ;
setLeadRequestRows ( items . map ( ( item : any ) = > ( {
id : String ( item . id ? ? '' ) ,
title : String ( item . title ? ? item . requirement_title ? ? 'Lead Request' ) ,
city : String ( item . location ? ? item . city ? ? 'India' ) ,
requestDate : item.created_at
? new Date ( item . created_at ) . toLocaleDateString ( 'en-US' , { month : 'short' , day : '2-digit' , year : 'numeric' } )
: '--' ,
status : statusMap [ String ( item . status ? ? '' ) ] ? ? 'request_sent' ,
decisionDate : item.decision_date
? new Date ( item . decision_date ) . toLocaleDateString ( 'en-US' , { month : 'short' , day : '2-digit' , year : 'numeric' } )
: '--' ,
} ) ) ) ;
} ) ;
createEffect ( ( ) = > {
const d = requirementsResource ( ) ;
if ( ! d ) return ;
const items : any [ ] = Array . isArray ( d . items ) ? d.items : Array.isArray ( d ) ? d : [ ] ;
if ( ! items . length ) return ;
const statusMap : Record < string , string > = {
PENDING : 'under review' , APPROVED : 'approved' , ACTIVE : 'active' ,
REJECTED : 'closed' , CLOSED : 'closed' ,
} ;
setRequirementRows ( items . map ( ( item : any ) = > ( {
id : String ( item . id ? ? '' ) ,
title : String ( item . title ? ? item . description ? ? 'Requirement' ) ,
summary : String ( item . description ? ? '' ) ,
category : String ( item . category ? ? item . profession_key ? ? '' ) ,
budget : item.budget_min != null
? ` ₹ ${ Math . round ( item . budget_min / 100 ) . toLocaleString ( 'en-IN' ) } - ₹ ${ Math . round ( ( item . budget_max ? ? item . budget_min ) / 100 ) . toLocaleString ( 'en-IN' ) } `
: '₹0' ,
location : String ( item . location ? ? 'India' ) ,
submission : item.created_at
? new Date ( item . created_at ) . toLocaleDateString ( 'en-US' , { month : 'short' , day : '2-digit' , year : 'numeric' } )
: '--' ,
status : statusMap [ String ( item . status ? ? '' ) ] ? ? 'under review' ,
responseTag : ` Active ( ${ Number ( item . response_count ? ? 0 ) } Responses) ` ,
} ) ) ) ;
} ) ;
createEffect ( ( ) = > {
const d = jobsResource ( ) ;
if ( ! d ) return ;
const items : any [ ] = Array . isArray ( d . items ) ? d.items : Array.isArray ( d ) ? d : [ ] ;
if ( ! items . length ) return ;
setJobBoardJobs ( items . map ( ( item : any ) = > ( {
id : String ( item . id ? ? '' ) ,
title : String ( item . title ? ? 'Position' ) ,
company : String ( item . company_name ? ? item . company ? ? 'Company' ) ,
location : String ( item . location ? ? 'India' ) ,
salary : item.salary_min != null
? ` ₹ ${ Math . round ( item . salary_min / 100 ) . toLocaleString ( 'en-IN' ) } + `
: 'Negotiable' ,
exp : String ( item . experience_required ? ? item . experience ? ? '0-2 yrs' ) ,
type : String ( item . employment_type ? ? item . type ? ? 'Full-Time' ) ,
tags : Array.isArray ( item . tags ) ? item . tags : [ ] ,
match : '' ,
posted : item.created_at
? new Date ( item . created_at ) . toLocaleDateString ( 'en-US' , { month : 'short' , day : 'numeric' } )
: '--' ,
} ) ) ) ;
} ) ;
createEffect ( ( ) = > {
const d = profileResource ( ) ;
if ( ! d ) return ;
const parts = String ( d . display_name || d . full_name || d . business_name || '' ) . split ( ' ' ) ;
const map : Record < string , string > = { } ;
map [ 'First Name' ] = d . first_name || parts [ 0 ] || '' ;
map [ 'Last Name' ] = d . last_name || parts . slice ( 1 ) . join ( ' ' ) || '' ;
map [ 'Business Name' ] = d . business_name || d . display_name || '' ;
map [ 'Contact Person Name' ] = d . contact_person || d . display_name || '' ;
map [ 'Company Name' ] = d . company_name || d . display_name || '' ;
map [ 'Email Address' ] = d . email || '' ;
map [ 'Mobile Number' ] = d . phone || d . mobile || '' ;
if ( d . location ) map [ 'City' ] = d . location ;
if ( d . area ) map [ 'Area' ] = d . area ;
if ( d . state ) map [ 'State' ] = d . state ;
if ( d . pin_code ) map [ 'PIN Code' ] = d . pin_code ;
if ( d . bio ) map [ 'Bio' ] = d . bio ;
if ( d . status ) setProfileApprovalState (
d . status === 'APPROVED' ? 'APPROVED'
: d . status === 'REJECTED' ? 'REJECTED'
: d . status === 'PENDING' ? 'IN_REVIEW'
: 'DRAFT' ,
) ;
setProfileFormData ( ( prev ) = > ( { . . . prev , . . . map } ) ) ;
} ) ;
// ─── End live API integration ─────────────────────────────────────────────
2026-04-05 16:52:02 +02:00
const submitProfileForApproval = ( ) = > {
setProfileApprovalState ( 'SUBMITTED' ) ;
setTimeout ( ( ) = > setProfileApprovalState ( 'IN_REVIEW' ) , 250 ) ;
} ;
const submitPortfolioForApproval = ( ) = > {
setPortfolioApprovalState ( 'SUBMITTED' ) ;
setTimeout ( ( ) = > setPortfolioApprovalState ( 'IN_REVIEW' ) , 250 ) ;
} ;
createEffect ( ( ) = > {
const roleKey = normalizeRoleKey ( props . roleKey || '' ) ;
const spec = portfolioSpecForRole ( roleKey ) ;
setPortfolioSpecialties ( spec . specialties . slice ( 0 , 6 ) ) ;
setPortfolioLanguages ( [ 'English' , 'Hindi' , 'Tamil' ] ) ;
setPortfolioServiceAreas ( [ 'T. Nagar' , 'Adyar' , 'Velachery' , 'Anna Nagar' ] ) ;
setPortfolioSpecialtyInput ( '' ) ;
setPortfolioLanguageInput ( '' ) ;
setPortfolioAreaInput ( '' ) ;
setPortfolioFormValues ( { } ) ;
setPortfolioFormErrors ( { } ) ;
setPortfolioValidationNotice ( '' ) ;
setPortfolioServices ( [ { name : 'Core Service' , model : 'Flat' , price : '₹15,000' , details : 'Includes planning and delivery' } ] ) ;
setPortfolioServiceDraft ( { name : '' , model : 'Flat' , price : '' , details : '' } ) ;
setPortfolioExperiences ( [ { year : '2022' , title : 'Started professional practice' , details : 'Handled 40+ projects successfully' } ] ) ;
setPortfolioExperienceDraft ( { year : '' , title : '' , details : '' } ) ;
setPortfolioToolInput ( '' ) ;
setPortfolioDesignTools ( [ 'Figma' , 'Adobe Photoshop' , 'Illustrator' ] ) ;
setPortfolioPhotos ( [ 'sample-1.jpg' , 'sample-2.jpg' ] ) ;
} ) ;
const addPortfolioTag = (
value : string ,
setter : ( next : ( prev : string [ ] ) = > string [ ] ) = > void ,
max = 6 ,
) = > {
const normalized = String ( value || '' ) . trim ( ) ;
if ( ! normalized ) return false ;
let added = false ;
setter ( ( prev ) = > {
if ( prev . some ( ( item ) = > item . toLowerCase ( ) === normalized . toLowerCase ( ) ) || prev . length >= max ) return prev ;
added = true ;
return [ . . . prev , normalized ] ;
} ) ;
return added ;
} ;
const removePortfolioTag = (
value : string ,
setter : ( next : ( prev : string [ ] ) = > string [ ] ) = > void ,
) = > setter ( ( prev ) = > prev . filter ( ( item ) = > item !== value ) ) ;
const selectedTicket = createMemo ( ( ) = > HELP_TICKET_ROWS . find ( ( row ) = > row . id === activeTicketId ( ) ) || HELP_TICKET_ROWS [ 0 ] ) ;
const selectedTicketDetails = createMemo ( ( ) = > HELP_TICKET_DETAILS [ selectedTicket ( ) . id ] || HELP_TICKET_DETAILS [ 'TCK-1042' ] ) ;
const leadCostPerContact = 25 ;
const leadsPerPage = 3 ;
const requestedPerPage = 5 ;
const lockedLeadCredits = createMemo ( ( ) = > leadCards ( ) . filter ( ( card ) = > card . status === 'requested' ) . length * leadCostPerContact ) ;
const usableLeadCredits = createMemo ( ( ) = > Math . max ( 0 , leadCredits ( ) - lockedLeadCredits ( ) ) ) ;
const filteredLeadCards = createMemo ( ( ) = > {
const query = leadSearch ( ) . trim ( ) . toLowerCase ( ) ;
const area = leadAreaFilter ( ) ;
const budget = leadBudgetFilter ( ) ;
const date = leadDateFilter ( ) ;
const list = leadCards ( ) . filter ( ( lead ) = > {
if ( lead . status !== 'open' ) return false ;
const matchesQuery = ! query
|| lead . title . toLowerCase ( ) . includes ( query )
|| lead . category . toLowerCase ( ) . includes ( query )
|| lead . location . toLowerCase ( ) . includes ( query )
|| String ( lead . area || '' ) . toLowerCase ( ) . includes ( query ) ;
if ( ! matchesQuery ) return false ;
if ( area !== 'All Areas' && String ( lead . area || '' ) !== area ) return false ;
if ( date === 'Within 7 Days' ) {
const diff = ( Date . parse ( lead . dateRequired ) - Date . now ( ) ) / ( 1000 * 60 * 60 * 24 ) ;
if ( ! ( diff >= 0 && diff <= 7 ) ) return false ;
}
if ( date === 'This Month' ) {
const value = new Date ( lead . dateRequired ) ;
const now = new Date ( ) ;
if ( ! ( value . getMonth ( ) === now . getMonth ( ) && value . getFullYear ( ) === now . getFullYear ( ) ) ) return false ;
}
if ( budget === 'Under ₹1L' && lead . budgetValue >= 100000 ) return false ;
if ( budget === '₹1L - ₹2L' && ! ( lead . budgetValue >= 100000 && lead . budgetValue <= 200000 ) ) return false ;
if ( budget === 'Above ₹2L' && lead . budgetValue <= 200000 ) return false ;
return true ;
} ) ;
const sorted = [ . . . list ] ;
if ( leadSortFilter ( ) === 'Budget High-Low' ) sorted . sort ( ( a , b ) = > b . budgetValue - a . budgetValue ) ;
if ( leadSortFilter ( ) === 'Budget Low-High' ) sorted . sort ( ( a , b ) = > a . budgetValue - b . budgetValue ) ;
if ( leadSortFilter ( ) === 'Newest First' ) sorted . sort ( ( a , b ) = > Date . parse ( b . dateRequired ) - Date . parse ( a . dateRequired ) ) ;
return sorted ;
} ) ;
const totalLeadPages = createMemo ( ( ) = > Math . max ( 1 , Math . ceil ( filteredLeadCards ( ) . length / leadsPerPage ) ) ) ;
const pagedLeadCards = createMemo ( ( ) = > {
const start = ( leadPage ( ) - 1 ) * leadsPerPage ;
return filteredLeadCards ( ) . slice ( start , start + leadsPerPage ) ;
} ) ;
const filteredRequestedRows = createMemo ( ( ) = > {
const query = requestedSearch ( ) . trim ( ) . toLowerCase ( ) ;
const status = requestedStatusFilter ( ) ;
const sorted = leadRequestRows ( ) . filter ( ( row ) = > {
if ( status !== 'All Status' && status !== titleCase ( row . status . replace ( /_/g , ' ' ) ) ) return false ;
if ( ! query ) return true ;
return row . id . toLowerCase ( ) . includes ( query ) || row . title . toLowerCase ( ) . includes ( query ) || row . city . toLowerCase ( ) . includes ( query ) ;
} ) ;
if ( requestedSortFilter ( ) === 'Oldest First' ) {
sorted . sort ( ( a , b ) = > Date . parse ( a . requestDate ) - Date . parse ( b . requestDate ) ) ;
} else {
sorted . sort ( ( a , b ) = > Date . parse ( b . requestDate ) - Date . parse ( a . requestDate ) ) ;
}
return sorted ;
} ) ;
const totalRequestedPages = createMemo ( ( ) = > Math . max ( 1 , Math . ceil ( filteredRequestedRows ( ) . length / requestedPerPage ) ) ) ;
const pagedRequestedRows = createMemo ( ( ) = > {
const start = ( requestedPage ( ) - 1 ) * requestedPerPage ;
return filteredRequestedRows ( ) . slice ( start , start + requestedPerPage ) ;
} ) ;
const ticketSummary = createMemo ( ( ) = > {
const openCount = HELP_TICKET_ROWS . filter ( ( row ) = > row . status !== 'Resolved' ) . length ;
const resolvedCount = HELP_TICKET_ROWS . length - openCount ;
return { openCount , resolvedCount , total : HELP_TICKET_ROWS.length } ;
} ) ;
const openTicketDetails = ( ticketId : string ) = > {
setHelpCenterTab ( 'my_tickets' ) ;
setActiveTicketId ( ticketId ) ;
setMyTicketsTab ( 'view_ticket' ) ;
setTicketMessage ( '' ) ;
setViewTicketFiles ( [ ] ) ;
} ;
createEffect ( ( ) = > {
const key = customerKey ( ) ;
if ( key !== lastSidebarKey ( ) ) {
setLastSidebarKey ( key ) ;
if ( key === 'credits' ) props . onTabSelect ( 'overview' ) ;
}
} ) ;
createEffect ( ( ) = > {
setDashboardWidgetOrder ( previewWidgets ( ) ) ;
} ) ;
createEffect ( ( ) = > {
if ( customerKey ( ) !== 'help center' && customerKey ( ) !== 'support' ) setHelpCenterTab ( 'help_center' ) ;
} ) ;
createEffect ( ( ) = > {
if ( customerKey ( ) !== 'my requirements' ) {
setRequirementsView ( 'list' ) ;
setRequirementsStep ( 1 ) ;
}
} ) ;
createEffect ( ( ) = > {
if ( customerKey ( ) !== 'leads' ) {
setLeadMarketplaceTab ( 'All Leads' ) ;
setLeadSearch ( '' ) ;
setLeadAreaFilter ( 'All Areas' ) ;
setLeadBudgetFilter ( 'All Budgets' ) ;
setLeadDateFilter ( 'Any Date' ) ;
setLeadSortFilter ( 'Newest First' ) ;
setLeadFiltersOpen ( false ) ;
setLeadSortOpen ( false ) ;
setLeadPage ( 1 ) ;
setActiveLeadDetailId ( '' ) ;
setLeadContactConfirmId ( '' ) ;
setActiveResponseLeadId ( '' ) ;
setResponsesDetailMode ( false ) ;
setRequestedSearch ( '' ) ;
setRequestedStatusFilter ( 'All Status' ) ;
setRequestedSortFilter ( 'Newest First' ) ;
setRequestedSortOpen ( false ) ;
setRequestedFilterOpen ( false ) ;
setRequestedPage ( 1 ) ;
}
} ) ;
createEffect ( ( ) = > {
void leadSearch ( ) ;
void leadAreaFilter ( ) ;
void leadBudgetFilter ( ) ;
void leadDateFilter ( ) ;
void leadSortFilter ( ) ;
setLeadPage ( 1 ) ;
} ) ;
createEffect ( ( ) = > {
if ( leadPage ( ) > totalLeadPages ( ) ) setLeadPage ( totalLeadPages ( ) ) ;
} ) ;
createEffect ( ( ) = > {
void requestedSearch ( ) ;
void requestedStatusFilter ( ) ;
void requestedSortFilter ( ) ;
setRequestedPage ( 1 ) ;
} ) ;
createEffect ( ( ) = > {
if ( requestedPage ( ) > totalRequestedPages ( ) ) setRequestedPage ( totalRequestedPages ( ) ) ;
} ) ;
createEffect ( ( ) = > {
if ( customerKey ( ) !== 'jobs' ) {
setJobPostView ( 'form' ) ;
setJobPostStep ( 1 ) ;
setJobSeekerScreen ( 'list' ) ;
setJobSeekerSelectedId ( jobBoardJobs ( ) [ 0 ] ? . id || '' ) ;
setJobSeekerApplyStep ( 2 ) ;
setLastJobSeekerTabKey ( '' ) ;
}
} ) ;
createEffect ( ( ) = > {
if ( customerKey ( ) !== 'my portfolio' ) setPortfolioEditMode ( false ) ;
if ( customerKey ( ) !== 'my portfolio' ) setPortfolioTopTab ( 'my_portfolio' ) ;
} ) ;
const openPortfolioPreviewInline = ( ) = > {
setPortfolioTopTab ( 'preview' ) ;
props . onSidebarSelect ( 'My Portfolio' ) ;
props . onTabSelect ( 'about' ) ;
} ;
createEffect ( ( ) = > {
if ( customerKey ( ) !== 'jobs' || ! isJobSeekerRole ( ) ) return ;
const tabKey = normalizeTabKey ( resolvedTabKey ( ) ) ;
if ( tabKey === lastJobSeekerTabKey ( ) ) return ;
setLastJobSeekerTabKey ( tabKey ) ;
setJobSeekerScreen ( 'list' ) ;
setJobSeekerSelectedId ( jobBoardJobs ( ) [ 0 ] ? . id || '' ) ;
} ) ;
createEffect ( ( ) = > {
const rows = jobBoardJobs ( ) ;
const active = jobSeekerSelectedId ( ) ;
if ( ! rows . length ) return ;
if ( ! rows . some ( ( job ) = > job . id === active ) ) {
setJobSeekerSelectedId ( rows [ 0 ] . id ) ;
}
} ) ;
createEffect ( ( ) = > {
if ( helpCenterTab ( ) !== 'my_tickets' ) {
setMyTicketsTab ( 'all_tickets' ) ;
setActiveTicketId ( 'TCK-1042' ) ;
setViewTicketFiles ( [ ] ) ;
}
} ) ;
createEffect ( ( ) = > {
if ( helpCenterTab ( ) !== 'create_ticket' ) setCreateTicketFiles ( [ ] ) ;
} ) ;
const moveDashboardWidget = ( movingKey : string , targetKey : string ) = > {
if ( ! movingKey || ! targetKey || movingKey === targetKey ) return ;
setDashboardWidgetOrder ( ( prev ) = > {
const from = prev . indexOf ( movingKey ) ;
const to = prev . indexOf ( targetKey ) ;
if ( from === - 1 || to === - 1 ) return prev ;
const next = [ . . . prev ] ;
next . splice ( from , 1 ) ;
next . splice ( to , 0 , movingKey ) ;
return next ;
} ) ;
} ;
const statusChip = ( value : string ) = > {
const key = value . toLowerCase ( ) ;
if ( key === 'active' || key === 'new' ) return { bg : '#FFF1EB' , c : '#FF5E13' } ;
if ( key === 'closed' || key === 'contacted' ) return { bg : '#ECFDF3' , c : '#059669' } ;
if ( key === 'shortlisted' ) return { bg : '#EEF2FF' , c : '#4338CA' } ;
if ( key === 'draft' ) return { bg : '#FEF3C7' , c : '#B45309' } ;
return { bg : '#F3F4F6' , c : '#6B7280' } ;
} ;
const requestLeadContact = ( leadId : string ) = > {
if ( usableLeadCredits ( ) < leadCostPerContact ) return ;
Wire user dashboard to real APIs — profile, credits, leads, requirements, jobs
- dashboard.tsx: fetch session for real user name/ID/role, fallback to
localStorage; show dashboard with role defaults when runtime config unavailable
- DashboardDesignPreview: add liveData prop; createResource for credits,
marketplace, lead requests, customer requirements, jobs, and profile
- Profile form: inputs now track state via profileFormData signal; pre-filled
from GET /api/${prefix}/profile/me; Save Changes PATCHes real endpoint
- Lead actions: Send Request POSTs to /api/${prefix}/leads/request; Cancel
DELETEs /api/${prefix}/leads/requests/{id}; both refetch after completion
- Requirement submit: POSTs to /api/customers/requirements then submits for approval
- Replace hardcoded "Alex" with real session name; credits from wallet balance API
- Fix launch.json PATH so npm is found in sh shell
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 21:13:54 +02:00
if ( hasLive ( ) ) {
apiPost ( ` /api/ ${ livePrefix ( ) } /leads/request ` , { requirement_id : leadId } )
. then ( ( ) = > refetchLeadRequestsLive ( ) ) ;
}
2026-04-05 16:52:02 +02:00
let changed = false ;
setLeadCards ( ( prev ) = > prev . map ( ( card ) = > {
if ( card . id !== leadId || card . status !== 'open' || card . contactCount >= card . maxContacts ) return card ;
changed = true ;
const nextCount = Math . min ( card . maxContacts , card . contactCount + 1 ) ;
return {
. . . card ,
contactCount : nextCount ,
status : ( nextCount >= card . maxContacts ? 'closed' : 'requested' ) as LeadCardStatus ,
} ;
} ) ) ;
if ( ! changed ) return ;
setLeadRequestRows ( ( prev ) = > {
const idx = prev . findIndex ( ( row ) = > row . id === leadId ) ;
if ( idx >= 0 ) {
const next = [ . . . prev ] ;
next [ idx ] = { . . . next [ idx ] , status : 'request_sent' , decisionDate : '--' } ;
return next ;
}
const selected = leadCards ( ) . find ( ( card ) = > card . id === leadId ) ;
return [
{
id : leadId ,
title : selected?.title || 'Lead Request' ,
city : selected ? ` ${ selected . area || 'Central' } , ${ selected . location } ` : 'Chennai, India' ,
requestDate : 'Apr 02, 2026' ,
status : 'request_sent' as const ,
decisionDate : '--' ,
} ,
. . . prev ,
] ;
} ) ;
} ;
const leadDetailsSpec = ( lead : { category : string } ) = > {
const c = lead . category . toLowerCase ( ) ;
if ( c . includes ( 'photography' ) || c . includes ( 'fashion' ) ) {
return { timeframe : '3-5 shoot days + 7 days edit' , scope : 'Pre-shoot planning, full day coverage, edited album delivery' , highlights : [ 'Shot list alignment with customer brief' , 'Venue and lighting plan finalization' , 'Edited photos + social cuts' ] } ;
}
if ( c . includes ( 'design' ) || c . includes ( 'branding' ) ) {
return { timeframe : '7-14 working days' , scope : 'Brand exploration, visual concepts, revision rounds, handoff' , highlights : [ 'Moodboard and style direction' , 'Logo/system deliverables' , 'Source files + usage formats' ] } ;
}
if ( c . includes ( 'video' ) ) {
return { timeframe : '5-10 working days' , scope : 'Editing, color correction, sound polish, export variants' , highlights : [ 'Storyboard-based edits' , 'Platform-specific outputs' , 'Two revision rounds' ] } ;
}
return { timeframe : '5-10 working days' , scope : 'Requirement discovery, execution plan, and final delivery' , highlights : [ 'Clear milestone tracking' , 'Weekly progress updates' , 'Final quality checklist' ] } ;
} ;
const openLeadDetailsInNewTab = ( leadId : string ) = > {
setActiveLeadDetailId ( leadId ) ;
setLeadMarketplaceTab ( 'View Details' ) ;
} ;
const openResponseLeadDetails = ( leadId : string ) = > {
setActiveResponseLeadId ( leadId ) ;
setResponsesDetailMode ( true ) ;
} ;
const openLeadContactConfirm = ( leadId : string ) = > setLeadContactConfirmId ( leadId ) ;
const confirmLeadContactRequest = ( ) = > {
const leadId = leadContactConfirmId ( ) ;
if ( ! leadId ) return ;
requestLeadContact ( leadId ) ;
setLeadContactConfirmId ( '' ) ;
} ;
const approveLeadContact = ( leadId : string ) = > {
setLeadCards ( ( prev ) = > prev . map ( ( card ) = > card . id === leadId && card . status === 'requested'
? { . . . card , status : 'unlocked' as const }
: card ) ) ;
setLeadCredits ( ( prev ) = > Math . max ( 0 , prev - leadCostPerContact ) ) ;
setLeadRequestRows ( ( prev ) = > prev . map ( ( row ) = > row . id === leadId
? { . . . row , status : 'contact_unlocked' as const , decisionDate : 'Apr 03, 2026' }
: row ) ) ;
} ;
const cancelLeadRequest = ( leadId : string ) = > {
Wire user dashboard to real APIs — profile, credits, leads, requirements, jobs
- dashboard.tsx: fetch session for real user name/ID/role, fallback to
localStorage; show dashboard with role defaults when runtime config unavailable
- DashboardDesignPreview: add liveData prop; createResource for credits,
marketplace, lead requests, customer requirements, jobs, and profile
- Profile form: inputs now track state via profileFormData signal; pre-filled
from GET /api/${prefix}/profile/me; Save Changes PATCHes real endpoint
- Lead actions: Send Request POSTs to /api/${prefix}/leads/request; Cancel
DELETEs /api/${prefix}/leads/requests/{id}; both refetch after completion
- Requirement submit: POSTs to /api/customers/requirements then submits for approval
- Replace hardcoded "Alex" with real session name; credits from wallet balance API
- Fix launch.json PATH so npm is found in sh shell
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 21:13:54 +02:00
if ( hasLive ( ) ) {
apiDelete ( ` /api/ ${ livePrefix ( ) } /leads/requests/ ${ leadId } ` )
. then ( ( ) = > refetchLeadRequestsLive ( ) ) ;
}
2026-04-05 16:52:02 +02:00
setLeadCards ( ( prev ) = > prev . map ( ( card ) = > {
if ( card . id !== leadId ) return card ;
if ( card . status !== 'requested' && card . status !== 'closed' ) return card ;
return { . . . card , contactCount : Math.max ( 0 , card . contactCount - 1 ) , status : 'open' as LeadCardStatus } ;
} ) ) ;
setLeadCredits ( ( prev ) = > Math . max ( 0 , prev - leadCostPerContact ) ) ;
setLeadRequestRows ( ( prev ) = > prev . map ( ( row ) = > row . id === leadId
? { . . . row , status : 'cancelled_by_professional' as const , decisionDate : 'Apr 03, 2026' }
: row ) ) ;
} ;
const refundPendingLead = ( leadId : string ) = > {
setLeadCards ( ( prev ) = > prev . map ( ( card ) = > {
if ( card . id !== leadId ) return card ;
if ( card . status !== 'requested' && card . status !== 'closed' ) return card ;
return { . . . card , contactCount : Math.max ( 0 , card . contactCount - 1 ) , status : 'open' as LeadCardStatus } ;
} ) ) ;
setLeadRequestRows ( ( prev ) = > prev . map ( ( row ) = > row . id === leadId
? { . . . row , status : 'expired_refunded' as const , decisionDate : 'Auto-refunded' }
: row ) ) ;
} ;
const leadMatchPercent = ( lead : { match : string } ) = > {
const value = Number . parseInt ( String ( lead . match || '' ) . replace ( /[^0-9]/g , '' ) , 10 ) ;
return Number . isFinite ( value ) ? value : 70 ;
} ;
const leadProbability = ( lead : { match : string ; urgency : string ; contactCount : number ; maxContacts : number } ) = > {
const urgencyBoost = lead . urgency . includes ( 'High' ) ? 8 : lead.urgency.includes ( 'Medium' ) ? 4 : 0 ;
const slotFactor = ( ( lead . maxContacts - lead . contactCount ) / Math . max ( 1 , lead . maxContacts ) ) * 40 ;
const score = Math . round ( 0.5 * leadMatchPercent ( lead ) + slotFactor + urgencyBoost ) ;
return Math . min ( 95 , Math . max ( 5 , score ) ) ;
} ;
const leadProbabilityColor = ( score : number ) = > {
if ( score >= 75 ) return '#16A34A' ;
if ( score >= 50 ) return '#F59E0B' ;
return '#DC2626' ;
} ;
const leadProbabilityLabel = ( score : number ) = > {
if ( score >= 75 ) return 'High' ;
if ( score >= 50 ) return 'Medium' ;
return 'Low' ;
} ;
const leadGaugeNeedlePoint = ( score : number ) = > {
const clamped = Math . max ( 0 , Math . min ( 100 , score ) ) ;
const angleDeg = 180 - ( clamped * 180 ) / 100 ;
const angleRad = ( angleDeg * Math . PI ) / 180 ;
const radius = 34 ;
return {
x : 60 + Math . cos ( angleRad ) * radius ,
y : 60 - Math . sin ( angleRad ) * radius ,
} ;
} ;
const leadGaugeDash = ( score : number ) = > {
const clamped = Math . max ( 0 , Math . min ( 100 , score ) ) ;
const arcLength = Math . PI * 50 ;
return ` ${ ( ( clamped / 100 ) * arcLength ) . toFixed ( 1 ) } ${ arcLength . toFixed ( 1 ) } ` ;
} ;
const renderPortfolioContent = ( ) = > {
const spec = portfolioSpecForRole ( props . roleKey || '' ) ;
const mediaConfig = portfolioMediaConfig ( props . roleKey || '' ) ;
const submissionTabs = spec . tabs . filter ( ( item ) = > normalizeTabKey ( item ) !== normalizeTabKey ( 'testimonials' ) ) ;
const selectedPortfolioTab = submissionTabs . find ( ( item ) = > normalizeTabKey ( item ) === normalizeTabKey ( resolvedTabKey ( ) ) ) || submissionTabs [ 0 ] || 'about' ;
const selectedPortfolioTabKey = normalizeTabKey ( selectedPortfolioTab ) ;
const serviceTabKey = normalizeTabKey ( spec . serviceTabLabel ) ;
const galleryTabKey = normalizeTabKey ( spec . galleryTabLabel ) ;
const experienceTabKey = normalizeTabKey ( spec . experienceTabLabel ) ;
const testimonialsTabKey = normalizeTabKey ( 'testimonials' ) ;
const faqsTabKey = normalizeTabKey ( 'faqs' ) ;
const portfolioJobsCompleted = portfolioJobsCompletedPreview ;
const portfolioFeedbackCount = portfolioFeedbackCountPreview ;
const testimonialsUnlocked = portfolioTestimonialsUnlocked ( ) ;
const isPreviewMode = portfolioTopTab ( ) === 'preview' ;
const canEdit = ! isPreviewMode ;
const portfolioStepKeys = submissionTabs . map ( ( item ) = > normalizeTabKey ( item ) ) ;
const activePortfolioStepIndex = Math . max ( 0 , portfolioStepKeys . findIndex ( ( key ) = > key === selectedPortfolioTabKey ) ) ;
const goToPortfolioStep = ( index : number ) = > {
const bounded = Math . max ( 0 , Math . min ( portfolioStepKeys . length - 1 , index ) ) ;
props . onTabSelect ( submissionTabs [ bounded ] || submissionTabs [ 0 ] || 'about' ) ;
} ;
const portfolioTools = ( ( ) = > {
const role = normalizeRoleKey ( props . roleKey || '' ) ;
if ( role === 'GRAPHIC_DESIGNER' ) return [ 'Figma' , 'Adobe Photoshop' , 'Illustrator' , 'InDesign' , 'After Effects' ] ;
if ( role === 'SOCIAL_MEDIA_MANAGER' ) return [ 'Meta Business Suite' , 'Canva' , 'Buffer' , 'Hootsuite' , 'Google Analytics' ] ;
if ( role === 'DEVELOPER' ) return [ 'React' , 'TypeScript' , 'Node.js' , 'PostgreSQL' , 'Docker' ] ;
if ( role === 'VIDEO_EDITOR' ) return [ 'Premiere Pro' , 'After Effects' , 'DaVinci Resolve' , 'CapCut' , 'Audition' ] ;
if ( role === 'PHOTOGRAPHER' ) return [ 'Lightroom' , 'Photoshop' , 'Capture One' , 'Bridge' , 'Snapseed' ] ;
return [ 'Domain Tool 1' , 'Domain Tool 2' , 'Domain Tool 3' ] ;
} ) ( ) ;
const portfolioFormFieldsByTab : Record < string , string [ ] > = {
about : [ 'Professional Headline' , 'About You' , 'Area' , 'Place' , 'Travel Preference' , 'Response Time' ] ,
[ serviceTabKey ] : [ 'Primary Service' , 'Pricing Model' , 'Starting Price' , 'Delivery Timeline' , 'Includes' , 'Additional Notes' ] ,
[ galleryTabKey ] : [ 'Portfolio Item Title' , 'Portfolio Link' , 'Category' , 'Project Summary' , 'Outcome' , 'Asset Upload' ] ,
[ experienceTabKey ] : [ 'Years of Experience' , 'Top Tools' , 'Milestone 1' , 'Milestone 2' , 'Certifications' , 'Working Style' ] ,
[ testimonialsTabKey ] : [ 'Client Name' , 'Client Feedback' , 'Rating' , 'Project Type' , 'Client Location' , 'Consent' ] ,
[ faqsTabKey ] : [ 'Question 1' , 'Answer 1' , 'Question 2' , 'Answer 2' , 'Question 3' , 'Answer 3' ] ,
} ;
const selectedPortfolioFormFields = portfolioFormFieldsByTab [ selectedPortfolioTabKey ] || portfolioFormFieldsByTab . about ;
const setPortfolioFieldValue = ( field : string , value : string ) = > {
const fieldKey = ` ${ selectedPortfolioTabKey } :: ${ field } ` ;
setPortfolioFormValues ( ( prev ) = > ( { . . . prev , [ fieldKey ] : value } ) ) ;
setPortfolioFormErrors ( ( prev ) = > ( { . . . prev , [ fieldKey ] : '' } ) ) ;
setPortfolioValidationNotice ( '' ) ;
} ;
const renderPortfolioFormField = ( field : string ) = > {
const key = String ( field || '' ) . toLowerCase ( ) ;
const isSelect = /model|category|style|rating|consent|response time|travel|type|timeline/i . test ( key ) ;
const isLong = /about|summary|notes|answer|feedback|includes/i . test ( key ) ;
const fieldKey = ` ${ selectedPortfolioTabKey } :: ${ field } ` ;
const fieldValue = portfolioFormValues ( ) [ fieldKey ] || '' ;
const fieldError = portfolioFormErrors ( ) [ fieldKey ] || '' ;
const placeholder = ( ( ) = > {
if ( key . includes ( 'area' ) ) return 'Enter area in Chennai' ;
if ( key . includes ( 'place' ) ) return 'Enter place in Chennai' ;
if ( key . includes ( 'price' ) ) return 'Enter amount' ;
if ( key . includes ( 'link' ) ) return 'Paste URL' ;
if ( key . includes ( 'upload' ) ) return 'Upload file (PDF/JPG/PNG)' ;
return ` ${ isSelect ? 'Select' : 'Enter' } ${ field . toLowerCase ( ) } ` ;
} ) ( ) ;
return (
< div style = "display:flex;flex-direction:column;gap:6px" >
< p style = "margin:0;font-size:11px;font-weight:700;color:#6B7280;letter-spacing:0.01em;text-transform:none" > { field } < / p >
< Show
when = { isLong }
fallback = {
< div style = "position:relative" >
< input type = "text" value = { fieldValue } onInput = { ( e ) = > setPortfolioFieldValue ( field , e . currentTarget . value ) } placeholder = { placeholder } style = { ` width:100%;height:36px;border:1px solid ${ fieldError ? '#FFD8C2' : '#E5E7EB' } ;border-radius:8px;background:white;padding:0 30px 0 10px;font-size:12px;color:#111827;outline:none ` } / >
< Show when = { isSelect } >
< span style = "position:absolute;right:10px;top:50%;transform:translateY(-50%);color:#9CA3AF;font-size:12px" > ▾ < / span >
< / Show >
< / div >
}
>
< textarea value = { fieldValue } onInput = { ( e ) = > setPortfolioFieldValue ( field , e . currentTarget . value ) } placeholder = { placeholder } style = { ` width:100%;min-height:74px;border:1px solid ${ fieldError ? '#FFD8C2' : '#E5E7EB' } ;border-radius:8px;background:white;padding:8px 10px;font-size:12px;color:#111827;outline:none;resize:vertical ` } / >
< / Show >
< Show when = { ! ! fieldError } >
< p style = "margin:0;font-size:11px;color:#C2410C" > { fieldError } < / p >
< / Show >
< / div >
) ;
} ;
const validatePortfolioStep = ( stepKey : string ) = > {
const nextErrors : Record < string , string > = { } ;
const stepFields = portfolioFormFieldsByTab [ stepKey ] || [ ] ;
stepFields . forEach ( ( field ) = > {
const fKey = ` ${ stepKey } :: ${ field } ` ;
const value = ( portfolioFormValues ( ) [ fKey ] || '' ) . trim ( ) ;
const optional = /optional/i . test ( field ) ;
const isUpload = /asset upload/i . test ( field ) ;
if ( ! optional && ! isUpload && ! value ) nextErrors [ fKey ] = 'This field is required' ;
} ) ;
if ( stepKey === serviceTabKey && portfolioServices ( ) . length < 1 ) {
setPortfolioValidationNotice ( 'Add at least one service with pricing.' ) ;
} else if ( stepKey === galleryTabKey && ( portfolioPhotos ( ) . length < 1 || portfolioPhotos ( ) . length > 6 ) ) {
setPortfolioValidationNotice ( 'Add 1 to 6 portfolio photos.' ) ;
} else if ( stepKey === experienceTabKey && portfolioExperiences ( ) . length < 1 ) {
setPortfolioValidationNotice ( 'Add at least one experience entry.' ) ;
} else if ( Object . keys ( nextErrors ) . length ) {
setPortfolioValidationNotice ( 'Please fill all required fields in this tab.' ) ;
} else {
setPortfolioValidationNotice ( '' ) ;
}
setPortfolioFormErrors ( ( prev ) = > ( { . . . prev , . . . nextErrors } ) ) ;
return Object . keys ( nextErrors ) . length === 0
&& ! ( stepKey === serviceTabKey && portfolioServices ( ) . length < 1 )
&& ! ( stepKey === galleryTabKey && ( portfolioPhotos ( ) . length < 1 || portfolioPhotos ( ) . length > 6 ) )
&& ! ( stepKey === experienceTabKey && portfolioExperiences ( ) . length < 1 ) ;
} ;
const addServiceDraft = ( ) = > {
const draft = portfolioServiceDraft ( ) ;
if ( ! draft . name . trim ( ) || ! draft . price . trim ( ) ) {
setPortfolioValidationNotice ( 'Add service name and price to continue.' ) ;
return ;
}
setPortfolioServices ( ( prev ) = > [ . . . prev , {
name : draft.name.trim ( ) ,
model : draft.model.trim ( ) || 'Flat' ,
price : draft.price.trim ( ) ,
details : draft.details.trim ( ) ,
} ] ) ;
setPortfolioServiceDraft ( { name : '' , model : 'Flat' , price : '' , details : '' } ) ;
setPortfolioValidationNotice ( '' ) ;
} ;
const removeServiceItem = ( index : number ) = > {
setPortfolioServices ( ( prev ) = > prev . filter ( ( _ , i ) = > i !== index ) ) ;
} ;
const addExperienceDraft = ( ) = > {
const draft = portfolioExperienceDraft ( ) ;
if ( ! draft . year . trim ( ) || ! draft . title . trim ( ) ) {
setPortfolioValidationNotice ( 'Add year and title for each experience entry.' ) ;
return ;
}
setPortfolioExperiences ( ( prev ) = > [ . . . prev , {
year : draft.year.trim ( ) ,
title : draft.title.trim ( ) ,
details : draft.details.trim ( ) ,
} ] ) ;
setPortfolioExperienceDraft ( { year : '' , title : '' , details : '' } ) ;
setPortfolioValidationNotice ( '' ) ;
} ;
const removeExperienceItem = ( index : number ) = > {
setPortfolioExperiences ( ( prev ) = > prev . filter ( ( _ , i ) = > i !== index ) ) ;
} ;
const addPhotoItem = ( ) = > {
const current = portfolioPhotos ( ) ;
if ( current . length >= 6 ) {
setPortfolioValidationNotice ( 'Portfolio is limited to 6 photos.' ) ;
return ;
}
const nextLabel = ` portfolio- ${ current . length + 1 } .jpg ` ;
setPortfolioPhotos ( ( prev ) = > [ . . . prev , nextLabel ] ) ;
setPortfolioValidationNotice ( '' ) ;
} ;
const removePhotoItem = ( index : number ) = > {
setPortfolioPhotos ( ( prev ) = > prev . filter ( ( _ , i ) = > i !== index ) ) ;
} ;
const showSection = ( tabKey : string ) = > ( isPreviewMode ? portfolioStepKeys . includes ( tabKey ) : selectedPortfolioTabKey === tabKey ) ;
return (
< div style = "display:flex;flex-direction:column;gap:14px" >
< div style = "border-radius:12px;border:1px solid #E5E7EB;background:white;padding:0 12px;box-shadow:0 1px 3px rgba(0,0,0,0.04)" >
< div style = "display:flex;align-items:center;gap:20px;border-bottom:1px solid #E5E7EB;padding-top:10px" >
< button
type = "button"
onClick = { ( ) = > setPortfolioTopTab ( 'my_portfolio' ) }
style = { ` padding:0 0 10px;font-size:13px;font-weight:500;background:none;border:none;white-space:nowrap;cursor:pointer;color: ${ portfolioTopTab ( ) === 'my_portfolio' ? '#FF5E13' : '#6B7280' } ;border-bottom: ${ portfolioTopTab ( ) === 'my_portfolio' ? '2px solid #FF5E13' : '2px solid transparent' } ;margin-bottom:-1px ` }
>
My Portfolio
< / button >
< button
type = "button"
onClick = { ( ) = > setPortfolioTopTab ( 'preview' ) }
style = { ` padding:0 0 10px;font-size:13px;font-weight:500;background:none;border:none;white-space:nowrap;cursor:pointer;color: ${ portfolioTopTab ( ) === 'preview' ? '#FF5E13' : '#6B7280' } ;border-bottom: ${ portfolioTopTab ( ) === 'preview' ? '2px solid #FF5E13' : '2px solid transparent' } ;margin-bottom:-1px ` }
>
Preview
< / button >
< / div >
< / div >
{ /* Profile Header */ }
< div style = "border-radius:14px;border:1px solid #E5E7EB;background:white;box-shadow:0 1px 3px rgba(0,0,0,0.06);overflow:hidden" >
< div style = "height:3px;background:linear-gradient(90deg,#FF5E13 0%,#FF8A4C 55%,#FFD0B5 100%)" / >
< Show when = { isPreviewMode } >
< div style = "padding:16px 20px;border-bottom:1px solid #E5E7EB;display:flex;align-items:flex-start;justify-content:space-between;gap:12px" >
< div style = "display:grid;gap:8px" >
< div style = "display:flex;align-items:center;gap:8px;flex-wrap:wrap" >
< p style = "margin:0;font-size:18px;font-weight:800;color:#111827" > Alex Morgan < / p >
< span style = "font-size:11px;font-weight:700;color:#374151;background:#F3F4F6;border:1px solid #E5E7EB;border-radius:999px;padding:2px 9px" > Verified < / span >
< span style = "font-size:11px;font-weight:700;color:#C2410C;background:#FFF1EB;border:1px solid #FFD8C2;border-radius:999px;padding:2px 9px" > Top Response Rate < / span >
< / div >
< p style = "margin:0;font-size:13px;color:#4B5563" > { spec . roleLabel } < / p >
< div style = "display:flex;align-items:center;gap:6px;flex-wrap:wrap" >
< span style = "height:24px;padding:0 10px;border-radius:999px;background:#FFF1EB;color:#C2410C;font-size:11px;font-weight:700;display:inline-flex;align-items:center" > Area : South Chennai < / span >
< span style = "height:24px;padding:0 10px;border-radius:999px;background:#FFF1EB;color:#C2410C;font-size:11px;font-weight:700;display:inline-flex;align-items:center" > Place : T. Nagar , Chennai < / span >
< span style = "height:24px;padding:0 10px;border-radius:999px;border:1px solid #E5E7EB;background:#F9FAFB;color:#374151;font-size:11px;font-weight:700;display:inline-flex;align-items:center" > Travel : T. Nagar , Adyar , Velachery , Anna Nagar < / span >
< / div >
< / div >
< div style = "display:flex;gap:8px;flex-shrink:0;align-self:center" / >
< / div >
< / Show >
< div style = "padding:0 20px;border-bottom:1px solid #E5E7EB;background:#FFFFFF" >
< div style = "display:flex;align-items:center;gap:20px;overflow-x:auto;padding-top:10px" >
< For each = { [ . . . submissionTabs , 'preview' ] } >
{ ( item ) = > {
const itemKey = normalizeTabKey ( item ) ;
const isPreviewStep = itemKey === 'preview' ;
const isLockedTestimonialsTab = itemKey === 'testimonials' && ! testimonialsUnlocked ;
const isActive = isPreviewStep ? isPreviewMode : ( ! isPreviewMode && selectedPortfolioTabKey === itemKey ) ;
return (
< button
type = "button"
disabled = { isLockedTestimonialsTab }
onClick = { ( ) = > {
if ( isLockedTestimonialsTab ) return ;
if ( isPreviewStep ) {
setPortfolioTopTab ( 'preview' ) ;
return ;
}
setPortfolioTopTab ( 'my_portfolio' ) ;
props . onTabSelect ( item ) ;
} }
title = { isLockedTestimonialsTab ? 'Unlock after 3 completed jobs and 2 customer feedback entries' : ( isPreviewStep ? 'Final preview before submit' : '' ) }
style = { ` padding:0 0 10px;font-size:13px;font-weight:500;background:none;border:none;white-space:nowrap;cursor: ${ isLockedTestimonialsTab ? 'not-allowed' : 'pointer' } ;opacity: ${ isLockedTestimonialsTab ? 0.5 : 1 } ;color: ${ isActive ? '#FF5E13' : '#6B7280' } ;border-bottom: ${ isActive ? '2px solid #FF5E13' : '2px solid transparent' } ;margin-bottom:-1px;flex-shrink:0 ` }
>
{ titleCase ( item ) } { isLockedTestimonialsTab ? ' • Locked' : '' }
< / button >
) ;
} }
< / For >
< / div >
< / div >
< Show when = { isPreviewMode } >
< div style = "padding:14px 16px;display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:10px" >
{ spec . statsLabels . slice ( 0 , 4 ) . map ( ( label , i ) = > (
< div style = "border:1px solid #E5E7EB;border-radius:12px;background:#FAFBFD;padding:12px 12px" >
< p style = "margin:0;font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:0.05em;color:#9CA3AF" > { label } < / p >
< p style = "margin:5px 0 0;font-size:14px;font-weight:800;color:#111827" > { i === 0 ? '248' : i === 1 ? '7+' : i === 2 ? 'Verified' : i === 3 ? '2 days' : 'Yes' } < / p >
< / div >
) ) }
< / div >
< / Show >
< / div >
< Show when = { ! isPreviewMode } >
< div style = "display:grid;grid-template-columns:2fr 1fr;gap:10px" >
< div style = "border:1px solid #E5E7EB;background:white;border-radius:16px;padding:14px;box-shadow:0 1px 4px rgba(0,0,0,0.06)" >
< div style = "display:flex;justify-content:space-between;align-items:center;gap:10px" >
< p style = "margin:0;font-size:13px;font-weight:700;color:#111827" > { titleCase ( selectedPortfolioTab ) } < / p >
< span style = "height:22px;padding:0 8px;border-radius:999px;border:1px solid #DDEBFF;background:#EEF4FF;display:inline-flex;align-items:center;font-size:10px;font-weight:700;color:#03004E" >
{ selectedPortfolioFormFields . length } fields
< / span >
< / div >
< Show when = { ! ! portfolioValidationNotice ( ) } >
< div style = "margin-top:10px;border:1px solid #E5E7EB;background:#F9FAFB;border-radius:8px;padding:8px 10px;font-size:12px;color:#374151" >
{ portfolioValidationNotice ( ) }
< / div >
< / Show >
< Show when = { selectedPortfolioTabKey === serviceTabKey } >
< div style = "display:grid;gap:10px;margin-top:10px" >
< div style = "display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px" >
< input type = "text" value = { portfolioServiceDraft ( ) . name } onInput = { ( e ) = > setPortfolioServiceDraft ( ( prev ) = > ( { . . . prev , name : e.currentTarget.value } ) ) } placeholder = "Service name" style = "height:36px;border:1px solid #E5E7EB;border-radius:8px;background:white;padding:0 10px;font-size:12px;color:#111827;outline:none" / >
< input type = "text" value = { portfolioServiceDraft ( ) . price } onInput = { ( e ) = > setPortfolioServiceDraft ( ( prev ) = > ( { . . . prev , price : e.currentTarget.value } ) ) } placeholder = "Starting price" style = "height:36px;border:1px solid #E5E7EB;border-radius:8px;background:white;padding:0 10px;font-size:12px;color:#111827;outline:none" / >
< input type = "text" value = { portfolioServiceDraft ( ) . model } onInput = { ( e ) = > setPortfolioServiceDraft ( ( prev ) = > ( { . . . prev , model : e.currentTarget.value } ) ) } placeholder = "Pricing model (Flat/Hourly)" style = "height:36px;border:1px solid #E5E7EB;border-radius:8px;background:white;padding:0 10px;font-size:12px;color:#111827;outline:none" / >
< input type = "text" value = { portfolioServiceDraft ( ) . details } onInput = { ( e ) = > setPortfolioServiceDraft ( ( prev ) = > ( { . . . prev , details : e.currentTarget.value } ) ) } placeholder = "Delivery timeline or details" style = "height:36px;border:1px solid #E5E7EB;border-radius:8px;background:white;padding:0 10px;font-size:12px;color:#111827;outline:none" / >
< / div >
< div style = "display:flex;justify-content:flex-start" >
< button type = "button" onClick = { addServiceDraft } style = "height:32px;border-radius:8px;border:1px solid #E5E7EB;background:white;padding:0 12px;font-size:12px;font-weight:700;color:#374151;cursor:pointer" > Add Service < / button >
< / div >
< div style = "display:grid;gap:8px" >
< For each = { portfolioServices ( ) } > { ( service , idx ) = > (
< div style = "border:1px solid #E5E7EB;border-radius:10px;background:#FAFBFD;padding:10px;display:grid;grid-template-columns:1fr auto;gap:8px;align-items:start" >
< div style = "display:grid;gap:4px" >
< p style = "margin:0;font-size:12px;font-weight:700;color:#111827" > { service . name } < / p >
< p style = "margin:0;font-size:11px;color:#6B7280" > { service . model } • { service . price } < / p >
< p style = "margin:0;font-size:11px;color:#374151" > { service . details || 'No additional notes' } < / p >
< / div >
< button type = "button" onClick = { ( ) = > removeServiceItem ( idx ( ) ) } style = "height:28px;border-radius:8px;border:1px solid #E5E7EB;background:white;padding:0 10px;font-size:11px;font-weight:700;color:#374151;cursor:pointer" > Remove < / button >
< / div >
) } < / For >
< / div >
< / div >
< / Show >
< Show when = { selectedPortfolioTabKey === galleryTabKey } >
< div style = "display:grid;gap:10px;margin-top:10px" >
< div style = "display:flex;align-items:center;justify-content:space-between;gap:8px" >
< p style = "margin:0;font-size:12px;color:#374151" > Portfolio photos ( { portfolioPhotos ( ) . length } / 6 ) < / p >
< button type = "button" onClick = { addPhotoItem } disabled = { portfolioPhotos ( ) . length >= 6 } style = { ` height:32px;border-radius:8px;border:1px solid #E5E7EB;background:white;padding:0 12px;font-size:12px;font-weight:700;color:#374151;cursor: ${ portfolioPhotos ( ) . length >= 6 ? 'not-allowed' : 'pointer' } ;opacity: ${ portfolioPhotos ( ) . length >= 6 ? 0.6 : 1 } ` } > Add Photo < / button >
< / div >
< div style = "display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px" >
< For each = { portfolioPhotos ( ) } > { ( photo , idx ) = > (
< div style = "border:1px solid #E5E7EB;border-radius:10px;background:#FAFBFD;padding:10px;display:flex;align-items:center;justify-content:space-between;gap:8px" >
< span style = "font-size:12px;font-weight:600;color:#374151" > { photo } < / span >
< button type = "button" onClick = { ( ) = > removePhotoItem ( idx ( ) ) } style = "height:26px;border-radius:8px;border:1px solid #E5E7EB;background:white;padding:0 8px;font-size:11px;font-weight:700;color:#374151;cursor:pointer" > Remove < / button >
< / div >
) } < / For >
< / div >
< / div >
< / Show >
< Show when = { selectedPortfolioTabKey === experienceTabKey } >
< div style = "display:grid;gap:10px;margin-top:10px" >
< div style = "display:grid;grid-template-columns:110px 1fr;gap:10px" >
< input type = "text" value = { portfolioExperienceDraft ( ) . year } onInput = { ( e ) = > setPortfolioExperienceDraft ( ( prev ) = > ( { . . . prev , year : e.currentTarget.value } ) ) } placeholder = "Year" style = "height:36px;border:1px solid #E5E7EB;border-radius:8px;background:white;padding:0 10px;font-size:12px;color:#111827;outline:none" / >
< input type = "text" value = { portfolioExperienceDraft ( ) . title } onInput = { ( e ) = > setPortfolioExperienceDraft ( ( prev ) = > ( { . . . prev , title : e.currentTarget.value } ) ) } placeholder = "Role or milestone title" style = "height:36px;border:1px solid #E5E7EB;border-radius:8px;background:white;padding:0 10px;font-size:12px;color:#111827;outline:none" / >
< / div >
< textarea value = { portfolioExperienceDraft ( ) . details } onInput = { ( e ) = > setPortfolioExperienceDraft ( ( prev ) = > ( { . . . prev , details : e.currentTarget.value } ) ) } placeholder = "Brief details" style = "min-height:72px;border:1px solid #E5E7EB;border-radius:8px;background:white;padding:8px 10px;font-size:12px;color:#111827;outline:none;resize:vertical" / >
< div style = "display:flex;justify-content:flex-start" >
< button type = "button" onClick = { addExperienceDraft } style = "height:32px;border-radius:8px;border:1px solid #E5E7EB;background:white;padding:0 12px;font-size:12px;font-weight:700;color:#374151;cursor:pointer" > Add Experience < / button >
< / div >
< div style = "border:1px solid #E5E7EB;border-radius:10px;background:#FAFBFD;padding:10px;display:grid;gap:8px" >
< p style = "margin:0;font-size:11px;font-weight:700;color:#6B7280;letter-spacing:0.01em;text-transform:none" > Design Tools < / p >
< div style = "display:flex;flex-wrap:wrap;gap:6px" >
< For each = { portfolioDesignTools ( ) } >
{ ( tool ) = > (
< span style = "height:24px;padding:0 8px;border-radius:6px;border:1px solid #E5E7EB;background:white;font-size:11px;font-weight:600;color:#374151;display:inline-flex;align-items:center;gap:6px" >
{ tool }
< button type = "button" onClick = { ( ) = > removePortfolioTag ( tool , setPortfolioDesignTools ) } style = "border:none;background:none;color:#6B7280;cursor:pointer;padding:0;font-size:11px;line-height:1" > x < / button >
< / span >
) }
< / For >
< / div >
< div style = "display:grid;grid-template-columns:1fr auto;gap:8px;align-items:center" >
< input type = "text" value = { portfolioToolInput ( ) } onInput = { ( e ) = > setPortfolioToolInput ( e . currentTarget . value ) } placeholder = "Add tools (e.g. Figma, Photoshop, Illustrator)" style = "height:34px;border:1px solid #E5E7EB;border-radius:8px;background:white;padding:0 10px;font-size:12px;color:#374151;outline:none" / >
< button
type = "button"
onClick = { ( ) = > {
const added = addPortfolioTag ( portfolioToolInput ( ) , setPortfolioDesignTools , 10 ) ;
if ( added ) setPortfolioToolInput ( '' ) ;
} }
style = "height:32px;border-radius:8px;border:1px solid #E5E7EB;background:white;padding:0 12px;font-size:12px;font-weight:700;color:#374151;cursor:pointer"
>
Add Tool
< / button >
< / div >
< / div >
< div style = "display:grid;gap:8px" >
< For each = { portfolioExperiences ( ) } > { ( entry , idx ) = > (
< div style = "border:1px solid #E5E7EB;border-radius:10px;background:#FAFBFD;padding:10px;display:grid;grid-template-columns:1fr auto;gap:8px;align-items:start" >
< div style = "display:grid;gap:4px" >
< p style = "margin:0;font-size:12px;font-weight:700;color:#111827" > { entry . year } - { entry . title } < / p >
< p style = "margin:0;font-size:11px;color:#374151" > { entry . details || 'No additional notes' } < / p >
< / div >
< button type = "button" onClick = { ( ) = > removeExperienceItem ( idx ( ) ) } style = "height:28px;border-radius:8px;border:1px solid #E5E7EB;background:white;padding:0 10px;font-size:11px;font-weight:700;color:#374151;cursor:pointer" > Remove < / button >
< / div >
) } < / For >
< / div >
< / div >
< / Show >
< Show when = { selectedPortfolioTabKey !== serviceTabKey && selectedPortfolioTabKey !== galleryTabKey && selectedPortfolioTabKey !== experienceTabKey } >
< div style = "display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px;margin-top:10px" >
< For each = { selectedPortfolioFormFields } > { ( field ) = > renderPortfolioFormField ( field ) } < / For >
< / div >
< / Show >
< / div >
< div style = "border:1px solid #E5E7EB;background:white;border-radius:16px;padding:14px;box-shadow:0 1px 4px rgba(0,0,0,0.06)" >
< p style = "margin:0;font-size:11px;letter-spacing:0.04em;text-transform:uppercase;color:#6B7280" > Portfolio Form < / p >
< p style = "margin:8px 0 0;font-size:18px;font-weight:800;color:#111827" > Simple step - by - step input < / p >
< p style = "margin:8px 0 0;font-size:12px;color:#6B7280;line-height:1.5" > Fill one tab at a time . Use Next to move through sections , then check final Preview before submitting . < / p >
< Show when = { ! ! portfolioValidationNotice ( ) } >
< div style = "margin-top:10px;border:1px solid #E5E7EB;background:#F9FAFB;border-radius:8px;padding:8px 10px;font-size:12px;color:#374151" >
{ portfolioValidationNotice ( ) }
< / div >
< / Show >
< div style = "display:grid;gap:6px;margin-top:10px" >
< For each = { submissionTabs } > { ( tabName , idx ) = > (
< div style = { ` height:28px;border-radius:8px;border:1px solid #E5E7EB;background: ${ idx ( ) === activePortfolioStepIndex ? '#FFF8F4' : '#F9FAFB' } ;padding:0 10px;display:flex;align-items:center;font-size:11px;font-weight:700;color: ${ idx ( ) === activePortfolioStepIndex ? '#C2410C' : '#374151' } ` } >
{ idx ( ) + 1 } . { titleCase ( tabName ) }
< / div >
) } < / For >
< / div >
< / div >
< / div >
< / Show >
< Show when = { isPreviewMode } >
< Show when = { showSection ( normalizeTabKey ( 'about' ) ) } >
{ /* About + Specialties */ }
< div style = "display:grid;grid-template-columns:2fr 1fr;gap:10px" >
< div style = "border-radius:14px;border:1px solid #E5E7EB;background:white;box-shadow:0 1px 3px rgba(0,0,0,0.05);overflow:hidden" >
< div style = "padding:12px 16px;border-bottom:1px solid #E5E7EB" >
< p style = "margin:0;font-size:13px;font-weight:700;color:#111827;display:flex;align-items:center;gap:6px" > < UserCircle2 size = { 14 } style = "color:#FF5E13" / > About < / p >
< / div >
< div style = "padding:14px 16px" >
< Show
when = { canEdit }
fallback = { < p style = "margin:0;font-size:13px;color:#374151;line-height:1.6" > Professional { spec . roleLabel . toLowerCase ( ) } with 7 + years of experience delivering high - quality work across India . Committed to excellence , creativity , and client satisfaction on every project . < / p > }
>
< textarea
value = { ` Professional ${ spec . roleLabel . toLowerCase ( ) } with 7+ years of experience delivering high-quality work across India. Committed to excellence, creativity, and client satisfaction on every project. ` }
style = "width:100%;min-height:84px;border:1px solid #FFD8C2;border-radius:10px;background:#FFFCFA;padding:10px;font-size:13px;color:#374151;line-height:1.6;resize:vertical"
/ >
< / Show >
< div style = "margin-top:10px;display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px" >
{ [ 'Fast response within 2 hours' , 'Clear scope & milestone planning' , 'Delivery-first execution' ] . map ( ( line ) = > (
< div style = "border:1px solid #F3F4F6;border-radius:8px;background:#FAFAFA;padding:8px;font-size:11px;font-weight:600;color:#374151" > { line } < / div >
) ) }
< / div >
< / div >
< div style = "display:flex;border-top:1px solid #F3F4F6" >
{ [ [ '7+' , 'Years Exp' ] , [ '248' , 'Projects' ] , [ '4.9' , 'Rating' ] , [ '128' , 'Reviews' ] ] . map ( ( [ val , lbl ] , i ) = > (
< div style = { ` flex:1;padding:12px 16px; ${ i < 3 ? 'border-right:1px solid #F3F4F6' : '' } ` }>
< p style = "margin:0;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:0.05em;color:#9CA3AF" > { lbl } < / p >
< p style = "margin:4px 0 0;font-size:14px;font-weight:700;color:#111827" > { val } < / p >
< / div >
) ) }
< / div >
< / div >
< div style = "border-radius:14px;border:1px solid #E5E7EB;background:white;box-shadow:0 1px 3px rgba(0,0,0,0.05);overflow:hidden" >
< div style = "padding:12px 16px;border-bottom:1px solid #E5E7EB" >
< p style = "margin:0;font-size:13px;font-weight:700;color:#111827;display:flex;align-items:center;gap:6px" > < Rocket size = { 14 } style = "color:#FF5E13" / > Specialties < / p >
< / div >
< div style = "padding:14px 16px;display:flex;flex-wrap:wrap;gap:6px" >
< For each = { portfolioSpecialties ( ) } >
{ ( s ) = > (
< span style = "height:24px;padding:0 8px;border-radius:6px;border:1px solid #E5E7EB;background:#F9FAFB;font-size:11px;font-weight:600;color:#374151;display:inline-flex;align-items:center;gap:6px" >
{ s }
< Show when = { canEdit } >
< button
type = "button"
onClick = { ( ) = > removePortfolioTag ( s , setPortfolioSpecialties ) }
style = "border:none;background:none;color:#6B7280;cursor:pointer;padding:0;font-size:11px;line-height:1"
aria - label = { ` Remove ${ s } ` }
>
x
< / button >
< / Show >
< / span >
) }
< / For >
< / div >
< Show when = { canEdit } >
< div style = "padding:0 16px 12px;display:flex;gap:8px;align-items:center" >
< input
type = "text"
value = { portfolioSpecialtyInput ( ) }
onInput = { ( e ) = > setPortfolioSpecialtyInput ( e . currentTarget . value ) }
placeholder = "Add specialty"
style = "flex:1;height:32px;border:1px solid #E5E7EB;border-radius:8px;background:white;padding:0 10px;font-size:12px;color:#374151;outline:none"
/ >
< button
type = "button"
onClick = { ( ) = > {
const added = addPortfolioTag ( portfolioSpecialtyInput ( ) , setPortfolioSpecialties , 6 ) ;
if ( added ) setPortfolioSpecialtyInput ( '' ) ;
} }
style = "height:32px;border-radius:8px;border:1px solid #E5E7EB;background:white;padding:0 10px;font-size:11px;font-weight:700;color:#374151;cursor:pointer"
>
Add
< / button >
< / div >
< / Show >
< div style = "padding:0 16px 14px;border-top:1px solid #F3F4F6;margin-top:4px" >
< p style = "margin:10px 0 6px;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:0.05em;color:#9CA3AF" > Languages < / p >
< div style = "display:flex;gap:5px;flex-wrap:wrap" >
< For each = { portfolioLanguages ( ) } >
{ ( l ) = > (
< span style = "height:22px;padding:0 8px;border-radius:6px;border:1px solid #E5E7EB;background:#F9FAFB;font-size:11px;font-weight:600;color:#374151;display:inline-flex;align-items:center;gap:6px" >
{ l }
< Show when = { canEdit } >
< button
type = "button"
onClick = { ( ) = > removePortfolioTag ( l , setPortfolioLanguages ) }
style = "border:none;background:none;color:#6B7280;cursor:pointer;padding:0;font-size:11px;line-height:1"
aria - label = { ` Remove ${ l } ` }
>
x
< / button >
< / Show >
< / span >
) }
< / For >
< / div >
< Show when = { canEdit } >
< div style = "margin-top:6px;display:flex;gap:8px;align-items:center" >
< input
type = "text"
value = { portfolioLanguageInput ( ) }
onInput = { ( e ) = > setPortfolioLanguageInput ( e . currentTarget . value ) }
placeholder = "Add language"
style = "flex:1;height:30px;border:1px solid #E5E7EB;border-radius:8px;background:white;padding:0 10px;font-size:12px;color:#374151;outline:none"
/ >
< button
type = "button"
onClick = { ( ) = > {
const added = addPortfolioTag ( portfolioLanguageInput ( ) , setPortfolioLanguages , 6 ) ;
if ( added ) setPortfolioLanguageInput ( '' ) ;
} }
style = "height:30px;border-radius:8px;border:1px solid #E5E7EB;background:white;padding:0 10px;font-size:11px;font-weight:700;color:#374151;cursor:pointer"
>
Add
< / button >
< / div >
< / Show >
< p style = "margin:10px 0 6px;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:0.05em;color:#9CA3AF" > Service Areas < / p >
< div style = "display:flex;flex-wrap:wrap;gap:5px" >
< For each = { portfolioServiceAreas ( ) } >
{ ( c ) = > (
< span style = "height:22px;padding:0 8px;border-radius:6px;border:1px solid #E5E7EB;background:#F9FAFB;font-size:11px;font-weight:600;color:#374151;display:inline-flex;align-items:center;gap:6px" >
{ c }
< Show when = { canEdit } >
< button
type = "button"
onClick = { ( ) = > removePortfolioTag ( c , setPortfolioServiceAreas ) }
style = "border:none;background:none;color:#6B7280;cursor:pointer;padding:0;font-size:11px;line-height:1"
aria - label = { ` Remove ${ c } ` }
>
x
< / button >
< / Show >
< / span >
) }
< / For >
< / div >
< Show when = { canEdit } >
< div style = "margin-top:6px;display:flex;gap:8px;align-items:center" >
< input
type = "text"
value = { portfolioAreaInput ( ) }
onInput = { ( e ) = > setPortfolioAreaInput ( e . currentTarget . value ) }
placeholder = "Add service area in Chennai"
style = "flex:1;height:30px;border:1px solid #E5E7EB;border-radius:8px;background:white;padding:0 10px;font-size:12px;color:#374151;outline:none"
/ >
< button
type = "button"
onClick = { ( ) = > {
const added = addPortfolioTag ( portfolioAreaInput ( ) , setPortfolioServiceAreas , 8 ) ;
if ( added ) setPortfolioAreaInput ( '' ) ;
} }
style = "height:30px;border-radius:8px;border:1px solid #E5E7EB;background:white;padding:0 10px;font-size:11px;font-weight:700;color:#374151;cursor:pointer"
>
Add
< / button >
< / div >
< / Show >
< / div >
< / div >
< / div >
< / Show >
< Show when = { showSection ( serviceTabKey ) } >
{ /* Packages */ }
< div style = "border-radius:12px;border:1px solid #E5E7EB;background:linear-gradient(180deg,#FFFFFF 0%,#FCFCFD 100%);box-shadow:0 1px 3px rgba(0,0,0,0.06);overflow:hidden" >
< div style = "padding:12px 16px;border-bottom:1px solid #E5E7EB;display:flex;justify-content:space-between;align-items:center" >
< p style = "margin:0;font-size:13px;font-weight:700;color:#111827;display:flex;align-items:center;gap:6px" > < Coins size = { 14 } style = "color:#FF5E13" / > { titleCase ( spec . serviceTabLabel ) } < / p >
< span style = "height:22px;padding:0 8px;border-radius:999px;background:#FFF1EB;border:1px solid #FFD8C2;color:#C2410C;font-size:10px;font-weight:800;display:inline-flex;align-items:center" > Transparent Pricing < / span >
< / div >
< Show when = { canEdit } >
< div style = "padding:10px 12px;border-bottom:1px solid #E5E7EB;background:#FFFFFF;display:grid;gap:8px" >
< div style = "display:grid;grid-template-columns:1.2fr 0.9fr 1fr 1.3fr auto;gap:8px;padding:8px;border:1px solid #E5E7EB;border-radius:10px;background:#F9FAFB" >
< p style = "margin:0;font-size:11px;font-weight:700;color:#6B7280;text-transform:uppercase" > Service < / p >
< p style = "margin:0;font-size:11px;font-weight:700;color:#6B7280;text-transform:uppercase" > Pricing Type < / p >
< p style = "margin:0;font-size:11px;font-weight:700;color:#6B7280;text-transform:uppercase" > Amount < / p >
< p style = "margin:0;font-size:11px;font-weight:700;color:#6B7280;text-transform:uppercase" > Details < / p >
< p style = "margin:0;font-size:11px;font-weight:700;color:#6B7280;text-transform:uppercase;text-align:center" > Action < / p >
< / div >
{ [ 1 , 2 ] . map ( ( row ) = > (
< div style = "display:grid;grid-template-columns:1.2fr 0.9fr 1fr 1.3fr auto;gap:8px;align-items:center;padding:8px;border:1px solid #E5E7EB;border-radius:10px;background:white" >
< input type = "text" value = { row === 1 ? 'Wedding Coverage' : 'Pre-wedding Shoot' } style = "height:34px;border:1px solid #E5E7EB;border-radius:8px;background:white;padding:0 10px;font-size:12px;color:#374151" / >
< select style = "height:34px;border:1px solid #E5E7EB;border-radius:8px;background:white;padding:0 10px;font-size:12px;color:#374151" >
< option > Flat < / option >
< option > Hourly < / option >
< option > Per Day < / option >
< / select >
< input type = "text" value = { row === 1 ? '₹45,000' : '₹18,000' } style = "height:34px;border:1px solid #E5E7EB;border-radius:8px;background:white;padding:0 10px;font-size:12px;color:#374151" / >
< input type = "text" value = { row === 1 ? '8 hours, 300 photos' : '4 hours, 120 photos' } style = "height:34px;border:1px solid #E5E7EB;border-radius:8px;background:white;padding:0 10px;font-size:12px;color:#374151" / >
< button type = "button" style = "height:32px;border-radius:8px;border:1px solid #E5E7EB;background:white;padding:0 10px;font-size:11px;font-weight:700;color:#374151" > Remove < / button >
< / div >
) ) }
< div style = "display:flex;justify-content:flex-start" >
< button type = "button" style = "height:32px;border-radius:8px;border:1px solid #E5E7EB;background:white;padding:0 12px;font-size:12px;font-weight:700;color:#374151" > + Add Service < / button >
< / div >
< / div >
< / Show >
< div style = "display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px;padding:12px" >
{ spec . packages . map ( ( pkg , i ) = > (
< div style = { ` border:1px solid ${ i === 1 ? '#FFD8C2' : '#E5E7EB' } ;background: ${ i === 1 ? '#FFF8F4' : '#FFFFFF' } ;border-radius:10px;padding:12px ` } >
< Show when = { i === 1 } >
< span style = "height:20px;padding:0 8px;border-radius:999px;background:#FF5E13;color:white;font-size:10px;font-weight:800;display:inline-flex;align-items:center" > Most Chosen < / span >
< / Show >
< p style = "margin:0;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:0.05em;color:#9CA3AF" > { pkg . name } < / p >
< p style = "margin:4px 0 0;font-size:16px;font-weight:700;color:#111827" > { pkg . price } < / p >
< div style = "margin-top:8px;display:grid;gap:4px" >
{ pkg . items . map ( ( item ) = > (
< div style = "display:flex;align-items:center;gap:6px;font-size:12px;color:#374151" >
< CheckCircle2 size = { 11 } style = "color:#9CA3AF;flex-shrink:0" / > { item }
< / div >
) ) }
< / div >
< / div >
) ) }
< / div >
< / div >
< / Show >
< Show when = { showSection ( galleryTabKey ) } >
{ /* Work Gallery */ }
< div style = "border-radius:12px;border:1px solid #E5E7EB;background:linear-gradient(180deg,#FFFFFF 0%,#FCFCFD 100%);box-shadow:0 1px 3px rgba(0,0,0,0.06);overflow:hidden" >
< div style = "padding:12px 16px;border-bottom:1px solid #E5E7EB;display:flex;align-items:center;justify-content:space-between" >
< p style = "margin:0;font-size:13px;font-weight:700;color:#111827;display:flex;align-items:center;gap:6px" > < Image size = { 14 } style = "color:#FF5E13" / > { titleCase ( spec . galleryTabLabel ) } < / p >
< button type = "button" disabled = { ! canEdit } style = { ` height:30px;border-radius:8px;border:none;background:#0D0D2A;color:white;padding:0 12px;font-size:11px;font-weight:600;cursor: ${ canEdit ? 'pointer' : 'not-allowed' } ;opacity: ${ canEdit ? 1 : 0.6 } ` } > { mediaConfig . ctaLabel } < / button >
< / div >
< Show when = { canEdit } >
< div style = "padding:10px 12px;border-bottom:1px solid #E5E7EB;background:#FFF8F4" >
< div style = "height:34px;border:1px dashed #FFD8C2;border-radius:8px;background:#FFFCFA;display:flex;align-items:center;justify-content:center;font-size:12px;color:#9A3412;font-weight:600" >
Drag files here or click upload to add portfolio samples ( max 6 photos )
< / div >
< / div >
< / Show >
< Show
when = { mediaConfig . mode === 'visual' }
fallback = {
< div style = "padding:14px 16px;display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px" >
< For each = { mediaConfig . items } >
{ ( item , idx ) = > (
< div style = "min-height:84px;border-radius:10px;border:1px solid #E5E7EB;background:#F9FAFB;padding:10px;display:flex;gap:8px;align-items:flex-start" >
< FileText size = { 16 } style = "color:#9CA3AF;flex-shrink:0;margin-top:2px" / >
< div >
< p style = "margin:0;font-size:12px;font-weight:700;color:#111827" > { item } < / p >
< p style = "margin:4px 0 0;font-size:11px;color:#6B7280" > Entry # { idx ( ) + 1 } • Structured proof without mandatory photos < / p >
< / div >
< / div >
) }
< / For >
< / div >
}
>
< div style = "padding:14px 16px;display:grid;grid-template-columns:repeat(3,1fr);gap:8px" >
{ [ 0 , 1 , 2 , 3 , 4 , 5 ] . map ( ( i ) = > (
< div style = "height:110px;border-radius:10px;border:1px solid #E5E7EB;background:linear-gradient(180deg,#FFFFFF 0%,#F8FAFC 100%);display:flex;flex-direction:column;align-items:center;justify-content:center;gap:6px" >
< Image size = { 20 } style = "color:#C5CCD5" / >
< span style = "font-size:10px;color:#9CA3AF;text-align:center;padding:0 6px;line-height:1.35" > { mediaConfig . items [ i % mediaConfig . items . length ] } < / span >
< / div >
) ) }
< / div >
< / Show >
< / div >
< / Show >
< Show when = { showSection ( experienceTabKey ) } >
{ /* Experience */ }
< div style = "border-radius:12px;border:1px solid #E5E7EB;background:linear-gradient(180deg,#FFFFFF 0%,#FCFCFD 100%);box-shadow:0 1px 3px rgba(0,0,0,0.06);overflow:hidden" >
< div style = "padding:12px 16px;border-bottom:1px solid #E5E7EB" >
< p style = "margin:0;font-size:13px;font-weight:700;color:#111827;display:flex;align-items:center;gap:6px" > < BriefcaseBusiness size = { 14 } style = "color:#FF5E13" / > { titleCase ( spec . experienceTabLabel ) } < / p >
< / div >
< Show when = { canEdit } >
< div style = "padding:10px 12px;border-bottom:1px solid #E5E7EB;background:#FFF8F4;display:grid;grid-template-columns:120px 1fr;gap:8px" >
< input type = "text" value = "Year" style = "height:34px;border:1px solid #FFD8C2;border-radius:8px;background:#FFFCFA;padding:0 10px;font-size:12px;color:#374151" / >
< input type = "text" value = "Milestone description" style = "height:34px;border:1px solid #FFD8C2;border-radius:8px;background:#FFFCFA;padding:0 10px;font-size:12px;color:#374151" / >
< / div >
< / Show >
< div style = "padding:14px 16px" >
< p style = "margin:0 0 8px;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:0.05em;color:#9CA3AF" > { spec . equipmentLabel } < / p >
< div style = "display:flex;flex-wrap:wrap;gap:6px" >
{ spec . specialties . map ( ( item ) = > (
< span style = "height:24px;padding:0 8px;border-radius:6px;border:1px solid #E5E7EB;background:#F9FAFB;font-size:11px;font-weight:600;color:#374151;display:inline-flex;align-items:center" > { item } < / span >
) ) }
< / div >
< div style = "margin-top:12px" >
< p style = "margin:0 0 8px;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:0.05em;color:#9CA3AF" > Design Tools < / p >
< Show
when = { canEdit }
fallback = {
< div style = "display:flex;flex-wrap:wrap;gap:6px" >
{ ( portfolioDesignTools ( ) . length ? portfolioDesignTools ( ) : portfolioTools ) . map ( ( tool ) = > (
< span style = "height:24px;padding:0 8px;border-radius:6px;border:1px solid #E5E7EB;background:#F9FAFB;font-size:11px;font-weight:600;color:#374151;display:inline-flex;align-items:center" > { tool } < / span >
) ) }
< / div >
}
>
< div style = "display:grid;grid-template-columns:1fr auto;gap:8px;align-items:center" >
< input type = "text" placeholder = "Add tools (e.g. Figma, Photoshop, Illustrator)" style = "height:34px;border:1px solid #E5E7EB;border-radius:8px;background:white;padding:0 10px;font-size:12px;color:#374151" / >
< button type = "button" style = "height:32px;border-radius:8px;border:1px solid #E5E7EB;background:white;padding:0 12px;font-size:12px;font-weight:700;color:#374151" > + Add Tool < / button >
< / div >
< div style = "display:flex;flex-wrap:wrap;gap:6px;margin-top:8px" >
{ ( portfolioDesignTools ( ) . length ? portfolioDesignTools ( ) : portfolioTools ) . map ( ( tool ) = > (
< span style = "height:24px;padding:0 8px;border-radius:6px;border:1px solid #E5E7EB;background:#F9FAFB;font-size:11px;font-weight:600;color:#374151;display:inline-flex;align-items:center;gap:6px" >
{ tool }
< button type = "button" style = "border:none;background:none;color:#9CA3AF;cursor:pointer;font-size:11px;line-height:1" > ✕ < / button >
< / span >
) ) }
< / div >
< / Show >
< / div >
< / div >
< div style = "border-top:1px solid #F3F4F6" >
{ [ [ '2018' , 'Started professional career as ' + spec . roleLabel ] , [ '2020' , 'Completed 100+ successful projects' ] , [ '2022' , 'Won regional industry recognition' ] , [ '2024' , 'Joined Nxtgauge marketplace' ] ] . map ( ( [ yr , desc ] , i , arr ) = > (
< div style = { ` display:flex;gap:12px;align-items:flex-start;padding:10px 16px; ${ i < arr.length - 1 ? 'border-bottom:1px solid #F3F4F6' : '' } ` }>
< span style = "margin-top:2px;width:8px;height:8px;border-radius:999px;background:#FF5E13;flex-shrink:0" / >
< p style = "margin:0;font-size:11px;font-weight:700;color:#9CA3AF;min-width:32px" > { yr } < / p >
< p style = "margin:0;font-size:13px;color:#374151;line-height:1.5" > { desc } < / p >
< / div >
) ) }
< / div >
< / div >
< / Show >
< Show when = { showSection ( testimonialsTabKey ) } >
{ /* Testimonials */ }
< div style = "border-radius:12px;border:1px solid #E5E7EB;background:linear-gradient(180deg,#FFFFFF 0%,#FCFCFD 100%);box-shadow:0 1px 3px rgba(0,0,0,0.06);overflow:hidden" >
< div style = "padding:12px 16px;border-bottom:1px solid #E5E7EB;display:flex;align-items:center;justify-content:space-between;gap:10px" >
< p style = "margin:0;font-size:13px;font-weight:700;color:#111827;display:flex;align-items:center;gap:6px" > < Star size = { 14 } style = "color:#FF5E13" / > Testimonials < / p >
< Show
when = { testimonialsUnlocked }
fallback = { < span style = "font-size:11px;font-weight:700;color:#C2410C;background:#FFF1EB;border:1px solid #FFD8C2;border-radius:6px;padding:1px 7px" > Locked For New Profiles < / span > }
>
< span style = "font-size:11px;font-weight:600;color:#374151;background:#F3F4F6;border:1px solid #E5E7EB;border-radius:6px;padding:1px 7px" > 4.9 · 128 reviews < / span >
< / Show >
< / div >
< Show
when = { testimonialsUnlocked }
fallback = {
< div style = "padding:16px" >
< div style = "border:1px dashed #FFD8C2;background:#FFF8F4;border-radius:10px;padding:14px" >
< p style = "margin:0;font-size:13px;font-weight:700;color:#111827" > Testimonials will be activated automatically . < / p >
< p style = "margin:6px 0 0;font-size:12px;line-height:1.5;color:#6B7280" >
New professionals unlock testimonials after completing at least < strong > 3 jobs < / strong > and receiving at least < strong > 2 customer feedback entries < / strong > .
< / p >
< p style = "margin:8px 0 0;font-size:11px;color:#9CA3AF" > Current progress : { portfolioJobsCompleted } jobs completed • { portfolioFeedbackCount } feedback received < / p >
< / div >
< / div >
}
>
< div style = "display:grid;grid-template-columns:1fr 1fr;gap:10px;padding:10px" >
{ PORTFOLIO_TESTIMONIALS . map ( ( t , i ) = > (
< div style = "padding:14px 16px;border:1px solid #E5E7EB;border-radius:10px;background:#FFFFFF" >
< div style = "display:flex;justify-content:space-between;align-items:center" >
< p style = "margin:0;font-size:13px;font-weight:700;color:#111827" > { t . name } < / p >
< div style = "display:flex;gap:1px" >
{ Array . from ( { length : t.rating } ) . map ( ( ) = > < Star size = { 11 } style = "color:#F59E0B" fill = "#F59E0B" / > ) }
< / div >
< / div >
< p style = "margin:2px 0 0;font-size:11px;color:#9CA3AF" > { t . category } < / p >
< p style = "margin:8px 0 0;font-size:12px;color:#374151;line-height:1.5" > { t . text } < / p >
< / div >
) ) }
< / div >
< / Show >
< / div >
< / Show >
< Show when = { showSection ( faqsTabKey ) } >
{ /* FAQs */ }
< div style = "border-radius:12px;border:1px solid #E5E7EB;background:linear-gradient(180deg,#FFFFFF 0%,#FCFCFD 100%);box-shadow:0 1px 3px rgba(0,0,0,0.06);overflow:hidden" >
< div style = "padding:12px 16px;border-bottom:1px solid #E5E7EB" >
< p style = "margin:0;font-size:13px;font-weight:700;color:#111827;display:flex;align-items:center;gap:6px" > < HelpCircle size = { 14 } style = "color:#FF5E13" / > Frequently Asked Questions < / p >
< / div >
< Show when = { canEdit } >
< div style = "padding:10px 12px;border-bottom:1px solid #E5E7EB;background:#FFF8F4;display:grid;gap:8px" >
< input type = "text" value = "Add question" style = "height:34px;border:1px solid #FFD8C2;border-radius:8px;background:#FFFCFA;padding:0 10px;font-size:12px;color:#374151" / >
< textarea style = "min-height:66px;border:1px solid #FFD8C2;border-radius:8px;background:#FFFCFA;padding:8px 10px;font-size:12px;color:#374151;resize:vertical" > Add answer < / textarea >
< / div >
< / Show >
< For each = { spec . faqItems } > { ( faq , i ) = > (
< div style = { ` border-bottom: ${ i ( ) < spec . faqItems . length - 1 ? '1px solid #F3F4F6' : 'none' } ` } >
< button
type = "button"
onClick = { ( ) = > setOpenFaqIndex ( openFaqIndex ( ) === i ( ) ? null : i ( ) ) }
style = { ` width:100%;display:flex;justify-content:space-between;align-items:center;padding:12px 16px;background: ${ openFaqIndex ( ) === i ( ) ? '#FFF8F4' : 'none' } ;border:none;cursor:pointer;text-align:left ` }
>
< span style = "font-size:13px;font-weight:700;color:#111827" > { faq . q } < / span >
{ openFaqIndex ( ) === i ( ) ? < ChevronUp size = { 15 } style = "color:#374151;flex-shrink:0" / > : < ChevronDown size = { 15 } style = "color:#9CA3AF;flex-shrink:0" / > }
< / button >
< Show when = { openFaqIndex ( ) === i ( ) } >
< div style = "padding:0 16px 12px" >
< p style = "margin:0;font-size:13px;color:#374151;line-height:1.6" > { faq . a } < / p >
< / div >
< / Show >
< / div >
) } < / For >
< / div >
< / Show >
< / Show >
< div style = "border-radius:12px;border:1px solid #E5E7EB;background:white;padding:10px 12px;display:flex;align-items:center;justify-content:space-between;gap:10px" >
< p style = "margin:0;font-size:12px;color:#6B7280" >
< Show when = { ! isPreviewMode } fallback = { < span > Final review mode : check all tabs , then submit for approval . < / span > } >
< span > Step { activePortfolioStepIndex + 1 } of { portfolioStepKeys . length } < / span >
< / Show >
< / p >
< div style = "display:flex;align-items:center;gap:8px" >
< button
type = "button"
onClick = { ( ) = > {
if ( isPreviewMode ) {
setPortfolioTopTab ( 'my_portfolio' ) ;
goToPortfolioStep ( portfolioStepKeys . length - 1 ) ;
return ;
}
goToPortfolioStep ( activePortfolioStepIndex - 1 ) ;
} }
disabled = { ! isPreviewMode && activePortfolioStepIndex === 0 }
style = { ` height:32px;border-radius:8px;border:1px solid #E5E7EB;background:white;padding:0 12px;font-size:12px;font-weight:700;color:#374151;cursor: ${ ( ! isPreviewMode && activePortfolioStepIndex === 0 ) ? 'not-allowed' : 'pointer' } ;opacity: ${ ( ! isPreviewMode && activePortfolioStepIndex === 0 ) ? 0.5 : 1 } ` }
>
Previous
< / button >
< Show
when = { ! isPreviewMode }
fallback = {
< button type = "button" onClick = { ( ) = > setPortfolioTopTab ( 'my_portfolio' ) } style = "height:32px;border-radius:8px;border:1px solid #E5E7EB;background:white;padding:0 12px;font-size:12px;font-weight:700;color:#374151" >
Back To Edit
< / button >
}
>
< button
type = "button"
onClick = { ( ) = > {
const currentStepKey = portfolioStepKeys [ activePortfolioStepIndex ] ;
if ( ! validatePortfolioStep ( currentStepKey ) ) return ;
if ( activePortfolioStepIndex >= portfolioStepKeys . length - 1 ) {
setPortfolioTopTab ( 'preview' ) ;
return ;
}
goToPortfolioStep ( activePortfolioStepIndex + 1 ) ;
} }
style = "height:32px;border-radius:8px;border:none;background:#03004E;color:white;padding:0 12px;font-size:12px;font-weight:700"
>
{ activePortfolioStepIndex >= portfolioStepKeys . length - 1 ? 'Final Preview' : 'Next' }
< / button >
< / Show >
< / div >
< / div >
< / div >
) ;
} ;
const renderCustomerContent = ( ) = > {
const tab = resolvedTabKey ( ) ;
if ( customerKey ( ) === 'my dashboard' ) {
if ( tab === 'recent requirements' ) {
return (
< div style = "border:1px solid #E5E7EB;background:white;border-radius:16px;padding:14px;box-shadow:0 1px 4px rgba(0,0,0,0.06)" >
< p style = "margin:0;font-size:14px;font-weight:700;color:#111827" > Recent Requirements < / p >
< div style = "margin-top:10px;display:grid;gap:8px" >
< For each = { requirementRows ( ) } > { ( row ) = > {
const chip = statusChip ( row . status ) ;
return (
< div style = "display:grid;grid-template-columns:2fr 1fr 1fr 1fr;gap:8px;padding:8px;border:1px solid #E5E7EB;border-radius:8px;background:#F9FAFB" >
< span style = "font-size:12px;font-weight:700;color:#111827" > { row . title } < / span >
< span style = "font-size:12px;color:#6B7280" > { row . amount } < / span >
< span style = { ` display:inline-flex;align-items:center;justify-content:center;height:22px;border-radius:999px;background: ${ chip . bg } ;color: ${ chip . c } ;font-size:11px;font-weight:700;text-transform:uppercase ` } > { row . status } < / span >
< span style = "font-size:12px;color:#374151" > { row . responses } responses < / span >
< / div >
) ;
} } < / For >
< / div >
< / div >
) ;
}
if ( tab === 'quick actions' ) {
return (
< div style = "display:grid;grid-template-columns:1fr 1fr;gap:10px" >
< div style = "border:1px solid #E5E7EB;background:white;border-radius:16px;padding:14px;box-shadow:0 1px 4px rgba(0,0,0,0.06)" >
< p style = "margin:0;font-size:14px;font-weight:700;color:#111827" > Quick Actions < / p >
< div style = "display:grid;gap:8px;margin-top:8px" >
{ [ 'Post New Requirement' , 'View Responses' , 'Buy Credits' , 'Explore Professionals' ] . map ( ( a , i ) = > (
< button type = "button" style = { ` height:34px;border-radius:8px;border: ${ i === 0 ? 'none' : '1px solid #E5E7EB' } ;background: ${ i === 0 ? '#FF5E13' : 'white' } ;color: ${ i === 0 ? 'white' : '#374151' } ;font-size:12px;font-weight:700;cursor:pointer;text-align:left;padding:0 10px ` } >
{ a }
< / button >
) ) }
< / div >
< / div >
< div style = "border:1px solid #E5E7EB;background:white;border-radius:16px;padding:14px;box-shadow:0 1px 4px rgba(0,0,0,0.06)" >
< p style = "margin:0;font-size:14px;font-weight:700;color:#111827" > Recent Activity < / p >
< div style = "margin-top:8px;display:grid;gap:8px" >
{ [ 'Requirement posted' , 'New response received' , 'Credit purchase completed' ] . map ( ( a ) = > < div style = "padding:8px;border:1px solid #E5E7EB;border-radius:8px;background:#F9FAFB;font-size:12px;color:#374151" > { a } < / div > ) }
< / div >
< / div >
< / div >
) ;
}
return (
< div style = "display:flex;flex-direction:column;gap:10px" >
< Show when = { ! bothApprovalsApproved ( ) } >
< div style = "border:1px solid #E5E7EB;background:white;border-radius:12px;padding:12px;box-shadow:0 1px 4px rgba(0,0,0,0.04)" >
< p style = "margin:0;font-size:14px;font-weight:800;color:#111827" > Complete Verification < / p >
< p style = "margin:6px 0 0;font-size:12px;color:#6B7280;line-height:1.5" >
To start receiving opportunities , submit your profile and portfolio for admin approval .
< / p >
< div style = "display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-top:10px" >
< div style = "border:1px solid #E5E7EB;border-radius:8px;background:#F9FAFB;padding:8px" >
< p style = "margin:0;font-size:11px;color:#6B7280" > Step 1 < / p >
< p style = "margin:4px 0 0;font-size:12px;font-weight:800;color:#111827" > Profile : { approvalTone ( profileApprovalState ( ) ) . label } < / p >
< / div >
< div style = "border:1px solid #E5E7EB;border-radius:8px;background:#F9FAFB;padding:8px" >
< p style = "margin:0;font-size:11px;color:#6B7280" > Step 2 < / p >
< p style = "margin:4px 0 0;font-size:12px;font-weight:800;color:#111827" > Portfolio : { approvalTone ( portfolioApprovalState ( ) ) . label } < / p >
< / div >
< / div >
< div style = "display:flex;gap:8px;flex-wrap:wrap;margin-top:10px" >
< button type = "button" onClick = { ( ) = > { props . onSidebarSelect ( 'My Profile' ) ; props . onTabSelect ( 'basic information' ) ; } } style = "height:32px;border-radius:8px;border:1px solid #E5E7EB;background:white;padding:0 12px;font-size:12px;font-weight:700;color:#374151" > Open My Profile < / button >
< Show when = { isProfessionalRole ( ) } >
< button type = "button" onClick = { ( ) = > { props . onSidebarSelect ( 'My Portfolio' ) ; props . onTabSelect ( 'about' ) ; } } style = "height:32px;border-radius:8px;border:1px solid #E5E7EB;background:white;padding:0 12px;font-size:12px;font-weight:700;color:#374151" > Open My Portfolio < / button >
< / Show >
< button type = "button" onClick = { ( ) = > props . onSidebarSelect ( 'Verification' ) } style = "height:32px;border-radius:8px;border:none;background:#03004E;color:white;padding:0 12px;font-size:12px;font-weight:700" > Open Verification < / button >
< / div >
< / div >
< / Show >
< div style = "border:1px solid #E5E7EB;background:white;border-radius:12px;padding:10px 12px;box-shadow:0 1px 4px rgba(0,0,0,0.04)" >
< p style = "margin:0;font-size:11px;letter-spacing:0.04em;text-transform:uppercase;color:#6B7280" > Widget Customization < / p >
< p style = "margin:4px 0 0;font-size:12px;color:#374151" > Drag and drop cards below to reorder your dashboard widgets . < / p >
< / div >
< div style = "display:grid;grid-template-columns:2fr 1fr 1fr;gap:10px" >
< div style = "border:1px solid #E5E7EB;background:white;border-radius:16px;padding:14px;box-shadow:0 1px 4px rgba(0,0,0,0.06)" >
Wire user dashboard to real APIs — profile, credits, leads, requirements, jobs
- dashboard.tsx: fetch session for real user name/ID/role, fallback to
localStorage; show dashboard with role defaults when runtime config unavailable
- DashboardDesignPreview: add liveData prop; createResource for credits,
marketplace, lead requests, customer requirements, jobs, and profile
- Profile form: inputs now track state via profileFormData signal; pre-filled
from GET /api/${prefix}/profile/me; Save Changes PATCHes real endpoint
- Lead actions: Send Request POSTs to /api/${prefix}/leads/request; Cancel
DELETEs /api/${prefix}/leads/requests/{id}; both refetch after completion
- Requirement submit: POSTs to /api/customers/requirements then submits for approval
- Replace hardcoded "Alex" with real session name; credits from wallet balance API
- Fix launch.json PATH so npm is found in sh shell
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 21:13:54 +02:00
< p style = "margin:0;font-size:32px;font-weight:800;color:#111827;line-height:1.1" > Welcome back , { props . liveData ? . userName ? ? 'Alex' } < / p >
2026-04-05 16:52:02 +02:00
< p style = "margin:6px 0 0;font-size:13px;color:#6B7280" > To start receiving opportunities , complete My Profile and My Portfolio , then submit both for approval . < / p >
< div style = "display:flex;gap:8px;flex-wrap:wrap;margin-top:10px" >
< button type = "button" onClick = { ( ) = > { props . onSidebarSelect ( 'My Profile' ) ; props . onTabSelect ( 'basic information' ) ; } } style = "height:30px;border-radius:8px;border:1px solid #E5E7EB;background:white;padding:0 10px;font-size:11px;font-weight:700;color:#374151" > Fill My Profile < / button >
< Show when = { isProfessionalRole ( ) } >
< button type = "button" onClick = { ( ) = > { props . onSidebarSelect ( 'My Portfolio' ) ; props . onTabSelect ( 'about' ) ; } } style = "height:30px;border-radius:8px;border:1px solid #E5E7EB;background:white;padding:0 10px;font-size:11px;font-weight:700;color:#374151" > Fill My Portfolio < / button >
< / Show >
< button type = "button" onClick = { ( ) = > props . onSidebarSelect ( 'Verification' ) } style = "height:30px;border-radius:8px;border:none;background:#03004E;color:white;padding:0 10px;font-size:11px;font-weight:700" > Submit For Approval < / button >
< / div >
< / div >
< div style = "border:1px solid #E5E7EB;background:white;border-radius:16px;padding:14px;box-shadow:0 1px 4px rgba(0,0,0,0.06)" >
< p style = "margin:0;font-size:11px;letter-spacing:0.04em;color:#6B7280;text-transform:uppercase" > Profile Status < / p >
< p style = "margin:8px 0 0;font-size:34px;font-weight:800;color:#111827" > 85 % < / p >
< / div >
< div style = "border:1px solid #E5E7EB;background:white;border-radius:16px;padding:14px;box-shadow:0 1px 4px rgba(0,0,0,0.06)" >
< p style = "margin:0;font-size:11px;letter-spacing:0.04em;text-transform:uppercase;color:#6B7280" > Credits Balance < / p >
Wire user dashboard to real APIs — profile, credits, leads, requirements, jobs
- dashboard.tsx: fetch session for real user name/ID/role, fallback to
localStorage; show dashboard with role defaults when runtime config unavailable
- DashboardDesignPreview: add liveData prop; createResource for credits,
marketplace, lead requests, customer requirements, jobs, and profile
- Profile form: inputs now track state via profileFormData signal; pre-filled
from GET /api/${prefix}/profile/me; Save Changes PATCHes real endpoint
- Lead actions: Send Request POSTs to /api/${prefix}/leads/request; Cancel
DELETEs /api/${prefix}/leads/requests/{id}; both refetch after completion
- Requirement submit: POSTs to /api/customers/requirements then submits for approval
- Replace hardcoded "Alex" with real session name; credits from wallet balance API
- Fix launch.json PATH so npm is found in sh shell
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 21:13:54 +02:00
< p style = "margin:8px 0 0;font-size:34px;font-weight:800;line-height:1;color:#111827" > { leadCredits ( ) . toLocaleString ( 'en-IN' ) } < / p >
2026-04-05 16:52:02 +02:00
< / div >
< / div >
< div style = "display:grid;grid-template-columns:repeat(5,minmax(0,1fr));gap:10px" >
< For each = { dashboardWidgetOrder ( ) . slice ( 0 , 5 ) } >
{ ( w ) = > (
< div
draggable
onDragStart = { ( e ) = > {
setDraggingDashboardWidget ( w ) ;
e . dataTransfer ? . setData ( 'text/plain' , w ) ;
} }
onDragOver = { ( e ) = > e . preventDefault ( ) }
onDrop = { ( e ) = > {
e . preventDefault ( ) ;
moveDashboardWidget ( draggingDashboardWidget ( ) || '' , w ) ;
setDraggingDashboardWidget ( null ) ;
} }
style = { ` border:1px solid #E5E7EB;background: ${ draggingDashboardWidget ( ) === w ? '#FFF8F4' : 'white' } ;border-radius:12px;padding:10px;min-height:92px;box-shadow:0 1px 3px rgba(0,0,0,0.05);cursor:grab ` }
>
< p style = "margin:0;font-size:11px;letter-spacing:0.04em;text-transform:uppercase;color:#6B7280" > { titleCase ( w ) } < / p >
< p style = "margin:10px 0 0;font-size:22px;font-weight:800;color:#111827" > 42 < / p >
< / div >
) }
< / For >
< / div >
< / div >
) ;
}
if ( customerKey ( ) . includes ( 'profile' ) ) {
const spec = currentProfileSpec ( ) ;
const selectedTab = spec . tabs . find ( ( item ) = > normalizeTabKey ( item ) === normalizeTabKey ( tab ) ) || spec . tabs [ 0 ] || 'basic info' ;
const selectedTabKey = normalizeTabKey ( selectedTab ) ;
const defaultFields = [ 'First Name' , 'Last Name' , 'Email Address' , 'Mobile Number' , 'City' ] ;
const fieldsForTab = spec . tabFields [ selectedTab ] || spec . tabFields [ spec . tabs [ 0 ] || '' ] || defaultFields ;
const isPreferencesTab = normalizeTabKey ( selectedTab ) === 'preferences' ;
const isDocumentsTab = selectedTabKey === 'documents' ;
const isRequiredField = ( field : string ) = > ! /\(optional\)|optional/i . test ( field ) ;
const requiredCount = fieldsForTab . filter ( ( f ) = > isRequiredField ( f ) ) . length ;
const profileState = ( ) = > approvalTone ( profileApprovalState ( ) ) ;
const renderField = ( field : string ) = > {
const required = isRequiredField ( field ) ;
const isSelect = /country|mode|type|category|level|gender|industry|subjects|platforms|budget|response time/i . test ( field ) ;
const isSpecialitiesField = /specialit|specializ|skills/i . test ( field ) ;
const cleanLabel = String ( field || '' ) . replace ( /\(optional\)/ig , '' ) . trim ( ) ;
const safeLabel = cleanLabel || 'value' ;
const key = safeLabel . toLowerCase ( ) ;
const labelText = ( ( ) = > {
if ( key . includes ( 'specialt' ) || key . includes ( 'specialit' ) || key . includes ( 'specializ' ) ) return 'Specialities' ;
return safeLabel ;
} ) ( ) ;
const isCityField = key === 'city' ;
const placeholderText = ( ( ) = > {
if ( key . includes ( 'gender' ) ) return 'Select gender' ;
if ( key . includes ( 'address line 1' ) ) return 'Enter address line 1' ;
if ( key . includes ( 'address line 2' ) ) return 'Enter address line 2' ;
if ( key === 'city' ) return 'Chennai' ;
if ( key === 'state' ) return 'Enter state' ;
if ( key === 'address' ) return 'Enter full address in Chennai' ;
if ( key . includes ( 'address proof' ) ) return 'Upload address proof (PDF/JPG/PNG)' ;
if ( key . includes ( 'proof' ) || key . includes ( 'certificate' ) || key . includes ( 'document' ) ) return ` Upload ${ labelText } (PDF/JPG/PNG) ` ;
if ( key . includes ( 'pin code' ) || key . includes ( 'pincode' ) ) return 'Enter 6-digit pincode' ;
if ( key . includes ( 'area' ) ) return 'Enter area in Chennai' ;
if ( key . includes ( 'place' ) ) return 'Enter place in Chennai' ;
if ( isSpecialitiesField ) return 'e.g. Wedding, Product, Portrait' ;
return ` ${ isSelect ? 'Select' : 'Enter' } ${ labelText . toLowerCase ( ) } ` ;
} ) ( ) ;
return (
< div style = "display:flex;flex-direction:column;gap:6px" >
< p style = "margin:0;font-size:11px;font-weight:700;color:#6B7280;letter-spacing:0.01em;text-transform:none" >
{ labelText }
< Show when = { required } >
< span style = "color:#FF5E13;margin-left:4px" > * < / span >
< / Show >
< / p >
< div style = "position:relative" >
< input
type = "text"
Wire user dashboard to real APIs — profile, credits, leads, requirements, jobs
- dashboard.tsx: fetch session for real user name/ID/role, fallback to
localStorage; show dashboard with role defaults when runtime config unavailable
- DashboardDesignPreview: add liveData prop; createResource for credits,
marketplace, lead requests, customer requirements, jobs, and profile
- Profile form: inputs now track state via profileFormData signal; pre-filled
from GET /api/${prefix}/profile/me; Save Changes PATCHes real endpoint
- Lead actions: Send Request POSTs to /api/${prefix}/leads/request; Cancel
DELETEs /api/${prefix}/leads/requests/{id}; both refetch after completion
- Requirement submit: POSTs to /api/customers/requirements then submits for approval
- Replace hardcoded "Alex" with real session name; credits from wallet balance API
- Fix launch.json PATH so npm is found in sh shell
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 21:13:54 +02:00
value = { profileFormData ( ) [ field ] ? ? ( isCityField ? 'Chennai' : '' ) }
2026-04-05 16:52:02 +02:00
readOnly = { isCityField }
Wire user dashboard to real APIs — profile, credits, leads, requirements, jobs
- dashboard.tsx: fetch session for real user name/ID/role, fallback to
localStorage; show dashboard with role defaults when runtime config unavailable
- DashboardDesignPreview: add liveData prop; createResource for credits,
marketplace, lead requests, customer requirements, jobs, and profile
- Profile form: inputs now track state via profileFormData signal; pre-filled
from GET /api/${prefix}/profile/me; Save Changes PATCHes real endpoint
- Lead actions: Send Request POSTs to /api/${prefix}/leads/request; Cancel
DELETEs /api/${prefix}/leads/requests/{id}; both refetch after completion
- Requirement submit: POSTs to /api/customers/requirements then submits for approval
- Replace hardcoded "Alex" with real session name; credits from wallet balance API
- Fix launch.json PATH so npm is found in sh shell
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 21:13:54 +02:00
onInput = { ( e ) = > ! isCityField && setProfileFormData ( ( prev ) = > ( { . . . prev , [ field ] : e . currentTarget . value } ) ) }
2026-04-05 16:52:02 +02:00
placeholder = { placeholderText }
style = "width:100%;height:36px;border:1px solid #E5E7EB;border-radius:8px;background:white;padding:0 30px 0 10px;font-size:12px;color:#111827;outline:none"
/ >
< Show when = { isSelect } >
< span style = "position:absolute;right:10px;top:50%;transform:translateY(-50%);color:#9CA3AF;font-size:12px" > ▾ < / span >
< / Show >
< / div >
< / div >
) ;
} ;
if ( selectedTab === 'settings' ) {
const settingsTabs : Array < { key : 'change_password' | 'notifications' | 'privacy' ; label : string } > = [
{ key : 'change_password' , label : 'Change Password' } ,
{ key : 'notifications' , label : 'Notifications' } ,
{ key : 'privacy' , label : 'Privacy' } ,
] ;
const activeSettingsTab = profileSettingsTab ( ) ;
return (
< div style = "display:grid;grid-template-columns:1fr;gap:10px" >
< div style = "border:1px solid #E5E7EB;background:white;border-radius:16px;padding:14px;box-shadow:0 1px 4px rgba(0,0,0,0.06)" >
< p style = "margin:0;font-size:13px;font-weight:700;color:#111827" > Settings < / p >
< div style = "display:flex;align-items:center;gap:20px;margin-top:10px;border-bottom:1px solid #E5E7EB" >
< For each = { settingsTabs } >
{ ( item ) = > (
< button
type = "button"
onClick = { ( ) = > setProfileSettingsTab ( item . key ) }
style = { ` padding:0 0 10px;font-size:13px;font-weight:500;background:none;border:none;cursor:pointer; ${ activeSettingsTab === item . key ? 'color:#FF5E13;border-bottom:2px solid #FF5E13;margin-bottom:-1px' : 'color:#6B7280' } ` }
>
{ item . label }
< / button >
) }
< / For >
< / div >
< Show when = { activeSettingsTab === 'change_password' } >
< div style = "display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-top:10px" >
< div style = "display:flex;flex-direction:column;gap:6px" >
< p style = "margin:0;font-size:11px;font-weight:700;color:#6B7280;letter-spacing:0.04em;text-transform:uppercase" > Current Password < span style = "color:#FF5E13;margin-left:4px" > * < / span > < / p >
< input type = "password" placeholder = "Enter current password" style = "height:36px;border:1px solid #E5E7EB;border-radius:8px;background:white;padding:0 10px;font-size:12px;color:#111827;outline:none" / >
< / div >
< div style = "display:flex;flex-direction:column;gap:6px" >
< p style = "margin:0;font-size:11px;font-weight:700;color:#6B7280;letter-spacing:0.04em;text-transform:uppercase" > New Password < span style = "color:#FF5E13;margin-left:4px" > * < / span > < / p >
< input type = "password" placeholder = "Enter new password" style = "height:36px;border:1px solid #E5E7EB;border-radius:8px;background:white;padding:0 10px;font-size:12px;color:#111827;outline:none" / >
< / div >
< / div >
< div style = "display:flex;flex-direction:column;gap:6px;margin-top:10px" >
< p style = "margin:0;font-size:11px;font-weight:700;color:#6B7280;letter-spacing:0.04em;text-transform:uppercase" > Confirm Password < span style = "color:#FF5E13;margin-left:4px" > * < / span > < / p >
< input type = "password" placeholder = "Confirm new password" style = "height:36px;border:1px solid #E5E7EB;border-radius:8px;background:white;padding:0 10px;font-size:12px;color:#111827;outline:none" / >
< / div >
< / Show >
< Show when = { activeSettingsTab === 'notifications' } >
< div style = "display:grid;gap:8px;margin-top:10px" >
{ [
[ 'email_updates' , 'Email Updates' ] ,
[ 'in_app_alerts' , 'In-app Alerts' ] ,
[ 'verification_reminders' , 'Verification Reminders' ] ,
] . map ( ( [ key , title ] ) = > (
< div style = "display:flex;justify-content:space-between;align-items:center;gap:10px;padding:10px;border:1px solid #E5E7EB;border-radius:10px;background:#F9FAFB" >
< p style = "margin:0;font-size:12px;font-weight:700;color:#111827" > { title } < / p >
< button
type = "button"
onClick = { ( ) = > toggleProfileSetting ( String ( key ) ) }
style = { ` height:30px;border-radius:999px;padding:0 12px;font-size:11px;font-weight:700;cursor:pointer;border:1px solid ${ profileSettingToggles ( ) [ String ( key ) ] ? '#C7D2FE' : '#D1D5DB' } ;background: ${ profileSettingToggles ( ) [ String ( key ) ] ? '#EEF2FF' : 'white' } ;color: ${ profileSettingToggles ( ) [ String ( key ) ] ? '#03004E' : '#374151' } ` }
>
{ profileSettingToggles ( ) [ String ( key ) ] ? 'On' : 'Off' }
< / button >
< / div >
) ) }
< / div >
< / Show >
< Show when = { activeSettingsTab === 'privacy' } >
< div style = "display:grid;gap:8px;margin-top:10px" >
{ [
[ 'profile_visibility' , 'Profile Visibility' ] ,
[ 'data_sharing_consent' , 'Data Sharing Consent' ] ,
] . map ( ( [ key , title ] ) = > (
< div style = "display:flex;justify-content:space-between;align-items:center;gap:10px;padding:10px;border:1px solid #E5E7EB;border-radius:10px;background:#F9FAFB" >
< p style = "margin:0;font-size:12px;font-weight:700;color:#111827" > { title } < / p >
< button
type = "button"
onClick = { ( ) = > toggleProfileSetting ( String ( key ) ) }
style = { ` height:30px;border-radius:999px;padding:0 12px;font-size:11px;font-weight:700;cursor:pointer;border:1px solid ${ profileSettingToggles ( ) [ String ( key ) ] ? '#C7D2FE' : '#D1D5DB' } ;background: ${ profileSettingToggles ( ) [ String ( key ) ] ? '#EEF2FF' : 'white' } ;color: ${ profileSettingToggles ( ) [ String ( key ) ] ? '#03004E' : '#374151' } ` }
>
{ profileSettingToggles ( ) [ String ( key ) ] ? 'On' : 'Off' }
< / button >
< / div >
) ) }
< div style = "display:flex;justify-content:space-between;align-items:center;gap:10px;padding:10px;border:1px solid #FFE2D3;border-radius:10px;background:#FFF8F4" >
< div >
< p style = "margin:0;font-size:12px;font-weight:700;color:#111827" > Delete Account < / p >
< p style = "margin:2px 0 0;font-size:11px;color:#6B7280" > Permanently remove your account and data . < / p >
< / div >
< button
type = "button"
onClick = { ( ) = > setShowDeleteAccountModal ( true ) }
style = "height:30px;border-radius:8px;padding:0 12px;font-size:11px;font-weight:700;cursor:pointer;border:1px solid #FFD0BA;background:white;color:#FF5E13"
>
Delete
< / button >
< / div >
< / div >
< / Show >
< div style = "display:flex;justify-content:flex-end;gap:8px;margin-top:12px;padding-top:12px;border-top:1px solid #E5E7EB" >
< button type = "button" style = "height:34px;border-radius:8px;border:1px solid #D1D5DB;background:white;color:#374151;padding:0 14px;font-size:12px;font-weight:700" > Cancel < / button >
< button type = "button" style = "height:34px;border-radius:8px;border:none;background:#03004E;color:white;padding:0 14px;font-size:12px;font-weight:700" >
{ activeSettingsTab === 'change_password' ? 'Update Password' : 'Save Settings' }
< / button >
< / div >
< / div >
< Show when = { showDeleteAccountModal ( ) } >
< div style = "position:fixed;inset:0;background:rgba(15,23,42,0.4);display:flex;align-items:center;justify-content:center;z-index:50;padding:16px" >
< div style = "width:min(460px,100%);border:1px solid #E5E7EB;background:white;border-radius:14px;box-shadow:0 10px 30px rgba(0,0,0,0.18);padding:16px" >
< div style = "display:flex;align-items:center;gap:8px" >
< span style = "width:22px;height:22px;border-radius:999px;background:#FFF1EB;color:#FF5E13;display:inline-flex;align-items:center;justify-content:center;font-size:13px;font-weight:800" > ! < / span >
< p style = "margin:0;font-size:15px;font-weight:800;color:#111827" > Delete account ? < / p >
< / div >
< p style = "margin:10px 0 0;font-size:13px;color:#374151;line-height:1.5" >
This will permanently remove your account . This action cannot be undone .
< / p >
< div style = "display:flex;justify-content:flex-end;gap:8px;margin-top:14px" >
< button
type = "button"
onClick = { ( ) = > setShowDeleteAccountModal ( false ) }
style = "height:34px;border-radius:8px;border:1px solid #D1D5DB;background:white;color:#374151;padding:0 14px;font-size:12px;font-weight:700"
>
Cancel
< / button >
< button
type = "button"
onClick = { ( ) = > setShowDeleteAccountModal ( false ) }
style = "height:34px;border-radius:8px;border:none;background:#03004E;color:white;padding:0 14px;font-size:12px;font-weight:700"
>
Yes , Delete Account
< / button >
< / div >
< / div >
< / div >
< / Show >
< / div >
) ;
}
return (
< div style = "display:grid;grid-template-columns:2fr 1fr;gap:10px" >
< div style = "border:1px solid #E5E7EB;background:white;border-radius:16px;padding:14px;box-shadow:0 1px 4px rgba(0,0,0,0.06)" >
< div style = "display:flex;justify-content:space-between;align-items:center;gap:10px" >
< p style = "margin:0;font-size:13px;font-weight:700;color:#111827" > { isPreferencesTab ? 'Preference Details' : titleCase ( selectedTab ) } < / p >
< span style = "height:22px;padding:0 8px;border-radius:999px;border:1px solid #DDEBFF;background:#EEF4FF;display:inline-flex;align-items:center;font-size:10px;font-weight:700;color:#03004E" >
{ fieldsForTab . length } fields
< / span >
< / div >
< Show
when = { isDocumentsTab }
fallback = {
< Show
when = { isPreferencesTab }
fallback = {
< div style = "display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px;margin-top:10px" >
< For each = { fieldsForTab } > { ( field ) = > renderField ( field ) } < / For >
< / div >
}
>
< div style = "display:grid;grid-template-columns:1fr;gap:8px;margin-top:10px;max-width:560px" >
< For each = { fieldsForTab } > { ( field ) = > renderField ( field ) } < / For >
< / div >
< / Show >
}
>
< div style = "display:grid;grid-template-columns:1fr;gap:10px;margin-top:10px;max-width:560px" >
< div style = "display:flex;flex-direction:column;gap:6px" >
< p style = "margin:0;font-size:11px;font-weight:700;color:#6B7280;letter-spacing:0.01em;text-transform:none" >
Document type < span style = "color:#FF5E13;margin-left:4px" > * < / span >
< / p >
< select
value = { profileDocumentType ( ) }
onChange = { ( e ) = > setProfileDocumentType ( e . currentTarget . value ) }
style = "width:100%;height:36px;border:1px solid #E5E7EB;border-radius:8px;background:white;padding:0 10px;font-size:12px;color:#111827;outline:none"
>
< option > Aadhar Card < / option >
< option > PAN Card < / option >
< option > Passport < / option >
< option > Driving License < / option >
< / select >
< / div >
< div style = "display:flex;flex-direction:column;gap:6px" >
< p style = "margin:0;font-size:11px;font-weight:700;color:#6B7280;letter-spacing:0.01em;text-transform:none" >
Upload document < span style = "color:#FF5E13;margin-left:4px" > * < / span >
< / p >
< input
type = "text"
value = ""
placeholder = { ` Upload ${ profileDocumentType ( ) . toLowerCase ( ) } (PDF/JPG/PNG) ` }
style = "width:100%;height:36px;border:1px solid #E5E7EB;border-radius:8px;background:white;padding:0 10px;font-size:12px;color:#111827;outline:none"
/ >
< / div >
< / div >
< / Show >
< div style = "display:flex;justify-content:flex-end;gap:8px;margin-top:12px;padding-top:12px;border-top:1px solid #E5E7EB" >
Wire user dashboard to real APIs — profile, credits, leads, requirements, jobs
- dashboard.tsx: fetch session for real user name/ID/role, fallback to
localStorage; show dashboard with role defaults when runtime config unavailable
- DashboardDesignPreview: add liveData prop; createResource for credits,
marketplace, lead requests, customer requirements, jobs, and profile
- Profile form: inputs now track state via profileFormData signal; pre-filled
from GET /api/${prefix}/profile/me; Save Changes PATCHes real endpoint
- Lead actions: Send Request POSTs to /api/${prefix}/leads/request; Cancel
DELETEs /api/${prefix}/leads/requests/{id}; both refetch after completion
- Requirement submit: POSTs to /api/customers/requirements then submits for approval
- Replace hardcoded "Alex" with real session name; credits from wallet balance API
- Fix launch.json PATH so npm is found in sh shell
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 21:13:54 +02:00
< button type = "button" onClick = { ( ) = > setProfileFormData ( { } ) } style = "height:34px;border-radius:8px;border:1px solid #D1D5DB;background:white;color:#374151;padding:0 14px;font-size:12px;font-weight:700" > Cancel < / button >
< button
type = "button"
onClick = { ( ) = > {
if ( ! hasLive ( ) ) return ;
const data = profileFormData ( ) ;
setProfileSaving ( true ) ;
const fn = data [ 'First Name' ] || '' ;
const ln = data [ 'Last Name' ] || '' ;
const display = data [ 'Business Name' ] || data [ 'Company Name' ] || data [ 'Contact Person Name' ] || [ fn , ln ] . filter ( Boolean ) . join ( ' ' ) ;
fetch ( ` ${ GW } /api/ ${ livePrefix ( ) } /profile/me ` , {
method : 'PATCH' ,
credentials : 'include' ,
headers : { 'Content-Type' : 'application/json' } ,
body : JSON.stringify ( {
display_name : display || undefined ,
bio : data [ 'Bio' ] || undefined ,
location : data [ 'City' ] || undefined ,
area : data [ 'Area' ] || undefined ,
state : data [ 'State' ] || undefined ,
pin_code : data [ 'PIN Code' ] || undefined ,
} ) ,
} ) . then ( ( r ) = > {
setProfileSaving ( false ) ;
setProfileSaveStatus ( r . ok ? 'saved' : 'error' ) ;
setTimeout ( ( ) = > setProfileSaveStatus ( 'idle' ) , 2500 ) ;
} ) . catch ( ( ) = > { setProfileSaving ( false ) ; setProfileSaveStatus ( 'error' ) ; } ) ;
} }
style = { ` height:34px;border-radius:8px;border:none;background: ${ profileSaveStatus ( ) === 'error' ? '#DC2626' : profileSaveStatus ( ) === 'saved' ? '#16A34A' : '#03004E' } ;color:white;padding:0 14px;font-size:12px;font-weight:700 ` }
>
{ profileSaving ( ) ? 'Saving…' : profileSaveStatus ( ) === 'saved' ? 'Saved ✓' : profileSaveStatus ( ) === 'error' ? 'Error — Retry' : 'Save Changes' }
< / button >
2026-04-05 16:52:02 +02:00
< / div >
< / div >
< div style = "border:1px solid #E5E7EB;background:white;border-radius:16px;padding:14px;box-shadow:0 1px 4px rgba(0,0,0,0.06)" >
< p style = "margin:0;font-size:11px;letter-spacing:0.04em;text-transform:uppercase;color:#6B7280" > What To Do Next < / p >
< p style = "margin:8px 0 0;font-size:18px;font-weight:800;color:#111827;line-height:1.3" > Complete profile and portfolio to unlock leads < / p >
< p style = "margin:8px 0 0;font-size:12px;color:#6B7280;line-height:1.5" > Finish My Profile and My Portfolio , then submit both for admin approval . Leads will unlock only after approval . < / p >
< div style = "display:grid;gap:8px;margin-top:10px" >
< div style = "display:flex;align-items:center;justify-content:space-between;gap:8px;padding:8px;border:1px solid #E5E7EB;border-radius:8px;background:#F9FAFB" >
< span style = "font-size:11px;font-weight:700;color:#374151" > My Profile < / span >
< span style = "font-size:11px;font-weight:800;color:#111827" > { approvalTone ( profileApprovalState ( ) ) . label } < / span >
< / div >
< Show when = { isProfessionalRole ( ) } >
< div style = "display:flex;align-items:center;justify-content:space-between;gap:8px;padding:8px;border:1px solid #E5E7EB;border-radius:8px;background:#F9FAFB" >
< span style = "font-size:11px;font-weight:700;color:#374151" > My Portfolio < / span >
< span style = "font-size:11px;font-weight:800;color:#111827" > { approvalTone ( portfolioApprovalState ( ) ) . label } < / span >
< / div >
< / Show >
< / div >
< div style = "display:grid;gap:8px;margin-top:10px" >
< button type = "button" onClick = { ( ) = > { props . onSidebarSelect ( 'My Profile' ) ; props . onTabSelect ( 'basic information' ) ; } } style = "height:32px;border:1px solid #D1D5DB;border-radius:8px;background:white;color:#374151;padding:0 12px;font-size:12px;font-weight:700" > Fill My Profile < / button >
< Show when = { isProfessionalRole ( ) } >
< button type = "button" onClick = { ( ) = > { props . onSidebarSelect ( 'My Portfolio' ) ; props . onTabSelect ( 'about' ) ; } } style = "height:32px;border:1px solid #D1D5DB;border-radius:8px;background:white;color:#374151;padding:0 12px;font-size:12px;font-weight:700" > Fill My Portfolio < / button >
< / Show >
< button type = "button" onClick = { ( ) = > props . onSidebarSelect ( 'Verification' ) } style = "height:32px;border:none;border-radius:8px;background:#03004E;color:white;padding:0 12px;font-size:12px;font-weight:700" > Submit For Approval < / button >
< / div >
< / div >
< / div >
) ;
}
if ( customerKey ( ) === 'leads' ) {
if ( ! bothApprovalsApproved ( ) ) {
return (
< div style = "display:grid;gap:12px" >
< div style = "border:1px solid #E5E7EB;background:white;border-radius:14px;padding:14px" >
< p style = "margin:0;font-size:22px;font-weight:800;color:#111827" > Leads Locked < / p >
< p style = "margin:8px 0 0;font-size:13px;color:#6B7280;line-height:1.55" >
Complete verification before accessing leads . You must submit profile and portfolio , then get admin approval .
< / p >
< div style = "display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-top:10px" >
< div style = "border:1px solid #E5E7EB;border-radius:10px;background:#F9FAFB;padding:10px" >
< p style = "margin:0;font-size:11px;color:#6B7280;text-transform:uppercase;letter-spacing:0.05em" > Profile Verification < / p >
< p style = "margin:6px 0 0;font-size:13px;font-weight:800;color:#111827" > { approvalTone ( profileApprovalState ( ) ) . label } < / p >
< / div >
< div style = "border:1px solid #E5E7EB;border-radius:10px;background:#F9FAFB;padding:10px" >
< p style = "margin:0;font-size:11px;color:#6B7280;text-transform:uppercase;letter-spacing:0.05em" > Portfolio Verification < / p >
< p style = "margin:6px 0 0;font-size:13px;font-weight:800;color:#111827" > { approvalTone ( portfolioApprovalState ( ) ) . label } < / p >
< / div >
< / div >
< div style = "display:flex;gap:8px;flex-wrap:wrap;margin-top:10px" >
< button type = "button" onClick = { ( ) = > props . onSidebarSelect ( 'Verification' ) } style = "height:32px;border-radius:8px;border:none;background:#03004E;color:white;padding:0 12px;font-size:12px;font-weight:700" > Open Verification < / button >
< button type = "button" onClick = { ( ) = > { props . onSidebarSelect ( 'My Profile' ) ; props . onTabSelect ( 'basic information' ) ; } } style = "height:32px;border-radius:8px;border:1px solid #E5E7EB;background:white;color:#374151;padding:0 12px;font-size:12px;font-weight:700" > Complete Profile < / button >
< button type = "button" onClick = { ( ) = > { props . onSidebarSelect ( 'My Portfolio' ) ; props . onTabSelect ( 'about' ) ; } } style = "height:32px;border-radius:8px;border:1px solid #E5E7EB;background:white;color:#374151;padding:0 12px;font-size:12px;font-weight:700" > Complete Portfolio < / button >
< / div >
< / div >
< / div >
) ;
}
const requestStatusPill = ( status : string ) = > {
if ( status === 'request_sent' ) return { bg : '#DBEAFE' , c : '#1D4ED8' , text : 'Request Sent' } ;
if ( status === 'approved' ) return { bg : '#DCFCE7' , c : '#15803D' , text : 'Approved' } ;
if ( status === 'contact_unlocked' ) return { bg : '#ECFDF3' , c : '#047857' , text : 'Contact Unlocked' } ;
if ( status === 'rejected' ) return { bg : '#FEE2E2' , c : '#B91C1C' , text : 'Rejected' } ;
if ( status === 'expired_refunded' ) return { bg : '#E0E7FF' , c : '#4338CA' , text : 'Expired · Refunded' } ;
if ( status === 'cancelled_by_professional' ) return { bg : '#FFF1EB' , c : '#C2410C' , text : 'Cancelled by Professional' } ;
return { bg : '#F3F4F6' , c : '#6B7280' , text : titleCase ( status ) } ;
} ;
const leadTabs = ( ) = > ( activeLeadDetailId ( ) ? [ . . . LEAD_MARKETPLACE_TABS , 'View Details' ] : LEAD_MARKETPLACE_TABS ) ;
const selectedLead = ( ) = > leadCards ( ) . find ( ( item ) = > item . id === activeLeadDetailId ( ) ) || null ;
if ( resolvedTabKey ( ) === 'requested contacts' ) {
return (
< div style = "display:flex;flex-direction:column;gap:10px" >
< div style = "display:grid;grid-template-columns:1fr 1fr 1fr;gap:10px" >
< div style = "border:1px solid #E5E7EB;background:white;border-radius:14px;padding:12px" >
< p style = "margin:0;font-size:11px;letter-spacing:0.06em;text-transform:uppercase;color:#9CA3AF" > Total Spent < / p >
< p style = "margin:8px 0 0;font-size:30px;line-height:1;font-weight:800;color:#111827" > { 250 - leadCredits ( ) } < / p >
< p style = "margin:6px 0 0;font-size:12px;color:#6B7280" > Tracecoins used this month < / p >
< / div >
< div style = "border:1px solid #D7DBFF;background:#03004E;border-radius:14px;padding:12px;color:white" >
< p style = "margin:0;font-size:11px;letter-spacing:0.06em;text-transform:uppercase;color:#D7DBFF" > Active Requests < / p >
< p style = "margin:8px 0 0;font-size:30px;line-height:1;font-weight:800" > { leadCards ( ) . filter ( ( card ) = > card . status === 'requested' ) . length } < / p >
< div style = "height:4px;border-radius:999px;background:rgba(255,255,255,0.2);overflow:hidden;margin-top:8px" >
< div style = { ` height:100%;width: ${ Math . min ( 100 , leadCards ( ) . filter ( ( card ) = > card . status === 'requested' ) . length * 10 ) } %;background:#FF5E13 ` } / >
< / div >
< / div >
< div style = "border:1px solid #E5E7EB;background:white;border-radius:14px;padding:12px" >
< p style = "margin:0;font-size:11px;letter-spacing:0.06em;text-transform:uppercase;color:#9CA3AF" > Locked Credits < / p >
< p style = "margin:8px 0 0;font-size:30px;line-height:1;font-weight:800;color:#111827" > { lockedLeadCredits ( ) } < / p >
< p style = "margin:6px 0 0;font-size:12px;color:#6B7280" > Held until service seeker decision ( auto - refund in 1 day ) < / p >
< / div >
< / div >
< div style = "border:1px solid #E5E7EB;background:white;border-radius:16px;overflow:hidden;box-shadow:0 1px 4px rgba(0,0,0,0.06)" >
< div style = "padding:10px 12px;border-bottom:1px solid #E5E7EB;display:flex;justify-content:space-between;align-items:center" >
< p style = "margin:0;font-size:18px;font-weight:800;color:#111827" > Requested Contacts < / p >
< div style = "display:flex;gap:8px" >
< div style = "position:relative" >
< button type = "button" onClick = { ( ) = > { setRequestedSortOpen ( ( prev ) = > ! prev ) ; setRequestedFilterOpen ( false ) ; } } style = "display:inline-flex;height:32px;align-items:center;gap:6px;border-radius:8px;border:1px solid #E5E7EB;background:white;padding:0 10px;font-size:12px;color:#374151;font-weight:600" >
< svg width = "13" height = "13" viewBox = "0 0 24 24" fill = "none" stroke = "#FF5E13" stroke-width = "2" > < path d = "M7 4v13" / > < path d = "m3 13 4 4 4-4" / > < path d = "M17 20V7" / > < path d = "m21 11-4-4-4 4" / > < / svg >
Sort
< / button >
< Show when = { requestedSortOpen ( ) } >
< div style = "position:absolute;right:0;top:36px;z-index:20;min-width:170px;border:1px solid #E5E7EB;border-radius:10px;background:white;padding:6px;box-shadow:0 8px 24px rgba(0,0,0,0.12)" >
{ [ 'Newest First' , 'Oldest First' ] . map ( ( item ) = > (
< button type = "button" onClick = { ( ) = > { setRequestedSortFilter ( item ) ; setRequestedSortOpen ( false ) ; } } style = { ` display:block;width:100%;text-align:left;border:none;background: ${ requestedSortFilter ( ) === item ? '#FFF1EB' : 'transparent' } ;color: ${ requestedSortFilter ( ) === item ? '#FF5E13' : '#374151' } ;padding:8px 10px;border-radius:8px;font-size:12px;font-weight:600;cursor:pointer ` } >
{ item }
< / button >
) ) }
< / div >
< / Show >
< / div >
< div style = "position:relative" >
< button type = "button" onClick = { ( ) = > { setRequestedFilterOpen ( ( prev ) = > ! prev ) ; setRequestedSortOpen ( false ) ; } } style = "display:inline-flex;height:32px;align-items:center;gap:6px;border-radius:8px;border:1px solid #E5E7EB;background:white;padding:0 10px;font-size:12px;color:#374151;font-weight:600" >
< svg width = "13" height = "13" viewBox = "0 0 24 24" fill = "none" stroke = "#FF5E13" stroke-width = "2" > < path d = "M3 5h18M6 12h12M10 19h4" / > < / svg >
Filter
< / button >
< Show when = { requestedFilterOpen ( ) } >
< div style = "position:absolute;right:0;top:36px;z-index:20;min-width:220px;border:1px solid #E5E7EB;border-radius:10px;background:white;padding:6px;box-shadow:0 8px 24px rgba(0,0,0,0.12)" >
{ [ 'All Status' , 'Request Sent' , 'Contact Unlocked' , 'Rejected' , 'Expired Refunded' , 'Cancelled By Professional' ] . map ( ( item ) = > (
< button type = "button" onClick = { ( ) = > { setRequestedStatusFilter ( item ) ; setRequestedFilterOpen ( false ) ; } } style = { ` display:block;width:100%;text-align:left;border:none;background: ${ requestedStatusFilter ( ) === item ? '#FFF1EB' : 'transparent' } ;color: ${ requestedStatusFilter ( ) === item ? '#FF5E13' : '#374151' } ;padding:8px 10px;border-radius:8px;font-size:12px;font-weight:600;cursor:pointer ` } >
{ item }
< / button >
) ) }
< / div >
< / Show >
< / div >
< button type = "button" style = "height:32px;border-radius:8px;border:none;background:#03004E;padding:0 10px;font-size:12px;font-weight:700;color:white" > Export < / button >
< / div >
< / div >
< div style = "padding:10px 12px;border-bottom:1px solid #E5E7EB;display:flex;align-items:center;gap:8px;flex-wrap:wrap" >
< div style = "height:32px;min-width:220px;flex:1;border:1px solid #E5E7EB;border-radius:8px;background:#F9FAFB;padding:0 10px;display:flex;align-items:center;gap:8px;font-size:12px;color:#9CA3AF" >
< Search size = { 14 } / >
< input value = { requestedSearch ( ) } onInput = { ( e ) = > setRequestedSearch ( e . currentTarget . value ) } placeholder = "Search by lead ID / title / area..." style = "border:none;background:transparent;outline:none;width:100%;font-size:12px;color:#111827" / >
< / div >
< span style = "height:24px;padding:0 10px;border-radius:999px;border:1px solid #E5E7EB;background:#F9FAFB;display:inline-flex;align-items:center;font-size:11px;font-weight:700;color:#6B7280" > Sort : { requestedSortFilter ( ) } < / span >
< span style = "height:24px;padding:0 10px;border-radius:999px;border:1px solid #E5E7EB;background:#F9FAFB;display:inline-flex;align-items:center;font-size:11px;font-weight:700;color:#6B7280" > Status : { requestedStatusFilter ( ) } < / span >
< / div >
< div style = "max-height:360px;overflow:auto" >
< table style = "width:100%;border-collapse:collapse" >
< thead style = "background:#03004E;color:white" >
< tr >
{ [ 'Lead ID' , 'Lead Title' , 'Request Date' , 'Request Status' , 'Cost' , 'Decision Date' , 'Action' ] . map ( ( h ) = > (
< th style = "padding:10px;text-align:left;font-size:10px;letter-spacing:0.06em;text-transform:uppercase" > { h } < / th >
) ) }
< / tr >
< / thead >
< tbody >
< For each = { pagedRequestedRows ( ) } >
{ ( row ) = > {
const badge = requestStatusPill ( row . status ) ;
return (
< tr style = "border-top:1px solid #F3F4F6" >
< td style = "padding:10px;font-size:12px;color:#4B5563;font-weight:700" > # { row . id } < / td >
< td style = "padding:10px" >
< p style = "margin:0;font-size:14px;font-weight:700;color:#111827" > { row . title } < / p >
< p style = "margin:2px 0 0;font-size:12px;color:#9CA3AF" > { row . city } < / p >
< / td >
< td style = "padding:10px;font-size:12px;color:#374151" > { row . requestDate } < / td >
< td style = "padding:10px" > < span style = { ` height:22px;padding:0 10px;border-radius:999px;display:inline-flex;align-items:center;font-size:11px;font-weight:700;background: ${ badge . bg } ;color: ${ badge . c } ` } > { badge . text } < / span > < / td >
< td style = "padding:10px;font-size:14px;font-weight:700;color:#111827" > { leadCostPerContact } < / td >
< td style = "padding:10px;font-size:12px;color:#6B7280" > { row . decisionDate } < / td >
< td style = "padding:10px" >
< div style = "display:flex;gap:6px;flex-wrap:wrap" >
< Show when = { row . status === 'request_sent' } >
< button type = "button" onClick = { ( ) = > approveLeadContact ( row . id ) } style = "height:28px;border-radius:8px;border:none;background:#03004E;padding:0 10px;font-size:11px;font-weight:700;color:white" > Approve ( Demo ) < / button >
< button type = "button" onClick = { ( ) = > cancelLeadRequest ( row . id ) } style = "height:28px;border-radius:8px;border:1px solid #FECACA;background:#FEF2F2;padding:0 10px;font-size:11px;font-weight:700;color:#B91C1C" > Cancel Request < / button >
< button type = "button" onClick = { ( ) = > refundPendingLead ( row . id ) } style = "height:28px;border-radius:8px;border:1px solid #E5E7EB;background:white;padding:0 10px;font-size:11px;font-weight:700;color:#374151" > Refund After 1 Day < / button >
< / Show >
< Show when = { row . status === 'contact_unlocked' } >
< button type = "button" style = "height:28px;border-radius:8px;border:none;background:#FF5E13;padding:0 10px;font-size:11px;font-weight:700;color:white" > View Data < / button >
< / Show >
< / div >
< / td >
< / tr >
) ;
} }
< / For >
< / tbody >
< / table >
< / div >
< div style = "padding:10px 12px;border-top:1px solid #E5E7EB;display:flex;justify-content:space-between;align-items:center" >
< p style = "margin:0;font-size:12px;color:#6B7280" >
Showing { pagedRequestedRows ( ) . length ? ( requestedPage ( ) - 1 ) * requestedPerPage + 1 : 0 } to { ( requestedPage ( ) - 1 ) * requestedPerPage + pagedRequestedRows ( ) . length } of { filteredRequestedRows ( ) . length } requests
< / p >
< div style = "display:flex;gap:6px" >
< For each = { Array . from ( { length : totalRequestedPages ( ) } , ( _ , i ) = > i + 1 ) } >
{ ( pageNo ) = > (
< button
type = "button"
onClick = { ( ) = > setRequestedPage ( pageNo ) }
style = { ` width:30px;height:30px;border-radius:8px;border:1px solid #E5E7EB;background: ${ requestedPage ( ) === pageNo ? '#FF5E13' : 'white' } ;color: ${ requestedPage ( ) === pageNo ? 'white' : '#6B7280' } ;font-size:12px;font-weight:700 ` }
>
{ pageNo }
< / button >
) }
< / For >
< / div >
< / div >
< / div >
< / div >
) ;
}
if ( leadMarketplaceTab ( ) === 'View Details' && selectedLead ( ) ) {
const lead = selectedLead ( ) ! ;
const spec = leadDetailsSpec ( lead ) ;
return (
< div style = "display:flex;flex-direction:column;gap:10px" >
< div style = "border:1px solid #E5E7EB;background:white;border-radius:16px;padding:14px;box-shadow:0 1px 4px rgba(0,0,0,0.06)" >
< div style = "border:1px solid #E5E7EB;border-radius:12px;background:linear-gradient(135deg,#FFF8F4 0%, #FFFFFF 70%);padding:14px" >
< div style = "display:flex;align-items:flex-start;justify-content:space-between;gap:10px;flex-wrap:wrap" >
< div >
< p style = "margin:0;font-size:11px;letter-spacing:0.06em;text-transform:uppercase;color:#6B7280" > View Details < / p >
< p style = "margin:4px 0 0;font-size:26px;font-weight:800;color:#111827;line-height:1.2" > { lead . title } < / p >
< p style = "margin:6px 0 0;font-size:12px;color:#6B7280" > { lead . category } • { lead . location } • { lead . area } < / p >
< / div >
< span style = "height:24px;padding:0 10px;border-radius:999px;background:#FFF1EB;color:#C2410C;display:inline-flex;align-items:center;font-size:11px;font-weight:700" > { lead . match } < / span >
< / div >
< div style = "margin-top:10px;display:flex;align-items:center;gap:8px;flex-wrap:wrap" >
< span style = "height:24px;padding:0 8px;border-radius:999px;border:1px solid #E5E7EB;background:white;font-size:11px;color:#374151;display:inline-flex;align-items:center;gap:4px" > < Calendar size = { 11 } style = "color:#6B7280" / > < strong style = "font-weight:700;color:#6B7280" > Time Frame : < / strong > { spec . timeframe } < / span >
< span style = "height:24px;padding:0 8px;border-radius:999px;border:1px solid #E5E7EB;background:white;font-size:11px;color:#374151;display:inline-flex;align-items:center;gap:4px" > < Calendar size = { 11 } style = "color:#6B7280" / > < strong style = "font-weight:700;color:#6B7280" > Date : < / strong > { lead . dateRequired } < / span >
< span style = "height:24px;padding:0 8px;border-radius:999px;border:1px solid #E5E7EB;background:white;font-size:11px;color:#374151;display:inline-flex;align-items:center;gap:4px" > < Coins size = { 11 } style = "color:#6B7280" / > < strong style = "font-weight:700;color:#6B7280" > Budget : < / strong > { lead . budget } < / span >
< span style = "height:24px;padding:0 8px;border-radius:999px;border:1px solid #E5E7EB;background:white;font-size:11px;color:#374151;display:inline-flex;align-items:center;gap:4px" > < MapPin size = { 11 } style = "color:#6B7280" / > < strong style = "font-weight:700;color:#6B7280" > Area : < / strong > { String ( lead . area || 'Chennai' ) } < / span >
< / div >
< / div >
< div style = "margin-top:10px;display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:8px" >
< div style = "border:1px solid #E5E7EB;border-radius:10px;background:white;padding:10px;display:flex;align-items:flex-start;gap:8px" >
< span style = "width:24px;height:24px;border-radius:999px;background:#FFF1EB;display:inline-flex;align-items:center;justify-content:center;flex-shrink:0" > < Calendar size = { 13 } style = "color:#FF5E13" / > < / span >
< div > < p style = "margin:0;font-size:11px;color:#6B7280;font-weight:700" > Schedule < / p > < p style = "margin:3px 0 0;font-size:13px;font-weight:800;color:#111827;line-height:1.35" > { spec . timeframe } < / p > < / div >
< / div >
< div style = "border:1px solid #E5E7EB;border-radius:10px;background:white;padding:10px;display:flex;align-items:flex-start;gap:8px" >
< span style = "width:24px;height:24px;border-radius:999px;background:#FFF1EB;display:inline-flex;align-items:center;justify-content:center;flex-shrink:0" > < Calendar size = { 13 } style = "color:#FF5E13" / > < / span >
< div > < p style = "margin:0;font-size:11px;color:#6B7280;font-weight:700" > Date Required < / p > < p style = "margin:3px 0 0;font-size:13px;font-weight:800;color:#111827" > { lead . dateRequired } < / p > < / div >
< / div >
< div style = "border:1px solid #E5E7EB;border-radius:10px;background:white;padding:10px;display:flex;align-items:flex-start;gap:8px" >
< span style = "width:24px;height:24px;border-radius:999px;background:#FFF1EB;display:inline-flex;align-items:center;justify-content:center;flex-shrink:0" > < MapPin size = { 13 } style = "color:#FF5E13" / > < / span >
< div > < p style = "margin:0;font-size:11px;color:#6B7280;font-weight:700" > Area < / p > < p style = "margin:3px 0 0;font-size:13px;font-weight:800;color:#111827" > { String ( lead . area || 'Chennai' ) } < / p > < / div >
< / div >
< div style = "border:1px solid #E5E7EB;border-radius:10px;background:white;padding:10px;display:flex;align-items:flex-start;gap:8px" >
< span style = "width:24px;height:24px;border-radius:999px;background:#FFF1EB;display:inline-flex;align-items:center;justify-content:center;flex-shrink:0" > < TrendingUp size = { 13 } style = "color:#FF5E13" / > < / span >
< div style = "min-width:0;flex:1" >
< p style = "margin:0;font-size:11px;color:#6B7280;font-weight:700" > Win Probability < / p >
< p style = { ` margin:3px 0 0;font-size:13px;font-weight:800;color: ${ leadProbabilityColor ( leadProbability ( lead ) ) } ` } > { leadProbability ( lead ) } % < / p >
< div style = "margin-top:5px;height:5px;border-radius:999px;background:#E5E7EB;overflow:hidden" >
< div style = { ` height:100%;width: ${ leadProbability ( lead ) } %;background: ${ leadProbabilityColor ( leadProbability ( lead ) ) } ` } / >
< / div >
< / div >
< / div >
< / div >
< div style = "margin-top:10px;display:grid;grid-template-columns:2fr 1fr;gap:10px" >
< div style = "display:grid;gap:10px" >
< div style = "border:1px solid #E5E7EB;border-radius:12px;background:white;padding:12px" >
< p style = "margin:0;font-size:11px;letter-spacing:0.05em;text-transform:uppercase;color:#6B7280;font-weight:700" > Work Scope < / p >
< p style = "margin:8px 0 0;font-size:14px;color:#1F2937;line-height:1.65" > { spec . scope } < / p >
< / div >
< div style = "border:1px solid #E5E7EB;border-radius:12px;background:#F9FAFB;padding:12px" >
< p style = "margin:0 0 8px;font-size:11px;letter-spacing:0.05em;text-transform:uppercase;color:#6B7280;font-weight:700" > Lead Specific Highlights < / p >
< div style = "display:grid;gap:6px" >
< For each = { spec . highlights } > { ( item ) = > < div style = "display:flex;align-items:flex-start;gap:6px" > < span style = "margin-top:3px;width:6px;height:6px;border-radius:999px;background:#FF5E13;flex-shrink:0" / > < span style = "font-size:14px;color:#1F2937;line-height:1.55" > { item } < / span > < / div > } < / For >
< / div >
< / div >
< / div >
< div style = "border:1px solid #E5E7EB;border-radius:12px;background:white;padding:12px;display:grid;gap:8px;align-content:start" >
< div style = "display:flex;justify-content:space-between;gap:8px;padding-bottom:6px;border-bottom:1px solid #F3F4F6" > < span style = "font-size:12px;color:#6B7280" > Lead ID < / span > < span style = "font-size:12px;color:#111827;font-weight:700" > { lead . id } < / span > < / div >
< div style = "display:flex;justify-content:space-between;gap:8px;padding-bottom:6px;border-bottom:1px solid #F3F4F6" >
< span style = "font-size:12px;color:#6B7280" > Unlock Cost < / span >
< span style = "height:22px;padding:0 8px;border-radius:999px;border:1px solid #FFD8C2;background:#FFF1EB;display:inline-flex;align-items:center;gap:6px" > < span style = "width:14px;height:14px;border-radius:999px;background:#FF5E13;color:white;display:inline-flex;align-items:center;justify-content:center;font-size:9px;font-weight:800;line-height:1" > NG < / span > < span style = "font-size:12px;color:#C2410C;font-weight:800" > { lead . cost } Tracecoin < / span > < / span >
< / div >
< div style = "display:flex;justify-content:space-between;gap:8px;padding-bottom:6px;border-bottom:1px solid #F3F4F6" > < span style = "font-size:12px;color:#6B7280" > Contacted < / span > < span style = "font-size:12px;color:#111827;font-weight:700" > { lead . contactCount } / { lead . maxContacts } < / span > < / div >
< div style = "display:flex;gap:8px;margin-top:6px;flex-wrap:wrap" >
< button type = "button" onClick = { ( ) = > { setLeadMarketplaceTab ( 'All Leads' ) ; setActiveLeadDetailId ( '' ) ; } } style = "height:32px;border-radius:8px;border:1px solid #E5E7EB;background:white;padding:0 12px;font-size:12px;font-weight:700;color:#374151" > Back to Leads < / button >
< button type = "button" onClick = { ( ) = > openLeadContactConfirm ( lead . id ) } disabled = { usableLeadCredits ( ) < leadCostPerContact || lead . contactCount >= lead . maxContacts } style = { ` height:32px;border-radius:8px;border:none;padding:0 12px;font-size:12px;font-weight:700;color:white;background: ${ usableLeadCredits ( ) < leadCostPerContact || lead . contactCount >= lead . maxContacts ? '#9CA3AF' : '#03004E' } ` } > Request Contact < / button >
< / div >
< / div >
< / div >
< / div >
< / div >
) ;
}
return (
< div style = "display:flex;flex-direction:column;gap:10px" >
< div style = "border:1px solid #E5E7EB;background:white;border-radius:16px;overflow:hidden;box-shadow:0 1px 4px rgba(0,0,0,0.06)" >
< div style = "padding:10px 12px;border-bottom:1px solid #E5E7EB;display:flex;align-items:center;gap:8px;flex-wrap:wrap" >
< For each = { leadTabs ( ) } >
{ ( item ) = > (
< button
type = "button"
onClick = { ( ) = > { setLeadMarketplaceTab ( item ) ; if ( item !== 'View Details' ) setActiveLeadDetailId ( '' ) ; } }
style = { ` height:30px;padding:0 8px;border:none;border-bottom: ${ leadMarketplaceTab ( ) === item ? '2px solid #FF5E13' : '2px solid transparent' } ;background:none;font-size:12px;font-weight:700;color: ${ leadMarketplaceTab ( ) === item ? '#FF5E13' : '#6B7280' } ` }
>
{ item }
< / button >
) }
< / For >
< / div >
< div style = "padding:10px 12px;border-bottom:1px solid #E5E7EB;display:flex;align-items:center;gap:8px;flex-wrap:wrap" >
< div style = "height:34px;min-width:220px;flex:1;border:1px solid #E5E7EB;border-radius:8px;background:#F9FAFB;padding:0 10px;display:flex;align-items:center;gap:8px;font-size:12px;color:#9CA3AF" >
< Search size = { 14 } / >
< input value = { leadSearch ( ) } onInput = { ( e ) = > setLeadSearch ( e . currentTarget . value ) } placeholder = "Search by role, area, keyword..." style = "border:none;background:transparent;outline:none;width:100%;font-size:12px;color:#111827" / >
< / div >
< div style = "position:relative" >
< button
type = "button"
onClick = { ( ) = > { setLeadFiltersOpen ( ( prev ) = > ! prev ) ; setLeadSortOpen ( false ) ; } }
style = "display:inline-flex;height:34px;align-items:center;gap:6px;border-radius:8px;border:1px solid #E5E7EB;background:white;padding:0 12px;font-size:12px;color:#374151;font-weight:600"
>
< svg width = "13" height = "13" viewBox = "0 0 24 24" fill = "none" stroke = "#FF5E13" stroke-width = "2" > < path d = "M3 5h18M6 12h12M10 19h4" / > < / svg >
Filters
< / button >
< Show when = { leadFiltersOpen ( ) } >
< div style = "position:absolute;right:0;top:38px;z-index:20;min-width:280px;border:1px solid #E5E7EB;border-radius:12px;background:white;box-shadow:0 8px 24px rgba(0,0,0,0.12);padding:10px;display:grid;gap:8px" >
< div >
< p style = "margin:0 0 4px;font-size:10px;letter-spacing:0.05em;text-transform:uppercase;color:#9CA3AF;font-weight:700" > Area < / p >
< select value = { leadAreaFilter ( ) } onChange = { ( e ) = > setLeadAreaFilter ( e . currentTarget . value ) } style = "height:34px;width:100%;border-radius:8px;border:1px solid #E5E7EB;background:white;padding:0 10px;font-size:12px;color:#374151" >
< option > All Areas < / option >
< option > ECR < / option >
< option > Nungambakkam < / option >
< option > Sholinganallur < / option >
< option > Mylapore < / option >
< option > T Nagar < / option >
< option > Guindy < / option >
< / select >
< / div >
< div >
< p style = "margin:0 0 4px;font-size:10px;letter-spacing:0.05em;text-transform:uppercase;color:#9CA3AF;font-weight:700" > Budget < / p >
< select value = { leadBudgetFilter ( ) } onChange = { ( e ) = > setLeadBudgetFilter ( e . currentTarget . value ) } style = "height:34px;width:100%;border-radius:8px;border:1px solid #E5E7EB;background:white;padding:0 10px;font-size:12px;color:#374151" >
< option > All Budgets < / option >
< option > Under ₹ 1 L < / option >
< option > ₹ 1 L - ₹ 2 L < / option >
< option > Above ₹ 2 L < / option >
< / select >
< / div >
< div >
< p style = "margin:0 0 4px;font-size:10px;letter-spacing:0.05em;text-transform:uppercase;color:#9CA3AF;font-weight:700" > Date < / p >
< select value = { leadDateFilter ( ) } onChange = { ( e ) = > setLeadDateFilter ( e . currentTarget . value ) } style = "height:34px;width:100%;border-radius:8px;border:1px solid #E5E7EB;background:white;padding:0 10px;font-size:12px;color:#374151" >
< option > Any Date < / option >
< option > Within 7 Days < / option >
< option > This Month < / option >
< / select >
< / div >
< div style = "display:flex;justify-content:flex-end;gap:8px;padding-top:4px" >
< button type = "button" onClick = { ( ) = > { setLeadAreaFilter ( 'All Areas' ) ; setLeadBudgetFilter ( 'All Budgets' ) ; setLeadDateFilter ( 'Any Date' ) ; } } style = "height:30px;border-radius:8px;border:1px solid #E5E7EB;background:white;padding:0 10px;font-size:11px;color:#374151;font-weight:700" > Reset < / button >
< button type = "button" onClick = { ( ) = > setLeadFiltersOpen ( false ) } style = "height:30px;border-radius:8px;border:none;background:#03004E;color:white;padding:0 10px;font-size:11px;font-weight:700" > Apply < / button >
< / div >
< / div >
< / Show >
< / div >
< div style = "position:relative" >
< button
type = "button"
onClick = { ( ) = > { setLeadSortOpen ( ( prev ) = > ! prev ) ; setLeadFiltersOpen ( false ) ; } }
style = "display:inline-flex;height:34px;align-items:center;gap:6px;border-radius:8px;border:1px solid #E5E7EB;background:white;padding:0 12px;font-size:12px;color:#374151;font-weight:600"
>
< svg width = "13" height = "13" viewBox = "0 0 24 24" fill = "none" stroke = "#FF5E13" stroke-width = "2" > < path d = "M7 4v13" / > < path d = "m3 13 4 4 4-4" / > < path d = "M17 20V7" / > < path d = "m21 11-4-4-4 4" / > < / svg >
Sort
< / button >
< Show when = { leadSortOpen ( ) } >
< div style = "position:absolute;right:0;top:38px;z-index:20;min-width:190px;border:1px solid #E5E7EB;border-radius:10px;background:white;padding:6px;box-shadow:0 8px 24px rgba(0,0,0,0.12)" >
{ [ 'Newest First' , 'Budget High-Low' , 'Budget Low-High' ] . map ( ( item ) = > (
< button type = "button" onClick = { ( ) = > { setLeadSortFilter ( item ) ; setLeadSortOpen ( false ) ; } } style = { ` display:block;width:100%;text-align:left;border:none;background: ${ leadSortFilter ( ) === item ? '#FFF1EB' : 'transparent' } ;color: ${ leadSortFilter ( ) === item ? '#FF5E13' : '#374151' } ;padding:8px 10px;border-radius:8px;font-size:12px;font-weight:600;cursor:pointer ` } >
{ item }
< / button >
) ) }
< / div >
< / Show >
< / div >
< span style = "height:24px;padding:0 10px;border-radius:999px;border:1px solid #E5E7EB;background:#F9FAFB;display:inline-flex;align-items:center;font-size:11px;font-weight:700;color:#6B7280" > Sort : { leadSortFilter ( ) } < / span >
< / div >
< div style = "padding:12px;display:grid;gap:10px;background:#FAFBFF;max-height:520px;overflow:auto" >
< For each = { pagedLeadCards ( ) } >
{ ( lead ) = > (
< div style = { ` border:1px solid ${ lead . status === 'requested' ? '#FFE2D3' : lead . status === 'unlocked' ? '#CFF5E9' : lead . status === 'closed' ? '#F3F4F6' : '#E5E7EB' } ;border-radius:14px;background:white;padding:14px 16px ` } >
< div style = "display:grid;grid-template-columns:minmax(0,1fr) auto;gap:12px;align-items:start" >
< div style = "min-width:0" >
< div style = "display:flex;align-items:center;gap:8px;flex-wrap:wrap" >
< span style = { ` height:20px;padding:0 8px;border-radius:999px;font-size:10px;font-weight:700;display:inline-flex;align-items:center;background: ${ lead . status === 'unlocked' ? '#ECFDF3' : lead . status === 'requested' ? '#FFF1EB' : lead . status === 'closed' ? '#F3F4F6' : '#EEF2FF' } ;color: ${ lead . status === 'unlocked' ? '#047857' : lead . status === 'requested' ? '#C2410C' : lead . status === 'closed' ? '#6B7280' : '#3730A3' } ` } >
{ lead . status === 'unlocked' ? 'Contact Unlocked' : lead . status === 'requested' ? 'Request Sent' : lead . status === 'closed' ? 'Lead Closed' : 'Open Lead' }
< / span >
< span style = "font-size:12px;color:#6B7280" > { lead . category } • { lead . location } < / span >
< / div >
< p style = "margin:6px 0 0;font-size:20px;line-height:1.3;font-weight:800;color:#111827" > { lead . title } < / p >
< div style = "margin-top:10px;display:flex;align-items:center;gap:12px;white-space:nowrap;overflow-x:auto;padding-bottom:2px" >
< span style = "font-size:12px;color:#111827;font-weight:700" > Area : { lead . area } < / span >
< span style = "font-size:12px;color:#D1D5DB" > | < / span >
< span style = "font-size:12px;color:#111827;font-weight:700" > Date : { lead . dateRequired } < / span >
< span style = "font-size:12px;color:#D1D5DB" > | < / span >
< span style = "height:24px;display:inline-flex;align-items:center;gap:6px;padding:0 8px;border:1px solid #FFE2D3;border-radius:999px;background:#FFF7F2" >
< span style = "width:14px;height:14px;border-radius:999px;background:#FF5E13;color:white;display:inline-flex;align-items:center;justify-content:center;font-size:10px;font-weight:800;line-height:1" > T < / span >
< span style = "font-size:12px;color:#C2410C;font-weight:800" > { lead . cost } < / span >
< / span >
< span style = "font-size:12px;color:#D1D5DB" > | < / span >
< span style = "display:inline-flex;align-items:center;gap:6px" >
< svg width = "62" height = "34" viewBox = "0 0 120 70" aria-label = "Lead probability gauge" >
< path d = "M 10 60 A 50 50 0 0 1 110 60" fill = "none" stroke = "#E5E7EB" stroke-width = "8" stroke-linecap = "round" / >
< path d = "M 10 60 A 50 50 0 0 1 110 60" fill = "none" stroke = { leadProbabilityColor ( leadProbability ( lead ) ) } stroke-width = "8" stroke-linecap = "round" stroke-dasharray = { leadGaugeDash ( leadProbability ( lead ) ) } / >
< line x1 = "60" y1 = "60" x2 = { leadGaugeNeedlePoint ( leadProbability ( lead ) ) . x } y2 = { leadGaugeNeedlePoint ( leadProbability ( lead ) ) . y } stroke = "#111827" stroke-width = "3" stroke-linecap = "round" / >
< circle cx = "60" cy = "60" r = "4.5" fill = "#111827" / >
< / svg >
< span style = { ` font-size:12px;font-weight:800;color: ${ leadProbabilityColor ( leadProbability ( lead ) ) } ` } > { leadProbability ( lead ) } % < / span >
< / span >
< span style = "font-size:12px;color:#D1D5DB" > | < / span >
< span style = "height:24px;display:inline-flex;align-items:center;gap:6px;padding:0 8px;border:1px solid #E5E7EB;border-radius:999px;background:#F9FAFB" >
< svg width = "12" height = "12" viewBox = "0 0 24 24" fill = "none" stroke = "#6B7280" stroke-width = "2" > < path d = "M16 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2" / > < circle cx = "8.5" cy = "7" r = "4" / > < path d = "M20 8v6" / > < path d = "M23 11h-6" / > < / svg >
< span style = "font-size:11px;color:#111827;font-weight:700" > { lead . contactCount } / { lead . maxContacts } < / span >
< span style = "font-size:10px;color:#6B7280" > contacted < / span >
< / span >
< span style = "font-size:12px;color:#6B7280;font-weight:700" > { Math . max ( 0 , lead . maxContacts - lead . contactCount ) } slots left < / span >
< / div >
< / div >
< div style = "display:flex;flex-direction:column;align-items:flex-end;gap:8px" >
< p style = "margin:0;font-size:11px;letter-spacing:0.06em;text-transform:uppercase;color:#FF5E13;font-weight:700" > { lead . match } < / p >
< p style = "margin:0;font-size:24px;line-height:1;font-weight:800;color:#111827" > { lead . budget } < / p >
< p style = "margin:0;font-size:11px;color:#9CA3AF" > Est . Budget < / p >
< div style = "display:flex;gap:6px;flex-wrap:wrap;justify-content:flex-end;padding-top:2px" >
< button type = "button" onClick = { ( ) = > openLeadDetailsInNewTab ( lead . id ) } style = "height:30px;border-radius:8px;border:1px solid #E5E7EB;background:white;padding:0 10px;font-size:12px;font-weight:700;color:#374151" > View Details < / button >
< Show when = { lead . status === 'open' } >
< button
type = "button"
onClick = { ( ) = > openLeadContactConfirm ( lead . id ) }
disabled = { usableLeadCredits ( ) < leadCostPerContact || lead . contactCount >= lead . maxContacts }
style = { ` height:30px;border-radius:8px;border:none;padding:0 10px;font-size:12px;font-weight:700;color:white;background: ${ usableLeadCredits ( ) < leadCostPerContact || lead . contactCount >= lead . maxContacts ? '#9CA3AF' : '#03004E' } ;cursor: ${ usableLeadCredits ( ) < leadCostPerContact || lead . contactCount >= lead . maxContacts ? 'not-allowed' : 'pointer' } ` }
>
Request Contact
< / button >
< / Show >
< Show when = { lead . status === 'requested' } >
< button type = "button" onClick = { ( ) = > cancelLeadRequest ( lead . id ) } style = "height:30px;border-radius:8px;border:1px solid #FECACA;background:#FEF2F2;padding:0 10px;font-size:12px;font-weight:700;color:#B91C1C" > Cancel Request < / button >
< / Show >
< / div >
< / div >
< / div >
< / div >
) }
< / For >
< / div >
< div style = "padding:10px 12px;border-top:1px solid #E5E7EB;display:flex;justify-content:space-between;align-items:center" >
< p style = "margin:0;font-size:12px;color:#6B7280" >
Showing { pagedLeadCards ( ) . length ? ( leadPage ( ) - 1 ) * leadsPerPage + 1 : 0 } to { ( leadPage ( ) - 1 ) * leadsPerPage + pagedLeadCards ( ) . length } of { filteredLeadCards ( ) . length } leads
< / p >
< div style = "display:flex;gap:6px" >
< For each = { Array . from ( { length : totalLeadPages ( ) } , ( _ , i ) = > i + 1 ) } >
{ ( pageNo ) = > (
< button
type = "button"
onClick = { ( ) = > setLeadPage ( pageNo ) }
style = { ` width:30px;height:30px;border-radius:8px;border:1px solid #E5E7EB;background: ${ leadPage ( ) === pageNo ? '#FF5E13' : 'white' } ;color: ${ leadPage ( ) === pageNo ? 'white' : '#6B7280' } ;font-size:12px;font-weight:700 ` }
>
{ pageNo }
< / button >
) }
< / For >
< / div >
< / div >
< / div >
< Show when = { leadContactConfirmId ( ) } >
< div style = "position:fixed;inset:0;background:rgba(15,23,42,0.45);display:flex;align-items:center;justify-content:center;z-index:60;padding:16px" >
< div style = "width:min(460px,100%);border:1px solid #E5E7EB;background:white;border-radius:14px;box-shadow:0 10px 30px rgba(0,0,0,0.18);padding:16px" >
< div style = "display:flex;align-items:center;gap:8px" >
< span style = "width:22px;height:22px;border-radius:999px;background:#FFF1EB;color:#FF5E13;display:inline-flex;align-items:center;justify-content:center;font-size:13px;font-weight:800" > T < / span >
< p style = "margin:0;font-size:16px;font-weight:800;color:#111827" > Confirm Contact Unlock < / p >
< / div >
< p style = "margin:10px 0 0;font-size:13px;color:#374151;line-height:1.5" > You are about to spend < strong > 25 Tracecoins < / strong > to request and view this service seeker contact when approved . Do you want to continue ? < / p >
< div style = "display:flex;justify-content:flex-end;gap:8px;margin-top:14px" >
< button type = "button" onClick = { ( ) = > setLeadContactConfirmId ( '' ) } style = "height:34px;border-radius:8px;border:1px solid #D1D5DB;background:white;color:#374151;padding:0 14px;font-size:12px;font-weight:700" > Cancel < / button >
< button type = "button" onClick = { confirmLeadContactRequest } style = "height:34px;border-radius:8px;border:none;background:#03004E;color:white;padding:0 14px;font-size:12px;font-weight:700" > Yes , Request Contact < / button >
< / div >
< / div >
< / div >
< / Show >
< / div >
) ;
}
if ( customerKey ( ) === 'jobs' ) {
if ( isJobSeekerRole ( ) ) {
const selectedJob = ( ) = > jobBoardJobs ( ) . find ( ( job ) = > job . id === jobSeekerSelectedId ( ) ) || jobBoardJobs ( ) [ 0 ] ;
const selectedAppliedTab = normalizeTabKey ( tab ) === 'applied' ;
const selectedOpenJobCards = ( ) = > {
const tabKey = normalizeTabKey ( tab ) ;
const rows = jobBoardJobs ( ) ;
if ( tabKey === 'recommended' ) return rows . slice ( 0 , 3 ) ;
if ( tabKey === 'saved' ) return rows . slice ( 1 ) ;
if ( tabKey === 'expiring soon' ) return rows . filter ( ( _ , idx ) = > idx % 2 === 0 ) ;
return rows ;
} ;
const stepWidth = ( ) = > ` ${ Math . round ( ( jobSeekerApplyStep ( ) / 4 ) * 100 ) } % ` ;
if ( selectedAppliedTab ) {
return (
< div style = "display:flex;flex-direction:column;gap:10px" >
< div style = "display:flex;justify-content:space-between;align-items:center" >
< div style = "display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:8px;flex:1" >
{ [
{ label : 'Total Applications' , value : '24' , tone : 'dark' } ,
{ label : 'Under Review' , value : '08' , tone : 'blue' } ,
{ label : 'Shortlisted' , value : '03' , tone : 'orange' } ,
{ label : 'Interviews' , value : '02' , tone : 'green' } ,
] . map ( ( card ) = > (
< div style = { ` border:1px solid #E5E7EB;border-radius:12px;padding:10px;background: ${ card . tone === 'dark' ? '#03004E' : 'white' } ;color: ${ card . tone === 'dark' ? 'white' : '#111827' } ;box-shadow:0 1px 3px rgba(0,0,0,0.05) ` } >
< p style = { ` margin:0;font-size:11px;letter-spacing:0.05em;text-transform:uppercase;color: ${ card . tone === 'dark' ? '#D7DBFF' : '#9CA3AF' } ` } > { card . label } < / p >
< p style = { ` margin:4px 0 0;font-size:32px;line-height:1;font-weight:800;color: ${ card . tone === 'orange' ? '#C2410C' : card . tone === 'green' ? '#16A34A' : card . tone === 'blue' ? '#2563EB' : card . tone === 'dark' ? 'white' : '#111827' } ` } > { card . value } < / p >
< / div >
) ) }
< / div >
< div style = "display:flex;gap:8px;margin-left:10px" >
< button type = "button" style = "height:34px;border-radius:8px;border:1px solid #E5E7EB;background:white;padding:0 10px;font-size:12px;font-weight:700;color:#374151" > Filters < / button >
< button type = "button" style = "height:34px;border-radius:8px;border:1px solid #E5E7EB;background:white;padding:0 10px;font-size:12px;font-weight:700;color:#374151" > Most Recent < / button >
< / div >
< / div >
< div style = "border:1px solid #E5E7EB;border-radius:14px;background:white;overflow:hidden;box-shadow:0 1px 4px rgba(0,0,0,0.06)" >
< div style = "padding:10px 12px;border-bottom:1px solid #E5E7EB;display:flex;justify-content:space-between;align-items:center" >
< p style = "margin:0;font-size:14px;font-weight:800;color:#111827;letter-spacing:0.05em;text-transform:uppercase" > Latest Updates < / p >
< span style = "font-size:11px;color:#9CA3AF" > Track your application status and recruiter responses < / span >
< / div >
< div style = "display:grid;gap:8px;padding:10px" >
< For each = { JOB_SEEKER_APPLIED_ROWS } >
{ ( row ) = > {
const tone = row . status === 'Shortlisted'
? { bg : '#FFF1EB' , c : '#C2410C' }
: row . status === 'Under Review'
? { bg : '#E8F0FF' , c : '#2563EB' }
: row . status === 'Not Selected'
? { bg : '#FEE2E2' , c : '#B91C1C' }
: { bg : '#F3F4F6' , c : '#374151' } ;
return (
< div style = { ` border:1px solid #E5E7EB;border-left:4px solid ${ tone . c } ;border-radius:10px;background:#FCFCFD;padding:10px;display:grid;grid-template-columns:1fr auto auto;gap:10px;align-items:center ` } >
< div >
< p style = "margin:0;font-size:10px;letter-spacing:0.05em;text-transform:uppercase;color:#6B7280" > { row . id } • Applied On Oct 12 , 2023 < / p >
< p style = "margin:4px 0 0;font-size:28px;line-height:1.2;font-weight:800;color:#111827" > { row . title } < / p >
< p style = "margin:4px 0 0;font-size:13px;color:#4B5563" > { row . company } • { row . location } < / p >
< / div >
< div style = "text-align:right" >
< span style = { ` height:24px;padding:0 10px;border-radius:999px;background: ${ tone . bg } ;color: ${ tone . c } ;font-size:11px;font-weight:700;display:inline-flex;align-items:center ` } > { row . status } < / span >
< p style = "margin:6px 0 0;font-size:12px;color:#6B7280" > { row . note } < / p >
< / div >
< button type = "button" style = "height:32px;border-radius:8px;border:1px solid #E5E7EB;background:white;padding:0 10px;font-size:12px;font-weight:700;color:#374151" > View Details < / button >
< / div >
) ;
} }
< / For >
< / div >
< / div >
< div style = "border:1px solid #FFD8C2;border-radius:14px;background:#FFF8F4;padding:14px;display:flex;justify-content:space-between;align-items:center;gap:10px" >
< div >
< p style = "margin:0;font-size:30px;line-height:1.2;font-weight:800;color:#111827" > Boost your response rate < / p >
< p style = "margin:6px 0 0;font-size:13px;color:#6B7280" > Recruiters are more likely to respond to profiles with updated resume and portfolio links . < / p >
< / div >
< button type = "button" onClick = { ( ) = > setJobSeekerScreen ( 'apply' ) } style = "height:34px;border:none;border-radius:9px;background:#03004E;color:white;padding:0 12px;font-size:12px;font-weight:700;white-space:nowrap" > Optimize Profile < / button >
< / div >
< / div >
) ;
}
if ( jobSeekerScreen ( ) === 'apply' ) {
return (
< div style = "border:1px solid #E5E7EB;border-radius:16px;background:white;box-shadow:0 1px 4px rgba(0,0,0,0.06);overflow:hidden" >
< div style = "display:grid;grid-template-columns:280px 1fr;min-height:520px" >
< aside style = "background:#03004E;color:white;padding:14px;display:flex;flex-direction:column;justify-content:space-between" >
< div >
< p style = "margin:0;font-size:36px;line-height:1.1;font-weight:800" > Nxtgauge < / p >
< p style = "margin:12px 0 0;font-size:10px;letter-spacing:0.08em;text-transform:uppercase;color:#A7B2FF" > Applying For < / p >
< p style = "margin:4px 0 0;font-size:42px;line-height:1.1;font-weight:800" > { selectedJob ( ) . title } < / p >
< p style = "margin:8px 0 0;font-size:13px;color:#D7DBFF" > Full - time • Remote • { selectedJob ( ) . salary } < / p >
< div style = "margin-top:14px;border:1px solid rgba(255,255,255,0.18);border-radius:10px;padding:10px;background:rgba(255,255,255,0.04)" >
< p style = "margin:0;font-size:12px;font-weight:700" > { selectedJob ( ) . company } < / p >
< p style = "margin:3px 0 0;font-size:11px;color:#D7DBFF" > { selectedJob ( ) . location } < / p >
< / div >
< / div >
< div >
< p style = "margin:0;font-size:12px;color:#D7DBFF" > Step { jobSeekerApplyStep ( ) } of 4 < / p >
< div style = "height:4px;border-radius:999px;background:rgba(255,255,255,0.2);margin-top:6px;overflow:hidden" > < div style = { ` height:100%;background:#FF5E13;width: ${ stepWidth ( ) } ` } / > < / div >
< / div >
< / aside >
< div style = "padding:14px;display:flex;flex-direction:column;gap:12px" >
< div style = "display:flex;align-items:center;gap:8px;border-bottom:1px solid #E5E7EB;padding-bottom:10px" >
{ [
{ key : 1 , label : 'Profile' } ,
{ key : 2 , label : 'Materials' } ,
{ key : 3 , label : 'Details' } ,
{ key : 4 , label : 'Review' } ,
] . map ( ( step , idx , arr ) = > (
< >
< button
type = "button"
onClick = { ( ) = > setJobSeekerApplyStep ( step . key ) }
style = { ` height:28px;padding:0 10px;border-radius:999px;border:1px solid ${ jobSeekerApplyStep ( ) === step . key ? '#FF5E13' : '#E5E7EB' } ;background: ${ jobSeekerApplyStep ( ) === step . key ? '#FFF1EB' : '#F9FAFB' } ;font-size:11px;font-weight:700;color: ${ jobSeekerApplyStep ( ) === step . key ? '#C2410C' : '#6B7280' } ` }
>
{ step . key } . { step . label }
< / button >
< Show when = { idx < arr . length - 1 } > < span style = "color:#D1D5DB" > — < / span > < / Show >
< / >
) ) }
< / div >
< p style = "margin:0;font-size:38px;line-height:1.1;font-weight:800;color:#111827" > Resume & Portfolio < / p >
< p style = "margin:0;font-size:14px;color:#6B7280" > Showcase your best work and professional journey to the hiring team . < / p >
< div >
< p style = "margin:0 0 6px;font-size:11px;letter-spacing:0.06em;text-transform:uppercase;color:#6B7280" > Resume ( PDF ) < / p >
< div style = "min-height:120px;border:1px dashed #F4B6A0;border-radius:12px;background:#FFFCFB;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px" >
< FileText size = { 24 } style = "color:#C2410C" / >
< p style = "margin:0;font-size:14px;color:#111827" > Drop your resume here or < span style = "color:#C2410C;font-weight:700" > browse < / span > < / p >
< p style = "margin:0;font-size:12px;color:#6B7280" > Maximum file size : 5MB < / p >
< / div >
< / div >
< div style = "display:grid;grid-template-columns:1fr 1fr;gap:10px" >
< div style = "grid-column:1 / -1" >
< p style = "margin:0 0 6px;font-size:11px;letter-spacing:0.06em;text-transform:uppercase;color:#6B7280" > Portfolio Link < / p >
< div style = "height:40px;border:1px solid #E5E7EB;border-radius:10px;background:#F9FAFB;padding:0 10px;display:flex;align-items:center;font-size:13px;color:#6B7280" > https : //yourportfolio.com</div>
< / div >
< div style = "height:54px;border:1px solid #E5E7EB;border-radius:10px;background:#F9FAFB;padding:0 10px;display:flex;align-items:center;justify-content:space-between" >
< div > < p style = "margin:0;font-size:12px;font-weight:700;color:#111827" > Cover Letter < / p > < p style = "margin:2px 0 0;font-size:11px;color:#6B7280" > Optional doc < / p > < / div >
< button type = "button" style = "width:20px;height:20px;border-radius:999px;border:none;background:#FFF1EB;color:#C2410C;font-size:14px;font-weight:700;line-height:1" > + < / button >
< / div >
< div style = "height:54px;border:1px solid #E5E7EB;border-radius:10px;background:#F9FAFB;padding:0 10px;display:flex;align-items:center;justify-content:space-between" >
< div > < p style = "margin:0;font-size:12px;font-weight:700;color:#111827" > GitHub Profile < / p > < p style = "margin:2px 0 0;font-size:11px;color:#6B7280" > Optional link < / p > < / div >
< button type = "button" style = "width:20px;height:20px;border-radius:999px;border:none;background:#FFF1EB;color:#C2410C;font-size:14px;font-weight:700;line-height:1" > + < / button >
< / div >
< / div >
< div style = "display:flex;align-items:center;justify-content:space-between;border-top:1px solid #E5E7EB;padding-top:10px" >
< button type = "button" onClick = { ( ) = > setJobSeekerScreen ( 'detail' ) } style = "height:34px;border-radius:8px;border:1px solid #E5E7EB;background:white;padding:0 12px;font-size:12px;font-weight:700;color:#374151" > Back < / button >
< div style = "display:flex;gap:8px" >
< button type = "button" style = "height:34px;border-radius:8px;border:1px solid #E5E7EB;background:white;padding:0 12px;font-size:12px;font-weight:700;color:#374151" > Save Draft < / button >
< button type = "button" onClick = { ( ) = > setJobSeekerApplyStep ( ( prev ) = > Math . min ( 4 , prev + 1 ) ) } style = "height:34px;border-radius:8px;border:none;background:#C2410C;padding:0 14px;font-size:12px;font-weight:700;color:white" > Continue < / button >
< / div >
< / div >
< / div >
< / div >
< / div >
) ;
}
if ( jobSeekerScreen ( ) === 'detail' ) {
return (
< div style = "display:grid;grid-template-columns:1.1fr 1fr;gap:10px;align-items:start" >
< div style = "border:1px solid #E5E7EB;border-radius:14px;background:white;padding:12px;display:flex;flex-direction:column;gap:8px;box-shadow:0 1px 4px rgba(0,0,0,0.06);max-height:78vh;overflow:auto" >
< p style = "margin:0;font-size:24px;line-height:1.15;font-weight:800;color:#111827" > Available Positions < / p >
< p style = "margin:0;font-size:13px;color:#6B7280" > Found 128 high - match opportunities for your profile . < / p >
< For each = { selectedOpenJobCards ( ) } >
{ ( job ) = > (
< button
type = "button"
onClick = { ( ) = > setJobSeekerSelectedId ( job . id ) }
style = { ` text-align:left;border:1px solid ${ job . id === selectedJob ( ) . id ? '#FF5E13' : '#E5E7EB' } ;border-radius:12px;background: ${ job . id === selectedJob ( ) . id ? '#FFFCFA' : '#FCFCFD' } ;padding:10px;display:grid;gap:6px ` }
>
< div style = "display:flex;justify-content:space-between;align-items:center" >
< p style = "margin:0;font-size:18px;line-height:1.25;font-weight:800;color:#111827" > { job . title } < / p >
< span style = "height:20px;padding:0 8px;border-radius:999px;background:#FFF1EB;color:#C2410C;font-size:10px;font-weight:700;display:inline-flex;align-items:center" > Top Match < / span >
< / div >
< p style = "margin:0;font-size:13px;color:#4B5563" > { job . company } • { job . location } < / p >
< p style = "margin:0;font-size:13px;color:#6B7280;line-height:1.45" > We are looking for a visionary professional to lead core product experiences and collaborate across teams . < / p >
< div style = "display:flex;justify-content:space-between;align-items:center;gap:8px" >
< div style = "display:flex;flex-wrap:wrap;gap:6px" >
< For each = { job . tags . slice ( 0 , 3 ) } >
{ ( tag ) = > < span style = "height:22px;padding:0 8px;border-radius:7px;border:1px solid #E5E7EB;background:#F3F4F6;font-size:10px;font-weight:700;color:#4B5563;display:inline-flex;align-items:center" > { tag } < / span > }
< / For >
< / div >
< p style = "margin:0;font-size:20px;line-height:1.1;font-weight:800;color:#111827" > { job . salary } < / p >
< / div >
< / button >
) }
< / For >
< / div >
< div style = "border:1px solid #E5E7EB;border-radius:14px;background:white;box-shadow:0 1px 4px rgba(0,0,0,0.06);overflow:hidden;max-height:78vh;display:flex;flex-direction:column" >
< div style = "height:140px;background:linear-gradient(180deg,#d8e3eb 0%,#f8fafc 100%);position:relative" >
< button type = "button" onClick = { ( ) = > setJobSeekerScreen ( 'list' ) } style = "position:absolute;right:10px;top:10px;width:28px;height:28px;border-radius:999px;border:1px solid rgba(255,255,255,0.7);background:rgba(17,24,39,0.35);color:white;display:flex;align-items:center;justify-content:center" > × < / button >
< / div >
< div style = "padding:12px;display:grid;gap:10px;overflow:auto" >
< div >
< p style = "margin:0;font-size:26px;line-height:1.2;font-weight:800;color:#111827" > { selectedJob ( ) . title } < / p >
< p style = "margin:6px 0 0;font-size:13px;font-weight:700;color:#C2410C" > { selectedJob ( ) . company } • { selectedJob ( ) . location } < / p >
< / div >
< div style = "display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px" >
{ [
[ 'Location' , selectedJob ( ) . location ] ,
[ 'Experience' , selectedJob ( ) . exp ] ,
[ 'Employment' , selectedJob ( ) . type ] ,
[ 'Compensation' , selectedJob ( ) . salary ] ,
[ 'Match' , selectedJob ( ) . match ] ,
[ 'Posted' , selectedJob ( ) . posted ] ,
] . map ( ( [ label , val ] ) = > (
< div style = "border:1px solid #E5E7EB;border-radius:10px;background:#F9FAFB;padding:10px" >
< p style = "margin:0;font-size:10px;letter-spacing:0.06em;text-transform:uppercase;color:#9CA3AF" > { label } < / p >
< p style = "margin:5px 0 0;font-size:13px;font-weight:700;color:#111827" > { val } < / p >
< / div >
) ) }
< / div >
< div >
< p style = "margin:0;font-size:11px;letter-spacing:0.09em;text-transform:uppercase;color:#6B7280" > Skills < / p >
< div style = "display:flex;flex-wrap:wrap;gap:6px;margin-top:7px" >
< For each = { selectedJob ( ) . tags } >
{ ( tag ) = > < span style = "height:24px;padding:0 9px;border-radius:8px;border:1px solid #E5E7EB;background:#F3F4F6;font-size:10px;font-weight:700;color:#374151;display:inline-flex;align-items:center" > { tag } < / span > }
< / For >
< / div >
< / div >
< div >
< p style = "margin:0;font-size:11px;letter-spacing:0.09em;text-transform:uppercase;color:#6B7280" > The Opportunity < / p >
< p style = "margin:6px 0 0;font-size:14px;line-height:1.6;color:#374151" > As a senior role at Nxtgauge , you will shape product experiences used by thousands of professionals and build systems that scale with ambition . < / p >
< / div >
< div >
< p style = "margin:0;font-size:11px;letter-spacing:0.09em;text-transform:uppercase;color:#6B7280" > Core Requirements < / p >
< div style = "display:grid;gap:6px;margin-top:6px" >
{ [
'Mastery of product and design system governance.' ,
'Strong track record of shipping complex B2B products.' ,
'Deep understanding of accessibility and UX research.' ,
] . map ( ( item ) = > < p style = "margin:0;font-size:13px;color:#374151;line-height:1.45" > • { item } < / p > ) }
< / div >
< / div >
< div style = "border:1px solid #191970;border-radius:12px;background:#03004E;color:white;padding:12px" >
< p style = "margin:0;font-size:10px;letter-spacing:0.08em;text-transform:uppercase;color:#CDD4FF" > Total Compensation < / p >
< p style = "margin:6px 0 0;font-size:24px;line-height:1.2;font-weight:800" > { selectedJob ( ) . salary } < span style = "font-size:12px;font-weight:600" > / year < / span > < / p >
< div style = "display:flex;gap:8px;margin-top:10px" >
< button type = "button" onClick = { ( ) = > { setJobSeekerApplyStep ( 2 ) ; setJobSeekerScreen ( 'apply' ) ; } } style = "height:36px;flex:1;border:none;border-radius:9px;background:#FF5E13;color:white;padding:0 12px;font-size:12px;font-weight:700" > Apply Now < / button >
< button type = "button" style = "width:36px;height:36px;border-radius:9px;border:1px solid rgba(255,255,255,0.3);background:transparent;color:white;display:flex;align-items:center;justify-content:center" > < Bookmark size = { 14 } / > < / button >
< / div >
< p style = "margin:8px 0 0;font-size:10px;letter-spacing:0.08em;text-transform:uppercase;color:#AAB4FF;text-align:center" > Application update : { selectedJob ( ) . posted } < / p >
< / div >
< / div >
< / div >
< / div >
) ;
}
return (
< div style = "display:flex;flex-direction:column;gap:10px" >
< div style = "display:grid;grid-template-columns:repeat(5,minmax(0,1fr));gap:8px" >
{ [
{ label : 'Matching Jobs' , value : '124' , accent : '#B91C1C' , hint : '' } ,
{ label : 'Saved Jobs' , value : '12' , accent : '#4338CA' , hint : '' } ,
{ label : 'Applied' , value : '48' , accent : '#6B7280' , hint : '' } ,
{ label : 'Shortlisted' , value : '06' , accent : '#0EA5E9' , hint : '' } ,
{ label : 'New Today' , value : '18' , accent : 'white' , hint : 'dark' } ,
] . map ( ( card ) = > (
< div style = { ` border:1px solid #E5E7EB;border-radius:12px;padding:10px;background: ${ card . hint === 'dark' ? '#03004E' : 'white' } ;box-shadow:0 1px 3px rgba(0,0,0,0.05) ` } >
< p style = { ` margin:0;font-size:10px;letter-spacing:0.06em;text-transform:uppercase;color: ${ card . hint === 'dark' ? '#D7DBFF' : '#6B7280' } ` } > { card . label } < / p >
< p style = { ` margin:5px 0 0;font-size:34px;line-height:1;font-weight:800;color: ${ card . hint === 'dark' ? 'white' : card . accent } ` } > { card . value } < / p >
< / div >
) ) }
< / div >
< div style = "border:1px solid #E5E7EB;border-radius:12px;background:white;padding:10px;display:flex;align-items:center;gap:8px;flex-wrap:wrap;box-shadow:0 1px 3px rgba(0,0,0,0.05)" >
< button type = "button" style = "height:32px;border-radius:8px;border:1px solid #E5E7EB;background:#F9FAFB;padding:0 10px;font-size:12px;font-weight:700;color:#374151" > Filters < / button >
{ [ 'Role: All Roles' , 'Industry: Tech' , 'Location: Remote' , 'Salary: $100k+' , 'Experience: Senior' ] . map ( ( item ) = > (
< button type = "button" style = "height:32px;border-radius:8px;border:1px solid #E5E7EB;background:white;padding:0 10px;font-size:12px;color:#374151" > { item } < / button >
) ) }
< button type = "button" style = "height:32px;border-radius:8px;border:none;background:#EEF2FF;padding:0 10px;font-size:12px;font-weight:700;color:#3730A3" > Reset < / button >
< / div >
< div style = "display:grid;gap:8px" >
< For each = { selectedOpenJobCards ( ) } >
{ ( job ) = > (
< div style = "border:1px solid #E5E7EB;border-radius:12px;background:white;padding:10px;display:grid;grid-template-columns:1fr auto;gap:10px;box-shadow:0 1px 3px rgba(0,0,0,0.05)" >
< div >
< p style = "margin:0;font-size:20px;line-height:1.25;font-weight:800;color:#111827" > { job . title } < / p >
< p style = "margin:5px 0 0;font-size:13px;color:#4B5563" > { job . company } • { job . location } < / p >
< div style = "display:flex;flex-wrap:wrap;gap:6px;margin-top:8px" >
{ [ job . salary , job . exp , job . type ] . map ( ( pill , idx ) = > (
< span style = { ` height:22px;padding:0 8px;border-radius:8px;border:1px solid #E5E7EB;background: ${ idx === 2 ? '#FFF8F4' : '#F9FAFB' } ;font-size:10px;font-weight:700;color: ${ idx === 2 ? '#C2410C' : '#374151' } ;display:inline-flex;align-items:center ` } > { pill } < / span >
) ) }
< / div >
< div style = "display:flex;flex-wrap:wrap;gap:6px;margin-top:8px" >
< For each = { job . tags } > { ( tag ) = > < span style = "height:22px;padding:0 8px;border-radius:7px;border:1px solid #E5E7EB;background:#F3F4F6;font-size:10px;font-weight:700;color:#4B5563;display:inline-flex;align-items:center" > { tag } < / span > } < / For >
< / div >
< / div >
< div style = "display:flex;flex-direction:column;align-items:flex-end;justify-content:space-between;gap:8px;min-width:200px" >
< div style = "text-align:right" >
< span style = "height:20px;padding:0 8px;border-radius:999px;background:#FFF1EB;color:#C2410C;font-size:10px;font-weight:700;display:inline-flex;align-items:center" > { job . match } < / span >
< p style = "margin:8px 0 0;font-size:12px;color:#9CA3AF" > { job . posted } < / p >
< / div >
< div style = "display:flex;align-items:center;gap:6px" >
< button
type = "button"
onClick = { ( ) = > {
setJobSeekerSelectedId ( job . id ) ;
setJobSeekerScreen ( 'detail' ) ;
} }
style = "height:32px;border-radius:8px;border:1px solid #E5E7EB;background:white;padding:0 10px;font-size:12px;font-weight:700;color:#374151"
>
View Details
< / button >
< button
type = "button"
onClick = { ( ) = > {
setJobSeekerSelectedId ( job . id ) ;
setJobSeekerApplyStep ( 2 ) ;
setJobSeekerScreen ( 'apply' ) ;
} }
style = "height:32px;border-radius:8px;border:none;background:#03004E;padding:0 12px;font-size:12px;font-weight:700;color:white"
>
Apply Now
< / button >
< / div >
< / div >
< / div >
) }
< / For >
< / div >
< div style = "display:flex;justify-content:center" >
< button type = "button" style = "height:34px;border-radius:10px;border:1px solid #FFD8C2;background:#FFFCFA;padding:0 16px;font-size:12px;font-weight:700;color:#C2410C" > Show More Opportunities < / button >
< / div >
< / div >
) ;
}
const progressWidth = ( ) = > ` ${ Math . round ( ( jobPostStep ( ) / COMPANY_JOB_STEPS . length ) * 100 ) } % ` ;
const latestSubmission = ( ) = > companyJobSubmissions ( ) [ 0 ] || null ;
const latestStatusLabel = ( ) = > {
const status = latestSubmission ( ) ? . status ;
if ( status === 'VERIFICATION_PENDING' ) return 'In Verification Management' ;
if ( status === 'VERIFIED' || status === 'APPROVAL_PENDING' ) return 'In Approval Management' ;
if ( status === 'APPROVED_LIVE' ) return 'Approved and Live' ;
return 'Submitted' ;
} ;
const goNextJobStep = ( ) = > {
if ( jobPostStep ( ) >= COMPANY_JOB_STEPS . length ) {
setJobPostView ( 'review' ) ;
return ;
}
setJobPostStep ( ( prev ) = > Math . min ( COMPANY_JOB_STEPS . length , prev + 1 ) ) ;
} ;
const goPrevJobStep = ( ) = > setJobPostStep ( ( prev ) = > Math . max ( 1 , prev - 1 ) ) ;
if ( jobPostView ( ) === 'success' ) {
return (
< div style = "display:grid;grid-template-columns:2fr 1fr;gap:10px" >
< div style = "border:1px solid #E5E7EB;background:white;border-radius:16px;padding:20px;box-shadow:0 1px 4px rgba(0,0,0,0.06)" >
< div style = "display:flex;flex-direction:column;align-items:center;text-align:center;padding:16px 0 8px" >
< div style = "width:150px;height:150px;border-radius:999px;border:1px solid #F3E2DA;background:#FFF9F5;display:flex;align-items:center;justify-content:center" >
< div style = "width:56px;height:56px;border-radius:999px;background:#C2410C;color:white;display:flex;align-items:center;justify-content:center;font-size:28px;font-weight:800" > ✓ < / div >
< / div >
< p style = "margin:14px 0 0;font-size:52px;line-height:1.05;font-weight:800;color:#0D0D2A" > Job Submitted Successfully ! < / p >
< p style = "margin:10px 0 0;font-size:14px;color:#4B5563;max-width:640px;line-height:1.5" > Your listing for < strong > { companyJobDraft ( ) . title } < / strong > has been sent to Verification Management first , then Approval Management . Job seekers can see it only after final approval . < / p >
< p style = "margin:8px 0 0;font-size:12px;font-weight:700;color:#C2410C" > { latestStatusLabel ( ) } < / p >
< div style = "display:flex;gap:8px;margin-top:14px" >
< button type = "button" style = "height:36px;border-radius:10px;border:1px solid #E5E7EB;background:white;padding:0 14px;font-size:12px;font-weight:700;color:#374151" > View Job Details < / button >
< button type = "button" onClick = { ( ) = > { setJobPostView ( 'form' ) ; setJobPostStep ( 1 ) ; } } style = "height:36px;border-radius:10px;border:none;background:#C2410C;padding:0 14px;font-size:12px;font-weight:700;color:white" > Post Another Job < / button >
< / div >
< / div >
< div style = "margin-top:14px;border:1px solid #E5E7EB;border-radius:14px;background:#FCFCFD;padding:14px" >
< div style = "display:flex;justify-content:space-between;align-items:center" >
< p style = "margin:0;font-size:30px;font-weight:800;color:#111827" > Approval Tracking < / p >
< span style = "height:24px;padding:0 10px;border-radius:999px;background:#FFF1EB;color:#C2410C;font-size:11px;font-weight:700;display:inline-flex;align-items:center" > REF : # JX - 9902 < / span >
< / div >
< div style = "display:grid;gap:10px;margin-top:12px" >
{ [
{ title : 'Job Submitted' , desc : 'Form submitted by company.' , done : true } ,
{ title : 'Verification Management' , desc : 'Job content and policy checks run here first.' , done : ! ! latestSubmission ( ) && latestSubmission ( ) ! . status !== 'VERIFICATION_PENDING' } ,
{ title : 'Approval Management' , desc : 'Post-verification approval gate for publishing.' , done : ! ! latestSubmission ( ) && latestSubmission ( ) ! . status === 'APPROVED_LIVE' } ,
{ title : 'Published To Job Seekers' , desc : 'Visible in Jobs list and View Details only after approval.' , done : ! ! latestSubmission ( ) && latestSubmission ( ) ! . status === 'APPROVED_LIVE' } ,
] . map ( ( row , idx ) = > (
< div style = "display:flex;align-items:flex-start;gap:10px" >
< span style = { ` width:18px;height:18px;border-radius:999px;display:inline-flex;align-items:center;justify-content:center;font-size:10px;font-weight:800;flex-shrink:0;background: ${ row . done ? '#FFF1EB' : '#F3F4F6' } ;color: ${ row . done ? '#C2410C' : '#9CA3AF' } ` } > { row . done ? '✓' : idx + 1 } < / span >
< div >
< p style = { ` margin:0;font-size:14px;font-weight:700;color: ${ row . done ? '#111827' : '#94A3B8' } ` } > { row . title } < / p >
< p style = { ` margin:2px 0 0;font-size:12px;line-height:1.45;color: ${ row . done ? '#6B7280' : '#B8C1D1' } ` } > { row . desc } < / p >
< / div >
< / div >
) ) }
< / div >
< / div >
< / div >
< div style = "display:flex;flex-direction:column;gap:10px" >
< div style = "border:1px solid #E5E7EB;background:#03004E;border-radius:16px;padding:14px;color:white;box-shadow:0 1px 4px rgba(0,0,0,0.06)" >
< p style = "margin:0;font-size:30px;line-height:1.15;font-weight:800" > Did you know ? < / p >
< p style = "margin:8px 0 0;font-size:13px;line-height:1.55;color:#D5D8FF" > Jobs with high - quality company descriptions receive < strong > 40 % more applications < / strong > . Take a moment to update your profile . < / p >
< button type = "button" style = "margin-top:10px;height:30px;border:none;border-radius:8px;background:#C2410C;padding:0 10px;font-size:12px;font-weight:700;color:white" > Enhance Profile < / button >
< / div >
< div style = "border:1px solid #E5E7EB;background:white;border-radius:16px;padding:14px;box-shadow:0 1px 4px rgba(0,0,0,0.06)" >
< p style = "margin:0;font-size:18px;font-weight:800;color:#111827" > Back to Dashboard < / p >
< p style = "margin:6px 0 0;font-size:12px;color:#6B7280" > Monitor all your active listings and candidate inflow . < / p >
< button type = "button" style = "margin-top:10px;height:34px;width:100%;border:1px solid #E5E7EB;border-radius:10px;background:#F9FAFB;padding:0 12px;font-size:12px;font-weight:700;color:#374151" > Go to Dashboard < / button >
< / div >
< div style = "border:1px solid #E5E7EB;background:white;border-radius:12px;padding:10px;display:flex;align-items:center;justify-content:space-between" >
< div >
< p style = "margin:0;font-size:13px;font-weight:700;color:#111827" > Need help ? < / p >
< p style = "margin:2px 0 0;font-size:11px;color:#6B7280" > Chat with a hiring specialist < / p >
< / div >
< button type = "button" style = "height:28px;border:none;border-radius:8px;background:#FFF1EB;color:#C2410C;padding:0 10px;font-size:11px;font-weight:700" > Connect < / button >
< / div >
< / div >
< / div >
) ;
}
if ( jobPostView ( ) === 'review' ) {
return (
< div style = "display:grid;grid-template-columns:2fr 1fr;gap:10px" >
< div style = "display:flex;flex-direction:column;gap:10px" >
< div style = "border:1px solid #E5E7EB;background:white;border-radius:16px;padding:14px;box-shadow:0 1px 4px rgba(0,0,0,0.06)" >
< p style = "margin:0;font-size:11px;letter-spacing:0.05em;text-transform:uppercase;color:#9CA3AF" > Jobs & gt ; Review & Checkout < / p >
< p style = "margin:6px 0 0;font-size:50px;line-height:1.05;font-weight:800;color:#0D0D2A" > Review & Checkout < / p >
< p style = "margin:8px 0 0;font-size:14px;color:#6B7280" > Step 5 of 5 : Finalize your job posting details and approve payment . < / p >
< / div >
< div style = "border:1px solid #E5E7EB;background:white;border-radius:16px;padding:14px;box-shadow:0 1px 4px rgba(0,0,0,0.06)" >
< div style = "display:flex;justify-content:space-between;align-items:center" >
< p style = "margin:0;font-size:35px;font-weight:800;color:#111827" > Job Basics < / p >
< button type = "button" style = "height:28px;border:none;border-radius:8px;background:#FFF1EB;color:#C2410C;padding:0 10px;font-size:11px;font-weight:700" > Edit < / button >
< / div >
< div style = "display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-top:10px" >
{ [
[ 'Job Title' , companyJobDraft ( ) . title ] ,
[ 'Employment Type' , companyJobDraft ( ) . type ] ,
[ 'Company Department' , companyJobDraft ( ) . department ] ,
[ 'Openings' , companyJobDraft ( ) . openings ] ,
] . map ( ( [ label , value ] ) = > (
< div style = "border:1px solid #E5E7EB;border-radius:10px;background:#F9FAFB;padding:10px" >
< p style = "margin:0;font-size:10px;letter-spacing:0.06em;text-transform:uppercase;color:#9CA3AF" > { label } < / p >
< p style = "margin:6px 0 0;font-size:20px;font-weight:800;color:#111827;line-height:1.2" > { value } < / p >
< / div >
) ) }
< / div >
< / div >
< div style = "border:1px solid #E5E7EB;background:white;border-radius:16px;padding:14px;box-shadow:0 1px 4px rgba(0,0,0,0.06)" >
< div style = "display:flex;justify-content:space-between;align-items:center" >
< p style = "margin:0;font-size:35px;font-weight:800;color:#111827" > Role & Requirements < / p >
< button type = "button" style = "height:28px;border:none;border-radius:8px;background:#FFF1EB;color:#C2410C;padding:0 10px;font-size:11px;font-weight:700" > Edit < / button >
< / div >
< p style = "margin:10px 0 0;font-size:11px;letter-spacing:0.05em;text-transform:uppercase;color:#9CA3AF" > Technical Skills < / p >
< div style = "display:flex;flex-wrap:wrap;gap:6px;margin-top:6px" >
< For each = { companyJobDraft ( ) . tags } >
{ ( skill ) = > < span style = "height:24px;padding:0 8px;border-radius:8px;border:1px solid #E5E7EB;background:#F3F4F6;font-size:11px;font-weight:700;color:#374151;display:inline-flex;align-items:center" > { skill } < / span > }
< / For >
< / div >
< p style = "margin:10px 0 0;font-size:11px;letter-spacing:0.05em;text-transform:uppercase;color:#9CA3AF" > Required Experience < / p >
< p style = "margin:4px 0 0;font-size:16px;font-weight:700;color:#111827" > { companyJobDraft ( ) . exp } < / p >
< / div >
< div style = "border:1px solid #E5E7EB;background:white;border-radius:16px;padding:14px;box-shadow:0 1px 4px rgba(0,0,0,0.06)" >
< div style = "display:flex;justify-content:space-between;align-items:center" >
< p style = "margin:0;font-size:35px;font-weight:800;color:#111827" > Compensation & Location < / p >
< button type = "button" style = "height:28px;border:none;border-radius:8px;background:#FFF1EB;color:#C2410C;padding:0 10px;font-size:11px;font-weight:700" > Edit < / button >
< / div >
< div style = "display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-top:10px" >
< div style = "border:1px solid #E5E7EB;border-radius:10px;background:#F9FAFB;padding:10px" >
< p style = "margin:0;font-size:10px;letter-spacing:0.06em;text-transform:uppercase;color:#9CA3AF" > Salary Range < / p >
< p style = "margin:6px 0 0;font-size:20px;font-weight:800;color:#111827" > { companyJobDraft ( ) . salary } / year < / p >
< / div >
< div style = "border:1px solid #E5E7EB;border-radius:10px;background:#F9FAFB;padding:10px" >
< p style = "margin:0;font-size:10px;letter-spacing:0.06em;text-transform:uppercase;color:#9CA3AF" > Location < / p >
< p style = "margin:6px 0 0;font-size:20px;font-weight:800;color:#111827" > { companyJobDraft ( ) . location } < / p >
< / div >
< / div >
< / div >
< / div >
< div style = "display:flex;flex-direction:column;gap:10px" >
< div style = "border:1px solid #E5E7EB;background:white;border-radius:16px;overflow:hidden;box-shadow:0 1px 4px rgba(0,0,0,0.06)" >
< div style = "background:#03004E;padding:12px;color:white" >
< p style = "margin:0;font-size:30px;font-weight:800;line-height:1.1" > Pricing & Approval < / p >
< p style = "margin:6px 0 0;font-size:13px;color:#D7DBFF" > Review costs and confirm submission < / p >
< / div >
< div style = "padding:12px" >
< div style = "border:1px solid #E5E7EB;border-radius:10px;background:#F9FAFB;padding:10px;display:flex;justify-content:space-between;align-items:center" >
< span style = "font-size:14px;color:#374151" > Post Status < / span >
< span style = "font-size:14px;font-weight:700;color:#C2410C" > This is a Paid Job Post < / span >
< / div >
< div style = "display:grid;gap:8px;margin-top:10px" >
< div style = "display:flex;justify-content:space-between;font-size:14px;color:#374151" > < span > Credits Required < / span > < strong style = "color:#111827" > 500 Tracecoins < / strong > < / div >
< div style = "display:flex;justify-content:space-between;font-size:14px;color:#374151" > < span > Current Balance < / span > < strong style = "color:#111827" > 1200 Tracecoins < / strong > < / div >
< div style = "height:1px;background:#E5E7EB" / >
< div style = "display:flex;justify-content:space-between;font-size:16px;color:#111827;font-weight:800" > < span > Remaining Balance < / span > < span style = "color:#16A34A" > 700 Tracecoins < / span > < / div >
< / div >
< div style = "margin-top:10px;border:1px solid #FFD8C2;border-radius:10px;background:#FFF8F4;padding:10px" >
< p style = "margin:0;font-size:13px;font-weight:700;color:#111827" > Approval Required : Yes < / p >
< p style = "margin:4px 0 0;font-size:12px;color:#6B7280;line-height:1.45" > Your post will be reviewed by our moderation team within 24 hours . < / p >
< / div >
< button type = "button" onClick = { ( ) = > { submitCompanyJobForReview ( ) ; setJobPostView ( 'success' ) ; } } style = "margin-top:10px;height:38px;width:100%;border:none;border-radius:10px;background:#03004E;color:white;font-size:12px;font-weight:700" > Pay 500 Tracecoins & Submit < / button >
< / div >
< / div >
< div style = "border:1px solid #FFD8C2;background:linear-gradient(135deg,#3A1E00 0%,#C2410C 90%);border-radius:16px;padding:14px;color:white;box-shadow:0 1px 4px rgba(0,0,0,0.06)" >
< p style = "margin:0;font-size:11px;letter-spacing:0.06em;text-transform:uppercase;color:#FFE7DA" > Pro Tip < / p >
< p style = "margin:8px 0 0;font-size:16px;line-height:1.4;font-weight:700" > Boost your visibility by 40 % with Sponsored Highlights . < / p >
< button type = "button" style = "margin-top:10px;height:30px;border:1px solid rgba(255,255,255,0.35);border-radius:8px;background:transparent;color:white;padding:0 10px;font-size:12px;font-weight:700" > Learn More < / button >
< / div >
< div style = "display:flex;justify-content:space-between;gap:8px" >
< button type = "button" onClick = { ( ) = > { setJobPostView ( 'form' ) ; setJobPostStep ( 4 ) ; } } style = "height:34px;flex:1;border-radius:8px;border:1px solid #E5E7EB;background:white;color:#374151;padding:0 12px;font-size:12px;font-weight:700" > Back < / button >
< button type = "button" onClick = { ( ) = > { submitCompanyJobForReview ( ) ; setJobPostView ( 'success' ) ; } } style = "height:34px;flex:1;border-radius:8px;border:none;background:#C2410C;color:white;padding:0 12px;font-size:12px;font-weight:700" > Confirm & Submit < / button >
< / div >
< / div >
< / div >
) ;
}
return (
< div style = "display:flex;flex-direction:column;gap:10px" >
< div style = "display:grid;grid-template-columns:2fr 3fr;gap:10px" >
< div style = "border:1px solid #E5E7EB;background:white;border-radius:16px;padding:14px;box-shadow:0 1px 4px rgba(0,0,0,0.06)" >
< p style = "margin:0;font-size:11px;letter-spacing:0.06em;text-transform:uppercase;color:#9CA3AF" > Billing Policy < / p >
< p style = "margin:6px 0 0;font-size:34px;line-height:1.1;font-weight:800;color:#111827" > Job Posting Rule < / p >
< span style = "margin-top:8px;height:22px;padding:0 8px;border-radius:999px;background:#ECFDF3;color:#15803D;font-size:11px;font-weight:700;display:inline-flex;align-items:center" > Free First Job < / span >
< div style = "display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-top:10px" >
< div style = "border:1px solid #E5E7EB;border-radius:10px;background:#F9FAFB;padding:10px" > < p style = "margin:0;font-size:11px;color:#6B7280" > Cost Required < / p > < p style = "margin:6px 0 0;font-size:20px;font-weight:800;color:#111827" > 0 Tracecoins < / p > < / div >
< div style = "border:1px solid #E5E7EB;border-radius:10px;background:#F9FAFB;padding:10px" > < p style = "margin:0;font-size:11px;color:#6B7280" > Usage Count < / p > < p style = "margin:6px 0 0;font-size:20px;font-weight:800;color:#111827" > 1 Job Posted < / p > < / div >
< / div >
< / div >
< div style = "border:1px solid #E5E7EB;background:white;border-radius:16px;padding:14px;box-shadow:0 1px 4px rgba(0,0,0,0.06)" >
< p style = "margin:0;font-size:11px;letter-spacing:0.06em;text-transform:uppercase;color:#9CA3AF" > Lifecycle < / p >
< p style = "margin:6px 0 0;font-size:34px;line-height:1.1;font-weight:800;color:#111827" > Approval Flow Status < / p >
< div style = "display:flex;align-items:center;gap:6px;margin-top:12px" >
< For each = { [ 'Draft' , 'Submitted' , 'Under Review' , 'Approved' , 'Live' ] } >
{ ( step , idx ) = > (
< >
< span style = { ` width:26px;height:26px;border-radius:999px;display:inline-flex;align-items:center;justify-content:center;font-size:11px;font-weight:800;background: ${ idx ( ) + 1 <= jobPostStep ( ) ? '#C2410C' : '#E5E7EB' } ;color: ${ idx ( ) + 1 <= jobPostStep ( ) ? 'white' : '#9CA3AF' } ` } > { idx ( ) + 1 } < / span >
< Show when = { idx ( ) < 4 } >
< span style = { ` height:2px;flex:1;min-width:18px;background: ${ idx ( ) + 1 < jobPostStep ( ) ? '#C2410C' : '#E5E7EB' } ` } / >
< / Show >
< / >
) }
< / For >
< / div >
< / div >
< / div >
< div style = "display:grid;grid-template-columns:2fr 1fr;gap:10px" >
< div style = "border:1px solid #E5E7EB;background:white;border-radius:16px;padding:14px;box-shadow:0 1px 4px rgba(0,0,0,0.06)" >
< p style = "margin:0;font-size:36px;font-weight:800;color:#111827" > Step { jobPostStep ( ) } : { COMPANY_JOB_STEPS [ jobPostStep ( ) - 1 ] } < / p >
< div style = "height:6px;border-radius:999px;background:#E5E7EB;overflow:hidden;margin-top:10px" > < div style = { ` height:100%;background:#C2410C;width: ${ progressWidth ( ) } ` } / > < / div >
< div style = "display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-top:12px" >
< div style = "grid-column:1 / -1;display:flex;flex-direction:column;gap:6px" > < p style = "margin:0;font-size:11px;letter-spacing:0.05em;text-transform:uppercase;color:#6B7280" > Job Title < / p > < div style = "height:38px;border:1px solid #E5E7EB;border-radius:10px;background:#F9FAFB;padding:0 10px;display:flex;align-items:center;font-size:12px;color:#374151" > { companyJobDraft ( ) . title } < / div > < / div >
< div style = "display:flex;flex-direction:column;gap:6px" > < p style = "margin:0;font-size:11px;letter-spacing:0.05em;text-transform:uppercase;color:#6B7280" > Department < / p > < div style = "height:38px;border:1px solid #E5E7EB;border-radius:10px;background:#F9FAFB;padding:0 10px;display:flex;align-items:center;justify-content:space-between;font-size:12px;color:#374151" > < span > { companyJobDraft ( ) . department } < / span > < span > ▾ < / span > < / div > < / div >
< div style = "display:flex;flex-direction:column;gap:6px" > < p style = "margin:0;font-size:11px;letter-spacing:0.05em;text-transform:uppercase;color:#6B7280" > Job Category < / p > < div style = "height:38px;border:1px solid #E5E7EB;border-radius:10px;background:#F9FAFB;padding:0 10px;display:flex;align-items:center;justify-content:space-between;font-size:12px;color:#374151" > < span > Select Category < / span > < span > ▾ < / span > < / div > < / div >
< div style = "grid-column:1 / -1;display:flex;flex-direction:column;gap:6px" > < p style = "margin:0;font-size:11px;letter-spacing:0.05em;text-transform:uppercase;color:#6B7280" > Employment Type < / p > < div style = "display:flex;gap:6px" > < span style = "height:28px;padding:0 10px;border-radius:999px;border:1px solid #E5E7EB;background:#FFF1EB;color:#C2410C;display:inline-flex;align-items:center;font-size:11px;font-weight:700" > { companyJobDraft ( ) . type } < / span > < / div > < / div >
< div style = "display:flex;flex-direction:column;gap:6px" > < p style = "margin:0;font-size:11px;letter-spacing:0.05em;text-transform:uppercase;color:#6B7280" > Seniority < / p > < div style = "height:38px;border:1px solid #E5E7EB;border-radius:10px;background:#F9FAFB;padding:0 10px;display:flex;align-items:center;justify-content:space-between;font-size:12px;color:#374151" > < span > Entry Level < / span > < span > ▾ < / span > < / div > < / div >
< div style = "display:flex;flex-direction:column;gap:6px" > < p style = "margin:0;font-size:11px;letter-spacing:0.05em;text-transform:uppercase;color:#6B7280" > Openings < / p > < div style = "height:38px;border:1px solid #E5E7EB;border-radius:10px;background:#F9FAFB;padding:0 10px;display:flex;align-items:center;font-size:12px;color:#374151" > { companyJobDraft ( ) . openings } < / div > < / div >
< div style = "grid-column:1 / -1;display:flex;flex-direction:column;gap:6px" > < p style = "margin:0;font-size:11px;letter-spacing:0.05em;text-transform:uppercase;color:#6B7280" > Description < / p > < div style = "min-height:120px;border:1px solid #E5E7EB;border-radius:10px;background:#F9FAFB;padding:10px;font-size:12px;color:#9CA3AF" > Detailed job responsibilities , skills , and expectations . . . < / div > < / div >
< / div >
< div style = "display:flex;justify-content:space-between;align-items:center;gap:8px;margin-top:12px;border-top:1px solid #E5E7EB;padding-top:10px" >
< button type = "button" onClick = { goPrevJobStep } disabled = { jobPostStep ( ) === 1 } style = { ` height:34px;border-radius:8px;border:1px solid #E5E7EB;background:white;padding:0 12px;font-size:12px;font-weight:700;color: ${ jobPostStep ( ) === 1 ? '#9CA3AF' : '#374151' } ;cursor: ${ jobPostStep ( ) === 1 ? 'not-allowed' : 'pointer' } ` } > Back < / button >
< div style = "display:flex;gap:8px" >
< button type = "button" style = "height:34px;border-radius:8px;border:1px solid #E5E7EB;background:white;padding:0 12px;font-size:12px;font-weight:700;color:#374151" > Save as Draft < / button >
< button type = "button" onClick = { jobPostStep ( ) === COMPANY_JOB_STEPS . length ? ( ) = > setJobPostView ( 'review' ) : goNextJobStep } style = "height:34px;border-radius:8px;border:none;background:#03004E;padding:0 12px;font-size:12px;font-weight:700;color:white" >
{ jobPostStep ( ) === COMPANY_JOB_STEPS . length ? 'Go To Review' : 'Next: Role & Requirements' }
< / button >
< / div >
< / div >
< / div >
< div style = "display:flex;flex-direction:column;gap:10px" >
< div style = "border:1px solid #D9D8F9;background:#4F4B8A;border-radius:16px;padding:14px;color:white;box-shadow:0 1px 4px rgba(0,0,0,0.06)" >
< div style = "display:flex;justify-content:space-between;align-items:center" >
< p style = "margin:0;font-size:11px;letter-spacing:0.06em;text-transform:uppercase;color:#DADAFB" > Live Preview < / p >
< span style = "height:20px;padding:0 8px;border-radius:999px;border:1px solid rgba(255,255,255,0.3);font-size:10px;font-weight:700;display:inline-flex;align-items:center" > FREE POST < / span >
< / div >
< p style = "margin:10px 0 0;font-size:10px;color:#DADAFB;letter-spacing:0.06em;text-transform:uppercase" > Position < / p >
< p style = "margin:4px 0 0;font-size:26px;line-height:1.15;font-weight:800;color:white" > Product Design Manager < / p >
< div style = "display:grid;gap:8px;margin-top:12px" >
{ [
[ 'Location' , 'Remote / San Francisco' ] ,
[ 'Type' , 'Full-Time Role' ] ,
[ 'Posting Fee' , '$0.00 (Tracecoins: 0)' ] ,
] . map ( ( [ label , val ] ) = > (
< div style = "display:flex;justify-content:space-between;align-items:center;gap:8px" >
< span style = "font-size:11px;color:#D7DBFF" > { label } < / span >
< span style = "font-size:12px;font-weight:700;color:white;text-align:right" > { val } < / span >
< / div >
) ) }
< / div >
< / div >
< div style = "border:1px solid #E5E7EB;background:#FFF8F4;border-radius:14px;padding:12px;box-shadow:0 1px 3px rgba(0,0,0,0.05)" >
< p style = "margin:0;font-size:12px;font-weight:700;color:#111827" > Need help writing ? < / p >
< p style = "margin:6px 0 0;font-size:12px;color:#6B7280;line-height:1.45" > Try our AI Generator to create a compelling description in seconds . < / p >
< button type = "button" style = "margin-top:8px;height:30px;border:none;border-radius:8px;background:#FFF1EB;color:#C2410C;padding:0 10px;font-size:11px;font-weight:700" > Launch AI Assistant < / button >
< / div >
< / div >
< / div >
< / div >
) ;
}
if ( customerKey ( ) === 'my requirements' ) {
const statusMeta = ( value : string ) = > {
const key = String ( value || '' ) . toLowerCase ( ) ;
if ( key === 'approved' ) return { bg : '#DCFCE7' , c : '#15803D' , label : 'Approved' } ;
if ( key === 'active' ) return { bg : '#DBEAFE' , c : '#1D4ED8' , label : 'Active' } ;
if ( key === 'under review' ) return { bg : '#FFEDD5' , c : '#C2410C' , label : 'Under Review' } ;
if ( key === 'draft' ) return { bg : '#F3F4F6' , c : '#4B5563' , label : 'Draft' } ;
if ( key === 'closed' ) return { bg : '#E5E7EB' , c : '#4B5563' , label : 'Closed' } ;
if ( key === 'rejected' ) return { bg : '#FEE2E2' , c : '#B91C1C' , label : 'Rejected' } ;
return { bg : '#F3F4F6' , c : '#6B7280' , label : titleCase ( value ) } ;
} ;
const filteredRows = requirementRows ( ) . filter ( ( row ) = > {
if ( tab === 'open' ) return row . status === 'active' || row . status === 'under review' ;
if ( tab === 'closed' ) return row . status === 'closed' ;
if ( tab === 'drafts' ) return row . status === 'draft' ;
return true ;
} ) ;
const selectedRoleDetails = ( ) = > REQUIREMENT_ROLE_DETAILS [ selectedRequirementRole ( ) ] || REQUIREMENT_ROLE_DETAILS . PHOTOGRAPHER ;
const steps = [ 'Choose Type' , 'Basic Details' , 'Role-Specific' , 'Budget & Location' , 'Attachments' , 'Review' ] ;
const canGoBack = ( ) = > requirementsStep ( ) > 1 ;
const nextLabel = ( ) = > ( requirementsStep ( ) === steps . length ? 'Submit For Approval' : 'Next' ) ;
const goNextStep = ( ) = > {
if ( requirementsStep ( ) >= steps . length ) {
submitRequirementForReview ( ) ;
return ;
}
setRequirementsStep ( ( prev ) = > Math . min ( steps . length , prev + 1 ) ) ;
} ;
const goBackStep = ( ) = > setRequirementsStep ( ( prev ) = > Math . max ( 1 , prev - 1 ) ) ;
if ( requirementsView ( ) === 'new' ) {
return (
< div style = "display:flex;flex-direction:column;gap:12px" >
< div style = "border:1px solid #E5E7EB;background:white;border-radius:16px;padding:16px;box-shadow:0 1px 4px rgba(0,0,0,0.06)" >
< div style = "display:flex;align-items:center;justify-content:space-between" >
< p style = "margin:0;font-size:20px;font-weight:800;color:#111827" > Post New Requirement < / p >
< button
type = "button"
onClick = { ( ) = > { setRequirementsView ( 'list' ) ; setRequirementsStep ( 1 ) ; } }
style = "height:30px;border-radius:8px;border:1px solid #E5E7EB;background:white;color:#374151;padding:0 10px;font-size:12px;font-weight:700"
>
Back To List
< / button >
< / div >
< div style = "display:flex;align-items:flex-start;justify-content:space-between;gap:8px;margin-top:12px" >
< For each = { steps } >
{ ( step , idx ) = > (
< div style = "flex:1;display:flex;align-items:center;gap:8px" >
< div style = "display:flex;flex-direction:column;align-items:center;gap:6px;min-width:58px" >
< span style = { ` width:32px;height:32px;border-radius:999px;display:inline-flex;align-items:center;justify-content:center;font-size:12px;font-weight:800;border:1px solid ${ requirementsStep ( ) > idx ( ) + 1 ? '#FF5E13' : '#D1D5DB' } ;background: ${ requirementsStep ( ) === idx ( ) + 1 ? '#FF5E13' : requirementsStep ( ) > idx ( ) + 1 ? '#FFF1EB' : '#F9FAFB' } ;color: ${ requirementsStep ( ) === idx ( ) + 1 ? 'white' : requirementsStep ( ) > idx ( ) + 1 ? '#FF5E13' : '#6B7280' } ` } > { idx ( ) + 1 } < / span >
< p style = { ` margin:0;font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:0.04em;color: ${ requirementsStep ( ) === idx ( ) + 1 ? '#111827' : '#9CA3AF' } ` } > { step } < / p >
< / div >
< Show when = { idx ( ) < steps . length - 1 } >
< div style = { ` height:2px;flex:1;background: ${ requirementsStep ( ) > idx ( ) + 1 ? '#FF5E13' : '#E5E7EB' } ;margin-top:16px ` } / >
< / Show >
< / div >
) }
< / For >
< / div >
< / div >
< Show when = { requirementsStep ( ) === 1 } >
< div style = "border:1px solid #E5E7EB;background:white;border-radius:16px;padding:16px;box-shadow:0 1px 4px rgba(0,0,0,0.06)" >
< p style = "margin:0;font-size:30px;font-weight:800;color:#111827;text-align:center" > What kind of professional are you looking for ? < / p >
< p style = "margin:8px 0 0;font-size:14px;color:#6B7280;text-align:center" > Select a category to start your requirement . This helps us match you with the right experts . < / p >
< div style = "display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px;margin-top:14px" >
< For each = { REQUIREMENT_ROLE_OPTIONS } >
{ ( role ) = > {
const active = selectedRequirementRole ( ) === role . key ;
return (
< button
type = "button"
onClick = { ( ) = > setSelectedRequirementRole ( role . key ) }
style = { ` text-align:left;border:1px solid ${ active ? '#FF5E13' : '#E5E7EB' } ;border-radius:12px;background:white;padding:12px;min-height:154px;display:flex;flex-direction:column;gap:10px ` }
>
< div style = "display:flex;align-items:center;justify-content:space-between" >
< span style = "width:40px;height:40px;border-radius:10px;background:#F3F4F6;border:1px solid #E5E7EB;display:flex;align-items:center;justify-content:center" >
< img src = { role . icon } alt = "" style = { ` width:18px;height:18px;object-fit:contain;filter: ${ ORANGE_ICON_FILTER } ` } / >
< / span >
< Show when = { active } >
< span style = "display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;border-radius:999px;background:#FFF1EB;border:1px solid #FFD8C2;color:#FF5E13;font-size:12px;font-weight:800" > ✓ < / span >
< / Show >
< / div >
< p style = "margin:0;font-size:16px;font-weight:800;color:#111827" > { role . title } < / p >
< p style = "margin:0;font-size:12px;line-height:1.5;color:#6B7280" > { role . desc } < / p >
< span style = { ` margin-top:auto;height:30px;border-radius:8px;display:inline-flex;align-items:center;justify-content:center;font-size:12px;font-weight:700; ${ active ? 'background:#FF5E13;color:white' : 'border:1px solid #E5E7EB;color:#FF5E13' } ` } > { active ? 'Continue' : 'Select' } < / span >
< / button >
) ;
} }
< / For >
< / div >
< / div >
< / Show >
< Show when = { requirementsStep ( ) === 2 } >
< div style = "border:1px solid #E5E7EB;background:white;border-radius:16px;padding:16px;box-shadow:0 1px 4px rgba(0,0,0,0.06)" >
< p style = "margin:0;font-size:30px;font-weight:800;color:#111827" > Basic Requirement Details < / p >
< p style = "margin:8px 0 0;font-size:13px;color:#6B7280" > Add core details before role - specific inputs . < / p >
< div style = "display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-top:12px" >
{ [ 'Requirement Title' , 'Priority' , 'Requirement Description' , 'Expected Start Date' , 'Service City' , 'Contact Number' ] . map ( ( field ) = > (
< div style = "display:flex;flex-direction:column;gap:6px" >
< p style = "margin:0;font-size:11px;font-weight:700;letter-spacing:0.05em;text-transform:uppercase;color:#6B7280" > { field } < / p >
< div style = "height:38px;border:1px solid #E5E7EB;border-radius:10px;background:#F9FAFB;padding:0 10px;display:flex;align-items:center;font-size:12px;color:#9CA3AF" >
Enter { field . toLowerCase ( ) }
< / div >
< / div >
) ) }
< / div >
< / div >
< / Show >
< Show when = { requirementsStep ( ) === 3 } >
< div style = "border:1px solid #E5E7EB;background:white;border-radius:16px;padding:16px;box-shadow:0 1px 4px rgba(0,0,0,0.06)" >
< p style = "margin:0;font-size:30px;font-weight:800;color:#111827" > { selectedRoleDetails ( ) . title } < / p >
< p style = "margin:8px 0 0;font-size:13px;color:#6B7280" > { selectedRoleDetails ( ) . subtitle } < / p >
< div style = "display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-top:12px" >
< For each = { selectedRoleDetails ( ) . fields } >
{ ( field , idx ) = > (
< div style = { ` display:flex;flex-direction:column;gap:6px; ${ field . includes ( 'Venue' ) ? 'grid-column:1 / -1' : '' } ` } >
< p style = "margin:0;font-size:11px;font-weight:700;letter-spacing:0.05em;text-transform:uppercase;color:#6B7280" > { field } < / p >
< div style = "height:38px;border:1px solid #E5E7EB;border-radius:10px;background:#F9FAFB;padding:0 10px;display:flex;align-items:center;justify-content:space-between;font-size:12px;color:#9CA3AF" >
< span > { idx ( ) % 2 === 0 ? 'Select' : 'Enter' } { field . toLowerCase ( ) } < / span >
< Show when = { idx ( ) % 2 === 0 } > < span > ▾ < / span > < / Show >
< / div >
< / div >
) }
< / For >
< / div >
< div style = "display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px;margin-top:12px" >
< For each = { selectedRoleDetails ( ) . toggles } >
{ ( toggleLabel ) = > (
< div style = "display:flex;align-items:center;gap:10px;padding:10px;border:1px solid #E5E7EB;border-radius:10px;background:#FBFBFB" >
< span style = "width:32px;height:18px;border-radius:999px;background:#E5E7EB;position:relative;display:inline-flex;align-items:center;padding:2px" >
< span style = "width:14px;height:14px;border-radius:999px;background:white" / >
< / span >
< span style = "font-size:12px;font-weight:700;color:#374151" > { toggleLabel } < / span >
< / div >
) }
< / For >
< / div >
< div style = "margin-top:12px" >
< p style = "margin:0;font-size:11px;font-weight:700;letter-spacing:0.05em;text-transform:uppercase;color:#6B7280" > Style Needed < / p >
< div style = "display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:8px;margin-top:8px" >
< For each = { selectedRoleDetails ( ) . styles } >
{ ( styleLabel ) = > (
< div style = "height:34px;border:1px solid #E5E7EB;border-radius:8px;background:#F9FAFB;padding:0 10px;display:flex;align-items:center;gap:8px;font-size:12px;color:#374151" >
< span style = "width:14px;height:14px;border-radius:4px;border:1px solid #F5B197;background:white" / >
{ styleLabel }
< / div >
) }
< / For >
< / div >
< / div >
< / div >
< / Show >
< Show when = { requirementsStep ( ) === 4 } >
< div style = "border:1px solid #E5E7EB;background:white;border-radius:16px;padding:16px;box-shadow:0 1px 4px rgba(0,0,0,0.06)" >
< p style = "margin:0;font-size:30px;font-weight:800;color:#111827" > Budget & Location < / p >
< p style = "margin:8px 0 0;font-size:13px;color:#6B7280" > Set your target budget and service location to get relevant quotes . < / p >
< div style = "display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-top:12px" >
{ [ 'Budget Range' , 'Urgency Level' , 'Service Location' , 'Mode (Remote / On-site)' , 'Preferred Start Date' , 'Flexible Budget' ] . map ( ( field , idx ) = > (
< div style = "display:flex;flex-direction:column;gap:6px" >
< p style = "margin:0;font-size:11px;font-weight:700;letter-spacing:0.05em;text-transform:uppercase;color:#6B7280" > { field } < / p >
< div style = "height:38px;border:1px solid #E5E7EB;border-radius:10px;background:#F9FAFB;padding:0 10px;display:flex;align-items:center;justify-content:space-between;font-size:12px;color:#9CA3AF" >
< span > { idx % 2 === 0 ? 'Enter' : 'Select' } { field . toLowerCase ( ) } < / span >
< Show when = { idx % 2 === 1 } > < span > ▾ < / span > < / Show >
< / div >
< / div >
) ) }
< / div >
< / div >
< / Show >
< Show when = { requirementsStep ( ) === 5 } >
< div style = "border:1px solid #E5E7EB;background:white;border-radius:16px;padding:16px;box-shadow:0 1px 4px rgba(0,0,0,0.06)" >
< p style = "margin:0;font-size:30px;font-weight:800;color:#111827" > Attachments < / p >
< p style = "margin:8px 0 0;font-size:13px;color:#6B7280" > Upload reference files to help professionals understand your requirement better . < / p >
< div style = "display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:10px;margin-top:12px" >
{ [ 1 , 2 , 3 , 4 ] . map ( ( slot ) = > (
< div style = "height:110px;border:1px dashed #F5B197;border-radius:12px;background:#FFF9F6;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:6px" >
< span style = "width:26px;height:26px;border-radius:999px;background:white;border:1px solid #FFD8C2;display:flex;align-items:center;justify-content:center;color:#FF5E13;font-weight:800" > + < / span >
< p style = "margin:0;font-size:11px;color:#6B7280" > Attachment { slot } < / p >
< / div >
) ) }
< / div >
< / div >
< / Show >
< Show when = { requirementsStep ( ) === 6 } >
< div style = "display:grid;grid-template-columns:2fr 1fr;gap:10px" >
< div style = "border:1px solid #E5E7EB;background:white;border-radius:16px;padding:16px;box-shadow:0 1px 4px rgba(0,0,0,0.06)" >
< p style = "margin:0;font-size:30px;font-weight:800;color:#111827" > Review & Submit < / p >
< p style = "margin:8px 0 0;font-size:13px;color:#6B7280" > Final check before submitting for approval . < / p >
< div style = "margin-top:12px;display:grid;gap:8px" >
{ [
[ 'Category' , titleCase ( selectedRequirementRole ( ) . replace ( /_/g , ' ' ) . toLowerCase ( ) ) ] ,
[ 'Requirement Title' , 'Luxury Wedding Shoot' ] ,
[ 'Budget Range' , '₹1,50,000 - ₹2,00,000' ] ,
[ 'Service Location' , 'Chennai, TN' ] ,
[ 'Expected Start Date' , 'Nov 12, 2023' ] ,
] . map ( ( [ label , value ] ) = > (
< div style = "display:flex;justify-content:space-between;align-items:center;padding:10px;border:1px solid #E5E7EB;border-radius:10px;background:#F9FAFB" >
< span style = "font-size:12px;color:#6B7280" > { label } < / span >
< span style = "font-size:12px;font-weight:700;color:#111827" > { value } < / span >
< / div >
) ) }
< / div >
< / div >
< div style = "border:1px solid #E5E7EB;background:linear-gradient(180deg,#FFFFFF 0%,#FFF6F0 100%);border-radius:16px;padding:16px;box-shadow:0 1px 4px rgba(0,0,0,0.06)" >
< p style = "margin:0;font-size:11px;letter-spacing:0.06em;text-transform:uppercase;color:#6B7280" > Approval Flow < / p >
< p style = "margin:8px 0 0;font-size:20px;font-weight:800;color:#111827" > Submit For Approval < / p >
< p style = "margin:8px 0 0;font-size:12px;color:#6B7280;line-height:1.5" > Once submitted , your requirement goes to Verification Management first , then Approval Management . Only after both stages it goes live in professional leads . < / p >
< div style = "display:grid;gap:8px;margin-top:12px" >
{ [ 'Draft Created' , 'Verification Management' , 'Approval Management' , 'Live In Leads' ] . map ( ( state , idx ) = > (
< div style = "display:flex;align-items:center;gap:8px" >
< span style = { ` width:18px;height:18px;border-radius:999px;display:inline-flex;align-items:center;justify-content:center;font-size:10px;font-weight:800;background: ${ idx < 2 ? '#ECFDF3' : '#F3F4F6' } ;color: ${ idx < 2 ? '#059669' : '#9CA3AF' } ` } > { idx < 2 ? '✓' : idx + 1 } < / span >
< span style = { ` font-size:12px;color: ${ idx < 2 ? '#111827' : '#6B7280' } ` } > { state } < / span >
< / div >
) ) }
< / div >
< / div >
< / div >
< / Show >
< div style = "display:flex;align-items:center;justify-content:space-between;border-top:1px solid #E5E7EB;padding-top:12px" >
< button
type = "button"
onClick = { goBackStep }
disabled = { ! canGoBack ( ) }
style = { ` height:34px;border-radius:8px;padding:0 12px;font-size:12px;font-weight:700;border:1px solid #E5E7EB;background:white;color: ${ canGoBack ( ) ? '#374151' : '#9CA3AF' } ;cursor: ${ canGoBack ( ) ? 'pointer' : 'not-allowed' } ` }
>
Back
< / button >
< div style = "display:flex;gap:8px" >
< button type = "button" style = "height:34px;border-radius:8px;border:1px solid #E5E7EB;background:white;color:#374151;padding:0 12px;font-size:12px;font-weight:700" > Save As Draft < / button >
< button type = "button" onClick = { goNextStep } style = "height:34px;border-radius:8px;border:none;background:#03004E;color:white;padding:0 12px;font-size:12px;font-weight:700" > { nextLabel ( ) } < / button >
< / div >
< / div >
< / div >
) ;
}
if ( requirementsView ( ) === 'detail' ) {
const selectedRow = requirementRows ( ) . find ( ( row ) = > row . id === selectedRequirementId ( ) ) || requirementRows ( ) [ 0 ] ;
const status = statusMeta ( selectedRow . status ) ;
return (
< div style = "display:grid;grid-template-columns:2fr 1fr;gap:10px" >
< div style = "display:flex;flex-direction:column;gap:10px" >
< div style = "border:1px solid #E5E7EB;background:white;border-radius:16px;padding:14px;box-shadow:0 1px 4px rgba(0,0,0,0.06)" >
< div style = "display:flex;align-items:center;justify-content:space-between;gap:8px" >
< div >
< p style = "margin:0;font-size:11px;letter-spacing:0.05em;text-transform:uppercase;color:#9CA3AF" > Requirements > Details < / p >
< p style = "margin:6px 0 0;font-size:38px;line-height:1.05;font-weight:800;color:#111827" > { selectedRow . title } < / p >
< div style = "display:flex;align-items:center;gap:8px;margin-top:8px" >
< span style = { ` height:22px;padding:0 10px;border-radius:999px;display:inline-flex;align-items:center;font-size:11px;font-weight:700;background: ${ status . bg } ;color: ${ status . c } ` } > { status . label } < / span >
< span style = "height:22px;padding:0 10px;border-radius:999px;display:inline-flex;align-items:center;font-size:11px;font-weight:700;background:#DBEAFE;color:#1D4ED8" > Active < / span >
< span style = "font-size:12px;color:#6B7280" > Created on { selectedRow . submission } < / span >
< / div >
< / div >
< div style = "display:flex;gap:8px" >
< button type = "button" style = "height:32px;border-radius:8px;border:1px solid #E5E7EB;background:white;padding:0 12px;font-size:12px;font-weight:700;color:#374151" > Duplicate < / button >
< button type = "button" style = "height:32px;border-radius:8px;border:none;background:#FF5E13;padding:0 12px;font-size:12px;font-weight:700;color:white" > Boost Requirement < / button >
< / div >
< / div >
< div style = "display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px;margin-top:10px" >
{ [
[ 'Proposed Budget' , selectedRow . budget ] ,
[ 'Location & Urgency' , selectedRow . location ] ,
[ 'Project Scope' , '3 Day Event' ] ,
] . map ( ( [ label , val ] ) = > (
< div style = "border:1px solid #E5E7EB;border-radius:12px;background:#F9FAFB;padding:10px" >
< p style = "margin:0;font-size:11px;letter-spacing:0.05em;text-transform:uppercase;color:#9CA3AF" > { label } < / p >
< p style = "margin:7px 0 0;font-size:16px;font-weight:800;color:#111827" > { val } < / p >
< / div >
) ) }
< / div >
< / div >
< div style = "border:1px solid #E5E7EB;background:white;border-radius:16px;padding:14px;box-shadow:0 1px 4px rgba(0,0,0,0.06)" >
< p style = "margin:0;font-size:34px;font-weight:800;color:#111827" > Requirement Overview < / p >
< p style = "margin:10px 0 0;font-size:13px;line-height:1.7;color:#374151" > We are looking for a premium { selectedRow . category . toLowerCase ( ) . replace ( /_/g , ' ' ) } team for a high - end project with a focus on quality , timeline ownership , and clear communication throughout execution . < / p >
< div style = "margin-top:10px;padding:10px;border:1px solid #FEC7AA;border-radius:10px;background:#FFF9F5" >
< p style = "margin:0;font-size:11px;letter-spacing:0.05em;text-transform:uppercase;color:#C2410C;font-weight:700" > Special Instructions < / p >
< ul style = "margin:8px 0 0;padding-left:18px;color:#374151;font-size:13px;line-height:1.6" >
< li > Mandatory experience in similar premium projects . < / li >
< li > Must be available for milestone reviews every 72 hours . < / li >
< li > NDA required before onboarding and deliverable sharing . < / li >
< / ul >
< / div >
< / div >
< div style = "border:1px solid #E5E7EB;background:white;border-radius:16px;padding:14px;box-shadow:0 1px 4px rgba(0,0,0,0.06)" >
< p style = "margin:0;font-size:30px;font-weight:800;color:#111827" > Reference Material < / p >
< div style = "display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:8px;margin-top:10px" >
{ [ 1 , 2 , 3 , 4 ] . map ( ( slot ) = > (
< div style = "height:84px;border:1px dashed #F5B197;border-radius:10px;background:#FFF9F6;display:flex;align-items:center;justify-content:center;color:#9CA3AF;font-size:12px;font-weight:700" >
{ slot === 4 ? 'Add More' : ` Asset ${ slot } ` }
< / div >
) ) }
< / div >
< / div >
< / div >
< div style = "display:flex;flex-direction:column;gap:10px" >
< div style = "border:1px solid #D9D8F9;background:#4F4B8A;border-radius:16px;padding:14px;color:white;box-shadow:0 1px 4px rgba(0,0,0,0.08)" >
< p style = "margin:0;font-size:11px;letter-spacing:0.06em;text-transform:uppercase;color:#DADAFB" > Response Metrics < / p >
< p style = "margin:8px 0 0;font-size:54px;line-height:1;font-weight:800" > 08 < / p >
< p style = "margin:4px 0 0;font-size:12px;color:#E5E7FF" > Total Responses < / p >
< div style = "height:1px;background:rgba(255,255,255,0.2);margin:10px 0" / >
< div style = "display:flex;justify-content:space-between;font-size:12px" >
< span > 03 Shortlisted < / span >
< span > 03 Rejected < / span >
< / div >
< / div >
< div style = "border:1px solid #E5E7EB;background:white;border-radius:16px;padding:14px;box-shadow:0 1px 4px rgba(0,0,0,0.06)" >
< p style = "margin:0;font-size:11px;letter-spacing:0.06em;text-transform:uppercase;color:#9CA3AF" > Approval History < / p >
< div style = "display:grid;gap:10px;margin-top:10px" >
{ [ 'Requirement Approved' , 'Under Review' , 'Submitted' , 'Draft Created' ] . map ( ( step ) = > (
< div style = "display:flex;align-items:center;gap:8px" >
< span style = "width:18px;height:18px;border-radius:999px;background:#ECFDF3;color:#059669;display:inline-flex;align-items:center;justify-content:center;font-size:10px;font-weight:800" > ✓ < / span >
< div >
< p style = "margin:0;font-size:12px;font-weight:700;color:#111827" > { step } < / p >
< p style = "margin:1px 0 0;font-size:11px;color:#9CA3AF" > Oct 24 , 2023 < / p >
< / div >
< / div >
) ) }
< / div >
< / div >
< div style = "border:1px solid #E5E7EB;background:white;border-radius:16px;padding:12px;display:grid;gap:8px;box-shadow:0 1px 4px rgba(0,0,0,0.06)" >
{ [ 'Extend Expiry' , 'Edit Details' , 'Close Requirement' ] . map ( ( action , idx ) = > (
< button type = "button" style = { ` height:36px;border-radius:8px;border:1px solid ${ idx === 2 ? '#FECACA' : '#E5E7EB' } ;background: ${ idx === 2 ? '#FEF2F2' : '#F9FAFB' } ;color: ${ idx === 2 ? '#B91C1C' : '#374151' } ;font-size:12px;font-weight:700 ` } > { action } < / button >
) ) }
< / div >
< / div >
< / div >
) ;
}
const totalCount = requirementRows ( ) . length ;
const draftCount = requirementRows ( ) . filter ( ( item ) = > item . status === 'draft' ) . length ;
const submittedCount = requirementRows ( ) . filter ( ( item ) = > item . status === 'under review' ) . length ;
const approvedCount = requirementRows ( ) . filter ( ( item ) = > item . status === 'approved' ) . length ;
const activeCount = requirementRows ( ) . filter ( ( item ) = > item . status === 'active' ) . length ;
const rejectedCount = requirementRows ( ) . filter ( ( item ) = > item . status === 'rejected' ) . length ;
return (
< div style = "display:flex;flex-direction:column;gap:10px" >
< div style = "border:1px solid #E5E7EB;background:white;border-radius:16px;padding:14px;box-shadow:0 1px 4px rgba(0,0,0,0.06)" >
< div style = "display:flex;align-items:center;justify-content:space-between;gap:10px" >
< div >
< p style = "margin:0;font-size:47px;line-height:1.05;font-weight:800;color:#111827" > My Requirements < / p >
< p style = "margin:6px 0 0;font-size:14px;color:#6B7280" > Create , submit for approval , and manage your requirements with precision and clarity . < / p >
< / div >
< button
type = "button"
onClick = { ( ) = > { setRequirementsView ( 'new' ) ; setRequirementsStep ( 1 ) ; } }
style = "height:38px;border-radius:10px;border:none;background:#03004E;color:white;padding:0 14px;font-size:12px;font-weight:700"
>
+ Post New Requirement
< / button >
< / div >
< div style = "display:grid;grid-template-columns:2fr repeat(5,minmax(0,1fr));gap:8px;margin-top:10px" >
{ [
[ 'Total Requirements' , String ( totalCount ) ] ,
[ 'Drafts' , String ( draftCount ) ] ,
[ 'Submitted' , String ( submittedCount ) ] ,
[ 'Approved' , String ( approvedCount ) ] ,
[ 'Active' , String ( activeCount ) ] ,
[ 'Rejected' , String ( rejectedCount ) ] ,
] . map ( ( [ label , value ] , idx ) = > (
< div style = { ` border:1px solid #E5E7EB;border-radius:10px;background: ${ idx === 0 ? '#FFF8F4' : '#F9FAFB' } ;padding:10px; ${ idx === 0 ? 'border-left:3px solid #FF5E13' : '' } ` } >
< p style = "margin:0;font-size:10px;letter-spacing:0.06em;text-transform:uppercase;color:#9CA3AF;font-weight:700" > { label } < / p >
< p style = { ` margin:6px 0 0;font-size: ${ idx === 0 ? '42px' : '36px' } ;line-height:1;font-weight:800;color:#111827 ` } > { value } < / p >
< / div >
) ) }
< / div >
< / div >
< div style = "border:1px solid #E5E7EB;background:white;border-radius:16px;overflow:hidden;box-shadow:0 1px 4px rgba(0,0,0,0.06)" >
< div style = "padding:10px 12px;border-bottom:1px solid #E5E7EB;display:flex;align-items:center;gap:8px" >
{ [ 'All Requirements' , 'Drafts' , 'Submitted' , 'Approved' , 'Active' , 'Rejected' , 'Closed' , 'Expired' ] . map ( ( label , idx ) = > (
< button type = "button" style = { ` height:30px;padding:0 8px;border:none;border-bottom: ${ idx === 0 ? '2px solid #FF5E13' : '2px solid transparent' } ;background:none;font-size:12px;font-weight:700;color: ${ idx === 0 ? '#FF5E13' : '#6B7280' } ` } > { label } < / button >
) ) }
< / div >
< div style = "padding:10px 12px;border-bottom:1px solid #E5E7EB;display:flex;align-items:center;gap:8px" >
< div style = "height:34px;flex:1;border:1px solid #E5E7EB;border-radius:8px;background:#F9FAFB;padding:0 10px;display:flex;align-items:center;gap:8px;font-size:12px;color:#9CA3AF" > < Search size = { 14 } / > Search ID or Title . . . < / div >
{ [ 'Sort By: Newest' , 'Category: All' , 'Status: All' ] . map ( ( item ) = > (
< button type = "button" style = "height:34px;border-radius:8px;border:1px solid #E5E7EB;background:white;padding:0 10px;font-size:12px;color:#374151" > { item } < / button >
) ) }
< button type = "button" style = "height:34px;border-radius:8px;border:1px solid #E5E7EB;background:white;padding:0 10px;font-size:12px;color:#374151" > Export < / button >
< / div >
< div style = "overflow-x:auto" >
< table style = "width:100%;border-collapse:collapse;min-width:920px" >
< thead style = "background:#F9FAFB" >
< tr >
{ [ 'ID' , 'Requirement Details' , 'Category' , 'Budget & Location' , 'Submission' , 'Status' , 'Actions' ] . map ( ( head ) = > (
< th style = "padding:10px;text-align:left;font-size:10px;letter-spacing:0.06em;text-transform:uppercase;color:#9CA3AF" > { head } < / th >
) ) }
< / tr >
< / thead >
< tbody >
< For each = { filteredRows } >
{ ( row ) = > {
const status = statusMeta ( row . status ) ;
return (
< tr style = "border-top:1px solid #F3F4F6" >
< td style = "padding:10px;font-size:12px;color:#9CA3AF;font-weight:700" > { row . id } < / td >
< td style = "padding:10px" >
< p style = "margin:0;font-size:14px;font-weight:700;color:#111827" > { row . title } < / p >
< p style = "margin:3px 0 0;font-size:12px;color:#6B7280" > { row . summary } < / p >
< / td >
< td style = "padding:10px" > < span style = "height:20px;padding:0 8px;border-radius:999px;border:1px solid #E5E7EB;background:#F3F4F6;display:inline-flex;align-items:center;font-size:10px;font-weight:700;color:#4B5563" > { row . category } < / span > < / td >
< td style = "padding:10px" >
< p style = "margin:0;font-size:13px;font-weight:700;color:#111827" > { row . budget } < / p >
< p style = "margin:2px 0 0;font-size:11px;color:#9CA3AF" > { row . location } < / p >
< / td >
< td style = "padding:10px;font-size:12px;color:#6B7280" > { row . submission } < / td >
< td style = "padding:10px" >
< span style = { ` height:22px;padding:0 10px;border-radius:999px;display:inline-flex;align-items:center;font-size:11px;font-weight:700;background: ${ status . bg } ;color: ${ status . c } ` } > { status . label } < / span >
< p style = "margin:6px 0 0;font-size:11px;color:#1D4ED8;font-weight:700" > { row . responseTag } < / p >
< / td >
< td style = "padding:10px" >
< div style = "display:flex;gap:6px" >
< button
type = "button"
onClick = { ( ) = > { setSelectedRequirementId ( row . id ) ; setRequirementsView ( 'detail' ) ; } }
style = "height:28px;border-radius:8px;border:1px solid #E5E7EB;background:white;padding:0 10px;font-size:11px;font-weight:700;color:#374151"
>
View
< / button >
< button type = "button" style = "height:28px;border-radius:8px;border:none;background:#03004E;padding:0 10px;font-size:11px;font-weight:700;color:white" > Edit < / button >
< / div >
< / td >
< / tr >
) ;
} }
< / For >
< / tbody >
< / table >
< / div >
< div style = "padding:10px 12px;border-top:1px solid #E5E7EB;display:flex;justify-content:space-between;align-items:center" >
< p style = "margin:0;font-size:12px;color:#9CA3AF" > Showing 1 to { Math . min ( filteredRows . length , 6 ) } of { filteredRows . length } requirements < / p >
< div style = "display:flex;gap:6px" >
{ [ 1 , 2 , 3 ] . map ( ( p ) = > < button type = "button" style = { ` width:28px;height:28px;border-radius:8px;border:1px solid #E5E7EB;background: ${ p === 1 ? '#FF5E13' : 'white' } ;color: ${ p === 1 ? 'white' : '#6B7280' } ;font-size:12px;font-weight:700 ` } > { p } < / button > ) }
< / div >
< / div >
< / div >
< / div >
) ;
}
if ( customerKey ( ) === 'received responses' || customerKey ( ) === 'my responses' ) {
if ( normalizeRoleKey ( props . roleKey || '' ) !== 'CUSTOMER' ) {
const leadStatusPill = ( status : string ) = > {
if ( status === 'request_sent' ) return { c : '#374151' , text : 'Request Sent' } ;
if ( status === 'contact_unlocked' ) return { c : '#374151' , text : 'Contact Unlocked' } ;
if ( status === 'approved' ) return { c : '#374151' , text : 'Approved' } ;
if ( status === 'rejected' ) return { c : '#374151' , text : 'Rejected' } ;
if ( status === 'expired_refunded' ) return { c : '#374151' , text : 'Expired · Refunded' } ;
if ( status === 'cancelled_by_professional' ) return { c : '#374151' , text : 'Cancelled' } ;
return { c : '#374151' , text : titleCase ( status ) } ;
} ;
const responseSelectedLead = ( ) = > leadCards ( ) . find ( ( item ) = > item . id === activeResponseLeadId ( ) ) || null ;
const list = filteredRequestedRows ( ) . filter ( ( row ) = > {
if ( resolvedTabKey ( ) === 'pending leads' ) return row . status === 'request_sent' ;
return true ;
} ) ;
const totalPages = Math . max ( 1 , Math . ceil ( list . length / requestedPerPage ) ) ;
const currentPage = Math . min ( requestedPage ( ) , totalPages ) ;
const pagedList = list . slice ( ( currentPage - 1 ) * requestedPerPage , ( currentPage - 1 ) * requestedPerPage + requestedPerPage ) ;
if ( responsesDetailMode ( ) && responseSelectedLead ( ) ) {
const lead = responseSelectedLead ( ) ! ;
const spec = leadDetailsSpec ( lead ) ;
return (
< div style = "display:flex;flex-direction:column;gap:10px" >
< div style = "border:1px solid #E5E7EB;background:white;border-radius:16px;padding:14px;box-shadow:0 1px 4px rgba(0,0,0,0.06)" >
< div style = "border:1px solid #E5E7EB;border-radius:12px;background:#FFFFFF;padding:14px" >
< div style = "display:flex;align-items:flex-start;justify-content:space-between;gap:10px;flex-wrap:wrap" >
< div >
< p style = "margin:0;font-size:11px;letter-spacing:0.06em;text-transform:uppercase;color:#6B7280" > My Responses • View Details < / p >
< p style = "margin:4px 0 0;font-size:24px;font-weight:800;color:#111827" > { lead . title } < / p >
< p style = "margin:4px 0 0;font-size:12px;color:#6B7280" > { lead . category } • { lead . location } • { lead . area } < / p >
< / div >
< button type = "button" onClick = { ( ) = > { setResponsesDetailMode ( false ) ; setActiveResponseLeadId ( '' ) ; } } style = "height:32px;border-radius:8px;border:1px solid #E5E7EB;background:white;padding:0 12px;font-size:12px;font-weight:700;color:#374151" > Back to My Responses < / button >
< / div >
< div style = "margin-top:10px;display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:8px" >
< div style = "border:1px solid #E5E7EB;border-radius:10px;background:#F9FAFB;padding:10px;display:flex;gap:8px" >
< span style = "width:24px;height:24px;border-radius:999px;background:#FFF1EB;display:inline-flex;align-items:center;justify-content:center;flex-shrink:0" > < Calendar size = { 13 } style = "color:#FF5E13" / > < / span >
< div > < p style = "margin:0;font-size:11px;color:#6B7280;font-weight:700" > Schedule < / p > < p style = "margin:3px 0 0;font-size:13px;font-weight:800;color:#111827" > { spec . timeframe } < / p > < / div >
< / div >
< div style = "border:1px solid #E5E7EB;border-radius:10px;background:#F9FAFB;padding:10px;display:flex;gap:8px" >
< span style = "width:24px;height:24px;border-radius:999px;background:#FFF1EB;display:inline-flex;align-items:center;justify-content:center;flex-shrink:0" > < Calendar size = { 13 } style = "color:#FF5E13" / > < / span >
< div > < p style = "margin:0;font-size:11px;color:#6B7280;font-weight:700" > Date Required < / p > < p style = "margin:3px 0 0;font-size:13px;font-weight:800;color:#111827" > { lead . dateRequired } < / p > < / div >
< / div >
< div style = "border:1px solid #E5E7EB;border-radius:10px;background:#F9FAFB;padding:10px;display:flex;gap:8px" >
< span style = "width:24px;height:24px;border-radius:999px;background:#FFF1EB;display:inline-flex;align-items:center;justify-content:center;flex-shrink:0" > < MapPin size = { 13 } style = "color:#FF5E13" / > < / span >
< div > < p style = "margin:0;font-size:11px;color:#6B7280;font-weight:700" > Area < / p > < p style = "margin:3px 0 0;font-size:13px;font-weight:800;color:#111827" > { String ( lead . area || 'Chennai' ) } < / p > < / div >
< / div >
< div style = "border:1px solid #E5E7EB;border-radius:10px;background:#F9FAFB;padding:10px;display:flex;gap:8px" >
< span style = "width:24px;height:24px;border-radius:999px;background:#FFF1EB;display:inline-flex;align-items:center;justify-content:center;flex-shrink:0" > < Coins size = { 13 } style = "color:#FF5E13" / > < / span >
< div > < p style = "margin:0;font-size:11px;color:#6B7280;font-weight:700" > Unlock Cost < / p > < p style = "margin:3px 0 0;font-size:13px;font-weight:800;color:#C2410C" > { lead . cost } Tracecoin < / p > < / div >
< / div >
< / div >
< / div >
< div style = "margin-top:10px;display:grid;grid-template-columns:2fr 1fr;gap:10px" >
< div style = "display:grid;gap:10px" >
< div style = "border:1px solid #E5E7EB;border-radius:12px;background:white;padding:12px" >
< p style = "margin:0;font-size:11px;letter-spacing:0.05em;text-transform:uppercase;color:#6B7280;font-weight:700" > Work Scope < / p >
< p style = "margin:8px 0 0;font-size:14px;color:#1F2937;line-height:1.65" > { spec . scope } < / p >
< / div >
< div style = "border:1px solid #E5E7EB;border-radius:12px;background:#F9FAFB;padding:12px" >
< p style = "margin:0 0 8px;font-size:11px;letter-spacing:0.05em;text-transform:uppercase;color:#6B7280;font-weight:700" > Lead Specific Highlights < / p >
< div style = "display:grid;gap:6px" > < For each = { spec . highlights } > { ( item ) = > < div style = "display:flex;align-items:flex-start;gap:6px" > < span style = "margin-top:3px;width:6px;height:6px;border-radius:999px;background:#FF5E13;flex-shrink:0" / > < span style = "font-size:14px;color:#1F2937;line-height:1.55" > { item } < / span > < / div > } < / For > < / div >
< / div >
< / div >
< div style = "border:1px solid #E5E7EB;border-radius:12px;background:white;padding:12px;display:grid;gap:8px;align-content:start" >
< div style = "display:flex;justify-content:space-between;gap:8px;padding-bottom:6px;border-bottom:1px solid #F3F4F6" > < span style = "font-size:12px;color:#6B7280" > Lead ID < / span > < span style = "font-size:12px;color:#111827;font-weight:700" > { lead . id } < / span > < / div >
< div style = "display:flex;justify-content:space-between;gap:8px;padding-bottom:6px;border-bottom:1px solid #F3F4F6" > < span style = "font-size:12px;color:#6B7280" > Category < / span > < span style = "font-size:12px;color:#111827;font-weight:700" > { lead . category } < / span > < / div >
< div style = "display:flex;justify-content:space-between;gap:8px;padding-bottom:6px;border-bottom:1px solid #F3F4F6" > < span style = "font-size:12px;color:#6B7280" > Location < / span > < span style = "font-size:12px;color:#111827;font-weight:700" > { lead . location } < / span > < / div >
< div style = "display:flex;justify-content:space-between;gap:8px;padding-bottom:6px;border-bottom:1px solid #F3F4F6" > < span style = "font-size:12px;color:#6B7280" > Status < / span > < span style = "font-size:12px;color:#111827;font-weight:700" > From My Responses < / span > < / div >
< / div >
< / div >
< / div >
< / div >
) ;
}
return (
< div style = "display:flex;flex-direction:column;gap:10px" >
< div style = "border:1px solid #E5E7EB;background:white;border-radius:16px;overflow:hidden;box-shadow:0 1px 4px rgba(0,0,0,0.06)" >
< div style = "padding:10px 12px;border-bottom:1px solid #E5E7EB;display:flex;justify-content:space-between;align-items:center;gap:10px" >
< p style = "margin:0;font-size:18px;font-weight:800;color:#111827" > My Responses < / p >
< div style = "display:flex;gap:8px" >
< div style = "position:relative" >
< button type = "button" onClick = { ( ) = > { setRequestedSortOpen ( ( prev ) = > ! prev ) ; setRequestedFilterOpen ( false ) ; } } style = "display:inline-flex;height:32px;align-items:center;gap:6px;border-radius:8px;border:1px solid #E5E7EB;background:white;padding:0 10px;font-size:12px;color:#374151;font-weight:600" >
< svg width = "13" height = "13" viewBox = "0 0 24 24" fill = "none" stroke = "#FF5E13" stroke-width = "2" > < path d = "M7 4v13" / > < path d = "m3 13 4 4 4-4" / > < path d = "M17 20V7" / > < path d = "m21 11-4-4-4 4" / > < / svg >
Sort
< / button >
< Show when = { requestedSortOpen ( ) } >
< div style = "position:absolute;right:0;top:36px;z-index:20;min-width:170px;border:1px solid #E5E7EB;border-radius:10px;background:white;padding:6px;box-shadow:0 8px 24px rgba(0,0,0,0.12)" >
{ [ 'Newest First' , 'Oldest First' ] . map ( ( item ) = > (
< button type = "button" onClick = { ( ) = > { setRequestedSortFilter ( item ) ; setRequestedSortOpen ( false ) ; } } style = { ` display:block;width:100%;text-align:left;border:none;background: ${ requestedSortFilter ( ) === item ? '#FFF1EB' : 'transparent' } ;color: ${ requestedSortFilter ( ) === item ? '#FF5E13' : '#374151' } ;padding:8px 10px;border-radius:8px;font-size:12px;font-weight:600;cursor:pointer ` } >
{ item }
< / button >
) ) }
< / div >
< / Show >
< / div >
< div style = "position:relative" >
< button type = "button" onClick = { ( ) = > { setRequestedFilterOpen ( ( prev ) = > ! prev ) ; setRequestedSortOpen ( false ) ; } } style = "display:inline-flex;height:32px;align-items:center;gap:6px;border-radius:8px;border:1px solid #E5E7EB;background:white;padding:0 10px;font-size:12px;color:#374151;font-weight:600" >
< svg width = "13" height = "13" viewBox = "0 0 24 24" fill = "none" stroke = "#FF5E13" stroke-width = "2" > < path d = "M3 5h18M6 12h12M10 19h4" / > < / svg >
Filter
< / button >
< Show when = { requestedFilterOpen ( ) } >
< div style = "position:absolute;right:0;top:36px;z-index:20;min-width:220px;border:1px solid #E5E7EB;border-radius:10px;background:white;padding:6px;box-shadow:0 8px 24px rgba(0,0,0,0.12)" >
{ [ 'All Status' , 'Request Sent' , 'Contact Unlocked' , 'Rejected' , 'Expired Refunded' , 'Cancelled By Professional' ] . map ( ( item ) = > (
< button type = "button" onClick = { ( ) = > { setRequestedStatusFilter ( item ) ; setRequestedFilterOpen ( false ) ; } } style = { ` display:block;width:100%;text-align:left;border:none;background: ${ requestedStatusFilter ( ) === item ? '#FFF1EB' : 'transparent' } ;color: ${ requestedStatusFilter ( ) === item ? '#FF5E13' : '#374151' } ;padding:8px 10px;border-radius:8px;font-size:12px;font-weight:600;cursor:pointer ` } >
{ item }
< / button >
) ) }
< / div >
< / Show >
< / div >
< button type = "button" style = "display:inline-flex;height:32px;align-items:center;gap:6px;border-radius:8px;border:none;background:#03004E;padding:0 10px;font-size:12px;font-weight:700;color:white" > < svg width = "13" height = "13" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" > < path d = "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" / > < polyline points = "7 10 12 15 17 10" / > < line x1 = "12" y1 = "15" x2 = "12" y2 = "3" / > < / svg > Export < / button >
< / div >
< / div >
< div style = "padding:10px 12px;border-bottom:1px solid #E5E7EB;display:flex;align-items:center;gap:8px;flex-wrap:wrap" >
< div style = "height:32px;min-width:220px;flex:1;border:1px solid #E5E7EB;border-radius:8px;background:#F9FAFB;padding:0 10px;display:flex;align-items:center;gap:8px;font-size:12px;color:#9CA3AF" >
< Search size = { 14 } / >
< input value = { requestedSearch ( ) } onInput = { ( e ) = > setRequestedSearch ( e . currentTarget . value ) } placeholder = "Search by lead ID / title / area..." style = "border:none;background:transparent;outline:none;width:100%;font-size:12px;color:#111827" / >
< / div >
< span style = "height:24px;padding:0 10px;border-radius:999px;border:1px solid #E5E7EB;background:#F9FAFB;display:inline-flex;align-items:center;font-size:11px;font-weight:700;color:#6B7280" > Sort : { requestedSortFilter ( ) } < / span >
< span style = "height:24px;padding:0 10px;border-radius:999px;border:1px solid #E5E7EB;background:#F9FAFB;display:inline-flex;align-items:center;font-size:11px;font-weight:700;color:#6B7280" > Status : { requestedStatusFilter ( ) } < / span >
< / div >
< div style = "max-height:420px;overflow:auto" >
< table style = "width:100%;border-collapse:collapse" >
< thead style = "background:#03004E;color:white" >
< tr >
{ [ 'Lead ID' , 'Lead Title' , 'Request Date' , 'Status' , 'Cost' , 'Decision Date' , 'Action' ] . map ( ( h ) = > (
< th style = "padding:10px;text-align:left;font-size:10px;letter-spacing:0.06em;text-transform:uppercase" > { h } < / th >
) ) }
< / tr >
< / thead >
< tbody >
< For each = { pagedList } >
{ ( row ) = > {
const badge = leadStatusPill ( row . status ) ;
return (
< tr style = "border-top:1px solid #F3F4F6" >
< td style = "padding:10px;font-size:12px;color:#4B5563;font-weight:700" > # { row . id } < / td >
< td style = "padding:10px" >
< p style = "margin:0;font-size:14px;font-weight:700;color:#111827" > { row . title } < / p >
< p style = "margin:2px 0 0;font-size:12px;color:#9CA3AF" > { row . city } < / p >
< / td >
< td style = "padding:10px;font-size:12px;color:#374151" > { row . requestDate } < / td >
< td style = "padding:10px" >
< span style = { ` display:inline-flex;align-items:center;font-size:12px;font-weight:600;color: ${ badge . c } ` } > { badge . text } < / span >
< / td >
< td style = "padding:10px;font-size:14px;font-weight:700;color:#111827" > { leadCostPerContact } < / td >
< td style = "padding:10px;font-size:12px;color:#6B7280" > { row . decisionDate } < / td >
< td style = "padding:10px" >
< div style = "display:flex;gap:6px;flex-wrap:wrap" >
< button
type = "button"
title = "View Details"
aria - label = "View Details"
onClick = { ( ) = > openResponseLeadDetails ( row . id ) }
style = "width:30px;height:30px;border-radius:8px;border:1px solid #E5E7EB;background:white;display:inline-flex;align-items:center;justify-content:center;color:#374151"
>
< Eye size = { 14 } / >
< / button >
< Show when = { row . status === 'request_sent' } >
< button
type = "button"
title = "Cancel Request"
aria - label = "Cancel Request"
onClick = { ( ) = > cancelLeadRequest ( row . id ) }
style = "width:30px;height:30px;border-radius:8px;border:1px solid #E5E7EB;background:white;display:inline-flex;align-items:center;justify-content:center;color:#374151"
>
< X size = { 14 } / >
< / button >
< / Show >
< / div >
< / td >
< / tr >
) ;
} }
< / For >
< / tbody >
< / table >
< / div >
< div style = "padding:10px 12px;border-top:1px solid #E5E7EB;display:flex;justify-content:space-between;align-items:center" >
< p style = "margin:0;font-size:12px;color:#6B7280" >
Showing { pagedList . length ? ( currentPage - 1 ) * requestedPerPage + 1 : 0 } to { ( currentPage - 1 ) * requestedPerPage + pagedList . length } of { list . length } responses
< / p >
< div style = "display:flex;gap:6px" >
< For each = { Array . from ( { length : totalPages } , ( _ , i ) = > i + 1 ) } >
{ ( pageNo ) = > (
< button
type = "button"
onClick = { ( ) = > setRequestedPage ( pageNo ) }
style = { ` width:30px;height:30px;border-radius:8px;border:1px solid #E5E7EB;background: ${ currentPage === pageNo ? '#FF5E13' : 'white' } ;color: ${ currentPage === pageNo ? 'white' : '#6B7280' } ;font-size:12px;font-weight:700 ` }
>
{ pageNo }
< / button >
) }
< / For >
< / div >
< / div >
< / div >
< / div >
) ;
}
const list = RESPONSE_ROWS . filter ( ( r ) = > {
if ( tab === 'new' ) return r . status === 'new' ;
if ( tab === 'shortlisted' ) return r . status === 'shortlisted' ;
if ( tab === 'rejected' ) return r . status === 'rejected' ;
return true ;
} ) ;
return (
< div style = "border:1px solid #E5E7EB;background:white;border-radius:16px;padding:14px;box-shadow:0 1px 4px rgba(0,0,0,0.06);display:grid;gap:8px" >
< div style = "display:flex;align-items:center;justify-content:space-between;gap:8px;padding:10px;border:1px solid #FFE2D3;border-radius:10px;background:#FFF8F4" >
< p style = "margin:0;font-size:12px;color:#9A3412;line-height:1.4" > Before accepting contact request , review portfolio , experience , and quoted charges . < / p >
< button
type = "button"
onClick = { openPortfolioPreviewInline }
style = "height:30px;border-radius:8px;border:none;background:#03004E;color:white;padding:0 10px;font-size:12px;font-weight:700;white-space:nowrap"
>
Preview Portfolio
< / button >
< / div >
< For each = { list } > { ( row ) = > {
const chip = statusChip ( row . status ) ;
return (
< div style = "display:grid;grid-template-columns:2fr 1fr 1fr;gap:8px;padding:10px;border:1px solid #E5E7EB;border-radius:8px;background:#fff" >
< div >
< p style = "margin:0;font-size:13px;font-weight:700;color:#111827" > { row . name } < / p >
< p style = "margin:2px 0 0;font-size:12px;color:#6B7280" > Applied for active requirement < / p >
< p style = "margin:6px 0 0;font-size:11px;font-weight:700;color:#FF5E13" > Experience : 7 + years < / p >
< / div >
< div style = "font-size:18px;font-weight:800;color:#111827" > { row . quote } < / div >
< div style = "display:flex;align-items:center;justify-content:flex-end;gap:8px;flex-wrap:wrap" >
< span style = { ` display:inline-flex;align-items:center;justify-content:center;height:22px;border-radius:999px;padding:0 8px;background: ${ chip . bg } ;color: ${ chip . c } ;font-size:11px;font-weight:700;text-transform:uppercase ` } > { row . status } < / span >
< button
type = "button"
onClick = { openPortfolioPreviewInline }
style = "height:30px;border-radius:8px;border:1px solid #E5E7EB;background:white;color:#374151;padding:0 10px;font-size:12px;font-weight:700"
>
View Portfolio
< / button >
< button type = "button" style = "height:30px;border-radius:8px;border:none;background:#0D0D2A;color:white;padding:0 10px;font-size:12px;font-weight:700" > View Profile < / button >
< button type = "button" style = "height:30px;border-radius:8px;border:none;background:#FF5E13;color:white;padding:0 10px;font-size:12px;font-weight:700" > Accept Contact < / button >
< / div >
< / div >
) ;
} } < / For >
< / div >
) ;
}
if ( customerKey ( ) === 'shortlisted responses' ) {
const list = RESPONSE_ROWS . filter ( ( r ) = > {
if ( tab === 'interview scheduled' ) return r . status === 'shortlisted' ;
if ( tab === 'finalized' ) return r . status === 'shortlisted' ;
return r . status === 'shortlisted' ;
} ) ;
return (
< div style = "display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px" >
< For each = { list } > { ( p ) = > (
< div style = "border:1px solid #E5E7EB;background:white;border-radius:16px;padding:12px;box-shadow:0 1px 4px rgba(0,0,0,0.06)" >
< p style = "margin:0;font-size:13px;font-weight:700;color:#111827" > { p . name } < / p >
< p style = "margin:4px 0 0;font-size:12px;color:#6B7280" > High - potential response for active requirement < / p >
< p style = "margin:8px 0 0;font-size:12px;font-weight:700;color:#FF5E13" > Quoted { p . quote } < / p >
< div style = "display:flex;gap:8px;margin-top:10px" >
< button type = "button" style = "height:30px;border-radius:8px;border:1px solid #E5E7EB;background:#F9FAFB;color:#374151;padding:0 10px;font-size:12px;font-weight:700" > View Details < / button >
< button type = "button" style = "height:30px;border-radius:8px;border:none;background:#FF5E13;color:white;padding:0 10px;font-size:12px;font-weight:700" > Finalize < / button >
< / div >
< / div >
) } < / For >
< / div >
) ;
}
if ( customerKey ( ) === 'credits' ) {
const txRows = [
[ '#INV-2023-089' , 'Enterprise Growth' , '5,000' , '₹1,20,000' , 'Completed' , 'Oct 24, 2023' ] ,
[ '#INV-2023-074' , 'Starter Kick' , '500' , '₹15,000' , 'Pending' , 'Oct 23, 2023' ] ,
[ '#INV-2023-052' , 'Pro Pack' , '2,500' , '₹65,000' , 'Completed' , 'Oct 21, 2023' ] ,
[ '#INV-2023-031' , 'Top-up' , '1,000' , '₹30,000' , 'Failed' , 'Oct 20, 2023' ] ,
] as const ;
const invoiceRows = [
[ '#INV-2023-089' , 'Oct 14, 2023' , 'Enterprise Pack' , '₹499.00' , 'Paid' ] ,
[ '#INV-2023-074' , 'Sep 14, 2023' , 'Enterprise Pack' , '₹499.00' , 'Paid' ] ,
[ '#INV-2023-052' , 'Aug 14, 2023' , 'Growth Starter' , '₹135.00' , 'Paid' ] ,
[ '#INV-2023-031' , 'Jul 14, 2023' , 'Growth Starter' , '₹135.00' , 'Failed' ] ,
] as const ;
if ( tab === 'buy credits' ) {
return (
< div style = "display:flex;flex-direction:column;gap:10px" >
< div style = "border:1px solid #E5E7EB;background:white;border-radius:16px;padding:14px;box-shadow:0 1px 4px rgba(0,0,0,0.06);display:flex;align-items:center;justify-content:space-between;gap:12px" >
< div >
< p style = "margin:0;font-size:11px;text-transform:uppercase;letter-spacing:0.06em;color:#6B7280" > Current Balance < / p >
< p style = "margin:6px 0 0;font-size:22px;font-weight:800;color:#111827;line-height:1" > 12 , 450 < span style = "font-size:14px;color:#64748B;font-weight:700" > Tracecoins < / span > < / p >
< p style = "margin:6px 0 0;font-size:12px;color:#FF5E13;font-weight:700" > + 12 % from last month < / p >
< / div >
< button type = "button" style = "height:36px;border:none;border-radius:8px;background:#03004E;color:white;padding:0 14px;font-size:12px;font-weight:700;white-space:nowrap" > Buy Credits < / button >
< / div >
< div style = "display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:10px" >
{ [
{ name : 'Basic' , subtitle : 'Starter' , credits : '500' , price : '₹4,999' , bonus : '+50 bonus' , popular : false } ,
{ name : 'Standard' , subtitle : 'Growth' , credits : '2,500' , price : '₹18,999' , bonus : '+250 bonus' , popular : true } ,
{ name : 'Premium' , subtitle : 'Power' , credits : '10,000' , price : '₹69,999' , bonus : '+1,500 bonus' , popular : false } ,
{ name : 'Elite' , subtitle : 'Enterprise' , credits : '50,000' , price : '₹2,49,999' , bonus : '+10,000 bonus' , popular : false } ,
] . map ( ( pkg ) = > (
< div style = { ` border: ${ pkg . popular ? '2px solid #FF5E13' : '1px solid #E5E7EB' } ;background:white;border-radius:12px;padding:14px;box-shadow:0 1px 3px rgba(0,0,0,0.05);position:relative ` } >
< Show when = { pkg . popular } >
< span style = "position:absolute;top:-10px;right:10px;height:20px;padding:0 10px;border-radius:999px;background:#FF5E13;color:white;font-size:10px;font-weight:700;display:inline-flex;align-items:center" > MOST POPULAR < / span >
< / Show >
< div style = "width:28px;height:28px;border-radius:999px;background:#FFF1EB;border:1px solid #FFD8C2;display:flex;align-items:center;justify-content:center" >
< img src = "/sidebar-icons/credits.svg" alt = "" style = { ` width:14px;height:14px;object-fit:contain;filter: ${ ORANGE_ICON_FILTER } ` } / >
< / div >
< p style = "margin:0;font-size:10px;letter-spacing:0.06em;text-transform:uppercase;font-weight:700;color:#9CA3AF" > { pkg . subtitle } < / p >
< p style = "margin:3px 0 0;font-size:20px;font-weight:800;color:#111827;line-height:1.1" > { pkg . name } < / p >
< p style = "margin:8px 0 0;font-size:20px;font-weight:800;color:#111827;line-height:1" > { pkg . credits } < / p >
< p style = "margin:0;font-size:12px;color:#6B7280" > Credits < / p >
< p style = "margin:8px 0 0;font-size:10px;font-weight:700;color:#FF5E13;background:#FFF1EB;border:1px solid #FFD8C2;height:20px;padding:0 8px;border-radius:999px;display:inline-flex;align-items:center" > { pkg . bonus } < / p >
< p style = "margin:10px 0 0;font-size:20px;font-weight:800;color:#111827;line-height:1" > { pkg . price } < / p >
< button type = "button" style = { ` margin-top:10px;height:32px;width:100%;border:none;border-radius:8px;background: ${ pkg . popular ? '#FF5E13' : '#03004E' } ;color:white;font-size:12px;font-weight:700 ` } > Buy Package < / button >
< / div >
) ) }
< / div >
< div style = "display:grid;grid-template-columns:2fr 1fr;gap:10px" >
< div style = "border:1px solid #E5E7EB;background:white;border-radius:12px;padding:12px;box-shadow:0 1px 3px rgba(0,0,0,0.05)" >
< p style = "margin:0;font-size:13px;font-weight:700;color:#111827" > Why buy larger packs ? < / p >
< div style = "display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px;margin-top:8px" >
< div style = "border:1px solid #E5E7EB;border-radius:10px;padding:10px;background:#F9FAFB" >
< p style = "margin:0;font-size:12px;font-weight:700;color:#111827" > More savings < / p >
< p style = "margin:4px 0 0;font-size:11px;color:#6B7280" > Higher packs include bonus credits . < / p >
< / div >
< div style = "border:1px solid #E5E7EB;border-radius:10px;padding:10px;background:#F9FAFB" >
< p style = "margin:0;font-size:12px;font-weight:700;color:#111827" > No expiry < / p >
< p style = "margin:4px 0 0;font-size:11px;color:#6B7280" > Credits stay active with your account . < / p >
< / div >
< div style = "border:1px solid #E5E7EB;border-radius:10px;padding:10px;background:#F9FAFB" >
< p style = "margin:0;font-size:12px;font-weight:700;color:#111827" > Instant activation < / p >
< p style = "margin:4px 0 0;font-size:11px;color:#6B7280" > Credits reflect right after purchase . < / p >
< / div >
< / div >
< / div >
< div style = "border:1px solid #E5E7EB;background:#03004E;border-radius:12px;padding:12px;color:white;box-shadow:0 1px 3px rgba(0,0,0,0.05)" >
< p style = "margin:0;font-size:11px;letter-spacing:0.06em;text-transform:uppercase;color:#C7D2FE" > Recommended < / p >
< p style = "margin:6px 0 0;font-size:20px;font-weight:800;line-height:1.2" > Start with Standard < / p >
< p style = "margin:6px 0 0;font-size:12px;color:#D7DBFF;line-height:1.45" > Best value for active buying and response unlocks . < / p >
< button type = "button" style = "margin-top:10px;height:32px;border:none;border-radius:8px;background:#FF5E13;color:white;padding:0 12px;font-size:12px;font-weight:700;width:100%" > Buy Standard < / button >
< / div >
< / div >
< / div >
) ;
}
if ( tab === 'transactions' ) {
return (
< div style = "display:flex;flex-direction:column;gap:10px" >
< div style = "border:1px solid #E5E7EB;background:white;border-radius:16px;box-shadow:0 1px 4px rgba(0,0,0,0.06);overflow:hidden" >
< div style = "padding:12px;display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid #E5E7EB;background:#F9FAFB" >
< div style = "display:flex;align-items:center;gap:8px;flex:1" >
< input value = "" readOnly placeholder = "Search transactions..." style = "height:34px;min-width:220px;flex:1;border-radius:8px;border:1px solid #E5E7EB;background:white;padding:0 12px;font-size:12px;color:#6B7280;outline:none" / >
< button type = "button" style = "display:inline-flex;height:34px;align-items:center;gap:6px;border-radius:8px;border:1px solid #E5E7EB;background:white;padding:0 12px;font-size:12px;font-weight:500;color:#374151" >
< svg width = "13" height = "13" viewBox = "0 0 24 24" fill = "none" stroke = "#FF5E13" stroke-width = "2" > < path d = "M7 4v13" / > < path d = "m3 13 4 4 4-4" / > < path d = "M17 20V7" / > < path d = "m21 11-4-4-4 4" / > < / svg >
Sort
< / button >
< button type = "button" style = "display:inline-flex;height:34px;align-items:center;gap:6px;border-radius:8px;border:1px solid #E5E7EB;background:white;padding:0 12px;font-size:12px;font-weight:500;color:#374151" >
< svg width = "13" height = "13" viewBox = "0 0 24 24" fill = "none" stroke = "#FF5E13" stroke-width = "2" > < path d = "M3 5h18M6 12h12M10 19h4" / > < / svg >
Filters
< / button >
< / div >
< button type = "button" style = "display:inline-flex;align-items:center;gap:6px;height:34px;border:none;border-radius:8px;background:#0D0D2A;color:white;padding:0 12px;font-size:12px;font-weight:600" >
< svg width = "13" height = "13" viewBox = "0 0 24 24" fill = "none" stroke = "#FF5E13" stroke-width = "2" > < path d = "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" / > < polyline points = "7 10 12 15 17 10" / > < line x1 = "12" y1 = "15" x2 = "12" y2 = "3" / > < / svg >
Export
< / button >
< / div >
< table style = "width:100%;border-collapse:collapse" >
< thead style = "background:#03004E;color:white" >
< tr >
{ [ 'Invoice No' , 'Package' , 'Credits' , 'Amount Paid' , 'Status' , 'Date' , 'Actions' ] . map ( ( h ) = > (
< th style = "padding:10px;font-size:11px;text-align:left;letter-spacing:0.04em;text-transform:uppercase" > { h } < / th >
) ) }
< / tr >
< / thead >
< tbody >
{ txRows . map ( ( row ) = > (
< tr style = "border-bottom:1px solid #E5E7EB" >
< td style = "padding:10px;font-size:12px;font-weight:700;color:#374151" > { row [ 0 ] } < / td >
< td style = "padding:10px;font-size:12px;color:#111827" > { row [ 1 ] } < / td >
< td style = "padding:10px;font-size:12px;color:#111827" > { row [ 2 ] } < / td >
< td style = "padding:10px;font-size:12px;font-weight:700;color:#111827" > { row [ 3 ] } < / td >
< td style = "padding:10px;font-size:12px" >
< span style = { ` display:inline-flex;height:22px;padding:0 10px;border-radius:999px;font-size:11px;font-weight:700;align-items:center;background: ${ row [ 4 ] === 'Completed' ? '#FFF3EE' : row [ 4 ] === 'Pending' ? '#EEF2FF' : '#F3F4F6' } ;color: ${ row [ 4 ] === 'Completed' ? '#FF5E13' : row [ 4 ] === 'Pending' ? '#03004E' : '#6B7280' } ` } > { row [ 4 ] } < / span >
< / td >
< td style = "padding:10px;font-size:12px;color:#64748B" > { row [ 5 ] } < / td >
< td style = "padding:10px" >
< button type = "button" style = "display:inline-flex;align-items:center;gap:6px;height:28px;border-radius:8px;border:1px solid #E5E7EB;background:white;padding:0 10px;font-size:11px;font-weight:700;color:#374151" >
< svg width = "12" height = "12" viewBox = "0 0 24 24" fill = "none" stroke = "#FF5E13" stroke-width = "2" > < path d = "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" / > < polyline points = "7 10 12 15 17 10" / > < line x1 = "12" y1 = "15" x2 = "12" y2 = "3" / > < / svg >
Download
< / button >
< / td >
< / tr >
) ) }
< / tbody >
< / table >
< div style = "padding:10px;display:flex;align-items:center;justify-content:space-between" >
< p style = "margin:0;font-size:12px;color:#64748B" > Showing 1 to 4 of 42 transactions < / p >
< div style = "display:flex;gap:6px" >
{ [ 1 , 2 , 3 ] . map ( ( p ) = > (
< button type = "button" style = { ` width:30px;height:30px;border-radius:8px;border:1px solid #E5E7EB;background: ${ p === 1 ? '#FF5E13' : '#fff' } ;color: ${ p === 1 ? 'white' : '#6B7280' } ;font-size:12px;font-weight:700 ` } > { p } < / button >
) ) }
< / div >
< / div >
< / div >
< / div >
) ;
}
if ( tab === 'usage history' ) {
return (
< div style = "display:flex;flex-direction:column;gap:10px" >
< div style = "display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px" >
< div style = "border:1px solid #E5E7EB;background:white;border-radius:12px;padding:12px;box-shadow:0 1px 3px rgba(0,0,0,0.05)" > < div style = "display:flex;align-items:center;gap:6px" > < img src = "/sidebar-icons/report.svg" alt = "" style = { ` width:14px;height:14px;object-fit:contain;filter: ${ ORANGE_ICON_FILTER } ` } / > < p style = "margin:0;font-size:11px;color:#6B7280;text-transform:uppercase" > Total Used ( 30 d ) < / p > < / div > < p style = "margin:8px 0 0;font-size:20px;font-weight:800;color:#111827" > 12 , 480 < / p > < / div >
< div style = "border:1px solid #E5E7EB;background:white;border-radius:12px;padding:12px;box-shadow:0 1px 3px rgba(0,0,0,0.05)" > < div style = "display:flex;align-items:center;gap:6px" > < img src = "/sidebar-icons/leads.svg" alt = "" style = { ` width:14px;height:14px;object-fit:contain;filter: ${ ORANGE_ICON_FILTER } ` } / > < p style = "margin:0;font-size:11px;color:#6B7280;text-transform:uppercase" > Most Used Action < / p > < / div > < p style = "margin:8px 0 0;font-size:18px;font-weight:800;color:#111827" > Boost Profile < / p > < / div >
< div style = "border:1px solid #E5E7EB;background:white;border-radius:12px;padding:12px;box-shadow:0 1px 3px rgba(0,0,0,0.05)" > < div style = "display:flex;align-items:center;gap:6px" > < img src = "/sidebar-icons/credits.svg" alt = "" style = { ` width:14px;height:14px;object-fit:contain;filter: ${ ORANGE_ICON_FILTER } ` } / > < p style = "margin:0;font-size:11px;color:#6B7280;text-transform:uppercase" > Current Balance < / p > < / div > < p style = "margin:8px 0 0;font-size:20px;font-weight:800;color:#111827" > 12 , 450 TC < / p > < / div >
< / div >
< div style = "border:1px solid #E5E7EB;background:white;border-radius:16px;box-shadow:0 1px 4px rgba(0,0,0,0.06);overflow:hidden" >
< div style = "padding:12px;display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid #E5E7EB;background:#F9FAFB" >
< div style = "display:flex;align-items:center;gap:8px;flex:1" >
< input value = "" readOnly placeholder = "Search usage history..." style = "height:34px;min-width:220px;flex:1;border-radius:8px;border:1px solid #E5E7EB;background:white;padding:0 12px;font-size:12px;color:#6B7280;outline:none" / >
< button type = "button" style = "display:inline-flex;height:34px;align-items:center;gap:6px;border-radius:8px;border:1px solid #E5E7EB;background:white;padding:0 12px;font-size:12px;font-weight:500;color:#374151" >
< svg width = "13" height = "13" viewBox = "0 0 24 24" fill = "none" stroke = "#FF5E13" stroke-width = "2" > < path d = "M7 4v13" / > < path d = "m3 13 4 4 4-4" / > < path d = "M17 20V7" / > < path d = "m21 11-4-4-4 4" / > < / svg >
Sort
< / button >
< button type = "button" style = "display:inline-flex;height:34px;align-items:center;gap:6px;border-radius:8px;border:1px solid #E5E7EB;background:white;padding:0 12px;font-size:12px;font-weight:500;color:#374151" >
< svg width = "13" height = "13" viewBox = "0 0 24 24" fill = "none" stroke = "#FF5E13" stroke-width = "2" > < path d = "M3 5h18M6 12h12M10 19h4" / > < / svg >
Filters
< / button >
< / div >
< button type = "button" style = "display:inline-flex;align-items:center;gap:6px;height:34px;border:none;border-radius:8px;background:#0D0D2A;color:white;padding:0 12px;font-size:12px;font-weight:600" >
< svg width = "13" height = "13" viewBox = "0 0 24 24" fill = "none" stroke = "#FF5E13" stroke-width = "2" > < path d = "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" / > < polyline points = "7 10 12 15 17 10" / > < line x1 = "12" y1 = "15" x2 = "12" y2 = "3" / > < / svg >
Export
< / button >
< / div >
< table style = "width:100%;border-collapse:collapse" >
< thead style = "background:#03004E;color:white" >
< tr >
{ [ 'Usage ID' , 'Action Type' , 'Credits Used' , 'Related ID' , 'Date' , 'Remarks' ] . map ( ( h ) = > (
< th style = "padding:10px;font-size:11px;text-align:left;letter-spacing:0.04em;text-transform:uppercase" > { h } < / th >
) ) }
< / tr >
< / thead >
< tbody >
{ [
[ '#US-99421' , 'Unlock Contact' , '-5.00' , 'CONT-2034' , 'Oct 24, 2023' , 'Direct profile unlock for enterprise tier' ] ,
[ '#US-99418' , 'Boost Profile' , '-25.00' , 'PROF-8812' , 'Oct 23, 2023' , 'Featured placement (7 days)' ] ,
[ '#US-99405' , 'Batch Export' , '-150.00' , 'EXP-0044' , 'Oct 22, 2023' , 'Export of 300 leads' ] ,
[ '#US-99388' , 'Unlock Contact' , '-5.00' , 'CONT-1992' , 'Oct 21, 2023' , 'Individual unlock action' ] ,
] . map ( ( row ) = > (
< tr style = "border-bottom:1px solid #E5E7EB" >
< td style = "padding:10px;font-size:12px;font-weight:700;color:#64748B" > { row [ 0 ] } < / td >
< td style = "padding:10px;font-size:12px;color:#111827" > { row [ 1 ] } < / td >
< td style = "padding:10px;font-size:12px;font-weight:700;color:#03004E" > { row [ 2 ] } < / td >
< td style = "padding:10px;font-size:12px;color:#64748B" > { row [ 3 ] } < / td >
< td style = "padding:10px;font-size:12px;color:#64748B" > { row [ 4 ] } < / td >
< td style = "padding:10px;font-size:12px;color:#64748B" > { row [ 5 ] } < / td >
< / tr >
) ) }
< / tbody >
< / table >
< div style = "padding:10px;display:flex;justify-content:space-between;align-items:center;border-top:1px solid #E5E7EB" >
< p style = "margin:0;font-size:12px;color:#64748B" > Showing 1 to 4 of 142 results < / p >
< div style = "display:flex;gap:6px" >
{ [ 1 , 2 , 3 ] . map ( ( p ) = > (
< button type = "button" style = { ` width:30px;height:30px;border-radius:8px;border:1px solid #E5E7EB;background: ${ p === 1 ? '#03004E' : '#fff' } ;color: ${ p === 1 ? 'white' : '#6B7280' } ;font-size:12px;font-weight:700 ` } > { p } < / button >
) ) }
< / div >
< / div >
< / div >
< / div >
) ;
}
if ( tab === 'invoices' ) {
return (
< div style = "border:1px solid #E5E7EB;background:white;border-radius:16px;box-shadow:0 1px 4px rgba(0,0,0,0.06);overflow:hidden" >
< div style = "padding:12px;display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid #E5E7EB;background:#F9FAFB" >
< div style = "display:flex;align-items:center;gap:8px;flex:1" >
< input value = "" readOnly placeholder = "Search invoices..." style = "height:34px;min-width:220px;flex:1;border-radius:8px;border:1px solid #E5E7EB;background:white;padding:0 12px;font-size:12px;color:#6B7280;outline:none" / >
< button type = "button" style = "display:inline-flex;height:34px;align-items:center;gap:6px;border-radius:8px;border:1px solid #E5E7EB;background:white;padding:0 12px;font-size:12px;font-weight:500;color:#374151" >
< svg width = "13" height = "13" viewBox = "0 0 24 24" fill = "none" stroke = "#FF5E13" stroke-width = "2" > < path d = "M7 4v13" / > < path d = "m3 13 4 4 4-4" / > < path d = "M17 20V7" / > < path d = "m21 11-4-4-4 4" / > < / svg >
Sort
< / button >
< button type = "button" style = "display:inline-flex;height:34px;align-items:center;gap:6px;border-radius:8px;border:1px solid #E5E7EB;background:white;padding:0 12px;font-size:12px;font-weight:500;color:#374151" >
< svg width = "13" height = "13" viewBox = "0 0 24 24" fill = "none" stroke = "#FF5E13" stroke-width = "2" > < path d = "M3 5h18M6 12h12M10 19h4" / > < / svg >
Filters
< / button >
< / div >
< button type = "button" style = "display:inline-flex;align-items:center;gap:6px;height:34px;border:none;border-radius:8px;background:#0D0D2A;color:white;padding:0 12px;font-size:12px;font-weight:600" >
< svg width = "13" height = "13" viewBox = "0 0 24 24" fill = "none" stroke = "#FF5E13" stroke-width = "2" > < path d = "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" / > < polyline points = "7 10 12 15 17 10" / > < line x1 = "12" y1 = "15" x2 = "12" y2 = "3" / > < / svg >
Export
< / button >
< / div >
< table style = "width:100%;border-collapse:collapse" >
< thead style = "background:#03004E;color:white" >
< tr >
{ [ 'Invoice Number' , 'Billing Date' , 'Package' , 'Total' , 'Status' ] . map ( ( h ) = > (
< th style = "padding:10px;font-size:11px;text-align:left;letter-spacing:0.04em;text-transform:uppercase" > { h } < / th >
) ) }
< / tr >
< / thead >
< tbody >
{ invoiceRows . map ( ( row ) = > (
< tr style = "border-bottom:1px solid #E5E7EB" >
< td style = "padding:10px;font-size:12px;font-weight:700;color:#111827" > { row [ 0 ] } < / td >
< td style = "padding:10px;font-size:12px;color:#6B7280" > { row [ 1 ] } < / td >
< td style = "padding:10px;font-size:12px;color:#111827" > { row [ 2 ] } < / td >
< td style = "padding:10px;font-size:12px;font-weight:700;color:#111827" > { row [ 3 ] } < / td >
< td style = "padding:10px;font-size:12px" >
< span style = { ` display:inline-flex;height:22px;padding:0 10px;border-radius:999px;font-size:11px;font-weight:700;align-items:center;background: ${ row [ 4 ] === 'Paid' ? '#FFF3EE' : '#F3F4F6' } ;color: ${ row [ 4 ] === 'Paid' ? '#FF5E13' : '#6B7280' } ` } > { row [ 4 ] } < / span >
< / td >
< / tr >
) ) }
< / tbody >
< / table >
< div style = "padding:10px;display:flex;justify-content:space-between;align-items:center;border-top:1px solid #E5E7EB" >
< p style = "margin:0;font-size:12px;color:#64748B" > Showing 1 to 4 of 24 invoices < / p >
< div style = "display:flex;gap:6px" >
{ [ 1 , 2 , 3 ] . map ( ( p ) = > (
< button type = "button" style = { ` width:30px;height:30px;border-radius:8px;border:1px solid #E5E7EB;background: ${ p === 1 ? '#03004E' : '#fff' } ;color: ${ p === 1 ? 'white' : '#6B7280' } ;font-size:12px;font-weight:700 ` } > { p } < / button >
) ) }
< / div >
< / div >
< / div >
) ;
}
return (
< div style = "display:flex;flex-direction:column;gap:10px" >
< div style = "display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px" >
< div style = "border:1px solid #E5E7EB;background:white;border-radius:12px;padding:12px;box-shadow:0 1px 3px rgba(0,0,0,0.05)" >
< div style = "display:flex;align-items:center;gap:6px" >
< img src = "/sidebar-icons/credits.svg" alt = "" style = { ` width:14px;height:14px;object-fit:contain;filter: ${ ORANGE_ICON_FILTER } ` } / >
< p style = "margin:0;font-size:11px;color:#6B7280;text-transform:uppercase" > Current Balance < / p >
< / div >
< p style = "margin:8px 0 0;font-size:26px;font-weight:800;color:#111827" > 12 , 450 TC < / p >
< p style = "margin:4px 0 0;font-size:12px;color:#FF5E13;font-weight:700" > + 12 % from last month < / p >
< / div >
< div style = "border:1px solid #E5E7EB;background:white;border-radius:12px;padding:12px;box-shadow:0 1px 3px rgba(0,0,0,0.05)" >
< div style = "display:flex;align-items:center;gap:6px" >
< img src = "/sidebar-icons/report.svg" alt = "" style = { ` width:14px;height:14px;object-fit:contain;filter: ${ ORANGE_ICON_FILTER } ` } / >
< p style = "margin:0;font-size:11px;color:#6B7280;text-transform:uppercase" > Monthly Usage < / p >
< / div >
< p style = "margin:8px 0 0;font-size:26px;font-weight:800;color:#111827" > 1 , 248 < / p >
< p style = "margin:4px 0 0;font-size:12px;color:#6B7280" > Avg 42 / day < / p >
< / div >
< div style = "border:1px solid #E5E7EB;background:white;border-radius:12px;padding:12px;box-shadow:0 1px 3px rgba(0,0,0,0.05)" >
< div style = "display:flex;align-items:center;gap:6px" >
< img src = "/sidebar-icons/approval.svg" alt = "" style = { ` width:14px;height:14px;object-fit:contain;filter: ${ ORANGE_ICON_FILTER } ` } / >
< p style = "margin:0;font-size:11px;color:#6B7280;text-transform:uppercase" > Pending Invoices < / p >
< / div >
< p style = "margin:8px 0 0;font-size:26px;font-weight:800;color:#111827" > 1 < / p >
< button type = "button" style = "margin-top:8px;height:30px;border:none;border-radius:8px;background:#03004E;color:white;padding:0 10px;font-size:12px;font-weight:700" > Buy Credits < / button >
< / div >
< / div >
< div style = "border:1px solid #E5E7EB;background:white;border-radius:16px;box-shadow:0 1px 4px rgba(0,0,0,0.06);overflow:hidden" >
< div style = "padding:10px 12px;background:#F9FAFB;border-bottom:1px solid #E5E7EB;display:flex;justify-content:space-between;align-items:center" >
< p style = "margin:0;font-size:14px;font-weight:700;color:#111827" > Recent Transactions < / p >
< button type = "button" style = "height:30px;border:none;border-radius:8px;background:#03004E;color:white;padding:0 10px;font-size:12px;font-weight:700" > Buy Credits < / button >
< / div >
< div style = "padding:12px;display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid #E5E7EB;background:#F9FAFB" >
< div style = "display:flex;align-items:center;gap:8px;flex:1" >
< input value = "" readOnly placeholder = "Search transactions..." style = "height:34px;min-width:220px;flex:1;border-radius:8px;border:1px solid #E5E7EB;background:white;padding:0 12px;font-size:12px;color:#6B7280;outline:none" / >
< button type = "button" style = "display:inline-flex;height:34px;align-items:center;gap:6px;border-radius:8px;border:1px solid #E5E7EB;background:white;padding:0 12px;font-size:12px;font-weight:500;color:#374151" >
< svg width = "13" height = "13" viewBox = "0 0 24 24" fill = "none" stroke = "#FF5E13" stroke-width = "2" > < path d = "M7 4v13" / > < path d = "m3 13 4 4 4-4" / > < path d = "M17 20V7" / > < path d = "m21 11-4-4-4 4" / > < / svg >
Sort
< / button >
< button type = "button" style = "display:inline-flex;height:34px;align-items:center;gap:6px;border-radius:8px;border:1px solid #E5E7EB;background:white;padding:0 12px;font-size:12px;font-weight:500;color:#374151" >
< svg width = "13" height = "13" viewBox = "0 0 24 24" fill = "none" stroke = "#FF5E13" stroke-width = "2" > < path d = "M3 5h18M6 12h12M10 19h4" / > < / svg >
Filters
< / button >
< / div >
< button type = "button" style = "display:inline-flex;align-items:center;gap:6px;height:34px;border:none;border-radius:8px;background:#0D0D2A;color:white;padding:0 12px;font-size:12px;font-weight:600" >
< svg width = "13" height = "13" viewBox = "0 0 24 24" fill = "none" stroke = "#FF5E13" stroke-width = "2" > < path d = "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" / > < polyline points = "7 10 12 15 17 10" / > < line x1 = "12" y1 = "15" x2 = "12" y2 = "3" / > < / svg >
Export
< / button >
< / div >
< table style = "width:100%;border-collapse:collapse" >
< thead style = "background:#03004E;color:white" >
< tr >
{ [ 'Transaction ID' , 'Package' , 'Credits' , 'Amount Paid' , 'Status' , 'Date' ] . map ( ( h ) = > (
< th style = "padding:10px;font-size:11px;text-align:left;letter-spacing:0.04em;text-transform:uppercase" > { h } < / th >
) ) }
< / tr >
< / thead >
< tbody >
{ txRows . slice ( 0 , 3 ) . map ( ( row ) = > (
< tr style = "border-bottom:1px solid #E5E7EB" >
< td style = "padding:10px;font-size:12px;font-weight:700;color:#374151" > { row [ 0 ] } < / td >
< td style = "padding:10px;font-size:12px;color:#111827" > { row [ 1 ] } < / td >
< td style = "padding:10px;font-size:12px;color:#111827" > { row [ 2 ] } < / td >
< td style = "padding:10px;font-size:12px;font-weight:700;color:#111827" > { row [ 3 ] } < / td >
< td style = "padding:10px;font-size:12px" >
< span style = { ` display:inline-flex;height:22px;padding:0 10px;border-radius:999px;font-size:11px;font-weight:700;align-items:center;background: ${ row [ 4 ] === 'Completed' ? '#FFF3EE' : row [ 4 ] === 'Pending' ? '#EEF2FF' : '#F3F4F6' } ;color: ${ row [ 4 ] === 'Completed' ? '#FF5E13' : row [ 4 ] === 'Pending' ? '#03004E' : '#6B7280' } ` } > { row [ 4 ] } < / span >
< / td >
< td style = "padding:10px;font-size:12px;color:#64748B" > { row [ 5 ] } < / td >
< / tr >
) ) }
< / tbody >
< / table >
< / div >
< / div >
) ;
}
if ( customerKey ( ) === 'explore nxtgauge' ) {
return (
< div style = "display:flex;flex-direction:column;gap:12px" >
< div style = "border:1px solid #E5E7EB;border-radius:16px;background:white;box-shadow:0 1px 4px rgba(0,0,0,0.06);padding:20px" >
< p style = "margin:0;font-size:34px;line-height:1.12;font-weight:800;color:#111827;max-width:760px" > Explore opportunities on Nxtgauge < / p >
< p style = "margin:10px 0 0;font-size:14px;line-height:1.5;max-width:760px;color:#6B7280" > Discover services , connect with verified users , and expand into additional roles using the same dashboard workflow . < / p >
< / div >
< div style = "display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px" >
{ [
{ key : 'COMPANY' , title : 'Company' , icon : '/sidebar-icons/users.svg' , subtitle : 'Hire talent, post jobs, and manage applications in one path.' } ,
{ key : 'JOB_SEEKER' , title : 'Job Seeker' , icon : '/sidebar-icons/company.svg' , subtitle : 'Explore opportunities and apply to roles with your profile.' } ,
{ key : 'SERVICE_SEEKER' , title : 'Service Seeker' , icon : '/sidebar-icons/candidate.svg' , subtitle : 'Post requirements and connect with verified professionals.' } ,
] . map ( ( roleCard ) = > {
const isCurrentRole = normalizeRoleKey ( props . roleKey || '' ) === normalizeRoleKey ( roleCard . key ) ;
return (
< div style = "border:1px solid #E5E7EB;background:white;border-radius:16px;padding:14px;box-shadow:0 1px 4px rgba(0,0,0,0.06);display:flex;flex-direction:column;gap:8px" >
< img src = { roleCard . icon } alt = "" style = { ` width:20px;height:20px;object-fit:contain;filter: ${ ORANGE_ICON_FILTER } ` } / >
< p style = "margin:0;font-size:18px;font-weight:800;color:#111827" > { roleCard . title } < / p >
< p style = "margin:0;font-size:12px;line-height:1.5;color:#6B7280" > { roleCard . subtitle } < / p >
< button
type = "button"
style = { ` margin-top:auto;height:32px;border-radius:8px;border:none;background: ${ isCurrentRole ? '#E5E7EB' : '#03004E' } ;color: ${ isCurrentRole ? '#4B5563' : 'white' } ;padding:0 10px;font-size:12px;font-weight:700 ` }
>
{ isCurrentRole ? 'Current Role' : ` Register as ${ roleCard . title } ` }
< / button >
< / div >
) ;
} ) }
< / div >
< div style = "border:1px solid #E5E7EB;background:white;border-radius:16px;padding:14px;box-shadow:0 1px 4px rgba(0,0,0,0.06)" >
< p style = "margin:0;font-size:30px;font-weight:800;color:#111827" > Professional Services < / p >
< p style = "margin:6px 0 0;font-size:13px;color:#6B7280" > Choose from 10 professional roles , including UGC Content Creator . < / p >
< div style = "display:grid;grid-template-columns:repeat(5,minmax(0,1fr));gap:10px;margin-top:12px" >
< For each = { exploreRoleCards ( ) } >
{ ( role ) = > (
< div style = "border:1px solid #E5E7EB;border-radius:12px;background:white;padding:12px;display:flex;flex-direction:column;min-height:174px" >
< div style = "display:flex;align-items:center;justify-content:space-between" >
< Show
when = { role . iconAsset }
fallback = { < role.Icon size = { 18 } color = "#FF5E13" strokeWidth = { 2.2 } / > }
>
< img src = { role . iconAsset } alt = "" style = { ` width:18px;height:18px;object-fit:contain;filter: ${ ORANGE_ICON_FILTER } ` } / >
< / Show >
< span style = { ` display:inline-flex;align-items:center;height:20px;padding:0 8px;border-radius:999px;font-size:10px;font-weight:700;border:1px solid ${ role . status === 'Registered' ? '#D1D5DB' : '#FFD8C2' } ;background: ${ role . status === 'Registered' ? '#F3F4F6' : '#FFF1EB' } ;color: ${ role . status === 'Registered' ? '#4B5563' : '#FF5E13' } ` } > { role . status } < / span >
< / div >
< p style = "margin:10px 0 0;font-size:18px;font-weight:800;color:#111827;line-height:1.2" > { role . title } < / p >
< p style = "margin:6px 0 0;font-size:12px;line-height:1.45;color:#6B7280" > { role . subtitle } < / p >
< button type = "button" style = { ` margin-top:auto;height:32px;border-radius:8px;border:none;background: ${ role . action === 'Register' ? '#03004E' : '#E5E7EB' } ;color: ${ role . action === 'Register' ? 'white' : '#4B5563' } ;padding:0 10px;font-size:12px;font-weight:700 ` } > { role . action } < / button >
< / div >
) }
< / For >
< / div >
< / div >
< div style = "border:1px solid #E5E7EB;background:linear-gradient(180deg,#FFFFFF 0%,#FFFAF7 100%);border-radius:20px;padding:18px;box-shadow:0 8px 20px rgba(15,23,42,0.06)" >
< p style = "margin:0;font-size:12px;letter-spacing:0.08em;text-transform:uppercase;font-weight:700;color:#FF5E13;text-align:center" > Growth Advantage < / p >
< p style = "margin:4px 0 0;font-size:32px;font-weight:800;color:#111827;text-align:center;line-height:1.1" > Why Add More Services ? < / p >
< p style = "margin:8px auto 0;font-size:13px;line-height:1.5;color:#6B7280;text-align:center;max-width:760px" > A multi - service profile helps you acquire more opportunities , improve trust , and scale consistently on a single platform . < / p >
< div style = "margin-top:14px;display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:12px" >
< div style = "border:1px solid #E5E7EB;background:white;border-radius:14px;padding:14px;box-shadow:0 2px 6px rgba(0,0,0,0.05)" >
< div style = "width:34px;height:34px;border-radius:999px;background:#FFF3EE;display:flex;align-items:center;justify-content:center" > < img src = "/sidebar-icons/leads.svg" alt = "" style = { ` width:16px;height:16px;object-fit:contain;filter: ${ ORANGE_ICON_FILTER } ` } / > < / div >
< p style = "margin:10px 0 0;font-size:15px;font-weight:800;color:#111827" > Reach More Buyers < / p >
< p style = "margin:6px 0 0;font-size:12px;color:#6B7280;line-height:1.45" > Get discovered by customers across multiple demand categories . < / p >
< / div >
< div style = "border:1px solid #E5E7EB;background:white;border-radius:14px;padding:14px;box-shadow:0 2px 6px rgba(0,0,0,0.05)" >
< div style = "width:34px;height:34px;border-radius:999px;background:#FFF3EE;display:flex;align-items:center;justify-content:center" > < img src = "/sidebar-icons/pricing.svg" alt = "" style = { ` width:16px;height:16px;object-fit:contain;filter: ${ ORANGE_ICON_FILTER } ` } / > < / div >
< p style = "margin:10px 0 0;font-size:15px;font-weight:800;color:#111827" > Increase Revenue Paths < / p >
< p style = "margin:6px 0 0;font-size:12px;color:#6B7280;line-height:1.45" > Offer additional services and create new income streams . < / p >
< / div >
< div style = "border:1px solid #E5E7EB;background:white;border-radius:14px;padding:14px;box-shadow:0 2px 6px rgba(0,0,0,0.05)" >
< div style = "width:34px;height:34px;border-radius:999px;background:#FFF3EE;display:flex;align-items:center;justify-content:center" > < img src = "/sidebar-icons/approval.svg" alt = "" style = { ` width:16px;height:16px;object-fit:contain;filter: ${ ORANGE_ICON_FILTER } ` } / > < / div >
< p style = "margin:10px 0 0;font-size:15px;font-weight:800;color:#111827" > Strengthen Credibility < / p >
< p style = "margin:6px 0 0;font-size:12px;color:#6B7280;line-height:1.45" > Verified multi - service profiles build confidence and improve conversion . < / p >
< / div >
< div style = "border:1px solid #E5E7EB;background:white;border-radius:14px;padding:14px;box-shadow:0 2px 6px rgba(0,0,0,0.05)" >
< div style = "width:34px;height:34px;border-radius:999px;background:#FFF3EE;display:flex;align-items:center;justify-content:center" > < img src = "/sidebar-icons/report.svg" alt = "" style = { ` width:16px;height:16px;object-fit:contain;filter: ${ ORANGE_ICON_FILTER } ` } / > < / div >
< p style = "margin:10px 0 0;font-size:15px;font-weight:800;color:#111827" > Scale Faster < / p >
< p style = "margin:6px 0 0;font-size:12px;color:#6B7280;line-height:1.45" > Grow your business from one unified account and workflow . < / p >
< / div >
< / div >
< / div >
< / div >
) ;
}
if ( customerKey ( ) === 'verification' ) {
if ( tab === 'approval status' ) {
return (
< div style = "display:flex;flex-direction:column;gap:12px" >
< div style = "border:1px solid #E5E7EB;background:white;border-radius:14px;padding:14px" >
< p style = "margin:0;font-size:22px;font-weight:800;color:#111827" > Verification Center < / p >
< p style = "margin:6px 0 0;font-size:13px;color:#6B7280;line-height:1.5" > Complete just 2 steps . Submit profile and portfolio . We review and unlock access . < / p >
< div style = "display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px;margin-top:10px" >
< div style = "border:1px solid #E5E7EB;border-radius:10px;background:#F9FAFB;padding:10px" >
< p style = "margin:0;font-size:10px;letter-spacing:0.06em;text-transform:uppercase;color:#9CA3AF" > Step 1 < / p >
< p style = "margin:8px 0 0;font-size:13px;font-weight:800;color:#111827" > Profile : { approvalTone ( profileApprovalState ( ) ) . label } < / p >
< / div >
< div style = "border:1px solid #E5E7EB;border-radius:10px;background:#F9FAFB;padding:10px" >
< p style = "margin:0;font-size:10px;letter-spacing:0.06em;text-transform:uppercase;color:#9CA3AF" > Step 2 < / p >
< p style = "margin:8px 0 0;font-size:13px;font-weight:800;color:#111827" > Portfolio : { approvalTone ( portfolioApprovalState ( ) ) . label } < / p >
< / div >
< div style = "border:1px solid #E5E7EB;border-radius:10px;background:#F9FAFB;padding:10px" >
< p style = "margin:0;font-size:10px;letter-spacing:0.06em;text-transform:uppercase;color:#9CA3AF" > Final Access < / p >
< p style = "margin:8px 0 0;font-size:13px;font-weight:800;color:#111827" > { bothApprovalsApproved ( ) ? 'Unlocked' : 'Blocked' } < / p >
< / div >
< / div >
< / div >
< div style = "display:grid;grid-template-columns:1fr 1fr;gap:12px" >
< div style = "border:1px solid #E5E7EB;background:white;border-radius:14px;padding:14px" >
< p style = "margin:0;font-size:18px;font-weight:800;color:#111827" > Profile Verification < / p >
< p style = "margin:6px 0 0;font-size:12px;color:#6B7280" > Finish your basic information and required documents , then submit . < / p >
< div style = "display:flex;gap:8px;justify-content:flex-end;margin-top:10px" >
< button type = "button" onClick = { ( ) = > { props . onSidebarSelect ( 'My Profile' ) ; props . onTabSelect ( 'basic information' ) ; } } style = "height:32px;border-radius:8px;border:1px solid #E5E7EB;background:white;padding:0 12px;font-size:12px;font-weight:700;color:#374151" > Open My Profile < / button >
< button type = "button" onClick = { submitProfileForApproval } disabled = { profileApprovalState ( ) === 'IN_REVIEW' } style = { ` height:32px;border-radius:8px;border:none;background: ${ profileApprovalState ( ) === 'IN_REVIEW' ? '#9CA3AF' : '#03004E' } ;color:white;padding:0 12px;font-size:12px;font-weight:700 ` } > Submit Profile < / button >
< / div >
< / div >
< div style = "border:1px solid #E5E7EB;background:white;border-radius:14px;padding:14px" >
< p style = "margin:0;font-size:18px;font-weight:800;color:#111827" > Portfolio Verification < / p >
< p style = "margin:6px 0 0;font-size:12px;color:#6B7280" > Add portfolio details and submit separately for review . < / p >
< div style = "display:flex;gap:8px;justify-content:flex-end;margin-top:10px" >
< button type = "button" onClick = { ( ) = > { props . onSidebarSelect ( 'My Portfolio' ) ; props . onTabSelect ( 'about' ) ; } } style = "height:32px;border-radius:8px;border:1px solid #E5E7EB;background:white;padding:0 12px;font-size:12px;font-weight:700;color:#374151" > Open My Portfolio < / button >
< button type = "button" onClick = { submitPortfolioForApproval } disabled = { portfolioApprovalState ( ) === 'IN_REVIEW' } style = { ` height:32px;border-radius:8px;border:none;background: ${ portfolioApprovalState ( ) === 'IN_REVIEW' ? '#9CA3AF' : '#03004E' } ;color:white;padding:0 12px;font-size:12px;font-weight:700 ` } > Submit Portfolio < / button >
< / div >
< / div >
< / div >
< div style = "border:1px solid #E5E7EB;background:#F9FAFB;border-radius:12px;padding:12px" >
< p style = "margin:0;font-size:12px;color:#374151;line-height:1.5" > < strong > Admin Flow : < / strong > once submitted , both items appear in Verification Management . Admin can request documents or approve . Leads remain blocked until both are approved . < / p >
< / div >
< / div >
) ;
}
if ( tab === 'submitted details' ) {
return (
< div style = "display:grid;grid-template-columns:1fr 1.5fr;gap:12px" >
< div style = "border:1px solid #E5E7EB;background:white;border-radius:14px;padding:14px" >
< p style = "margin:0;font-size:20px;font-weight:800;color:#111827" > Application Snapshot < / p >
< div style = "display:grid;gap:8px;margin-top:10px" >
{ [
[ 'Service' , 'Social Media Manager' ] ,
[ 'Submission ID' , 'VRF-2023-1042' ] ,
[ 'Submitted On' , 'Oct 22, 2023' ] ,
[ 'Status' , 'Under Review' ] ,
] . map ( ( [ k , v ] ) = > (
< div style = "padding:10px;border:1px solid #E5E7EB;border-radius:10px;background:#F9FAFB;font-size:12px;color:#374151" > < strong > { k } : < / strong > { v } < / div >
) ) }
< / div >
< / div >
< div style = "border:1px solid #E5E7EB;background:white;border-radius:14px;padding:14px" >
< p style = "margin:0;font-size:20px;font-weight:800;color:#111827" > Submitted Details < / p >
< div style = "margin-top:10px;padding:10px;border:1px solid #E5E7EB;border-radius:10px;background:#F9FAFB;font-size:12px;line-height:1.6;color:#374151" >
Strategic Social Media Manager with 6 + years of experience in content strategy , campaign planning , and audience growth for D2C brands .
< / div >
< div style = "display:flex;gap:6px;flex-wrap:wrap;margin-top:10px" >
{ [ 'Sprout Social' , 'Figma' , 'Google Analytics' ] . map ( ( tool ) = > < span style = "height:24px;padding:0 10px;border-radius:999px;border:1px solid #E5E7EB;background:white;display:inline-flex;align-items:center;font-size:11px;color:#374151" > { tool } < / span > ) }
< / div >
< / div >
< / div >
) ;
}
if ( tab === 'documents' ) {
return (
< div style = "display:grid;gap:12px" >
< div style = "display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px" >
< div style = "border:1px solid #E5E7EB;background:white;border-radius:12px;padding:12px" >
< p style = "margin:0;font-size:10px;letter-spacing:0.06em;text-transform:uppercase;color:#9CA3AF" > Total Documents < / p >
< p style = "margin:6px 0 0;font-size:22px;font-weight:800;color:#111827" > 3 < / p >
< / div >
< div style = "border:1px solid #E5E7EB;background:white;border-radius:12px;padding:12px" >
< p style = "margin:0;font-size:10px;letter-spacing:0.06em;text-transform:uppercase;color:#9CA3AF" > Approved < / p >
< p style = "margin:6px 0 0;font-size:22px;font-weight:800;color:#03004E" > 2 < / p >
< / div >
< div style = "border:1px solid #FFE2D3;background:#FFF8F4;border-radius:12px;padding:12px" >
< p style = "margin:0;font-size:10px;letter-spacing:0.06em;text-transform:uppercase;color:#9CA3AF" > Needs Action < / p >
< p style = "margin:6px 0 0;font-size:22px;font-weight:800;color:#FF5E13" > 1 < / p >
< / div >
< / div >
< div style = "border:1px solid #FFE2D3;border-radius:12px;background:#FFF8F4;padding:12px;display:flex;align-items:center;justify-content:space-between;gap:10px" >
< div >
< p style = "margin:0;font-size:12px;font-weight:800;color:#111827" > Admin requested missing documents < / p >
< p style = "margin:4px 0 0;font-size:12px;color:#374151" > Required Missing Documents : Address Proof ( clear PDF / JPG / PNG ) . < / p >
< / div >
< button type = "button" style = "height:32px;border:none;border-radius:8px;background:#03004E;color:white;padding:0 12px;font-size:12px;font-weight:700;white-space:nowrap" > Upload Missing < / button >
< / div >
< div style = "border:1px solid #E5E7EB;background:white;border-radius:14px;overflow:hidden" >
< div style = "padding:12px;display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid #E5E7EB" >
< p style = "margin:0;font-size:18px;font-weight:800;color:#111827" > Documents < / p >
< button type = "button" style = "height:32px;border:none;border-radius:8px;background:#03004E;color:white;padding:0 12px;font-size:12px;font-weight:700" > Upload New < / button >
< / div >
< table style = "width:100%;border-collapse:collapse" >
< thead >
< tr style = "border-bottom:1px solid #E5E7EB;background:#F9FAFB" >
< th style = "text-align:left;padding:10px;font-size:11px;color:#6B7280" > Document < / th >
< th style = "text-align:left;padding:10px;font-size:11px;color:#6B7280" > File < / th >
< th style = "text-align:left;padding:10px;font-size:11px;color:#6B7280" > Status < / th >
< th style = "text-align:right;padding:10px;font-size:11px;color:#6B7280" > Action < / th >
< / tr >
< / thead >
< tbody >
{ [
[ 'Identity Proof' , 'id_scan_v2.pdf' , 'Approved' ] ,
[ 'Address Proof' , 'electricity_bill.jpg' , 'Rejected' ] ,
[ 'Tax Document' , 'itr_form16_2023.pdf' , 'Approved' ] ,
] . map ( ( [ doc , file , state ] ) = > (
< tr style = "border-top:1px solid #F3F4F6" >
< td style = "padding:10px;font-size:12px;color:#111827;font-weight:600" > { doc } < / td >
< td style = "padding:10px;font-size:12px;color:#374151" > { file } < / td >
< td style = "padding:10px" >
< span style = { ` height:22px;padding:0 8px;border-radius:999px;border:1px solid ${ state === 'Rejected' ? '#FFD8C2' : '#DDEBFF' } ;background: ${ state === 'Rejected' ? '#FFF1EB' : '#EEF4FF' } ;display:inline-flex;align-items:center;font-size:10px;font-weight:700;color: ${ state === 'Rejected' ? '#FF5E13' : '#03004E' } ` } > { state } < / span >
< / td >
< td style = "padding:10px;text-align:right" >
< Show
when = { state === 'Rejected' }
fallback = { < span style = "font-size:11px;color:#9CA3AF" > No action < / span > }
>
< button type = "button" style = "height:28px;border:1px solid #D1D5DB;border-radius:8px;background:white;color:#374151;padding:0 10px;font-size:11px;font-weight:700" > Re - upload < / button >
< / Show >
< / td >
< / tr >
) ) }
< / tbody >
< / table >
< / div >
< div style = "border:1px solid #FFE2D3;border-radius:12px;background:#FFF8F4;padding:10px;font-size:12px;color:#374151" >
Address Proof needs re - upload to continue verification .
< / div >
< / div >
) ;
}
if ( tab === 're-upload' ) {
return (
< div style = "display:grid;grid-template-columns:1fr 1.5fr;gap:12px" >
< div style = "border:1px solid #FFE2D3;background:#FFF8F4;border-radius:14px;padding:12px" >
< p style = "margin:0;font-size:12px;color:#FF5E13;font-weight:700" > 1 item needs correction < / p >
< p style = "margin:8px 0 0;font-size:12px;color:#374151" > Identity proof is unclear . Please upload a clear copy . < / p >
< / div >
< div style = "border:1px solid #E5E7EB;background:white;border-radius:14px;padding:12px" >
< p style = "margin:0;font-size:20px;font-weight:800;color:#111827" > Upload Correction < / p >
< input type = "file" style = "margin-top:10px;font-size:12px;color:#374151" / >
< div style = "display:flex;justify-content:flex-end;gap:8px;margin-top:10px" >
< button type = "button" style = "height:32px;border-radius:8px;border:1px solid #D1D5DB;background:white;color:#374151;padding:0 12px;font-size:12px;font-weight:700" > Cancel < / button >
< button type = "button" style = "height:32px;border-radius:8px;border:none;background:#03004E;color:white;padding:0 12px;font-size:12px;font-weight:700" > Submit < / button >
< / div >
< / div >
< / div >
) ;
}
if ( tab === 'activity' ) {
return (
< div style = "border:1px solid #E5E7EB;background:white;border-radius:14px;overflow:hidden" >
< div style = "padding:12px;border-bottom:1px solid #E5E7EB" >
< p style = "margin:0;font-size:20px;font-weight:800;color:#111827" > Verification Updates < / p >
< / div >
< div style = "padding:12px;display:grid;gap:8px" >
{ [
[ 'Today, 11:30 AM' , 'Correction submitted' , 'Completed' ] ,
[ 'Today, 10:45 AM' , 'Re-upload requested' , 'Action Needed' ] ,
[ 'Yesterday, 04:20 PM' , 'Reviewer note added' , 'Updated' ] ,
[ 'Yesterday, 11:10 AM' , 'Verification started' , 'Started' ] ,
] . map ( ( [ time , title , state ] ) = > (
< div style = "display:flex;align-items:center;justify-content:space-between;gap:10px;padding:10px;border:1px solid #E5E7EB;border-radius:10px;background:#F9FAFB" >
< div >
< p style = "margin:0;font-size:12px;font-weight:700;color:#111827" > { title } < / p >
< p style = "margin:2px 0 0;font-size:11px;color:#6B7280" > { time } < / p >
< / div >
< span style = { ` height:22px;padding:0 8px;border-radius:999px;border:1px solid ${ state === 'Action Needed' ? '#FFD8C2' : '#DDEBFF' } ;background: ${ state === 'Action Needed' ? '#FFF1EB' : '#EEF4FF' } ;display:inline-flex;align-items:center;font-size:10px;font-weight:700;color: ${ state === 'Action Needed' ? '#FF5E13' : '#03004E' } ` } > { state } < / span >
< / div >
) ) }
< / div >
< / div >
) ;
}
return (
< div style = "border:1px solid #E5E7EB;background:white;border-radius:14px;padding:14px" >
< p style = "margin:0;font-size:14px;font-weight:700;color:#111827" > Details will appear here < / p >
< p style = "margin:6px 0 0;font-size:12px;color:#6B7280" > This section is being prepared . Please use the available tabs above . < / p >
< / div >
) ;
}
if ( customerKey ( ) === 'help center' || customerKey ( ) === 'support' ) {
const active = helpCenterTab ( ) ;
return (
< div style = "display:flex;flex-direction:column;gap:12px" >
< div style = "border:1px solid #E5E7EB;background:white;border-radius:16px;padding:14px;box-shadow:0 1px 4px rgba(0,0,0,0.06)" >
< p style = "margin:0;font-size:28px;line-height:1.1;font-weight:800;color:#111827" > Help Center < / p >
< p style = "margin:8px 0 0;font-size:14px;color:#6B7280" > Get help , manage tickets , and contact support . < / p >
< div style = "display:flex;align-items:center;gap:24px;margin-top:12px;border-bottom:1px solid #E5E7EB;padding-bottom:8px" >
< button type = "button" onClick = { ( ) = > setHelpCenterTab ( 'help_center' ) } style = { ` border:none;background:none;padding:0 0 8px;font-size:13px;font-weight: ${ active === 'help_center' ? 700 : 500 } ;color: ${ active === 'help_center' ? '#FF5E13' : '#6B7280' } ;border-bottom: ${ active === 'help_center' ? '2px solid #FF5E13' : '2px solid transparent' } ;cursor:pointer ` } > Help Center < / button >
< button type = "button" onClick = { ( ) = > setHelpCenterTab ( 'my_tickets' ) } style = { ` border:none;background:none;padding:0 0 8px;font-size:13px;font-weight: ${ active === 'my_tickets' ? 700 : 500 } ;color: ${ active === 'my_tickets' ? '#FF5E13' : '#6B7280' } ;border-bottom: ${ active === 'my_tickets' ? '2px solid #FF5E13' : '2px solid transparent' } ;cursor:pointer ` } > My Tickets < / button >
< button type = "button" onClick = { ( ) = > setHelpCenterTab ( 'create_ticket' ) } style = { ` border:none;background:none;padding:0 0 8px;font-size:13px;font-weight: ${ active === 'create_ticket' ? 700 : 500 } ;color: ${ active === 'create_ticket' ? '#FF5E13' : '#6B7280' } ;border-bottom: ${ active === 'create_ticket' ? '2px solid #FF5E13' : '2px solid transparent' } ;cursor:pointer ` } > Create Ticket < / button >
< / div >
< / div >
< Show when = { active === 'help_center' } >
< div style = "border:1px solid #E5E7EB;background:white;border-radius:16px;padding:14px;box-shadow:0 1px 4px rgba(0,0,0,0.06)" >
< p style = "margin:0;font-size:11px;letter-spacing:0.08em;text-transform:uppercase;font-weight:700;color:#FF5E13" > Knowledge Base < / p >
< div style = "display:grid;grid-template-columns:1fr 1.8fr auto;gap:8px;margin-top:10px" >
< div style = "height:36px;border:1px solid #D1D5DB;border-radius:10px;background:white;padding:0 12px;display:flex;align-items:center;font-size:12px;color:#111827" > All Categories < / div >
< div style = "height:36px;border:1px solid #D1D5DB;border-radius:10px;background:white;padding:0 12px;display:flex;align-items:center;font-size:12px;color:#6B7280" > Search help articles , tickets , and guides < / div >
< button type = "button" style = "height:36px;width:44px;border:none;border-radius:10px;background:#0D0D2A;color:white;font-size:16px;font-weight:700" > ⌕ < / button >
< / div >
< div style = "display:flex;align-items:center;gap:8px;margin-top:10px" >
< span style = "font-size:11px;color:#6B7280" > Popular : < / span >
{ [ 'Account Access' , 'Verification' , 'Hiring Fees' ] . map ( ( tag ) = > < span style = "height:22px;padding:0 10px;border-radius:999px;border:1px solid #E5E7EB;background:#F9FAFB;color:#374151;display:inline-flex;align-items:center;font-size:11px" > { tag } < / span > ) }
< / div >
< / div >
< div style = "display:flex;justify-content:space-between;align-items:center" >
< p style = "margin:0;font-size:24px;font-weight:800;color:#111827;line-height:1.1" > Browse by Category < / p >
< / div >
< div style = "display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:10px" >
< For each = { HELP_CENTER_CATEGORIES } > { ( cat ) = > (
< div style = "border:1px solid #E5E7EB;background:white;border-radius:14px;padding:12px;box-shadow:0 1px 4px rgba(0,0,0,0.05)" >
< img src = { cat . icon } alt = "" style = { ` width:18px;height:18px;object-fit:contain;filter: ${ ORANGE_ICON_FILTER } ` } / >
< p style = "margin:10px 0 0;font-size:14px;font-weight:800;color:#111827" > { cat . title } < / p >
< p style = "margin:6px 0 0;font-size:12px;line-height:1.45;color:#6B7280" > { cat . description } < / p >
< div style = "display:flex;justify-content:space-between;align-items:center;margin-top:10px" >
< span style = "font-size:11px;color:#9CA3AF" > { cat . articles } Articles < / span >
< / div >
< / div >
) } < / For >
< / div >
< div style = "display:grid;grid-template-columns:1fr 1.2fr;gap:12px" >
< div >
< p style = "margin:0 0 10px;font-size:24px;font-weight:800;color:#111827;line-height:1.1" > Popular Articles < / p >
< div style = "display:grid;gap:8px" >
< For each = { HELP_CENTER_ARTICLES } > { ( a ) = > (
< button type = "button" style = "height:44px;border:1px solid #E5E7EB;background:white;border-radius:10px;padding:0 10px;display:flex;justify-content:space-between;align-items:center;cursor:pointer" >
< span style = "font-size:12px;color:#111827;text-align:left" > { a } < / span >
< span style = "font-size:14px;color:#9CA3AF" > › < / span >
< / button >
) } < / For >
< / div >
< / div >
< div >
< p style = "margin:0 0 10px;font-size:24px;font-weight:800;color:#111827;line-height:1.1" > Frequently Asked Questions < / p >
< div style = "display:grid;gap:8px" >
< For each = { HELP_CENTER_FAQS } > { ( faq , i ) = > (
< button type = "button" onClick = { ( ) = > setOpenFaqIndex ( openFaqIndex ( ) === i ( ) ? null : i ( ) ) } style = "border:1px solid #E5E7EB;background:white;border-radius:10px;padding:10px;text-align:left;cursor:pointer" >
< div style = "display:flex;justify-content:space-between;align-items:center;gap:8px;width:100%" >
< p style = "margin:0;font-size:13px;font-weight:700;color:#111827" > { faq . q } < / p >
< span style = "font-size:14px;color:#9CA3AF" > { openFaqIndex ( ) === i ( ) ? '▾' : '▸' } < / span >
< / div >
< Show when = { openFaqIndex ( ) === i ( ) } >
< p style = "margin:8px 0 0;font-size:12px;line-height:1.5;color:#6B7280" > { faq . a } < / p >
< / Show >
< / button >
) } < / For >
< / div >
< / div >
< / div >
< div >
< div style = "display:flex;justify-content:space-between;align-items:center;margin-bottom:10px" >
< p style = "margin:0;font-size:24px;font-weight:800;color:#111827;line-height:1.1" > Quick Guides < / p >
< span style = "font-size:11px;color:#9CA3AF" > Actionable tutorials to master Nxtgauge < / span >
< / div >
< div style = "display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px" >
{ [
{ title : 'The First 24 Hours: Setup' , text : 'Complete your core profile requirements and find your first lead fast.' , read : '8 min read' , level : 'Beginner' , Icon : Rocket } ,
{ title : 'Maximizing Lead Conversions' , text : 'Use response quality and timing strategies to improve closure rate.' , read : '15 min read' , level : 'Growth' , Icon : TrendingUp } ,
{ title : 'Data Privacy & Security' , text : 'Understand how we protect your business and financial information.' , read : '5 min read' , level : 'Essential' , Icon : ShieldCheck } ,
] . map ( ( g ) = > (
< div style = "border:1px solid #E5E7EB;background:white;border-radius:12px;overflow:hidden;box-shadow:0 1px 4px rgba(0,0,0,0.05)" >
< div style = "padding:10px;display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid #F3F4F6;background:#FAFAFA" >
< span style = "height:22px;padding:0 8px;border-radius:999px;background:#FFF1EB;color:#FF5E13;font-size:10px;font-weight:700;display:inline-flex;align-items:center;border:1px solid #FFD8C2" > { g . level } < / span >
< span style = "width:24px;height:24px;border-radius:999px;background:#FFF1EB;display:inline-flex;align-items:center;justify-content:center;color:#FF5E13;border:1px solid #FFD8C2" >
< g.Icon size = { 13 } / >
< / span >
< / div >
< div style = "padding:12px" >
< p style = "margin:0;font-size:14px;font-weight:800;color:#111827" > { g . title } < / p >
< p style = "margin:6px 0 0;font-size:12px;line-height:1.45;color:#6B7280" > { g . text } < / p >
< div style = "margin-top:10px;display:flex;align-items:center;justify-content:space-between" >
< p style = "margin:0;font-size:11px;color:#9CA3AF;text-transform:uppercase" > { g . read } < / p >
< button type = "button" style = "height:26px;border-radius:7px;border:1px solid #D1D5DB;background:white;color:#374151;padding:0 9px;font-size:11px;font-weight:700" > Read < / button >
< / div >
< / div >
< / div >
) ) }
< / div >
< / div >
< / Show >
< Show when = { active === 'my_tickets' } >
< div style = "border:1px solid #E5E7EB;background:white;border-radius:16px;padding:16px;box-shadow:0 1px 4px rgba(0,0,0,0.06)" >
< div style = "display:flex;justify-content:space-between;align-items:center;gap:10px" >
< p style = "margin:0;font-size:24px;line-height:1.1;font-weight:800;color:#111827" > My Tickets < / p >
< div style = "display:flex;gap:8px" >
< span style = "height:26px;padding:0 10px;border-radius:999px;border:1px solid #E5E7EB;background:#F9FAFB;display:inline-flex;align-items:center;font-size:11px;color:#374151;font-weight:600" > Open : { ticketSummary ( ) . openCount } < / span >
< span style = "height:26px;padding:0 10px;border-radius:999px;border:1px solid #E5E7EB;background:#F9FAFB;display:inline-flex;align-items:center;font-size:11px;color:#374151;font-weight:600" > Resolved : { ticketSummary ( ) . resolvedCount } < / span >
< / div >
< / div >
< div style = "margin-top:10px;border:1px solid #E5E7EB;border-radius:10px;background:#F9FAFB;padding:10px 12px" >
< p style = "margin:0;font-size:12px;color:#6B7280" > You have { ticketSummary ( ) . total } tickets . { ticketSummary ( ) . openCount } still need action and { ticketSummary ( ) . resolvedCount } are resolved . < / p >
< / div >
< div style = "margin-top:14px;display:flex;align-items:center;gap:18px;border-bottom:1px solid #E5E7EB;padding-bottom:8px" >
< button type = "button" onClick = { ( ) = > setMyTicketsTab ( 'all_tickets' ) } style = { ` border:none;background:none;padding:0 0 8px;font-size:13px;font-weight: ${ myTicketsTab ( ) === 'all_tickets' ? 700 : 500 } ;color: ${ myTicketsTab ( ) === 'all_tickets' ? '#FF5E13' : '#6B7280' } ;border-bottom: ${ myTicketsTab ( ) === 'all_tickets' ? '2px solid #FF5E13' : '2px solid transparent' } ;cursor:pointer ` } > All Tickets < / button >
< button type = "button" onClick = { ( ) = > setMyTicketsTab ( 'view_ticket' ) } style = { ` border:none;background:none;padding:0 0 8px;font-size:13px;font-weight: ${ myTicketsTab ( ) === 'view_ticket' ? 700 : 500 } ;color: ${ myTicketsTab ( ) === 'view_ticket' ? '#FF5E13' : '#6B7280' } ;border-bottom: ${ myTicketsTab ( ) === 'view_ticket' ? '2px solid #FF5E13' : '2px solid transparent' } ;cursor:pointer ` } > View Ticket < / button >
< / div >
< Show when = { myTicketsTab ( ) === 'all_tickets' } >
< div style = "margin-top:12px;border:1px solid #E5E7EB;border-radius:12px;overflow:hidden;background:white" >
< table style = "width:100%;border-collapse:collapse" >
< thead >
< tr style = "background:#F9FAFB;border-bottom:1px solid #E5E7EB" >
< th style = "text-align:left;padding:10px;font-size:11px;color:#6B7280;text-transform:uppercase;letter-spacing:0.04em" > Ticket ID < / th >
< th style = "text-align:left;padding:10px;font-size:11px;color:#6B7280;text-transform:uppercase;letter-spacing:0.04em" > Request < / th >
< th style = "text-align:left;padding:10px;font-size:11px;color:#6B7280;text-transform:uppercase;letter-spacing:0.04em" > State < / th >
< th style = "text-align:left;padding:10px;font-size:11px;color:#6B7280;text-transform:uppercase;letter-spacing:0.04em" > Priority < / th >
< th style = "text-align:left;padding:10px;font-size:11px;color:#6B7280;text-transform:uppercase;letter-spacing:0.04em" > Last Message < / th >
< th style = "text-align:left;padding:10px;font-size:11px;color:#6B7280;text-transform:uppercase;letter-spacing:0.04em" > Updated < / th >
< th style = "text-align:left;padding:10px;font-size:11px;color:#6B7280;text-transform:uppercase;letter-spacing:0.04em" > Action < / th >
< / tr >
< / thead >
< tbody >
{ HELP_TICKET_ROWS . map ( ( t ) = > (
< tr onClick = { ( ) = > openTicketDetails ( t . id ) } style = { ` border-top:1px solid #F3F4F6;background: ${ activeTicketId ( ) === t . id ? '#F5F7FF' : 'white' } ;cursor:pointer ` } >
< td style = "padding:10px;font-size:12px;font-weight:700;color:#374151" > { t . id } < / td >
< td style = "padding:10px;font-size:12px;color:#111827;font-weight:600" > { t . title } < / td >
< td style = "padding:10px" >
< span style = { ` height:22px;padding:0 8px;border-radius:999px;border:1px solid ${ t . status === 'Resolved' ? '#D1FAE5' : '#E5E7EB' } ;background: ${ t . status === 'Resolved' ? '#ECFDF3' : '#F9FAFB' } ;display:inline-flex;align-items:center;font-size:10px;font-weight:700;color: ${ t . status === 'Resolved' ? '#059669' : '#374151' } ` } > { t . status } < / span >
< / td >
< td style = "padding:10px;font-size:12px;color:#6B7280" > { t . priority } < / td >
< td style = "padding:10px;font-size:12px;color:#6B7280" > { t . lastMessage } < / td >
< td style = "padding:10px;font-size:12px;color:#6B7280" > { t . updated } < / td >
< td style = "padding:10px" >
< button
type = "button"
onClick = { ( ) = > {
openTicketDetails ( t . id ) ;
} }
style = "height:28px;border-radius:8px;border:none;background:#0D0D2A;color:white;font-size:11px;font-weight:700;padding:0 10px;cursor:pointer"
>
Open
< / button >
< / td >
< / tr >
) ) }
< / tbody >
< / table >
< / div >
< / Show >
< Show when = { myTicketsTab ( ) === 'view_ticket' } >
< div style = "margin-top:12px;border:1px solid #E5E7EB;border-radius:12px;background:white;padding:14px" >
< div style = "display:flex;justify-content:space-between;align-items:center;gap:8px" >
< div >
< p style = "margin:0;font-size:16px;font-weight:800;color:#111827" > Ticket Communication < / p >
< p style = "margin:3px 0 0;font-size:12px;color:#6B7280" > { activeTicketId ( ) } • Share updates and attachments with support team . < / p >
< / div >
< span style = "height:24px;padding:0 10px;border-radius:999px;border:1px solid #E5E7EB;background:#F9FAFB;display:inline-flex;align-items:center;font-size:11px;color:#374151" > Message Support < / span >
< / div >
< div style = "margin-top:12px;display:grid;grid-template-columns:1.4fr 1fr;gap:10px" >
< div style = "display:grid;gap:8px" >
< div style = "border:1px solid #E5E7EB;border-radius:8px;background:#F9FAFB;padding:10px" >
< p style = "margin:0;font-size:11px;color:#6B7280" > User message < / p >
< p style = "margin:4px 0 0;font-size:12px;color:#111827" > { selectedTicketDetails ( ) . userMessage } < / p >
< / div >
< div style = "border:1px solid #E5E7EB;border-radius:8px;background:#F9FAFB;padding:10px" >
< p style = "margin:0;font-size:11px;color:#6B7280" > Admin reply < / p >
< p style = "margin:4px 0 0;font-size:12px;color:#111827" > { selectedTicketDetails ( ) . adminMessage } < / p >
< / div >
< div style = "margin-top:2px;border:1px solid #E5E7EB;border-radius:10px;background:white;padding:10px" >
< p style = "margin:0 0 8px;font-size:12px;font-weight:700;color:#111827" > Reply to Support < / p >
< textarea
value = { ticketMessage ( ) }
onInput = { ( event ) = > setTicketMessage ( event . currentTarget . value ) }
placeholder = "Write your message to support team..."
style = "min-height:82px;width:100%;border:1px solid #E5E7EB;border-radius:10px;background:#F9FAFB;padding:10px;font-size:12px;color:#111827;resize:vertical"
/ >
< div style = "display:flex;justify-content:flex-end;margin-top:8px" >
< button type = "button" style = "height:36px;border-radius:10px;border:none;background:#0D0D2A;color:white;padding:0 14px;font-size:12px;font-weight:700" > Send Message < / button >
< / div >
< / div >
< / div >
< div style = "display:grid;gap:8px" >
< div style = "border:1px solid #E5E7EB;border-radius:10px;background:#F9FAFB;padding:10px" >
< p style = "margin:0;font-size:11px;font-weight:700;color:#6B7280;text-transform:uppercase" > Requested Documents < / p >
< div style = "display:grid;gap:6px;margin-top:8px" >
< For each = { selectedTicketDetails ( ) . requestedDocuments . length ? selectedTicketDetails ( ) . requestedDocuments : [ 'No documents requested' ] } > { ( d ) = > (
< div style = "display:flex;justify-content:space-between;align-items:center;background:white;border:1px solid #E5E7EB;border-radius:8px;padding:6px 8px" >
< span style = "font-size:12px;color:#111827" > { d } < / span >
< span style = "font-size:10px;font-weight:700;color:#6B7280" > { selectedTicketDetails ( ) . requestedDocuments . length ? 'Pending' : 'NA' } < / span >
< / div >
) } < / For >
< / div >
< / div >
< div style = "border:1px solid #E5E7EB;border-radius:10px;background:#F9FAFB;padding:10px" >
< p style = "margin:0;font-size:11px;font-weight:700;color:#6B7280;text-transform:uppercase" > Received From User < / p >
< div style = "display:grid;gap:6px;margin-top:8px" >
< For each = { selectedTicketDetails ( ) . receivedFiles . length ? selectedTicketDetails ( ) . receivedFiles : [ { file : 'No files received yet' , state : 'Pending Upload' as const } ] } > { ( f ) = > (
< div style = "display:flex;justify-content:space-between;align-items:center;background:white;border:1px solid #E5E7EB;border-radius:8px;padding:6px 8px" >
< span style = "font-size:12px;color:#111827" > { f . file } < / span >
< span style = { ` font-size:10px;font-weight:700;color: ${ f . state === 'Received' ? '#059669' : '#6B7280' } ` } > { f . state } < / span >
< / div >
) } < / For >
< / div >
< / div >
< div style = "border:1px dashed #D1D5DB;border-radius:10px;background:#F9FAFB;padding:10px" >
< p style = "margin:0 0 8px;font-size:11px;font-weight:700;color:#6B7280;text-transform:uppercase" > Upload Requested Files < / p >
< input
type = "file"
multiple
onChange = { ( event ) = > {
const files = event . currentTarget . files ;
setViewTicketFiles ( files ? Array . from ( files ) . map ( ( file ) = > file . name ) : [ ] ) ;
} }
style = "width:100%;font-size:12px;color:#374151"
/ >
< Show when = { viewTicketFiles ( ) . length } >
< div style = "display:flex;flex-wrap:wrap;gap:6px;margin-top:8px" >
< For each = { viewTicketFiles ( ) } > { ( name ) = > < span style = "height:24px;padding:0 8px;border-radius:999px;background:white;border:1px solid #E5E7EB;display:inline-flex;align-items:center;font-size:11px;color:#374151" > { name } < / span > } < / For >
< / div >
< / Show >
< / div >
< / div >
< / div >
< / div >
< / Show >
< / div >
< / Show >
< Show when = { active === 'create_ticket' } >
< div style = "border:1px solid #E5E7EB;background:white;border-radius:16px;padding:16px;box-shadow:0 1px 4px rgba(0,0,0,0.06)" >
< p style = "margin:0;font-size:24px;line-height:1.1;font-weight:800;color:#111827" > Create Ticket < / p >
< p style = "margin:6px 0 0;font-size:13px;color:#6B7280" > Create a support request with complete details for faster resolution . < / p >
< div style = "margin-top:12px;display:grid;grid-template-columns:1fr 1fr;gap:12px" >
< div > < p style = "margin:0 0 6px;font-size:11px;font-weight:700;color:#6B7280;text-transform:uppercase" > Subject < / p > < input type = "text" placeholder = "Enter ticket subject" style = "height:38px;width:100%;border:1px solid #D1D5DB;border-radius:10px;background:white;padding:0 10px;font-size:12px;color:#111827" / > < / div >
< div > < p style = "margin:0 0 6px;font-size:11px;font-weight:700;color:#6B7280;text-transform:uppercase" > Category < / p > < select style = "height:38px;width:100%;border:1px solid #D1D5DB;border-radius:10px;background:white;padding:0 10px;font-size:12px;color:#111827" > < option > Profile & Verification < / option > < option > Billing & Credits < / option > < option > Requirements < / option > < option > Other < / option > < / select > < / div >
< div > < p style = "margin:0 0 6px;font-size:11px;font-weight:700;color:#6B7280;text-transform:uppercase" > Priority < / p > < select style = "height:38px;width:100%;border:1px solid #D1D5DB;border-radius:10px;background:white;padding:0 10px;font-size:12px;color:#111827" > < option > Medium < / option > < option > High < / option > < option > Low < / option > < / select > < / div >
< div > < p style = "margin:0 0 6px;font-size:11px;font-weight:700;color:#6B7280;text-transform:uppercase" > Order / Requirement ID < / p > < input type = "text" placeholder = "Optional reference" style = "height:38px;width:100%;border:1px solid #D1D5DB;border-radius:10px;background:white;padding:0 10px;font-size:12px;color:#111827" / > < / div >
< div style = "grid-column:1 / -1" > < p style = "margin:0 0 6px;font-size:11px;font-weight:700;color:#6B7280;text-transform:uppercase" > Description < / p > < textarea placeholder = "Describe the issue in detail..." style = "min-height:120px;width:100%;border:1px solid #D1D5DB;border-radius:10px;background:white;padding:10px;font-size:12px;color:#111827;resize:vertical" / > < / div >
< div style = "grid-column:1 / -1" >
< p style = "margin:0 0 6px;font-size:11px;font-weight:700;color:#6B7280;text-transform:uppercase" > Attachment < / p >
< div style = "border:1px dashed #D1D5DB;border-radius:10px;background:#F9FAFB;padding:12px" >
< input
type = "file"
multiple
onChange = { ( event ) = > {
const files = event . currentTarget . files ;
setCreateTicketFiles ( files ? Array . from ( files ) . map ( ( file ) = > file . name ) : [ ] ) ;
} }
style = "width:100%;font-size:12px;color:#374151"
/ >
< Show when = { createTicketFiles ( ) . length } >
< div style = "display:flex;flex-wrap:wrap;gap:6px;margin-top:8px" >
< For each = { createTicketFiles ( ) } > { ( name ) = > < span style = "height:24px;padding:0 8px;border-radius:999px;background:white;border:1px solid #E5E7EB;display:inline-flex;align-items:center;font-size:11px;color:#374151" > { name } < / span > } < / For >
< / div >
< / Show >
< / div >
< / div >
< / div >
< div style = "display:flex;justify-content:flex-end;gap:8px;margin-top:12px" >
< button type = "button" style = "height:36px;border-radius:8px;border:1px solid #D1D5DB;background:white;color:#374151;padding:0 12px;font-size:12px;font-weight:700" > Cancel < / button >
< button type = "button" style = "height:36px;border-radius:8px;border:none;background:#0D0D2A;color:white;padding:0 14px;font-size:12px;font-weight:700" > Submit Ticket < / button >
< / div >
< / div >
< / Show >
< / div >
) ;
}
if ( customerKey ( ) === 'settings' ) {
if ( tab === 'privacy' ) {
return < div style = "border:1px solid #E5E7EB;background:white;border-radius:16px;padding:14px;box-shadow:0 1px 4px rgba(0,0,0,0.06)" > { [ 'Profile visibility' , 'Data sharing consent' , 'Session management' ] . map ( ( s ) = > < div style = "display:flex;justify-content:space-between;align-items:center;padding:8px;border:1px solid #E5E7EB;border-radius:8px;background:#F9FAFB;margin-top:8px" > < span style = "font-size:12px;color:#374151" > { s } < / span > < span style = "font-size:12px;color:#6B7280" > Enabled < / span > < / div > ) } < / div > ;
}
if ( tab === 'notifications' ) {
return < div style = "border:1px solid #E5E7EB;background:white;border-radius:16px;padding:14px;box-shadow:0 1px 4px rgba(0,0,0,0.06)" > { [ 'Response alerts' , 'Billing alerts' , 'Security alerts' ] . map ( ( s ) = > < div style = "display:flex;justify-content:space-between;align-items:center;padding:8px;border:1px solid #E5E7EB;border-radius:8px;background:#F9FAFB;margin-top:8px" > < span style = "font-size:12px;color:#374151" > { s } < / span > < span style = "font-size:12px;color:#6B7280" > On < / span > < / div > ) } < / div > ;
}
return < div style = "border:1px solid #E5E7EB;background:white;border-radius:16px;padding:14px;box-shadow:0 1px 4px rgba(0,0,0,0.06)" > { [ 'Change Password' , 'Two-factor authentication' , 'Login activity' ] . map ( ( s ) = > < div style = "padding:8px;border:1px solid #E5E7EB;border-radius:8px;background:#F9FAFB;font-size:12px;color:#374151;margin-top:8px" > { s } < / div > ) } < / div > ;
}
if ( customerKey ( ) === 'switch role' || customerKey ( ) === 'switch services' || customerKey ( ) === 'switch service' ) {
if ( tab === 'pending approvals' ) {
return < div style = "border:1px solid #E5E7EB;background:white;border-radius:16px;padding:14px;box-shadow:0 1px 4px rgba(0,0,0,0.06)" > { [ 'Professional - Under Review' , 'Company - Documents Pending' ] . map ( ( r ) = > < div style = "padding:8px;border:1px solid #E5E7EB;border-radius:8px;background:#F9FAFB;font-size:12px;color:#374151;margin-top:8px" > { r } < / div > ) } < / div > ;
}
if ( tab === 'onboarding' ) {
return < div style = "border:1px solid #E5E7EB;background:white;border-radius:16px;padding:14px;box-shadow:0 1px 4px rgba(0,0,0,0.06)" > { [ 'Complete profile docs' , 'Submit KYC proofs' , 'Wait for approval' ] . map ( ( r ) = > < div style = "padding:8px;border:1px solid #E5E7EB;border-radius:8px;background:#F9FAFB;font-size:12px;color:#374151;margin-top:8px" > { r } < / div > ) } < / div > ;
}
return (
< div style = "display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px" >
{ [ 'Service Seeker (Active)' , 'Professional' , 'Company' ] . map ( ( r , i ) = > < div style = { ` border:1px solid ${ i === 0 ? '#FF5E13' : '#E5E7EB' } ;background:white;border-radius:16px;padding:12px;box-shadow:0 1px 4px rgba(0,0,0,0.06) ` } > < p style = "margin:0;font-size:13px;font-weight:700;color:#111827" > { r } < / p > < button type = "button" style = "margin-top:10px;height:30px;border-radius:8px;border:none;background:#0D0D2A;color:white;padding:0 10px;font-size:12px;font-weight:700" > { i === 0 ? 'Current Service' : 'Switch' } < / button > < / div > ) }
< / div >
) ;
}
if ( customerKey ( ) === 'logout' ) {
if ( tab === 'cancel' ) {
return < div style = "max-width:420px;margin:0 auto;border:1px solid #E5E7EB;background:white;border-radius:16px;padding:14px;text-align:center;box-shadow:0 1px 4px rgba(0,0,0,0.06)" > < p style = "margin:0;font-size:18px;font-weight:800;color:#111827" > Logout Cancelled < / p > < p style = "margin:6px 0 0;font-size:13px;color:#6B7280" > Your session is still active . < / p > < / div > ;
}
return (
< div style = "max-width:420px;margin:0 auto;border:1px solid #E5E7EB;background:white;border-radius:16px;padding:14px;text-align:center;box-shadow:0 1px 4px rgba(0,0,0,0.06)" >
< p style = "margin:0;font-size:18px;font-weight:800;color:#111827" > Confirm Logout < / p >
< p style = "margin:6px 0 0;font-size:13px;color:#6B7280" > Are you sure you want to logout from the service seeker portal ? < / p >
< div style = "display:flex;justify-content:center;gap:8px;margin-top:12px" > < button type = "button" style = "height:32px;border-radius:8px;border:1px solid #E5E7EB;background:white;padding:0 12px;font-size:12px;font-weight:700;color:#374151" > Cancel < / button > < button type = "button" style = "height:32px;border-radius:8px;border:none;background:#FF5E13;color:white;padding:0 12px;font-size:12px;font-weight:700" > Logout < / button > < / div >
< / div >
) ;
}
if ( customerKey ( ) === 'my portfolio' ) {
if ( ! isProfessionalRoleKey ( props . roleKey || '' ) ) {
return (
< div style = "border:1px solid #E5E7EB;background:white;border-radius:16px;padding:16px;box-shadow:0 1px 4px rgba(0,0,0,0.06)" >
< p style = "margin:0;font-size:16px;font-weight:800;color:#111827" > My Portfolio is only for professionals < / p >
< p style = "margin:6px 0 0;font-size:13px;color:#6B7280" > Switch to a professional role to manage and preview portfolio content . < / p >
< / div >
) ;
}
return renderPortfolioContent ( ) ;
}
return (
< div style = "border:1px solid #E5E7EB;background:white;border-radius:16px;padding:14px;box-shadow:0 1px 4px rgba(0,0,0,0.06)" >
< p style = "margin:0;font-size:13px;font-weight:700;color:#111827" > Screen Preview < / p >
< p style = "margin:6px 0 0;font-size:13px;color:#6B7280" > Service seeker screen is selected and interactive . < / p >
< / div >
) ;
} ;
const portfolioTabIcon = ( tabLabel : string ) = > {
const key = normalizeTabKey ( tabLabel ) ;
if ( key === 'overview' ) return LayoutGrid ;
if ( key === 'about' ) return UserCircle2 ;
if ( key . includes ( 'services' ) ) return Coins ;
if ( key . includes ( 'portfolio' ) ) return Image ;
if ( key . includes ( 'experience' ) ) return BriefcaseBusiness ;
if ( key === 'testimonials' ) return Star ;
if ( key === 'faqs' ) return HelpCircle ;
return FileText ;
} ;
return (
< div style = "border:1px solid #E5E7EB;border-radius:12px;overflow:hidden;background:#fff" >
< Show when = { ! props . hidePreviewHeader } >
< div style = "padding:10px 14px;border-bottom:1px solid #E5E7EB;background:#F9FAFB;display:flex;justify-content:space-between;align-items:center" >
< p style = "margin:0;font-size:12px;font-weight:700;color:#374151" > Actual End - User Dashboard UI Preview < / p >
< div style = "display:flex;align-items:center;gap:8px" >
< Show when = { props . onOpenFullscreen } >
< button
type = "button"
onClick = { ( ) = > props . onOpenFullscreen ? . ( ) }
title = "Open Full Screen Preview"
aria - label = "Open Full Screen Preview"
style = "height:28px;width:28px;border-radius:7px;border:1px solid #E5E7EB;background:white;display:inline-flex;align-items:center;justify-content:center;cursor:pointer;color:#374151"
>
< svg width = "14" height = "14" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" >
< polyline points = "15 3 21 3 21 9" > < / polyline >
< polyline points = "9 21 3 21 3 15" > < / polyline >
< line x1 = "21" y1 = "3" x2 = "14" y2 = "10" > < / line >
< line x1 = "3" y1 = "21" x2 = "10" y2 = "14" > < / line >
< / svg >
< / button >
< / Show >
< StatusBadge status = { props . status } / >
< / div >
< / div >
< / Show >
< div style = "display:grid;grid-template-columns:220px 1fr;min-height:680px;background:#F3F4F6" >
< aside style = "display:flex;flex-direction:column;border-right:1px solid #E5E7EB;background:#fff" >
< div style = "height:64px;display:flex;align-items:center;border-bottom:1px solid #E5E7EB;padding:0 14px" >
< img src = "/nxtgauge-logo.png" alt = "Nxtgauge" style = "height:40px;object-fit:contain;max-width:170px" / >
< / div >
< div style = "padding:10px 8px;display:flex;flex-direction:column;gap:4px;overflow:auto" >
< For each = { previewSidebarItems ( ) } >
{ ( item ) = > (
< button
type = "button"
onClick = { ( ) = > props . onSidebarSelect ( item ) }
style = { ` display:flex;align-items:center;gap:9px;text-align:left;height:34px;border-radius:8px;padding:0 10px;border:none;cursor:pointer;font-size:12px;font-weight:600;background: ${ props . activeSidebar === item ? '#FFF3EE' : 'transparent' } ;color: ${ props . activeSidebar === item ? '#FF5E13' : '#6B7280' } ` }
>
{ ( ( ) = > {
const Icon = sidebarIcon ( item ) ;
return < Icon size = { 14 } style = { ` color: ${ props . activeSidebar === item ? '#FF5E13' : '#9CA3AF' } ;flex-shrink:0 ` } strokeWidth = { props . activeSidebar === item ? 2.5 : 2 } / > ;
} ) ( ) }
{ titleCase ( item ) }
< / button >
) }
< / For >
< / div >
< / aside >
< main style = "display:flex;flex-direction:column" >
< header style = "height:64px;background:#fff;border-bottom:1px solid #E5E7EB;display:flex;align-items:center;justify-content:space-between;padding:0 16px" >
< div style = "display:flex;align-items:center;gap:10px" >
< div style = "position:relative" >
< Search size = { 16 } style = "position:absolute;left:10px;top:50%;transform:translateY(-50%);color:#9CA3AF" / >
< input value = "" readOnly placeholder = "Search resources..." style = "height:36px;width:280px;border-radius:10px;border:1px solid #E5E7EB;background:#F9FAFB;padding:0 12px 0 32px;font-size:12px;color:#6B7280" / >
< / div >
< / div >
< div style = "display:flex;align-items:center;gap:8px" >
< button type = "button" style = "width:34px;height:34px;border-radius:8px;border:1px solid #E5E7EB;background:white;display:flex;align-items:center;justify-content:center;cursor:pointer;color:#6B7280" > < Bell size = { 16 } / > < / button >
< button type = "button" style = "width:34px;height:34px;border-radius:8px;border:1px solid #E5E7EB;background:white;display:flex;align-items:center;justify-content:center;cursor:pointer;color:#6B7280" > < Settings size = { 16 } / > < / button >
< button type = "button" style = "width:34px;height:34px;border-radius:8px;border:1px solid #E5E7EB;background:white;display:flex;align-items:center;justify-content:center;cursor:pointer;color:#6B7280" > < User size = { 16 } / > < / button >
< / div >
< / header >
< div style = "padding:14px" >
< Show when = { ! ( isCustomerExternalMode ( ) && ( customerKey ( ) === 'help center' || customerKey ( ) === 'support' ) ) } >
< div style = "border:1px solid #E5E7EB;background:white;border-radius:16px;padding:14px;box-shadow:0 1px 4px rgba(0,0,0,0.06)" >
< p style = "margin:0;font-size:12px;color:#6B7280" > Current View < / p >
< p style = "margin:4px 0 0;font-size:24px;font-weight:800;color:#111827" > { isCustomerExternalMode ( ) ? customerView ( ) . title : titleCase ( props . activeSidebar ) } < / p >
< p style = "margin:4px 0 0;font-size:13px;color:#6B7280" > { isCustomerExternalMode ( ) ? customerView ( ) . subtitle : 'Interactive preview for configured dashboard.' } < / p >
< Show when = { previewTabs ( ) . length > 0 && customerKey ( ) !== 'my portfolio' } >
< Show
when = { customerKey ( ) === 'my portfolio' }
fallback = {
< div style = "margin-top:12px;display:flex;align-items:center;gap:20px;border-bottom:1px solid #E5E7EB" >
< For each = { previewTabs ( ) } >
{ ( item ) = > (
( ( ) = > {
const itemKey = normalizeTabKey ( item ) ;
const isLockedTestimonialsTab = customerKey ( ) === 'my portfolio' && itemKey === 'testimonials' && ! portfolioTestimonialsUnlocked ( ) ;
return (
< button
type = "button"
disabled = { isLockedTestimonialsTab }
onClick = { ( ) = > {
if ( isLockedTestimonialsTab ) return ;
props . onTabSelect ( item ) ;
} }
title = { isLockedTestimonialsTab ? 'Unlock after 3 completed jobs and 2 customer feedback entries' : '' }
style = { ` padding-bottom:10px;font-size:13px;font-weight:500;background:none;border:none;cursor: ${ isLockedTestimonialsTab ? 'not-allowed' : 'pointer' } ;opacity: ${ isLockedTestimonialsTab ? 0.5 : 1 } ; ${ resolvedTabKey ( ) === itemKey ? 'color:#FF5E13;border-bottom:2px solid #FF5E13;margin-bottom:-1px' : 'color:#6B7280' } ` }
>
{ titleCase ( item ) } { isLockedTestimonialsTab ? '• Locked' : '' }
< / button >
) ;
} ) ( )
) }
< / For >
< / div >
}
>
< div style = "margin-top:12px;display:flex;align-items:center;gap:8px;overflow-x:auto;padding-bottom:2px" >
< For each = { previewTabs ( ) } >
{ ( item ) = > {
const itemKey = normalizeTabKey ( item ) ;
const isLockedTestimonialsTab = itemKey === 'testimonials' && ! portfolioTestimonialsUnlocked ( ) ;
const isActive = resolvedTabKey ( ) === itemKey ;
const Icon = portfolioTabIcon ( item ) ;
return (
< button
type = "button"
disabled = { isLockedTestimonialsTab }
onClick = { ( ) = > {
if ( isLockedTestimonialsTab ) return ;
props . onTabSelect ( item ) ;
} }
title = { isLockedTestimonialsTab ? 'Unlock after 3 completed jobs and 2 customer feedback entries' : '' }
style = { ` min-width:148px;height:40px;border-radius:10px;border:1px solid ${ isActive ? '#FFD8C2' : '#E5E7EB' } ;background: ${ isActive ? '#FFF8F4' : 'white' } ;padding:0 10px;display:flex;align-items:center;gap:8px;cursor: ${ isLockedTestimonialsTab ? 'not-allowed' : 'pointer' } ;opacity: ${ isLockedTestimonialsTab ? 0.5 : 1 } ;flex-shrink:0 ` }
>
< span style = { ` width:22px;height:22px;border-radius:7px;display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;border:1px solid ${ isActive ? '#FFD8C2' : '#E5E7EB' } ;background: ${ isActive ? '#FFF1EB' : '#F9FAFB' } ` } >
< Icon size = { 12 } style = { ` color: ${ isActive ? '#C2410C' : '#6B7280' } ` } / >
< / span >
< span style = { ` font-size:12px;font-weight:700;line-height:1.2;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color: ${ isActive ? '#111827' : '#374151' } ` } >
{ titleCase ( item ) } { isLockedTestimonialsTab ? ' · Locked' : '' }
< / span >
< / button >
) ;
} }
< / For >
< / div >
< / Show >
< / Show >
< / div >
< / Show >
< Show when = { isCustomerExternalMode ( ) } fallback = { < div style = "margin-top:10px;border:1px solid #E5E7EB;background:white;border-radius:16px;padding:14px;box-shadow:0 1px 4px rgba(0,0,0,0.06)" > < p style = "margin:0;font-size:13px;color:#374151" > Default preview mode . < / p > < / div > } >
< div style = "margin-top:10px" > { renderCustomerContent ( ) } < / div >
< / Show >
< Show when = { ! isCustomerExternalMode ( ) } >
< div style = "border:1px solid #E5E7EB;background:white;border-radius:16px;padding:14px;margin-top:10px;box-shadow:0 1px 4px rgba(0,0,0,0.06)" >
< p style = "margin:0;font-size:11px;letter-spacing:0.05em;text-transform:uppercase;color:#6B7280" > Fields View < / p >
< div style = "display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;margin-top:8px" >
< For each = { previewFields ( ) } >
{ ( f ) = > < div style = "padding:8px;border:1px solid #E5E7EB;border-radius:8px;background:#F9FAFB;font-size:12px;color:#374151" > { titleCase ( f ) } < / div > }
< / For >
< / div >
< / div >
< / Show >
< / div >
< / main >
< / div >
< / div >
) ;
}