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:
@@ -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 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() {
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* CTA */}
|
||||
{/* CTA — local/dev only; the live site has no way past the curtain. */}
|
||||
{canDismiss && (
|
||||
<div className="mt-9">
|
||||
<button
|
||||
type="button"
|
||||
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"
|
||||
>
|
||||
مشاهدهٔ نسخهٔ آزمایشی
|
||||
مشاهدهٔ نسخهٔ آزمایشی (محلی)
|
||||
<span className="transition group-hover:-translate-x-1">←</span>
|
||||
</button>
|
||||
<p className="mt-3 text-xs text-white/40">
|
||||
میتوانید همین حالا نسخهٔ آزمایشی را ببینید
|
||||
این دکمه فقط در نسخهٔ محلی نمایش داده میشود
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user