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

118 lines
4.5 KiB
TypeScript

/**
* RegionBackfillCard — admin-only: backfill missing `city`/`country` tags on the
* logged-in user's own legacy spots & shops.
*
* Why: spots created before reverse geocoding existed have coordinates (so they
* appear on the map) but no region tags (so `/stats` can only call them
* "Uncategorised", under-counting the country vs the map). This re-derives the
* tags from each pin and republishes the addressable event.
*
* Renders nothing for non-admins, so it can sit in Settings → Advanced beside the
* other owner cards.
*/
import { useState } from 'react';
import { MapPin, Loader2 } from 'lucide-react';
import { Card, CardContent } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { useCurrentUser } from '@/hooks/useCurrentUser';
import { useRegionBackfill, type BackfillResult } from '@/hooks/useRegionBackfill';
import { isAdminPubkey } from '@/lib/admins';
import { toast } from '@/hooks/useToast';
import { useTranslation } from '@/contexts/language';
export function RegionBackfillCard() {
const { user } = useCurrentUser();
const isAdmin = isAdminPubkey(user?.pubkey);
const { candidates, isLoading, backfill, progress, isRunning } = useRegionBackfill(isAdmin);
const [summary, setSummary] = useState<BackfillResult | null>(null);
const { t } = useTranslation();
if (!isAdmin) return null;
const count = candidates.length;
const run = async () => {
setSummary(null);
try {
const result = await backfill();
setSummary(result);
if (result.updated > 0) {
toast({
title: t('settingsUi.regionBackfill.taggedTitle', { count: result.updated, s: result.updated === 1 ? '' : 's' }),
description:
result.skipped > 0
? t('settingsUi.regionBackfill.taggedSkipped', { count: result.skipped })
: t('settingsUi.regionBackfill.taggedOk'),
});
} else {
toast({
title: t('settingsUi.regionBackfill.nothingTitle'),
description: t('settingsUi.regionBackfill.nothingDesc'),
variant: 'destructive',
});
}
} catch (err) {
toast({
title: t('settingsUi.regionBackfill.failedTitle'),
description: err instanceof Error ? err.message : t('common.tryAgain'),
variant: 'destructive',
});
}
};
return (
<Card className="border-white/10 bg-white/2">
<CardContent className="py-3.5 space-y-2.5">
<p className="flex items-center gap-2 text-sm font-bold text-white">
<MapPin className="h-4 w-4 text-[#ccff00]" />
{t('settingsUi.regionBackfill.title')}
<span className="rounded bg-[#ccff00]/10 px-1.5 py-0.5 text-[9px] font-black uppercase tracking-widest text-[#ccff00]/70">
{t('settingsUi.regionBackfill.admin')}
</span>
</p>
<p className="text-xs text-muted-foreground">
{t('settingsUi.regionBackfill.descBefore')}{' '}
<strong>{t('settingsUi.regionBackfill.descStrong')}</strong>{' '}
{t('settingsUi.regionBackfill.descAfter')}
</p>
{isLoading ? (
<p className="text-xs text-muted-foreground">{t('settingsUi.regionBackfill.scanning')}</p>
) : count === 0 ? (
<p className="text-xs text-[#ccff00]">
{t('settingsUi.regionBackfill.allTagged')}
</p>
) : (
<>
<p className="text-xs text-muted-foreground">
{t('settingsUi.regionBackfill.countLine', { count, s: count === 1 ? '' : 's' })}
</p>
<div className="flex flex-wrap items-center gap-2">
<Button
onClick={run}
disabled={isRunning}
className="h-9 gap-1.5 bg-[#ccff00] font-bold text-black hover:bg-[#ccff00]/90"
>
{isRunning ? (
<Loader2 className="h-4 w-4 animate-spin" />
) : (
<MapPin className="h-4 w-4" />
)}
{isRunning && progress
? t('settingsUi.regionBackfill.tagging', { done: progress.done, total: progress.total })
: t('settingsUi.regionBackfill.run')}
</Button>
{summary && (
<span className="text-xs text-muted-foreground">
{t('settingsUi.regionBackfill.summary', { updated: summary.updated, skipped: summary.skipped, failed: summary.failed })}
</span>
)}
</div>
</>
)}
</CardContent>
</Card>
);
}