/** * 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(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 (

{t('settingsUi.regionBackfill.title')} {t('settingsUi.regionBackfill.admin')}

{t('settingsUi.regionBackfill.descBefore')}{' '} {t('settingsUi.regionBackfill.descStrong')}{' '} {t('settingsUi.regionBackfill.descAfter')}

{isLoading ? (

{t('settingsUi.regionBackfill.scanning')}

) : count === 0 ? (

{t('settingsUi.regionBackfill.allTagged')}

) : ( <>

{t('settingsUi.regionBackfill.countLine', { count, s: count === 1 ? '' : 's' })}

{summary && ( {t('settingsUi.regionBackfill.summary', { updated: summary.updated, skipped: summary.skipped, failed: summary.failed })} )}
)}
); }