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:
parent
ed3ec86025
commit
7f47ec4809
1 changed files with 183 additions and 126 deletions
|
|
@ -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>(
|
||||||
|
"/api/integrations/google/credentials",
|
||||||
|
{
|
||||||
google_oauth_client_id: googleClientId,
|
google_oauth_client_id: googleClientId,
|
||||||
google_oauth_client_secret: googleClientSecret,
|
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>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue