シングルユーザー運用のため env フォールバックを廃止し、 APIオリジンを https://api.altdock.app.amania.jp に固定した。 - dashboard.ts / PublicSourceView.tsx の localhost:4000 フォールバックを削除 - API側 config.ts の PUBLIC_BASE_URL/WEB_ORIGIN デフォルトも本番ドメインに - compose/web の NEXT_PUBLIC_API_BASE_URL 環境変数を削除(コード固定のため)
47 lines
3.8 KiB
TypeScript
47 lines
3.8 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useState } from "react";
|
|
import Link from "next/link";
|
|
|
|
type SourceDocument = {
|
|
name: string;
|
|
subtitle?: string;
|
|
description?: string;
|
|
iconURL?: string;
|
|
tintColor?: string;
|
|
apps: Array<{ name: string; bundleIdentifier: string; developerName: string; subtitle?: string; localizedDescription: string; iconURL?: string; category: string; versions: Array<{ version: string; buildVersion: string; date: string; downloadURL: string; size: number }> }>;
|
|
};
|
|
|
|
// Single-user deployment: hard-code the public API origin.
|
|
const API_BASE = "https://api.altdock.app.amania.jp";
|
|
|
|
function formatBytes(value: number) {
|
|
if (!value) return "0 B";
|
|
const units = ["B", "KB", "MB", "GB"];
|
|
const index = Math.min(Math.floor(Math.log(value) / Math.log(1024)), units.length - 1);
|
|
return `${(value / 1024 ** index).toFixed(index === 0 ? 0 : 1)} ${units[index]}`;
|
|
}
|
|
|
|
export default function PublicSourceView({ slug }: { slug: string }) {
|
|
const [document, setDocument] = useState<SourceDocument | null>(null);
|
|
const [error, setError] = useState(false);
|
|
|
|
useEffect(() => {
|
|
fetch(`${API_BASE}/sources/${slug}/source.json`)
|
|
.then((response) => response.ok ? response.json() : Promise.reject(new Error("not found")))
|
|
.then(setDocument)
|
|
.catch(() => setError(true));
|
|
}, [slug]);
|
|
|
|
if (error) return <main className="public-source-error"><span className="brand-mark">A</span><h1>Source not found</h1><p>This Source is still a draft or the URL is incorrect.</p><Link href="/">Open AltDock</Link></main>;
|
|
if (!document) return <main className="public-source-loading"><span className="brand-mark">A</span><p>Loading Source…</p></main>;
|
|
|
|
return <main className="public-source-page" style={{ "--source-tint": document.tintColor || "#E9694B" } as React.CSSProperties}>
|
|
<header className="public-source-header"><Link className="public-brand" href="/"><span className="brand-mark">A</span><span>AltDock</span></Link><span className="public-badge">ALTSTORE PAL SOURCE</span></header>
|
|
<section className="public-source-hero"><div className="public-source-avatar">{document.iconURL ? <img src={document.iconURL} alt="" /> : document.name.slice(0, 1)}</div><div><div className="section-kicker">SOURCE / {slug}</div><h1>{document.name}</h1><p>{document.subtitle}</p></div><button className="button primary" onClick={() => navigator.clipboard?.writeText(`${API_BASE}/sources/${slug}/source.json`)}>Copy Source URL</button></section>
|
|
{document.description && <p className="public-source-description">{document.description}</p>}
|
|
<section className="public-app-grid">{document.apps.length ? document.apps.map((app) => { const release = app.versions[0]; return <article className="public-app-card" key={app.bundleIdentifier}><div className="public-app-card-top"><span className="public-app-icon" style={{ background: document.tintColor || "#E9694B" }}>{app.iconURL ? <img src={app.iconURL} alt="" /> : app.name.slice(0, 1)}</span><span className="public-category">{app.category}</span></div><h2>{app.name}</h2><p className="public-developer">{app.developerName} · {app.bundleIdentifier}</p><p className="public-app-description">{app.localizedDescription || app.subtitle || "A PAL-ready iOS app."}</p>{release ? <div className="public-release"><span>Latest release</span><strong>v{release.version} <small>build {release.buildVersion}</small></strong><span>{formatBytes(release.size)}</span></div> : <div className="public-release"><span>No published release yet</span></div>}</article>; }) : <div className="public-no-apps"><strong>No published apps yet.</strong><p>Come back when the developer ships their first release.</p></div>}</section>
|
|
<footer className="public-source-footer"><span>Powered by AltDock</span><span>Source JSON is available for AltStore PAL.</span></footer>
|
|
</main>;
|
|
}
|