SkateSpots/src/components/CrewsForSpotCard.tsx
SkateSpots 09735375c2 feat(i18n): Spanish (Castellano) — infra + Map/Spots, Marketplace, Lab (batch 1)
- First-visit language auto-detect (Spanish devices land in es; stored choice wins).
- Sasquatch AI replies in the app language (skate trick names kept English).
- i18n safety net: scripts/check-i18n.mjs wired into npm test — fails on any t()
  key missing from en/es or any en/es key drift.
- ~1,680 new keys (2,297 total) across mapUi / spotUi / marketUi / labUi and
  ~65 files wired to t(): Map, spots (detail/create/edit/chat), marketplace,
  trades, offers, escrow, shipping, The Lab, /become/* forms, coaches, skaters.
- Policy: only skate TRICK names stay English; user-generated content untouched.
2026-10-01 23:57:20 +07:00

80 lines
3.3 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 { Skeleton } from '@/components/ui/skeleton';
import { useCrewsForSpot } from '@/hooks/useCrewsForSpot';
import { crewAccent } from '@/lib/crewHelpers';
import { useTranslation } from '@/contexts/language';
import { Users } from 'lucide-react';
/**
* Reverse link on a spot page: "Crews that skate here". Makes the local scene
* visible from the spot side, not just from the crew side.
*/
export function CrewsForSpotCard({
spotPubkey,
spotIdentifier,
}: {
spotPubkey?: string;
spotIdentifier?: string;
}) {
const { t } = useTranslation();
const { data: crews, isLoading } = useCrewsForSpot(spotPubkey, spotIdentifier);
// Nothing to show and not loading → render nothing (keeps the page clean).
if (!isLoading && (!crews || crews.length === 0)) return null;
return (
<Card className="border-2 border-teal-500/20 bg-black/90">
<CardHeader className="pb-3">
<CardTitle className="flex items-center gap-2 text-lg font-black uppercase text-white">
<Users className="h-5 w-5 text-teal-400" /> {t('spotUi.crewsThatSkateHere')}
</CardTitle>
</CardHeader>
<CardContent className="space-y-2">
{isLoading ? (
Array.from({ length: 2 }).map((_, i) => (
<div key={i} className="flex items-center gap-3">
<Skeleton className="h-10 w-10 rounded-xl" />
<div className="flex-1 space-y-1.5">
<Skeleton className="h-3.5 w-28" />
<Skeleton className="h-3 w-20" />
</div>
</div>
))
) : (
crews!.map((crew) => {
const accent = crewAccent(crew);
return (
<Link
key={`${crew.pubkey}:${crew.identifier}`}
to={`/crew/${crew.pubkey}/${crew.identifier}`}
className="flex items-center gap-3 rounded-xl border border-white/5 bg-black/40 p-2 transition-colors hover:bg-black/60"
>
<Avatar className="h-10 w-10 rounded-xl border" style={{ borderColor: `${accent.hex}55` }}>
<AvatarImage src={crew.image} alt={crew.name} className="object-cover" />
<AvatarFallback
className="rounded-xl text-xs font-black"
style={{ color: accent.hex, background: `${accent.hex}22` }}
>
{crew.name.slice(0, 2).toUpperCase()}
</AvatarFallback>
</Avatar>
<div className="min-w-0 flex-1">
<p className="truncate text-sm font-bold text-white">{crew.name}</p>
<p className="flex items-center gap-1 text-xs text-gray-500">
<Users className="h-3 w-3" />{' '}
{crew.members.length === 1
? t('spotUi.memberOne', { count: crew.members.length })
: t('spotUi.memberOther', { count: crew.members.length })}
{crew.location && ` · ${crew.location}`}
</p>
</div>
</Link>
);
})
)}
</CardContent>
</Card>
);
}