-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[feat] created AuthContext #20
base: main
Are you sure you want to change the base?
Changes from 8 commits
d2c8b96
3e7cacf
10cc0fc
b02515d
d76ae9e
1e13530
088ed77
0d4d431
d6f162a
594ad58
3a7501c
e578a4d
958de99
1beb0f8
993d3c8
5050894
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
'use client'; | ||
|
||
import { useState } from 'react'; | ||
import { useAuth } from '../../utils/AuthProvider'; | ||
|
||
export default function SignUp() { | ||
const { signUp } = useAuth(); | ||
const [email, setEmail] = useState(''); | ||
const [password, setPassword] = useState(''); | ||
|
||
const handleSignUp = async () => { | ||
// Define handleSignUp | ||
try { | ||
await signUp(email, password); | ||
} catch (error) { | ||
if (error instanceof Error) { | ||
console.error(error.message); | ||
} | ||
} | ||
}; | ||
|
||
return ( | ||
<div> | ||
<input | ||
name="email" | ||
onChange={e => setEmail(e.target.value)} | ||
value={email} | ||
placeholder="Email" | ||
/> | ||
<input | ||
type="password" | ||
name="password" | ||
onChange={e => setPassword(e.target.value)} | ||
value={password} | ||
placeholder="Password" | ||
/> | ||
<input | ||
type="password" | ||
name="Confirm Password" | ||
onChange={e => setPassword(e.target.value)} | ||
value={password} | ||
placeholder="Confirm Password" | ||
/> | ||
<button type="button" onClick={handleSignUp}> | ||
Sign up | ||
</button>{' '} | ||
{/* Sign up button */} | ||
</div> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
'use client'; | ||
|
||
import { AuthProvider, useAuth } from '../utils/AuthProvider'; | ||
|
||
export default function DashboardPage() { | ||
return ( | ||
<AuthProvider> | ||
<Dashboard /> | ||
</AuthProvider> | ||
); | ||
} | ||
|
||
function Dashboard() { | ||
const { authUser, isLoggedIn, signOut } = useAuth(); | ||
|
||
if (!authUser) { | ||
return <p>Loading...</p>; | ||
} | ||
|
||
return ( | ||
<> | ||
<header> | ||
<h1>Dashboard</h1> | ||
</header> | ||
|
||
<main> | ||
<p>User is currently: {isLoggedIn ? 'Logged In' : 'Logged Out'}</p> | ||
{/* Display user's email */} | ||
{authUser && <p>User name: {authUser.email}</p>}{' '} | ||
<button onClick={signOut}>Log Out</button> {/* Logout button */} | ||
</main> | ||
</> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,174 @@ | ||
'use client'; | ||
|
||
import { | ||
createContext, | ||
ReactNode, | ||
useContext, | ||
useEffect, | ||
useState, | ||
} from 'react'; | ||
import { useRouter } from 'next/navigation'; | ||
import supabase from '../../api/supabase/createClient'; | ||
|
||
interface AuthContextType { | ||
authUser: AuthUser | null; | ||
isLoggedIn: boolean; | ||
signUp: (email: string, password: string) => Promise<void>; | ||
signIn: (email: string, password: string) => Promise<void>; | ||
signOut: () => Promise<void>; | ||
loading: boolean; | ||
} | ||
|
||
interface AuthUser { | ||
id: string; | ||
email: string; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we don't need to pack these together, we can directly have 2 states in AuthProvider (i.e. after line 40):
|
||
} | ||
|
||
// Create the AuthContext | ||
const AuthContext = createContext<AuthContextType | null>(null); | ||
|
||
// Custom hook to use AuthContext | ||
export function useAuth() { | ||
const context = useContext(AuthContext); | ||
if (!context) { | ||
throw new Error('useAuth must be used within an AuthProvider'); | ||
} | ||
return context; | ||
} | ||
|
||
// AuthProvider component that wraps around your app or specific pages | ||
export function AuthProvider({ children }: { children: ReactNode }) { | ||
const [authUser, setAuthUser] = useState<AuthUser | null>(null); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we should be using tracking session as well, see https://github.com/calblueprint/immigration-justice-project/blob/main/src/utils/AuthProvider.tsx |
||
const [isLoggedIn, setIsLoggedIn] = useState(false); | ||
const [loading, setLoading] = useState(true); | ||
const { push } = useRouter(); // Get Next.js router for navigation | ||
|
||
// Sign Up function | ||
const signUp = async (email: string, password: string) => { | ||
try { | ||
const { data, error } = await supabase.auth.signUp({ email, password }); | ||
if (error) throw new Error(error.message); | ||
|
||
if (data.user) { | ||
// Map the user data to AuthUser type, ensuring `id` and `email` are defined | ||
const user: AuthUser = { | ||
id: data.user.id ?? '', // Fallback to an empty string if `id` is undefined | ||
email: data.user.email ?? '', // Fallback to an empty string if `email` is undefined | ||
}; | ||
|
||
setAuthUser(user); | ||
setIsLoggedIn(true); | ||
push('/dashboard'); // Redirect to dashboard after sign-up | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the redirecting should be moved to /signup. The functions in AuthProvider should only handle the supabase calls and updating the AuthContext. All additional logic/processing should happen within the respective page also, since /dashboard is a test page that will be deleted, we should nav to '/' instead (i.e. the home page) |
||
} | ||
} catch (error) { | ||
if (error instanceof Error) { | ||
console.error('Sign-up Error:', error.message); | ||
} | ||
} | ||
}; | ||
|
||
// Sign In function | ||
const signIn = async (email: string, password: string) => { | ||
try { | ||
const { data, error } = await supabase.auth.signInWithPassword({ | ||
email, | ||
password, | ||
}); | ||
if (error) throw new Error(error.message); | ||
|
||
// Map the user data to AuthUser type | ||
const user: AuthUser = { | ||
id: data.user.id ?? '', // Fallback to an empty string if `id` is undefined | ||
email: data.user.email ?? '', // Fallback to an empty string if `email` is undefined | ||
}; | ||
|
||
setAuthUser(user); | ||
setIsLoggedIn(true); | ||
push('/dashboard'); // Redirect to dashboard after sign-in | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the redirecting should be moved to |
||
} catch (error) { | ||
if (error instanceof Error) { | ||
console.error('Sign-in Error:', error.message); | ||
} | ||
} | ||
}; | ||
|
||
// Sign Out function | ||
const signOut = async () => { | ||
try { | ||
const { error } = await supabase.auth.signOut(); | ||
if (error) throw new Error(error.message); | ||
setAuthUser(null); | ||
setIsLoggedIn(false); | ||
push('/login'); // Redirect to login after sign-out | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. flagging that this routing shouldn't be here also to clarify, we're not using |
||
} catch (error) { | ||
if (error instanceof Error) { | ||
console.error('Sign-out Error:', error.message); | ||
} | ||
} | ||
}; | ||
|
||
// Fetch the currently logged-in user on mount and redirect to dashboard if signed in | ||
useEffect(() => { | ||
const getUser = async () => { | ||
setLoading(true); | ||
const { data } = await supabase.auth.getUser(); | ||
if (data.user && data.user.id && data.user.email) { | ||
// Map the user data to AuthUser type | ||
const user: AuthUser = { | ||
id: data.user.id, | ||
email: data.user.email, | ||
}; | ||
|
||
setAuthUser(user); | ||
setIsLoggedIn(true); | ||
} else { | ||
setAuthUser(null); | ||
setIsLoggedIn(false); | ||
} | ||
setLoading(false); | ||
}; | ||
|
||
getUser(); | ||
|
||
// Listen for auth state changes | ||
const { | ||
data: { subscription }, | ||
} = supabase.auth.onAuthStateChange((_event, session) => { | ||
if (session?.user) { | ||
const user: AuthUser = { | ||
id: session.user.id ?? '', // Fallback to empty string if `id` is undefined | ||
email: session.user.email ?? '', // Fallback to empty string if `email` is undefined | ||
}; | ||
|
||
setAuthUser(user); | ||
setIsLoggedIn(true); | ||
} else { | ||
setAuthUser(null); | ||
setIsLoggedIn(false); | ||
|
||
if (!loading) { | ||
if (window.location.pathname !== '/signup') { | ||
push('/login'); | ||
} | ||
} | ||
} | ||
}); | ||
|
||
return () => subscription?.unsubscribe(); | ||
}, [push]); | ||
Check warning on line 158 in app/utils/AuthProvider.tsx GitHub Actions / Run ESLint, Prettier, and TypeScript compiler
|
||
|
||
const value: AuthContextType = { | ||
authUser, | ||
isLoggedIn, | ||
signUp, | ||
signIn, | ||
signOut, | ||
loading, | ||
}; | ||
|
||
return ( | ||
<AuthContext.Provider value={value}> | ||
{!loading ? children : <div>Loading...</div>} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. let's remove this conditional check. we can do this
|
||
</AuthContext.Provider> | ||
); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
while this is a good test of using AuthProvider, we don't want a dashboard page yet, so let's delete this file.
You can leave the code in this file as a block comment in AuthProvider.tsx for example usage