refactor(01-06): decompose social.tsx and runs.tsx into focused sub-components

- social.tsx (541 lines) -> 5 files: SocialPanel, AccountCard, AddAccountDialog, ActionHistory, social-config
- runs.tsx (506 lines) -> 4 files: RunCenter, RunQueue, RunDetail, run-helpers
- All sub-component files under 300 lines
- Backward-compatible re-export barrels preserve existing App.tsx imports
This commit is contained in:
ZacharyZhang-NY committed 2026-03-22 02:54:02 -04:00
1 parent d77486504d
commit 93e599befd
11 files changed
+1147 -1047

No files matched your search

@@ -0,0 +1,161 @@
import { useCallback, useState } from "react";
import { AnimatePresence } from "framer-motion";
import { Browser, Megaphone, Plus } from "@phosphor-icons/react";
import type { BrowserActionRecord, SocialAccountRecord, SocialPlatform } from "@shared/types";
import { ActionButton } from "../ui";
import { useT } from "../../i18n";
import { AccountCard } from "./AccountCard";
import { AddAccountDialog } from "./AddAccountDialog";
import { ActionHistory } from "./ActionHistory";
interface SocialPanelProps {
accounts: SocialAccountRecord[];
actions: BrowserActionRecord[];
agents: Array<{ id: string; name: string }>;
companyId: string;
onSave: (input: {
companyId: string;
platform: SocialPlatform;
accountName: string;
displayName: string;
requireApproval: boolean;
}) => Promise<void>;
onDelete: (id: string) => Promise<void>;
onLogin: (accountId: string) => Promise<void>;
onOpenPath: (path: string) => Promise<void>;
onCancelAction: (actionId: string) => Promise<void>;
onNavigate: (section: "tasks" | "approvals", entityId: string) => void;
}
export function SocialAccountsPanel({
accounts,
actions,
agents,
companyId,
onSave,
onDelete,
onLogin,
onOpenPath,
onCancelAction,
onNavigate,
}: SocialPanelProps) {
const t = useT();
const [showAddDialog, setShowAddDialog] = useState(false);
const [activeTab, setActiveTab] = useState<"accounts" | "history">("accounts");
const handleAdd = useCallback(async (platform: SocialPlatform, accountName: string, displayName: string, requireApproval: boolean) => {
await onSave({ companyId, platform, accountName, displayName, requireApproval });
setShowAddDialog(false);
}, [companyId, onSave]);
return (
<div className="space-y-5">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="flex h-10 w-10 items-center justify-center rounded-[10px] bg-[color:var(--accent-soft)]">
<Megaphone size={20} weight="fill" className="text-[color:var(--accent)]" />
</div>
<div>
<h2 className="text-[16px] font-bold text-[color:var(--text)]">{t("social.socialMedia")}</h2>
<p className="text-[12px] text-[color:var(--muted)]">
{accounts.length} account{accounts.length !== 1 ? "s" : ""} connected
</p>
</div>
</div>
<ActionButton
label="Add Account"
onClick={() => setShowAddDialog(true)}
icon={Plus}
tone="accent"
/>
</div>
<div className="rounded-[10px] border border-[color:var(--accent)] border-opacity-20 bg-[color:var(--accent-soft)] px-4 py-3">
<div className="flex items-start gap-2 text-[12px] text-[color:var(--accent)]">
<Browser size={16} className="mt-0.5 shrink-0" />
<div>
<div className="font-medium">How it works</div>
<div className="mt-1 text-[11px] opacity-80">
Add your social media accounts here. Click "Login" to open a browser where you can sign in.
Your session is saved locally so marketing agents can post content, reply to users, and browse feeds
using browser automation. All actions are logged and can require Board approval.
</div>
</div>
</div>
</div>
<div className="flex gap-1 rounded-[8px] bg-[color:var(--panel-soft)] p-1">
<button
type="button"
onClick={() => setActiveTab("accounts")}
className={`flex-1 rounded-[6px] px-3 py-1.5 text-[12px] font-medium transition ${
activeTab === "accounts"
? "bg-[color:var(--panel)] text-[color:var(--text)] shadow-sm"
: "text-[color:var(--muted)] hover:text-[color:var(--text)]"
}`}
>
Accounts ({accounts.length})
</button>
<button
type="button"
onClick={() => setActiveTab("history")}
className={`flex-1 rounded-[6px] px-3 py-1.5 text-[12px] font-medium transition ${
activeTab === "history"
? "bg-[color:var(--panel)] text-[color:var(--text)] shadow-sm"
: "text-[color:var(--muted)] hover:text-[color:var(--text)]"
}`}
>
Action History ({actions.length})
</button>
</div>
{activeTab === "accounts" ? (
accounts.length === 0 ? (
<div className="flex flex-col items-center justify-center rounded-[10px] border border-dashed border-[color:var(--line)] py-16 text-center">
<Megaphone size={32} className="mb-3 text-[color:var(--muted)]" />
<div className="text-[14px] font-medium text-[color:var(--muted-strong)]">{t("social.noAccounts")}</div>
<div className="mt-1 text-[12px] text-[color:var(--muted)]">
{t("social.noAccountsDesc")}
</div>
<div className="mt-4">
<ActionButton
label="Add Account"
onClick={() => setShowAddDialog(true)}
icon={Plus}
tone="accent"
/>
</div>
</div>
) : (
<div className="space-y-2">
{accounts.map((account) => (
<AccountCard
key={account.id}
account={account}
onLogin={() => void onLogin(account.id)}
onDelete={() => void onDelete(account.id)}
/>
))}
</div>
)
) : (
<ActionHistory
actions={actions}
agents={agents}
onOpenPath={onOpenPath}
onCancelAction={onCancelAction}
onNavigate={onNavigate}
/>
)}
<AnimatePresence>
{showAddDialog ? (
<AddAccountDialog
onAdd={(platform, accountName, displayName, requireApproval) => void handleAdd(platform, accountName, displayName, requireApproval)}
onClose={() => setShowAddDialog(false)}
/>
) : null}
</AnimatePresence>
</div>
);
}