SkateSpots/src/components/ProfilePreviewCard.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

88 lines
3.8 KiB
TypeScript

import { useState } from 'react';
import { Link } from 'react-router-dom';
import { nip19 } from 'nostr-tools';
import { ExternalLink, ShieldCheck } from 'lucide-react';
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import { ProfileBadges } from '@/components/ProfileBadges';
import { useCurrentUser } from '@/hooks/useCurrentUser';
import { isSkateSpotsNip05, profileBadges } from '@/lib/badges';
import { useTranslation } from '@/contexts/language';
import { cn } from '@/lib/utils';
/**
* A live preview of how the signed-in user appears to everyone else — banner,
* avatar, handle, bio and badges — shown at the top of the Edit Profile screen
* so "how do I look?" has an answer without leaving the page.
*
* Reads the same kind-0 metadata the form edits, so it reflects the last SAVED
* profile (the form's unsaved edits aren't shown; the sticky Save button commits).
*/
export function ProfilePreviewCard({ className }: { className?: string }) {
const { user, metadata, isLoadingProfile } = useCurrentUser();
const { t } = useTranslation();
const [bannerOk, setBannerOk] = useState(true);
if (!user) return null;
const name = metadata?.display_name || metadata?.name || '';
const initials = (name || user.pubkey.slice(0, 2)).slice(0, 2).toUpperCase();
const nip05 = metadata?.nip05;
const about = metadata?.about?.trim();
const badges = profileBadges(metadata);
return (
<div className={cn('overflow-hidden rounded-2xl border border-white/10 bg-white/2', className)}>
{/* Banner (falls back to the brand gradient) */}
<div className="relative h-28 w-full bg-gradient-to-r from-[#ff0040]/25 via-[#ff6600]/15 to-[#ccff00]/20">
{metadata?.banner && bannerOk && (
<img
src={metadata.banner}
alt=""
className="h-full w-full object-cover"
onError={() => setBannerOk(false)}
/>
)}
<div className="absolute inset-0 bg-gradient-to-t from-black/60 via-black/10 to-transparent" />
<Link
to={`/${nip19.npubEncode(user.pubkey)}`}
className="absolute right-2 top-2 inline-flex items-center gap-1.5 rounded-full bg-black/55 px-3 py-1.5 text-xs font-bold text-white backdrop-blur transition-colors hover:bg-black/75"
>
{t('labUi.profilePreview.viewPublic')}
<ExternalLink className="h-3 w-3" />
</Link>
</div>
{/* Identity */}
<div className="px-4 pb-4">
<div className="flex items-end gap-3">
<Avatar className="-mt-10 h-20 w-20 border-4 border-background shadow-lg">
{metadata?.picture && <AvatarImage src={metadata.picture} alt={name} />}
<AvatarFallback className="text-lg font-black">{initials}</AvatarFallback>
</Avatar>
<div className="min-w-0 flex-1 pb-1">
<p className="truncate text-lg font-black leading-tight text-white">
{name || (isLoadingProfile ? t('common.loading') : t('labUi.profilePreview.unnamed'))}
</p>
{nip05 ? (
<span
className={cn(
'mt-0.5 inline-flex items-center gap-1 text-xs font-bold',
isSkateSpotsNip05(nip05) ? 'text-[#ccff00]' : 'text-blue-400',
)}
>
<ShieldCheck className="h-3.5 w-3.5 flex-shrink-0" />
<span className="truncate">{nip05}</span>
</span>
) : (
<span className="mt-0.5 block text-xs text-muted-foreground">{t('labUi.profilePreview.noHandle')}</span>
)}
</div>
</div>
{about && <p className="mt-3 line-clamp-3 text-sm leading-relaxed text-muted-foreground">{about}</p>}
{badges.length > 0 && <ProfileBadges badges={badges} size="sm" className="mt-3" />}
</div>
</div>
);
}