Refactor SettingsPage component for improved readability and consistency

Update formatting in SettingsPage.tsx to enhance code clarity by standardizing quotation marks and restructuring object definitions. Maintain functionality while improving overall code style and organization.
This commit is contained in:
Jonny Nguyen 2026-06-26 23:55:39 -07:00
parent ed3ec86025
commit 7f47ec4809

View file

@ -1,149 +1,177 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from "react"
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"
import { Loader2, Play, KeyRound } from 'lucide-react'; import { Loader2, Play, KeyRound } from "lucide-react"
import { apiGet, apiPatch, apiPost } from '../lib/api'; import { apiGet, apiPatch, apiPost } from "../lib/api"
import { useAuth } from '../lib/auth'; import { useAuth } from "../lib/auth"
import type { Settings, JobStatus } from '../lib/types'; import type { Settings, JobStatus } from "../lib/types"
import { PageHeader } from '../components/PageHeader'; import { PageHeader } from "../components/PageHeader"
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, CardContent, CardHeader, CardTitle, CardDescription } from '../components/ui/Card'; import {
import { Alert } from '../components/ui/Alert'; Card,
import { Skeleton } from '../components/ui/Skeleton'; CardContent,
CardHeader,
CardTitle,
CardDescription,
} from "../components/ui/Card"
import { Alert } from "../components/ui/Alert"
import { Skeleton } from "../components/ui/Skeleton"
const JOBS = [ const JOBS = [
{ id: 'dispatcher', label: 'Campaign Dispatcher', path: '/api/jobs/dispatcher/run' }, {
{ id: 'rollover', label: 'Campaign Rollover', path: '/api/jobs/rollover/run' }, id: "dispatcher",
{ id: 'rescuer', label: 'Spam Rescuer', path: '/api/jobs/rescuer/run' }, label: "Campaign Dispatcher",
{ id: 'rollup', label: 'Stats Rollup', path: '/api/jobs/rollup/run' }, path: "/api/jobs/dispatcher/run",
] as const; },
{
id: "rollover",
label: "Campaign Rollover",
path: "/api/jobs/rollover/run",
},
{ id: "rescuer", label: "Spam Rescuer", path: "/api/jobs/rescuer/run" },
{ id: "rollup", label: "Stats Rollup", path: "/api/jobs/rollup/run" },
] as const
type PostmasterIntegrationStatus = { type PostmasterIntegrationStatus = {
configured: boolean; configured: boolean
connected: boolean; connected: boolean
domains: string[]; domains: string[]
message: string; message: string
hasClientId: boolean; hasClientId: boolean
}; }
export function SettingsPage() { export function SettingsPage() {
const queryClient = useQueryClient(); const queryClient = useQueryClient()
const { apiKey, baseUrl, login } = useAuth(); const { apiKey, baseUrl, login } = useAuth()
const [localKey, setLocalKey] = useState(apiKey); const [localKey, setLocalKey] = useState(apiKey)
const [localUrl, setLocalUrl] = useState(baseUrl); const [localUrl, setLocalUrl] = useState(baseUrl)
const [form, setForm] = useState<Settings>({}); const [form, setForm] = useState<Settings>({})
const [message, setMessage] = useState(''); const [message, setMessage] = useState("")
const [error, setError] = useState(''); const [error, setError] = useState("")
const [runningJob, setRunningJob] = useState<string | null>(null); const [runningJob, setRunningJob] = useState<string | null>(null)
const [googleClientId, setGoogleClientId] = useState(''); const [googleClientId, setGoogleClientId] = useState("")
const [googleClientSecret, setGoogleClientSecret] = useState(''); const [googleClientSecret, setGoogleClientSecret] = useState("")
const [microsoftClientId, setMicrosoftClientId] = useState(''); const [microsoftClientId, setMicrosoftClientId] = useState("")
const [microsoftClientSecret, setMicrosoftClientSecret] = useState(''); const [microsoftClientSecret, setMicrosoftClientSecret] = useState("")
const { data: postmasterStatus, refetch: refetchPostmaster } = useQuery({ const { data: postmasterStatus, refetch: refetchPostmaster } = useQuery({
queryKey: ['integrations', 'google', 'status'], queryKey: ["integrations", "google", "status"],
queryFn: () => apiGet<PostmasterIntegrationStatus>('/api/integrations/google/status'), queryFn: () =>
}); apiGet<PostmasterIntegrationStatus>("/api/integrations/google/status"),
})
const { data: microsoftStatus, refetch: refetchMicrosoft } = useQuery({ const { data: microsoftStatus, refetch: refetchMicrosoft } = useQuery({
queryKey: ['integrations', 'microsoft', 'status'], queryKey: ["integrations", "microsoft", "status"],
queryFn: () => apiGet<{ configured: boolean; hasClientId: boolean }>( queryFn: () =>
'/api/integrations/microsoft/status', apiGet<{ configured: boolean; hasClientId: boolean }>(
), "/api/integrations/microsoft/status",
}); ),
})
const saveMicrosoftCredsMutation = useMutation({ const saveMicrosoftCredsMutation = useMutation({
mutationFn: () => mutationFn: () =>
apiPost('/api/integrations/microsoft/credentials', { apiPost("/api/integrations/microsoft/credentials", {
microsoft_oauth_client_id: microsoftClientId, microsoft_oauth_client_id: microsoftClientId,
microsoft_oauth_client_secret: microsoftClientSecret, microsoft_oauth_client_secret: microsoftClientSecret,
}), }),
onSuccess: () => { onSuccess: () => {
setMessage('Microsoft OAuth credentials saved'); setMessage("Microsoft OAuth credentials saved")
refetchMicrosoft(); refetchMicrosoft()
}, },
onError: (err: Error) => setError(err.message), onError: (err: Error) => setError(err.message),
}); })
const saveGoogleCredsMutation = useMutation({ const saveGoogleCredsMutation = useMutation({
mutationFn: () => mutationFn: () =>
apiPost<PostmasterIntegrationStatus>('/api/integrations/google/credentials', { apiPost<PostmasterIntegrationStatus>(
google_oauth_client_id: googleClientId, "/api/integrations/google/credentials",
google_oauth_client_secret: googleClientSecret, {
}), google_oauth_client_id: googleClientId,
google_oauth_client_secret: googleClientSecret,
},
),
onSuccess: () => { onSuccess: () => {
setMessage('Google OAuth credentials saved'); setMessage("Google OAuth credentials saved")
refetchPostmaster(); refetchPostmaster()
}, },
onError: (err: Error) => setError(err.message), onError: (err: Error) => setError(err.message),
}); })
async function connectPostmaster() { async function connectPostmaster() {
setError(''); setError("")
try { try {
const { url } = await apiGet<{ url: string }>('/api/integrations/google/auth-url'); const { url } = await apiGet<{ url: string }>(
window.open(url, '_blank', 'noopener,noreferrer'); "/api/integrations/google/auth-url",
setMessage('Complete Google authorization in the new tab, then refresh this page.'); )
window.open(url, "_blank", "noopener,noreferrer")
setMessage(
"Complete Google authorization in the new tab, then refresh this page.",
)
} catch (err) { } catch (err) {
setError(err instanceof Error ? err.message : 'Failed to start Google OAuth'); setError(
err instanceof Error ? err.message : "Failed to start Google OAuth",
)
} }
} }
useEffect(() => { useEffect(() => {
if (window.location.hash === '#google-postmaster') { if (window.location.hash === "#google-postmaster") {
document.getElementById('google-postmaster')?.scrollIntoView({ behavior: 'smooth' }); document
.getElementById("google-postmaster")
?.scrollIntoView({ behavior: "smooth" })
} }
}, []); }, [])
const { data, isLoading } = useQuery({ const { data, isLoading } = useQuery({
queryKey: ['settings'], queryKey: ["settings"],
queryFn: () => apiGet<Settings>('/api/settings'), queryFn: () => apiGet<Settings>("/api/settings"),
}); })
const { data: jobStatus } = useQuery({ const { data: jobStatus } = useQuery({
queryKey: ['jobs', 'status'], queryKey: ["jobs", "status"],
queryFn: () => apiGet<{ jobs: JobStatus[] }>('/api/jobs/status'), queryFn: () => apiGet<{ jobs: JobStatus[] }>("/api/jobs/status"),
refetchInterval: 30_000, refetchInterval: 30_000,
}); })
useEffect(() => { useEffect(() => {
if (data) setForm(data); if (data) setForm(data)
}, [data]); }, [data])
const saveMutation = useMutation({ const saveMutation = useMutation({
mutationFn: (payload: Settings) => apiPatch<Settings>('/api/settings', payload), mutationFn: (payload: Settings) =>
apiPatch<Settings>("/api/settings", payload),
onSuccess: (updated) => { onSuccess: (updated) => {
setForm(updated); setForm(updated)
queryClient.setQueryData(['settings'], updated); queryClient.setQueryData(["settings"], updated)
setMessage('Settings saved'); setMessage("Settings saved")
setError(''); setError("")
}, },
onError: (err: Error) => setError(err.message), onError: (err: Error) => setError(err.message),
}); })
function handleSaveSettings(e: React.FormEvent) { function handleSaveSettings(e: React.FormEvent) {
e.preventDefault(); e.preventDefault()
saveMutation.mutate(form); saveMutation.mutate(form)
} }
function handleSaveApiConfig(e: React.FormEvent) { function handleSaveApiConfig(e: React.FormEvent) {
e.preventDefault(); e.preventDefault()
login(localKey.trim(), localUrl.trim()); login(localKey.trim(), localUrl.trim())
setMessage('API configuration updated'); setMessage("API configuration updated")
} }
async function runJob(path: string, id: string) { async function runJob(path: string, id: string) {
setRunningJob(id); setRunningJob(id)
setError(''); setError("")
try { try {
await apiPost(path); await apiPost(path)
setMessage(`Job "${id}" completed`); setMessage(`Job "${id}" completed`)
} catch (err) { } catch (err) {
setError(err instanceof Error ? err.message : 'Job failed'); setError(err instanceof Error ? err.message : "Job failed")
} finally { } finally {
setRunningJob(null); setRunningJob(null)
} }
} }
@ -160,7 +188,9 @@ export function SettingsPage() {
<KeyRound className="h-5 w-5" /> <KeyRound className="h-5 w-5" />
API Configuration API Configuration
</CardTitle> </CardTitle>
<CardDescription>Connection settings for this dashboard session</CardDescription> <CardDescription>
Connection settings for this dashboard session
</CardDescription>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<form onSubmit={handleSaveApiConfig} className="space-y-4 max-w-md"> <form onSubmit={handleSaveApiConfig} className="space-y-4 max-w-md">
@ -201,8 +231,10 @@ export function SettingsPage() {
<Label htmlFor="dispatcher_cron">Dispatcher Cron</Label> <Label htmlFor="dispatcher_cron">Dispatcher Cron</Label>
<Input <Input
id="dispatcher_cron" id="dispatcher_cron"
value={form.dispatcher_cron ?? ''} value={form.dispatcher_cron ?? ""}
onChange={(e) => setForm({ ...form, dispatcher_cron: e.target.value })} onChange={(e) =>
setForm({ ...form, dispatcher_cron: e.target.value })
}
placeholder="*/5 * * * *" placeholder="*/5 * * * *"
/> />
</div> </div>
@ -210,8 +242,10 @@ export function SettingsPage() {
<Label htmlFor="rescuer_cron">Rescuer Cron</Label> <Label htmlFor="rescuer_cron">Rescuer Cron</Label>
<Input <Input
id="rescuer_cron" id="rescuer_cron"
value={form.rescuer_cron ?? ''} value={form.rescuer_cron ?? ""}
onChange={(e) => setForm({ ...form, rescuer_cron: e.target.value })} onChange={(e) =>
setForm({ ...form, rescuer_cron: e.target.value })
}
placeholder="*/10 * * * *" placeholder="*/10 * * * *"
/> />
</div> </div>
@ -219,28 +253,36 @@ export function SettingsPage() {
<Label htmlFor="ramp_increment">Ramp Increment</Label> <Label htmlFor="ramp_increment">Ramp Increment</Label>
<Input <Input
id="ramp_increment" id="ramp_increment"
value={form.ramp_increment ?? ''} value={form.ramp_increment ?? ""}
onChange={(e) => setForm({ ...form, ramp_increment: e.target.value })} onChange={(e) =>
setForm({ ...form, ramp_increment: e.target.value })
}
/> />
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="ai_model">AI Model</Label> <Label htmlFor="ai_model">AI Model</Label>
<Input <Input
id="ai_model" id="ai_model"
value={form.ai_model ?? ''} value={form.ai_model ?? ""}
onChange={(e) => setForm({ ...form, ai_model: e.target.value })} onChange={(e) =>
setForm({ ...form, ai_model: e.target.value })
}
/> />
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="industries">Industries (JSON array)</Label> <Label htmlFor="industries">Industries (JSON array)</Label>
<Input <Input
id="industries" id="industries"
value={form.industries ?? ''} value={form.industries ?? ""}
onChange={(e) => setForm({ ...form, industries: e.target.value })} onChange={(e) =>
setForm({ ...form, industries: e.target.value })
}
/> />
</div> </div>
<Button type="submit" disabled={saveMutation.isPending}> <Button type="submit" disabled={saveMutation.isPending}>
{saveMutation.isPending && <Loader2 className="h-4 w-4 animate-spin" />} {saveMutation.isPending && (
<Loader2 className="h-4 w-4 animate-spin" />
)}
Save Settings Save Settings
</Button> </Button>
</form> </form>
@ -250,17 +292,18 @@ export function SettingsPage() {
<Card id="microsoft-outlook"> <Card id="microsoft-outlook">
<CardHeader> <CardHeader>
<CardTitle>Microsoft Outlook (@live.com)</CardTitle> <CardTitle>Microsoft Outlook</CardTitle>
<CardDescription> <CardDescription>
Required for consumer Outlook accounts app passwords no longer work for SMTP/IMAP Required for consumer Outlook accounts app passwords no longer
work for SMTP/IMAP
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
<CardContent className="space-y-4 max-w-2xl"> <CardContent className="space-y-4 max-w-2xl">
{microsoftStatus && ( {microsoftStatus && (
<Alert variant={microsoftStatus.configured ? 'success' : 'warning'}> <Alert variant={microsoftStatus.configured ? "success" : "warning"}>
{microsoftStatus.configured {microsoftStatus.configured
? 'Microsoft OAuth credentials are configured. Connect mailboxes from Mailboxes → Add.' ? "Microsoft OAuth credentials are configured. Connect mailboxes from Mailboxes → Add."
: 'Add Azure app credentials below before connecting @live.com mailboxes.'} : "Add Azure app credentials below before connecting @live.com mailboxes."}
</Alert> </Alert>
)} )}
@ -268,7 +311,7 @@ export function SettingsPage() {
<p className="font-medium">Setup (one-time)</p> <p className="font-medium">Setup (one-time)</p>
<ol className="list-decimal pl-5 space-y-1"> <ol className="list-decimal pl-5 space-y-1">
<li> <li>
In{' '} In{" "}
<a <a
href="https://portal.azure.com/#view/Microsoft_AAD_RegisteredApps/ApplicationsListBlade" href="https://portal.azure.com/#view/Microsoft_AAD_RegisteredApps/ApplicationsListBlade"
target="_blank" target="_blank"
@ -277,20 +320,24 @@ export function SettingsPage() {
> >
Azure App registrations Azure App registrations
</a> </a>
, create a Web app. Supported account types: personal Microsoft accounts. , create a Web app. Supported account types: personal Microsoft
accounts.
</li> </li>
<li> <li>
Add redirect URI:{' '} Add redirect URI:{" "}
<code className="text-xs bg-white px-1 py-0.5 rounded"> <code className="text-xs bg-white px-1 py-0.5 rounded">
http://localhost:3000/api/integrations/microsoft/callback http://localhost:3000/api/integrations/microsoft/callback
</code> </code>
</li> </li>
<li> <li>
Under API permissions, add delegated{' '} Under API permissions, add delegated{" "}
<strong>IMAP.AccessAsUser.All</strong> and <strong>SMTP.Send</strong> for Office <strong>IMAP.AccessAsUser.All</strong> and{" "}
365 / Outlook. <strong>SMTP.Send</strong> for Office 365 / Outlook.
</li>
<li>
Create a client secret, paste ID + secret below, then add
mailboxes via Connect Microsoft.
</li> </li>
<li>Create a client secret, paste ID + secret below, then add mailboxes via Connect Microsoft.</li>
</ol> </ol>
</div> </div>
@ -316,7 +363,9 @@ export function SettingsPage() {
type="button" type="button"
variant="outline" variant="outline"
disabled={ disabled={
!microsoftClientId || !microsoftClientSecret || saveMicrosoftCredsMutation.isPending !microsoftClientId ||
!microsoftClientSecret ||
saveMicrosoftCredsMutation.isPending
} }
onClick={() => saveMicrosoftCredsMutation.mutate()} onClick={() => saveMicrosoftCredsMutation.mutate()}
> >
@ -330,16 +379,17 @@ export function SettingsPage() {
<CardHeader> <CardHeader>
<CardTitle>Google Postmaster Tools</CardTitle> <CardTitle>Google Postmaster Tools</CardTitle>
<CardDescription> <CardDescription>
Connect once to auto-verify Gmail/Workspace domains in campaign compliance checks Connect once to auto-verify Gmail/Workspace domains in campaign
compliance checks
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
<CardContent className="space-y-4 max-w-2xl"> <CardContent className="space-y-4 max-w-2xl">
{postmasterStatus && ( {postmasterStatus && (
<Alert variant={postmasterStatus.connected ? 'success' : 'warning'}> <Alert variant={postmasterStatus.connected ? "success" : "warning"}>
{postmasterStatus.message} {postmasterStatus.message}
{postmasterStatus.domains.length > 0 && ( {postmasterStatus.domains.length > 0 && (
<span className="block mt-1 text-sm"> <span className="block mt-1 text-sm">
Domains: {postmasterStatus.domains.join(', ')} Domains: {postmasterStatus.domains.join(", ")}
</span> </span>
)} )}
</Alert> </Alert>
@ -349,7 +399,7 @@ export function SettingsPage() {
<p className="font-medium">Setup (one-time)</p> <p className="font-medium">Setup (one-time)</p>
<ol className="list-decimal pl-5 space-y-1"> <ol className="list-decimal pl-5 space-y-1">
<li> <li>
In{' '} In{" "}
<a <a
href="https://console.cloud.google.com/apis/library/gmailpostmastertools.googleapis.com" href="https://console.cloud.google.com/apis/library/gmailpostmastertools.googleapis.com"
target="_blank" target="_blank"
@ -361,13 +411,14 @@ export function SettingsPage() {
, enable the <strong>Gmail Postmaster Tools API</strong>. , enable the <strong>Gmail Postmaster Tools API</strong>.
</li> </li>
<li> <li>
Create OAuth 2.0 credentials (Web application). Add redirect URI:{' '} Create OAuth 2.0 credentials (Web application). Add redirect
URI:{" "}
<code className="text-xs bg-white px-1 py-0.5 rounded"> <code className="text-xs bg-white px-1 py-0.5 rounded">
http://localhost:3000/api/integrations/google/callback http://localhost:3000/api/integrations/google/callback
</code> </code>
</li> </li>
<li> <li>
Add your sending domain at{' '} Add your sending domain at{" "}
<a <a
href="https://postmaster.google.com" href="https://postmaster.google.com"
target="_blank" target="_blank"
@ -375,10 +426,12 @@ export function SettingsPage() {
className="text-primary-600 underline" className="text-primary-600 underline"
> >
postmaster.google.com postmaster.google.com
</a>{' '} </a>{" "}
and complete DNS verification there. and complete DNS verification there.
</li> </li>
<li>Paste Client ID and Secret below, save, then click Connect.</li> <li>
Paste Client ID and Secret below, save, then click Connect.
</li>
</ol> </ol>
</div> </div>
@ -405,7 +458,11 @@ export function SettingsPage() {
<Button <Button
type="button" type="button"
variant="outline" variant="outline"
disabled={!googleClientId || !googleClientSecret || saveGoogleCredsMutation.isPending} disabled={
!googleClientId ||
!googleClientSecret ||
saveGoogleCredsMutation.isPending
}
onClick={() => saveGoogleCredsMutation.mutate()} onClick={() => saveGoogleCredsMutation.mutate()}
> >
Save OAuth Credentials Save OAuth Credentials
@ -439,10 +496,10 @@ export function SettingsPage() {
<span className="text-slate-500"> <span className="text-slate-500">
{job.lastRunAt {job.lastRunAt
? `${new Date(job.lastRunAt).toLocaleString()}${ ? `${new Date(job.lastRunAt).toLocaleString()}${
job.success ? 'OK' : 'Failed' job.success ? "OK" : "Failed"
}` }`
: 'Never run'} : "Never run"}
{job.message ? ` (${job.message})` : ''} {job.message ? ` (${job.message})` : ""}
</span> </span>
</div> </div>
))} ))}
@ -469,5 +526,5 @@ export function SettingsPage() {
</CardContent> </CardContent>
</Card> </Card>
</div> </div>
); )
} }