The admin backfill lived only in Settings -> Advanced, so an admin staring at the 'Uncategorised' row had no obvious way to fix it. Render the admin-only RegionBackfillCard inline in the region section when uncategorised > 0, and clarify the zero-state (remaining untagged pins belong to other mappers).
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';
|
|
|
|
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);
|
|
|
|
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: `Tagged ${result.updated} location${result.updated === 1 ? '' : 's'}`,
|
|
description:
|
|
result.skipped > 0
|
|
? `${result.skipped} skipped (no coordinates or no geocoder match).`
|
|
: 'Region tags written to the community relay.',
|
|
});
|
|
} else {
|
|
toast({
|
|
title: 'Nothing to tag',
|
|
description: 'No pins could be geocoded. Try again later.',
|
|
variant: 'destructive',
|
|
});
|
|
}
|
|
} catch (err) {
|
|
toast({
|
|
title: 'Backfill failed',
|
|
description: err instanceof Error ? err.message : 'Try again.',
|
|
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]" />
|
|
Backfill region tags
|
|
<span className="rounded bg-[#ccff00]/10 px-1.5 py-0.5 text-[9px] font-black uppercase tracking-widest text-[#ccff00]/70">
|
|
Admin
|
|
</span>
|
|
</p>
|
|
<p className="text-xs text-muted-foreground">
|
|
Pins made before geocoding existed have coordinates but no city/country tag, so the map
|
|
draws them while Community Stats can only say “Uncategorised”. This re-derives the tags
|
|
from each pin and republishes <strong>your own</strong> untagged spots & shops.
|
|
</p>
|
|
|
|
{isLoading ? (
|
|
<p className="text-xs text-muted-foreground">Scanning your spots…</p>
|
|
) : count === 0 ? (
|
|
<p className="text-xs text-[#ccff00]">
|
|
Every spot & shop you own already has a region tag. 🎉 Any remaining
|
|
“Uncategorised” pins belong to other mappers — only their own keys can tag them.
|
|
</p>
|
|
) : (
|
|
<>
|
|
<p className="text-xs text-muted-foreground">
|
|
{count} of your spot{count === 1 ? '' : 's'}/shops have no region tag. Your signer may
|
|
ask to approve each one.
|
|
</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
|
|
? `Tagging ${progress.done}/${progress.total}…`
|
|
: 'Backfill region tags'}
|
|
</Button>
|
|
{summary && (
|
|
<span className="text-xs text-muted-foreground">
|
|
{summary.updated} updated · {summary.skipped} skipped · {summary.failed} failed
|
|
</span>
|
|
)}
|
|
</div>
|
|
</>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|