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
14 changes: 14 additions & 0 deletions src/components/layout/Navbar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useAppDispatch, useAppSelector } from '../../store/hooks';
import { selectCurrentUser } from '../../features/auth/authSelectors';
import { logoutUser } from '../../features/auth/authThunks';
import { toastSuccess } from '../../utils/toast';
import { useRole } from '../../hooks/useRole';

const NAV_LINKS = [
{ label: 'How it works', to: '/how-it-works' },
Expand All @@ -24,6 +25,7 @@ export default function Navbar() {
const dispatch = useAppDispatch();
const navigate = useNavigate();
const user = useAppSelector(selectCurrentUser);
const { isAdmin } = useRole();

const [menuOpen, setMenuOpen] = useState(false);
const [avatarOpen, setAvatarOpen] = useState(false);
Expand Down Expand Up @@ -365,6 +367,12 @@ export default function Navbar() {
<div className="sna-dropdown-name">{user.name}</div>
<div className="sna-dd-divider" />
<Link to="/dashboard" role="menuitem" onClick={() => setAvatarOpen(false)}>Dashboard</Link>
{isAdmin && (
<Link to="/admin" role="menuitem" onClick={() => setAvatarOpen(false)}>Admin Panel</Link>
)}
{!isAdmin && (
<Link to="/create" role="menuitem" onClick={() => setAvatarOpen(false)}>Create Campaign</Link>
)}
<Link to="/profile" role="menuitem" onClick={() => setAvatarOpen(false)}>Profile</Link>
<Link to="/settings" role="menuitem" onClick={() => setAvatarOpen(false)}>Settings</Link>
<div className="sna-dd-divider" />
Expand Down Expand Up @@ -417,6 +425,12 @@ export default function Navbar() {
{user ? (
<>
<Link to="/dashboard" onClick={handleNavClick}>Dashboard</Link>
{isAdmin && (
<Link to="/admin" onClick={handleNavClick}>Admin Panel</Link>
)}
{!isAdmin && (
<Link to="/create" onClick={handleNavClick}>Create Campaign</Link>
)}
<Link to="/profile" onClick={handleNavClick}>Profile</Link>
<Link to="/settings" onClick={handleNavClick}>Settings</Link>
<div className="sna-mobile-divider" />
Expand Down
21 changes: 21 additions & 0 deletions src/hooks/useRole.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { useAppSelector } from '../store/hooks';
import { selectCurrentUser } from '../features/auth/authSelectors';

/**
* Custom hook to determine user roles
* @returns {Object} { isAdmin: boolean, isUser: boolean }
*/
export const useRole = () => {
const user = useAppSelector(selectCurrentUser);

// Determine if user is admin based on role property
const isAdmin = user?.role === 'admin';

// Determine if user is a regular user (authenticated but not admin)
const isUser = !!user && user?.role !== 'admin';

return {
isAdmin,
isUser,
};
};
72 changes: 68 additions & 4 deletions src/pages/CampaignDetails.jsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,76 @@
import { useParams } from 'react-router-dom';
import { useParams, useNavigate } from 'react-router-dom';
import { useAppSelector } from '../store/hooks';
import { selectCurrentUser } from '../features/auth/authSelectors';
import { useState } from 'react';

const CampaignDetails = () => {
const { id } = useParams();
const navigate = useNavigate();
const user = useAppSelector(selectCurrentUser);

// TODO: Replace with actual API call to fetch campaign by id
// For now, using mock data structure
const [campaign] = useState({
id: id,
title: 'Sample Campaign',
description: 'This is a sample campaign description',
ownerId: null, // This should come from the API response
ownerName: 'Campaign Owner',
// ... other campaign fields
});

// Check if current user is the campaign owner
const isOwner = user && campaign?.ownerId === user.id;

const handleEdit = () => {
navigate(`/campaign/${id}/edit`);
};

const handleDelete = () => {
if (window.confirm('Are you sure you want to delete this campaign?')) {
// TODO: Implement delete campaign API call
console.log('Deleting campaign:', id);
// After successful deletion, redirect to explore page
// navigate('/explore');
}
};

return (
<div>
<h1>Campaign Details</h1>
<p>Viewing details for campaign ID: {id}</p>
<div className="max-w-4xl mx-auto px-4 py-8">
<div className="bg-white rounded-lg shadow-md p-6">
<div className="flex justify-between items-start mb-4">
<div>
<h1 className="text-3xl font-bold text-gray-900 mb-2">
{campaign.title}
</h1>
<p className="text-gray-600">Campaign ID: {id}</p>
</div>

{/* Edit and Delete buttons - only visible to campaign owner */}
{isOwner && (
<div className="flex gap-2">
<button
onClick={handleEdit}
className="bg-blue-600 hover:bg-blue-700 text-white font-medium px-4 py-2 rounded-lg transition-colors duration-200"
>
Edit Campaign
</button>
<button
onClick={handleDelete}
className="bg-red-600 hover:bg-red-700 text-white font-medium px-4 py-2 rounded-lg transition-colors duration-200"
>
Delete Campaign
</button>
</div>
)}
</div>

<div className="mt-6">
<p className="text-gray-700">{campaign.description}</p>
</div>

{/* TODO: Add more campaign details here */}
</div>
</div>
);
};
Expand Down
24 changes: 24 additions & 0 deletions src/routes/AdminRoute.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Navigate } from 'react-router-dom';
import { useRole } from '../hooks/useRole';

/**
* AdminRoute - A route wrapper that only allows admin users
* Redirects non-admin users to /dashboard
*
* @param {Object} props - Component props
* @param {React.ReactNode} props.children - The component to render if user is admin
* @returns {React.ReactElement} Either the children or a redirect
*/
const AdminRoute = ({ children }) => {
const { isAdmin } = useRole();

// If user is not an admin, redirect to dashboard
if (!isAdmin) {
return <Navigate to="/dashboard" replace />;
}

// User is admin, render the protected component
return children;
};

export default AdminRoute;
7 changes: 6 additions & 1 deletion src/routes/AppRouter.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import GlobalLoadingWrapper from '../components/common/GlobalLoadingWrapper';
import Spinner from '../components/common/Spinner';
import NotFound from '../pages/NotFound';
import ErrorTest from '../components/common/ErrorTest';
import AdminRoute from './AdminRoute';

// Auth pages
import Login from '../pages/Login';
Expand Down Expand Up @@ -51,7 +52,11 @@ const AppRouter = () => {

{/* App pages */}
<Route path="dashboard" element={<Dashboard />} />
<Route path="admin" element={<Admin />} />
<Route path="admin" element={
<AdminRoute>
<Admin />
</AdminRoute>
} />

{/* 404 */}
<Route path="*" element={<NotFound />} />
Expand Down