SkateSpots/src/components/CrewBadge.tsx
SkateSpots 0b796440f1 feat(i18n): Spanish batch 2 — nav/chrome, events/shops/tours, crews/DMs, settings/auth, wallet/stash/newsletter/stats
~3,470 new keys (5,770 total, EN=ES), ~115 more files wired.
- nav + chromeUi: header, More menu, burger menu, footer, map toolbar widgets
  (sort/trust/weather), spot-type label helper.
- placesUi: events, shops, tours, food. socialUi: crews, DMs, notifications,
  collectives, crew chat/coordinator. settingsUi: settings, auth/signup,
  onboarding, profile edit, legal. walletUi: wallet/Cashu, stash, mints,
  app shop, newsletter, community stats, leaderboards, profile.
- Fixed 738 usages where an agent dropped the socialUi. prefix (caught by check:i18n).
2026-10-02 10:11:06 +07:00

92 lines
2.9 KiB
TypeScript

import { cn } from '@/lib/utils';
import { useTranslation } from '@/contexts/language';
import { crewBadge, ACTIVITY_META, type CrewActivity } from '@/lib/crewHelpers';
import type { SkateCrew } from '@/lib/types';
/**
* A small "legitimacy" badge (OG / Verified / Established) derived from a crew's
* founding year, NIP-05 and member count. Social proof for the directory.
*/
export function CrewRepBadge({
crew,
confirmedMembers,
className,
}: {
crew: Pick<SkateCrew, 'founded' | 'nip05' | 'members'>;
confirmedMembers?: number;
className?: string;
}) {
const badge = crewBadge(crew, confirmedMembers);
if (badge.key === 'crew') return null;
const styles: Record<string, string> = {
og: 'bg-amber-500/15 text-amber-300 border-amber-500/40',
verified: 'bg-teal-500/15 text-teal-300 border-teal-500/40',
established: 'bg-fuchsia-500/15 text-fuchsia-300 border-fuchsia-500/40',
};
return (
<span
className={cn(
'inline-flex items-center gap-1 rounded-full border px-1.5 py-0.5 text-[10px] font-bold uppercase tracking-wide',
styles[badge.key],
className,
)}
title={badge.label}
>
<span className="leading-none">{badge.emoji}</span>
{badge.label}
</span>
);
}
/**
* A subtle activity pulse ("🔥 Active this week"). Dormant crews render nothing
* unless `showDormant` is set, so the grid highlights life rather than death.
*/
export function CrewActivityPulse({
activity,
showDormant = false,
className,
}: {
activity: CrewActivity | undefined;
showDormant?: boolean;
className?: string;
}) {
const { t } = useTranslation();
if (!activity) return null;
// In dense card grids we only surface positive signals — hide dormant crews.
if (activity.level === 'dormant' && !showDormant) return null;
const meta = ACTIVITY_META[activity.level];
const isHot = activity.level === 'hot';
const upcoming =
activity.upcomingEvents === 1
? t('socialUi.badge.upcomingOne', { count: activity.upcomingEvents })
: t('socialUi.badge.upcomingMany', { count: activity.upcomingEvents });
const coming =
activity.upcomingEvents === 1
? t('socialUi.badge.comingOne', { count: activity.upcomingEvents })
: t('socialUi.badge.comingMany', { count: activity.upcomingEvents });
return (
<span
className={cn('inline-flex items-center gap-1 text-[10px] font-bold', meta.text, className)}
title={
activity.upcomingEvents > 0
? upcoming
: meta.label
}
>
<span className="relative flex h-1.5 w-1.5">
{isHot && (
<span className={cn('absolute inline-flex h-full w-full animate-ping rounded-full opacity-75', meta.dot)} />
)}
<span className={cn('relative inline-flex h-1.5 w-1.5 rounded-full', meta.dot)} />
</span>
{activity.upcomingEvents > 0
? coming
: meta.label}
</span>
);
}