-
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 6 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 |
---|---|---|
@@ -1,51 +1,29 @@ | ||
'use client'; | ||
|
||
import { useState } from 'react'; | ||
import { useRouter } from 'next/navigation'; | ||
import supabase from '../../../api/supabase/createClient'; | ||
import { AuthProvider, useAuth } from '../../utils/AuthProvider'; | ||
|
||
export default function Login() { | ||
export default function LoginLayout() { | ||
return ( | ||
<AuthProvider> | ||
<Login /> | ||
</AuthProvider> | ||
); | ||
} | ||
Comment on lines
+9
to
+13
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. @rachaelch3n follow up on these comments about moving AuthProvider to |
||
|
||
function Login() { | ||
const { signIn } = useAuth(); | ||
const [email, setEmail] = useState(''); | ||
const [password, setPassword] = useState(''); | ||
const { push } = useRouter(); | ||
|
||
const handleSignUp = async () => { | ||
const { data, error } = await supabase.auth.signUp({ | ||
email, | ||
password, | ||
}); | ||
|
||
if (error) { | ||
// Check if the error is due to an already registered email | ||
if (error.message.includes('User already registered')) { | ||
throw new Error( | ||
'This email is already registered. Please try signing in instead.', | ||
); | ||
} else { | ||
throw new Error( | ||
`An error occurred trying to sign up: ${error.message}`, | ||
); | ||
const handleSignIn = async () => { | ||
try { | ||
await signIn(email, password); | ||
} catch (error) { | ||
if (error instanceof Error) { | ||
console.error(error.message); | ||
} | ||
} | ||
|
||
push('/'); | ||
|
||
return data; | ||
}; | ||
|
||
const handleSignInWithEmail = async () => { | ||
const { data, error } = await supabase.auth.signInWithPassword({ | ||
email, | ||
password, | ||
}); | ||
|
||
if (error) { | ||
throw new Error(`An error occurred trying to sign in: ${error}`); | ||
} | ||
|
||
push('/'); | ||
|
||
return data; | ||
}; | ||
|
||
return ( | ||
|
@@ -63,12 +41,10 @@ export default function Login() { | |
value={password} | ||
placeholder="Password" | ||
/> | ||
<button type="button" onClick={handleSignUp}> | ||
Sign up | ||
</button> | ||
<button type="button" onClick={handleSignInWithEmail}> | ||
<button type="button" onClick={handleSignIn}> | ||
Sign in | ||
</button> | ||
</button>{' '} | ||
{/* Sign in button */} | ||
</> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
'use client'; | ||
|
||
import { useState } from 'react'; | ||
import { AuthProvider, useAuth } from '../../utils/AuthProvider'; | ||
|
||
export default function SignUpLayout() { | ||
return ( | ||
<AuthProvider> | ||
<SignUp /> | ||
</AuthProvider> | ||
); | ||
} | ||
|
||
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 ( | ||
<> | ||
<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 */} | ||
</> | ||
); | ||
} |
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. 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 |
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> | ||
</> | ||
); | ||
} |
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 delete this! |
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]); | ||
|
||
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.
You only need to wrap the
layout.tsx
file with theAuthProvider
to give its child screens access to context data. It seems that an Auth context would need to wrap the entire app, so look into wrapping the rootlayout.tsx
file with the auth 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.
+1! See outermost
layout.tsx
in IJP's codebase