109 lines
4.5 KiB
TypeScript
109 lines
4.5 KiB
TypeScript
import { Link } from 'react-router-dom';
|
|
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
|
import { Users, ChevronRight, MapPin } from 'lucide-react';
|
|
import { useCrewsByFocus } from '@/hooks/useCrews';
|
|
import { CREW_FOCUS_LABELS, crewFocusForServiceType, type SkateCrew, type CrewFocus } from '@/lib/types';
|
|
import { Skeleton } from '@/components/ui/skeleton';
|
|
|
|
/** The Lab page's own ServiceType union (kept in sync with LabPage). */
|
|
type ServiceType =
|
|
| 'coaching' | 'filming' | 'editing' | 'photography' | 'building'
|
|
| 'art' | 'graffiti' | 'tattoo' | 'training' | 'music';
|
|
|
|
/** Translate the Lab page's ServiceType into a crew focus scene. */
|
|
function focusForLabService(serviceType: ServiceType): Exclude<CrewFocus, 'mixed'> {
|
|
switch (serviceType) {
|
|
case 'art':
|
|
case 'graffiti':
|
|
case 'tattoo':
|
|
return 'art';
|
|
case 'music':
|
|
return 'music';
|
|
case 'training':
|
|
return 'wellness';
|
|
case 'building':
|
|
return crewFocusForServiceType('building');
|
|
case 'coaching':
|
|
return crewFocusForServiceType('coaching');
|
|
default:
|
|
return crewFocusForServiceType('filming'); // filming / editing / photography → photo
|
|
}
|
|
}
|
|
|
|
function CrewChip({ crew }: { crew: SkateCrew }) {
|
|
return (
|
|
<Link
|
|
to={`/crew/${crew.pubkey}/${crew.identifier}`}
|
|
className="group flex w-56 flex-shrink-0 items-center gap-3 rounded-2xl border border-teal-500/20 bg-white/[0.03] p-3 transition-all hover:border-teal-500/50 hover:bg-white/[0.06] active:scale-[0.99]"
|
|
>
|
|
<Avatar className="h-11 w-11 rounded-xl border border-teal-500/30">
|
|
<AvatarImage src={crew.image} alt={crew.name} className="object-cover" />
|
|
<AvatarFallback className="rounded-xl bg-teal-900/40 text-teal-300 font-black text-sm">
|
|
{crew.name.slice(0, 2).toUpperCase()}
|
|
</AvatarFallback>
|
|
</Avatar>
|
|
<div className="min-w-0 flex-1">
|
|
<p className="truncate text-sm font-black text-white leading-tight">{crew.name}</p>
|
|
{crew.location ? (
|
|
<p className="mt-0.5 flex items-center gap-1 truncate text-xs text-gray-400">
|
|
<MapPin className="h-3 w-3 flex-shrink-0 text-teal-400" />
|
|
{crew.location}
|
|
</p>
|
|
) : (
|
|
<p className="mt-0.5 flex items-center gap-1 text-xs text-gray-500">
|
|
<Users className="h-3 w-3 text-teal-400" />
|
|
{crew.members.length} {crew.members.length === 1 ? 'member' : 'members'}
|
|
</p>
|
|
)}
|
|
</div>
|
|
<ChevronRight className="h-4 w-4 flex-shrink-0 text-gray-600 group-hover:text-teal-400" />
|
|
</Link>
|
|
);
|
|
}
|
|
|
|
/**
|
|
* A horizontal strip of crews that share the active Lab scene's focus.
|
|
* Shown beneath the Lab results so crews and individual service providers
|
|
* cross-reference each other. Renders nothing when there are no matching crews.
|
|
*/
|
|
export function LabCrewsStrip({ serviceType }: { serviceType: ServiceType }) {
|
|
const focus = focusForLabService(serviceType);
|
|
const { data: crews, isLoading } = useCrewsByFocus(focus, 20);
|
|
|
|
// Only render the section when there's something to show (or while loading).
|
|
if (!isLoading && (!crews || crews.length === 0)) return null;
|
|
|
|
return (
|
|
<section className="mt-8">
|
|
<div className="mb-3 flex items-center justify-between gap-3">
|
|
<h2 className="flex items-center gap-2 text-sm font-black uppercase tracking-wide text-teal-400">
|
|
<Users className="h-4 w-4" />
|
|
Crews in this scene
|
|
<span className="rounded-full bg-teal-500/15 px-2 py-0.5 text-[10px] font-bold text-teal-300">
|
|
{CREW_FOCUS_LABELS[focus]}
|
|
</span>
|
|
</h2>
|
|
<Link
|
|
to="/crews"
|
|
className="flex items-center gap-0.5 text-xs font-bold text-gray-400 hover:text-teal-300"
|
|
>
|
|
All crews <ChevronRight className="h-3.5 w-3.5" />
|
|
</Link>
|
|
</div>
|
|
|
|
<div className="-mx-4 flex gap-3 overflow-x-auto px-4 pb-2 scroll-container">
|
|
{isLoading
|
|
? Array.from({ length: 3 }).map((_, i) => (
|
|
<div key={i} className="flex w-56 flex-shrink-0 items-center gap-3 rounded-2xl border border-white/5 p-3">
|
|
<Skeleton className="h-11 w-11 rounded-xl" />
|
|
<div className="flex-1 space-y-2">
|
|
<Skeleton className="h-3.5 w-28" />
|
|
<Skeleton className="h-3 w-20" />
|
|
</div>
|
|
</div>
|
|
))
|
|
: crews?.map((crew) => <CrewChip key={`${crew.pubkey}:${crew.identifier}`} crew={crew} />)}
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|