From 99e232beea6bce564c806dc9327cf0ce6f1af74e Mon Sep 17 00:00:00 2001 From: Prakhar Trivedi Date: Fri, 11 Jul 2025 00:01:41 +0800 Subject: [PATCH 1/5] restructured layouts, added ProtectedLayout and CentredSpinner --- src/DefaultLayout.jsx | 14 +++++++++ src/ProtectedLayout.jsx | 49 ++++++++++++++++++++++++++++++ src/{Layout.jsx => RootLayout.jsx} | 7 ++--- src/components/CentredSpinner.jsx | 18 +++++++++++ src/components/Navbar.jsx | 8 ++++- src/main.jsx | 30 +++++++++++------- src/networking.js | 6 +++- src/pages/Login.jsx | 10 +++++- src/pages/SampleProtected.jsx | 2 -- 9 files changed, 124 insertions(+), 20 deletions(-) create mode 100644 src/DefaultLayout.jsx create mode 100644 src/ProtectedLayout.jsx rename src/{Layout.jsx => RootLayout.jsx} (80%) create mode 100644 src/components/CentredSpinner.jsx diff --git a/src/DefaultLayout.jsx b/src/DefaultLayout.jsx new file mode 100644 index 0000000..3c3fb6b --- /dev/null +++ b/src/DefaultLayout.jsx @@ -0,0 +1,14 @@ +import React from 'react' +import { Outlet } from 'react-router-dom' +import Navbar from './components/Navbar' + +function DefaultLayout() { + return ( +
+ + +
+ ) +} + +export default DefaultLayout \ No newline at end of file diff --git a/src/ProtectedLayout.jsx b/src/ProtectedLayout.jsx new file mode 100644 index 0000000..6335bfd --- /dev/null +++ b/src/ProtectedLayout.jsx @@ -0,0 +1,49 @@ +import React, { useEffect } from 'react' +import { useSelector } from 'react-redux' +import ToastWizard from './components/toastWizard'; +import { Outlet, useNavigate } from 'react-router-dom'; +import { AnimatePresence, motion } from 'framer-motion'; +import { Box } from '@chakra-ui/react'; +import CentredSpinner from './components/CentredSpinner'; + +const MotionBox = motion.create(Box); + +function ProtectedLayout() { + const navigate = useNavigate(); + const { username, loaded, disableSessionCheck, error } = useSelector(state => state.auth); + + useEffect(() => { + if (loaded && !username && !disableSessionCheck) { + console.log("Unauthorised access to protected route. Redirecting to login."); + navigate("/auth/login"); + + ToastWizard.standard("error", "Please sign in first.") + } + }, [username, loaded]) + + return ( + + {!loaded ? ( + + + + ) : ( + + + + )} + + ) +} + +export default ProtectedLayout \ No newline at end of file diff --git a/src/Layout.jsx b/src/RootLayout.jsx similarity index 80% rename from src/Layout.jsx rename to src/RootLayout.jsx index 334f41f..6128fe5 100644 --- a/src/Layout.jsx +++ b/src/RootLayout.jsx @@ -3,21 +3,20 @@ import { useDispatch } from 'react-redux' import { Outlet, useNavigate } from 'react-router'; import Navbar from './components/Navbar'; import { Toaster } from './components/ui/toaster'; +import { fetchSession } from './slices/AuthState'; function Layout() { const dispatch = useDispatch(); const navigate = useNavigate(); useEffect(() => { + dispatch(fetchSession()); console.log("Root layout mounted."); }, []) return ( <> -
- - -
+ ) diff --git a/src/components/CentredSpinner.jsx b/src/components/CentredSpinner.jsx new file mode 100644 index 0000000..64e0165 --- /dev/null +++ b/src/components/CentredSpinner.jsx @@ -0,0 +1,18 @@ +import { Center, Spinner } from '@chakra-ui/react' +import { useEffect, useState } from 'react' + +function CentredSpinner() { + const [color, setColor] = useState('primaryColour'); + + useEffect(() => { + const timer = setInterval(() => { + setColor(prevColor => prevColor === 'primaryColour' ? 'sccciColour' : 'primaryColour'); + }, 500); + + return () => clearInterval(timer); + }, []) + + return
+} + +export default CentredSpinner \ No newline at end of file diff --git a/src/components/Navbar.jsx b/src/components/Navbar.jsx index fa5b3dd..a8640e6 100644 --- a/src/components/Navbar.jsx +++ b/src/components/Navbar.jsx @@ -3,10 +3,16 @@ import Sidebar from './Sidebar' import { Avatar, Box, Button, Flex, HStack, Icon, Spacer, Text } from '@chakra-ui/react' import { FaHamburger } from 'react-icons/fa' import { FaBarsStaggered } from 'react-icons/fa6' +import { useNavigate } from 'react-router-dom' function Navbar() { + const navigate = useNavigate(); const [isOpen, setIsOpen] = useState(false); + const handleLogoClick = () => { + navigate('/'); + } + const toggleSidebar = () => { setIsOpen(!isOpen); } @@ -15,7 +21,7 @@ function Navbar() { - ArchAIve + ArchAIve diff --git a/src/main.jsx b/src/main.jsx index 28bc32f..ce3d7ea 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -9,12 +9,15 @@ import authReducer from './slices/AuthState.js' import { BrowserRouter, Route, Routes } from 'react-router' import Catalogue from './pages/Catalogue.jsx' // import Test from './pages/test.jsx' -import Layout from './Layout.jsx' +import RootLayout from './RootLayout.jsx' import GalleryLayout from './GalleryLayout.jsx'; import MainTheme from './themes/MainTheme.js' import Health from './pages/Health.jsx' import Homepage from './pages/Homepage.jsx' import Login from './pages/Login.jsx'; +import DefaultLayout from './DefaultLayout.jsx'; +import SampleProtected from './pages/SampleProtected.jsx'; +import ProtectedLayout from './ProtectedLayout.jsx'; const store = configureStore({ reducer: { @@ -28,19 +31,24 @@ createRoot(document.getElementById('root')).render( - }> - } /> - } /> + }> + }> + } /> - - } /> - + } /> - {/* } /> */} - + + } /> + - }> - } /> + }> + } /> + + + + }> + } /> + diff --git a/src/networking.js b/src/networking.js index 5d3617e..9225523 100644 --- a/src/networking.js +++ b/src/networking.js @@ -42,8 +42,12 @@ const instance = axios.create({ }) instance.interceptors.request.use((config) => { - config.headers["Content-Type"] = "application/json"; + if (config.method == 'post') { + config.headers["Content-Type"] = "application/json"; + } + config.headers["APIKey"] = import.meta.env.VITE_BACKEND_API_KEY; + config.withCredentials = true; return config; }, (err) => { diff --git a/src/pages/Login.jsx b/src/pages/Login.jsx index 4c0e424..ed6e5e1 100644 --- a/src/pages/Login.jsx +++ b/src/pages/Login.jsx @@ -4,8 +4,14 @@ import { useState } from 'react' import sccciBuildingImage from '../assets/sccciBuildingOpening1964.png' import server, { JSONResponse } from '../networking' import ToastWizard from '../components/toastWizard' +import { useDispatch } from 'react-redux' +import { useNavigate } from 'react-router-dom' +import { fetchSession } from '../slices/AuthState' function Login() { + const dispatch = useDispatch(); + const navigate = useNavigate(); + const [usernameOrEmail, setUsernameOrEmail] = useState(''); const [password, setPassword] = useState(''); const [loginLoading, setLoginLoading] = useState(false); @@ -38,6 +44,8 @@ function Login() { } // Success case + dispatch(fetchSession()); + navigate("/sampleProtected"); ToastWizard.standard("success", "Login successful.", "Welcome back to ArchAIve!") } else { console.log("Unexpected response in login request:", res.data); @@ -47,7 +55,7 @@ function Login() { setLoginLoading(false); }) .catch(err => { - if (err.response.data instanceof JSONResponse) { + if (err.response && err.response.data instanceof JSONResponse) { console.log("Error response in login request:", err.response.data.fullMessage()); if (err.response.data.userErrorType()) { ToastWizard.standard("error", "Login failed.", err.response.data.message); diff --git a/src/pages/SampleProtected.jsx b/src/pages/SampleProtected.jsx index 64c4f14..9a0eaab 100644 --- a/src/pages/SampleProtected.jsx +++ b/src/pages/SampleProtected.jsx @@ -1,5 +1,3 @@ -import React from 'react' - function SampleProtected() { return (
SampleProtected
From 6a3f2d81e618f602bc69341819a5b20f92b09a4b Mon Sep 17 00:00:00 2001 From: Prakhar Trivedi Date: Fri, 11 Jul 2025 10:19:12 +0800 Subject: [PATCH 2/5] added logout button in sample protected page catalogue is now a protected page --- src/components/Navbar.jsx | 6 ++++- src/components/toastWizard.js | 10 ++++----- src/components/ui/toaster.jsx | 4 ++-- src/main.jsx | 4 +++- src/pages/SampleProtected.jsx | 42 ++++++++++++++++++++++++++++++++++- src/slices/AuthState.js | 2 +- 6 files changed, 57 insertions(+), 11 deletions(-) diff --git a/src/components/Navbar.jsx b/src/components/Navbar.jsx index a8640e6..6b99dea 100644 --- a/src/components/Navbar.jsx +++ b/src/components/Navbar.jsx @@ -13,6 +13,10 @@ function Navbar() { navigate('/'); } + const handleProfileClick = () => { + navigate('/sampleProtected'); + } + const toggleSidebar = () => { setIsOpen(!isOpen); } @@ -23,7 +27,7 @@ function Navbar() { ArchAIve - +
diff --git a/src/components/toastWizard.js b/src/components/toastWizard.js index 2e12ebf..e40c502 100644 --- a/src/components/toastWizard.js +++ b/src/components/toastWizard.js @@ -72,20 +72,20 @@ class ToastWizard { return id; } - static promiseBased(promise, successTitle, successDescription, errorTitle, errorDescription, loadingTitle, loadingDescription, action=null, actionLabel=null) { + static promiseBased(promise, successTitle, successDescription, errorTitle, errorDescription, loadingTitle, loadingDescription, action=null, actionLabel=null, actionForTypes=[]) { return toaster.promise(promise, { success: { title: successTitle, description: successDescription, - action: action ? { + action: action && actionLabel && actionForTypes.indexOf('success') > -1 ? { label: actionLabel, onClick: action - } : null + }: null }, error: { title: errorTitle, description: errorDescription, - action: action ? { + action: action && actionLabel && actionForTypes.indexOf('error') > -1 ? { label: actionLabel, onClick: action } : null @@ -93,7 +93,7 @@ class ToastWizard { loading: { title: loadingTitle, description: loadingDescription, - action: action ? { + action: action && actionLabel && actionForTypes.indexOf('loading') > -1 ? { label: actionLabel, onClick: action } : null diff --git a/src/components/ui/toaster.jsx b/src/components/ui/toaster.jsx index 4e66227..f9aec7c 100644 --- a/src/components/ui/toaster.jsx +++ b/src/components/ui/toaster.jsx @@ -19,8 +19,8 @@ const toastColors = { error: 'sccciColour', info: 'blue.500', warning: 'orange.500', - loading: 'gray.500', - default: 'gray.700', + loading: 'white', + default: 'white', }; diff --git a/src/main.jsx b/src/main.jsx index ce3d7ea..f4d9d89 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -47,7 +47,9 @@ createRoot(document.getElementById('root')).render( }> - } /> + }> + } /> + diff --git a/src/pages/SampleProtected.jsx b/src/pages/SampleProtected.jsx index 9a0eaab..03a67a4 100644 --- a/src/pages/SampleProtected.jsx +++ b/src/pages/SampleProtected.jsx @@ -1,6 +1,46 @@ +import { Button, Text } from "@chakra-ui/react" +import { useDispatch } from "react-redux" +import { logout } from "../slices/AuthState"; +import ToastWizard from "../components/toastWizard"; +import { useNavigate } from "react-router-dom"; + function SampleProtected() { + const dispatch = useDispatch(); + const navigate = useNavigate(); + + const handleLogout = () => { + // ToastWizard.standard("success", "Logout successful.", "See you soon!"); + + const logoutPromise = new Promise((resolve, reject) => { + dispatch(logout(true, (msg) => { + if (typeof msg === 'string') { + reject(msg); + } else { + navigate('/auth/login'); + resolve(msg); + } + })); + }) + + ToastWizard.promiseBased( + logoutPromise, + "Logged out successfully.", + "You have been logged out.", + "Logout failed.", + "Something went wrong while logging out. Please try again.", + "Logging out...", + "Connecting to server...", + handleLogout, + "Retry", + ['error'] + ) + } + return ( -
SampleProtected
+
+ SampleProtected + +
) } diff --git a/src/slices/AuthState.js b/src/slices/AuthState.js index 9c4daea..42a30cc 100644 --- a/src/slices/AuthState.js +++ b/src/slices/AuthState.js @@ -126,7 +126,7 @@ export const logout = (disableSessionCheck=false, handler=null) => async (dispat } else if (typeof err === 'string') { errorMessage = err; } - console.log("Error in logout:", errorMessage); + console.log("ASLOGOUT ERROR:", errorMessage); if (handler) { handler(errorMessage); } From 4796ae7b97135a573585a0c2922e7b365f78d512 Mon Sep 17 00:00:00 2001 From: Prakhar Trivedi Date: Fri, 11 Jul 2025 10:57:57 +0800 Subject: [PATCH 3/5] added AnimateIn wrapper for smooth transitions --- src/AnimateIn.jsx | 21 +++++++++++++++++++++ src/ProtectedLayout.jsx | 7 +++++-- src/components/CentredSpinner.jsx | 2 +- src/main.jsx | 11 +++++++---- 4 files changed, 34 insertions(+), 7 deletions(-) create mode 100644 src/AnimateIn.jsx diff --git a/src/AnimateIn.jsx b/src/AnimateIn.jsx new file mode 100644 index 0000000..b6b9e98 --- /dev/null +++ b/src/AnimateIn.jsx @@ -0,0 +1,21 @@ +import { Box } from '@chakra-ui/react'; +import { motion } from 'framer-motion'; +import React from 'react' +import { Outlet } from 'react-router-dom'; + +const MotionBox = motion.create(Box); + +function AnimateIn() { + return ( + + + + ) +} + +export default AnimateIn \ No newline at end of file diff --git a/src/ProtectedLayout.jsx b/src/ProtectedLayout.jsx index 6335bfd..624c616 100644 --- a/src/ProtectedLayout.jsx +++ b/src/ProtectedLayout.jsx @@ -1,14 +1,16 @@ import React, { useEffect } from 'react' -import { useSelector } from 'react-redux' +import { useDispatch, useSelector } from 'react-redux' import ToastWizard from './components/toastWizard'; import { Outlet, useNavigate } from 'react-router-dom'; import { AnimatePresence, motion } from 'framer-motion'; import { Box } from '@chakra-ui/react'; import CentredSpinner from './components/CentredSpinner'; +import { setDisableSessionCheck } from './slices/AuthState'; const MotionBox = motion.create(Box); function ProtectedLayout() { + const dispatch = useDispatch(); const navigate = useNavigate(); const { username, loaded, disableSessionCheck, error } = useSelector(state => state.auth); @@ -16,8 +18,9 @@ function ProtectedLayout() { if (loaded && !username && !disableSessionCheck) { console.log("Unauthorised access to protected route. Redirecting to login."); navigate("/auth/login"); - ToastWizard.standard("error", "Please sign in first.") + } else if (disableSessionCheck) { + dispatch(setDisableSessionCheck(false)); } }, [username, loaded]) diff --git a/src/components/CentredSpinner.jsx b/src/components/CentredSpinner.jsx index 64e0165..33b5966 100644 --- a/src/components/CentredSpinner.jsx +++ b/src/components/CentredSpinner.jsx @@ -12,7 +12,7 @@ function CentredSpinner() { return () => clearInterval(timer); }, []) - return
+ return
} export default CentredSpinner \ No newline at end of file diff --git a/src/main.jsx b/src/main.jsx index f4d9d89..5951971 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -18,6 +18,7 @@ import Login from './pages/Login.jsx'; import DefaultLayout from './DefaultLayout.jsx'; import SampleProtected from './pages/SampleProtected.jsx'; import ProtectedLayout from './ProtectedLayout.jsx'; +import AnimateIn from './AnimateIn.jsx'; const store = configureStore({ reducer: { @@ -33,12 +34,14 @@ createRoot(document.getElementById('root')).render( }> }> - } /> + }> + } /> - } /> + } /> - - } /> + + } /> + }> From cf338faf29ab2d79fd8b1bf5323e3ad8331e33e7 Mon Sep 17 00:00:00 2001 From: Prakhar Trivedi Date: Fri, 11 Jul 2025 11:09:18 +0800 Subject: [PATCH 4/5] navbar avatar now responds to current username --- src/components/Navbar.jsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/Navbar.jsx b/src/components/Navbar.jsx index 6b99dea..b5a8314 100644 --- a/src/components/Navbar.jsx +++ b/src/components/Navbar.jsx @@ -4,10 +4,12 @@ import { Avatar, Box, Button, Flex, HStack, Icon, Spacer, Text } from '@chakra-u import { FaHamburger } from 'react-icons/fa' import { FaBarsStaggered } from 'react-icons/fa6' import { useNavigate } from 'react-router-dom' +import { useSelector } from 'react-redux' function Navbar() { const navigate = useNavigate(); const [isOpen, setIsOpen] = useState(false); + const { username } = useSelector(state => state.auth); const handleLogoClick = () => { navigate('/'); @@ -28,7 +30,7 @@ function Navbar() { ArchAIve - + setIsOpen(e.open)} /> From 995d045a5d036ad31f9b9fbb2c19bf59fe92c242 Mon Sep 17 00:00:00 2001 From: Prakhar Trivedi Date: Fri, 11 Jul 2025 11:14:34 +0800 Subject: [PATCH 5/5] added some documentation and commentsg --- src/components/toastWizard.js | 1 + src/main.jsx | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/src/components/toastWizard.js b/src/components/toastWizard.js index e40c502..67cdae1 100644 --- a/src/components/toastWizard.js +++ b/src/components/toastWizard.js @@ -39,6 +39,7 @@ import { toaster } from "./ui/toaster"; * "Please wait while we load your data", // loadingDescription * callback, // action (optional) * "Cancel" // actionLabel. provide if action is being provided. (optional) + * ['success', 'error', 'loading'] // actionForTypes, i.e which states should render the action button. provide if action is being provided. (optional) * ) * ``` * diff --git a/src/main.jsx b/src/main.jsx index 5951971..32e7b94 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -33,6 +33,7 @@ createRoot(document.getElementById('root')).render( }> + {/* Default Layout Pages */} }> }> } /> @@ -44,11 +45,15 @@ createRoot(document.getElementById('root')).render( + {/* Protected Pages */} }> } /> + {/* Pages needing a custom layout */} + + {/* Gallery Layout Pages */} }> }> } />