T-HUB/apps/web/src/components/mobile/amplify-sheet.tsx
glass beadian aa5e262f3a Tactical Media Broadcast Hub — initial release
Self-hosted multi-platform broadcast newsroom for activist collectives:
publishing pipeline with group approval and DST-proof scheduling, TweetDeck-style
column wall with hostile-content-safe rendering, live control room with Auto
Director, bridged chat governance with polls and quorum, distributed sandboxed
transcode workers, encrypted content-addressed archive, monthly analytics.

MIT licensed. See README.md and docs/.
2026-10-10 11:56:42 +00:00

49 lines
4.2 KiB
TypeScript

"use client";
import { useState } from "react";
import { useRouter } from "next/navigation";
import type { FeedItem } from "../../lib/fixtures";
import { PLATFORMS, type Platform } from "../../lib/platforms";
import type { ConnectionSummary } from "../../lib/api-types";
import { useApiResource } from "../../lib/use-api";
import { Sheet } from "./sheet";
import { nativeAmplifyActions } from "../../lib/amplify";
const identity = (value: ConnectionSummary[]): ConnectionSummary[] => value;
const empty: ConnectionSummary[] = [];
export function AmplifySheet({ post, platform, open, onClose }: { post: FeedItem | null; platform: Platform | null; open: boolean; onClose: () => void }): React.ReactElement {
const router = useRouter();
const connections = useApiResource<ConnectionSummary[], ConnectionSummary[]>("/connections", identity, empty);
const [picked, setPicked] = useState<string[]>([]);
const [notice, setNotice] = useState("");
const native = platform === null ? [] : nativeAmplifyActions(platform);
function compose(ids: string[]): void {
if (post === null) return;
const params = new URLSearchParams({ text: `${post.text}\n\nVia ${post.handle}`, targets: ids.join(",") });
onClose();
router.push(`/compose?${params.toString()}`);
}
async function share(): Promise<void> {
if (post === null) return;
const url = window.location.href;
if (navigator.share) await navigator.share({ title: `Post by ${post.handle}`, text: post.text, url }).catch(() => undefined);
else if (navigator.clipboard) { await navigator.clipboard.writeText(`${post.text}\n${url}`); setNotice("Post text copied"); }
}
return <Sheet open={open} onClose={onClose} title={post === null ? "Amplify" : `Amplify ${post.handle}`} footer={<div className="flex gap-2"><button type="button" className="min-h-11 min-w-11 rounded-md border border-border px-3" onClick={() => void share()}>Share</button><button type="button" className="min-h-11 flex-1 rounded-md bg-accent px-3 text-sm font-semibold text-white disabled:opacity-40" disabled={picked.length === 0} onClick={() => compose(picked)}>Crosspost to {picked.length}</button></div>}>
{platform !== null && <><h3 className="mt-2 text-xs font-semibold uppercase text-text-faint">Native on {platform.displayName}</h3>
{native.length === 0 ? <p className="mt-3 text-xs text-text-faint">This platform exposes no native amplify action.</p> : native.map((action) => <button key={action.kind} type="button" className="mt-2 flex min-h-12 w-full items-center rounded-md border border-border px-4 text-left text-sm" onClick={() => action.kind === "quote" ? compose([]) : setNotice(`${action.label} is available in the platform, but its connector action is not wired yet.`)}>{action.label}{action.kind === "quote" && <span className="ml-auto text-xs text-text-faint">Opens compose</span>}</button>)}
</>}
{notice && <p className="mt-2 text-xs text-warn" role="status">{notice}</p>}
<h3 className="mt-6 text-xs font-semibold uppercase text-text-faint">Crosspost to your other accounts</h3>
{connections.source !== "live" && <p className="mt-2 text-xs text-text-faint">Connect to the API to choose publishing accounts.</p>}
{connections.data.filter((connection) => connection.provider !== platform?.id).map((connection) => {
const target = PLATFORMS[connection.provider];
const reason = !target?.capabilities.publishText ? "Text publishing unavailable" : target.capabilities.requiresMediaForPublish && !post?.hasMedia ? `${target.displayName} needs media` : connection.health !== "healthy" ? "Reconnect account first" : "";
return <label key={connection.id} className={`mt-2 flex min-h-14 items-center gap-3 rounded-md border border-border px-3 ${reason ? "opacity-60" : ""}`}>
<input type="checkbox" className="h-5 w-5 accent-accent" disabled={reason !== ""} checked={picked.includes(connection.id)} onChange={(event) => setPicked((current) => event.target.checked ? [...current, connection.id] : current.filter((id) => id !== connection.id))} />
<span className="min-w-0 flex-1"><span className="block text-sm">{target?.displayName ?? connection.provider}</span><span className="block text-xs text-text-faint">{reason || "Creates a new attributed post in compose"}</span></span>
</label>;
})}
</Sheet>;
}