fix(admin): add missing detail pages for 8 profession admin lists
All checks were successful
build-and-release / build (push) Successful in 44s

Each of these profession admin list pages (makeup artist, tutor, developer,
video editor, graphic designer, social media manager, fitness trainer,
catering services) linked "View Profile" to /admin/{role}/{id}, but only
Photographer and Company had a real [id].tsx route — every other role fell
through to the legacy catch-all module and showed a placeholder instead of
the applicant's actual profile. Converts each flat list route into a
folder (index.tsx + [id].tsx) and adds a shared ProfessionDetailPage
component, modeled on the working Photographer detail page.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Ashwin Kumar Sivakumar 2026-07-14 21:24:02 +05:30
parent 7ae59fee20
commit 0866b7a069
17 changed files with 259 additions and 0 deletions

View file

@ -0,0 +1,163 @@
import { A, useParams } from '@solidjs/router';
import { For, createMemo, createResource, Show } from 'solid-js';
const API = '';
type ProfessionProfile = {
id: string;
name?: string;
full_name?: string;
first_name?: string;
last_name?: string;
email?: string;
phone?: string;
status?: string;
created_at?: string;
createdAt?: string;
city?: string;
state?: string;
experience?: string;
portfolio_url?: string;
phoneNumber?: string;
location?: string;
gender?: string;
availability?: string;
permanentAddress?: string;
hometown?: string;
pincode?: string;
languages?: Array<{ language?: string; proficiency?: string }>;
};
async function fetchProfile(id: string, apiSlug: string): Promise<ProfessionProfile | null> {
try {
const res = await fetch(`${API}/api/admin/users/${id}`);
if (res.ok) return res.json();
const fallback = await fetch(`${API}/api/users/${id}`);
if (fallback.ok) return fallback.json();
const roleRes = await fetch(`${API}/api/${apiSlug}/${id}`);
if (!roleRes.ok) return null;
return roleRes.json();
} catch {
return null;
}
}
/**
* Generic admin detail/drill-down page shared by every profession's "View Profile"
* link. Modeled on the Photographer detail page's fetch pattern (which already
* works via /api/admin/users/{id}) so each profession doesn't need its own
* bespoke backend endpoint just to render a read-only snapshot.
*/
export default function ProfessionDetailPage(props: {
roleLabel: string;
apiSlug: string;
backHref: string;
backLabel: string;
}) {
const params = useParams();
const [profile] = createResource(() => params.id, (id) => fetchProfile(id, props.apiSlug));
const name = createMemo(() => {
const p = profile();
if (!p) return props.roleLabel;
return (
p.name ||
p.full_name ||
[p.first_name, p.last_name].filter(Boolean).join(' ') ||
props.roleLabel
);
});
const created = createMemo(() => profile()?.createdAt || profile()?.created_at || '');
return (
<div class="flex flex-col -mx-6 -mt-6 min-h-full">
<div class="bg-white border-b border-gray-200 px-6 py-4 flex items-center justify-between">
<div>
<h1 class="text-xl font-semibold text-gray-900">{props.roleLabel} Detail</h1>
<p class="text-sm text-gray-500 mt-0.5">View profile snapshot and account metadata for one {props.roleLabel.toLowerCase()}.</p>
</div>
<div class="flex items-center gap-2">
<A class="rounded-lg border border-gray-200 px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href={props.backHref}>{props.backLabel}</A>
<A class="rounded-lg border border-gray-200 px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href={`/admin/users/details/${params.id}`}>Open User Detail</A>
</div>
</div>
<div class="p-6 flex-1">
<Show when={profile.loading}>
<div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Loading {props.roleLabel.toLowerCase()}...</p></div>
</Show>
<Show when={!profile.loading && !profile()}>
<div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">{props.roleLabel} not found.</p></div>
</Show>
<Show when={profile()}>
<section class="rounded-xl border border-gray-200 bg-white shadow-sm">
<div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2">
<div class="kv-item">
<p class="kv-label">Name</p>
<p class="kv-value">{name()}</p>
</div>
<div class="kv-item">
<p class="kv-label">Email</p>
<p class="kv-value">{profile()!.email || '—'}</p>
</div>
<div class="kv-item">
<p class="kv-label">Phone</p>
<p class="kv-value">{profile()!.phone || profile()!.phoneNumber || '—'}</p>
</div>
<div class="kv-item">
<p class="kv-label">Location</p>
<p class="kv-value">{profile()!.location || [profile()!.city, profile()!.state].filter(Boolean).join(', ') || '—'}</p>
</div>
<div class="kv-item">
<p class="kv-label">Gender</p>
<p class="kv-value">{profile()!.gender || '—'}</p>
</div>
<div class="kv-item">
<p class="kv-label">Experience</p>
<p class="kv-value">{profile()!.experience || '—'}</p>
</div>
<div class="kv-item">
<p class="kv-label">Availability</p>
<p class="kv-value">{profile()!.availability || '—'}</p>
</div>
<div class="kv-item">
<p class="kv-label">Permanent Address</p>
<p class="kv-value">{profile()!.permanentAddress || '—'}</p>
</div>
<div class="kv-item">
<p class="kv-label">Hometown</p>
<p class="kv-value">{profile()!.hometown || '—'}</p>
</div>
<div class="kv-item">
<p class="kv-label">Pincode</p>
<p class="kv-value">{profile()!.pincode || '—'}</p>
</div>
<div class="kv-item">
<p class="kv-label">Status</p>
<p class="kv-value">{profile()!.status || '—'}</p>
</div>
<div class="kv-item">
<p class="kv-label">Created</p>
<p class="kv-value">{created() ? new Date(created()).toLocaleString() : '—'}</p>
</div>
<div class="kv-item" style="grid-column:1 / -1">
<p class="kv-label">Portfolio</p>
<p class="kv-value">{profile()!.portfolio_url || '—'}</p>
</div>
<Show when={profile()!.languages && profile()!.languages!.length > 0}>
<div class="kv-item" style="grid-column:1 / -1">
<p class="kv-label">Languages</p>
<ul class="notice" style="margin:8px 0 0;padding-left:18px">
<For each={profile()!.languages!}>
{(item) => <li>{item.language || 'Unknown'} - {item.proficiency || 'N/A'}</li>}
</For>
</ul>
</div>
</Show>
</div>
</section>
</Show>
</div>
</div>
);
}

