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 <noreply@anthropic.com>
This commit is contained in:
soroush.asadi
2026-06-23 07:45:16 +03:30
parent cb6512fee3
commit fd364209e7
+34 -5
View File
@@ -21,6 +21,25 @@ const faDigits = ["۰", "۱", "۲", "۳", "۴", "۵", "۶", "۷", "۸", "۹"];
const fa = (n: number | string) => const fa = (n: number | string) =>
String(n).replace(/[0-9]/g, (d) => faDigits[Number(d)]); 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.1631.x (incl. the 172.28.x dev LAN)
);
}
const FEATURES = [ const FEATURES = [
{ icon: "⚡", title: "رندر فوق‌سریع ویدیو", desc: "ویدیوهایتان را در چند دقیقه و در فضای ابری رندر کنید" }, { icon: "⚡", title: "رندر فوق‌سریع ویدیو", desc: "ویدیوهایتان را در چند دقیقه و در فضای ابری رندر کنید" },
{ icon: "💰", title: "هزینهٔ بسیار پایین", desc: "پرداخت بر اساس ثانیه؛ مقرون‌به‌صرفه‌تر از همیشه" }, { icon: "💰", title: "هزینهٔ بسیار پایین", desc: "پرداخت بر اساس ثانیه؛ مقرون‌به‌صرفه‌تر از همیشه" },
@@ -49,14 +68,22 @@ function useCountdown(target: Date) {
} }
export function ComingSoonOverlay() { 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); const cd = useCountdown(LAUNCH);
useEffect(() => { useEffect(() => {
// Show unless dismissed earlier this session.
if (typeof window === "undefined") return; if (typeof window === "undefined") return;
const local = isLocalHost();
setCanDismiss(local);
if (local) {
// Local: respect a prior dismissal so dev navigation isn't blocked.
const dismissed = sessionStorage.getItem(STORAGE_KEY); const dismissed = sessionStorage.getItem(STORAGE_KEY);
setOpen(dismissed !== "1"); setOpen(dismissed !== "1");
}
// Live: no dismissal path — the curtain stays up, only the countdown shows.
}, []); }, []);
// Lock background scroll while the curtain is up. // Lock background scroll while the curtain is up.
@@ -198,20 +225,22 @@ export function ComingSoonOverlay() {
))} ))}
</div> </div>
{/* CTA */} {/* CTA — local/dev only; the live site has no way past the curtain. */}
{canDismiss && (
<div className="mt-9"> <div className="mt-9">
<button <button
type="button" type="button"
onClick={dismiss} onClick={dismiss}
className="group inline-flex items-center gap-2 rounded-full bg-gradient-to-l from-[#3ba7ff] to-[#a855f7] px-8 py-3.5 text-base font-bold text-white shadow-lg shadow-indigo-500/30 transition hover:scale-[1.03] hover:shadow-indigo-500/50" className="group inline-flex items-center gap-2 rounded-full bg-gradient-to-l from-[#3ba7ff] to-[#a855f7] px-8 py-3.5 text-base font-bold text-white shadow-lg shadow-indigo-500/30 transition hover:scale-[1.03] hover:shadow-indigo-500/50"
> >
مشاهدهٔ نسخهٔ آزمایشی مشاهدهٔ نسخهٔ آزمایشی (محلی)
<span className="transition group-hover:-translate-x-1"></span> <span className="transition group-hover:-translate-x-1"></span>
</button> </button>
<p className="mt-3 text-xs text-white/40"> <p className="mt-3 text-xs text-white/40">
میتوانید همین حالا نسخهٔ آزمایشی را ببینید این دکمه فقط در نسخهٔ محلی نمایش داده میشود
</p> </p>
</div> </div>
)}
</motion.div> </motion.div>
</motion.div> </motion.div>
)} )}