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:
parent
9ad8cc7e8a
commit
7f4ef20b7c
|
|
@ -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 {
|
||||||
|
|
@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -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>;
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue