"use client"; import { useEffect, useRef, useState } from "react"; import type { Data } from "@/server/services/model"; import { useDialog, useScrollLock } from "@/components/ui/Dialog"; import { Icon } from "@/components/ui/Icon"; import { EncryptedImage } from "@/components/media/EncryptedImage"; import { appDownloadURL } from "@/lib/app-download"; export function AgeGate({ onAccepted }: { onAccepted: () => void }) { const [open, setOpen] = useState(false); useEffect(() => { let accepted = false; try { accepted = localStorage.getItem("hg_age_ok") === "1"; } catch {} if (accepted) { document.documentElement.classList.add("hk-age-gate-ok"); onAccepted(); } else setOpen(true); }, [onAccepted]); const ref = useDialog(() => {}, open); useEffect(() => { document.body.classList.toggle("hk-age-gate-locked", open); return () => document.body.classList.remove("hk-age-gate-locked"); }, [open]); if (!open) return null; const accept = () => { try { localStorage.setItem("hg_age_ok", "1"); } catch {} document.documentElement.classList.add("hk-age-gate-ok"); setOpen(false); document.dispatchEvent(new CustomEvent("eighteen:closed")); onAccepted(); }; return (
); } export function DownloadDialog({ data, onClose, }: { data: Data; onClose: () => void; }) { const [mobile, setMobile] = useState( () => typeof window !== "undefined" && matchMedia("(max-width:768px)").matches, ); const [platform, setPlatform] = useState<"ios" | "android" | "other">( "other", ), [tab, setTab] = useState<"store" | "profile">("store"); const ref = useDialog(onClose), guide = data.installGuide || {}, fallback = guide.fallbackURL || appDownloadURL(data.appConfig) || appDownloadURL(data.config) || "/app/download/"; useEffect(() => { const media = matchMedia("(max-width:768px)"), resize = () => { if (mobile && !media.matches) { onClose(); return; } setMobile(media.matches); }; media.addEventListener("change", resize); setPlatform( /android/i.test(navigator.userAgent) ? "android" : /iPhone|iPad|iPod/i.test(navigator.userAgent) || (navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1) ? "ios" : "other", ); return () => media.removeEventListener("change", resize); }, [mobile, onClose]); if (!mobile) return (
); const panel = platform === "android" ? "android" : tab; const description = platform === "ios" ? guide.hasIOSStore || guide.hasIOSProfile ? "已识别为 iOS 设备,推荐商店版,也可选轻量版" : "已识别为 iOS 设备,可前往官方下载页查看安装方式" : platform === "android" ? guide.hasAndroidAPK ? "已识别为 Android 设备,下载 APK 直接安装" : "已识别为 Android 设备,可前往官方下载页查看安装方式" : "请选择适合当前设备的安装方式"; const notice = (text: string) => (

{text}

); const action = ( kind: "store" | "profile" | "android", href: string, label: string, icon: string, recommended = false, ) => ( {label} {recommended && 推荐} ); const steps = (items: [string, string][]) => (
    {items.map(([title, desc], index) => (
  1. {index + 1}

    {title}

    {desc}

  2. ))}
); return (
); } interface Ad { id?: string; advertiseCode?: string; images?: string; link?: string; title?: string; location?: string; advertiseLocationCode?: string; advertiseLocationName?: string; } function AdItem({ ad, onClose }: { ad: Ad; onClose: () => void }) { const [ready, setReady] = useState(false); const ref = useDialog(onClose, ready); const id = ad.advertiseCode || ad.id || ""; useEffect(() => { const timer = setTimeout(onClose, 12000); return () => clearTimeout(timer); }, [onClose]); useEffect(() => { if (ready && ref.current) document.dispatchEvent( new CustomEvent("external-ad:track", { detail: { element: ref.current.querySelector("[data-track-ad]"), eventType: "show", }, }), ); }, [ready, ref]); const image = ( setReady(true)} onResolveError={onClose} onError={onClose} /> ); const click = () => document.dispatchEvent( new CustomEvent("external-ad:track", { detail: { element: ref.current?.querySelector("[data-track-ad]"), eventType: "click", }, }), ); return (
{ if (event.target === event.currentTarget) onClose(); }} >
{ad.link ? ( {image} ) : (
{image}
)} 广告

{ad.title || "精彩推荐"}

每日展示一次

{ad.link && ( 立即观看 )}
); } export function AdvertisementQueue({ data, enabled, }: { data: Data; enabled: boolean; }) { const ads: Ad[] = data.appConfig?.dialogList || []; const signature = "2|" + ads.map((ad) => ad.advertiseCode || ad.id).join("|"); const [index, setIndex] = useState(-1); useEffect(() => { if (!enabled || !ads.length) return; try { if (sessionStorage.getItem("dialogAdDismissed") === signature) return; const saved = JSON.parse( sessionStorage.getItem("dialogAdProgress") || "null", ); setIndex(saved?.signature === signature ? saved.index : 0); } catch { setIndex(0); } }, [enabled, signature, ads.length]); const close = useRef(() => {}); close.current = () => { const next = index + 1; try { sessionStorage.setItem( "dialogAdProgress", JSON.stringify({ signature, index: next }), ); if (next >= ads.length) sessionStorage.setItem("dialogAdDismissed", signature); } catch {} setIndex(next); }; const stableClose = useRef(() => close.current()).current; if (!enabled || index < 0 || index >= ads.length) return null; return ; }