The first commit, the core and all files is adding to repo
This commit is contained in:
157
components/Dashboard.tsx
Normal file
157
components/Dashboard.tsx
Normal 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 <token></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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user