import type { AuthSession, AuthState, AuthUser } from '@pipu/core'; import { SUPPORTED_LOCALES, type Locale } from '@pipu/i18n'; import { createContext, useContext, useEffect, useSyncExternalStore, type ReactNode } from 'react'; import { useTranslation } from 'react-i18next'; const SessionContext = createContext(null); export function SessionProvider({ session, children }: { session: AuthSession; children: ReactNode }) { return ( {children} ); } export function useSession(): AuthSession { const s = useContext(SessionContext); if (!s) throw new Error('useSession outside SessionProvider'); return s; } export function useAuthState(): AuthState { const session = useSession(); return useSyncExternalStore( (fn) => session.subscribe(fn), () => session.state, ); } /** Текущий пользователь; вызывать только на защищённых страницах. */ export function useUser(): AuthUser { const state = useAuthState(); if (state.status !== 'signed_in') throw new Error('useUser: not signed in'); return state.user; } const isLocale = (l: string): l is Locale => (SUPPORTED_LOCALES as readonly string[]).includes(l); /** Применяет настройки вошедшего пользователя: язык и тему. */ function UserEffects({ session }: { session: AuthSession }) { const { i18n } = useTranslation(); const state = useSyncExternalStore( (fn) => session.subscribe(fn), () => session.state, ); const user = state.status === 'signed_in' ? state.user : null; const locale = user?.locale; const theme = user?.ui_prefs.theme; useEffect(() => { if (locale && isLocale(locale) && locale !== i18n.language) void i18n.changeLanguage(locale); }, [locale, i18n]); useEffect(() => { applyTheme(theme); }, [theme]); return null; } /** Тёмная тема по умолчанию (docs/design.md); system — по ОС через prefers-color-scheme (tokens.css). */ export function applyTheme(theme: string | undefined) { const root = document.documentElement; if (theme === 'system') delete root.dataset.theme; else root.dataset.theme = theme === 'light' ? 'light' : 'dark'; }