parent
commit
5b51820e40
  1. 107
      src/app/layout.tsx

107
src/app/layout.tsx

@ -1,8 +1,7 @@
import type { Metadata, Viewport } from "next"; import type { Metadata, Viewport } from "next";
import { Amiri } from "next/font/google"; import { Amiri } from "next/font/google";
import localFont from "next/font/local"; import localFont from "next/font/local";
import { headers } from "next/headers";
import Script from "next/script";
import { cookies, headers } from "next/headers";
import Providers from "./providers"; import Providers from "./providers";
import "./globals.css"; import "./globals.css";
import DevClickToComponent from "@/components/Componentes/dev-click-to-component"; import DevClickToComponent from "@/components/Componentes/dev-click-to-component";
@ -54,10 +53,20 @@ export default async function RootLayout({
}: Readonly<{ }: Readonly<{
children: React.ReactNode; children: React.ReactNode;
}>) { }>) {
const requestHeaders = await headers();
const [cookieStore, requestHeaders] = await Promise.all([
cookies(),
headers(),
]);
const headerLocale = requestHeaders.get("x-habib-locale"); const headerLocale = requestHeaders.get("x-habib-locale");
const cookieLocale =
cookieStore.get("HABIB_LANGUAGE")?.value ??
cookieStore.get("habib_language")?.value;
const locale = const locale =
headerLocale && isLocale(headerLocale) ? headerLocale : defaultLocale;
headerLocale && isLocale(headerLocale)
? headerLocale
: isLocale(cookieLocale)
? cookieLocale
: defaultLocale;
return ( return (
<html <html
@ -68,24 +77,22 @@ export default async function RootLayout({
<head> <head>
<link rel="preconnect" href="https://habibapp.com" /> <link rel="preconnect" href="https://habibapp.com" />
<link rel="dns-prefetch" href="https://habibapp.com" /> <link rel="dns-prefetch" href="https://habibapp.com" />
<Script
id="flutter-and-cookies-setup"
strategy="beforeInteractive"
// biome-ignore lint/security/noDangerouslySetInnerHtml: Static document-start bridge code must run before hydration.
<script
id="habib-core-bootstrap"
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
__html: ` __html: `
if (typeof window !== 'undefined') {
(function() {
if (typeof window === 'undefined') return;
// 1. Setup Flutter response dispatcher bridge
var flutterResponseListeners = window.__flutterResponseListeners || []; var flutterResponseListeners = window.__flutterResponseListeners || [];
window.__flutterResponseListeners = flutterResponseListeners; window.__flutterResponseListeners = flutterResponseListeners;
window.addFlutterResponseListener = window.addFlutterResponseListener || function(listener) { window.addFlutterResponseListener = window.addFlutterResponseListener || function(listener) {
flutterResponseListeners.push(listener); flutterResponseListeners.push(listener);
return function() { return function() {
var index = flutterResponseListeners.indexOf(listener); var index = flutterResponseListeners.indexOf(listener);
if (index >= 0) {
flutterResponseListeners.splice(index, 1);
}
if (index >= 0) flutterResponseListeners.splice(index, 1);
}; };
}; };
@ -99,6 +106,7 @@ export default async function RootLayout({
}); });
}; };
// 2. Cookie Management & Reactive Global Stores
var HABIB_TOKEN_COOKIE = 'HABIB_TOKEN'; var HABIB_TOKEN_COOKIE = 'HABIB_TOKEN';
var HABIB_COINS_COOKIE = 'HABIB_COINS'; var HABIB_COINS_COOKIE = 'HABIB_COINS';
var HABIB_ENTRY_PATH_COOKIE = 'HABIB_MARRIAGE_ENTRY_PATH'; var HABIB_ENTRY_PATH_COOKIE = 'HABIB_MARRIAGE_ENTRY_PATH';
@ -106,38 +114,25 @@ export default async function RootLayout({
function writeCookie(name, value) { function writeCookie(name, value) {
if (value === undefined || value === null || value === '') return; if (value === undefined || value === null || value === '') return;
var secure = window.location.protocol === 'https:'; var secure = window.location.protocol === 'https:';
var cookie = name + '=' + encodeURIComponent(String(value)) + '; Path=/; Max-Age=' + HABIB_COOKIE_MAX_AGE + '; SameSite=Lax'; var cookie = name + '=' + encodeURIComponent(String(value)) + '; Path=/; Max-Age=' + HABIB_COOKIE_MAX_AGE + '; SameSite=Lax';
if (secure) {
cookie += '; Secure';
}
if (secure) cookie += '; Secure';
document.cookie = cookie; document.cookie = cookie;
} }
function clearCookie(name) { function clearCookie(name) {
var secure = window.location.protocol === 'https:'; var secure = window.location.protocol === 'https:';
var cookie = name + '=; Path=/; Max-Age=0; SameSite=Lax'; var cookie = name + '=; Path=/; Max-Age=0; SameSite=Lax';
if (secure) {
cookie += '; Secure';
}
if (secure) cookie += '; Secure';
document.cookie = cookie; document.cookie = cookie;
} }
function readCookie(name) { function readCookie(name) {
var cookies = document.cookie ? document.cookie.split('; ') : []; var cookies = document.cookie ? document.cookie.split('; ') : [];
for (var i = 0; i < cookies.length; i += 1) { for (var i = 0; i < cookies.length; i += 1) {
var parts = cookies[i].split('='); var parts = cookies[i].split('=');
if (parts[0] === name) {
return decodeURIComponent(parts.slice(1).join('='));
}
if (parts[0] === name) return decodeURIComponent(parts.slice(1).join('='));
} }
return ''; return '';
} }
@ -155,24 +150,18 @@ export default async function RootLayout({
clearCookie(HABIB_ENTRY_PATH_COOKIE); clearCookie(HABIB_ENTRY_PATH_COOKIE);
} }
writeCookie(HABIB_TOKEN_COOKIE, nextToken); writeCookie(HABIB_TOKEN_COOKIE, nextToken);
try {
sessionStorage.setItem(HABIB_TOKEN_COOKIE, nextToken);
} catch (e) {}
try { sessionStorage.setItem(HABIB_TOKEN_COOKIE, nextToken); } catch (e) {}
} else { } else {
clearCookie(HABIB_TOKEN_COOKIE); clearCookie(HABIB_TOKEN_COOKIE);
clearCookie('habib_token'); clearCookie('habib_token');
clearCookie(HABIB_ENTRY_PATH_COOKIE); clearCookie(HABIB_ENTRY_PATH_COOKIE);
try {
sessionStorage.removeItem(HABIB_TOKEN_COOKIE);
} catch (e) {}
try { sessionStorage.removeItem(HABIB_TOKEN_COOKIE); } catch (e) {}
} }
window.dispatchEvent(new Event('habib:auth-token-changed')); window.dispatchEvent(new Event('habib:auth-token-changed'));
}, },
get: function() { get: function() {
var ssVal; var ssVal;
try {
ssVal = sessionStorage.getItem(HABIB_TOKEN_COOKIE);
} catch (e) {}
try { ssVal = sessionStorage.getItem(HABIB_TOKEN_COOKIE); } catch (e) {}
return this._habib_token || readCookie(HABIB_TOKEN_COOKIE) || ssVal || undefined; return this._habib_token || readCookie(HABIB_TOKEN_COOKIE) || ssVal || undefined;
} }
}); });
@ -185,43 +174,19 @@ export default async function RootLayout({
this._habib_coins = value; this._habib_coins = value;
if (value !== undefined && value !== null && value !== '') { if (value !== undefined && value !== null && value !== '') {
writeCookie(HABIB_COINS_COOKIE, value); writeCookie(HABIB_COINS_COOKIE, value);
try {
sessionStorage.setItem(HABIB_COINS_COOKIE, String(value));
} catch (e) {}
try { sessionStorage.setItem(HABIB_COINS_COOKIE, String(value)); } catch (e) {}
} }
}, },
get: function() { get: function() {
var cookieValue = readCookie(HABIB_COINS_COOKIE); var cookieValue = readCookie(HABIB_COINS_COOKIE);
var ssVal; var ssVal;
try {
ssVal = sessionStorage.getItem(HABIB_COINS_COOKIE);
} catch (e) {}
try { ssVal = sessionStorage.getItem(HABIB_COINS_COOKIE); } catch (e) {}
return this._habib_coins || parseInt(cookieValue || ssVal || '0'); return this._habib_coins || parseInt(cookieValue || ssVal || '0');
} }
}); });
} }
// Performance monitoring
window.addEventListener('load', function() {
try {
var perfData = performance.getEntriesByType('navigation')[0];
if (perfData) {
console.log('⏱️ Load time:', Math.round(perfData.loadEventEnd - perfData.fetchStart), 'ms');
}
} catch (e) {}
});
}
`,
}}
/>
<Script
id="habib-web-bootstrap"
strategy="beforeInteractive"
// biome-ignore lint/security/noDangerouslySetInnerHtml: Static bootstrap code must receive Flutter config before the app is visible.
dangerouslySetInnerHTML={{
__html: `
(function() {
if (typeof window === 'undefined') return;
// 3. Document-Level Safe Area & Bootstrap
var root = document.documentElement; var root = document.documentElement;
var configApplied = false; var configApplied = false;
@ -255,9 +220,6 @@ export default async function RootLayout({
window.dispatchEvent(new CustomEvent('habib:bootstrap-ready', { detail: config })); window.dispatchEvent(new CustomEvent('habib:bootstrap-ready', { detail: config }));
} }
// Document-start injection: if Flutter placed the config on
// window before this script ran, apply it immediately so the
// pre-config frame never renders at all.
if (window.__HABIB_BOOTSTRAP__) { if (window.__HABIB_BOOTSTRAP__) {
apply(window.__HABIB_BOOTSTRAP__); apply(window.__HABIB_BOOTSTRAP__);
} }
@ -269,16 +231,12 @@ export default async function RootLayout({
} }
}); });
// This script lives in <head>, so everything above runs before
// the body renders. Hide the shell before the first paint
// inside the Habib WebView so the untuned frame (wrong
// safe-area / locale) never becomes visible. Plain browsers
// (no HabibApp bridge) are never hidden.
var isWebView = !!(window.HabibApp && window.HabibApp.postMessage); var isWebView = !!(window.HabibApp && window.HabibApp.postMessage);
if (isWebView && !configApplied) { if (isWebView && !configApplied) {
root.dataset.webBootstrap = 'pending'; root.dataset.webBootstrap = 'pending';
} }
// 4. Instant web_ready Announcement
function announce() { function announce() {
if (window.__habibWebReadySent || !window.HabibApp || !window.HabibApp.postMessage) return false; if (window.__habibWebReadySent || !window.HabibApp || !window.HabibApp.postMessage) return false;
window.__habibWebReadySent = true; window.__habibWebReadySent = true;
@ -291,9 +249,6 @@ export default async function RootLayout({
function tryAnnounce() { function tryAnnounce() {
if (!announce()) return false; if (!announce()) return false;
// Watchdog: if the bridge never delivers initial_config
// (wedged WebView / very old client), release the shell so
// the app is never left invisible.
setTimeout(function() { setTimeout(function() {
if (root.dataset.webBootstrap === 'pending') { if (root.dataset.webBootstrap === 'pending') {
root.dataset.webBootstrap = 'ready'; root.dataset.webBootstrap = 'ready';

Loading…
Cancel
Save