From 8a24700e73b05dba052a3eef2bc1b43f2cf082a3 Mon Sep 17 00:00:00 2001 From: Ashwin Kumar Date: Mon, 30 Mar 2026 14:19:00 +0200 Subject: [PATCH] feat(dashboard): add My Portfolio to all professional role dashboards MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add full single-page portfolio preview in DashboardDesignPreview with profile header, about, packages, gallery, experience, testimonials, and FAQs sections matching Department Management design language - Fix sidebar persona resolution to always include My Portfolio for all professional subtypes (Photographer, Graphic Designer, Makeup Artist, Tutor, Developer, Video Editor, Social Media Manager, Fitness Trainer, Catering Services) via a three-layer fallback: roleId lookup → role key from roles list → formRoleKey stored directly from dashboard record - Add personaFromKey() helper so any professional role key maps to PROFESSIONAL sidebar even when the roles API returns empty data Co-Authored-By: Claude Sonnet 4.6 --- .../admin/DashboardDesignPreview.tsx | 552 +++++++++++++++++- .../external-dashboard-management/index.tsx | 37 +- 2 files changed, 575 insertions(+), 14 deletions(-) diff --git a/src/components/admin/DashboardDesignPreview.tsx b/src/components/admin/DashboardDesignPreview.tsx index 76ae098..045b8a9 100644 --- a/src/components/admin/DashboardDesignPreview.tsx +++ b/src/components/admin/DashboardDesignPreview.tsx @@ -1,21 +1,29 @@ import { For, Show, createEffect, createMemo, createSignal } from 'solid-js'; import { + Award, Bell, BadgeCheck, Bookmark, BriefcaseBusiness, Camera, + CheckCircle2, + ChevronDown, + ChevronUp, Clapperboard, Compass, Coins, Code2, Dumbbell, + Edit2, + Eye, FileText, GraduationCap, HandHelping, HeadphonesIcon, + Image, LayoutGrid, LogOut, + MapPin, Megaphone, PenTool, RefreshCw, @@ -25,11 +33,13 @@ import { Settings, Settings2, ShieldCheck, + Star, TrendingUp, UtensilsCrossed, User, UserCircle2, Users, + X, } from 'lucide-solid'; function titleCase(value: string) { @@ -52,6 +62,7 @@ function StatusBadge(props: { status: 'ACTIVE' | 'INACTIVE' }) { 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; @@ -274,6 +285,244 @@ function profileSpecForRole(roleKey: string): ProfileSpec { return PROFILE_SPECS[normalized] || PROFILE_SPECS.PROFESSIONAL; } +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 = { + PHOTOGRAPHER: { + roleLabel: 'Photographer', + tabs: ['overview', '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: ['overview', '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: ['overview', '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: ['overview', '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: ['overview', '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'] }, + ], + }, + GRAPHIC_DESIGNER: { + roleLabel: 'Graphic Designer', + tabs: ['overview', '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: ['overview', '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: ['overview', '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: ['overview', '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: ['overview', '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; +} + +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(); if (key === 'my dashboard') return { title: 'Customer Dashboard Overview', subtitle: 'Manage your enterprise requirements and track professional responses in real-time.', tabs: ['overview', 'recent requirements', 'quick actions'], cta: 'Post New Requirement' }; @@ -284,6 +533,10 @@ function customerViewFor(sidebar: string, roleKey: string): CustomerView { const spec = profileSpecForRole(roleKey); return { title: spec.title, subtitle: spec.subtitle, tabs: spec.tabs, cta: 'Save Changes' }; } + if (key === 'my portfolio') { + const spec = portfolioSpecForRole(roleKey); + return { title: `${spec.roleLabel} Portfolio`, subtitle: 'Manage your public portfolio, showcase your work, and control what customers see before they accept your contact request.', 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'] }; @@ -438,6 +691,11 @@ export default function DashboardDesignPreview(props: { 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()); @@ -472,6 +730,7 @@ export default function DashboardDesignPreview(props: { const [viewTicketFiles, setViewTicketFiles] = createSignal([]); const [profileSettingsTab, setProfileSettingsTab] = createSignal<'change_password' | 'notifications' | 'privacy'>('change_password'); const [showDeleteAccountModal, setShowDeleteAccountModal] = createSignal(false); + const [showPortfolioPreview, setShowPortfolioPreview] = createSignal(false); const [lastSidebarKey, setLastSidebarKey] = createSignal(''); const [profileSettingToggles, setProfileSettingToggles] = createSignal>({ email_updates: true, @@ -498,11 +757,6 @@ export default function DashboardDesignPreview(props: { setViewTicketFiles([]); }; - createEffect(() => { - const tabs = previewTabs(); - const hasMatch = tabs.some((item) => normalizeTabKey(item) === activeTabKey()); - if (!hasMatch && tabs[0]) props.onTabSelect(tabs[0]); - }); createEffect(() => { const key = customerKey(); @@ -554,8 +808,199 @@ export default function DashboardDesignPreview(props: { return { bg: '#F3F4F6', c: '#6B7280' }; }; + const renderPortfolioContent = () => { + const spec = portfolioSpecForRole(props.roleKey || ''); + + return ( +
+ + {/* Profile Header */} +
+
+
+
+ +
+
+
+

Alex Morgan

+ Verified +
+

{spec.roleLabel} · Mumbai, Maharashtra

+
+
+
+ + +
+
+
+ {spec.statsLabels.map((label, i) => ( +
+

{label}

+

{i===0?'248':i===1?'7+':i===2?'Verified':i===3?'2 days':'Yes'}

+
+ ))} +
+
+ + {/* About + Specialties */} +
+
+
+

About

+
+
+

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.

+
+
+ {[['7+','Years Exp'],['248','Projects'],['4.9','Rating'],['128','Reviews']].map(([val,lbl], i) => ( +
+

{lbl}

+

{val}

+
+ ))} +
+
+
+
+

Specialties

+
+
+ {spec.specialties.map((s) => ( + {s} + ))} +
+
+

Languages

+
+ {['English','Hindi','Marathi'].map((l) => ( + {l} + ))} +
+

Service Areas

+
+ {['Mumbai','Pune','Thane','Nashik'].map((c) => ( + {c} + ))} +
+
+
+
+ + {/* Packages */} +
+
+

{titleCase(spec.serviceTabLabel)}

+
+
+ {spec.packages.map((pkg, i) => ( +
+

{pkg.name}

+

{pkg.price}

+
+ {pkg.items.map((item) => ( +
+ {item} +
+ ))} +
+
+ ))} +
+
+ + {/* Work Gallery */} +
+
+

{titleCase(spec.galleryTabLabel)}

+ +
+
+ {[0,1,2,3,4,5,6,7].map((i) => ( +
+ + {spec.specialties[i % spec.specialties.length]} +
+ ))} +
+
+ + {/* Experience */} +
+
+

{titleCase(spec.experienceTabLabel)}

+
+
+

{spec.equipmentLabel}

+
+ {spec.specialties.map((item) => ( + {item} + ))} +
+
+
+ {[['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) => ( +
+

{yr}

+

{desc}

+
+ ))} +
+
+ + {/* Testimonials */} +
+
+

Testimonials

+ 4.9 · 128 reviews +
+
+ {PORTFOLIO_TESTIMONIALS.map((t, i) => ( +
+
+

{t.name}

+
+ {Array.from({length: t.rating}).map(() => )} +
+
+

{t.category}

+

{t.text}

+
+ ))} +
+
+ + {/* FAQs */} +
+
+

Frequently Asked Questions

+
+ {(faq, i) => ( +
+ + +
+

{faq.a}

+
+
+
+ )}
+
+ +
+ ); + }; + const renderCustomerContent = () => { - const tab = props.activeTab.toLowerCase(); + const tab = resolvedTabKey(); if (customerKey() === 'my dashboard') { if (tab === 'recent requirements') { @@ -1942,6 +2387,8 @@ export default function DashboardDesignPreview(props: { ); } + if (customerKey() === 'my portfolio') return renderPortfolioContent(); + return (

Screen Preview

@@ -2009,7 +2456,7 @@ export default function DashboardDesignPreview(props: { @@ -2037,6 +2484,97 @@ export default function DashboardDesignPreview(props: {
+ + +
setShowPortfolioPreview(false)} + > +
e.stopPropagation()} + > +
+
+ Customer View +

What customers see before accepting your contact request

+
+ +
+
+
+
+ +
+
+
+

Alex Morgan

+ + Verified + +
+

{portfolioSpecForRole(props.roleKey||'').roleLabel}

+
+ + Mumbai, Maharashtra +
+
+
+
+ {[1,2,3,4,5].map(() => )} + 4.9 + (128 reviews) +
+

+ Passionate {portfolioSpecForRole(props.roleKey||'').roleLabel.toLowerCase()} with 7+ years of experience delivering exceptional results. Specialising in {portfolioSpecForRole(props.roleKey||'').specialties.slice(0,3).join(', ')}. +

+
+ {[['248','Projects'],['7+','Yrs Exp'],['4.9','Rating'],['128','Reviews']].map(([val,lbl]) => ( +
+

{val}

+

{lbl}

+
+ ))} +
+
+

Specialties

+
+ {portfolioSpecForRole(props.roleKey||'').specialties.map((s) => ( + {s} + ))} +
+
+
+

Featured Package

+ {(() => { + const pkg = portfolioSpecForRole(props.roleKey||'').packages[1]; + return ( +
+
+

{pkg.name}

+ {pkg.price} +
+
+ {pkg.items.map((item) => ( + + {item} + + ))} +
+
+ ); + })()} +
+
+ + +
+
+
+
+
); } diff --git a/src/routes/admin/external-dashboard-management/index.tsx b/src/routes/admin/external-dashboard-management/index.tsx index e63dc61..6c252be 100644 --- a/src/routes/admin/external-dashboard-management/index.tsx +++ b/src/routes/admin/external-dashboard-management/index.tsx @@ -27,6 +27,7 @@ const ROLE_BASED_SIDEBAR: Record<'PROFESSIONAL' | 'COMPANY' | 'JOB_SEEKER' | 'CU PROFESSIONAL: [ 'My Dashboard', 'My Profile', + 'My Portfolio', 'Leads', 'My Responses', 'Credits', @@ -182,6 +183,7 @@ export default function ExternalDashboardManagementPage() { const [name, setName] = createSignal(''); const [code, setCode] = createSignal(''); const [roleId, setRoleId] = createSignal(''); + const [formRoleKey, setFormRoleKey] = createSignal(''); const [widgets, setWidgets] = createSignal([]); const [tabs, setTabs] = createSignal([]); const [sidebarItems, setSidebarItems] = createSignal([]); @@ -204,6 +206,15 @@ export default function ExternalDashboardManagementPage() { return map; }); + const personaFromKey = (key: string): 'PROFESSIONAL' | 'COMPANY' | 'JOB_SEEKER' | 'CUSTOMER' | null => { + const k = String(key || '').toUpperCase(); + if (!k) return null; + if (k.includes('COMPANY')) return 'COMPANY'; + if (k.includes('CUSTOMER')) return 'CUSTOMER'; + if (k.includes('JOB_SEEKER') || k.includes('JOBSEEKER')) return 'JOB_SEEKER'; + return 'PROFESSIONAL'; // photographer, makeup, tutor, developer, video, graphic, social, fitness, catering, etc. + }; + const sidebarLooksCustomer = createMemo(() => { const joined = sidebarItems().join(' ').toLowerCase(); return joined.includes('my requirements') @@ -211,19 +222,24 @@ export default function ExternalDashboardManagementPage() { || joined.includes('shortlisted responses'); }); + const selectedRoleKey = createMemo(() => { + const selected = roles().find((r) => r.id === roleId()); + return selected?.key || ''; + }); + const previewSidebarItems = createMemo(() => { const selectedRoleId = roleId(); - const persona = rolePersonaById()[selectedRoleId]; + // Primary: look up persona by role ID (from loaded roles list) + const personaById = rolePersonaById()[selectedRoleId]; + // Fallback: derive persona directly from the stored role key (works when roles API is empty or roleId unmatched) + const personaByKey = personaFromKey(selectedRoleKey() || formRoleKey()); + const persona = personaById ?? personaByKey; if (persona && ROLE_BASED_SIDEBAR[persona]?.length) return ROLE_BASED_SIDEBAR[persona]; if (sidebarLooksCustomer()) return ROLE_BASED_SIDEBAR.CUSTOMER; if (sidebarItems().length) return sidebarItems(); return ['My Dashboard', 'My Profile', 'Switch Services', 'Logout']; }); const previewTabs = createMemo(() => (tabs().length ? tabs() : ['overview'])); - const selectedRoleKey = createMemo(() => { - const selected = roles().find((r) => r.id === roleId()); - return selected?.key || ''; - }); const authHeaders = () => { const token = typeof sessionStorage !== 'undefined' ? sessionStorage.getItem('nxtgauge_admin_access_token') || '' : ''; @@ -326,7 +342,9 @@ export default function ExternalDashboardManagementPage() { setFormTab('preview'); setName(''); setCode(''); - setRoleId(roles()[0]?.id || ''); + const firstRole = roles()[0]; + setRoleId(firstRole?.id || ''); + setFormRoleKey(firstRole?.key || ''); setWidgets([]); setTabs([]); setSidebarItems([]); @@ -345,6 +363,7 @@ export default function ExternalDashboardManagementPage() { setName(row.name); setCode(row.code); setRoleId(row.roleId); + setFormRoleKey(row.roleKey); setWidgets(row.widgets); setTabs(row.tabs); setSidebarItems(row.sidebarItems); @@ -360,6 +379,9 @@ export default function ExternalDashboardManagementPage() { createEffect(() => { const selected = roleId(); if (!selected || view() !== 'form' || editingId()) return; + // Sync formRoleKey whenever roleId changes (so persona fallback stays fresh) + const matchedRole = roles().find((r) => r.id === selected); + if (matchedRole) setFormRoleKey(matchedRole.key); applySidebarPresetForRole(selected, false); applyPreviewPathForRole(selected, false); }); @@ -370,7 +392,8 @@ export default function ExternalDashboardManagementPage() { }); createEffect(() => { - const list = previewTabs(); + const list = tabs(); // use configured tabs only — not the fallback ['overview'] + if (!list.length) return; // sidebar-driven tabs (profile, portfolio, etc.) manage validation internally const active = normalizeToken(activePreviewTab()); if (!list.some((item) => normalizeToken(item) === active)) setActivePreviewTab(list[0] || ''); });