SkateSpots/src/components/LabCrewsStrip.tsx
shakespeare.diy 9a8609354e Polish native-only wallet card + add Crew↔Lab scene focus
Wallet (Request A): redesign the browser CashuWalletBrowserNotice into a
premium, intentional card — gradient hero header, "App only" badge, and a
"works in your browser now" panel highlighting NWC / Alby / Lightning
address zaps. Keeps the native-only gate; no bug reintroduced.

Crew ↔ Lab interconnection: crews can now declare an optional Lab scene
focus that surfaces them inside the matching Lab category, while regular
crews stay "Mixed" and only appear on the Crews page.
- types: CrewFocus ('mixed'|'builder'|'photo'|'coaching'), labels,
  crew-focus-* t-tag scheme, serviceType→focus mapping; parseSkateCrew
  extracts focus and excludes focus tags from vibes; SkateCrew.focus field.
- CrewForm: "Crew Type" selector (Mixed / DIY & Builders / Photo & Film /
  Coaching); publishes a queryable crew-focus-* t-tag when not mixed.
- useCrews: new useCrewsByFocus hook (relay-level #t filtering).
- CrewsPage: focus badge on cards + focus filter chips.
- LabPage: "Crews in this scene" horizontal strip beneath results, mapping
  building→builder, filming/photography/editing→photo, coaching→coaching.
- NIP.md: document the crew-focus-<scene> tag.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 18:08:51 +07:00

86 lines
3.8 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 } from '@/lib/types';
import { Skeleton } from '@/components/ui/skeleton';
type ServiceType = 'coaching' | 'filming' | 'editing' | 'photography' | 'building';
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 = crewFocusForServiceType(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>
);
}