Files
germantax-e2e-mrg17i61/apps/app/src/auth/session-provider.tsx
2026-07-11 09:17:02 +02:00

88 lines
2.0 KiB
TypeScript

import {
createContext,
useContext,
useEffect,
type ReactNode,
} from 'react'
import { useNavigate, useRouterState } from '@tanstack/react-router'
import { Center, Loader } from '@pikku/mantine/core'
import { usePikkuQuery } from '@germantax/functions-sdk/pikku/api.gen'
export interface Session {
userId: string
email: string
displayName: string
}
interface SessionContextValue {
session: Session
refetch: () => void
}
const SessionContext = createContext<SessionContextValue | null>(null)
const PUBLIC_ROUTES = new Set<string>(['/login', '/logout'])
const isPublicRoute = (pathname: string) => PUBLIC_ROUTES.has(pathname)
export function SessionProvider({ children }: { children: ReactNode }) {
const navigate = useNavigate()
const pathname = useRouterState({ select: (s) => s.location.pathname })
const onPublicRoute = isPublicRoute(pathname)
const me = usePikkuQuery('getMe', undefined as never, {
enabled: !onPublicRoute,
retry: false,
staleTime: 60_000,
})
useEffect(() => {
if (onPublicRoute) return
if (me.error) {
navigate({
to: '/login',
search: { redirect: pathname } as never,
replace: true,
})
}
}, [me.error, onPublicRoute, navigate, pathname])
if (onPublicRoute) {
return <>{children}</>
}
if (me.isLoading || me.error || !me.data) {
return (
<Center mih="100vh">
<Loader size="sm" />
</Center>
)
}
return (
<SessionContext.Provider
value={{ session: me.data, refetch: () => me.refetch() }}
>
{children}
</SessionContext.Provider>
)
}
export function useSession(): Session {
const ctx = useContext(SessionContext)
if (!ctx) {
throw new Error('useSession must be used inside <SessionProvider>')
}
return ctx.session
}
export function useOptionalSession(): Session | null {
const ctx = useContext(SessionContext)
return ctx?.session ?? null
}
export function useSessionRefetch(): () => void {
const ctx = useContext(SessionContext)
return ctx?.refetch ?? (() => {})
}