fix: auth error

This commit is contained in:
Maulana Sodiqin
2025-11-29 11:24:23 +07:00
parent 8280ba2a64
commit 66178ccc18
4 changed files with 115 additions and 56 deletions
+20 -10
View File
@@ -17,6 +17,7 @@ export default function LoginPage() {
const [email, setEmail] = useState(''); const [email, setEmail] = useState('');
const [password, setPassword] = useState(''); const [password, setPassword] = useState('');
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const [showPassword, setShowPassword] = useState(false);
const handleEmailLogin = async (e: React.FormEvent) => { const handleEmailLogin = async (e: React.FormEvent) => {
e.preventDefault(); e.preventDefault();
@@ -128,16 +129,25 @@ export default function LoginPage() {
Forgot password? Forgot password?
</Link> </Link>
</div> </div>
<input <div className="relative">
id="password" <input
type="password" id="password"
value={password} type={showPassword ? 'text' : 'password'}
onChange={(e) => setPassword(e.target.value)} value={password}
placeholder="••••••••" onChange={(e) => setPassword(e.target.value)}
disabled={loginMutation.isPending} placeholder="••••••••"
className="w-full px-4 py-2.5 border border-gray-300 dark:border-gray-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent bg-white dark:bg-gray-800 text-gray-900 dark:text-white placeholder:text-gray-400 dark:placeholder:text-gray-500 disabled:bg-gray-100 dark:disabled:bg-gray-700 disabled:cursor-not-allowed" disabled={loginMutation.isPending}
required className="w-full px-4 py-2.5 pr-12 border border-gray-300 dark:border-gray-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent bg-white dark:bg-gray-800 text-gray-900 dark:text-white placeholder:text-gray-400 dark:placeholder:text-gray-500 disabled:bg-gray-100 dark:disabled:bg-gray-700 disabled:cursor-not-allowed"
/> required
/>
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300"
>
<Icon icon={showPassword ? 'mdi:eye-off' : 'mdi:eye'} className="text-xl" />
</button>
</div>
</div> </div>
<button <button
@@ -2,6 +2,7 @@ import { useState, useEffect } from 'react';
import { useResetPassword, useAuthStore } from '@imphnen-frontend-service/service'; import { useResetPassword, useAuthStore } from '@imphnen-frontend-service/service';
import { useNavigate } from 'react-router'; import { useNavigate } from 'react-router';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { Icon } from '@iconify/react';
export default function ResetPasswordPage() { export default function ResetPasswordPage() {
const navigate = useNavigate(); const navigate = useNavigate();
@@ -10,11 +11,15 @@ export default function ResetPasswordPage() {
const [password, setPassword] = useState(''); const [password, setPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState(''); const [confirmPassword, setConfirmPassword] = useState('');
const [accessToken, setAccessToken] = useState<string | null>(null); const [accessToken, setAccessToken] = useState<string | null>(null);
const [showPassword, setShowPassword] = useState(false);
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
useEffect(() => { useEffect(() => {
// Get the access_token from URL hash (Supabase sends it as hash fragment) // Get the access_token from URL hash (Supabase sends it as hash fragment)
// or from query params (when redirected from callback page)
const hashParams = new URLSearchParams(globalThis.location.hash.substring(1)); const hashParams = new URLSearchParams(globalThis.location.hash.substring(1));
const token = hashParams.get('access_token'); const queryParams = new URLSearchParams(globalThis.location.search);
const token = hashParams.get('access_token') || queryParams.get('access_token');
if (token) { if (token) {
setAccessToken(token); setAccessToken(token);
@@ -91,17 +96,26 @@ export default function ResetPasswordPage() {
> >
New Password New Password
</label> </label>
<input <div className="relative">
id="password" <input
type="password" id="password"
value={password} type={showPassword ? 'text' : 'password'}
onChange={(e) => setPassword(e.target.value)} value={password}
placeholder="••••••••" onChange={(e) => setPassword(e.target.value)}
disabled={resetPasswordMutation.isPending} placeholder="••••••••"
className="w-full px-4 py-2.5 border border-gray-300 dark:border-gray-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent disabled:bg-gray-100 disabled:cursor-not-allowed bg-white dark:bg-gray-800 text-gray-900 dark:text-white" disabled={resetPasswordMutation.isPending}
required className="w-full px-4 py-2.5 pr-12 border border-gray-300 dark:border-gray-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent disabled:bg-gray-100 disabled:cursor-not-allowed bg-white dark:bg-gray-800 text-gray-900 dark:text-white"
minLength={6} required
/> minLength={6}
/>
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300"
>
<Icon icon={showPassword ? 'mdi:eye-off' : 'mdi:eye'} className="text-xl" />
</button>
</div>
</div> </div>
<div> <div>
@@ -111,17 +125,26 @@ export default function ResetPasswordPage() {
> >
Confirm New Password Confirm New Password
</label> </label>
<input <div className="relative">
id="confirmPassword" <input
type="password" id="confirmPassword"
value={confirmPassword} type={showConfirmPassword ? 'text' : 'password'}
onChange={(e) => setConfirmPassword(e.target.value)} value={confirmPassword}
placeholder="••••••••" onChange={(e) => setConfirmPassword(e.target.value)}
disabled={resetPasswordMutation.isPending} placeholder="••••••••"
className="w-full px-4 py-2.5 border border-gray-300 dark:border-gray-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent disabled:bg-gray-100 disabled:cursor-not-allowed bg-white dark:bg-gray-800 text-gray-900 dark:text-white" disabled={resetPasswordMutation.isPending}
required className="w-full px-4 py-2.5 pr-12 border border-gray-300 dark:border-gray-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent disabled:bg-gray-100 disabled:cursor-not-allowed bg-white dark:bg-gray-800 text-gray-900 dark:text-white"
minLength={6} required
/> minLength={6}
/>
<button
type="button"
onClick={() => setShowConfirmPassword(!showConfirmPassword)}
className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300"
>
<Icon icon={showConfirmPassword ? 'mdi:eye-off' : 'mdi:eye'} className="text-xl" />
</button>
</div>
</div> </div>
<button <button
+40 -20
View File
@@ -40,6 +40,8 @@ export default function SignupPage() {
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const [registrationSuccess, setRegistrationSuccess] = useState(false); const [registrationSuccess, setRegistrationSuccess] = useState(false);
const [registeredEmail, setRegisteredEmail] = useState(''); const [registeredEmail, setRegisteredEmail] = useState('');
const [showPassword, setShowPassword] = useState(false);
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
const { const {
register, register,
@@ -229,16 +231,25 @@ export default function SignupPage() {
> >
Password Password
</label> </label>
<input <div className="relative">
id="password" <input
type="password" id="password"
{...register('password')} type={showPassword ? 'text' : 'password'}
placeholder="••••••••" {...register('password')}
disabled={signupMutation.isPending} placeholder="••••••••"
className={`${inputBaseClass} ${ disabled={signupMutation.isPending}
errors.password ? inputErrorClass : inputNormalClass className={`${inputBaseClass} pr-12 ${
}`} errors.password ? inputErrorClass : inputNormalClass
/> }`}
/>
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300"
>
<Icon icon={showPassword ? 'mdi:eye-off' : 'mdi:eye'} className="text-xl" />
</button>
</div>
{errors.password && ( {errors.password && (
<p className="mt-1 text-sm text-red-500 dark:text-red-400"> <p className="mt-1 text-sm text-red-500 dark:text-red-400">
{errors.password.message} {errors.password.message}
@@ -253,16 +264,25 @@ export default function SignupPage() {
> >
Confirm Password Confirm Password
</label> </label>
<input <div className="relative">
id="confirmPassword" <input
type="password" id="confirmPassword"
{...register('confirmPassword')} type={showConfirmPassword ? 'text' : 'password'}
placeholder="••••••••" {...register('confirmPassword')}
disabled={signupMutation.isPending} placeholder="••••••••"
className={`${inputBaseClass} ${ disabled={signupMutation.isPending}
errors.confirmPassword ? inputErrorClass : inputNormalClass className={`${inputBaseClass} pr-12 ${
}`} errors.confirmPassword ? inputErrorClass : inputNormalClass
/> }`}
/>
<button
type="button"
onClick={() => setShowConfirmPassword(!showConfirmPassword)}
className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300"
>
<Icon icon={showConfirmPassword ? 'mdi:eye-off' : 'mdi:eye'} className="text-xl" />
</button>
</div>
{errors.confirmPassword && ( {errors.confirmPassword && (
<p className="mt-1 text-sm text-red-500 dark:text-red-400"> <p className="mt-1 text-sm text-red-500 dark:text-red-400">
{errors.confirmPassword.message} {errors.confirmPassword.message}
+9 -3
View File
@@ -31,10 +31,16 @@ hackathonApi.interceptors.response.use(
(response) => response, (response) => response,
(error) => { (error) => {
// Handle 401 - clear session and redirect to login // Handle 401 - clear session and redirect to login
// But skip redirect if already on auth pages (to avoid reload on login failure)
if (error.response?.status === 401) { if (error.response?.status === 401) {
useAuthStore.getState().clearSession(); const isAuthPage = globalThis.window !== undefined && globalThis.location.pathname.startsWith('/auth');
if (typeof window !== 'undefined') {
window.location.href = '/auth/login'; // Only clear session and redirect if not on auth page
if (!isAuthPage) {
useAuthStore.getState().clearSession();
if (globalThis.window !== undefined) {
globalThis.location.href = '/auth/login';
}
} }
} }