bookwiz.io / components / AuthProvider.tsx
AuthProvider.tsx
Raw
'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
}