Cache
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
export function getThemeBootScript(): string {
|
||||
return `(function(){try{var s=location.pathname.split("/").filter(Boolean)[0],t=null;if(s&&s!=="auth"){var r=localStorage.getItem("restaurant-theme:"+s);if(r)t=JSON.parse(r)}if(!t||t.version!==1||!t.menuColor){var l=localStorage.getItem("theme-primary-color");if(l){var x=l.replace("#","");if(x.length===6){var p=parseInt(x.slice(0,2),16),f=parseInt(x.slice(2,4),16),m=parseInt(x.slice(4,6),16),b="rgba("+p+","+f+","+m+",0.05)";document.documentElement.style.setProperty("--background",b)}}return}function h(n,a){var u=n.replace("#","");if(u.length!==6)return null;var v=parseInt(u.slice(0,2),16),g=parseInt(u.slice(2,4),16),y=parseInt(u.slice(4,6),16);return"rgba("+v+","+g+","+y+","+a+")"}var e=document.documentElement;if(t.primaryOklch){e.style.setProperty("--primary",t.primaryOklch);e.style.setProperty("--primary-foreground",t.primaryForeground||"oklch(1 0 0)")}if(!t.isPattern)return;e.setAttribute("data-pattern-bg","true");var k=t.backgroundTint||h(t.menuColor,0.05),w=t.bgPatternVector||h(t.menuColor,0.1),z=t.primaryLight||h(t.menuColor,0.15);k&&(e.style.setProperty("--background",k),e.style.setProperty("--bg-pattern-base",k));w&&e.style.setProperty("--bg-pattern-vector",w);z&&e.style.setProperty("--primary-light",z);if(t.patternDataUrl&&k){var d=document.createElement("div");d.id="cached-theme-bg";d.setAttribute("aria-hidden","true");d.style.cssText="position:fixed;inset:0;pointer-events:none;z-index:0;background-color:"+k+';background-image:url("'+t.patternDataUrl+'");background-repeat:repeat;background-size:auto;';document.body.appendChild(d)}}catch(u){}})();`;
|
||||
}
|
||||
@@ -0,0 +1,303 @@
|
||||
import { calculateBrightness, hexToOklch } from "@/lib/helpers/colorUtils";
|
||||
import {
|
||||
hexToRgba,
|
||||
isPatternBackground,
|
||||
PATTERN_BACKGROUND_OPACITY,
|
||||
PATTERN_VECTOR_OPACITY,
|
||||
PRIMARY_LIGHT_OPACITY,
|
||||
type RestaurantBackgroundSettings,
|
||||
} from "@/lib/helpers/backgroundUtils";
|
||||
import type { AboutResponse } from "@/app/[name]/(Main)/about/types/Types";
|
||||
import type {
|
||||
CategoriesResponse,
|
||||
FoodsResponse,
|
||||
} from "@/app/[name]/(Main)/types/Types";
|
||||
import type { QueryClient } from "@tanstack/react-query";
|
||||
|
||||
export type CachedRestaurantTheme = {
|
||||
version: 1;
|
||||
menuColor: string;
|
||||
bgUrl?: string | null;
|
||||
isPattern: boolean;
|
||||
patternDataUrl?: string | null;
|
||||
primaryOklch?: string;
|
||||
primaryForeground?: string;
|
||||
backgroundTint?: string;
|
||||
bgPatternVector?: string;
|
||||
primaryLight?: string;
|
||||
};
|
||||
|
||||
const CACHE_VERSION = 1;
|
||||
const CACHE_KEY_PREFIX = "restaurant-theme";
|
||||
const LEGACY_COLOR_KEY = "theme-primary-color";
|
||||
const ABOUT_CACHE_PREFIX = "restaurant-about";
|
||||
const MENU_CACHE_PREFIX = "restaurant-menu";
|
||||
const CATEGORIES_CACHE_PREFIX = "restaurant-categories";
|
||||
export const THEME_BG_ELEMENT_ID = "cached-theme-bg";
|
||||
|
||||
export function getThemeCacheKey(slug: string): string {
|
||||
return `${CACHE_KEY_PREFIX}:${slug}`;
|
||||
}
|
||||
|
||||
export function getAboutCacheKey(slug: string): string {
|
||||
return `${ABOUT_CACHE_PREFIX}:${slug}`;
|
||||
}
|
||||
|
||||
export function getMenuCacheKey(slug: string): string {
|
||||
return `${MENU_CACHE_PREFIX}:${slug}`;
|
||||
}
|
||||
|
||||
export function getCategoriesCacheKey(slug: string): string {
|
||||
return `${CATEGORIES_CACHE_PREFIX}:${slug}`;
|
||||
}
|
||||
|
||||
export function getRestaurantSlugFromPath(pathname?: string): string | null {
|
||||
const path =
|
||||
pathname ?? (typeof window !== "undefined" ? window.location.pathname : "");
|
||||
const segment = path.split("/").filter(Boolean)[0];
|
||||
if (!segment || segment === "auth") return null;
|
||||
return segment;
|
||||
}
|
||||
|
||||
export function computeThemeCssValues(menuColor: string) {
|
||||
const primaryOklch = hexToOklch(menuColor);
|
||||
const brightness = calculateBrightness(menuColor);
|
||||
|
||||
return {
|
||||
primaryOklch: primaryOklch ?? undefined,
|
||||
primaryForeground: brightness > 0.5 ? "oklch(0 0 0)" : "oklch(1 0 0)",
|
||||
backgroundTint: hexToRgba(menuColor, PATTERN_BACKGROUND_OPACITY),
|
||||
bgPatternVector: hexToRgba(menuColor, PATTERN_VECTOR_OPACITY),
|
||||
primaryLight: hexToRgba(menuColor, PRIMARY_LIGHT_OPACITY),
|
||||
};
|
||||
}
|
||||
|
||||
export function getThemeCache(slug: string): CachedRestaurantTheme | null {
|
||||
if (!slug || typeof window === "undefined") return null;
|
||||
|
||||
try {
|
||||
const raw = localStorage.getItem(getThemeCacheKey(slug));
|
||||
if (!raw) return null;
|
||||
|
||||
const parsed = JSON.parse(raw) as CachedRestaurantTheme;
|
||||
if (parsed.version !== CACHE_VERSION || !parsed.menuColor) return null;
|
||||
|
||||
if (!parsed.primaryOklch) {
|
||||
const enriched: CachedRestaurantTheme = {
|
||||
...parsed,
|
||||
...computeThemeCssValues(parsed.menuColor),
|
||||
version: CACHE_VERSION,
|
||||
};
|
||||
|
||||
try {
|
||||
localStorage.setItem(getThemeCacheKey(slug), JSON.stringify(enriched));
|
||||
} catch {
|
||||
// Ignore quota errors during migration
|
||||
}
|
||||
|
||||
return enriched;
|
||||
}
|
||||
|
||||
return parsed;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function getCachedMenuColor(slug?: string | null): string | null {
|
||||
if (slug) {
|
||||
const cached = getThemeCache(slug);
|
||||
if (cached?.menuColor) return cached.menuColor;
|
||||
}
|
||||
|
||||
if (typeof window === "undefined") return null;
|
||||
|
||||
try {
|
||||
return localStorage.getItem(LEGACY_COLOR_KEY);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function getPersistedAboutData(slug: string): AboutResponse | undefined {
|
||||
if (!slug || typeof window === "undefined") return undefined;
|
||||
|
||||
try {
|
||||
const raw = localStorage.getItem(getAboutCacheKey(slug));
|
||||
if (!raw) return undefined;
|
||||
return JSON.parse(raw) as AboutResponse;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export function setPersistedAboutData(slug: string, data: AboutResponse): void {
|
||||
if (!slug || typeof window === "undefined") return;
|
||||
|
||||
try {
|
||||
localStorage.setItem(getAboutCacheKey(slug), JSON.stringify(data));
|
||||
} catch {
|
||||
// Ignore quota errors
|
||||
}
|
||||
}
|
||||
|
||||
function readPersistedQueryData<T>(key: string): T | undefined {
|
||||
try {
|
||||
const raw = localStorage.getItem(key);
|
||||
if (!raw) return undefined;
|
||||
return JSON.parse(raw) as T;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function writePersistedQueryData<T>(key: string, data: T): void {
|
||||
try {
|
||||
localStorage.setItem(key, JSON.stringify(data));
|
||||
} catch {
|
||||
// Ignore quota errors
|
||||
}
|
||||
}
|
||||
|
||||
export function getPersistedMenuData(slug: string): FoodsResponse | undefined {
|
||||
if (!slug || typeof window === "undefined") return undefined;
|
||||
return readPersistedQueryData<FoodsResponse>(getMenuCacheKey(slug));
|
||||
}
|
||||
|
||||
export function setPersistedMenuData(slug: string, data: FoodsResponse): void {
|
||||
if (!slug || typeof window === "undefined") return;
|
||||
writePersistedQueryData(getMenuCacheKey(slug), data);
|
||||
}
|
||||
|
||||
export function getPersistedCategoriesData(
|
||||
slug: string,
|
||||
): CategoriesResponse | undefined {
|
||||
if (!slug || typeof window === "undefined") return undefined;
|
||||
return readPersistedQueryData<CategoriesResponse>(getCategoriesCacheKey(slug));
|
||||
}
|
||||
|
||||
export function setPersistedCategoriesData(
|
||||
slug: string,
|
||||
data: CategoriesResponse,
|
||||
): void {
|
||||
if (!slug || typeof window === "undefined") return;
|
||||
writePersistedQueryData(getCategoriesCacheKey(slug), data);
|
||||
}
|
||||
|
||||
export function restorePersistedRestaurantQueries(
|
||||
client: QueryClient,
|
||||
slug: string,
|
||||
): void {
|
||||
const about = getPersistedAboutData(slug);
|
||||
if (about) {
|
||||
client.setQueryData(["about", slug], about);
|
||||
}
|
||||
|
||||
const menu = getPersistedMenuData(slug);
|
||||
if (menu) {
|
||||
client.setQueryData(["menu", slug], menu);
|
||||
}
|
||||
|
||||
const categories = getPersistedCategoriesData(slug);
|
||||
if (categories) {
|
||||
client.setQueryData(["categories", slug], categories);
|
||||
}
|
||||
}
|
||||
|
||||
export function setThemeCache(
|
||||
slug: string,
|
||||
theme: Omit<CachedRestaurantTheme, "version">,
|
||||
): void {
|
||||
if (!slug || typeof window === "undefined") return;
|
||||
|
||||
const existing = getThemeCache(slug);
|
||||
const css = computeThemeCssValues(theme.menuColor);
|
||||
const payload: CachedRestaurantTheme = {
|
||||
...theme,
|
||||
...css,
|
||||
patternDataUrl: theme.patternDataUrl ?? existing?.patternDataUrl ?? null,
|
||||
version: CACHE_VERSION,
|
||||
};
|
||||
|
||||
try {
|
||||
localStorage.setItem(getThemeCacheKey(slug), JSON.stringify(payload));
|
||||
localStorage.setItem(LEGACY_COLOR_KEY, theme.menuColor);
|
||||
} catch {
|
||||
try {
|
||||
const { patternDataUrl: _pattern, ...withoutPattern } = payload;
|
||||
localStorage.setItem(
|
||||
getThemeCacheKey(slug),
|
||||
JSON.stringify({ ...withoutPattern, patternDataUrl: null }),
|
||||
);
|
||||
localStorage.setItem(LEGACY_COLOR_KEY, theme.menuColor);
|
||||
} catch {
|
||||
// Ignore quota errors
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function buildThemeCacheFromRestaurant(
|
||||
settings: RestaurantBackgroundSettings,
|
||||
): Pick<CachedRestaurantTheme, "menuColor" | "bgUrl" | "isPattern"> {
|
||||
return {
|
||||
menuColor: settings.menuColor || "#1E3A8A",
|
||||
bgUrl: settings.bgUrl,
|
||||
isPattern: isPatternBackground(settings),
|
||||
};
|
||||
}
|
||||
|
||||
export function svgToDataUrl(svg: string): string {
|
||||
return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;
|
||||
}
|
||||
|
||||
export function applyCachedThemeToDom(theme: CachedRestaurantTheme): void {
|
||||
if (typeof document === "undefined") return;
|
||||
|
||||
const root = document.documentElement;
|
||||
|
||||
if (theme.primaryOklch) {
|
||||
root.style.setProperty("--primary", theme.primaryOklch);
|
||||
root.style.setProperty(
|
||||
"--primary-foreground",
|
||||
theme.primaryForeground ?? "oklch(1 0 0)",
|
||||
);
|
||||
}
|
||||
|
||||
if (!theme.isPattern) return;
|
||||
|
||||
root.setAttribute("data-pattern-bg", "true");
|
||||
|
||||
if (theme.backgroundTint) {
|
||||
root.style.setProperty("--background", theme.backgroundTint);
|
||||
root.style.setProperty("--bg-pattern-base", theme.backgroundTint);
|
||||
}
|
||||
if (theme.bgPatternVector) {
|
||||
root.style.setProperty("--bg-pattern-vector", theme.bgPatternVector);
|
||||
}
|
||||
if (theme.primaryLight) {
|
||||
root.style.setProperty("--primary-light", theme.primaryLight);
|
||||
}
|
||||
|
||||
if (!theme.patternDataUrl || !theme.backgroundTint) return;
|
||||
|
||||
let bgElement = document.getElementById(THEME_BG_ELEMENT_ID);
|
||||
if (!bgElement) {
|
||||
bgElement = document.createElement("div");
|
||||
bgElement.id = THEME_BG_ELEMENT_ID;
|
||||
bgElement.setAttribute("aria-hidden", "true");
|
||||
bgElement.style.position = "fixed";
|
||||
bgElement.style.inset = "0";
|
||||
bgElement.style.pointerEvents = "none";
|
||||
bgElement.style.zIndex = "0";
|
||||
document.body.appendChild(bgElement);
|
||||
}
|
||||
|
||||
bgElement.style.backgroundColor = theme.backgroundTint;
|
||||
bgElement.style.backgroundImage = `url("${theme.patternDataUrl}")`;
|
||||
bgElement.style.backgroundRepeat = "repeat";
|
||||
bgElement.style.backgroundSize = "auto";
|
||||
}
|
||||
|
||||
export function removeCachedThemeBackground(): void {
|
||||
document.getElementById(THEME_BG_ELEMENT_ID)?.remove();
|
||||
}
|
||||
Reference in New Issue
Block a user