~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).
118 lines
4.5 KiB
TypeScript
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>
|
|
);
|
|
}
|