From c9b5f9356991cba2e75f3dea689b436824aa1d13 Mon Sep 17 00:00:00 2001 From: null Date: Sat, 4 Jul 2026 21:19:39 -0500 Subject: [PATCH] refactor(ts): convert LoginModeCard + EmailNotifCard admin cards to TSX (B18) LoginModeCard (AuthModeConfig; AdminUser[]) and EmailNotifCard (EmailConfig + keyed set(k,v)). typecheck 0, build green. Co-Authored-By: Claude Opus 4.8 --- ...{EmailNotifCard.jsx => EmailNotifCard.tsx} | 49 ++++++++++++------- .../{LoginModeCard.jsx => LoginModeCard.tsx} | 27 ++++++---- 2 files changed, 49 insertions(+), 27 deletions(-) rename client/components/admin/{EmailNotifCard.jsx => EmailNotifCard.tsx} (87%) rename client/components/admin/{LoginModeCard.jsx => LoginModeCard.tsx} (92%) diff --git a/client/components/admin/EmailNotifCard.jsx b/client/components/admin/EmailNotifCard.tsx similarity index 87% rename from client/components/admin/EmailNotifCard.jsx rename to client/components/admin/EmailNotifCard.tsx index 45ea319..0cfe96e 100644 --- a/client/components/admin/EmailNotifCard.jsx +++ b/client/components/admin/EmailNotifCard.tsx @@ -1,7 +1,8 @@ -import React, { useState, useEffect } from 'react'; +import { useState, useEffect } from 'react'; import { Eye, EyeOff } from 'lucide-react'; import { toast } from 'sonner'; import { api } from '@/api'; +import { errMessage } from '@/lib/utils'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card'; @@ -10,18 +11,32 @@ import { } from '@/components/ui/select'; import { SectionHeading, FieldRow, Toggle } from './adminShared'; -export default function EmailNotifCard() { - const DEFAULTS = { - enabled: false, - sender_name: '', sender_address: '', - smtp_host: '', smtp_port: '587', smtp_encryption: 'starttls', - smtp_self_signed: false, - smtp_username: '', smtp_password: '', - allow_user_config: false, - global_recipient: '', - }; +interface EmailConfig { + enabled: boolean; + sender_name: string; + sender_address: string; + smtp_host: string; + smtp_port: string; + smtp_encryption: string; + smtp_self_signed: boolean; + smtp_username: string; + smtp_password: string; + allow_user_config: boolean; + global_recipient: string; +} - const [cfg, setCfg] = useState(DEFAULTS); +const DEFAULTS: EmailConfig = { + enabled: false, + sender_name: '', sender_address: '', + smtp_host: '', smtp_port: '587', smtp_encryption: 'starttls', + smtp_self_signed: false, + smtp_username: '', smtp_password: '', + allow_user_config: false, + global_recipient: '', +}; + +export default function EmailNotifCard() { + const [cfg, setCfg] = useState(DEFAULTS); const [loading, setLoading] = useState(true); const [loadError, setLoadError] = useState(''); const [saving, setSaving] = useState(false); @@ -31,12 +46,12 @@ export default function EmailNotifCard() { useEffect(() => { api.notifAdmin() - .then(d => setCfg({ ...DEFAULTS, ...d })) - .catch(err => setLoadError(err.message || 'Failed to load email settings')) + .then(d => setCfg({ ...DEFAULTS, ...(d as Partial) })) + .catch(err => setLoadError(errMessage(err, 'Failed to load email settings'))) .finally(() => setLoading(false)); }, []); // eslint-disable-line react-hooks/exhaustive-deps - const set = (k, v) => setCfg(p => ({ ...p, [k]: v })); + const set = (k: keyof EmailConfig, v: string | boolean) => setCfg(p => ({ ...p, [k]: v }) as EmailConfig); const handleSave = async () => { setSaving(true); @@ -44,7 +59,7 @@ export default function EmailNotifCard() { await api.saveNotifAdmin(cfg); toast.success('Email settings saved.'); } catch (err) { - toast.error(err.message || 'Failed to save.'); + toast.error(errMessage(err, 'Failed to save.')); } finally { setSaving(false); } @@ -57,7 +72,7 @@ export default function EmailNotifCard() { await api.testEmail({ to: testEmail }); toast.success('Test email sent.'); } catch (err) { - toast.error(err.message || 'Failed to send test email.'); + toast.error(errMessage(err, 'Failed to send test email.')); } finally { setTesting(false); } diff --git a/client/components/admin/LoginModeCard.jsx b/client/components/admin/LoginModeCard.tsx similarity index 92% rename from client/components/admin/LoginModeCard.jsx rename to client/components/admin/LoginModeCard.tsx index 7156c2c..20ca2c7 100644 --- a/client/components/admin/LoginModeCard.jsx +++ b/client/components/admin/LoginModeCard.tsx @@ -1,7 +1,7 @@ -import React, { useState, useEffect } from 'react'; +import { useState, useEffect } from 'react'; import { toast } from 'sonner'; import { api } from '@/api'; -import { cn } from '@/lib/utils'; +import { cn, errMessage } from '@/lib/utils'; import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { Label } from '@/components/ui/label'; @@ -15,9 +15,15 @@ import { } from '@/components/ui/alert-dialog'; import { LogIn, UserCheck, ShieldCheck } from 'lucide-react'; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; +import type { AdminUser } from './UsersTable'; -export default function LoginModeCard({ users, onModeChange }) { - const [modeData, setModeData] = useState(null); +interface AuthModeConfig { + auth_mode?: string; + default_user_id?: number | null; +} + +export default function LoginModeCard({ users, onModeChange }: { users?: AdminUser[]; onModeChange?: (mode: string) => void }) { + const [modeData, setModeData] = useState(null); const [loading, setLoading] = useState(true); const [loadError, setLoadError] = useState(''); const [saving, setSaving] = useState(false); @@ -27,25 +33,26 @@ export default function LoginModeCard({ users, onModeChange }) { useEffect(() => { api.authModeConfig() - .then(d => { + .then(raw => { + const d = raw as AuthModeConfig; setModeData(d); const mode = d.auth_mode === 'single' ? 'single' : 'multi'; setSelected(mode); setSelectedUser(d.default_user_id?.toString() || ''); onModeChange?.(mode); }) - .catch(err => setLoadError(err.message || 'Failed to load login mode config')) + .catch(err => setLoadError(errMessage(err, 'Failed to load login mode config'))) .finally(() => setLoading(false)); }, []); // eslint-disable-line - const doSetMode = async (mode, userId) => { + const doSetMode = async (mode: string, userId: string | null) => { setSaving(true); try { await api.setAuthMode({ auth_mode: mode, - default_user_id: mode === 'single' ? parseInt(userId, 10) : null, + default_user_id: mode === 'single' ? parseInt(userId || '', 10) : null, }); - const d = await api.authModeConfig(); + const d = await api.authModeConfig() as AuthModeConfig; setModeData(d); const resolved = d.auth_mode === 'single' ? 'single' : 'multi'; setSelected(resolved); @@ -53,7 +60,7 @@ export default function LoginModeCard({ users, onModeChange }) { onModeChange?.(resolved); toast.success(mode === 'single' ? 'No Login mode enabled.' : 'Login requirement restored.'); } catch (err) { - toast.error(err.message || 'Failed to update login mode.'); + toast.error(errMessage(err, 'Failed to update login mode.')); // revert UI selection on error setSelected(modeData?.auth_mode === 'single' ? 'single' : 'multi'); } finally {