'use client'
import { createContext, useContext, useEffect, useState } from 'react'
import { User, Session } from '@supabase/supabase-js'
import { supabase } from '@/lib/supabase'
import { useRouter } from 'next/navigation'
import { trackAuth } from '@/lib/analytics'
interface AuthContextType {
user: User | null
session: Session | null
loading: boolean
signingOut: boolean
completingAuth: boolean
signInWithGoogle: () => Promise<void>
signInWithGitHub: () => Promise<void>
signOut: () => Promise<void>
}
const AuthContext = createContext<AuthContextType | undefined>(undefined)
export function AuthProvider({ children }: { children: React.ReactNode }) {
const [user, setUser] = useState<User | null>(null)
const [session, setSession] = useState<Session | null>(null)
const [loading, setLoading] = useState(true)
const [signingOut, setSigningOut] = useState(false)
const [completingAuth, setCompletingAuth] = useState(false)
const router = useRouter()
useEffect(() => {
// Get initial session and handle implicit flow tokens
const initializeAuth = async () => {
try {
// First check for implicit flow tokens in sessionStorage
const tokensString = sessionStorage.getItem('supabase_auth_tokens')
if (tokensString) {
console.log('Found implicit flow tokens, setting up session...')
setCompletingAuth(true)
try {
const tokens = JSON.parse(tokensString)
if (tokens.access_token && tokens.refresh_token) {
// Set the session in Supabase
const { data, error: sessionError } = await supabase.auth.setSession({
access_token: tokens.access_token,
refresh_token: tokens.refresh_token
})
if (sessionError) {
console.error('Session setup failed:', sessionError)
// Continue to try regular session get
} else if (data.session) {
console.log('Implicit flow session established successfully for:', data.user?.email)
setSession(data.session)
setUser(data.user)
// Clean up stored tokens
sessionStorage.removeItem('supabase_auth_tokens')
setCompletingAuth(false)
setLoading(false)
return
}
}
} catch (error) {
console.error('Error processing implicit flow tokens:', error)
}
// Clean up tokens on error
sessionStorage.removeItem('supabase_auth_tokens')
setCompletingAuth(false)
}
// Get regular session if no implicit flow tokens or if they failed
const { data: { session } } = await supabase.auth.getSession()
setSession(session)
setUser(session?.user ?? null)
setLoading(false)
} catch (error) {
console.error('Error initializing auth:', error)
setLoading(false)
setCompletingAuth(false)
}
}
initializeAuth()
// Listen for auth changes
const { data: { subscription } } = supabase.auth.onAuthStateChange(
async (event, session) => {
setSession(session)
setUser(session?.user ?? null)
setLoading(false)
setCompletingAuth(false)
// Clear signing out state when auth changes
if (event === 'SIGNED_OUT') {
setTimeout(() => setSigningOut(false), 1000)
}
}
)
return () => subscription.unsubscribe()
}, [])
const signInWithGoogle = async () => {
const { error } = await supabase.auth.signInWithOAuth({
provider: 'google',
options: {
redirectTo: `${window.location.origin}/auth/callback`
}
})
if (error) {
console.error('Error signing in with Google:', error)
}
}
const signInWithGitHub = async () => {
const { error } = await supabase.auth.signInWithOAuth({
provider: 'github',
options: {
redirectTo: `${window.location.origin}/auth/callback`
}
})
if (error) {
console.error('Error signing in with GitHub:', error)
}
}
const signOut = async () => {
setSigningOut(true)
// Clear storage
if (typeof window !== 'undefined') {
sessionStorage.removeItem('supabase_auth_tokens')
const supabaseKey = `sb-${process.env.NEXT_PUBLIC_SUPABASE_URL?.split('//')[1]?.split('.')[0]}-auth-token`
localStorage.removeItem(supabaseKey)
}
trackAuth.logout()
try {
await supabase.auth.signOut()
} catch (error) {
console.error('Error signing out:', error)
}
setUser(null)
setSession(null)
router.push('/')
}
const value = {
user,
session,
loading,
signingOut,
completingAuth,
signInWithGoogle,
signInWithGitHub,
signOut
}
return (
<AuthContext.Provider value={value}>
{children}
</AuthContext.Provider>
)
}
export function useAuth() {
const context = useContext(AuthContext)
if (context === undefined) {
throw new Error('useAuth must be used within an AuthProvider')
}
return context
}