freshyo/apps/web-ui/src/lib/auth-context.tsx
2026-09-06 18:47:21 +05:30

137 lines
3.6 KiB
TypeScript

import React, { createContext, useContext, useState, useEffect, useCallback } from 'react'
import { trpc } from './trpc-client'
import { useNavigate } from '@tanstack/react-router'
import type { User as SharedUser, AuthStateCore } from '@packages/shared'
// Auth user — serialized view of the shared User (optional name, no createdAt)
type User = Pick<SharedUser, 'id' | 'email'> &
Partial<Omit<SharedUser, 'id' | 'email' | 'createdAt' | 'mobile'>> & {
mobile: string | null
profileImage?: string | null
}
interface AuthState extends AuthStateCore {
user: User | null
userDetails: any | null
}
interface AuthContextType extends AuthState {
login: (token: string, user: User) => void
loginWithToken: (token: string, user: User) => void
register: (token: string, user: User) => void
logout: () => void
updateUser: (user: User) => void
updateUserDetails: (details: any) => void
}
const AuthContext = createContext<AuthContextType | undefined>(undefined)
export function AuthProvider({ children }: { children: React.ReactNode }) {
const [state, setState] = useState<AuthState>({
user: null,
userDetails: null,
isAuthenticated: false,
isLoading: true,
token: null,
})
const navigate = useNavigate()
// On mount, check for stored token
useEffect(() => {
const storedToken = localStorage.getItem('auth_token')
const storedUserId = localStorage.getItem('user_id')
if (storedToken && storedUserId) {
setState((prev) => ({
...prev,
token: storedToken,
isAuthenticated: true,
}))
} else {
setState((prev) => ({ ...prev, isLoading: false }))
}
}, [])
// Fetch user data when token is available
const { data: selfData } = trpc.user.user.getSelfData.useQuery(undefined, {
enabled: !!state.token && state.isAuthenticated,
retry: 1,
})
useEffect(() => {
if (selfData?.data?.user) {
setState((prev) => ({
...prev,
user: selfData.data.user,
userDetails: selfData.data.user,
isLoading: false,
}))
} else if (selfData === undefined && state.token) {
// Token might be invalid
}
}, [selfData, state.token])
const login = useCallback((token: string, user: User) => {
localStorage.setItem('auth_token', token)
localStorage.setItem('user_id', String(user.id))
setState({
user,
userDetails: user,
isAuthenticated: true,
isLoading: false,
token,
})
}, [])
const loginWithToken = useCallback((token: string, user: User) => {
login(token, user)
}, [login])
const register = useCallback((token: string, user: User) => {
login(token, user)
}, [login])
const logout = useCallback(() => {
localStorage.removeItem('auth_token')
localStorage.removeItem('user_id')
setState({
user: null,
userDetails: null,
isAuthenticated: false,
isLoading: false,
token: null,
})
navigate({ to: '/login' })
}, [navigate])
const updateUser = useCallback((user: User) => {
setState((prev) => ({ ...prev, user }))
}, [])
const updateUserDetails = useCallback((details: any) => {
setState((prev) => ({ ...prev, userDetails: details }))
}, [])
return (
<AuthContext.Provider
value={{
...state,
login,
loginWithToken,
register,
logout,
updateUser,
updateUserDetails,
}}
>
{children}
</AuthContext.Provider>
)
}
export function useAuth() {
const context = useContext(AuthContext)
if (!context) {
throw new Error('useAuth must be used within AuthProvider')
}
return context
}