quorlasocial/dashboard/web/src/pages/AddClientDialog.jsx
metatroncubeswdev 3cbc76e7f1 Support Instagram-only clients (no Facebook Page)
- Connector lists Instagram accounts assigned to the system user without a
  Page; numbers, inbox, mentions and publishing work for them
- Clients link by Page or Instagram account ("Link an account"); Facebook
  features explain the client has no Page instead of failing
- Drafts for Instagram-only clients drop Facebook automatically
- Images for Instagram-only clients are hosted via the agency's own Page
  (META_MEDIA_PAGE_ID, unpublished); never falls back to a client's Page

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 01:02:03 -04:00

105 lines
4.5 KiB
JavaScript

import { useEffect, useState } from "react";
import { RefreshCw, Instagram, Facebook } from "lucide-react";
import { api } from "../api.js";
import { Button, cx, Dialog, Empty, ErrorNote, Field, fmt, Spinner } from "../ui.jsx";
/** Pick a Facebook Page the agency can manage → create the client's folder, brand files and link. */
export default function AddClientDialog({ open, onClose, onAdded }) {
const [accounts, setAccounts] = useState(null);
const [error, setError] = useState(null);
const [picked, setPicked] = useState(null);
const [name, setName] = useState("");
const [runAudit, setRunAudit] = useState(true);
const [busy, setBusy] = useState(false);
const load = (refresh) => {
setAccounts(null);
setError(null);
api(`/api/accounts/unlinked${refresh ? "?refresh=1" : ""}`).then((r) => setAccounts(r.accounts)).catch(setError);
};
useEffect(() => {
if (open) { load(false); setPicked(null); setName(""); }
}, [open]);
const add = async () => {
setBusy(true);
setError(null);
try {
const r = await api("/api/clients", { method: "POST", body: { accountId: picked.account_id, name, runAudit } });
onClose();
onAdded?.(r.client);
} catch (e) {
setError(e);
} finally {
setBusy(false);
}
};
return (
<Dialog
open={open}
onClose={onClose}
title="Add a business"
footer={
<>
<Button onClick={onClose}>Cancel</Button>
<Button variant="primary" disabled={!picked} loading={busy} onClick={add}>
Create client
</Button>
</>
}
>
<p className="text-muted text-[13px] mb-3">
Pages connected to the agency's Meta account that don't have a client folder yet. Connect new Pages in Meta Business Suite first.
</p>
<ErrorNote error={error} className="mb-3" />
<div className="flex justify-end mb-2">
<Button size="sm" variant="ghost" icon={RefreshCw} onClick={() => load(true)}>Check Meta again</Button>
</div>
{!accounts && !error ? (
<Spinner label="Asking Meta…" />
) : accounts?.length === 0 ? (
<Empty title="Every connected Page already has a client">Add the new business's Page to the agency's Meta Business account, then check again.</Empty>
) : (
<ul className="border border-line rounded-lg divide-y divide-line max-h-[260px] overflow-y-auto">
{accounts?.map((a) => (
<li key={a.account_id}>
<button
onClick={() => { setPicked(a); setName(a.page_name); }}
className={cx("w-full text-left px-3.5 py-2.5 flex items-center gap-3", picked?.account_id === a.account_id ? "bg-brand-soft" : "hover:bg-[#f8fafc]")}
>
<span className={cx("size-4 rounded-full border-2 shrink-0", picked?.account_id === a.account_id ? "border-brand bg-brand shadow-[inset_0_0_0_2px_white]" : "border-line-strong")} />
<span className="min-w-0 flex-1">
<span className="block font-medium truncate">{a.page_name}</span>
<span className="flex gap-3 text-[12px] text-muted">
{a.page_id ? <span className="inline-flex items-center gap-1"><Facebook className="size-3" />{a.category || "Page"}</span> : <span>Instagram only · no Facebook Page</span>}
{a.instagram ? (
<span className="inline-flex items-center gap-1"><Instagram className="size-3" />@{a.instagram.username} · {fmt(a.instagram.followers_count)} followers</span>
) : (
<span>no Instagram linked</span>
)}
</span>
</span>
</button>
</li>
))}
</ul>
)}
{picked && (
<div className="mt-4 space-y-3">
<Field label="Client name" hint="Used for the folder, reports and AI runs.">
<input className="input" value={name} onChange={(e) => setName(e.target.value)} />
</Field>
<label className="flex items-start gap-2 text-[13px]">
<input type="checkbox" className="mt-0.5 accent-brand" checked={runAudit} onChange={(e) => setRunAudit(e.target.checked)} />
<span>
Run the first account audit now
<span className="block text-muted text-[12px]">Claude drafts brand.md, the internal and client audits and the branded PDFs. Takes a few minutes.</span>
</span>
</label>
</div>
)}
</Dialog>
);
}