"use client"; import { useState, useCallback, useEffect, useRef } from "react"; import Link from "next/link"; import JoinModal from "./JoinModal"; import { getPayEnv, redirectToPay, getDeviceFromEnv, } from "@/app/lib/payment"; import type { PayChannel } from "@/app/lib/payment"; const avatarPhotos = [ "https://i.pravatar.cc/150?img=32", "https://i.pravatar.cc/150?img=33", "https://i.pravatar.cc/150?img=25", "https://i.pravatar.cc/150?img=52", "https://i.pravatar.cc/150?img=44", ]; function isPrivateDevHost(hostname: string): boolean { if (!hostname) return false; if (hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1") return true; if (/^10\./.test(hostname) || /^192\.168\./.test(hostname)) return true; const match = hostname.match(/^172\.(\d{1,2})\./); if (!match) return false; const secondOctet = Number(match[1]); return secondOctet >= 16 && secondOctet <= 31; } function buildPendingUserId(email: string): string { return ( "pending_" + Array.from(new TextEncoder().encode(email)) .map((b) => b.toString(16).padStart(2, "0")) .join("") ); } export default function HeroSection() { const [email, setEmail] = useState(""); const [joinModalOpen, setJoinModalOpen] = useState(false); const [joinModalVipOnly, setJoinModalVipOnly] = useState(false); const [checking, setChecking] = useState(false); const [error, setError] = useState(null); const [isLoggedIn, setIsLoggedIn] = useState(false); useEffect(() => { const checkAuth = async () => { try { const res = await fetch("/api/auth/me", { credentials: "include" }); const data = await res.json(); setIsLoggedIn(!!data?.user); } catch { setIsLoggedIn(false); } }; checkAuth(); const onAuth = () => checkAuth(); window.addEventListener("auth:updated", onAuth); return () => window.removeEventListener("auth:updated", onAuth); }, []); const checkUserCacheRef = useRef<{ email: string; data: { exists: boolean; vip: boolean }; ts: number } | null>(null); const CACHE_TTL_MS = 60_000; const handleCheckAndProceed = useCallback( async (em: string) => { if (checking) return; const trimmed = em.trim(); if (!trimmed) { setError("请输入邮箱"); return; } setError(null); setChecking(true); try { const cached = checkUserCacheRef.current; if (cached && cached.email === trimmed && Date.now() - cached.ts < CACHE_TTL_MS) { const { exists, vip } = cached.data; if (exists) { setJoinModalVipOnly(!!vip); setJoinModalOpen(true); return; } } const checkRes = await fetch("/api/salon/check-user", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ email: trimmed }), credentials: "include", }); const checkData = await checkRes.json(); if (!checkRes.ok || !checkData?.ok) { throw new Error(checkData?.error || checkData?.detail || "操作失败"); } if (checkData.exists) { checkUserCacheRef.current = { email: trimmed, data: { exists: true, vip: !!checkData.vip }, ts: Date.now() }; setJoinModalVipOnly(!!checkData.vip); setJoinModalOpen(true); return; } const shouldPrecreateUser = typeof window !== "undefined" && isPrivateDevHost(window.location.hostname); let user_id = buildPendingUserId(trimmed); if (shouldPrecreateUser) { const ensureRes = await fetch("/api/salon/ensure-user", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ email: trimmed }), credentials: "include", }); const ensureData = await ensureRes.json(); if (!ensureRes.ok || !ensureData?.ok || !ensureData?.user_id) { throw new Error(ensureData?.error || ensureData?.detail || "创建用户失败"); } if (ensureData.is_new) { await fetch("/api/salon/set-needs-password-change", { method: "POST", credentials: "include" }); } user_id = String(ensureData.user_id).trim(); if (ensureData?.token && ensureData?.record) { await fetch("/api/auth/sync-session", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ token: ensureData.token, record: ensureData.record }), credentials: "include", }); } } const returnUrl = typeof window !== "undefined" ? `${window.location.origin}/join/paid` : "/join/paid"; const env = getPayEnv(); const channel: PayChannel = "wxpay"; redirectToPay({ user_id, return_url: returnUrl, channel, device: getDeviceFromEnv(env), useJoinConfig: true, }); } catch (err) { setError(err instanceof Error ? err.message : "操作失败"); } finally { setChecking(false); } }, [checking] ); const handleJoinClick = (e: React.FormEvent) => { e.preventDefault(); handleCheckAndProceed(email); }; return (
🏆 数字游民首选

探索中国
远程工作,自由生活

加入全球数字游民社区,发现中国最适合远程工作和生活的城市

{[ { icon: "🍹", text: "线下聚会与线上分享" }, { icon: "❤️", text: "结识志同道合的伙伴" }, { icon: "🧪", text: "探索新城市与新可能" }, { icon: "🌎", text: "全球数字游民网络" }, { icon: "💬", text: "社区互助与资源共享" }, ].map((f) => (
{f.icon} {f.text}
))}
{avatarPhotos.slice(0, 8).map((src, i) => ( ))}
已有 5,000+ 成员加入
{!isLoggedIn ? (
{ setEmail(e.target.value); setError(null); }} placeholder="输入邮箱,加入社区" className="w-full border border-gray-200 dark:border-gray-700 rounded-lg px-4 py-3 text-sm placeholder-gray-400 dark:placeholder-gray-500 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-[#ff4d4f]/20 focus:border-[#ff4d4f] transition-colors mb-3" /> {error && (

{error}

)}
) : (

您已登录,可前往社区参与互动

)}
{ setJoinModalOpen(false); setJoinModalVipOnly(false); }} initialEmail={email.trim()} vipOnly={joinModalVipOnly} />
💬 加入社区
); }