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 */} navigate(`/messages/${npub}`)} > Send 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} )} ); }
Joined {new Date(profile.createdAt * 1000).toLocaleDateString()}
{profile.bio}
{getSkillDescription(skill)}
{profile.gear}
{profile.serviceArea}
{profile.experience} years
{profile.rate}
{profile.location}