import { type IdTokenClaims, LogtoProvider, useLogto, type Prompt } from '@logto/react'; import { demoAppApplicationId } from '@logto/schemas'; import { useCallback, useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import '@/scss/normalized.scss'; import * as styles from './App.module.scss'; import Callback from './Callback'; import DevPanel from './DevPanel'; import congratsDark from './assets/congrats-dark.svg'; import congrats from './assets/congrats.svg'; import initI18n from './i18n/init'; import { getLocalData, setLocalData } from './utils'; void initI18n(); const Main = () => { const config = getLocalData('config'); const params = new URL(window.location.href).searchParams; const { isAuthenticated, isLoading, getIdTokenClaims, signIn, signOut } = useLogto(); const [user, setUser] = useState>(); const { t } = useTranslation(undefined, { keyPrefix: 'demo_app' }); const isInCallback = Boolean(params.get('code')); const isDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches; const [congratsIcon, setCongratsIcon] = useState(isDarkMode ? congratsDark : congrats); const [showDevPanel, setShowDevPanel] = useState(getLocalData('ui').showDevPanel ?? false); const error = params.get('error'); const errorDescription = params.get('error_description'); const toggleDevPanel = useCallback(() => { setShowDevPanel((previous) => { setLocalData('ui', { showDevPanel: !previous }); return !previous; }); }, []); useEffect(() => { if (isInCallback || isLoading || error) { return; } const loadIdTokenClaims = async () => { const userInfo = await getIdTokenClaims(); setUser(userInfo ?? { sub: 'N/A', username: 'N/A' }); }; // If user is authenticated but user info is not loaded yet, load it if (isAuthenticated && !user) { void loadIdTokenClaims(); } // If user is not authenticated, redirect to sign-in page if (!isAuthenticated) { void signIn({ redirectUri: window.location.origin + window.location.pathname, extraParams: Object.fromEntries( new URLSearchParams([ ...new URLSearchParams(config.signInExtraParams).entries(), ...new URLSearchParams(window.location.search).entries(), ]).entries() ), }); } }, [ config.signInExtraParams, error, getIdTokenClaims, isAuthenticated, isInCallback, isLoading, signIn, user, ]); useEffect(() => { const onThemeChange = (event: MediaQueryListEvent) => { const isDarkMode = event.matches; setCongratsIcon(isDarkMode ? congratsDark : congrats); }; window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', onThemeChange); return () => { window .matchMedia('(prefers-color-scheme: dark)') .removeEventListener('change', onThemeChange); }; }, []); if (isInCallback) { return ; } if (error) { return (

Error occurred: {error}
{errorDescription}

); } if (!isAuthenticated || !user) { return null; } return (
{showDevPanel && }
{congratsIcon && Congrats}
{t('title')}
{t('subtitle')}
{user.username && (
{t('username')} {user.username}
)}
{t('user_id')} {user.sub}
signOut(`${window.location.origin}/demo-app`)} onKeyDown={({ key }) => { if (key === 'Enter' || key === ' ') { void signOut(`${window.location.origin}/demo-app`); } }} > {t('sign_out')}
{ if (key === 'Enter' || key === ' ') { toggleDevPanel(); } }} > {showDevPanel ? 'Close' : 'Open'} dev panel
); }; const App = () => { const params = new URL(window.location.href).searchParams; const config = getLocalData('config'); return (
); }; export default App;