"use client"; import { useGetAbout } from "@/app/[name]/(Main)/about/hooks/useAboutData"; import { applyCustomImageCssVariables, applyPatternCssVariables, clearCustomImageCssVariables, clearPatternCssVariables, colorizeSvg, hexToRgba, isCustomImageBackground, isPatternBackground, normalizeBgNumber, PATTERN_BACKGROUND_OPACITY, } from "@/lib/helpers/backgroundUtils"; import { applyCachedThemeToDom, buildThemeCacheFromRestaurant, getThemeCache, removeCachedThemeBackground, setThemeCache, svgToDataUrl, themeBackgroundMatches, type CachedRestaurantTheme, } from "@/lib/helpers/themeCache"; import { useParams } from "next/navigation"; import { createContext, useContext, useEffect, useLayoutEffect, useMemo, useState, type CSSProperties, type ReactNode, } from "react"; const DEFAULT_MENU_COLOR = "#1E3A8A"; export type CustomImageSettings = { bgUrl: string; bgBlur: number | null; bgOpacity: number | null; bgOverlay: string | null; }; type PatternBackgroundContextValue = { isPattern: boolean; isCustomImage: boolean; backgroundStyle: CSSProperties; customImageSettings: CustomImageSettings | null; }; const PatternBackgroundContext = createContext({ isPattern: false, isCustomImage: false, backgroundStyle: {}, customImageSettings: null, }); export function PatternBackgroundProvider({ children }: { children: ReactNode }) { const { name: restaurantSlug } = useParams<{ name: string }>(); const { data: aboutData } = useGetAbout(); const restaurant = aboutData?.data; const [hydratedCache, setHydratedCache] = useState(null); const [patternImageUrl, setPatternImageUrl] = useState(null); const isPattern = restaurant ? isPatternBackground(restaurant) : (hydratedCache?.isPattern ?? false); const isCustomImage = restaurant ? isCustomImageBackground(restaurant) : (!hydratedCache?.isPattern && !!hydratedCache?.bgUrl); const menuColor = restaurant?.menuColor || hydratedCache?.menuColor || DEFAULT_MENU_COLOR; const baseTint = hexToRgba(menuColor, PATTERN_BACKGROUND_OPACITY); useLayoutEffect(() => { if (!restaurantSlug) return; const cached = getThemeCache(restaurantSlug); setHydratedCache(cached); if (!cached) return; applyCachedThemeToDom(cached); if (cached.patternDataUrl) { setPatternImageUrl(cached.patternDataUrl); } }, [restaurantSlug]); useEffect(() => { if (!restaurantSlug) { clearPatternCssVariables(); removeCachedThemeBackground(); setPatternImageUrl(null); return; } if (!restaurant) return; const themeBase = buildThemeCacheFromRestaurant(restaurant); if (!themeBase.isPattern) { clearPatternCssVariables(); setPatternImageUrl(null); if (isCustomImageBackground(restaurant)) { applyCustomImageCssVariables(); setThemeCache(restaurantSlug, { ...themeBase, patternDataUrl: null }); } else { clearCustomImageCssVariables(); removeCachedThemeBackground(); setThemeCache(restaurantSlug, { ...themeBase, patternDataUrl: null }); } return; } clearCustomImageCssVariables(); applyPatternCssVariables(menuColor); if (!themeBase.bgUrl) { setPatternImageUrl(null); setThemeCache(restaurantSlug, { ...themeBase, patternDataUrl: null, }); const saved = getThemeCache(restaurantSlug); if (saved) { applyCachedThemeToDom(saved); } return; } const cached = getThemeCache(restaurantSlug); const cacheHit = cached && themeBackgroundMatches(cached, restaurant) && cached.isPattern && cached.patternDataUrl; if (cacheHit) { setPatternImageUrl(cached.patternDataUrl!); setThemeCache(restaurantSlug, { ...themeBase, patternDataUrl: cached.patternDataUrl, }); applyCachedThemeToDom(cached); return; } let cancelled = false; const proxyUrl = `/${restaurantSlug}/api/proxy-svg?url=${encodeURIComponent(themeBase.bgUrl)}`; fetch(proxyUrl) .then((response) => { if (!response.ok) { throw new Error("Failed to fetch background pattern"); } return response.text(); }) .then((svg) => { if (cancelled) return; const dataUrl = svgToDataUrl(colorizeSvg(svg, menuColor)); setPatternImageUrl(dataUrl); const nextCache = { ...themeBase, patternDataUrl: dataUrl, }; setThemeCache(restaurantSlug, nextCache); const saved = getThemeCache(restaurantSlug); if (saved) { applyCachedThemeToDom(saved); } }) .catch(() => { if (!cancelled && cached?.patternDataUrl) { setPatternImageUrl(cached.patternDataUrl); applyCachedThemeToDom(cached); } }); return () => { cancelled = true; }; }, [menuColor, restaurant, restaurantSlug]); const backgroundStyle = useMemo( () => ({ backgroundColor: baseTint, ...(patternImageUrl ? { backgroundImage: `url("${patternImageUrl}")`, backgroundRepeat: "repeat", backgroundSize: "auto", } : {}), }), [baseTint, patternImageUrl], ); const customImageSettings = useMemo(() => { const src = restaurant?.bgUrl || hydratedCache?.bgUrl; if (!isCustomImage || !src) return null; return { bgUrl: src, bgBlur: normalizeBgNumber(restaurant?.bgBlur ?? hydratedCache?.bgBlur), bgOpacity: normalizeBgNumber(restaurant?.bgOpacity ?? hydratedCache?.bgOpacity), bgOverlay: restaurant?.bgOverlay ?? hydratedCache?.bgOverlay ?? null, }; }, [isCustomImage, restaurant, hydratedCache]); const value = useMemo( () => ({ isPattern, isCustomImage, backgroundStyle, customImageSettings }), [backgroundStyle, isPattern, isCustomImage, customImageSettings], ); return ( {children} ); } export function usePatternBackground() { return useContext(PatternBackgroundContext); }