/* * Copyright (C) 2026 Fluxer Contributors * * This file is part of Fluxer. * * Fluxer is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Fluxer is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with Fluxer. If not, see . */ import {I18nProvider} from '@lingui/react'; import clsx from 'clsx'; import {observer} from 'mobx-react-lite'; import {type ReactNode, useCallback, useEffect, useMemo, useRef, useState} from 'react'; import type {GuildSplashCardAlignmentValue} from '~/Constants'; import {GuildSplashCardAlignment} from '~/Constants'; import {AuthBackground} from '~/components/auth/AuthBackground'; import {AuthCardContainer} from '~/components/auth/AuthCardContainer'; import {NativeDragRegion} from '~/components/layout/NativeDragRegion'; import {NativeTitlebar} from '~/components/layout/NativeTitlebar'; import {Scroller, type ScrollerHandle} from '~/components/uikit/Scroller'; import {AuthLayoutContext} from '~/contexts/AuthLayoutContext'; import {useSetLayoutVariant} from '~/contexts/LayoutVariantContext'; import {useAuthBackground} from '~/hooks/useAuthBackground'; import {useNativePlatform} from '~/hooks/useNativePlatform'; import i18n, {initI18n} from '~/i18n'; import FluxerWordmarkMonochrome from '~/images/fluxer-logo-wordmark-monochrome.svg?react'; import foodPatternUrl from '~/images/i-like-food.svg'; import {useLocation} from '~/lib/router'; import {isMobileExperienceEnabled} from '~/utils/mobileExperience'; import styles from './AuthLayout.module.css'; const AuthLayoutContent = observer(function AuthLayoutContent({children}: {children?: ReactNode}) { const [viewportHeight, setViewportHeight] = useState(() => window.innerHeight); const [viewportWidth, setViewportWidth] = useState(() => window.innerWidth); const [splashUrl, setSplashUrl] = useState(null); const [showLogoSide, setShowLogoSide] = useState(true); const [splashAlignment, setSplashAlignment] = useState( GuildSplashCardAlignment.CENTER, ); const {isNative, isMacOS, platform} = useNativePlatform(); const splashUrlRef = useRef(null); const scrollerRef = useRef(null); const location = useLocation(); const {patternReady, splashLoaded, splashDimensions} = useAuthBackground(splashUrl, foodPatternUrl); const handleSetSplashUrl = useCallback( (url: string | null) => { if (splashUrlRef.current === url) return; splashUrlRef.current = url; setSplashUrl(url); if (!url) { setSplashAlignment(GuildSplashCardAlignment.CENTER); } }, [setSplashAlignment], ); useEffect(() => { const handleResize = () => { setViewportWidth(window.innerWidth); setViewportHeight(window.innerHeight); }; handleResize(); window.addEventListener('resize', handleResize); return () => window.removeEventListener('resize', handleResize); }, []); useEffect(() => { document.documentElement.classList.add('auth-page'); return () => { document.documentElement.classList.remove('auth-page'); }; }, []); useEffect(() => { scrollerRef.current?.scrollToTop(); }, [location.pathname]); const splashScale = useMemo(() => { if (!splashDimensions) return null; const {width, height} = splashDimensions; if (width <= 0 || height <= 0) return null; const heightScale = viewportHeight / height; const widthScale = viewportWidth / width; return Math.max(heightScale, widthScale); }, [splashDimensions, viewportHeight, viewportWidth]); const isMobileExperience = isMobileExperienceEnabled(); if (isMobileExperience) { return (
{children}
); } return (
{isNative && !isMacOS && }
{children}
); }); export const AuthLayout = observer(function AuthLayout({children}: {children?: ReactNode}) { const [isI18nInitialized, setIsI18nInitialized] = useState(false); const setLayoutVariant = useSetLayoutVariant(); useEffect(() => { setLayoutVariant('auth'); return () => { setLayoutVariant('app'); }; }, [setLayoutVariant]); useEffect(() => { initI18n().then(() => { setIsI18nInitialized(true); }); }, []); if (!isI18nInitialized) { return null; } return ( {children} ); });