Nutzap:
- Gate sending behind NUTZAP_SENDING_ENABLED=false with clear docs; sending
would require P2PK-locking proofs (fund-loss risk if wrong), so it's disabled
until implementable/auditable. Receiving + reading unchanged. No dead-end
button exists (hook was never wired to UI).
Cross-linking crews ↔ Lab collectives:
- types: parseAddrCoord/parseCrewRef/parseLabRefs helpers + LAB_KINDS; add
crewRef to all 5 Lab interfaces/parsers and labRefs[] to SkateCrew
- CrewLinkField (pick one of your own crews) added to all 5 Become-* forms,
shown only for collectives; publishes an a-tag on the Lab profile
- LabLinkField (pick your own Lab profiles) added to CrewForm; publishes a-tags
- CrossLinkCards: LinkedCrewCard on Lab detail ("Part of a Crew"), LinkedLabCard
on crew detail ("Hire This Crew"), resolving coordinates via useCrew/useLabProfile
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
109 lines
4.1 KiB
TypeScript
109 lines
4.1 KiB
TypeScript
import { Link } from 'react-router-dom';
|
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
|
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
|
import { Users, GraduationCap, ChevronRight, Link2 } from 'lucide-react';
|
|
import { useCrew } from '@/hooks/useCrews';
|
|
import { useLabProfile } from '@/hooks/useLabProfiles';
|
|
import { parseAddrCoord } from '@/lib/types';
|
|
|
|
const LAB_TYPE_LABEL: Record<number, string> = {
|
|
35001: 'Coach',
|
|
35002: 'Filmer',
|
|
35003: 'Editor',
|
|
35004: 'Photographer',
|
|
35005: 'Builder',
|
|
};
|
|
|
|
/** A single linked-crew row, resolved from a `35006:pubkey:d` coordinate. */
|
|
function CrewRow({ coord }: { coord: string }) {
|
|
const parsed = parseAddrCoord(coord);
|
|
const { data: crew } = useCrew(parsed?.identifier ?? '', parsed?.pubkey);
|
|
|
|
if (!parsed) return null;
|
|
|
|
return (
|
|
<Link
|
|
to={`/crew/${parsed.pubkey}/${parsed.identifier}`}
|
|
className="flex items-center gap-3 rounded-lg border border-gray-700 bg-black/40 p-2.5 hover:bg-black/60 hover:border-teal-500/40 transition-colors"
|
|
>
|
|
<div className="flex h-9 w-9 shrink-0 items-center justify-center overflow-hidden rounded-lg bg-teal-500/15">
|
|
{crew?.image ? (
|
|
<img src={crew.image} alt={crew.name} className="h-full w-full object-cover" />
|
|
) : (
|
|
<Users className="h-4 w-4 text-teal-400" />
|
|
)}
|
|
</div>
|
|
<div className="min-w-0 flex-1">
|
|
<p className="truncate text-sm font-bold text-white">{crew?.name ?? 'Skate Crew'}</p>
|
|
<p className="text-xs text-teal-400">Crew{crew?.location ? ` · ${crew.location}` : ''}</p>
|
|
</div>
|
|
<ChevronRight className="h-4 w-4 shrink-0 text-gray-500" />
|
|
</Link>
|
|
);
|
|
}
|
|
|
|
/** A single linked-Lab-profile row, resolved from a `kind:pubkey:d` coordinate. */
|
|
function LabRow({ coord }: { coord: string }) {
|
|
const parsed = parseAddrCoord(coord);
|
|
const { data: profile } = useLabProfile(parsed?.identifier ?? '', parsed?.pubkey);
|
|
|
|
if (!parsed) return null;
|
|
const typeLabel = LAB_TYPE_LABEL[parsed.kind] ?? 'Lab';
|
|
|
|
return (
|
|
<Link
|
|
to={`/coach/${parsed.pubkey}/${parsed.identifier}`}
|
|
className="flex items-center gap-3 rounded-lg border border-gray-700 bg-black/40 p-2.5 hover:bg-black/60 hover:border-teal-500/40 transition-colors"
|
|
>
|
|
<div className="flex h-9 w-9 shrink-0 items-center justify-center overflow-hidden rounded-lg bg-white/5">
|
|
{profile?.image ? (
|
|
<img src={profile.image} alt={profile.title} className="h-full w-full object-cover" />
|
|
) : (
|
|
<GraduationCap className="h-4 w-4 text-gray-300" />
|
|
)}
|
|
</div>
|
|
<div className="min-w-0 flex-1">
|
|
<p className="truncate text-sm font-bold text-white">{profile?.title ?? `${typeLabel} profile`}</p>
|
|
<p className="text-xs text-gray-400">In The Lab · {typeLabel}</p>
|
|
</div>
|
|
<ChevronRight className="h-4 w-4 shrink-0 text-gray-500" />
|
|
</Link>
|
|
);
|
|
}
|
|
|
|
/** Card shown on a Lab profile: the crew it belongs to. */
|
|
export function LinkedCrewCard({ crewRef }: { crewRef?: string }) {
|
|
if (!crewRef) return null;
|
|
return (
|
|
<Card className="bg-gray-900 border-gray-700">
|
|
<CardHeader className="pb-3">
|
|
<CardTitle className="flex items-center gap-2 text-base">
|
|
<Link2 className="h-4 w-4 text-teal-400" /> Part of a Crew
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<CrewRow coord={crewRef} />
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|
|
|
|
/** Card shown on a crew: the Lab service profiles it links to. */
|
|
export function LinkedLabCard({ labRefs }: { labRefs: string[] }) {
|
|
if (!labRefs || labRefs.length === 0) return null;
|
|
return (
|
|
<Card className="bg-gray-900 border-gray-700">
|
|
<CardHeader className="pb-3">
|
|
<CardTitle className="flex items-center gap-2 text-base">
|
|
<Link2 className="h-4 w-4 text-teal-400" /> Hire This Crew
|
|
</CardTitle>
|
|
<p className="text-xs text-gray-500">Services this crew offers in The Lab</p>
|
|
</CardHeader>
|
|
<CardContent className="space-y-2">
|
|
{labRefs.map((coord) => (
|
|
<LabRow key={coord} coord={coord} />
|
|
))}
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|