Add user authentication with login/signup/protected routes, boss pokemon detail fields and result team tracking, moves and abilities selector components and API, run ownership and visibility controls, and various UI improvements across encounters, run list, and journal pages. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
94 lines
2.8 KiB
TypeScript
94 lines
2.8 KiB
TypeScript
import { createContext, useContext, useEffect, useState, useCallback, useMemo } from 'react'
|
|
import type { User, Session, AuthError } from '@supabase/supabase-js'
|
|
import { supabase } from '../lib/supabase'
|
|
|
|
interface AuthState {
|
|
user: User | null
|
|
session: Session | null
|
|
loading: boolean
|
|
}
|
|
|
|
interface AuthContextValue extends AuthState {
|
|
signInWithEmail: (email: string, password: string) => Promise<{ error: AuthError | null }>
|
|
signUpWithEmail: (email: string, password: string) => Promise<{ error: AuthError | null }>
|
|
signInWithGoogle: () => Promise<{ error: AuthError | null }>
|
|
signInWithDiscord: () => Promise<{ error: AuthError | null }>
|
|
signOut: () => Promise<void>
|
|
}
|
|
|
|
const AuthContext = createContext<AuthContextValue | null>(null)
|
|
|
|
export function AuthProvider({ children }: { children: React.ReactNode }) {
|
|
const [state, setState] = useState<AuthState>({
|
|
user: null,
|
|
session: null,
|
|
loading: true,
|
|
})
|
|
|
|
useEffect(() => {
|
|
supabase.auth.getSession().then(({ data: { session } }) => {
|
|
setState({ user: session?.user ?? null, session, loading: false })
|
|
})
|
|
|
|
const {
|
|
data: { subscription },
|
|
} = supabase.auth.onAuthStateChange((_event, session) => {
|
|
setState({ user: session?.user ?? null, session, loading: false })
|
|
})
|
|
|
|
return () => subscription.unsubscribe()
|
|
}, [])
|
|
|
|
const signInWithEmail = useCallback(async (email: string, password: string) => {
|
|
const { error } = await supabase.auth.signInWithPassword({ email, password })
|
|
return { error }
|
|
}, [])
|
|
|
|
const signUpWithEmail = useCallback(async (email: string, password: string) => {
|
|
const { error } = await supabase.auth.signUp({ email, password })
|
|
return { error }
|
|
}, [])
|
|
|
|
const signInWithGoogle = useCallback(async () => {
|
|
const { error } = await supabase.auth.signInWithOAuth({
|
|
provider: 'google',
|
|
options: { redirectTo: `${window.location.origin}/auth/callback` },
|
|
})
|
|
return { error }
|
|
}, [])
|
|
|
|
const signInWithDiscord = useCallback(async () => {
|
|
const { error } = await supabase.auth.signInWithOAuth({
|
|
provider: 'discord',
|
|
options: { redirectTo: `${window.location.origin}/auth/callback` },
|
|
})
|
|
return { error }
|
|
}, [])
|
|
|
|
const signOut = useCallback(async () => {
|
|
await supabase.auth.signOut()
|
|
}, [])
|
|
|
|
const value = useMemo(
|
|
() => ({
|
|
...state,
|
|
signInWithEmail,
|
|
signUpWithEmail,
|
|
signInWithGoogle,
|
|
signInWithDiscord,
|
|
signOut,
|
|
}),
|
|
[state, signInWithEmail, signUpWithEmail, signInWithGoogle, signInWithDiscord, signOut]
|
|
)
|
|
|
|
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>
|
|
}
|
|
|
|
export function useAuth() {
|
|
const context = useContext(AuthContext)
|
|
if (!context) {
|
|
throw new Error('useAuth must be used within an AuthProvider')
|
|
}
|
|
return context
|
|
}
|