diff --git a/app/api/account/refresh-media/route.ts b/app/api/account/refresh-media/route.ts new file mode 100644 index 0000000..1109230 --- /dev/null +++ b/app/api/account/refresh-media/route.ts @@ -0,0 +1,41 @@ +import { NextRequest, NextResponse } from "next/server"; +import { resolveAccountId, unauthorized } from "@/lib/api"; +import { getAccountById, updateAccountConfig } from "@/lib/db"; +import { provisionTenant } from "@/lib/provision"; +import { listRepoAssets } from "@/lib/github"; + +export const runtime = "nodejs"; +export const dynamic = "force-dynamic"; + +// Re-pull media (images / video / audio) from the account's connected GitHub +// repo and refresh config.assets — without touching any other config field. +export async function POST(req: NextRequest) { + const id = await resolveAccountId(req); + if (!id) return unauthorized(); + + const acct = await getAccountById(id); + if (!acct) return NextResponse.json({ error: "account not found" }, { status: 404 }); + + const config: Record = { ...(acct.config || {}) }; + if (!config.repo) { + return NextResponse.json({ error: "Connect a GitHub repo first (owner/name), then Save." }, { status: 400 }); + } + + let assets: Awaited>; + try { + assets = await listRepoAssets(config.repo, { pattern: config.assetPattern }); + } catch (e: any) { + return NextResponse.json({ error: `Couldn't load from ${config.repo}: ${e?.message || e}` }, { status: 502 }); + } + + config.assets = assets; + const updated = await updateAccountConfig(id, config); + if (updated?.status === "active") await provisionTenant(updated); + + const counts: Record = {}; + for (const x of assets) { + const k = x.kind || "image"; + counts[k] = (counts[k] || 0) + 1; + } + return NextResponse.json({ ok: true, total: assets.length, counts, assets }); +} diff --git a/app/dashboard/page.tsx b/app/dashboard/page.tsx index 9c93969..f132059 100644 --- a/app/dashboard/page.tsx +++ b/app/dashboard/page.tsx @@ -207,7 +207,8 @@ function AccountPanel({ onError, onOk }: { onError: (m: string) => void; onOk: ( const [wallet, setWallet] = useState(""); const [repo, setRepo] = useState(""); const [assetPattern, setAssetPattern] = useState(""); - const [assets, setAssets] = useState<{ label: string; url: string }[]>([]); + const [assets, setAssets] = useState<{ label: string; url: string; kind?: string }[]>([]); + const [refreshing, setRefreshing] = useState(false); const [videos, setVideos] = useState<{ name: string; url: string }[]>([]); const [uploading, setUploading] = useState(false); const [blocks, setBlocks] = useState([]); @@ -439,15 +440,33 @@ function AccountPanel({ onError, onOk }: { onError: (m: string) => void; onOk: ( ))} -

GitHub assets (pull images from a repo onto your page)

+

GitHub media (pull images, video & audio from a repo onto your page)

setRepo(e.target.value)} />
-
setAssetPattern(e.target.value)} />
+
+ setAssetPattern(e.target.value)} /> + +
{assets.length > 0 && (
- {assets.slice(0, 12).map((a, i) => {a.label})} + {assets.slice(0, 12).map((a: any, i: number) => + a.kind === "video" ? 🎬 {a.label || "video"} + : a.kind === "audio" ? 🎧 {a.label || "audio"} + : {a.label}, + )}
)} - {repo &&

{assets.length} asset(s) loaded. Public repos work as-is; private repos need a server GITHUB_TOKEN.

} + {repo &&

{assets.length} asset(s) loaded. Public repos work as-is; private repos need a server GITHUB_TOKEN. Use ↻ Refresh media to re-pull without saving the rest of the form.

}
diff --git a/app/globals.css b/app/globals.css index 7431872..de228d5 100644 --- a/app/globals.css +++ b/app/globals.css @@ -424,6 +424,12 @@ dialog.qhelp::backdrop { background: rgba(0,0,0,.72); backdrop-filter: blur(2px) .t-asset { display: grid; place-items: center; aspect-ratio: 1/1; padding: 8px; background: #111214; border: 1px solid #262629; border-radius: 10px; overflow: hidden; transition: border-color .15s ease; } .t-asset:hover { border-color: var(--tenant-accent, var(--acid)); } .t-asset img { max-width: 100%; max-height: 100%; object-fit: contain; } +/* mixed-media repo assets: video/audio tiles + dashboard chips */ +.t-asset-av { aspect-ratio: 1/1; width: 100%; padding: 0; background: #000; } +.t-asset-av video, video.t-asset-av { width: 100%; height: 100%; object-fit: cover; } +.t-asset-audio { grid-column: 1 / -1; aspect-ratio: auto; width: 100%; padding: 8px; } +.t-asset-audio audio, audio.t-asset-audio { width: 100%; } +.t-asset-chip { aspect-ratio: auto; padding: 8px 10px; font-family: var(--mono); font-size: 11px; color: var(--ash); text-align: center; word-break: break-word; } /* Dashboard tabs */ .tabs { display: flex; gap: 8px; margin: 4px 0 18px; border-bottom: 1px solid var(--line); } diff --git a/components/Tenant.tsx b/components/Tenant.tsx index e1f0900..366763e 100644 --- a/components/Tenant.tsx +++ b/components/Tenant.tsx @@ -98,11 +98,17 @@ export default function Tenant({ cfg }: { cfg: TenantConfig }) { {cfg.assets.length > 0 && (
- {cfg.assets.map((a, i) => ( - - {a.label} - - ))} + {cfg.assets.map((a, i) => + a.kind === "video" ? ( +
)} diff --git a/lib/config.ts b/lib/config.ts index 72bd849..d726118 100644 --- a/lib/config.ts +++ b/lib/config.ts @@ -394,9 +394,14 @@ export function configFor(dn: string, opts: TenantOverrides = {}): TenantConfig const links = [...socialLinks(dn, override), ...parseLinks(opts.linkParams), ...cleanLinks(ov.customLinks, "link")]; // Sponsors: ?aff_linkN= query + saved sponsors. const sponsors = [...parseSponsors(opts.affParams), ...cleanLinks(ov.sponsors, "sponsor")]; - // GitHub repo image assets (saved with {name,url}); rendered as a gallery. + // GitHub repo media assets (images/video/audio, {name,url,kind}); a gallery. + const assetKinds = new Set(["image", "video", "audio"]); const assets = Array.isArray(ov.assets) - ? ov.assets.filter((a: any) => a && a.url).map((a: any) => ({ label: String(a.name || a.label || ""), url: String(a.url), kind: "image" })) + ? ov.assets.filter((a: any) => a && a.url).map((a: any) => ({ + label: String(a.name || a.label || ""), + url: String(a.url), + kind: assetKinds.has(a.kind) ? String(a.kind) : "image", + })) : []; // Uploaded MP4 videos ({name, url, poster?}). const videos = Array.isArray(ov.videos) diff --git a/lib/github.ts b/lib/github.ts index 2ef74d5..e680744 100644 Binary files a/lib/github.ts and b/lib/github.ts differ