"use client"; import { statusMessage } from "@/shared/i18n"; import { useI18n } from "@/components/I18nProvider"; 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"; import { ImportMap } from "./MapTransfer"; type Draft = MapListing & { upload_jobs: ProcessingJob | null; }; export function Dashboard() { const { t } = useI18n(); 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 ( <>

{t( "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.", )}

{t("Account settings")}
{error && (

{t(error)}

)} {!maps.length && (

{t("A blank map is a beginning.")}

{t( "Install the Fabric mod to capture while you explore, or the server plugin to export your world.", )}

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

{m.name}

{m.contribution_status ? t("Contribution: {status}", { status: t(statusMessage(m.contribution_status)), }) : m.update_state ? t("Update: {status}", { status: t(statusMessage(m.update_state)), }) : t(statusMessage(m.state))} {" "} {m.panorama_count} {t("locations")} {m.banned_at ? (

{t( "Map banned after three confirmed reports. Open your map to appeal.", )}

) : m.report_strikes > 0 ? (

{m.report_strikes} {t("/3 confirmed reports")}

) : null} {m.rejection_reason && (

{m.rejection_reason}

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

{m.upload_jobs.error}

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

{t("Link your server plugin.")}

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

{token && ( <>

{t( "Copy this token now. It is shown once. Keep it out of public chat.", )}

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