Skip to content
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

Open
wants to merge 16 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 22 additions & 43 deletions app/(auth)/login/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,50 +2,32 @@

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 +7 to +13

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 the AuthProvider 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 root layout.tsx file with the auth provider.

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

Comment on lines +9 to +13
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rachaelch3n follow up on these comments about moving AuthProvider to app/layout.tsx, and out of this page's layout.


function Login() {
const { signIn } = useAuth(); // Use `signIn` function from AuthProvider
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 router = useRouter();

const handleLogin = async () => {
// Define handleLogin
try {
await signIn(email, password);
router.push('/'); // Redirect to the home page on success
} catch (error) {
if (error instanceof Error) {
console.error('Login 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 (
Expand All @@ -63,10 +45,7 @@ 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={handleLogin}>
Sign in
</button>
</>
Expand Down
61 changes: 61 additions & 0 deletions app/(auth)/signup/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
'use client';

import { useState } from 'react';
import { useRouter } from 'next/navigation';
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 router = useRouter();

const handleSignUp = async () => {
// Define handleSignUp
try {
await signUp(email, password);
router.push('/');
} 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 */}
</>
);
}
36 changes: 36 additions & 0 deletions app/dashboard/page.tsx
Copy link
Collaborator

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

Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
'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>
{authUser && <p>User name: {authUser.email}</p>}{' '}
<button onClick={signOut}>Log Out</button>
</main>
</>
);
}

*/
5 changes: 4 additions & 1 deletion app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Metadata } from 'next';
import { Inter } from 'next/font/google';
import StyledComponentsRegistry from '@/lib/registry';
import { AuthProvider } from './utils/AuthProvider';

// font definitions
const sans = Inter({
Expand All @@ -22,7 +23,9 @@ export default function RootLayout({
return (
<html lang="en">
<body className={sans.className}>
<StyledComponentsRegistry>{children}</StyledComponentsRegistry>
<AuthProvider>
<StyledComponentsRegistry>{children}</StyledComponentsRegistry>
</AuthProvider>
</body>
</html>
);
Expand Down
Empty file added app/utils.ts
Empty file.
163 changes: 163 additions & 0 deletions app/utils/AuthProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
'use client';

import {
createContext,
ReactNode,
useContext,
useEffect,
useState,
} from 'react';
import { useRouter } from 'next/navigation';
import { Session } from '@supabase/supabase-js';
import supabase from '../../api/supabase/createClient';

interface AuthContextType {
userId: string | null;
email: string | null;
session: Session | null;
isLoggedIn: boolean;
signUp: (email: string, password: string) => Promise<void>;
signIn: (email: string, password: string) => Promise<void>;
signOut: () => Promise<void>;
loading: boolean;
}

// 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 [session, setSession] = useState<Session | null>(null);
const [userId, setUserId] = useState<string | null>(null);
const [email, setEmail] = useState<string | null>(null);
const [isLoggedIn, setIsLoggedIn] = useState(false);
const [loading, setLoading] = useState(true);
const { push } = useRouter();

// 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) {
setUserId(data.user.id ?? null);
setEmail(data.user.email ?? null);
setIsLoggedIn(true);
}
} 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);

setUserId(data.user.id ?? null);
setEmail(data.user.email ?? null);
setIsLoggedIn(true);
} 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);
setUserId(null);
setEmail(null);
setIsLoggedIn(false);
setSession(null);
// push('/login'); // Redirect to login after sign-out
} 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, error } = await supabase.auth.getSession();
if (error) {
console.error('Session Error:', error.message);
} else if (data.session) {
setSession(data.session);
const { user } = data.session;
if (user) {
setUserId(user.id ?? null);
setEmail(user.email ?? null);
setIsLoggedIn(true);
}
} else {
setUserId(null);
setEmail(null);
setIsLoggedIn(false);
}
setLoading(false);
};
getUser();

// Listen for auth state changes
const {
data: { subscription },
} = supabase.auth.onAuthStateChange((_event, session) => {
if (session) {
setSession(session);
const { user } = session;
if (user) {
setUserId(user.id ?? null);
setEmail(user.email ?? null);
setIsLoggedIn(true);
}
} else {
setUserId(null);
setEmail(null);
setIsLoggedIn(false);
setSession(null);

// if (!loading && window.location.pathname !== '/signup') {
// push('/login');
// }
}
});

return () => subscription?.unsubscribe();
}, [push, loading]);

const value: AuthContextType = {
userId,
email,
session,
isLoggedIn,
signUp,
signIn,
signOut,
loading,
};

return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@
"pre-commit": "(pnpm run tsc || true) && (pnpm run lint || true) && pnpm run prettier"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cc @ccatherinetan, maybe we want to remove this change & also the package-lock.json change? (do we want/need this change)?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pragyakallanagoudar what change are you referring to?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this comment is fully on the wrong line lol but i'm referring to the one on line 17/20 of this file

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes sg! we'll delete the @next/swc-darwin-arm64 line

},
"dependencies": {
"@next/swc-darwin-arm64": "^15.0.0",
"@supabase/supabase-js": "^2.45.4",
"dotenv": "^16.4.5",
"next": "^14.2.10",
"next": "^14.2.10",
"react": "^18",
"react-dom": "^18",
"react-multi-select-component": "^4.3.4",
Expand All @@ -33,7 +35,8 @@
"eslint-config-prettier": "^9.1.0",
"husky": "^9.1.5",
"prettier": "^3.3.3",
"typescript": "^5",
"typescript": "5.4",
"typescript": "5.4",
"yarnhook": "^0.6.2"
}
}
Loading
Loading