Files
dmenu-plus-front/src/components/background/PatternBackgroundProvider.tsx
T
hamid zarghami e8a950b0b6 background image
2026-06-22 09:20:08 +03:30

233 lines
6.2 KiB
TypeScript

"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<PatternBackgroundContextValue>({
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<CachedRestaurantTheme | null>(null);
const [patternImageUrl, setPatternImageUrl] = useState<string | null>(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<CSSProperties>(
() => ({
backgroundColor: baseTint,
...(patternImageUrl
? {
backgroundImage: `url("${patternImageUrl}")`,
backgroundRepeat: "repeat",
backgroundSize: "auto",
}
: {}),
}),
[baseTint, patternImageUrl],
);
const customImageSettings = useMemo<CustomImageSettings | null>(() => {
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 (
<PatternBackgroundContext.Provider value={value}>
{children}
</PatternBackgroundContext.Provider>
);
}
export function usePatternBackground() {
return useContext(PatternBackgroundContext);
}