From fd364209e7d49daee420151550bebbf721bbe40b Mon Sep 17 00:00:00 2001 From: "soroush.asadi" Date: Tue, 23 Jun 2026 07:45:16 +0330 Subject: [PATCH] feat(coming-soon): hard-lock the live curtain; closable only on local/dev hosts The curtain was sessionStorage-dismissible everywhere. NODE_ENV can't tell the live deploy from the local Docker site (both are prod builds), so gate on the hostname instead: localhost + private LAN ranges (incl. 172.28.x) keep the "view experimental (local)" close button; any public domain is hard-locked to just the countdown. Starts the curtain up by default so the live site never flashes a page before it mounts. Co-Authored-By: Claude Opus 4.8 --- src/components/layout/ComingSoonOverlay.tsx | 65 +++++++++++++++------ 1 file changed, 47 insertions(+), 18 deletions(-) diff --git a/src/components/layout/ComingSoonOverlay.tsx b/src/components/layout/ComingSoonOverlay.tsx index 22fef79..f3a1296 100644 --- a/src/components/layout/ComingSoonOverlay.tsx +++ b/src/components/layout/ComingSoonOverlay.tsx @@ -21,6 +21,25 @@ const faDigits = ["۰", "۱", "۲", "۳", "۴", "۵", "۶", "۷", "۸", "۹"]; const fa = (n: number | string) => String(n).replace(/[0-9]/g, (d) => faDigits[Number(d)]); +/** + * Local/dev hosts may close the curtain; the live (public) site is hard-locked. + * NODE_ENV can't distinguish them — both local Docker and the deploy are prod + * builds — so we key off the hostname: localhost + private LAN ranges = local. + */ +function isLocalHost(): boolean { + if (typeof window === "undefined") return false; + const h = window.location.hostname; + return ( + h === "localhost" || + h === "127.0.0.1" || + h === "0.0.0.0" || + h.endsWith(".local") || + /^10\./.test(h) || + /^192\.168\./.test(h) || + /^172\.(1[6-9]|2\d|3[01])\./.test(h) // 172.16–31.x (incl. the 172.28.x dev LAN) + ); +} + const FEATURES = [ { icon: "⚡", title: "رندر فوق‌سریع ویدیو", desc: "ویدیوهایتان را در چند دقیقه و در فضای ابری رندر کنید" }, { icon: "💰", title: "هزینهٔ بسیار پایین", desc: "پرداخت بر اساس ثانیه؛ مقرون‌به‌صرفه‌تر از همیشه" }, @@ -49,14 +68,22 @@ function useCountdown(target: Date) { } export function ComingSoonOverlay() { - const [open, setOpen] = useState(false); + // Start UP so the live site never flashes the page before the curtain mounts. + const [open, setOpen] = useState(true); + // Only local/dev hosts get a way out; the live site stays locked. + const [canDismiss, setCanDismiss] = useState(false); const cd = useCountdown(LAUNCH); useEffect(() => { - // Show unless dismissed earlier this session. if (typeof window === "undefined") return; - const dismissed = sessionStorage.getItem(STORAGE_KEY); - setOpen(dismissed !== "1"); + const local = isLocalHost(); + setCanDismiss(local); + if (local) { + // Local: respect a prior dismissal so dev navigation isn't blocked. + const dismissed = sessionStorage.getItem(STORAGE_KEY); + setOpen(dismissed !== "1"); + } + // Live: no dismissal path — the curtain stays up, only the countdown shows. }, []); // Lock background scroll while the curtain is up. @@ -198,20 +225,22 @@ export function ComingSoonOverlay() { ))} - {/* CTA */} -
- -

- می‌توانید همین حالا نسخهٔ آزمایشی را ببینید -

-
+ {/* CTA — local/dev only; the live site has no way past the curtain. */} + {canDismiss && ( +
+ +

+ این دکمه فقط در نسخهٔ محلی نمایش داده می‌شود +

+
+ )} )}