Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions src/components/common/GlobalLoadingWrapper.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { useNavigation } from 'react-router-dom';
import { useSelector } from 'react-redux';
import Spinner from './Spinner';

const selectIsAnyLoading = (state) =>
state.auth.isLoading ||
state.campaigns.loading ||
state.donations.loading ||
state.dashboard.loading;

const GlobalLoadingWrapper = ({ children }) => {
const navigation = useNavigation();
const isReduxLoading = useSelector(selectIsAnyLoading);

const isLoading = navigation.state !== 'idle' || isReduxLoading;

return (
<>
{children}
{isLoading && (
<div
aria-busy="true"
className="fixed inset-0 z-50 flex items-center justify-center bg-white/60 backdrop-blur-sm"
>
<Spinner size="lg" className="text-accent" />
</div>
)}
</>
);
};

export default GlobalLoadingWrapper;
39 changes: 34 additions & 5 deletions src/pages/NotFound.jsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,40 @@
import { Link } from 'react-router-dom';
import { Link, useNavigate } from 'react-router-dom';

const NotFound = () => {
const navigate = useNavigate();

return (
<div style={{ textAlign: 'center', marginTop: '50px' }}>
<h1>404 - Page Not Found</h1>
<p>The page you are looking for does not exist.</p>
<Link to="/" style={{ color: 'blue', textDecoration: 'underline' }}>Go back to Home</Link>
<div className="min-h-screen bg-white flex flex-col items-center justify-center px-6 text-center animate-fade-in">
<div className="max-w-md w-full">
<p className="text-[9rem] font-bold leading-none text-accent select-none">
404
</p>

<h1 className="mt-2 text-2xl font-semibold text-primary">
Page Not Found
</h1>

<p className="mt-3 text-slate-500 text-sm leading-relaxed">
The page you&apos;re looking for doesn&apos;t exist or may have been moved.
Check the URL or head back home.
</p>

<div className="mt-8 flex flex-col sm:flex-row items-center justify-center gap-3">
<Link
to="/"
className="w-full sm:w-auto inline-flex items-center justify-center gap-2 bg-accent hover:bg-indigo-600 active:bg-indigo-700 text-white font-medium px-6 py-3 rounded-lg transition-colors duration-150"
>
Go Back Home
</Link>

<button
onClick={() => navigate(-1)}
className="w-full sm:w-auto inline-flex items-center justify-center gap-2 bg-white hover:bg-slate-50 active:bg-slate-100 text-primary font-medium px-6 py-3 rounded-lg border border-slate-200 hover:border-slate-300 transition-colors duration-150"
>
Go Back
</button>
</div>
</div>
</div>
);
};
Expand Down
64 changes: 38 additions & 26 deletions src/routes/AppRouter.jsx
Original file line number Diff line number Diff line change
@@ -1,41 +1,53 @@
import { lazy, Suspense } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';

// Layout
import MainLayout from '../components/layout/MainLayout';
import GlobalLoadingWrapper from '../components/common/GlobalLoadingWrapper';
import Spinner from '../components/common/Spinner';
import NotFound from '../pages/NotFound';
import ErrorTest from '../components/common/ErrorTest';

// Pages
import Home from '../pages/Home';
import Explore from '../pages/Explore';
// Auth pages — small, kept as static imports
import Login from '../pages/Login';
import Register from '../pages/Register';
import ForgotPasswordPage from '../pages/auth/ForgotPasswordPage';
import Dashboard from '../pages/Dashboard';
import CampaignDetails from '../pages/CampaignDetails';
import CreateCampaign from '../pages/CreateCampaign';
import Admin from '../pages/Admin';
import NotFound from '../pages/NotFound';

// Test Component
import ErrorTest from '../components/common/ErrorTest';
// Main pages — lazy-loaded for code splitting
const Home = lazy(() => import('../pages/Home'));
const Explore = lazy(() => import('../pages/Explore'));
const CampaignDetails = lazy(() => import('../pages/CampaignDetails'));
const CreateCampaign = lazy(() => import('../pages/CreateCampaign'));
const Dashboard = lazy(() => import('../pages/Dashboard'));
const Admin = lazy(() => import('../pages/Admin'));

const SuspenseFallback = () => (
<div className="flex min-h-screen items-center justify-center">
<Spinner size="lg" className="text-accent" />
</div>
);

const AppRouter = () => {
return (
<BrowserRouter>
<Routes>
<Route path="register" element={<Register />} />
<Route path="login" element={<Login />} />
<Route path="forgot-password" element={<ForgotPasswordPage />} />
<Route path="admin" element={<Admin />} />
<Route path="dashboard" element={<Dashboard />} />
<Route path="*" element={<NotFound />} />
<Route path="/" element={<MainLayout />}>
<Route index element={<Home />} />
<Route path="explore" element={<Explore />} />
<Route path="campaign/:id" element={<CampaignDetails />} />
<Route path="create" element={<CreateCampaign />} />
<Route path="test-error" element={<ErrorTest />} />
</Route>
</Routes>
<GlobalLoadingWrapper>
<Suspense fallback={<SuspenseFallback />}>
<Routes>
<Route path="register" element={<Register />} />
<Route path="login" element={<Login />} />
<Route path="forgot-password" element={<ForgotPasswordPage />} />
<Route path="admin" element={<Admin />} />
<Route path="dashboard" element={<Dashboard />} />
<Route path="*" element={<NotFound />} />
<Route path="/" element={<MainLayout />}>
<Route index element={<Home />} />
<Route path="explore" element={<Explore />} />
<Route path="campaign/:id" element={<CampaignDetails />} />
<Route path="create" element={<CreateCampaign />} />
<Route path="test-error" element={<ErrorTest />} />
</Route>
</Routes>
</Suspense>
</GlobalLoadingWrapper>
</BrowserRouter>
);
};
Expand Down