You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

340 lines
14 KiB

import type { Metadata, Viewport } from "next";
import localFont from "next/font/local";
import { cookies, headers } from "next/headers";
import Providers from "./providers";
import "./globals.css";
import DevClickToComponent from "@/components/Componentes/dev-click-to-component";
import TokenSwitcher from "@/components/Componentes/token-switcher";
import {
defaultLocale,
isLocale,
localeDirections,
} from "@/translations/config";
const faminela = localFont({
src: "../../public/fonts/Faminela/Faminela.otf",
variable: "--font-faminela-local",
display: "swap",
preload: false,
fallback: ["Arial", "sans-serif"],
});
const amiri = localFont({
src: [
{
path: "../../public/fonts/Amiri/Amiri-Regular.ttf",
weight: "400",
style: "normal",
},
{
path: "../../public/fonts/Amiri/Amiri-Italic.ttf",
weight: "400",
style: "italic",
},
{
path: "../../public/fonts/Amiri/Amiri-Bold.ttf",
weight: "700",
style: "normal",
},
{
path: "../../public/fonts/Amiri/Amiri-BoldItalic.ttf",
weight: "700",
style: "italic",
},
],
variable: "--font-amiri",
display: "swap",
preload: false,
fallback: ["Arial", "sans-serif"],
});
const isDevelopment = process.env.NODE_ENV !== "production";
export const metadata: Metadata = {
title: "Habib Marriage",
description: "Islamic Marriage Platform",
};
export const viewport: Viewport = {
width: "device-width",
initialScale: 1,
maximumScale: 1,
userScalable: false,
viewportFit: "cover",
themeColor: "#ffffff",
};
export default async function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
const [cookieStore, requestHeaders] = await Promise.all([
cookies(),
headers(),
]);
const headerLocale = requestHeaders.get("x-habib-locale");
const cookieLocale =
cookieStore.get("HABIB_LANGUAGE")?.value ??
cookieStore.get("habib_language")?.value;
const locale =
headerLocale && isLocale(headerLocale)
? headerLocale
: isLocale(cookieLocale)
? cookieLocale
: defaultLocale;
return (
<html
lang={locale}
dir={localeDirections[locale] ?? "ltr"}
suppressHydrationWarning
>
<head>
<link rel="preconnect" href="https://habibapp.com" />
<link rel="dns-prefetch" href="https://habibapp.com" />
<script
id="habib-core-bootstrap"
dangerouslySetInnerHTML={{
__html: `
(function() {
if (typeof window === 'undefined') return;
// 1. Setup Flutter response dispatcher bridge
var flutterResponseListeners = window.__flutterResponseListeners || [];
window.__flutterResponseListeners = flutterResponseListeners;
window.addFlutterResponseListener = window.addFlutterResponseListener || function(listener) {
flutterResponseListeners.push(listener);
return function() {
var index = flutterResponseListeners.indexOf(listener);
if (index >= 0) flutterResponseListeners.splice(index, 1);
};
};
window.onFlutterResponse = function(event) {
flutterResponseListeners.slice().forEach(function(listener) {
try {
listener(event);
} catch (error) {
console.error('Flutter response listener failed', error);
}
});
};
// 2. Cookie Management & Reactive Global Stores
var HABIB_TOKEN_COOKIE = 'HABIB_TOKEN';
var HABIB_COINS_COOKIE = 'HABIB_COINS';
var HABIB_ENTRY_PATH_COOKIE = 'HABIB_MARRIAGE_ENTRY_PATH';
var HABIB_COOKIE_MAX_AGE = 60 * 60 * 24 * 30;
function writeCookie(name, value) {
if (value === undefined || value === null || value === '') return;
var secure = window.location.protocol === 'https:';
var cookie = name + '=' + encodeURIComponent(String(value)) + '; Path=/; Max-Age=' + HABIB_COOKIE_MAX_AGE + '; SameSite=Lax';
if (secure) cookie += '; Secure';
document.cookie = cookie;
}
function clearCookie(name) {
var secure = window.location.protocol === 'https:';
var cookie = name + '=; Path=/; Max-Age=0; SameSite=Lax';
if (secure) cookie += '; Secure';
document.cookie = cookie;
}
function readCookie(name) {
var cookies = document.cookie ? document.cookie.split('; ') : [];
for (var i = 0; i < cookies.length; i += 1) {
var parts = cookies[i].split('=');
if (parts[0] === name) return decodeURIComponent(parts.slice(1).join('='));
}
return '';
}
if (!Object.getOwnPropertyDescriptor(window, 'HABIB_TOKEN')) {
Object.defineProperty(window, 'HABIB_TOKEN', {
configurable: true,
set: function(value) {
var previousToken = readCookie(HABIB_TOKEN_COOKIE) || readCookie('habib_token');
var nextToken = value === undefined || value === null ? '' : String(value).trim();
var hasToken = nextToken !== '' && nextToken !== 'NO_TOKEN';
this._habib_token = hasToken ? nextToken : undefined;
if (hasToken) {
if (!previousToken || previousToken !== nextToken) {
clearCookie(HABIB_ENTRY_PATH_COOKIE);
}
writeCookie(HABIB_TOKEN_COOKIE, nextToken);
try { sessionStorage.setItem(HABIB_TOKEN_COOKIE, nextToken); } catch (e) {}
} else {
clearCookie(HABIB_TOKEN_COOKIE);
clearCookie('habib_token');
clearCookie(HABIB_ENTRY_PATH_COOKIE);
try { sessionStorage.removeItem(HABIB_TOKEN_COOKIE); } catch (e) {}
}
window.dispatchEvent(new Event('habib:auth-token-changed'));
},
get: function() {
var ssVal;
try { ssVal = sessionStorage.getItem(HABIB_TOKEN_COOKIE); } catch (e) {}
return this._habib_token || readCookie(HABIB_TOKEN_COOKIE) || ssVal || undefined;
}
});
}
if (!Object.getOwnPropertyDescriptor(window, 'HABIB_COINS')) {
Object.defineProperty(window, 'HABIB_COINS', {
configurable: true,
set: function(value) {
this._habib_coins = value;
if (value !== undefined && value !== null && value !== '') {
writeCookie(HABIB_COINS_COOKIE, value);
try { sessionStorage.setItem(HABIB_COINS_COOKIE, String(value)); } catch (e) {}
}
},
get: function() {
var cookieValue = readCookie(HABIB_COINS_COOKIE);
var ssVal;
try { ssVal = sessionStorage.getItem(HABIB_COINS_COOKIE); } catch (e) {}
return this._habib_coins || parseInt(cookieValue || ssVal || '0');
}
});
}
// 3. Document-Level Safe Area & Bootstrap
var root = document.documentElement;
var configApplied = false;
function apply(config) {
if (!config) return;
configApplied = true;
window.__HABIB_BOOTSTRAP__ = config;
var safe = config.safeArea || {};
root.style.setProperty('--safe-top', (Number(safe.top) || 0) + 'px');
root.style.setProperty('--safe-bottom', (Number(safe.bottom) || 0) + 'px');
root.style.setProperty('--safe-left', (Number(safe.left) || 0) + 'px');
root.style.setProperty('--safe-right', (Number(safe.right) || 0) + 'px');
var locale = config.locale || {};
var code = locale.languageCode || locale.language_code;
if (code) {
var secure = window.location.protocol === 'https:' ? '; Secure' : '';
document.cookie = 'HABIB_LANGUAGE=' + encodeURIComponent(String(code)) + '; Path=/; Max-Age=31536000; SameSite=Lax' + secure;
document.cookie = 'habib_language=' + encodeURIComponent(String(code)) + '; Path=/; Max-Age=31536000; SameSite=Lax' + secure;
root.lang = String(code);
root.dir = (locale.isRTL || locale.isRtl || locale.is_rtl) ? 'rtl' : 'ltr';
var segments = window.location.pathname.split('/');
var currentCode = segments[1];
if (currentCode && currentCode !== String(code)) {
root.dataset.webBootstrap = 'pending';
segments[1] = String(code);
window.location.replace(segments.join('/') + window.location.search + window.location.hash);
return;
}
}
root.dataset.webBootstrap = 'ready';
window.dispatchEvent(new CustomEvent('habib:bootstrap-ready', { detail: config }));
}
if (window.__HABIB_BOOTSTRAP__) {
apply(window.__HABIB_BOOTSTRAP__);
}
window.addFlutterResponseListener(function(event) {
var action = String(event && event.action || '').toLowerCase();
if (event && event.success !== false && action === 'initial_config') {
apply(event.data || event.payload);
}
});
var isWebView = !!(window.HabibApp && window.HabibApp.postMessage);
// 4. Queued web_ready Delivery Protocol
//
// Pages call window.__announceHabibWebReady() when their
// destination UI is ready. This only sets a "readyRequested"
// flag and attempts delivery. If HabibApp is not yet injected,
// the request stays pending and is retried when HabibApp
// appears. The 3-second watchdog requests readiness as a
// safety net but does NOT cancel pending delivery attempts.
//
// Contract:
// readyRequested = a destination page says "my UI is ready"
// __habibWebReadySent = postMessage was actually executed
//
var readyRequested = false;
function deliverReadyIfPossible() {
if (window.__habibWebReadySent) return true;
if (!readyRequested) return false;
if (!window.HabibApp || !window.HabibApp.postMessage) return false;
window.__habibWebReadySent = true;
root.dataset.webBootstrap = 'ready';
window.HabibApp.postMessage(JSON.stringify({ action: 'web_ready' }));
return true;
}
function requestWebReady() {
readyRequested = true;
deliverReadyIfPossible();
}
window.__announceHabibWebReady = requestWebReady;
// Safety fallback: auto-request readiness after 3s if no page
// called __announceHabibWebReady(). This ensures the cover is
// never stuck forever, even for pages that forgot the call.
var _habibAutoAnnounceTimer = setTimeout(function() {
requestWebReady();
}, 3000);
// Poll for HabibApp if it wasn't available at parse time.
// When HabibApp appears, attempt delivery of any pending
// ready request. This closes the race where a page requests
// readiness before the bridge is injected.
if (!window.HabibApp || !window.HabibApp.postMessage) {
var attempts = 0;
var bridgePollTimer = setInterval(function() {
attempts += 1;
if (window.HabibApp && window.HabibApp.postMessage) {
clearInterval(bridgePollTimer);
deliverReadyIfPossible();
} else if (attempts >= 100) {
clearInterval(bridgePollTimer);
}
}, 50);
}
// 5. Document Boot ID — reload detection instrumentation.
// If this ID changes across a back navigation, a hard reload
// or WebView recreation happened (not SPA navigation).
window.__habibDocumentBootId = Date.now().toString(36) + Math.random().toString(36).slice(2, 6);
// 6. Hardware Back contract stub.
// Flutter should call: window.__habibHandleHardwareBack()
// The real handler stack is set up by React (use-hardware-back-handler.ts).
// This stub ensures the function exists even before React hydrates.
if (!window.__habibHandleHardwareBack) {
window.__habibHandleHardwareBack = function() {
return Promise.resolve({ handled: false });
};
}
})();
`,
}}
/>
</head>
<body
className={`${faminela.variable} ${amiri.variable}`}
suppressHydrationWarning
>
<Providers>
{isDevelopment ? <TokenSwitcher /> : null}
<div className="app-shell">{children}</div>
</Providers>
{isDevelopment ? <DevClickToComponent /> : null}
</body>
</html>
);
}