- 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).
87 lines
2.6 KiB
TypeScript
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>
|
|
);
|
|
}
|