"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"; import type { ProcessingJob } from "@/shared/processing-progress"; import { ProcessingProgress } from "./ProcessingProgress"; type Draft = MapListing & { upload_jobs: ProcessingJob | null; }; export function Dashboard() { const [maps, setMaps] = useState([]), [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); setError(""); } 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!, ); const { error: signOutError } = await client.auth.signOut(); if (signOutError) { setError("Could not sign out. Please try again."); return; } router.refresh(); } return ( <>

Your maps start privately. You decide when they’re ready for review. Contributions are hidden here after five days. Their panoramas and direct links stay available.

Account settings
{error && (

{error}

)} {!maps.length && (

A blank map is a beginning.

Install the Fabric mod to capture while you explore, or the server plugin to export your world.

Get the capture tools →
)} {maps.map((m) => (
{m.source === "plugin" ? "SERVER EXPORT" : "PLAYER CAPTURE"}

{m.name}

{m.contribution_status ? `Contribution: ${m.contribution_status}` : m.update_state ? `Update: ${m.update_state}` : m.state} {" "} {m.panorama_count} locations {m.banned_at ? (

Map banned after three confirmed reports. Open your map to appeal.

) : m.report_strikes > 0 ? (

{m.report_strikes}/3 confirmed reports

) : null} {m.rejection_reason && (

{m.rejection_reason}

)} {m.upload_jobs?.error && (

{m.upload_jobs.error}

)}
{m.parent_dataset_id ? "Open contribution →" : "Open map →"}
))}
FOR SERVER ADMINISTRATORS

Link your server plugin.

Create a contributor token, then run{" "} /minegeo link <token> in your server console. Fabric uses browser pairing instead.

{token && ( <>

Copy this token now. It is shown once. Keep it out of public chat.

{token} )} {tokens.map((t) => (
{t.label}{" "} {t.revoked ? "· revoked" : ""} {!t.revoked && ( )}
))}
); }