feat: add auth system, boss pokemon details, moves/abilities API, and run ownership
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>
This commit is contained in:
93
frontend/src/contexts/AuthContext.tsx
Normal file
93
frontend/src/contexts/AuthContext.tsx
Normal file
@@ -0,0 +1,93 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user