Add backfill normalized usernames button to Admin page

This commit is contained in:
2026-03-11 20:56:25 -07:00
parent a6734a1fe4
commit 7904207f64
2 changed files with 68 additions and 0 deletions
+11
View File
@@ -85,3 +85,14 @@ export async function retroactiveAchievements(): Promise<RetroactiveAchievements
);
return data;
}
export interface BackfillUsernamesResult {
backfilled: number;
}
export async function backfillUsernames(): Promise<BackfillUsernamesResult> {
const { data } = await apiClient.post<BackfillUsernamesResult>(
'/admin/backfill-usernames',
);
return data;
}
+57
View File
@@ -13,10 +13,12 @@ import {
recalculateScores,
generateChallenge,
retroactiveAchievements,
backfillUsernames,
type PlatformStats,
type AdminUser,
type RecalculateResult,
type RetroactiveAchievementsResult,
type BackfillUsernamesResult,
} from '@/api/admin';
import {
Shield,
@@ -58,6 +60,8 @@ export default function Admin() {
<Separator />
<AchievementsSection />
<Separator />
<BackfillUsernamesSection />
<Separator />
<GenerateChallengeSection />
<Separator />
<UsersSection />
@@ -227,6 +231,59 @@ function AchievementsSection() {
);
}
function BackfillUsernamesSection() {
const [loading, setLoading] = useState(false);
const [result, setResult] = useState<BackfillUsernamesResult | null>(null);
const [error, setError] = useState('');
const handleBackfill = async () => {
setLoading(true);
setError('');
setResult(null);
try {
const res = await backfillUsernames();
setResult(res);
} catch {
setError('Failed to backfill usernames.');
} finally {
setLoading(false);
}
};
return (
<div>
<h2 className="mb-3 flex items-center gap-2 text-lg font-semibold">
<Users className="h-5 w-5" />
Backfill Normalized Usernames
</h2>
<p className="mb-3 text-sm text-muted-foreground">
Populates the normalized username column for existing users so they can
be found via friend search.
</p>
<Button onClick={handleBackfill} disabled={loading}>
{loading ? (
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
) : (
<Users className="mr-2 h-4 w-4" />
)}
Backfill Usernames
</Button>
{result && (
<Alert className="mt-3">
<AlertDescription>
Backfilled {result.backfilled} user{result.backfilled !== 1 ? 's' : ''}.
</AlertDescription>
</Alert>
)}
{error && (
<Alert variant="destructive" className="mt-3">
<AlertDescription>{error}</AlertDescription>
</Alert>
)}
</div>
);
}
function GenerateChallengeSection() {
const [date, setDate] = useState('');
const [difficulty, setDifficulty] = useState('medium');