diff --git a/frontend-solid.dev.log b/frontend-solid.dev.log new file mode 100644 index 0000000..e69de29 diff --git a/frontend-solid.dev.pid b/frontend-solid.dev.pid new file mode 100644 index 0000000..3813b54 --- /dev/null +++ b/frontend-solid.dev.pid @@ -0,0 +1 @@ +7995 diff --git a/src/components/dashboard/CustomerRequirementsPage.tsx b/src/components/dashboard/CustomerRequirementsPage.tsx index 7def79c..0891cad 100644 --- a/src/components/dashboard/CustomerRequirementsPage.tsx +++ b/src/components/dashboard/CustomerRequirementsPage.tsx @@ -16,10 +16,9 @@ type RequirementItem = { title: string; description?: string | null; status?: string; - budget_min?: number | null; - budget_max?: number | null; + budget_inr?: number | null; area?: string | null; - city?: string | null; + location?: string | null; created_at?: string; }; @@ -44,7 +43,7 @@ export default function CustomerRequirementsPage() { budget_min: "", budget_max: "", area: "", - city: "", + location: "", }); const loadRequirements = async () => { @@ -79,10 +78,12 @@ export default function CustomerRequirementsPage() { const payload = { title: form().title.trim(), description: form().description.trim() || undefined, - budget_min: form().budget_min ? Number(form().budget_min) : undefined, - budget_max: form().budget_max ? Number(form().budget_max) : undefined, + budget_inr: + form().budget_min || form().budget_max + ? Number(form().budget_min) || Number(form().budget_max) + : undefined, area: form().area.trim() || undefined, - city: form().city.trim() || undefined, + location: form().city.trim() || undefined, }; const res = await apiFetch("/api/customers/requirements", { method: "POST", @@ -94,7 +95,14 @@ export default function CustomerRequirementsPage() { return; } setMsg("Requirement created."); - setForm({ title: "", description: "", budget_min: "", budget_max: "", area: "", city: "" }); + setForm({ + title: "", + description: "", + budget_min: "", + budget_max: "", + area: "", + location: "", + }); await loadRequirements(); } catch { setErr("Network error while creating requirement."); @@ -193,10 +201,10 @@ export default function CustomerRequirementsPage() { />
- + setField("city", e.currentTarget.value)} + value={form().location} + onInput={(e) => setField("location", e.currentTarget.value)} style={INPUT} placeholder="Chennai" /> @@ -303,7 +311,7 @@ export default function CustomerRequirementsPage() { {row.title}

- {row.city || "—"} {row.area ? `• ${row.area}` : ""}{" "} + {row.location || row.city || "—"} {row.area ? `• ${row.area}` : ""}{" "} {row.created_at ? `• ${new Date(row.created_at).toLocaleString("en-IN")}` : ""} diff --git a/src/components/dashboard/PortfolioPage.tsx b/src/components/dashboard/PortfolioPage.tsx index cca9567..d15c691 100644 --- a/src/components/dashboard/PortfolioPage.tsx +++ b/src/components/dashboard/PortfolioPage.tsx @@ -57,6 +57,26 @@ const EMPTY_JOB_SEEKER_FORM: JobSeekerPortfolioState = { skills: '', }; const JOB_SEEKER_FALLBACK_TABS = ['About', 'Education', 'Work Experience', 'Skills']; +const PROFESSIONAL_PORTFOLIO_TABS: Record = { + DEVELOPER: ['About', 'Services & Pricing', 'Projects', 'Tech Stack & Experience', 'Testimonials', 'FAQs'], + default: ['About', 'Services & Pricing', 'Projects', 'Experience', 'Testimonials', 'FAQs'], +}; + +type ProfessionalPortfolioState = { + about: string; + services: string; + experience: string; + testimonials: string; + faqs: string; +}; + +const EMPTY_PROFESSIONAL_FORM: ProfessionalPortfolioState = { + about: '', + services: '', + experience: '', + testimonials: '', + faqs: '', +}; // ── Helpers ─────────────────────────────────────────────────────────────────── @@ -95,6 +115,9 @@ export default function PortfolioPage(props: Props) { const [jobSeekerSaving, setJobSeekerSaving] = createSignal(false); const [jobSeekerMsg, setJobSeekerMsg] = createSignal(''); const [jobSeekerErr, setJobSeekerErr] = createSignal(''); + const [professionalTab, setProfessionalTab] = createSignal('About'); + const [professionalForm, setProfessionalForm] = createSignal({ ...EMPTY_PROFESSIONAL_FORM }); + const [professionalMsg, setProfessionalMsg] = createSignal(''); const loadItems = async () => { if (!isProfessional()) { setLoading(false); return; } @@ -196,6 +219,54 @@ export default function PortfolioPage(props: Props) { return grouped; }; + const professionalTabs = () => { + const runtimeRaw = Array.isArray(props.runtimeTabs) ? props.runtimeTabs : []; + const fromRuntime = runtimeRaw + .map((tab) => toLabel(tab)) + .filter(Boolean) + .map((tab) => { + const t = normalizeToken(tab); + if (t.includes('about') || t.includes('overview') || t.includes('profile')) return 'About'; + if (t.includes('service') || t.includes('pricing') || t.includes('package')) return 'Services & Pricing'; + if (t.includes('project') || t.includes('portfolio') || t.includes('gallery') || t.includes('showreel')) return 'Projects'; + if (t.includes('stack') || t.includes('experience') || t.includes('qualification') || t.includes('tool')) return props.roleKey === 'DEVELOPER' ? 'Tech Stack & Experience' : 'Experience'; + if (t.includes('testimonial') || t.includes('review')) return 'Testimonials'; + if (t.includes('faq') || t.includes('question')) return 'FAQs'; + return ''; + }) + .filter(Boolean); + const uniqueRuntime = Array.from(new Set(fromRuntime)); + if (uniqueRuntime.length >= 3) return uniqueRuntime; + return PROFESSIONAL_PORTFOLIO_TABS[props.roleKey] || PROFESSIONAL_PORTFOLIO_TABS.default; + }; + + const professionalFormStorageKey = () => `nxtgauge_portfolio_meta_${String(props.roleKey || 'professional').toLowerCase()}`; + const loadProfessionalForm = () => { + if (typeof window === 'undefined') return; + try { + const raw = window.localStorage.getItem(professionalFormStorageKey()); + if (!raw) return; + const parsed = JSON.parse(raw) as Partial; + setProfessionalForm({ + about: String(parsed?.about || ''), + services: String(parsed?.services || ''), + experience: String(parsed?.experience || ''), + testimonials: String(parsed?.testimonials || ''), + faqs: String(parsed?.faqs || ''), + }); + } catch { + // Ignore malformed local storage payloads. + } + }; + + const saveProfessionalForm = () => { + if (typeof window !== 'undefined') { + window.localStorage.setItem(professionalFormStorageKey(), JSON.stringify(professionalForm())); + } + setProfessionalMsg('Portfolio section saved.'); + window.setTimeout(() => setProfessionalMsg(''), 1800); + }; + onMount(() => { if (isJobSeeker()) { loadJobSeekerPortfolio(); @@ -204,6 +275,11 @@ export default function PortfolioPage(props: Props) { setLoading(false); return; } + if (isProfessional()) { + const tabs = professionalTabs(); + setProfessionalTab(tabs[0] || 'About'); + loadProfessionalForm(); + } void loadItems(); }); @@ -418,195 +494,184 @@ export default function PortfolioPage(props: Props) { ); } + const isProjectsTab = () => { + const key = normalizeToken(professionalTab()); + return key.includes('project') || key.includes('portfolio') || key.includes('gallery') || key.includes('showreel'); + }; + + const isServicesTab = () => { + const key = normalizeToken(professionalTab()); + return key.includes('service') || key.includes('pricing') || key.includes('package'); + }; + + const isTestimonialsTab = () => normalizeToken(professionalTab()).includes('testimonial'); + const isFaqTab = () => normalizeToken(professionalTab()).includes('faq'); + const sectionFieldKey = () => { + if (isServicesTab()) return 'services' as const; + if (isTestimonialsTab()) return 'testimonials' as const; + if (isFaqTab()) return 'faqs' as const; + const key = normalizeToken(professionalTab()); + if (key.includes('experience') || key.includes('stack') || key.includes('tool') || key.includes('qualification')) return 'experience' as const; + return 'about' as const; + }; + + const sectionPlaceholder = () => { + if (isServicesTab()) return 'List your plans, pricing slabs, and deliverables...'; + if (isTestimonialsTab()) return 'Add client quotes and project outcomes...'; + if (isFaqTab()) return 'Add common questions and answers...'; + if (sectionFieldKey() === 'experience') return 'Share stack, years of experience, and toolchain...'; + return 'Write a short summary about your profile and strengths...'; + }; + return (

- - {/* ── Header ────────────────────────────────────────────────────── */} -
-
-

My Portfolio

-

- Showcase your work to attract clients. -

+
+
+ + {(tab) => ( + + )} + +
+
+
+

My Portfolio

+

+ Runtime-config driven tab layout aligned with external dashboard preview. +

+
+ + +
-
- {/* ── Create / Edit form ─────────────────────────────────────────── */} - -
-

- {editId() ? 'Edit Portfolio Item' : 'New Portfolio Item'} -

- -
-
- - setField('title', e.currentTarget.value)} - style={INPUT} - /> -
-
- + + +
+ {professionalMsg()} +
+
+

{professionalTab()}

+
+