View file

@ -0,0 +1,12 @@
import ProfessionDetailPage from '~/components/admin/ProfessionDetailPage';
export default function Page() {
return (
<ProfessionDetailPage
roleLabel="Catering Service"
apiSlug="catering-services"
backHref="/admin/catering-services"
backLabel="Back to Catering Services List"
/>
);
}

View file

@ -0,0 +1,12 @@
import ProfessionDetailPage from '~/components/admin/ProfessionDetailPage';
export default function Page() {
return (
<ProfessionDetailPage
roleLabel="Developer"
apiSlug="developers"
backHref="/admin/developers"
backLabel="Back to Developers List"
/>
);
}

View file

@ -0,0 +1,12 @@
import ProfessionDetailPage from '~/components/admin/ProfessionDetailPage';
export default function Page() {
return (
<ProfessionDetailPage
roleLabel="Fitness Trainer"
apiSlug="fitness-trainers"
backHref="/admin/fitness-trainers"
backLabel="Back to Fitness Trainers List"
/>
);
}

View file

@ -0,0 +1,12 @@
import ProfessionDetailPage from '~/components/admin/ProfessionDetailPage';
export default function Page() {
return (
<ProfessionDetailPage
roleLabel="Graphic Designer"
apiSlug="graphic-designers"
backHref="/admin/graphic-designers"
backLabel="Back to Graphic Designers List"
/>
);
}

View file

@ -0,0 +1,12 @@
import ProfessionDetailPage from '~/components/admin/ProfessionDetailPage';
export default function Page() {
return (
<ProfessionDetailPage
roleLabel="Makeup Artist"
apiSlug="makeup-artists"
backHref="/admin/makeup-artist"
backLabel="Back to Makeup Artist List"
/>
);
}

View file

@ -0,0 +1,12 @@
import ProfessionDetailPage from '~/components/admin/ProfessionDetailPage';
export default function Page() {
return (
<ProfessionDetailPage
roleLabel="Social Media Manager"
apiSlug="social-media-managers"
backHref="/admin/social-media-managers"
backLabel="Back to Social Media Managers List"
/>
);
}

View file

@ -0,0 +1,12 @@
import ProfessionDetailPage from '~/components/admin/ProfessionDetailPage';
export default function Page() {
return (
<ProfessionDetailPage
roleLabel="Tutor"
apiSlug="tutors"
backHref="/admin/tutors"
backLabel="Back to Tutors List"
/>
);
}

View file

@ -0,0 +1,12 @@
import ProfessionDetailPage from '~/components/admin/ProfessionDetailPage';
export default function Page() {
return (
<ProfessionDetailPage
roleLabel="Video Editor"
apiSlug="video-editors"
backHref="/admin/video-editors"
backLabel="Back to Video Editors List"
/>
);
}