"use client"; import { useCallback, useEffect, useMemo, useRef, useState, type CSSProperties, type FormEvent, } from "react"; interface TelegramUser { id: number; username?: string; first_name?: string; last_name?: string; } interface ThemeParams { bg_color?: string; text_color?: string; hint_color?: string; link_color?: string; button_color?: string; button_text_color?: string; secondary_bg_color?: string; } interface TelegramWebApp { initData: string; initDataUnsafe: { user?: TelegramUser }; colorScheme?: string; themeParams?: ThemeParams; ready: () => void; expand: () => void; close: () => void; openLink: (url: string) => void; setHeaderColor?: (color: string) => void; setBackgroundColor?: (color: string) => void; MainButton?: { text: string; color: string; textColor: string; isVisible: boolean; setText: (text: string) => void; show: () => void; hide: () => void; enable: () => void; disable: () => void; showProgress: (leaveActive?: boolean) => void; hideProgress: () => void; onClick: (handler: () => void) => void; offClick: (handler: () => void) => void; }; BackButton?: { show: () => void; hide: () => void; onClick: (handler: () => void) => void }; } declare global { interface Window { Telegram?: { WebApp?: TelegramWebApp }; } } type Status = "loading" | "form" | "success" | "outside"; const TELEGRAM_SCRIPT = "https://telegram.org/js/telegram-web-app.js"; const BOT_LINK = "https://t.me/MineGeoModeratorBot"; function loadTelegramScript(): Promise { if (window.Telegram?.WebApp) return Promise.resolve(); return new Promise((resolve, reject) => { const existing = document.querySelector( `script[src="${TELEGRAM_SCRIPT}"]`, ); if (existing) { existing.addEventListener("load", () => resolve(), { once: true }); existing.addEventListener("error", () => reject(new Error("load failed")), { once: true, }); return; } const script = document.createElement("script"); script.src = TELEGRAM_SCRIPT; script.async = true; script.onload = () => resolve(); script.onerror = () => reject(new Error("load failed")); document.head.appendChild(script); }); } export function ModeratorApplyApp() { const [status, setStatus] = useState("loading"); const [username, setUsername] = useState(""); const [error, setError] = useState(null); const [confirmationUrl, setConfirmationUrl] = useState(null); const [theme, setTheme] = useState({}); const tgRef = useRef(null); const submitRef = useRef<() => void>(() => {}); const submittingRef = useRef(false); useEffect(() => { let cancelled = false; loadTelegramScript() .then(() => { if (cancelled) return; const tg = window.Telegram?.WebApp; if (!tg) { setStatus("outside"); return; } tgRef.current = tg; tg.ready(); tg.expand(); setTheme(tg.themeParams ?? {}); const background = tg.themeParams?.bg_color ?? "#0e1621"; tg.setBackgroundColor?.(background); tg.setHeaderColor?.(tg.themeParams?.secondary_bg_color ?? background); if (tg.initData) setStatus("form"); else setStatus("outside"); }) .catch(() => !cancelled && setStatus("outside")); return () => { cancelled = true; }; }, []); const submit = useCallback(async () => { const tg = tgRef.current; if (!tg?.initData || !/^[A-Za-z0-9_]{3,32}$/.test(username.trim()) || submittingRef.current) return; submittingRef.current = true; const mainButton = tg.MainButton; setError(null); mainButton?.disable(); mainButton?.showProgress(true); try { const response = await fetch("/api/moderator-apply", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ initData: tg.initData, username: username.trim() }), }); const data = (await response.json().catch(() => null)) as { url?: string; error?: string; } | null; if (!response.ok || !data?.url) { setError(data?.error ?? "Something went wrong. Please try again."); return; } setConfirmationUrl(data.url); setStatus("success"); mainButton?.hide(); } catch { setError("Network error. Please try again."); } finally { submittingRef.current = false; mainButton?.hideProgress(); mainButton?.enable(); } }, [username]); submitRef.current = submit; useEffect(() => { if (status !== "form") return; const tg = tgRef.current; const mainButton = tg?.MainButton; if (!tg || !mainButton) return; mainButton.text = "Submit application"; if (tg.themeParams?.button_color) mainButton.color = tg.themeParams.button_color; if (tg.themeParams?.button_text_color) mainButton.textColor = tg.themeParams.button_text_color; const handler = () => submitRef.current(); mainButton.onClick(handler); mainButton.show(); return () => { mainButton.offClick(handler); mainButton.hide(); }; }, [status]); const onSubmit = (event: FormEvent) => { event.preventDefault(); void submit(); }; const style = useMemo( () => ({ "--tg-bg": theme.bg_color ?? (theme.secondary_bg_color ? "#0e1621" : "#ffffff"), "--tg-secondary-bg": theme.secondary_bg_color ?? "#f4f4f5", "--tg-text": theme.text_color ?? "#000000", "--tg-hint": theme.hint_color ?? "#707579", "--tg-link": theme.link_color ?? "#3390ec", "--tg-button": theme.button_color ?? "#3390ec", "--tg-button-text": theme.button_text_color ?? "#ffffff", }) as CSSProperties, [theme], ); const displayName = useMemo(() => { const user = tgRef.current?.initDataUnsafe?.user; if (!user) return null; const name = [user.first_name, user.last_name].filter(Boolean).join(" ").trim(); return name || user.username || null; }, [status]); return (
{status === "loading" &&

Loading…

} {status === "outside" && (

Moderator application

This form opens inside Telegram. Use the MineGeo bot to apply.

Open @MineGeoModeratorBot
)} {status === "form" && (

Apply for moderator

{displayName ? `Signed in as ${displayName}. ` : ""} Enter your MineGeo username. You will confirm the application from that account.

setUsername(event.target.value)} placeholder="your_username" autoCapitalize="none" autoCorrect="off" autoComplete="username" minLength={3} maxLength={32} pattern="[A-Za-z0-9_]{3,32}" required />

3–32 letters, numbers or underscores.

{error && (

{error}

)}
)} {status === "success" && confirmationUrl && (

Application created

Confirm it from your MineGeo account within 30 minutes. The staff will review it after confirmation.

{confirmationUrl}

)}
); }