From ad8a17a766325ae97e5a1376aaa3f497c8f3d4f8 Mon Sep 17 00:00:00 2001 From: Ashwin Kumar Date: Mon, 30 Mar 2026 21:43:49 +0200 Subject: [PATCH] Refine external preview flows and simplify verification/approval management --- scripts/admin-3000-daemon.sh | 24 - .../admin/DashboardDesignPreview.tsx | 1797 ++++++++++++++++- src/routes/admin/approval.tsx | 187 +- .../external-dashboard-management/index.tsx | 74 +- src/routes/admin/verification.tsx | 686 +++++-- 5 files changed, 2316 insertions(+), 452 deletions(-) delete mode 100755 scripts/admin-3000-daemon.sh diff --git a/scripts/admin-3000-daemon.sh b/scripts/admin-3000-daemon.sh deleted file mode 100755 index f022e83..0000000 --- a/scripts/admin-3000-daemon.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env bash -set -u - -ROOT_DIR="/Users/ashwin/workspace/nxtgauge-admin-solid" -APP_LOG="/tmp/nxtgauge-admin-3000.log" - -cd "$ROOT_DIR" || exit 1 - -echo "[$(date '+%Y-%m-%d %H:%M:%S')] admin-3000 daemon started" >> "$APP_LOG" - -while true; do - if [[ ! -f ".output/server/index.mjs" ]]; then - echo "[$(date '+%Y-%m-%d %H:%M:%S')] build output missing, running build..." >> "$APP_LOG" - npm run build >> "$APP_LOG" 2>&1 - fi - - echo "[$(date '+%Y-%m-%d %H:%M:%S')] launching admin on 0.0.0.0:3000" >> "$APP_LOG" - HOST=0.0.0.0 PORT=3000 node .output/server/index.mjs >> "$APP_LOG" 2>&1 - code=$? - - echo "[$(date '+%Y-%m-%d %H:%M:%S')] admin exited with code ${code}, restarting in 2s" >> "$APP_LOG" - sleep 2 -done - diff --git a/src/components/admin/DashboardDesignPreview.tsx b/src/components/admin/DashboardDesignPreview.tsx index 045b8a9..bbad7d6 100644 --- a/src/components/admin/DashboardDesignPreview.tsx +++ b/src/components/admin/DashboardDesignPreview.tsx @@ -525,7 +525,13 @@ const PORTFOLIO_TESTIMONIALS = [ function customerViewFor(sidebar: string, roleKey: string): CustomerView { const key = String(sidebar || '').toLowerCase().trim(); + const role = normalizeRoleKey(roleKey); 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' }; + if (key === 'leads') return { title: 'Leads', subtitle: 'Browse marketplace requirements, request contact access, and track conversion status.', tabs: ['marketplace', 'requested contacts'], 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') 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'] }; @@ -535,7 +541,7 @@ function customerViewFor(sidebar: string, roleKey: string): CustomerView { } 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' }; + 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: 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: [] }; @@ -548,12 +554,152 @@ function customerViewFor(sidebar: string, roleKey: string): CustomerView { } const REQUIREMENT_ROWS = [ - { title: 'Annual HVAC Maintenance Contract', status: 'active', responses: 12, amount: '₹9,96,000' }, - { title: 'Office Interior Redesign Phase 2', status: 'draft', responses: 0, amount: '₹49,80,000' }, - { title: 'Cloud Security Audit', status: 'closed', responses: 4, amount: '₹7,05,500' }, - { title: 'Social Media Management - 6 Months', status: 'active', responses: 28, amount: '₹2,07,500' }, + { + 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: '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 = { + 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'], + }, + 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' }, @@ -561,6 +707,65 @@ const RESPONSE_ROWS = [ { 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', 'Area Based', 'Type Based', 'Expiring Soon', 'Requested Leads']; + +const INITIAL_PRO_LEAD_CARDS = [ + { + id: 'LD-29831', + title: 'Luxury Destination Wedding - Lake Como', + category: 'Wedding Photography', + location: 'Italy / International', + dateRequired: 'Oct 14, 2024', + urgency: 'High / Immediate', + budget: '$12,500', + cost: 25, + status: 'open' as 'open' | 'requested' | 'unlocked', + match: '98% match', + }, + { + id: 'LD-29745', + title: 'Editorial Fashion Shoot - Avant-Garde Series', + category: 'Fashion / Editorial', + location: 'London Studio', + dateRequired: 'Sep 28, 2024', + urgency: 'Medium', + budget: '$4,800', + cost: 25, + status: 'requested' as 'open' | 'requested' | 'unlocked', + match: '92% match', + }, + { + id: 'LD-29612', + title: 'Corporate Branding - Tech HQ Reimagined', + category: 'Commercial Architecture', + location: 'San Francisco, CA', + dateRequired: 'Nov 05, 2024', + urgency: 'Low / Flexible', + budget: '$8,200', + cost: 25, + status: 'unlocked' as 'open' | 'requested' | 'unlocked', + match: '85% match', + }, +]; + +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' }, +]; + +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' }, @@ -686,6 +891,7 @@ export default function DashboardDesignPreview(props: { 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; @@ -699,6 +905,9 @@ export default function DashboardDesignPreview(props: { 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 : []; if (!roles.length) return []; @@ -731,6 +940,25 @@ export default function DashboardDesignPreview(props: { const [profileSettingsTab, setProfileSettingsTab] = createSignal<'change_password' | 'notifications' | 'privacy'>('change_password'); const [showDeleteAccountModal, setShowDeleteAccountModal] = createSignal(false); const [showPortfolioPreview, setShowPortfolioPreview] = createSignal(false); + const [portfolioEditMode, setPortfolioEditMode] = createSignal(false); + const [requirementsView, setRequirementsView] = createSignal<'list' | 'new' | 'detail'>('list'); + const [requirementsStep, setRequirementsStep] = createSignal(1); + 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 [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 [leadCredits, setLeadCredits] = createSignal(250); + const [leadRequestRows, setLeadRequestRows] = createSignal([ + { id: 'LD-29745', title: 'Editorial Fashion Shoot - Avant-Garde Series', city: 'London, UK', requestDate: 'Oct 24, 2023', status: 'request_sent', decisionDate: '--' }, + { id: 'LD-29612', title: 'Corporate Branding - Tech HQ Reimagined', city: 'San Francisco, CA', requestDate: 'Oct 23, 2023', status: 'contact_unlocked', decisionDate: 'Oct 23, 2023' }, + { id: 'LD-29588', title: 'Family Outdoor Session', city: 'Rome, Italy', requestDate: 'Oct 19, 2023', status: 'rejected', decisionDate: 'Oct 19, 2023' }, + ] 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>({ email_updates: true, @@ -744,6 +972,10 @@ export default function DashboardDesignPreview(props: { }; 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 lockedLeadCredits = createMemo(() => leadCards().filter((card) => card.status === 'requested').length * leadCostPerContact); + const usableLeadCredits = createMemo(() => Math.max(0, leadCredits() - lockedLeadCredits())); + const maxLeadRequests = createMemo(() => Math.floor(usableLeadCredits() / leadCostPerContact)); const ticketSummary = createMemo(() => { const openCount = HELP_TICKET_ROWS.filter((row) => row.status !== 'Resolved').length; const resolvedCount = HELP_TICKET_ROWS.length - openCount; @@ -774,6 +1006,43 @@ export default function DashboardDesignPreview(props: { 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'); + } + }); + + createEffect(() => { + if (customerKey() !== 'jobs') { + setJobPostView('form'); + setJobPostStep(1); + setJobSeekerScreen('list'); + setJobSeekerSelectedId(JOB_SEEKER_OPEN_JOBS[0]?.id || ''); + setJobSeekerApplyStep(2); + setLastJobSeekerTabKey(''); + } + }); + + createEffect(() => { + if (customerKey() !== 'my portfolio') setPortfolioEditMode(false); + }); + + createEffect(() => { + if (customerKey() !== 'jobs' || !isJobSeekerRole()) return; + const tabKey = normalizeTabKey(resolvedTabKey()); + if (tabKey === lastJobSeekerTabKey()) return; + setLastJobSeekerTabKey(tabKey); + setJobSeekerScreen('list'); + setJobSeekerSelectedId(JOB_SEEKER_OPEN_JOBS[0]?.id || ''); + }); + createEffect(() => { if (helpCenterTab() !== 'my_tickets') { setMyTicketsTab('all_tickets'); @@ -808,8 +1077,79 @@ export default function DashboardDesignPreview(props: { return { bg: '#F3F4F6', c: '#6B7280' }; }; + const requestLeadContact = (leadId: string) => { + if (usableLeadCredits() < leadCostPerContact) return; + let changed = false; + setLeadCards((prev) => prev.map((card) => { + if (card.id !== leadId || card.status !== 'open') return card; + changed = true; + return { ...card, status: 'requested' as const }; + })); + 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?.location || 'Remote', + requestDate: 'Oct 24, 2023', + status: 'request_sent' as const, + decisionDate: '--', + }, + ...prev, + ]; + }); + }; + + 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: 'Oct 25, 2023' } + : row)); + }; + + const cancelLeadRequest = (leadId: string) => { + setLeadCards((prev) => prev.map((card) => card.id === leadId && card.status === 'requested' + ? { ...card, status: 'open' as const } + : card)); + setLeadCredits((prev) => Math.max(0, prev - leadCostPerContact)); + setLeadRequestRows((prev) => prev.map((row) => row.id === leadId + ? { ...row, status: 'cancelled_by_professional' as const, decisionDate: 'Oct 25, 2023' } + : row)); + }; + + const refundPendingLead = (leadId: string) => { + setLeadCards((prev) => prev.map((card) => card.id === leadId && card.status === 'requested' + ? { ...card, status: 'open' as const } + : card)); + setLeadRequestRows((prev) => prev.map((row) => row.id === leadId + ? { ...row, status: 'expired_refunded' as const, decisionDate: 'Auto-refunded' } + : row)); + }; + const renderPortfolioContent = () => { const spec = portfolioSpecForRole(props.roleKey || ''); + const selectedPortfolioTab = spec.tabs.find((item) => normalizeTabKey(item) === normalizeTabKey(resolvedTabKey())) || spec.tabs[0] || 'overview'; + 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 showSection = (tabKey: string) => selectedPortfolioTabKey === normalizeTabKey('overview') || selectedPortfolioTabKey === tabKey; return (
@@ -817,23 +1157,35 @@ export default function DashboardDesignPreview(props: { {/* Profile Header */}
-
-
- +
+
+

Alex Morgan

+ Verified
-
-
-

Alex Morgan

- Verified -
-

{spec.roleLabel} · Mumbai, Maharashtra

+

{spec.roleLabel}

+
+ Area: Mumbai Region + Place: Andheri East, Mumbai + Travel: Mumbai, Navi Mumbai, Thane, Pune
- +
+ +
+

Portfolio edit mode is active. Update sections and save changes.

+ +
+
{spec.statsLabels.map((label, i) => (
@@ -844,14 +1196,23 @@ export default function DashboardDesignPreview(props: {
- {/* About + Specialties */} -
+ + {/* 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.

+ 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.

} + > +