"use client"; import { useEffect, useState, useRef } from "react"; import { createPortal } from "react-dom"; import PropTypes from "prop-types"; import { GITHUB_CONFIG } from "@/shared/constants/config"; export default function DonateModal({ isOpen, onClose }) { const [data, setData] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(""); const modalRef = useRef(null); useEffect(() => { if (!isOpen || data) return; setLoading(true); setError(""); fetch(GITHUB_CONFIG.donateUrl, { cache: "no-store" }) .then((res) => { if (!res.ok) throw new Error(`HTTP ${res.status}`); return res.json(); }) .then((json) => setData(json)) .catch((err) => setError(err.message || "Failed to load")) .finally(() => setLoading(false)); }, [isOpen, data]); useEffect(() => { const handleClickOutside = (e) => { if (modalRef.current && !modalRef.current.contains(e.target)) onClose(); }; if (isOpen) { document.addEventListener("mousedown", handleClickOutside); return () => document.removeEventListener("mousedown", handleClickOutside); } }, [isOpen, onClose]); if (!isOpen || typeof document === "undefined") return null; return createPortal(

volunteer_activism {data?.title || "Support 9Router"}

{loading && (
progress_activity Loading...
)} {error && (
Failed to load donate info: {error}
)} {!loading && !error && data && ( <> {data.message && (

{data.message}

)}
{data.channels?.map((ch) => ( ))}
)}
, document.body ); } function DonateChannelCard({ channel }) { const { label, description, icon, color, url, qr } = channel; const content = ( <>
{icon}
{label}
{description && (
{description}
)} {qr && ( {`${label} )} ); return (
{content} {url && ( Open open_in_new )}
); } DonateModal.propTypes = { isOpen: PropTypes.bool.isRequired, onClose: PropTypes.func.isRequired, };