EMAILOUT / frontend /src /components /settings /ConnectMailboxSettings.jsx
Seth
update
bdec327
import React, { useCallback, useEffect, useState } from 'react';
import { Mail, RefreshCw, Loader2 } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { apiFetch } from '@/lib/api';
import { cn } from '@/lib/utils';
export default function ConnectMailboxSettings() {
const [loading, setLoading] = useState(true);
const [busy, setBusy] = useState(false);
const [accounts, setAccounts] = useState([]);
const [defaultRefId, setDefaultRefId] = useState(null);
const load = useCallback(async () => {
setLoading(true);
try {
const r = await apiFetch('/api/unipile/mailbox/campaign-defaults');
const d = r.ok ? await r.json() : {};
setAccounts(d.accounts || []);
setDefaultRefId(d.default_mailbox_unipile_account_ref_id ?? null);
} catch {
setAccounts([]);
} finally {
setLoading(false);
}
}, []);
useEffect(() => {
load();
}, [load]);
const connectAccount = async () => {
setBusy(true);
try {
const res = await apiFetch('/api/unipile/mailbox/hosted-link', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
label: 'Mailbox',
}),
});
const data = await res.json().catch(() => ({}));
if (!res.ok) throw new Error(data.detail || 'Could not start mailbox connect');
if (data.url) window.location.href = data.url;
} catch (e) {
alert(e.message || 'Connect failed');
} finally {
setBusy(false);
}
};
return (
<section className="rounded-2xl border border-slate-200 bg-white p-6 shadow-sm">
<div className="flex flex-wrap items-start justify-between gap-3">
<div className="flex items-center gap-2 font-semibold text-slate-800">
<Mail className="h-5 w-5 text-violet-600" />
Connect Mailbox
</div>
<Button type="button" variant="outline" size="sm" disabled={loading || busy} onClick={load}>
<RefreshCw className={cn('mr-2 h-4 w-4', loading && 'animate-spin')} />
Refresh
</Button>
</div>
<div className="mt-4 flex flex-col gap-3 md:flex-row md:items-end">
<Button type="button" disabled={busy} onClick={connectAccount} className="bg-slate-900 text-white">
Connect mailbox
</Button>
</div>
{loading ? (
<div className="mt-6 flex justify-center py-8 text-slate-400">
<Loader2 className="h-8 w-8 animate-spin" />
</div>
) : accounts.length === 0 ? (
<p className="mt-6 text-sm text-slate-500">Connected mailboxes: 0</p>
) : (
<div className="mt-6 space-y-3">
<p className="text-sm font-medium text-slate-700">
Connected mailboxes ({accounts.length}) — default for email sequence steps
</p>
<ul className="space-y-2">
{accounts.map((a) => {
const name = a.display_name || a.label || 'Mailbox';
const checked = defaultRefId != null && Number(defaultRefId) === Number(a.id);
return (
<li
key={a.id}
className={cn(
'flex cursor-pointer items-center gap-3 rounded-xl border px-3 py-2 transition',
checked ? 'border-violet-300 bg-violet-50/60' : 'border-slate-200 hover:bg-slate-50'
)}
onClick={() => setDefaultRefId(a.id)}
>
<input
type="radio"
className="h-4 w-4 accent-violet-600"
checked={checked}
onChange={() => setDefaultRefId(a.id)}
/>
<div className="min-w-0 flex-1">
<p className="truncate font-medium text-slate-900">{name}</p>
<p className="truncate text-xs text-slate-500">{a.label}</p>
</div>
<span className="text-[10px] font-semibold uppercase text-slate-400">
{a.status || '—'}
</span>
</li>
);
})}
</ul>
</div>
)}
</section>
);
}