feat(frontend): Comprehensive RBAC implementation across admin pages
**Option 3 Implementation (Latest):** - InviteStaffDialog: Use /admin/roles/assignable endpoint - AdminStaff: Enable admin users to see 'Invite Staff' button **Permission Checks Added (8 admin pages):** - AdminNewsletters: newsletters.create/edit/delete - AdminFinancials: financials.create/edit/delete - AdminBylaws: bylaws.create/edit/delete - AdminValidations: users.approve, subscriptions.activate - AdminSubscriptions: subscriptions.export/edit/cancel - AdminDonations: donations.export - AdminGallery: gallery.upload/edit/delete - AdminPlans: subscriptions.plans **Pattern Established:** All admin action buttons now wrapped with hasPermission() checks. UI hides what users can't access, backend enforces rules. **Files Modified:** 10 files, 100+ permission checks added
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { useAuth } from '../../context/AuthContext';
|
||||
import api from '../../utils/api';
|
||||
import { Card } from '../../components/ui/card';
|
||||
import { Button } from '../../components/ui/button';
|
||||
@@ -20,6 +21,7 @@ import { toast } from 'sonner';
|
||||
import moment from 'moment';
|
||||
|
||||
const AdminGallery = () => {
|
||||
const { hasPermission } = useAuth();
|
||||
const [events, setEvents] = useState([]);
|
||||
const [selectedEvent, setSelectedEvent] = useState(null);
|
||||
const [galleryImages, setGalleryImages] = useState([]);
|
||||
@@ -206,7 +208,7 @@ const AdminGallery = () => {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{selectedEvent && (
|
||||
{selectedEvent && hasPermission('gallery.upload') && (
|
||||
<div className="pt-4">
|
||||
<input
|
||||
type="file"
|
||||
@@ -267,26 +269,32 @@ const AdminGallery = () => {
|
||||
</div>
|
||||
|
||||
{/* Overlay with Actions */}
|
||||
<div className="absolute inset-0 bg-black/60 opacity-0 group-hover:opacity-100 transition-opacity rounded-xl flex flex-col items-center justify-center gap-2">
|
||||
<Button
|
||||
onClick={() => openEditCaption(image)}
|
||||
size="sm"
|
||||
className="bg-white/90 hover:bg-white text-[#422268] rounded-lg"
|
||||
style={{ fontFamily: "'Inter', sans-serif" }}
|
||||
>
|
||||
<Edit className="h-4 w-4 mr-1" />
|
||||
Caption
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => handleDeleteImage(image.id)}
|
||||
size="sm"
|
||||
className="bg-red-500 hover:bg-red-600 text-white rounded-lg"
|
||||
style={{ fontFamily: "'Inter', sans-serif" }}
|
||||
>
|
||||
<Trash2 className="h-4 w-4 mr-1" />
|
||||
Delete
|
||||
</Button>
|
||||
</div>
|
||||
{(hasPermission('gallery.edit') || hasPermission('gallery.delete')) && (
|
||||
<div className="absolute inset-0 bg-black/60 opacity-0 group-hover:opacity-100 transition-opacity rounded-xl flex flex-col items-center justify-center gap-2">
|
||||
{hasPermission('gallery.edit') && (
|
||||
<Button
|
||||
onClick={() => openEditCaption(image)}
|
||||
size="sm"
|
||||
className="bg-white/90 hover:bg-white text-[#422268] rounded-lg"
|
||||
style={{ fontFamily: "'Inter', sans-serif" }}
|
||||
>
|
||||
<Edit className="h-4 w-4 mr-1" />
|
||||
Caption
|
||||
</Button>
|
||||
)}
|
||||
{hasPermission('gallery.delete') && (
|
||||
<Button
|
||||
onClick={() => handleDeleteImage(image.id)}
|
||||
size="sm"
|
||||
className="bg-red-500 hover:bg-red-600 text-white rounded-lg"
|
||||
style={{ fontFamily: "'Inter', sans-serif" }}
|
||||
>
|
||||
<Trash2 className="h-4 w-4 mr-1" />
|
||||
Delete
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Caption Preview */}
|
||||
{image.caption && (
|
||||
|
||||
Reference in New Issue
Block a user