refactor(ts): convert admin shared helpers + AddUserCard; drop dead PrivacyAdminCard (B16)

adminShared.tsx (SectionHeading/FieldRow/Toggle/formatDateTime/BackupTypeBadge —
shared by the admin cards) and AddUserCard.tsx. Deleted PrivacyAdminCard.jsx:
TypeScript surfaced that it's imported nowhere AND calls non-existent
api.privacySettings/setPrivacySettings (the geolocation toggle actually lives on
/profile/settings) — dead code with broken calls, so removed rather than
converted. typecheck 0, build green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
null 2026-07-04 20:44:40 -05:00
parent 9ad8cc7e8a
commit 7f4ef20b7c
3 changed files with 19 additions and 71 deletions

View File

@ -1,18 +1,19 @@
import React, { useState } from 'react'; import { useState } from 'react';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { api } from '@/api'; import { api } from '@/api';
import { errMessage } from '@/lib/utils';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input'; import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label'; import { Label } from '@/components/ui/label';
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card'; import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card';
export default function AddUserCard({ onCreated }) { export default function AddUserCard({ onCreated }: { onCreated: () => void }) {
const [username, setUsername] = useState(''); const [username, setUsername] = useState('');
const [password, setPassword] = useState(''); const [password, setPassword] = useState('');
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [error, setError] = useState(''); const [error, setError] = useState('');
const handleCreate = async (e) => { const handleCreate = async (e: React.FormEvent) => {
e.preventDefault(); e.preventDefault();
setError(''); setError('');
@ -32,7 +33,7 @@ export default function AddUserCard({ onCreated }) {
setError(''); setError('');
onCreated(); onCreated();
} catch (err) { } catch (err) {
const errorMessage = err.message || 'Failed to create user.'; const errorMessage = errMessage(err, 'Failed to create user.');
setError(errorMessage); setError(errorMessage);
toast.error(errorMessage); toast.error(errorMessage);
} finally { } finally {

View File

@ -1,59 +0,0 @@
import React, { useState, useEffect } from 'react';
import { toast } from 'sonner';
import { api } from '@/api';
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card';
import { FieldRow, Toggle } from './adminShared';
export default function PrivacyAdminCard() {
const [geoEnabled, setGeoEnabled] = useState(false);
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false);
useEffect(() => {
api.privacySettings()
.then(d => { setGeoEnabled(!!d.geolocation_enabled); })
.catch(() => toast.error('Failed to load privacy settings'))
.finally(() => setLoading(false));
}, []);
async function toggleGeo(next) {
setSaving(true);
try {
const d = await api.setPrivacySettings({ geolocation_enabled: next });
setGeoEnabled(!!d.geolocation_enabled);
toast.success(next ? 'Geolocation enabled' : 'Geolocation disabled');
} catch {
toast.error('Failed to update privacy settings');
} finally {
setSaving(false);
}
}
return (
<Card>
<CardHeader>
<CardTitle>Privacy</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<FieldRow label="Login geolocation">
<div className="flex items-center gap-3">
<Toggle
checked={geoEnabled}
onChange={toggleGeo}
disabled={loading || saving}
label="Enable login geolocation"
/>
<span className="text-sm text-muted-foreground">
{geoEnabled ? 'On' : 'Off (default)'}
</span>
</div>
</FieldRow>
<p className="text-xs text-muted-foreground">
When enabled, new-device logins resolve the login IP to a city/region via{' '}
<span className="font-mono">ip-api.com</span> over plain HTTP. Disable to keep
all login data on-device. Location data is encrypted at rest.
</p>
</CardContent>
</Card>
);
}

View File

@ -1,12 +1,12 @@
import React from 'react'; import type { ReactNode } from 'react';
import { Badge } from '@/components/ui/badge'; import { Badge } from '@/components/ui/badge';
import { Label } from '@/components/ui/label'; import { Label } from '@/components/ui/label';
export function SectionHeading({ children }) { export function SectionHeading({ children }: { children?: ReactNode }) {
return <h2 className="text-base font-semibold text-foreground">{children}</h2>; return <h2 className="text-base font-semibold text-foreground">{children}</h2>;
} }
export function FieldRow({ label, children }) { export function FieldRow({ label, children }: { label?: ReactNode; children?: ReactNode }) {
return ( return (
<div className="grid gap-2 lg:grid-cols-[200px_1fr] lg:items-center lg:gap-4"> <div className="grid gap-2 lg:grid-cols-[200px_1fr] lg:items-center lg:gap-4">
<Label className="text-muted-foreground lg:text-right">{label}</Label> <Label className="text-muted-foreground lg:text-right">{label}</Label>
@ -15,7 +15,12 @@ export function FieldRow({ label, children }) {
); );
} }
export function Toggle({ checked, onChange, label, disabled = false }) { export function Toggle({ checked, onChange, label, disabled = false }: {
checked: boolean;
onChange: (checked: boolean) => void;
label?: string;
disabled?: boolean;
}) {
return ( return (
<button <button
type="button" type="button"
@ -31,20 +36,21 @@ export function Toggle({ checked, onChange, label, disabled = false }) {
); );
} }
export function formatDateTime(value) { export function formatDateTime(value: string | null | undefined): string {
if (!value) return '—'; if (!value) return '—';
const date = new Date(value); const date = new Date(value);
if (Number.isNaN(date.getTime())) return value; if (Number.isNaN(date.getTime())) return value;
return date.toLocaleString(); return date.toLocaleString();
} }
export function BackupTypeBadge({ type }) { export function BackupTypeBadge({ type }: { type?: string | null }) {
const cls = { const map: Record<string, string> = {
manual: 'bg-blue-500/15 text-blue-400 border-blue-500/20', manual: 'bg-blue-500/15 text-blue-400 border-blue-500/20',
scheduled: 'bg-emerald-500/15 text-emerald-400 border-emerald-500/20', scheduled: 'bg-emerald-500/15 text-emerald-400 border-emerald-500/20',
imported: 'bg-violet-500/15 text-violet-400 border-violet-500/20', imported: 'bg-violet-500/15 text-violet-400 border-violet-500/20',
'pre-restore': 'bg-amber-500/15 text-amber-400 border-amber-500/20', 'pre-restore': 'bg-amber-500/15 text-amber-400 border-amber-500/20',
}[type] || 'bg-muted text-muted-foreground border-border'; };
const cls = map[type ?? ''] || 'bg-muted text-muted-foreground border-border';
return <Badge className={cls}>{type || 'backup'}</Badge>; return <Badge className={cls}>{type || 'backup'}</Badge>;
} }