The first commit, the core and all files is adding to repo

This commit is contained in:
2026-10-04 14:20:13 +05:00
commit 53d7b263c4
94 changed files with 10668 additions and 0 deletions

157
components/Dashboard.tsx Normal file
View File

@@ -0,0 +1,157 @@
"use client";
import { useCallback, useEffect, useState } from "react";
import Link from "next/link";
import { createBrowserClient } from "@supabase/ssr";
import { useRouter } from "next/navigation";
import { api } from "./StartGame";
import type { MapListing } from "@/lib/catalog";
type Draft = MapListing & {
upload_jobs: { state: string; progress: number; error: string | null } | null;
};
export function Dashboard() {
const [maps, setMaps] = useState<Draft[]>([]),
[error, setError] = useState(""),
[tokens, setTokens] = useState<
{ id: string; label: string; revoked: boolean }[]
>([]),
[token, setToken] = useState(""),
[busy, setBusy] = useState(false);
const router = useRouter();
const load = useCallback(async () => {
try {
const [m, t] = await Promise.all([
api<{ maps: Draft[] }>("datasets"),
api<{ tokens: { id: string; label: string; revoked: boolean }[] }>(
"tokens",
),
]);
setMaps(m.maps);
setTokens(t.tokens);
} catch (e) {
setError((e as Error).message);
}
}, []);
useEffect(() => {
load();
const timer = setInterval(load, 10000);
return () => clearInterval(timer);
}, [load]);
async function newToken() {
setBusy(true);
try {
const result = await api<{ token: string }>("tokens", {
label: "Server plugin",
});
setToken(result.token);
await load();
} catch (e) {
setError((e as Error).message);
} finally {
setBusy(false);
}
}
async function revoke(id: string) {
try {
await api("tokens/" + id, undefined, "DELETE");
await load();
} catch (e) {
setError((e as Error).message);
}
}
async function logout() {
const client = createBrowserClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY!,
);
await client.auth.signOut();
router.refresh();
}
return (
<>
<div className="dashboard-toolbar">
<p className="muted">
Your maps start privately. You decide when they’re ready for review.
</p>
<button className="button" onClick={logout}>
Sign out
</button>
</div>
{error && (
<p role="alert" className="error-message">
{error}
</p>
)}
{!maps.length && (
<div className="empty-state">
<h2>A blank map is a beginning.</h2>
<p>
Install the Fabric mod to capture while you explore, or the server
plugin to export your world.
</p>
<Link className="button primary" href="/downloads">
Get the capture tools →
</Link>
</div>
)}
{maps.map((m) => (
<article className="draft-row" key={m.id}>
<div>
<span className="eyebrow">
{m.source === "plugin" ? "SERVER EXPORT" : "PLAYER CAPTURE"}
</span>
<h3>{m.name}</h3>
<span className="badge">{m.state}</span>{" "}
<span className="muted">
{m.panorama_count} locations{" "}
{m.upload_jobs?.state === "running"
? `· ${m.upload_jobs.progress}% processed`
: ""}
</span>
{m.rejection_reason && (
<p className="error-message">{m.rejection_reason}</p>
)}
{m.upload_jobs?.error && (
<p className="muted">{m.upload_jobs.error}</p>
)}
</div>
<Link href={"/dashboard/" + m.id} className="button">
Open draft →
</Link>
</article>
))}
<section className="token-box">
<span className="eyebrow">FOR SERVER ADMINISTRATORS</span>
<h2>Link your server plugin.</h2>
<p className="muted">
Create a contributor token, then run{" "}
<code>/minegeo link &lt;token&gt;</code> in your server console.
Fabric uses browser pairing instead.
</p>
<button className="button" disabled={busy} onClick={newToken}>
Generate plugin token
</button>
{token && (
<>
<p className="success-message">
Copy this token now. It is shown once. Keep it out of public chat.
</p>
<code>{token}</code>
</>
)}
{tokens.map((t) => (
<div className="draft-row" key={t.id}>
<span>
{t.label}{" "}
<small className="muted">{t.revoked ? "· revoked" : ""}</small>
</span>
{!t.revoked && (
<button className="button" onClick={() => revoke(t.id)}>
Revoke
</button>
)}
</div>
))}
</section>
</>
);
}