Download frontend/src/components/settings/ConnectMailboxSettings.jsx from Seth0330/qualifier: direct link, hf CLI and curl.
- Browser
- Download file 5.17 kB
-
https://huggingface.co/spaces/Seth0330/qualifier/resolve/main/frontend/src/components/settings/ConnectMailboxSettings.jsx
- Command line
-
hf download hf://spaces/Seth0330/qualifier/frontend/src/components/settings/ConnectMailboxSettings.jsx
-
curl -L -o ConnectMailboxSettings.jsx https://huggingface.co/spaces/Seth0330/qualifier/resolve/main/frontend/src/components/settings/ConnectMailboxSettings.jsx
5.17 kB
| 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> | |
| ); | |
| } | |