Files
shorturl-analytics/lib/auth.tsx

280 lines
7.5 KiB
TypeScript

'use client';
import React, { createContext, useContext, useEffect, useState } from 'react';
import { useRouter } from 'next/navigation';
import { Session, User } from '@supabase/supabase-js';
import supabase from './supabase';
// Define user type
export type AuthUser = User | null;
// Define auth context type
export type AuthContextType = {
user: AuthUser;
session: Session | null;
isLoading: boolean;
signIn: (email: string, password: string) => Promise<{ error?: unknown }>;
signInWithGoogle: () => Promise<{ error?: unknown }>;
signInWithGitHub: () => Promise<{ error?: unknown }>;
signUp: (email: string, password: string) => Promise<void>;
signOut: () => Promise<void>;
};
// Create auth context
const AuthContext = createContext<AuthContextType | undefined>(undefined);
// Auth provider component
export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [user, setUser] = useState<AuthUser>(null);
const [session, setSession] = useState<Session | null>(null);
const [isLoading, setIsLoading] = useState(true);
const router = useRouter();
// Initialize auth state
useEffect(() => {
const getSession = async () => {
setIsLoading(true);
try {
// Try to get session from Supabase
const { data: { session }, error } = await supabase.auth.getSession();
if (error) {
console.error('Error getting session:', error);
return;
}
// Print session info for debugging
console.log('Supabase session loaded:', session ? 'Found' : 'Not found');
if (session) {
console.log('User authenticated:', session.user.email);
if (session.expires_at) {
console.log('Session expires at:', new Date(session.expires_at * 1000).toLocaleString());
}
}
setSession(session);
setUser(session?.user || null);
} catch (error) {
console.error('Unexpected error during getSession:', error);
} finally {
setIsLoading(false);
}
};
getSession();
// Listen for auth state changes
const { data: { subscription } } = supabase.auth.onAuthStateChange((_event, session) => {
console.log('Auth state changed, event:', _event);
console.log('New session:', session ? 'Valid' : 'None');
setSession(session);
setUser(session?.user || null);
});
// Cleanup function
return () => {
subscription.unsubscribe();
};
}, []);
// Sign in function
const signIn = async (email: string, password: string) => {
setIsLoading(true);
try {
console.log('Attempting to sign in:', { email });
// Try to sign in with Supabase
const { data, error } = await supabase.auth.signInWithPassword({
email,
password,
});
if (error) {
console.error('Sign in error:', error);
return { error };
}
// Sign in successful, set session and user info
console.log('Sign in successful, user:', data.user?.email);
setSession(data.session);
setUser(data.user);
// Use hard redirect instead of router.push to ensure full page refresh
console.log('Preparing to redirect to analytics page');
// Add short delay to ensure state is updated, then redirect
setTimeout(() => {
window.location.href = '/analytics';
}, 100);
return {};
} catch (error) {
console.error('Error during sign in process:', error);
return { error };
} finally {
setIsLoading(false);
}
};
// Google sign in function
const signInWithGoogle = async () => {
setIsLoading(true);
try {
// Try to sign in with Google via Supabase
const { error } = await supabase.auth.signInWithOAuth({
provider: 'google',
options: {
redirectTo: `${window.location.origin}/auth/callback`,
queryParams: {
access_type: 'offline',
prompt: 'consent',
}
},
});
if (error) {
console.error('Google sign in error:', error);
return { error };
}
return {}; // Return empty object when successful
} catch (error) {
console.error('Error during Google sign in process:', error);
return { error };
} finally {
setIsLoading(false);
}
};
// GitHub sign in function
const signInWithGitHub = async () => {
setIsLoading(true);
try {
// Try to sign in with GitHub via Supabase
const { error } = await supabase.auth.signInWithOAuth({
provider: 'github',
options: {
redirectTo: `${window.location.origin}/auth/callback`,
},
});
if (error) {
console.error('GitHub login error:', error);
return { error };
}
return {}; // Return empty object when successful
} catch (error) {
console.error('GitHub login process error:', error);
return { error };
} finally {
setIsLoading(false);
}
};
// Sign up function
const signUp = async (email: string, password: string) => {
setIsLoading(true);
try {
// Try to sign up via Supabase
const { error } = await supabase.auth.signUp({
email,
password,
options: {
emailRedirectTo: `${window.location.origin}/auth/callback`,
}
});
if (error) {
console.error('Sign up error:', error);
throw error;
}
// After successful registration, redirect to login page with confirmation message
router.push('/login?message=Registration successful! Please check your email to verify your account before logging in.');
} catch (error) {
console.error('Error during sign up process:', error);
throw error;
} finally {
setIsLoading(false);
}
};
// Sign out function
const signOut = async () => {
setIsLoading(true);
try {
// Try to sign out via Supabase
const { error } = await supabase.auth.signOut();
if (error) {
console.error('Sign out error:', error);
throw error;
}
setSession(null);
setUser(null);
router.push('/login');
} catch (error) {
console.error('Error during sign out process:', error);
throw error;
} finally {
setIsLoading(false);
}
};
const contextValue: AuthContextType = {
user,
session,
isLoading,
signIn,
signInWithGoogle,
signInWithGitHub,
signUp,
signOut,
};
return (
<AuthContext.Provider value={contextValue}>
{children}
</AuthContext.Provider>
);
};
// Custom hook
export const useAuth = () => {
const context = useContext(AuthContext);
if (context === undefined) {
throw new Error('useAuth must be used within an AuthProvider');
}
return context;
};
// Protected route component
export const ProtectedRoute: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const { user, isLoading } = useAuth();
const router = useRouter();
useEffect(() => {
if (!isLoading && !user) {
router.push('/login');
}
}, [user, isLoading, router]);
if (isLoading) {
return (
<div className="flex items-center justify-center min-h-screen">
<div className="text-center">
<div className="animate-spin rounded-full h-16 w-16 border-t-2 border-b-2 border-blue-500 mx-auto"></div>
<p className="mt-4 text-lg text-gray-700 dark:text-gray-300">Loading...</p>
</div>
</div>
);
}
if (!user) {
return null;
}
return <>{children}</>;
};
export default AuthContext;