SkateSpots/src/components/SpotStatusBadge.tsx
SkateSpots e357833557 i18n(es): batch 3 part 3 — shared label maps + leftover routing/Sasquatch strings
- labelsUi.* (221+ keys): spot categories/features/types, access, shelter, event
  types, gear categories/conditions, price/listings, tours, crews,
  member-visibility, tag categories, spot status. New src/lib/labelKeys.ts holds
  the shared value->key maps (one source; the five duplicated EVENT_TYPE_KEYS
  copies now import it). spotStatus.statusTimeAgo() uses the locale date shim.
- formatPrice() now takes an optional t so FREE/Trade/Make Offer translate.
- Route helpers: skateTime/distanceReadout/maneuver descriptors/formatAge
  translated (routeUi.*), consumers updated.
- Sasquatch: PRO_TIERS labels/badges + summarizeDraft titles/lines/ctas
  translated (sasquatchUi.*).
- Lab *_LABELS/_DESCRIPTIONS maps remain English (documented out of scope).
2026-10-02 15:31:27 +07:00

87 lines
2.6 KiB
TypeScript

import { cn } from '@/lib/utils';
import { useTranslation } from '@/contexts/language';
import { SPOT_STATUS_META, statusTimeAgo } from '@/lib/spotStatus';
import type { AggregatedSpotStatus } from '@/hooks/useSpotStatus';
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from '@/components/ui/tooltip';
interface SpotStatusBadgeProps {
aggregated: AggregatedSpotStatus | null | undefined;
/** Compact = dot + label only, no meta. */
size?: 'sm' | 'md';
className?: string;
/** Hide the badge entirely when there are no reports (default true). */
hideWhenEmpty?: boolean;
}
/**
* Displays the community-consensus status for a spot.
* - No reports → renders nothing (unless hideWhenEmpty=false, then "Unverified").
* - Otherwise → colored badge with the winning status + reporter count tooltip.
*/
export function SpotStatusBadge({
aggregated,
size = 'md',
className,
hideWhenEmpty = true,
}: SpotStatusBadgeProps) {
const { t } = useTranslation();
const status = aggregated?.status ?? null;
if (!status) {
if (hideWhenEmpty) return null;
return (
<span
className={cn(
'inline-flex items-center gap-1 rounded-full border px-2 py-0.5 text-xs font-medium',
'text-muted-foreground bg-muted/40 border-border',
className,
)}
>
{t('spotUi.unverified')}
</span>
);
}
const meta = SPOT_STATUS_META[status];
const reporterCount = aggregated?.reporterCount ?? 0;
const lastAt = aggregated?.lastReportedAt ?? null;
const badge = (
<span
className={cn(
'inline-flex items-center gap-1 rounded-full border font-semibold',
meta.badgeClass,
size === 'sm' ? 'px-1.5 py-0.5 text-[10px]' : 'px-2 py-0.5 text-xs',
className,
)}
>
<span
className="inline-block rounded-full"
style={{
backgroundColor: meta.dot,
width: size === 'sm' ? 6 : 8,
height: size === 'sm' ? 6 : 8,
}}
/>
{t(meta.labelKey)}
</span>
);
return (
<Tooltip>
<TooltipTrigger asChild>{badge}</TooltipTrigger>
<TooltipContent className="max-w-56">
<p className="font-medium">{t(meta.labelKey)}</p>
<p className="text-xs text-muted-foreground">{t(meta.descriptionKey)}</p>
<p className="mt-1 text-xs text-muted-foreground">
{reporterCount} {reporterCount === 1 ? t('spotUi.reportOne') : t('spotUi.reports')}
{lastAt ? ` · ${t('spotUi.lastReported', { time: statusTimeAgo(lastAt) })}` : ''}
</p>
</TooltipContent>
</Tooltip>
);
}