| 1 | import { SsgoiTransition } from "@ssgoi/react"; |
| 2 | import { useEffect } from "react"; |
| 3 | import { useLocation, useNavigate } from "react-router-dom"; |
| 4 | import { BadgeDollarSign } from "lucide-react"; |
| 5 | |
| 6 | interface ServerDetailState { |
| 7 | id: string; |
| 8 | name: string; |
| 9 | description: string; |
| 10 | tags: string[]; |
| 11 | thumbnail: string; |
| 12 | owner: string; |
| 13 | online: boolean; |
| 14 | serverUrl: string; |
| 15 | paymentEnabled?: boolean; |
| 16 | paymentLabel?: string; |
| 17 | } |
| 18 | |
| 19 | export function ServerDetail() { |
| 20 | const location = useLocation(); |
| 21 | const navigate = useNavigate(); |
| 22 | const server = location.state as ServerDetailState; |
| 23 | |
| 24 | // Detect back navigation using pageshow event |
| 25 | useEffect(() => { |
| 26 | const handlePageShow = () => { |
| 27 | // Navigate to home if restored from bfcache or has push flag in localStorage |
| 28 | const hasPushFlag = localStorage.getItem("isPush") === "true"; |
| 29 | |
| 30 | if (hasPushFlag) { |
| 31 | localStorage.removeItem("isPush"); |
| 32 | navigate("/", { replace: true }); |
| 33 | } |
| 34 | }; |
| 35 | |
| 36 | window.addEventListener("pageshow", handlePageShow); |
| 37 | |
| 38 | return () => { |
| 39 | window.removeEventListener("pageshow", handlePageShow); |
| 40 | }; |
| 41 | }, [navigate]); |
| 42 | |
| 43 | useEffect(() => { |
| 44 | if (typeof localStorage === "undefined") return; |
| 45 | if (!server) { |
| 46 | navigate("/"); |
| 47 | return; |
| 48 | } |
| 49 | |
| 50 | // Check localStorage immediately (before pageshow) |
| 51 | const hasPushFlag = localStorage.getItem("isPush") === "true"; |
| 52 | |
| 53 | if (hasPushFlag) { |
| 54 | // If flag already exists, go home (double-check with pageshow handler) |
| 55 | localStorage.removeItem("isPush"); |
| 56 | navigate("/"); |
| 57 | return; |
| 58 | } |
| 59 | |
| 60 | // Redirect after animation |
| 61 | const timer = setTimeout(() => { |
| 62 | localStorage.setItem("isPush", "true"); |
| 63 | window.location.href = server.serverUrl; |
| 64 | }, 500); |
| 65 | |
| 66 | return () => { |
| 67 | clearTimeout(timer); |
| 68 | }; |
| 69 | }, [server, navigate]); |
| 70 | |
| 71 | // If no server data, show nothing (will redirect) |
| 72 | if (!server) { |
| 73 | return null; |
| 74 | } |
| 75 | |
| 76 | const { |
| 77 | id, |
| 78 | thumbnail, |
| 79 | name, |
| 80 | online, |
| 81 | description, |
| 82 | tags, |
| 83 | owner, |
| 84 | paymentEnabled, |
| 85 | paymentLabel = "", |
| 86 | } = server; |
| 87 | const normalizedPaymentLabel = paymentLabel.trim(); |
| 88 | const showPaymentBadge = paymentEnabled || normalizedPaymentLabel !== ""; |
| 89 | |
| 90 | // Base size multiplier (1 = default, 2 = 2x size) |
| 91 | const basicSize = 2.5; |
| 92 | |
| 93 | return ( |
| 94 | <SsgoiTransition id={`/server/${id}`}> |
| 95 | <div |
| 96 | data-hero-key={`server-bg-${id}`} |
| 97 | className="fixed inset-0 bg-center bg-no-repeat bg-cover w-screen h-screen" |
| 98 | style={{ ...(thumbnail && { backgroundImage: `url(${thumbnail})` }) }} |
| 99 | > |
| 100 | {/* Content overlay - Full screen */} |
| 101 | <div className="absolute inset-0 flex items-center justify-center p-6 md:p-12"> |
| 102 | <div className="w-full h-full max-w-7xl bg-background/78 backdrop-blur-sm rounded-lg flex flex-col gap-6 p-8 md:p-12 items-start text-start"> |
| 103 | <div className="w-full h-full flex flex-col justify-center gap-6 md:gap-8"> |
| 104 | <div |
| 105 | className="flex flex-col" |
| 106 | style={{ gap: `${1 * basicSize}%` }} |
| 107 | > |
| 108 | <div |
| 109 | className="flex items-center" |
| 110 | style={{ gap: `${0.8 * basicSize}%` }} |
| 111 | > |
| 112 | <div |
| 113 | className={`rounded-full ${ |
| 114 | online ? "bg-green-status" : "bg-red-500" |
| 115 | }`} |
| 116 | style={{ |
| 117 | width: `${1 * basicSize}vw`, |
| 118 | height: `${1 * basicSize}vw`, |
| 119 | }} |
| 120 | /> |
| 121 | <p |
| 122 | className={`font-medium leading-normal ${ |
| 123 | online ? "text-green-status" : "text-red-500" |
| 124 | }`} |
| 125 | style={{ fontSize: `${1.8 * basicSize}vw` }} |
| 126 | > |
| 127 | {online ? "Online" : "Offline"} |
| 128 | </p> |
| 129 | </div> |
| 130 | {showPaymentBadge && ( |
| 131 | <div className="inline-flex w-fit max-w-full items-center gap-2 rounded-md border border-amber-400/25 bg-amber-400/10 px-4 py-2 font-display text-amber-700 dark:text-amber-100"> |
| 132 | <BadgeDollarSign className="h-5 w-5 shrink-0" /> |
| 133 | <span className="truncate text-base font-bold uppercase tracking-wide"> |
| 134 | {normalizedPaymentLabel || "Paid app"} |
| 135 | </span> |
| 136 | </div> |
| 137 | )} |
| 138 | <p |
| 139 | className="text-foreground font-bold leading-tight" |
| 140 | style={{ fontSize: `${6 * basicSize}vw` }} |
| 141 | > |
| 142 | {name} |
| 143 | </p> |
| 144 | {description && ( |
| 145 | <p |
| 146 | className="text-text-muted font-normal leading-normal max-w-4xl" |
| 147 | style={{ fontSize: `${2.5 * basicSize}vw` }} |
| 148 | > |
| 149 | {description} |
| 150 | </p> |
| 151 | )} |
| 152 | {tags && tags.length > 0 && ( |
| 153 | <div |
| 154 | className="flex flex-wrap" |
| 155 | style={{ |
| 156 | gap: `${0.8 * basicSize}vw`, |
| 157 | marginTop: `${0.5 * basicSize}%`, |
| 158 | }} |
| 159 | > |
| 160 | {tags.map((tag, index) => ( |
| 161 | <span |
| 162 | key={index} |
| 163 | className="bg-secondary text-primary font-medium rounded-lg" |
| 164 | style={{ |
| 165 | padding: `${0.6 * basicSize}vw ${1.2 * basicSize}vw`, |
| 166 | fontSize: `${1.5 * basicSize}vw`, |
| 167 | }} |
| 168 | > |
| 169 | {tag} |
| 170 | </span> |
| 171 | ))} |
| 172 | </div> |
| 173 | )} |
| 174 | {owner && ( |
| 175 | <p |
| 176 | className="text-text-muted font-normal leading-normal" |
| 177 | style={{ |
| 178 | fontSize: `${1.8 * basicSize}vw`, |
| 179 | marginTop: `${1 * basicSize}%`, |
| 180 | }} |
| 181 | > |
| 182 | by {owner} |
| 183 | </p> |
| 184 | )} |
| 185 | </div> |
| 186 | </div> |
| 187 | </div> |
| 188 | </div> |
| 189 | </div> |
| 190 | </SsgoiTransition> |
| 191 | ); |
| 192 | } |