-
+
✓
-
+
Check Your Email
-
+
We've sent a password reset link to {email}
-
+
Click the link in the email to reset your password. The link will
expire in 1 hour.
@@ -70,7 +59,7 @@ export default function ForgotPasswordPage() {
@@ -116,18 +105,18 @@ export default function ForgotPasswordPage() {
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="your@email.com"
- disabled={isLoading}
- 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"
+ disabled={forgotPasswordMutation.isPending}
+ 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
/>
diff --git a/apps/hackathon/src/app/auth/reset-password/page.tsx b/apps/hackathon/src/app/auth/reset-password/page.tsx
index 905917b..fd88412 100644
--- a/apps/hackathon/src/app/auth/reset-password/page.tsx
+++ b/apps/hackathon/src/app/auth/reset-password/page.tsx
@@ -1,26 +1,28 @@
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 { toast } from 'sonner';
export default function ResetPasswordPage() {
const navigate = useNavigate();
+ const { clearSession } = useAuthStore();
+ const resetPasswordMutation = useResetPassword();
const [password, setPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
- const [isLoading, setIsLoading] = useState(false);
- const [isValidToken, setIsValidToken] = useState(false);
+ const [accessToken, setAccessToken] = useState
(null);
- // useEffect(() => {
- // // Check if we have a valid session (from the reset link)
- // supabase.auth.getSession().then(({ data: { session } }) => {
- // if (session) {
- // setIsValidToken(true);
- // } else {
- // toast.error('Invalid or expired reset link');
- // setTimeout(() => navigate('/auth/forgot-password'), 2000);
- // }
- // });
- // }, [navigate]);
+ useEffect(() => {
+ // Get the access_token from URL hash (Supabase sends it as hash fragment)
+ const hashParams = new URLSearchParams(globalThis.location.hash.substring(1));
+ const token = hashParams.get('access_token');
+
+ if (token) {
+ setAccessToken(token);
+ } else {
+ toast.error('Invalid or expired reset link');
+ setTimeout(() => navigate('/auth/forgot-password'), 2000);
+ }
+ }, [navigate]);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
@@ -35,42 +37,39 @@ export default function ResetPasswordPage() {
return;
}
+ if (!accessToken) {
+ toast.error('Invalid reset token');
+ return;
+ }
+
try {
- setIsLoading(true);
-
- const { error } = await supabase.auth.updateUser({
- password: password,
+ await resetPasswordMutation.mutateAsync({
+ access_token: accessToken,
+ new_password: password,
});
- if (error) {
- throw error;
- }
-
toast.success('Password updated successfully!');
- // Sign out and redirect to login
- await supabase.auth.signOut();
+ // Clear session and redirect to login
+ clearSession();
navigate('/auth/login');
} catch (err) {
- // console.error('Failed to reset password:', err);
toast.error((err as Error).message || 'Failed to reset password');
- } finally {
- setIsLoading(false);
}
};
- // if (!isValidToken) {
- // return (
- //
- //
- //
- //
- // Verifying reset link...
- //
- //
- //
- // );
- // }
+ if (!accessToken) {
+ return (
+
+
+
+
+ Verifying reset link...
+
+
+
+ );
+ }
return (
@@ -98,7 +97,7 @@ export default function ResetPasswordPage() {
value={password}
onChange={(e) => setPassword(e.target.value)}
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"
required
minLength={6}
@@ -118,7 +117,7 @@ export default function ResetPasswordPage() {
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
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"
required
minLength={6}
@@ -127,10 +126,10 @@ export default function ResetPasswordPage() {
diff --git a/apps/hackathon/src/app/layout.tsx b/apps/hackathon/src/app/layout.tsx
index c17b781..d6af127 100644
--- a/apps/hackathon/src/app/layout.tsx
+++ b/apps/hackathon/src/app/layout.tsx
@@ -5,7 +5,7 @@ import {
useNavigate,
} from 'react-router-dom';
import { useEffect, useState } from 'react';
-import { supabase } from '@imphnen-frontend-service/service';
+import { useAuthStore, useUserMe } from '@imphnen-frontend-service/service';
// Define onboarding routes
const ONBOARDING_ROUTES = new Set(['/onboarding/user']);
@@ -13,6 +13,8 @@ const ONBOARDING_ROUTES = new Set(['/onboarding/user']);
export default function RootLayout() {
const location = useLocation();
const navigate = useNavigate();
+ const { session } = useAuthStore();
+ const { data: userData, isLoading: isUserLoading } = useUserMe();
const [isChecking, setIsChecking] = useState(true);
useEffect(() => {
@@ -31,22 +33,8 @@ export default function RootLayout() {
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
const isPublicAuthPage = pathname === '/maintenance';
- // pathname === '/auth/login' ||
- // pathname === '/auth/signup' ||
- // pathname === '/auth/forgot-password' ||
- // pathname === '/auth/reset-password';
if (isPublicAuthPage) {
// 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
if (!session) {
navigate('/maintenance', { replace: true });
- // navigate('/auth/login', { replace: true });
setIsChecking(false);
return;
}
+ // Wait for user data to load before checking onboarding
+ if (isUserLoading) {
+ return;
+ }
+
// Check if user has completed onboarding (skip for onboarding routes)
if (!ONBOARDING_ROUTES.has(pathname)) {
- try {
- const { data: userData, error: userError } = await supabase
- .from('users')
- .select('location')
- .eq('id', session.user.id)
- .single();
+ const hasLocation = !!userData?.data?.location || !!session?.user?.location;
- if (userError) {
- console.error('[Layout] Failed to fetch user data:', userError);
- setIsChecking(false);
- return;
- }
-
- const hasLocation = !!userData?.location;
-
- if (!hasLocation) {
- navigate('/onboarding/user', { replace: true });
- setIsChecking(false);
- return;
- }
- } catch (error) {
- // Silently handle error
+ if (!hasLocation) {
+ navigate('/onboarding/user', { replace: true });
+ setIsChecking(false);
+ return;
}
}
@@ -105,7 +81,7 @@ export default function RootLayout() {
};
checkAuth();
- }, [location.pathname, navigate]);
+ }, [location.pathname, navigate, session, userData, isUserLoading]);
// Show loading state while checking auth
if (isChecking) {
diff --git a/apps/hackathon/src/components/sidebar.tsx b/apps/hackathon/src/components/sidebar.tsx
index 7b0eff1..34fe193 100644
--- a/apps/hackathon/src/components/sidebar.tsx
+++ b/apps/hackathon/src/components/sidebar.tsx
@@ -3,7 +3,6 @@ import { Link, useLocation } from 'react-router';
import {
useMyTeams,
useAuthStore,
- supabase,
} from '@imphnen-frontend-service/service';
import { useNavigate } from 'react-router';
import { toast } from 'sonner';
@@ -40,19 +39,11 @@ export const Sidebar: FC = ({ isOpen = true, onClose }) => {
}
}, [location.pathname]);
- const handleLogout = async () => {
- try {
- await supabase.auth.signOut();
- clearSession();
- localStorage.clear();
- toast.success('Logged out successfully');
- navigate('/auth/login');
- } catch (error) {
- console.error('Logout error:', error);
- clearSession();
- localStorage.clear();
- navigate('/auth/login');
- }
+ const handleLogout = () => {
+ clearSession();
+ localStorage.clear();
+ toast.success('Logged out successfully');
+ navigate('/auth/login');
};
const navItems: NavItem[] = [
diff --git a/apps/hackathon/src/middleware.ts b/apps/hackathon/src/middleware.ts
index d5001ae..010770d 100644
--- a/apps/hackathon/src/middleware.ts
+++ b/apps/hackathon/src/middleware.ts
@@ -1,5 +1,5 @@
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';
const mappingPublicRoutes = [
@@ -37,14 +37,9 @@ export const middleware = async ({ request }: LoaderFunctionArgs) => {
const url = new URL(request.url);
const pathname = url.pathname;
- // Get session from Supabase (authoritative source)
- const { data: { session: supabaseSession }, error: sessionError } = await supabase.auth.getSession();
-
- // Handle session errors
- if (sessionError) {
- console.error('[Middleware] Session error:', sessionError);
- // Don't redirect on session errors, let the app handle it
- }
+ // Get session from local storage (via SessionUser)
+ const session = SessionUser.get();
+ const isAuthenticated = !!session?.token?.access_token;
// Allow to access the hackathon pages without authentication
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
if (pathname.startsWith('/auth')) {
- if (supabaseSession) return redirect('/dashboard');
+ if (isAuthenticated) return redirect('/dashboard');
return null;
}
- // Require authentication for all other routes - ONLY check Supabase session
- if (!supabaseSession) {
+ // Require authentication for all other routes
+ if (!isAuthenticated) {
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
if (!mappingOnboardingRoutes.includes(pathname)) {
try {
- const userId = supabaseSession.user.id;
+ const userId = session?.user?.id;
+ if (!userId) {
+ return redirect('/auth/login');
+ }
+
const now = Date.now();
// Check cache first
@@ -86,20 +85,20 @@ export const middleware = async ({ request }: LoaderFunctionArgs) => {
if (cached && (now - cached.timestamp) < CACHE_DURATION) {
hasLocation = cached.hasLocation;
} else {
- const { data: userData, error: userError } = await supabase
- .from('users')
- .select('location')
- .eq('id', userId)
- .single();
-
- if (userError) {
- console.error('[Middleware] Failed to fetch user data:', userError);
- // If we can't fetch user data, allow access (don't break the app)
- return null;
+ // First check session data (faster)
+ if (session?.user?.location) {
+ hasLocation = true;
+ } else {
+ // Fetch from backend API
+ try {
+ const response = await hackathonApi.get('/users/me');
+ hasLocation = !!response.data?.data?.location;
+ } catch {
+ // If API fails, check session data as fallback
+ hasLocation = !!session?.user?.location;
+ }
}
- hasLocation = !!userData?.location;
-
// Update cache
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)
- const session = SessionUser.get();
+ // Check route permissions using user data from session
const userPermissions =
- session?.role?.permissions?.map?.((perm) => perm?.name) ?? [];
+ session?.user?.role?.permissions?.map?.((perm) => perm?.name) ?? [];
const matchedRoute = mappingRoutePermissions.find(
(route) => route.path === pathname
diff --git a/libs/service/src/index.ts b/libs/service/src/index.ts
index 981a008..d0aa0f5 100644
--- a/libs/service/src/index.ts
+++ b/libs/service/src/index.ts
@@ -2,5 +2,5 @@ export * from './api';
export * from './hooks';
export * from './types';
export * from './schemas';
-export * from './supabase';
export * from './storage';
+// Note: Supabase export removed - using backend API instead
diff --git a/libs/utils/src/hooks/use-session.ts b/libs/utils/src/hooks/use-session.ts
index 86bdc75..c2c5176 100644
--- a/libs/utils/src/hooks/use-session.ts
+++ b/libs/utils/src/hooks/use-session.ts
@@ -1,4 +1,4 @@
-import { supabase, useAuthStore } from '@imphnen-frontend-service/service';
+import { useAuthStore } from '@imphnen-frontend-service/service';
import { useNavigate } from 'react-router';
export const useSession = () => {
@@ -6,9 +6,9 @@ export const useSession = () => {
const { clearSession, session, status } = useAuthStore();
const isAuthenticated = status === 'authenticated';
- const signOut = async () => {
- await supabase.auth.signOut();
+ const signOut = () => {
clearSession();
+ localStorage.clear();
navigate('/auth/login');
};