change auth to fastapi
	
		
			
	
		
	
	
		
	
		
			All checks were successful
		
		
	
	
		
			
				
	
				PR Build Check / build (pull_request) Successful in 2m20s
				
			
		
		
	
	
				
					
				
			
		
			All checks were successful
		
		
	
	PR Build Check / build (pull_request) Successful in 2m20s
				
			This commit is contained in:
		
							parent
							
								
									1133e52ec0
								
							
						
					
					
						commit
						79c611d061
					
				| @ -68,28 +68,40 @@ const AdminDashboard = () => { | |||||||
|   // near other refs
 |   // near other refs
 | ||||||
|   const loggingRef = useRef<HTMLDivElement | null>(null); |   const loggingRef = useRef<HTMLDivElement | null>(null); | ||||||
| 
 | 
 | ||||||
|  |   const API = process.env.NEXT_PUBLIC_FASTAPI_URL || 'http://127.0.0.1:8000'; | ||||||
|  | 
 | ||||||
| useEffect(() => { | useEffect(() => { | ||||||
|  |   let cancelled = false; | ||||||
|  | 
 | ||||||
|   const checkAuth = async () => { |   const checkAuth = async () => { | ||||||
|     try { |     try { | ||||||
|         const res = await fetch('/api/auth/me', { credentials: 'include' }); |       const res = await fetch(`${API}/auth/me`, { | ||||||
|  |         credentials: 'include', | ||||||
|  |         cache: 'no-store', | ||||||
|  |       }); | ||||||
|  | 
 | ||||||
|       if (!res.ok) { |       if (!res.ok) { | ||||||
|         router.replace('/login'); |         router.replace('/login'); | ||||||
|         return; |         return; | ||||||
|       } |       } | ||||||
|         const data = await res.json(); | 
 | ||||||
|         if (!data.user) { |       const user = await res.json().catch(() => null); | ||||||
|  |       if (!user?.id) { | ||||||
|         router.replace('/login'); |         router.replace('/login'); | ||||||
|         return; |         return; | ||||||
|       } |       } | ||||||
|  |       // authenticated
 | ||||||
|     } catch { |     } catch { | ||||||
|       router.replace('/login'); |       router.replace('/login'); | ||||||
|  |       return; | ||||||
|     } finally { |     } finally { | ||||||
|         setAuthChecked(true); |       if (!cancelled) setAuthChecked(true); | ||||||
|     } |     } | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   checkAuth(); |   checkAuth(); | ||||||
|   }, [router]); |   return () => { cancelled = true; }; | ||||||
|  | }, [router, API]); | ||||||
| 
 | 
 | ||||||
|    |    | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -10,30 +10,48 @@ export default function LoginPage() { | |||||||
|   const router = useRouter(); |   const router = useRouter(); | ||||||
|   const [ready, setReady] = useState(false); // gate to avoid UI flash
 |   const [ready, setReady] = useState(false); // gate to avoid UI flash
 | ||||||
| 
 | 
 | ||||||
|  |   // Use ONE client-exposed API env var everywhere
 | ||||||
|  |   const API = process.env.NEXT_PUBLIC_FASTAPI_URL || 'http://127.0.0.1:8000'; | ||||||
|  | 
 | ||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
|     let cancelled = false; |     let cancelled = false; | ||||||
|  |     const controller = new AbortController(); | ||||||
| 
 | 
 | ||||||
|     (async () => { |     (async () => { | ||||||
|       try { |       try { | ||||||
|         const res = await fetch('/api/auth/me', { |         const res = await fetch(`${API}/auth/me`, { | ||||||
|           method: 'GET', |           credentials: 'include', | ||||||
|           cache: 'no-store', |           cache: 'no-store',        // don't reuse a cached 401
 | ||||||
|           credentials: 'include', // safe even if same-origin
 |           signal: controller.signal, | ||||||
|         }); |         }); | ||||||
|         if (!cancelled && res.ok) { | 
 | ||||||
|  |         if (!res.ok) { | ||||||
|  |           if (!cancelled) setReady(true); | ||||||
|  |           return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         const user = await res.json().catch(() => null); | ||||||
|  |         if (user?.id) { | ||||||
|  |           // already logged in -> go straight to dashboard
 | ||||||
|           router.replace('/adminDashboard'); |           router.replace('/adminDashboard'); | ||||||
|           return; |           return; | ||||||
|         } |         } | ||||||
|       } catch { | 
 | ||||||
|         // ignore errors; just show the form
 |         // not logged in -> show form
 | ||||||
|       } |  | ||||||
|         if (!cancelled) setReady(true); |         if (!cancelled) setReady(true); | ||||||
|  |       } catch { | ||||||
|  |         // network/error -> show form
 | ||||||
|  |         if (!cancelled) setReady(true); | ||||||
|  |       } | ||||||
|     })(); |     })(); | ||||||
| 
 | 
 | ||||||
|     return () => { cancelled = true; }; |     return () => { | ||||||
|   }, [router]); |       cancelled = true; | ||||||
|  |       controller.abort(); | ||||||
|  |     }; | ||||||
|  |   }, [router, API]); | ||||||
| 
 | 
 | ||||||
|   if (!ready) return null; // or a small spinner if you prefer
 |   if (!ready) return null; // or a spinner/skeleton
 | ||||||
| 
 | 
 | ||||||
|   return ( |   return ( | ||||||
|     <div className="relative min-h-screen overflow-hidden bg-[#060818] text-white"> |     <div className="relative min-h-screen overflow-hidden bg-[#060818] text-white"> | ||||||
| @ -66,10 +84,8 @@ export default function LoginPage() { | |||||||
|                      bg-[linear-gradient(45deg,#fffbe6_0%,rgba(255,251,230,0)_25%,rgba(255,251,230,0)_75%,#fffbe6_100%)] |                      bg-[linear-gradient(45deg,#fffbe6_0%,rgba(255,251,230,0)_25%,rgba(255,251,230,0)_75%,#fffbe6_100%)] | ||||||
|                      dark:bg-[linear-gradient(52.22deg,#facc15_0%,rgba(250,204,21,0)_20%,rgba(250,204,21,0)_80%,#facc15_100%)]" |                      dark:bg-[linear-gradient(52.22deg,#facc15_0%,rgba(250,204,21,0)_20%,rgba(250,204,21,0)_80%,#facc15_100%)]" | ||||||
|         > |         > | ||||||
|           {/* Inner card (glassmorphic effect) */} |  | ||||||
|           <div className="relative z-10 rounded-2xl bg-white/10 px-8 py-16 backdrop-blur-lg dark:bg-white/10 lg:min-h-[600px]"> |           <div className="relative z-10 rounded-2xl bg-white/10 px-8 py-16 backdrop-blur-lg dark:bg-white/10 lg:min-h-[600px]"> | ||||||
|             <div className="mx-auto w-full max-w-[440px] text-center"> |             <div className="mx-auto w-full max-w-[440px] text-center"> | ||||||
|               {/* Header */} |  | ||||||
|               <h1 className="text-4xl font-extrabold uppercase tracking-wide text-yellow-400 mb-2"> |               <h1 className="text-4xl font-extrabold uppercase tracking-wide text-yellow-400 mb-2"> | ||||||
|                 Sign In |                 Sign In | ||||||
|               </h1> |               </h1> | ||||||
| @ -77,10 +93,8 @@ export default function LoginPage() { | |||||||
|                 Enter your email and password to access your account. |                 Enter your email and password to access your account. | ||||||
|               </p> |               </p> | ||||||
| 
 | 
 | ||||||
|               {/* Login form */} |  | ||||||
|               <ComponentsAuthLoginForm /> |               <ComponentsAuthLoginForm /> | ||||||
| 
 | 
 | ||||||
|               {/* Footer link */} |  | ||||||
|               <div className="mt-6 text-sm text-gray-200 dark:text-gray-300"> |               <div className="mt-6 text-sm text-gray-200 dark:text-gray-300"> | ||||||
|                 Don’t have an account?{' '} |                 Don’t have an account?{' '} | ||||||
|                 <Link |                 <Link | ||||||
| @ -98,3 +112,4 @@ export default function LoginPage() { | |||||||
|   ); |   ); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|  | |||||||
| @ -3,31 +3,46 @@ import IconLockDots from '@/components/icon/icon-lock-dots'; | |||||||
| import IconMail from '@/components/icon/icon-mail'; | import IconMail from '@/components/icon/icon-mail'; | ||||||
| import { useRouter } from 'next/navigation'; | import { useRouter } from 'next/navigation'; | ||||||
| import { useState } from 'react'; | import { useState } from 'react'; | ||||||
| import axios from 'axios'; |  | ||||||
| import toast from 'react-hot-toast'; | import toast from 'react-hot-toast'; | ||||||
| 
 | 
 | ||||||
|  | type User = { id: string; email: string; is_active: boolean }; | ||||||
|  | 
 | ||||||
| const ComponentsAuthLoginForm = () => { | const ComponentsAuthLoginForm = () => { | ||||||
|   const [email, setEmail] = useState(''); |   const [email, setEmail] = useState(''); | ||||||
|   const [password, setPassword] = useState(''); |   const [password, setPassword] = useState(''); | ||||||
|   const [loading, setLoading] = useState(false); |   const [loading, setLoading] = useState(false); | ||||||
|   const router = useRouter(); |   const router = useRouter(); | ||||||
|  |   const API = process.env.NEXT_PUBLIC_FASTAPI_URL; // e.g. http://localhost:8000
 | ||||||
| 
 | 
 | ||||||
|   const submitForm = async (e: React.FormEvent) => { |   const submitForm = async (e: React.FormEvent<HTMLFormElement>) => { | ||||||
|     e.preventDefault(); |     e.preventDefault(); | ||||||
|     setLoading(true); |     setLoading(true); | ||||||
|     try { |     try { | ||||||
|       const res = await axios.post('/api/login', { email, password }); |       const res = await fetch(`${API}/auth/login`, { | ||||||
|       toast.success(res.data?.message || 'Login successful!'); |         method: 'POST', | ||||||
|  |         headers: { 'Content-Type': 'application/json' }, | ||||||
|  |         body: JSON.stringify({ email, password }), | ||||||
|  |         credentials: 'include', // cookie from FastAPI
 | ||||||
|  |       }); | ||||||
|  | 
 | ||||||
|  |       let data: any = null; | ||||||
|  |       try { | ||||||
|  |         data = await res.json(); | ||||||
|  |       } catch { | ||||||
|  |         // non-JSON error
 | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       if (!res.ok) { | ||||||
|  |         const msg = data?.detail || data?.message || 'Invalid credentials'; | ||||||
|  |         throw new Error(msg); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       const user: User = data; | ||||||
|  |       toast.success(`Welcome ${user.email}`); | ||||||
|       router.push('/adminDashboard'); |       router.push('/adminDashboard'); | ||||||
|       router.refresh(); |       router.refresh(); | ||||||
|       // token cookie is already set by the server:
 |  | ||||||
|     } catch (err: any) { |     } catch (err: any) { | ||||||
|       console.error('Login error:', err); |       toast.error(err?.message ?? 'Login failed'); | ||||||
|       const msg = |  | ||||||
|         err?.response?.data?.message || |  | ||||||
|         err?.message || |  | ||||||
|         'Invalid credentials'; |  | ||||||
|       toast.error(msg); |  | ||||||
|     } finally { |     } finally { | ||||||
|       setLoading(false); |       setLoading(false); | ||||||
|     } |     } | ||||||
| @ -52,6 +67,7 @@ const ComponentsAuthLoginForm = () => { | |||||||
|           </span> |           </span> | ||||||
|         </div> |         </div> | ||||||
|       </div> |       </div> | ||||||
|  | 
 | ||||||
|       <div className="pb-2"> |       <div className="pb-2"> | ||||||
|         <label htmlFor="Password" className="text-yellow-400 text-left">Password</label> |         <label htmlFor="Password" className="text-yellow-400 text-left">Password</label> | ||||||
|         <div className="relative text-white-dark"> |         <div className="relative text-white-dark"> | ||||||
| @ -70,6 +86,7 @@ const ComponentsAuthLoginForm = () => { | |||||||
|           </span> |           </span> | ||||||
|         </div> |         </div> | ||||||
|       </div> |       </div> | ||||||
|  | 
 | ||||||
|       <button |       <button | ||||||
|         type="submit" |         type="submit" | ||||||
|         disabled={loading} |         disabled={loading} | ||||||
| @ -83,3 +100,4 @@ const ComponentsAuthLoginForm = () => { | |||||||
| 
 | 
 | ||||||
| export default ComponentsAuthLoginForm; | export default ComponentsAuthLoginForm; | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|  | |||||||
| @ -33,12 +33,14 @@ export default function ComponentsAuthRegisterForm({ redirectTo = "/dashboard" } | |||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     const API = process.env.NEXT_PUBLIC_FASTAPI_URL; // e.g. http://localhost:8000
 | ||||||
|     try { |     try { | ||||||
|       setLoading(true); |       setLoading(true); | ||||||
|       const res = await fetch("/api/register", { |       const res = await fetch(`${API}/auth/register`, { | ||||||
|         method: "POST", |         method: 'POST', | ||||||
|         headers: { "Content-Type": "application/json" }, |         headers: { 'Content-Type': 'application/json' }, | ||||||
|         body: JSON.stringify({ email, password }), |         body: JSON.stringify({ email, password }), | ||||||
|  |         credentials: 'include', | ||||||
|       }); |       }); | ||||||
| 
 | 
 | ||||||
|       const data = await res.json(); |       const data = await res.json(); | ||||||
|  | |||||||
| @ -3,19 +3,18 @@ import { useEffect, useState } from 'react'; | |||||||
| import { useDispatch, useSelector } from 'react-redux'; | import { useDispatch, useSelector } from 'react-redux'; | ||||||
| import Link from 'next/link'; | import Link from 'next/link'; | ||||||
| import { IRootState } from '@/store'; | import { IRootState } from '@/store'; | ||||||
| import { toggleTheme, toggleSidebar, toggleRTL } from '@/store/themeConfigSlice'; | import { toggleTheme, toggleSidebar } from '@/store/themeConfigSlice'; | ||||||
| import Image from 'next/image'; | import Image from 'next/image'; | ||||||
| import Dropdown from '@/components/dropdown'; | import Dropdown from '@/components/dropdown'; | ||||||
| import IconMenu from '@/components/icon/icon-menu'; | import IconMenu from '@/components/icon/icon-menu'; | ||||||
| import IconSun from '@/components/icon/icon-sun'; | import IconSun from '@/components/icon/icon-sun'; | ||||||
| import IconMoon from '@/components/icon/icon-moon'; | import IconMoon from '@/components/icon/icon-moon'; | ||||||
| import IconUser from '@/components/icon/icon-user'; | import IconUser from '@/components/icon/icon-user'; | ||||||
| import IconMail from '@/components/icon/icon-mail'; |  | ||||||
| import IconLockDots from '@/components/icon/icon-lock-dots'; | import IconLockDots from '@/components/icon/icon-lock-dots'; | ||||||
| import IconLogout from '@/components/icon/icon-logout'; | import IconLogout from '@/components/icon/icon-logout'; | ||||||
| import { usePathname, useRouter } from 'next/navigation'; | import { usePathname, useRouter } from 'next/navigation'; | ||||||
| 
 | 
 | ||||||
| type UserData = { id: string; email: string; createdAt: string }; | type UserData = { id: string; email: string; is_active: boolean }; | ||||||
| 
 | 
 | ||||||
| export default function Header() { | export default function Header() { | ||||||
|   const pathname = usePathname(); |   const pathname = usePathname(); | ||||||
| @ -27,17 +26,16 @@ export default function Header() { | |||||||
|   const [user, setUser] = useState<UserData | null>(null); |   const [user, setUser] = useState<UserData | null>(null); | ||||||
|   const [loadingUser, setLoadingUser] = useState(true); |   const [loadingUser, setLoadingUser] = useState(true); | ||||||
| 
 | 
 | ||||||
|   // Highlight active menu (your original effect)
 |   const API = process.env.NEXT_PUBLIC_FASTAPI_URL || 'http://127.0.0.1:8000'; | ||||||
|  | 
 | ||||||
|  |   // highlight active menu
 | ||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
|     const selector = document.querySelector( |     const selector = document.querySelector( | ||||||
|       'ul.horizontal-menu a[href="' + window.location.pathname + '"]' |       `ul.horizontal-menu a[href="${window.location.pathname}"]` | ||||||
|     ); |     ); | ||||||
|     if (selector) { |     if (selector) { | ||||||
|       document |       document | ||||||
|         .querySelectorAll('ul.horizontal-menu .nav-link.active') |         .querySelectorAll('ul.horizontal-menu .nav-link.active, ul.horizontal-menu a.active') | ||||||
|         .forEach((el) => el.classList.remove('active')); |  | ||||||
|       document |  | ||||||
|         .querySelectorAll('ul.horizontal-menu a.active') |  | ||||||
|         .forEach((el) => el.classList.remove('active')); |         .forEach((el) => el.classList.remove('active')); | ||||||
|       selector.classList.add('active'); |       selector.classList.add('active'); | ||||||
|       const ul: any = selector.closest('ul.sub-menu'); |       const ul: any = selector.closest('ul.sub-menu'); | ||||||
| @ -48,16 +46,16 @@ export default function Header() { | |||||||
|     } |     } | ||||||
|   }, [pathname]); |   }, [pathname]); | ||||||
| 
 | 
 | ||||||
|   async function loadUser() { |   async function loadUser(signal?: AbortSignal) { | ||||||
|   try { |   try { | ||||||
|     const res = await fetch('/api/auth/me', { |     const res = await fetch(`${API}/auth/me`, { | ||||||
|       method: 'GET', |       credentials: 'include', | ||||||
|       credentials: 'include', // send cookie
 |       cache: 'no-store', | ||||||
|       cache: 'no-store',      // avoid stale cached responses
 |       signal, | ||||||
|     }); |     }); | ||||||
|     if (!res.ok) throw new Error(); |     if (!res.ok) throw new Error(); | ||||||
|     const data = await res.json(); |     const data = await res.json().catch(() => null); | ||||||
|     setUser(data.user); |     setUser(data?.id ? (data as UserData) : null); | ||||||
|   } catch { |   } catch { | ||||||
|     setUser(null); |     setUser(null); | ||||||
|   } finally { |   } finally { | ||||||
| @ -67,21 +65,31 @@ export default function Header() { | |||||||
| 
 | 
 | ||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
|     setLoadingUser(true); |     setLoadingUser(true); | ||||||
|   loadUser(); |     const controller = new AbortController(); | ||||||
|   // eslint-disable-next-line react-hooks/exhaustive-deps
 |     loadUser(controller.signal); | ||||||
| }, [pathname]); // re-fetch on route change (after login redirect)
 |     return () => controller.abort(); | ||||||
|  |   }, [pathname, API]); | ||||||
| 
 | 
 | ||||||
|   const handleLogout = async () => { |   const handleLogout = async () => { | ||||||
|     await fetch('/api/logout', { method: 'POST' }); |     try { | ||||||
|  |     await fetch(`${API}/auth/logout`, { | ||||||
|  |       method: 'POST', | ||||||
|  |       credentials: 'include', | ||||||
|  |       cache: 'no-store', | ||||||
|  |     }); | ||||||
|  |   } catch (_) { | ||||||
|  |     // ignore
 | ||||||
|  |   } finally { | ||||||
|     setUser(null); |     setUser(null); | ||||||
|     router.push('/login'); // go to login
 |     window.location.href = '/login'; | ||||||
|  |   } | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   return ( |   return ( | ||||||
|     <header className={`z-40 ${themeConfig.semidark && themeConfig.menu === 'horizontal' ? 'dark' : ''}`}> |     <header className={`z-40 ${themeConfig.semidark && themeConfig.menu === 'horizontal' ? 'dark' : ''}`}> | ||||||
|       <div className="shadow-sm"> |       <div className="shadow-sm"> | ||||||
|         <div className="relative flex w-full items-center bg-white px-5 py-2.5 dark:bg-rtgray-900"> |         <div className="relative flex w-full items-center bg-white px-5 py-2.5 dark:bg-rtgray-900"> | ||||||
|           {/* Logo */} |           {/* Logo + mobile toggler */} | ||||||
|           <div className="horizontal-logo flex items-center justify-between ltr:mr-2 rtl:ml-2 lg:hidden"> |           <div className="horizontal-logo flex items-center justify-between ltr:mr-2 rtl:ml-2 lg:hidden"> | ||||||
|             <div className="relative h-10 w-32 sm:h-11 sm:w-36 md:h-12 md:w-27 shrink-0 max-h-12"> |             <div className="relative h-10 w-32 sm:h-11 sm:w-36 md:h-12 md:w-27 shrink-0 max-h-12"> | ||||||
|               <Image |               <Image | ||||||
| @ -103,7 +111,6 @@ useEffect(() => { | |||||||
|             </button> |             </button> | ||||||
|           </div> |           </div> | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
|           {/* Right-side actions */} |           {/* Right-side actions */} | ||||||
|           <div className="flex items-center justify-end space-x-1.5 ltr:ml-auto rtl:mr-auto rtl:space-x-reverse dark:text-[#d0d2d6] lg:space-x-2"> |           <div className="flex items-center justify-end space-x-1.5 ltr:ml-auto rtl:mr-auto rtl:space-x-reverse dark:text-[#d0d2d6] lg:space-x-2"> | ||||||
|             {/* Theme toggle */} |             {/* Theme toggle */} | ||||||
| @ -131,14 +138,14 @@ useEffect(() => { | |||||||
|                 <Dropdown |                 <Dropdown | ||||||
|                   placement={isRtl ? 'bottom-start' : 'bottom-end'} |                   placement={isRtl ? 'bottom-start' : 'bottom-end'} | ||||||
|                   btnClassName="relative group block" |                   btnClassName="relative group block" | ||||||
|                 panelClassName="rounded-lg shadow-lg border border-white/10 bg-rtgray-100 dark:bg-rtgray-800 p-2" // ✅
 |                   panelClassName="rounded-lg shadow-lg border border-white/10 bg-rtgray-100 dark:bg-rtgray-800 p-2" | ||||||
|                   button={ |                   button={ | ||||||
|                     <div className="h-9 w-9 rounded-full bg-rtgray-200 dark:bg-rtgray-800 flex items-center justify-center group-hover:bg-rtgray-300 dark:group-hover:bg-rtgray-700"> |                     <div className="h-9 w-9 rounded-full bg-rtgray-200 dark:bg-rtgray-800 flex items-center justify-center group-hover:bg-rtgray-300 dark:group-hover:bg-rtgray-700"> | ||||||
|                       <IconUser className="h-5 w-5 text-gray-600 dark:text-gray-300" /> |                       <IconUser className="h-5 w-5 text-gray-600 dark:text-gray-300" /> | ||||||
|                     </div> |                     </div> | ||||||
|                   } |                   } | ||||||
|                 > |                 > | ||||||
|                 <ul className="w-[230px] font-semibold text-dark"> {/* make sure this stays transparent */} |                   <ul className="w-[230px] font-semibold text-dark"> | ||||||
|                     <li className="px-4 py-4 flex items-center"> |                     <li className="px-4 py-4 flex items-center"> | ||||||
|                       <div className="truncate ltr:pl-1.5 rtl:pr-4"> |                       <div className="truncate ltr:pl-1.5 rtl:pr-4"> | ||||||
|                         <h4 className="text-sm text-left">{user.email}</h4> |                         <h4 className="text-sm text-left">{user.email}</h4> | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user