feat(web): Next.js 15 app shell, auth, i18n, base UI
App Router setup with next-intl (en/fr), Better Auth wiring, shadcn/ui components, Tailwind, AES-256-GCM encrypt util, Redis rate limiter, tier limit checker, site_settings helper for runtime .env overrides.
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
"use client";
|
||||
|
||||
import { createContext, useContext, useEffect, useState } from "react";
|
||||
import { NextIntlClientProvider } from "next-intl";
|
||||
|
||||
export const SUPPORTED_LOCALES = [
|
||||
{ code: "en", label: "English" },
|
||||
{ code: "fr", label: "Français" },
|
||||
] as const;
|
||||
|
||||
export type Locale = (typeof SUPPORTED_LOCALES)[number]["code"];
|
||||
|
||||
const STORAGE_KEY = "epicure-locale";
|
||||
|
||||
const LocaleContext = createContext<{
|
||||
locale: Locale;
|
||||
setLocale: (locale: Locale) => void;
|
||||
}>({ locale: "en", setLocale: () => {} });
|
||||
|
||||
export function useLocale() {
|
||||
return useContext(LocaleContext);
|
||||
}
|
||||
|
||||
export function I18nProvider({
|
||||
children,
|
||||
messages,
|
||||
initialLocale,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
messages: Record<string, Record<string, unknown>>;
|
||||
initialLocale?: Locale;
|
||||
}) {
|
||||
const resolved = initialLocale ?? ("en" as Locale);
|
||||
const [locale, setLocaleState] = useState<Locale>(resolved);
|
||||
const [currentMessages, setCurrentMessages] = useState(messages[resolved] ?? messages["en"]!);
|
||||
|
||||
useEffect(() => {
|
||||
// If no server-side locale, fall back to localStorage
|
||||
if (!initialLocale) {
|
||||
const stored = localStorage.getItem(STORAGE_KEY) as Locale | null;
|
||||
if (stored && messages[stored]) {
|
||||
setLocaleState(stored);
|
||||
setCurrentMessages(messages[stored]!);
|
||||
}
|
||||
}
|
||||
}, [messages, initialLocale]);
|
||||
|
||||
function setLocale(next: Locale) {
|
||||
setLocaleState(next);
|
||||
setCurrentMessages(messages[next]!);
|
||||
localStorage.setItem(STORAGE_KEY, next);
|
||||
fetch("/api/v1/users/me", {
|
||||
method: "PATCH",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ locale: next }),
|
||||
}).catch(() => {});
|
||||
}
|
||||
|
||||
return (
|
||||
<LocaleContext.Provider value={{ locale, setLocale }}>
|
||||
<NextIntlClientProvider locale={locale} messages={currentMessages} timeZone="UTC">
|
||||
{children}
|
||||
</NextIntlClientProvider>
|
||||
</LocaleContext.Provider>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user