chore: update UI for signup and login

- add back button for better UX
This commit is contained in:
Hafid Nur
2025-11-25 18:57:03 +07:00
parent f40e0e35b9
commit 7642fef3e6
5 changed files with 174 additions and 91 deletions
+46 -34
View File
@@ -13,62 +13,72 @@ const CallbackPage: FC = (): ReactElement => {
useEffect(() => {
const handleCallback = async () => {
if (hasRunRef.current) {
console.log('[Callback] Already processed, skipping...');
// console.log('[Callback] Already processed, skipping...');
return;
}
hasRunRef.current = true;
try {
console.log('[Callback] Processing OAuth callback...');
console.log('[Callback] Current URL:', globalThis.location.href);
// console.log('[Callback] Processing OAuth callback...');
// console.log('[Callback] Current URL:', globalThis.location.href);
// Supabase client is configured with detectSessionInUrl: true
// This means Supabase automatically detects and processes OAuth tokens from the URL hash
// We just need to wait a moment for it to complete, then check for the session
console.log('[Callback] Waiting for Supabase to process OAuth callback...');
await new Promise(resolve => setTimeout(resolve, 1000));
// console.log('[Callback] Waiting for Supabase to process OAuth callback...');
await new Promise((resolve) => setTimeout(resolve, 1000));
// Get the session that Supabase automatically created from the URL hash
const { data: { session: sessionData }, error: sessionError } = await supabase.auth.getSession();
const {
data: { session: sessionData },
error: sessionError,
} = await supabase.auth.getSession();
if (sessionError) {
console.error('[Callback] Session error:', sessionError);
// console.error('[Callback] Session error:', sessionError);
throw new Error(sessionError.message || 'Failed to get session');
}
if (!sessionData || !sessionData.user) {
throw new Error('No session found after OAuth callback. Please try logging in again.');
throw new Error(
'No session found after OAuth callback. Please try logging in again.'
);
}
console.log('[Callback] Supabase session established:', {
userId: sessionData.user.id,
email: sessionData.user.email,
});
// console.log('[Callback] Supabase session established:', {
// userId: sessionData.user.id,
// email: sessionData.user.email,
// });
// Create/update user in the users table (for foreign key constraints)
console.log('[Callback] Creating/updating user record...');
// console.log('[Callback] Creating/updating user record...');
const { data: userData, error: upsertError } = await supabase
.from('users')
.upsert({
id: sessionData.user.id,
email: sessionData.user.email || '',
fullname: sessionData.user.user_metadata?.full_name ||
sessionData.user.user_metadata?.name ||
sessionData.user.email?.split('@')[0] || '',
avatar: sessionData.user.user_metadata?.avatar_url || '',
is_active: true,
updated_at: new Date().toISOString(),
}, {
onConflict: 'id',
})
.upsert(
{
id: sessionData.user.id,
email: sessionData.user.email || '',
fullname:
sessionData.user.user_metadata?.full_name ||
sessionData.user.user_metadata?.name ||
sessionData.user.email?.split('@')[0] ||
'',
avatar: sessionData.user.user_metadata?.avatar_url || '',
is_active: true,
updated_at: new Date().toISOString(),
},
{
onConflict: 'id',
}
)
.select()
.single();
if (upsertError) {
console.warn('[Callback] Failed to create user record:', upsertError);
// console.warn('[Callback] Failed to create user record:', upsertError);
// Don't throw - continue with login even if user record creation fails
} else {
console.log('[Callback] User record created/updated successfully');
// console.log('[Callback] User record created/updated successfully');
}
// Store user-friendly data in Zustand for UI purposes
@@ -77,9 +87,11 @@ const CallbackPage: FC = (): ReactElement => {
const userRecord = userData || {
id: sessionData.user.id,
email: sessionData.user.email || '',
fullname: sessionData.user.user_metadata?.full_name ||
sessionData.user.user_metadata?.name ||
sessionData.user.email?.split('@')[0] || '',
fullname:
sessionData.user.user_metadata?.full_name ||
sessionData.user.user_metadata?.name ||
sessionData.user.email?.split('@')[0] ||
'',
avatar: sessionData.user.user_metadata?.avatar_url || '',
phone_number: '',
birthdate: '',
@@ -114,21 +126,21 @@ const CallbackPage: FC = (): ReactElement => {
},
});
console.log('[Callback] Session stored successfully');
// console.log('[Callback] Session stored successfully');
toast.success('Login successful!');
setIsProcessing(false);
// Check if user has completed onboarding (has location)
// Use globalThis.location.replace for hard redirect to prevent history issues
if (userRecord.location) {
console.log('[Callback] User has completed onboarding, redirecting to dashboard...');
// console.log('[Callback] User has completed onboarding, redirecting to dashboard...');
globalThis.location.replace('/dashboard');
} else {
console.log('[Callback] User needs onboarding, redirecting...');
// console.log('[Callback] User needs onboarding, redirecting...');
globalThis.location.replace('/onboarding/user');
}
} catch (err) {
console.error('[Callback] Error:', err);
// console.error('[Callback] Error:', err);
setError((err as Error).message);
setIsProcessing(false);
toast.error('An error occurred during login');
@@ -1,12 +1,14 @@
import { useState } from 'react';
import { supabase } from '@imphnen-frontend-service/service';
import { Link } from 'react-router';
import { Link, useNavigate } from 'react-router';
import { toast } from 'sonner';
import { Icon } from '@iconify/react';
export default function ForgotPasswordPage() {
const [email, setEmail] = useState('');
const [isLoading, setIsLoading] = useState(false);
const [emailSent, setEmailSent] = useState(false);
const navigate = useNavigate();
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
@@ -30,7 +32,7 @@ export default function ForgotPasswordPage() {
setEmailSent(true);
toast.success('Password reset email sent! Check your inbox.');
} catch (err) {
console.error('Failed to send reset email:', err);
// console.error('Failed to send reset email:', err);
toast.error((err as Error).message || 'Failed to send reset email');
} finally {
setIsLoading(false);
@@ -55,7 +57,8 @@ export default function ForgotPasswordPage() {
<div className="space-y-4">
<p className="text-sm text-gray-600">
Click the link in the email to reset your password. The link will expire in 1 hour.
Click the link in the email to reset your password. The link will
expire in 1 hour.
</p>
<Link to="/auth/login">
@@ -90,7 +93,10 @@ export default function ForgotPasswordPage() {
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label htmlFor="email" className="block text-sm font-medium text-gray-700 mb-1">
<label
htmlFor="email"
className="block text-sm font-medium text-gray-700 mb-1"
>
Email Address
</label>
<input
@@ -114,9 +120,13 @@ export default function ForgotPasswordPage() {
</button>
</form>
<div className="mt-6 text-center">
<Link to="/auth/login" className="text-primary-600 hover:text-primary-700 font-semibold">
Back to Login
<div className="mt-8">
<Link
to="/auth/login"
className="text-primary-500 hover:text-primary-600 flex items-center"
>
<Icon icon="ic:baseline-chevron-left" width="24" height="24" />
<span> Back to Login</span>
</Link>
</div>
</div>
+44 -17
View File
@@ -1,11 +1,17 @@
import { useState } from 'react';
import { useGitHubAuth, useEmailAuth, supabase, useAuthStore } from '@imphnen-frontend-service/service';
import {
useGitHubAuth,
useEmailAuth,
supabase,
useAuthStore,
} from '@imphnen-frontend-service/service';
import { GithubOutlined } from '@ant-design/icons';
import { useNavigate, Link } from 'react-router';
import { toast } from 'sonner';
import { Icon } from '@iconify/react';
export default function LoginPage() {
console.log('[LoginPage] Rendering...');
// console.log('[LoginPage] Rendering...');
const navigate = useNavigate();
const { setSession } = useAuthStore();
@@ -28,10 +34,10 @@ export default function LoginPage() {
try {
setIsEmailLoading(true);
console.log('[Login] Attempting email login...');
// console.log('[Login] Attempting email login...');
const result = await signInWithEmail(email, password);
console.log('[Login] Email login successful:', result);
// console.log('[Login] Email login successful:', result);
// Get user data from database
const { data: userData } = await supabase
@@ -49,7 +55,8 @@ export default function LoginPage() {
user: {
id: result.user.id,
email: result.user.email || '',
fullname: userData?.fullname || result.user.user_metadata?.full_name || '',
fullname:
userData?.fullname || result.user.user_metadata?.full_name || '',
phone_number: userData?.phone_number || '',
avatar: userData?.avatar || '',
birthdate: userData?.birthdate || '',
@@ -86,22 +93,22 @@ export default function LoginPage() {
const handleGithubLogin = async () => {
try {
setIsGithubLoading(true);
console.log('[Login] Initiating GitHub OAuth...');
// console.log('[Login] Initiating GitHub OAuth...');
const result = await signInWithGitHub();
console.log('[Login] OAuth result:', result);
// console.log('[Login] OAuth result:', result);
// Check if we got a redirect URL
if (result?.url) {
console.log('[Login] Redirecting to GitHub OAuth:', result.url);
// console.log('[Login] Redirecting to GitHub OAuth:', result.url);
// Manually redirect immediately
globalThis.location.href = result.url;
} else {
console.error('[Login] No OAuth URL returned');
// console.error('[Login] No OAuth URL returned');
setIsGithubLoading(false);
}
} catch (error) {
console.error('[Login] GitHub login failed:', error);
// console.error('[Login] GitHub login failed');
setIsGithubLoading(false);
}
};
@@ -109,11 +116,19 @@ export default function LoginPage() {
return (
<div className="flex justify-center items-center min-h-screen bg-gray-50 p-4">
<div className="bg-white w-full max-w-md p-8 rounded-2xl shadow-lg border border-gray-200">
<button
onClick={() => navigate('/')}
className="cursor-pointer text-primary-500 hover:text-primary-600 text-base font-sans flex items-center mb-6"
>
<Icon icon="ic:baseline-chevron-left" width="24" height="24" />
Back to Homepage
</button>
<div className="text-center mb-8">
<h2 className="text-3xl font-bold text-gray-900 mb-2">
Welcome Back
</h2>
<p className="text-gray-600">
<p className="text-gray-600 font-sans">
Sign in to join or create your hackathon team
</p>
</div>
@@ -126,7 +141,10 @@ export default function LoginPage() {
<form onSubmit={handleEmailLogin} className="space-y-4">
<div>
<label htmlFor="email" className="block text-sm font-medium text-gray-700 mb-1">
<label
htmlFor="email"
className="block text-sm font-medium text-gray-700 mb-1"
>
Email
</label>
<input
@@ -143,10 +161,16 @@ export default function LoginPage() {
<div>
<div className="flex items-center justify-between mb-1">
<label htmlFor="password" className="block text-sm font-medium text-gray-700">
<label
htmlFor="password"
className="block text-sm font-medium text-gray-700"
>
Password
</label>
<Link to="/auth/forgot-password" className="text-sm text-primary-600 hover:text-primary-700">
<Link
to="/auth/forgot-password"
className="text-sm text-primary-600 hover:text-primary-700"
>
Forgot password?
</Link>
</div>
@@ -165,7 +189,7 @@ export default function LoginPage() {
<button
type="submit"
disabled={isEmailLoading}
className="w-full py-3 bg-primary-600 text-white rounded-lg font-semibold hover:bg-primary-700 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2 disabled:bg-gray-400 disabled:cursor-not-allowed transition-colors"
className="w-full py-3 bg-primary-600 text-white rounded-lg font-semibold hover:bg-primary-700 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2 disabled:bg-gray-400 disabled:cursor-not-allowed transition-colors cursor-pointer"
>
{isEmailLoading ? 'Signing in...' : 'Sign in with Email'}
</button>
@@ -181,7 +205,7 @@ export default function LoginPage() {
onClick={handleGithubLogin}
disabled={isGithubLoading}
type="button"
className="w-full py-3 flex items-center justify-center gap-2 bg-gray-100 border border-gray-300 rounded-lg font-semibold hover:bg-gray-200 focus:outline-none focus:ring-2 focus:ring-gray-400 focus:ring-offset-2 disabled:bg-gray-100 disabled:cursor-not-allowed transition-colors"
className="w-full py-3 flex items-center justify-center gap-2 bg-gray-100 border border-gray-300 rounded-lg font-semibold hover:bg-gray-200 focus:outline-none focus:ring-2 focus:ring-gray-400 focus:ring-offset-2 disabled:bg-gray-100 disabled:cursor-not-allowed transition-colors cursor-pointer"
>
<GithubOutlined className="text-xl" />
<span>
@@ -192,7 +216,10 @@ export default function LoginPage() {
<div className="mt-6 text-center">
<p className="text-gray-600 text-sm">
Don't have an account?{' '}
<a href="/auth/signup" className="text-primary-600 hover:text-primary-700 font-semibold">
<a
href="/auth/signup"
className="text-primary-600 hover:text-primary-700 font-semibold"
>
Sign up
</a>
</p>
@@ -39,7 +39,7 @@ export default function ResetPasswordPage() {
setIsLoading(true);
const { error } = await supabase.auth.updateUser({
password: password
password: password,
});
if (error) {
@@ -52,7 +52,7 @@ export default function ResetPasswordPage() {
await supabase.auth.signOut();
navigate('/auth/login');
} catch (err) {
console.error('Failed to reset password:', err);
// console.error('Failed to reset password:', err);
toast.error((err as Error).message || 'Failed to reset password');
} finally {
setIsLoading(false);
@@ -77,14 +77,15 @@ export default function ResetPasswordPage() {
<h2 className="text-3xl font-bold text-gray-900 mb-2">
Set New Password
</h2>
<p className="text-gray-600">
Enter your new password below
</p>
<p className="text-gray-600">Enter your new password below</p>
</div>
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label htmlFor="password" className="block text-sm font-medium text-gray-700 mb-1">
<label
htmlFor="password"
className="block text-sm font-medium text-gray-700 mb-1"
>
New Password
</label>
<input
@@ -101,7 +102,10 @@ export default function ResetPasswordPage() {
</div>
<div>
<label htmlFor="confirmPassword" className="block text-sm font-medium text-gray-700 mb-1">
<label
htmlFor="confirmPassword"
className="block text-sm font-medium text-gray-700 mb-1"
>
Confirm New Password
</label>
<input
+56 -26
View File
@@ -1,8 +1,14 @@
import { useState } from 'react';
import { useGitHubAuth, useEmailAuth, supabase, useAuthStore } from '@imphnen-frontend-service/service';
import {
useGitHubAuth,
useEmailAuth,
supabase,
useAuthStore,
} from '@imphnen-frontend-service/service';
import { GithubOutlined } from '@ant-design/icons';
import { useNavigate } from 'react-router';
import { toast } from 'sonner';
import { Icon } from '@iconify/react';
export default function SignupPage() {
const navigate = useNavigate();
@@ -38,30 +44,31 @@ export default function SignupPage() {
try {
setIsEmailLoading(true);
console.log('[Signup] Attempting email signup...');
// console.log('[Signup] Attempting email signup...');
const result = await signUpWithEmail(email, password, fullname);
console.log('[Signup] Email signup successful:', result);
// console.log('[Signup] Email signup successful:', result);
if (!result.user) {
throw new Error('Signup failed - no user returned');
}
// Create user record in database
const { error: upsertError } = await supabase
.from('users')
.upsert({
const { error: upsertError } = await supabase.from('users').upsert(
{
id: result.user.id,
email: result.user.email || '',
fullname: fullname,
is_active: true,
updated_at: new Date().toISOString(),
}, {
},
{
onConflict: 'id',
});
}
);
if (upsertError) {
console.warn('[Signup] Failed to create user record:', upsertError);
console.warn('[Signup] Failed to create user record');
}
// If session is available (email confirmation disabled), store it
@@ -94,13 +101,15 @@ export default function SignupPage() {
navigate('/onboarding/user');
} else {
// Email confirmation is enabled
toast.success('Account created! Please check your email to verify your account.');
toast.success(
'Account created! Please check your email to verify your account.'
);
setTimeout(() => {
navigate('/auth/login');
}, 2000);
}
} catch (err) {
console.error('[Signup] Email signup failed:', err);
// console.error('[Signup] Email signup failed:', err);
setError((err as Error).message || 'Signup failed');
setIsEmailLoading(false);
}
@@ -109,20 +118,20 @@ export default function SignupPage() {
const handleGithubLogin = async () => {
try {
setIsGithubLoading(true);
console.log('[Signup] Initiating GitHub OAuth...');
// console.log('[Signup] Initiating GitHub OAuth...');
const result = await signInWithGitHub();
console.log('[Signup] OAuth result:', result);
// console.log('[Signup] OAuth result:', result);
if (result?.url) {
console.log('[Signup] Redirecting to GitHub OAuth:', result.url);
// console.log('[Signup] Redirecting to GitHub OAuth:', result.url);
globalThis.location.href = result.url;
} else {
console.error('[Signup] No OAuth URL returned');
// console.error('[Signup] No OAuth URL returned');
setIsGithubLoading(false);
}
} catch (error) {
console.error('[Signup] GitHub login failed:', error);
// console.error('[Signup] GitHub login failed:', error);
setIsGithubLoading(false);
}
};
@@ -130,13 +139,19 @@ export default function SignupPage() {
return (
<div className="flex justify-center items-center min-h-screen bg-gray-50 p-4">
<div className="bg-white w-full max-w-md p-8 rounded-2xl shadow-lg border border-gray-200">
<button
onClick={() => navigate('/')}
className="cursor-pointer text-primary-500 hover:text-primary-600 text-base font-sans flex items-center mb-6"
>
<Icon icon="ic:baseline-chevron-left" width="24" height="24" />
Back to Homepage
</button>
<div className="text-center mb-8">
<h2 className="text-3xl font-bold text-gray-900 mb-2">
Create Account
</h2>
<p className="text-gray-600">
Join the hackathon community
</p>
<p className="text-gray-600">Join the hackathon community</p>
</div>
{error && (
@@ -147,7 +162,10 @@ export default function SignupPage() {
<form onSubmit={handleEmailSignup} className="space-y-4">
<div>
<label htmlFor="fullname" className="block text-sm font-medium text-gray-700 mb-1">
<label
htmlFor="fullname"
className="block text-sm font-medium text-gray-700 mb-1"
>
Full Name
</label>
<input
@@ -163,7 +181,10 @@ export default function SignupPage() {
</div>
<div>
<label htmlFor="email" className="block text-sm font-medium text-gray-700 mb-1">
<label
htmlFor="email"
className="block text-sm font-medium text-gray-700 mb-1"
>
Email
</label>
<input
@@ -179,7 +200,10 @@ export default function SignupPage() {
</div>
<div>
<label htmlFor="password" className="block text-sm font-medium text-gray-700 mb-1">
<label
htmlFor="password"
className="block text-sm font-medium text-gray-700 mb-1"
>
Password
</label>
<input
@@ -195,7 +219,10 @@ export default function SignupPage() {
</div>
<div>
<label htmlFor="confirmPassword" className="block text-sm font-medium text-gray-700 mb-1">
<label
htmlFor="confirmPassword"
className="block text-sm font-medium text-gray-700 mb-1"
>
Confirm Password
</label>
<input
@@ -213,7 +240,7 @@ export default function SignupPage() {
<button
type="submit"
disabled={isEmailLoading}
className="w-full py-3 bg-primary-600 text-white rounded-lg font-semibold hover:bg-primary-700 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2 disabled:bg-gray-400 disabled:cursor-not-allowed transition-colors"
className="w-full py-3 bg-primary-600 text-white rounded-lg font-semibold hover:bg-primary-700 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2 disabled:bg-gray-400 disabled:cursor-not-allowed transition-colors cursor-pointer"
>
{isEmailLoading ? 'Creating account...' : 'Create Account'}
</button>
@@ -229,7 +256,7 @@ export default function SignupPage() {
onClick={handleGithubLogin}
disabled={isGithubLoading}
type="button"
className="w-full py-3 flex items-center justify-center gap-2 bg-gray-100 border border-gray-300 rounded-lg font-semibold hover:bg-gray-200 focus:outline-none focus:ring-2 focus:ring-gray-400 focus:ring-offset-2 disabled:bg-gray-100 disabled:cursor-not-allowed transition-colors"
className="w-full py-3 flex items-center justify-center gap-2 bg-gray-100 border border-gray-300 rounded-lg font-semibold hover:bg-gray-200 focus:outline-none focus:ring-2 focus:ring-gray-400 focus:ring-offset-2 disabled:bg-gray-100 disabled:cursor-not-allowed transition-colors cursor-pointer"
>
<GithubOutlined className="text-xl" />
<span>
@@ -240,7 +267,10 @@ export default function SignupPage() {
<div className="mt-6 text-center">
<p className="text-gray-600 text-sm">
Already have an account?{' '}
<a href="/auth/login" className="text-primary-600 hover:text-primary-700 font-semibold">
<a
href="/auth/login"
className="text-primary-600 hover:text-primary-700 font-semibold"
>
Sign in
</a>
</p>