import { useSeoMeta } from '@unhead/react'; import { useParams, Link, useNavigate } from 'react-router-dom'; import { Layout } from '@/components/Layout'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; import { Skeleton } from '@/components/ui/skeleton'; import { ZapButton } from '@/components/ZapButton'; import { ClinkPayButton } from '@/components/ClinkPayButton'; import { CollectiveMembers } from '@/components/CollectiveMembers'; import { ConfirmMembershipCard } from '@/components/ConfirmMembershipCard'; import { LinkedCrewCard } from '@/components/CrossLinkCards'; import { TrustBadge } from '@/components/TrustBadge'; import { useLabProfile } from '@/hooks/useLabProfiles'; import { useAuthor } from '@/hooks/useAuthor'; import { genUserName } from '@/lib/genUserName'; import { COACH_SKILL_LABELS, COACH_SKILL_DESCRIPTIONS, FILMING_SERVICE_LABELS, FILMING_SERVICE_DESCRIPTIONS, FILMING_SKILL_LABELS, FILMING_SKILL_DESCRIPTIONS, EDITOR_SERVICE_LABELS, EDITOR_SERVICE_DESCRIPTIONS, EDITOR_SKILL_LABELS, EDITOR_SKILL_DESCRIPTIONS, PHOTO_SERVICE_LABELS, PHOTO_SERVICE_DESCRIPTIONS, PHOTO_CAMERA_LABELS, PHOTO_LENS_LABELS, PHOTO_LIGHTING_LABELS, PHOTO_SUPPORT_LABELS, PHOTO_STYLE_LABELS, BUILDER_SERVICE_LABELS, BUILDER_SERVICE_DESCRIPTIONS, BUILDER_SKILL_LABELS, BUILDER_SCALE_LABELS } from '@/lib/types'; import { sanitizeUrl, sanitizeTel, sanitizeEmail, urlDomain } from '@/lib/sanitizeUrl'; import { OfficialBadge } from '@/components/OfficialBadge'; import { ProBadge } from '@/components/ProBadge'; import NotFound from './NotFound'; import { GraduationCap, MapPin, ArrowLeft, MessageCircle, Mail, Phone, Globe, Instagram, Calendar, Video, Camera, Scissors, Aperture, Users, HardHat, } from 'lucide-react'; import { nip19 } from 'nostr-tools'; function CoachDetailSkeleton() { return (
); } export default function CoachDetailPage() { const { pubkey, identifier } = useParams<{ pubkey: string; identifier: string }>(); const { data: profile, isLoading, error } = useLabProfile(identifier || '', pubkey); const author = useAuthor(profile?.pubkey); const navigate = useNavigate(); const isCoach = profile?.profileType === 'coach'; const isFilmer = profile?.profileType === 'filmer'; const isEditor = profile?.profileType === 'editor'; const isPhotographer = profile?.profileType === 'photographer'; const isBuilder = profile?.profileType === 'builder'; useSeoMeta({ title: profile ? `${profile.title} - SkateSpots` : 'Profile - SkateSpots', description: profile?.bio || 'View profile.', }); const metadata = author.data?.metadata; const displayName = metadata?.name ?? genUserName(profile?.pubkey || ''); if (isLoading) { return ; } if (error || !profile) { return ; } const npub = nip19.npubEncode(profile.pubkey); // Per-type accent (text, border, bg, ring) โ€” keeps the detail page on-brand const accent = isCoach ? { text: 'text-[#ff0040]', bg: 'bg-[#ff0040]', border: 'border-[#ff0040]/30', hover: 'hover:bg-[#ff0040]/10 border-[#ff0040]' } : isFilmer ? { text: 'text-orange-500', bg: 'bg-orange-500', border: 'border-orange-500/30', hover: 'hover:bg-orange-500/10 border-orange-500' } : isPhotographer ? { text: 'text-indigo-400', bg: 'bg-indigo-600', border: 'border-indigo-500/30', hover: 'hover:bg-indigo-500/10 border-indigo-500' } : isBuilder ? { text: 'text-amber-400', bg: 'bg-amber-500', border: 'border-amber-500/30', hover: 'hover:bg-amber-500/10 border-amber-500' } : { text: 'text-purple-400', bg: 'bg-purple-600', border: 'border-purple-500/30', hover: 'hover:bg-purple-500/10 border-purple-500' }; const TypeIcon = isCoach ? GraduationCap : isFilmer ? Video : isPhotographer ? Camera : isBuilder ? HardHat : Scissors; // Kind for the collective coordinate (for NIP-32 membership attestations) const profileKind = isCoach ? 35001 : isFilmer ? 35002 : isEditor ? 35003 : isPhotographer ? 35004 : 35005; const isCollective = profile.entityType === 'collective' && profile.members.length > 0; // All photographer label maps merged for chip resolution const photoLabels: Record = { ...PHOTO_SERVICE_LABELS, ...PHOTO_CAMERA_LABELS, ...PHOTO_LENS_LABELS, ...PHOTO_LIGHTING_LABELS, ...PHOTO_SUPPORT_LABELS, ...PHOTO_STYLE_LABELS, }; const builderLabels: Record = { ...BUILDER_SERVICE_LABELS, ...BUILDER_SKILL_LABELS, ...BUILDER_SCALE_LABELS, }; // Get skill labels and descriptions based on profile type const getSkillLabel = (skill: string) => { if (isCoach) return COACH_SKILL_LABELS[skill] || skill; if (isFilmer) return FILMING_SERVICE_LABELS[skill] || FILMING_SKILL_LABELS[skill] || skill; if (isPhotographer) return photoLabels[skill] || skill; if (isBuilder) return builderLabels[skill] || skill; if (isEditor) return EDITOR_SERVICE_LABELS[skill] || EDITOR_SKILL_LABELS[skill] || skill; return skill; }; const getSkillDescription = (skill: string) => { if (isCoach) return COACH_SKILL_DESCRIPTIONS[skill]; if (isFilmer) return FILMING_SERVICE_DESCRIPTIONS[skill] || FILMING_SKILL_DESCRIPTIONS[skill]; if (isPhotographer) return PHOTO_SERVICE_DESCRIPTIONS[skill]; if (isBuilder) return BUILDER_SERVICE_DESCRIPTIONS[skill]; if (isEditor) return EDITOR_SERVICE_DESCRIPTIONS[skill] || EDITOR_SKILL_DESCRIPTIONS[skill]; return undefined; }; return (
Back to The Lab
{/* Main Content */}
{/* Confirm-membership prompt (only for listed pending members) */} {isCollective && ( )} {/* Profile Card */}
{profile.title.slice(0, 2).toUpperCase()}

{profile.title}

{profile.entityType === 'collective' && ( Collective )}

Joined {new Date(profile.createdAt * 1000).toLocaleDateString()}

{/* Bio */} {profile.bio && (

About

{profile.bio}

)} {/* Skills */} {profile.skills.length > 0 && (

{isCoach ? 'Skills' : isPhotographer ? 'Services & Gear' : isBuilder ? 'What We Build' : 'Services & Skills'}

{profile.skills.map((skill) => (
{getSkillLabel(skill)} {getSkillDescription(skill) && (

{getSkillDescription(skill)}

)}
))}
)} {/* In the Bag โ€” photographer / filmer gear (freeform) */} {(isPhotographer || isFilmer || isBuilder) && 'gear' in profile && profile.gear && (

{isBuilder ? : } {isBuilder ? 'How We Work' : 'In the Bag'}

{profile.gear}

)} {/* Builder โ€” service area & insured */} {isBuilder && 'serviceArea' in profile && (profile.serviceArea || profile.insured) && (
{profile.serviceArea && (

Service Area

{profile.serviceArea}

)} {profile.insured && (
Licensed / Insured
)}
)} {/* Experience & Rate */}
{profile.experience && (

Experience

{profile.experience} years

)} {profile.rate && (

Rate

{profile.rate}

)}
{/* Sidebar */}
{/* Contact Card */} Contact {/* Direct Message */} {/* Contact Info from Profile */} {sanitizeTel(profile.phone) && ( {profile.phone} )} {sanitizeEmail(profile.email) && ( {profile.email} )} {sanitizeUrl(profile.website) && ( {urlDomain(profile.website!)} )} {isCoach && profile.instagram && ( @{profile.instagram.replace('@', '')} )} {(isFilmer || isPhotographer || isEditor || isBuilder) && 'social' in profile && sanitizeUrl(profile.social) && ( {urlDomain(profile.social!)} )} {/* Portfolio / reel (filmers, photographers & editors) */} {(isPhotographer || isEditor || isFilmer || isBuilder) && 'portfolio' in profile && sanitizeUrl(profile.portfolio) && ( Portfolio ยท {urlDomain(profile.portfolio!)} )} {/* Lightning Address from Nostr Profile */} {metadata?.lud16 && (
{metadata.lud16}
)} {/* Zap Button */}
{/* CLINK self-custodial payment (only when a valid noffer is present) */} {'noffer' in profile && profile.noffer && ( )}
{/* Members Card (collectives only) */} {isCollective && ( )} {/* Linked crew (cross-link) */} {/* Location Card */} {profile.location && ( Location

{profile.location}

)}
); }