fix(hackathon): remove Supabase dependencies completely
- Remove Supabase export from service library - Update forgot-password and reset-password pages to use backend API - Update layout.tsx to use useAuthStore instead of Supabase session - Update sidebar logout to not use Supabase - Update middleware to use SessionUser instead of Supabase - Update use-session hook to remove Supabase dependency 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude
parent
9a97eb3e8e
commit
c385aa1996
@@ -1,5 +1,5 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { supabase } from '@imphnen-frontend-service/service';
|
import { useForgotPassword } from '@imphnen-frontend-service/service';
|
||||||
import { Link, useNavigate } from 'react-router';
|
import { Link, useNavigate } from 'react-router';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { Icon } from '@iconify/react';
|
import { Icon } from '@iconify/react';
|
||||||
@@ -7,9 +7,9 @@ import ThemeToggle from '../../../components/theme-toggle';
|
|||||||
|
|
||||||
export default function ForgotPasswordPage() {
|
export default function ForgotPasswordPage() {
|
||||||
const [email, setEmail] = useState('');
|
const [email, setEmail] = useState('');
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
|
||||||
const [emailSent, setEmailSent] = useState(false);
|
const [emailSent, setEmailSent] = useState(false);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const forgotPasswordMutation = useForgotPassword();
|
||||||
|
|
||||||
const handleSubmit = async (e: React.FormEvent) => {
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -20,44 +20,33 @@ export default function ForgotPasswordPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setIsLoading(true);
|
await forgotPasswordMutation.mutateAsync({ email });
|
||||||
|
|
||||||
const { error } = await supabase.auth.resetPasswordForEmail(email, {
|
|
||||||
redirectTo: `${window.location.origin}/auth/reset-password`,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
|
|
||||||
setEmailSent(true);
|
setEmailSent(true);
|
||||||
toast.success('Password reset email sent! Check your inbox.');
|
toast.success('Password reset email sent! Check your inbox.');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// console.error('Failed to send reset email:', err);
|
|
||||||
toast.error((err as Error).message || 'Failed to send reset email');
|
toast.error((err as Error).message || 'Failed to send reset email');
|
||||||
} finally {
|
|
||||||
setIsLoading(false);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (emailSent) {
|
if (emailSent) {
|
||||||
return (
|
return (
|
||||||
<div className="flex justify-center items-center min-h-screen bg-gray-50 p-4">
|
<div className="flex justify-center items-center min-h-screen bg-gray-50 dark:bg-gray-950 p-4">
|
||||||
<div className="bg-white w-full max-w-md p-8 rounded-2xl shadow-lg border border-gray-200 text-center">
|
<div className="bg-white dark:bg-gray-900 w-full max-w-md p-8 rounded-2xl shadow-lg border border-gray-200 dark:border-gray-700 text-center">
|
||||||
<div className="mb-6">
|
<div className="mb-6">
|
||||||
<div className="mx-auto w-16 h-16 bg-green-100 rounded-full flex items-center justify-center mb-4">
|
<div className="mx-auto w-16 h-16 bg-green-100 dark:bg-green-900/30 rounded-full flex items-center justify-center mb-4">
|
||||||
<span className="text-3xl">✓</span>
|
<span className="text-3xl">✓</span>
|
||||||
</div>
|
</div>
|
||||||
<h2 className="text-3xl font-bold text-gray-900 mb-2">
|
<h2 className="text-3xl font-bold text-gray-900 dark:text-white mb-2">
|
||||||
Check Your Email
|
Check Your Email
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-gray-600">
|
<p className="text-gray-600 dark:text-gray-400">
|
||||||
We've sent a password reset link to <strong>{email}</strong>
|
We've sent a password reset link to <strong>{email}</strong>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<p className="text-sm text-gray-600">
|
<p className="text-sm text-gray-600 dark:text-gray-400">
|
||||||
Click the link in the email to reset your password. The link will
|
Click the link in the email to reset your password. The link will
|
||||||
expire in 1 hour.
|
expire in 1 hour.
|
||||||
</p>
|
</p>
|
||||||
@@ -70,7 +59,7 @@ export default function ForgotPasswordPage() {
|
|||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={() => setEmailSent(false)}
|
onClick={() => setEmailSent(false)}
|
||||||
className="w-full py-3 text-gray-600 hover:text-gray-900 transition-colors"
|
className="w-full py-3 text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white transition-colors"
|
||||||
>
|
>
|
||||||
Send another email
|
Send another email
|
||||||
</button>
|
</button>
|
||||||
@@ -116,18 +105,18 @@ export default function ForgotPasswordPage() {
|
|||||||
value={email}
|
value={email}
|
||||||
onChange={(e) => setEmail(e.target.value)}
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
placeholder="your@email.com"
|
placeholder="your@email.com"
|
||||||
disabled={isLoading}
|
disabled={forgotPasswordMutation.isPending}
|
||||||
className="bg-white dark:bg-gray-800 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"
|
className="bg-white dark:bg-gray-800 text-gray-900 dark:text-white 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 dark:disabled:bg-gray-700 disabled:cursor-not-allowed"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={isLoading}
|
disabled={forgotPasswordMutation.isPending}
|
||||||
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"
|
||||||
>
|
>
|
||||||
{isLoading ? 'Sending...' : 'Send Reset Link'}
|
{forgotPasswordMutation.isPending ? 'Sending...' : 'Send Reset Link'}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,26 +1,28 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { supabase } 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';
|
||||||
|
|
||||||
export default function ResetPasswordPage() {
|
export default function ResetPasswordPage() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const { clearSession } = useAuthStore();
|
||||||
|
const resetPasswordMutation = useResetPassword();
|
||||||
const [password, setPassword] = useState('');
|
const [password, setPassword] = useState('');
|
||||||
const [confirmPassword, setConfirmPassword] = useState('');
|
const [confirmPassword, setConfirmPassword] = useState('');
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [accessToken, setAccessToken] = useState<string | null>(null);
|
||||||
const [isValidToken, setIsValidToken] = useState(false);
|
|
||||||
|
|
||||||
// useEffect(() => {
|
useEffect(() => {
|
||||||
// // Check if we have a valid session (from the reset link)
|
// Get the access_token from URL hash (Supabase sends it as hash fragment)
|
||||||
// supabase.auth.getSession().then(({ data: { session } }) => {
|
const hashParams = new URLSearchParams(globalThis.location.hash.substring(1));
|
||||||
// if (session) {
|
const token = hashParams.get('access_token');
|
||||||
// setIsValidToken(true);
|
|
||||||
// } else {
|
if (token) {
|
||||||
// toast.error('Invalid or expired reset link');
|
setAccessToken(token);
|
||||||
// setTimeout(() => navigate('/auth/forgot-password'), 2000);
|
} else {
|
||||||
// }
|
toast.error('Invalid or expired reset link');
|
||||||
// });
|
setTimeout(() => navigate('/auth/forgot-password'), 2000);
|
||||||
// }, [navigate]);
|
}
|
||||||
|
}, [navigate]);
|
||||||
|
|
||||||
const handleSubmit = async (e: React.FormEvent) => {
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -35,42 +37,39 @@ export default function ResetPasswordPage() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!accessToken) {
|
||||||
|
toast.error('Invalid reset token');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setIsLoading(true);
|
await resetPasswordMutation.mutateAsync({
|
||||||
|
access_token: accessToken,
|
||||||
const { error } = await supabase.auth.updateUser({
|
new_password: password,
|
||||||
password: password,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (error) {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
|
|
||||||
toast.success('Password updated successfully!');
|
toast.success('Password updated successfully!');
|
||||||
|
|
||||||
// Sign out and redirect to login
|
// Clear session and redirect to login
|
||||||
await supabase.auth.signOut();
|
clearSession();
|
||||||
navigate('/auth/login');
|
navigate('/auth/login');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// console.error('Failed to reset password:', err);
|
|
||||||
toast.error((err as Error).message || 'Failed to reset password');
|
toast.error((err as Error).message || 'Failed to reset password');
|
||||||
} finally {
|
|
||||||
setIsLoading(false);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// if (!isValidToken) {
|
if (!accessToken) {
|
||||||
// return (
|
return (
|
||||||
// <div className="flex justify-center items-center min-h-screen bg-gray-50 dark:bg-gray-950">
|
<div className="flex justify-center items-center min-h-screen bg-gray-50 dark:bg-gray-950">
|
||||||
// <div className="text-center">
|
<div className="text-center">
|
||||||
// <div className="inline-block animate-spin rounded-full h-12 w-12 border-b-2 border-primary-600 mb-4"></div>
|
<div className="inline-block animate-spin rounded-full h-12 w-12 border-b-2 border-primary-600 mb-4"></div>
|
||||||
// <p className="text-gray-600 dark:text-gray-400">
|
<p className="text-gray-600 dark:text-gray-400">
|
||||||
// Verifying reset link...
|
Verifying reset link...
|
||||||
// </p>
|
</p>
|
||||||
// </div>
|
</div>
|
||||||
// </div>
|
</div>
|
||||||
// );
|
);
|
||||||
// }
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex justify-center items-center min-h-screen bg-gray-50 dark:bg-gray-950 p-4">
|
<div className="flex justify-center items-center min-h-screen bg-gray-50 dark:bg-gray-950 p-4">
|
||||||
@@ -98,7 +97,7 @@ export default function ResetPasswordPage() {
|
|||||||
value={password}
|
value={password}
|
||||||
onChange={(e) => setPassword(e.target.value)}
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
placeholder="••••••••"
|
placeholder="••••••••"
|
||||||
disabled={isLoading}
|
disabled={resetPasswordMutation.isPending}
|
||||||
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"
|
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"
|
||||||
required
|
required
|
||||||
minLength={6}
|
minLength={6}
|
||||||
@@ -118,7 +117,7 @@ export default function ResetPasswordPage() {
|
|||||||
value={confirmPassword}
|
value={confirmPassword}
|
||||||
onChange={(e) => setConfirmPassword(e.target.value)}
|
onChange={(e) => setConfirmPassword(e.target.value)}
|
||||||
placeholder="••••••••"
|
placeholder="••••••••"
|
||||||
disabled={isLoading}
|
disabled={resetPasswordMutation.isPending}
|
||||||
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"
|
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"
|
||||||
required
|
required
|
||||||
minLength={6}
|
minLength={6}
|
||||||
@@ -127,10 +126,10 @@ export default function ResetPasswordPage() {
|
|||||||
|
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={isLoading}
|
disabled={resetPasswordMutation.isPending}
|
||||||
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"
|
||||||
>
|
>
|
||||||
{isLoading ? 'Updating...' : 'Update Password'}
|
{resetPasswordMutation.isPending ? 'Updating...' : 'Update Password'}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import {
|
|||||||
useNavigate,
|
useNavigate,
|
||||||
} from 'react-router-dom';
|
} from 'react-router-dom';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { supabase } from '@imphnen-frontend-service/service';
|
import { useAuthStore, useUserMe } from '@imphnen-frontend-service/service';
|
||||||
|
|
||||||
// Define onboarding routes
|
// Define onboarding routes
|
||||||
const ONBOARDING_ROUTES = new Set(['/onboarding/user']);
|
const ONBOARDING_ROUTES = new Set(['/onboarding/user']);
|
||||||
@@ -13,6 +13,8 @@ const ONBOARDING_ROUTES = new Set(['/onboarding/user']);
|
|||||||
export default function RootLayout() {
|
export default function RootLayout() {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const { session } = useAuthStore();
|
||||||
|
const { data: userData, isLoading: isUserLoading } = useUserMe();
|
||||||
const [isChecking, setIsChecking] = useState(true);
|
const [isChecking, setIsChecking] = useState(true);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -31,22 +33,8 @@ export default function RootLayout() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check Supabase session
|
|
||||||
const {
|
|
||||||
data: { session },
|
|
||||||
error,
|
|
||||||
} = await supabase.auth.getSession();
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
console.error('[Layout] Session error:', error);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Public auth pages (login, signup, forgot-password, reset-password) - allow unauthenticated access
|
// Public auth pages (login, signup, forgot-password, reset-password) - allow unauthenticated access
|
||||||
const isPublicAuthPage = pathname === '/maintenance';
|
const isPublicAuthPage = pathname === '/maintenance';
|
||||||
// pathname === '/auth/login' ||
|
|
||||||
// pathname === '/auth/signup' ||
|
|
||||||
// pathname === '/auth/forgot-password' ||
|
|
||||||
// pathname === '/auth/reset-password';
|
|
||||||
|
|
||||||
if (isPublicAuthPage) {
|
if (isPublicAuthPage) {
|
||||||
// If already authenticated and not on password reset pages, redirect to dashboard
|
// If already authenticated and not on password reset pages, redirect to dashboard
|
||||||
@@ -69,35 +57,23 @@ export default function RootLayout() {
|
|||||||
// Require authentication for all other routes
|
// Require authentication for all other routes
|
||||||
if (!session) {
|
if (!session) {
|
||||||
navigate('/maintenance', { replace: true });
|
navigate('/maintenance', { replace: true });
|
||||||
// navigate('/auth/login', { replace: true });
|
|
||||||
setIsChecking(false);
|
setIsChecking(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Wait for user data to load before checking onboarding
|
||||||
|
if (isUserLoading) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Check if user has completed onboarding (skip for onboarding routes)
|
// Check if user has completed onboarding (skip for onboarding routes)
|
||||||
if (!ONBOARDING_ROUTES.has(pathname)) {
|
if (!ONBOARDING_ROUTES.has(pathname)) {
|
||||||
try {
|
const hasLocation = !!userData?.data?.location || !!session?.user?.location;
|
||||||
const { data: userData, error: userError } = await supabase
|
|
||||||
.from('users')
|
|
||||||
.select('location')
|
|
||||||
.eq('id', session.user.id)
|
|
||||||
.single();
|
|
||||||
|
|
||||||
if (userError) {
|
if (!hasLocation) {
|
||||||
console.error('[Layout] Failed to fetch user data:', userError);
|
navigate('/onboarding/user', { replace: true });
|
||||||
setIsChecking(false);
|
setIsChecking(false);
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
const hasLocation = !!userData?.location;
|
|
||||||
|
|
||||||
if (!hasLocation) {
|
|
||||||
navigate('/onboarding/user', { replace: true });
|
|
||||||
setIsChecking(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
// Silently handle error
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,7 +81,7 @@ export default function RootLayout() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
checkAuth();
|
checkAuth();
|
||||||
}, [location.pathname, navigate]);
|
}, [location.pathname, navigate, session, userData, isUserLoading]);
|
||||||
|
|
||||||
// Show loading state while checking auth
|
// Show loading state while checking auth
|
||||||
if (isChecking) {
|
if (isChecking) {
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { Link, useLocation } from 'react-router';
|
|||||||
import {
|
import {
|
||||||
useMyTeams,
|
useMyTeams,
|
||||||
useAuthStore,
|
useAuthStore,
|
||||||
supabase,
|
|
||||||
} from '@imphnen-frontend-service/service';
|
} from '@imphnen-frontend-service/service';
|
||||||
import { useNavigate } from 'react-router';
|
import { useNavigate } from 'react-router';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
@@ -40,19 +39,11 @@ export const Sidebar: FC<SidebarProps> = ({ isOpen = true, onClose }) => {
|
|||||||
}
|
}
|
||||||
}, [location.pathname]);
|
}, [location.pathname]);
|
||||||
|
|
||||||
const handleLogout = async () => {
|
const handleLogout = () => {
|
||||||
try {
|
clearSession();
|
||||||
await supabase.auth.signOut();
|
localStorage.clear();
|
||||||
clearSession();
|
toast.success('Logged out successfully');
|
||||||
localStorage.clear();
|
navigate('/auth/login');
|
||||||
toast.success('Logged out successfully');
|
|
||||||
navigate('/auth/login');
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Logout error:', error);
|
|
||||||
clearSession();
|
|
||||||
localStorage.clear();
|
|
||||||
navigate('/auth/login');
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const navItems: NavItem[] = [
|
const navItems: NavItem[] = [
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { SessionUser } from '@imphnen-frontend-service/utils';
|
import { SessionUser } from '@imphnen-frontend-service/utils';
|
||||||
import { supabase } from '@imphnen-frontend-service/service';
|
import { hackathonApi } from '@imphnen-frontend-service/service';
|
||||||
import { LoaderFunctionArgs, redirect } from 'react-router';
|
import { LoaderFunctionArgs, redirect } from 'react-router';
|
||||||
|
|
||||||
const mappingPublicRoutes = [
|
const mappingPublicRoutes = [
|
||||||
@@ -37,14 +37,9 @@ export const middleware = async ({ request }: LoaderFunctionArgs) => {
|
|||||||
const url = new URL(request.url);
|
const url = new URL(request.url);
|
||||||
const pathname = url.pathname;
|
const pathname = url.pathname;
|
||||||
|
|
||||||
// Get session from Supabase (authoritative source)
|
// Get session from local storage (via SessionUser)
|
||||||
const { data: { session: supabaseSession }, error: sessionError } = await supabase.auth.getSession();
|
const session = SessionUser.get();
|
||||||
|
const isAuthenticated = !!session?.token?.access_token;
|
||||||
// Handle session errors
|
|
||||||
if (sessionError) {
|
|
||||||
console.error('[Middleware] Session error:', sessionError);
|
|
||||||
// Don't redirect on session errors, let the app handle it
|
|
||||||
}
|
|
||||||
|
|
||||||
// Allow to access the hackathon pages without authentication
|
// Allow to access the hackathon pages without authentication
|
||||||
if (mappingPublicPrefixRoutes.some((prefix) => pathname.startsWith(prefix))) {
|
if (mappingPublicPrefixRoutes.some((prefix) => pathname.startsWith(prefix))) {
|
||||||
@@ -63,20 +58,24 @@ export const middleware = async ({ request }: LoaderFunctionArgs) => {
|
|||||||
|
|
||||||
// Auth routes (all /auth/* paths) - redirect to dashboard if already authenticated
|
// Auth routes (all /auth/* paths) - redirect to dashboard if already authenticated
|
||||||
if (pathname.startsWith('/auth')) {
|
if (pathname.startsWith('/auth')) {
|
||||||
if (supabaseSession) return redirect('/dashboard');
|
if (isAuthenticated) return redirect('/dashboard');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Require authentication for all other routes - ONLY check Supabase session
|
// Require authentication for all other routes
|
||||||
if (!supabaseSession) {
|
if (!isAuthenticated) {
|
||||||
return redirect('/auth/login');
|
return redirect('/auth/login');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if user has completed onboarding by querying database (not localStorage!)
|
// Check if user has completed onboarding
|
||||||
// Skip onboarding check for onboarding routes themselves
|
// Skip onboarding check for onboarding routes themselves
|
||||||
if (!mappingOnboardingRoutes.includes(pathname)) {
|
if (!mappingOnboardingRoutes.includes(pathname)) {
|
||||||
try {
|
try {
|
||||||
const userId = supabaseSession.user.id;
|
const userId = session?.user?.id;
|
||||||
|
if (!userId) {
|
||||||
|
return redirect('/auth/login');
|
||||||
|
}
|
||||||
|
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
|
|
||||||
// Check cache first
|
// Check cache first
|
||||||
@@ -86,20 +85,20 @@ export const middleware = async ({ request }: LoaderFunctionArgs) => {
|
|||||||
if (cached && (now - cached.timestamp) < CACHE_DURATION) {
|
if (cached && (now - cached.timestamp) < CACHE_DURATION) {
|
||||||
hasLocation = cached.hasLocation;
|
hasLocation = cached.hasLocation;
|
||||||
} else {
|
} else {
|
||||||
const { data: userData, error: userError } = await supabase
|
// First check session data (faster)
|
||||||
.from('users')
|
if (session?.user?.location) {
|
||||||
.select('location')
|
hasLocation = true;
|
||||||
.eq('id', userId)
|
} else {
|
||||||
.single();
|
// Fetch from backend API
|
||||||
|
try {
|
||||||
if (userError) {
|
const response = await hackathonApi.get('/users/me');
|
||||||
console.error('[Middleware] Failed to fetch user data:', userError);
|
hasLocation = !!response.data?.data?.location;
|
||||||
// If we can't fetch user data, allow access (don't break the app)
|
} catch {
|
||||||
return null;
|
// If API fails, check session data as fallback
|
||||||
|
hasLocation = !!session?.user?.location;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
hasLocation = !!userData?.location;
|
|
||||||
|
|
||||||
// Update cache
|
// Update cache
|
||||||
onboardingCache.set(userId, { hasLocation, timestamp: now });
|
onboardingCache.set(userId, { hasLocation, timestamp: now });
|
||||||
}
|
}
|
||||||
@@ -114,10 +113,9 @@ export const middleware = async ({ request }: LoaderFunctionArgs) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check route permissions using fresh user data from Zustand (for UI metadata)
|
// Check route permissions using user data from session
|
||||||
const session = SessionUser.get();
|
|
||||||
const userPermissions =
|
const userPermissions =
|
||||||
session?.role?.permissions?.map?.((perm) => perm?.name) ?? [];
|
session?.user?.role?.permissions?.map?.((perm) => perm?.name) ?? [];
|
||||||
|
|
||||||
const matchedRoute = mappingRoutePermissions.find(
|
const matchedRoute = mappingRoutePermissions.find(
|
||||||
(route) => route.path === pathname
|
(route) => route.path === pathname
|
||||||
|
|||||||
@@ -2,5 +2,5 @@ export * from './api';
|
|||||||
export * from './hooks';
|
export * from './hooks';
|
||||||
export * from './types';
|
export * from './types';
|
||||||
export * from './schemas';
|
export * from './schemas';
|
||||||
export * from './supabase';
|
|
||||||
export * from './storage';
|
export * from './storage';
|
||||||
|
// Note: Supabase export removed - using backend API instead
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { supabase, useAuthStore } from '@imphnen-frontend-service/service';
|
import { useAuthStore } from '@imphnen-frontend-service/service';
|
||||||
import { useNavigate } from 'react-router';
|
import { useNavigate } from 'react-router';
|
||||||
|
|
||||||
export const useSession = () => {
|
export const useSession = () => {
|
||||||
@@ -6,9 +6,9 @@ export const useSession = () => {
|
|||||||
const { clearSession, session, status } = useAuthStore();
|
const { clearSession, session, status } = useAuthStore();
|
||||||
const isAuthenticated = status === 'authenticated';
|
const isAuthenticated = status === 'authenticated';
|
||||||
|
|
||||||
const signOut = async () => {
|
const signOut = () => {
|
||||||
await supabase.auth.signOut();
|
|
||||||
clearSession();
|
clearSession();
|
||||||
|
localStorage.clear();
|
||||||
navigate('/auth/login');
|
navigate('/auth/login');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user