137 lines
3.6 KiB
TypeScript
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
|
|
}
|