|
|
@ -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'; |
|
|
|