Compare commits
4 Commits
templates
...
24519a7080
| Author | SHA1 | Date | |
|---|---|---|---|
| 24519a7080 | |||
| b1b9a05d4f | |||
| a2070b4e4e | |||
| 6a21d32319 |
@@ -1,3 +0,0 @@
|
|||||||
REACT_APP_BACKEND_URL=http://localhost:8000
|
|
||||||
REACT_APP_BASENAME=/membership
|
|
||||||
PUBLIC_URL=/membership
|
|
||||||
@@ -22,7 +22,7 @@ import {
|
|||||||
Scale,
|
Scale,
|
||||||
HardDrive,
|
HardDrive,
|
||||||
Repeat,
|
Repeat,
|
||||||
Heart,
|
Heart
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
|
|
||||||
const AdminSidebar = ({ isOpen, onToggle, isMobile }) => {
|
const AdminSidebar = ({ isOpen, onToggle, isMobile }) => {
|
||||||
@@ -269,7 +269,8 @@ const AdminSidebar = ({ isOpen, onToggle, isMobile }) => {
|
|||||||
<img
|
<img
|
||||||
src={`${process.env.PUBLIC_URL}/loaf-logo.png`}
|
src={`${process.env.PUBLIC_URL}/loaf-logo.png`}
|
||||||
alt="LOAF Logo"
|
alt="LOAF Logo"
|
||||||
className={`object-contain transition-all duration-200 ${isOpen ? 'h-10 w-10' : 'h-8 w-8'
|
className={`object-contain transition-all duration-200 ${
|
||||||
|
isOpen ? 'h-10 w-10' : 'h-8 w-8'
|
||||||
}`}
|
}`}
|
||||||
/>
|
/>
|
||||||
{isOpen && (
|
{isOpen && (
|
||||||
@@ -299,7 +300,7 @@ const AdminSidebar = ({ isOpen, onToggle, isMobile }) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Navigation */}
|
{/* Navigation */}
|
||||||
<nav className="flex-1 overflow-y-auto p-4 scrollbar-dashboard scrollbar-x-dashboard">
|
<nav className="flex-1 overflow-y-auto p-4">
|
||||||
{/* Dashboard - Standalone */}
|
{/* Dashboard - Standalone */}
|
||||||
{renderNavItem(filteredNavItems.find(item => item.name === 'Dashboard'))}
|
{renderNavItem(filteredNavItems.find(item => item.name === 'Dashboard'))}
|
||||||
|
|
||||||
@@ -369,7 +370,7 @@ const AdminSidebar = ({ isOpen, onToggle, isMobile }) => {
|
|||||||
{/* User Section */}
|
{/* User Section */}
|
||||||
<div className="border-t border-[#ddd8eb] p-4 space-y-2">
|
<div className="border-t border-[#ddd8eb] p-4 space-y-2">
|
||||||
{isOpen && user && (
|
{isOpen && user && (
|
||||||
<div className="px-4 py-3 mb-2 flex justify-between items-center">
|
<div className="px-4 py-3 mb-2">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="h-10 w-10 rounded-full bg-[#DDD8EB] flex items-center justify-center text-[#422268] font-semibold">
|
<div className="h-10 w-10 rounded-full bg-[#DDD8EB] flex items-center justify-center text-[#422268] font-semibold">
|
||||||
{user.first_name?.[0]}{user.last_name?.[0]}
|
{user.first_name?.[0]}{user.last_name?.[0]}
|
||||||
@@ -383,8 +384,6 @@ const AdminSidebar = ({ isOpen, onToggle, isMobile }) => {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Link to='/profile'><Settings size={16} />
|
|
||||||
</Link>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -398,7 +397,8 @@ const AdminSidebar = ({ isOpen, onToggle, isMobile }) => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="w-full bg-[#ddd8eb] rounded-full h-2">
|
<div className="w-full bg-[#ddd8eb] rounded-full h-2">
|
||||||
<div
|
<div
|
||||||
className={`h-2 rounded-full transition-all ${storagePercentage > 90 ? 'bg-red-500' :
|
className={`h-2 rounded-full transition-all ${
|
||||||
|
storagePercentage > 90 ? 'bg-red-500' :
|
||||||
storagePercentage > 75 ? 'bg-yellow-500' :
|
storagePercentage > 75 ? 'bg-yellow-500' :
|
||||||
'bg-[#81B29A]'
|
'bg-[#81B29A]'
|
||||||
}`}
|
}`}
|
||||||
@@ -412,7 +412,8 @@ const AdminSidebar = ({ isOpen, onToggle, isMobile }) => {
|
|||||||
) : (
|
) : (
|
||||||
<div className="flex justify-center">
|
<div className="flex justify-center">
|
||||||
<div className="relative group">
|
<div className="relative group">
|
||||||
<HardDrive className={`h-5 w-5 ${storagePercentage > 90 ? 'text-red-500' :
|
<HardDrive className={`h-5 w-5 ${
|
||||||
|
storagePercentage > 90 ? 'text-red-500' :
|
||||||
storagePercentage > 75 ? 'text-yellow-500' :
|
storagePercentage > 75 ? 'text-yellow-500' :
|
||||||
'text-[#664fa3]'
|
'text-[#664fa3]'
|
||||||
}`} />
|
}`} />
|
||||||
|
|||||||
@@ -40,14 +40,15 @@ const InviteStaffDialog = ({ open, onOpenChange, onSuccess }) => {
|
|||||||
const fetchRoles = async () => {
|
const fetchRoles = async () => {
|
||||||
setLoadingRoles(true);
|
setLoadingRoles(true);
|
||||||
try {
|
try {
|
||||||
// New endpoint returns roles based on user's permission level
|
const response = await api.get('/admin/roles');
|
||||||
// Superadmin: all roles
|
// Filter to show only admin-type roles (not guest or member)
|
||||||
// Admin: admin, finance, and non-elevated custom roles
|
const staffRoles = response.data.filter(role =>
|
||||||
const response = await api.get('/admin/roles/assignable');
|
['admin', 'superadmin', 'finance'].includes(role.code) || !role.is_system_role
|
||||||
setRoles(response.data);
|
);
|
||||||
|
setRoles(staffRoles);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to fetch assignable roles:', error);
|
console.error('Failed to fetch roles:', error);
|
||||||
toast.error('Failed to load roles. Please try again.');
|
toast.error('Failed to load roles');
|
||||||
} finally {
|
} finally {
|
||||||
setLoadingRoles(false);
|
setLoadingRoles(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ const RegistrationStep4 = ({ formData, handleInputChange }) => {
|
|||||||
<label htmlFor="accepts_tos" className="text-sm text-gray-700" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
<label htmlFor="accepts_tos" className="text-sm text-gray-700" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||||
I agree to the{' '}
|
I agree to the{' '}
|
||||||
<a
|
<a
|
||||||
href="/become-a-member/terms-of-service"
|
href="/membership/terms-of-service"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="text-[#664fa3] hover:text-[#422268] font-semibold underline"
|
className="text-[#664fa3] hover:text-[#422268] font-semibold underline"
|
||||||
@@ -95,7 +95,7 @@ const RegistrationStep4 = ({ formData, handleInputChange }) => {
|
|||||||
</a>
|
</a>
|
||||||
{' '}and{' '}
|
{' '}and{' '}
|
||||||
<a
|
<a
|
||||||
href="become-a-member/privacy-policy"
|
href="/membership/privacy-policy"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="text-[#664fa3] hover:text-[#422268] font-semibold underline"
|
className="text-[#664fa3] hover:text-[#422268] font-semibold underline"
|
||||||
|
|||||||
@@ -1,33 +1,31 @@
|
|||||||
import * as React from "react";
|
import * as React from "react"
|
||||||
import * as TabsPrimitive from "@radix-ui/react-tabs";
|
import * as TabsPrimitive from "@radix-ui/react-tabs"
|
||||||
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
const Tabs = TabsPrimitive.Root;
|
const Tabs = TabsPrimitive.Root
|
||||||
|
|
||||||
const TabsList = React.forwardRef(({ className, ...props }, ref) => (
|
const TabsList = React.forwardRef(({ className, ...props }, ref) => (
|
||||||
<TabsPrimitive.List
|
<TabsPrimitive.List
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"inline-flex h-full items-center justify-center rounded-lg gap-6 p-1 text-muted-foreground",
|
"inline-flex h-9 items-center justify-center rounded-lg bg-muted p-1 text-muted-foreground",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props} />
|
||||||
/>
|
))
|
||||||
));
|
TabsList.displayName = TabsPrimitive.List.displayName
|
||||||
TabsList.displayName = TabsPrimitive.List.displayName;
|
|
||||||
|
|
||||||
const TabsTrigger = React.forwardRef(({ className, ...props }, ref) => (
|
const TabsTrigger = React.forwardRef(({ className, ...props }, ref) => (
|
||||||
<TabsPrimitive.Trigger
|
<TabsPrimitive.Trigger
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"inline-flex items-center justify-center whitespace-nowrap hover:bg-[#f1eef9] border-2 border-[#664fa3] rounded-2xl px-3 py-1 text-[#664fa3] text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-foreground data-[state=active]:text-background data-[state=active]:border-foreground data-[state=active]:shadow",
|
"inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props} />
|
||||||
/>
|
))
|
||||||
));
|
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
|
||||||
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
|
|
||||||
|
|
||||||
const TabsContent = React.forwardRef(({ className, ...props }, ref) => (
|
const TabsContent = React.forwardRef(({ className, ...props }, ref) => (
|
||||||
<TabsPrimitive.Content
|
<TabsPrimitive.Content
|
||||||
@@ -36,9 +34,8 @@ const TabsContent = React.forwardRef(({ className, ...props }, ref) => (
|
|||||||
"mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
"mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props} />
|
||||||
/>
|
))
|
||||||
));
|
TabsContent.displayName = TabsPrimitive.Content.displayName
|
||||||
TabsContent.displayName = TabsPrimitive.Content.displayName;
|
|
||||||
|
|
||||||
export { Tabs, TabsList, TabsTrigger, TabsContent };
|
export { Tabs, TabsList, TabsTrigger, TabsContent }
|
||||||
|
|||||||
@@ -116,27 +116,3 @@ code {
|
|||||||
border-bottom-color: inherit;
|
border-bottom-color: inherit;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@layer utilities {
|
|
||||||
@supports selector(::-webkit-scrollbar) {
|
|
||||||
.scrollbar-dashboard::-webkit-scrollbar {
|
|
||||||
width: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.scrollbar-dashboard::-webkit-scrollbar-thumb {
|
|
||||||
background-color: #ddd8eb;
|
|
||||||
border-radius: 9999px;
|
|
||||||
}
|
|
||||||
.scrollbar-x-dashboard::-webkit-scrollbar:horizontal {
|
|
||||||
height: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.scrollbar-x-dashboard::-webkit-scrollbar-thumb:horizontal {
|
|
||||||
background-color: #ddd8eb;
|
|
||||||
border-radius: 9999px;
|
|
||||||
}
|
|
||||||
.hide-scrollbar-x::-webkit-scrollbar:horizontal {
|
|
||||||
height: 0px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -12,10 +12,10 @@ const BoardOfDirectors = () => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
const boardMembers = [
|
const boardMembers = [
|
||||||
{ name: 'Danita Cole', title: 'Director' },
|
{ name: 'Danita Cole' },
|
||||||
{ name: 'Roxanne Cherico', title: 'Director' },
|
{ name: 'Roxanne Cherico' },
|
||||||
{ name: 'Lucretia Copeland', title: 'Director' },
|
{ name: 'Lucretia Copeland' },
|
||||||
{ name: 'Julie Fischer', title: 'Director' }
|
{ name: 'Julie Fischer' }
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import React from 'react';
|
|||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import PublicNavbar from '../components/PublicNavbar';
|
import PublicNavbar from '../components/PublicNavbar';
|
||||||
import PublicFooter from '../components/PublicFooter';
|
import PublicFooter from '../components/PublicFooter';
|
||||||
|
import ReactMarkdown from "react-markdown";
|
||||||
|
import remarkGfm from "remark-gfm";
|
||||||
|
|
||||||
|
|
||||||
export default function TermsOfService() {
|
export default function TermsOfService() {
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { useAuth } from '../../context/AuthContext';
|
|
||||||
import api from '../../utils/api';
|
import api from '../../utils/api';
|
||||||
import { Card } from '../../components/ui/card';
|
import { Card } from '../../components/ui/card';
|
||||||
import { Button } from '../../components/ui/button';
|
import { Button } from '../../components/ui/button';
|
||||||
@@ -33,7 +32,6 @@ import {
|
|||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
|
|
||||||
const AdminBylaws = () => {
|
const AdminBylaws = () => {
|
||||||
const { hasPermission } = useAuth();
|
|
||||||
const [bylaws, setBylaws] = useState([]);
|
const [bylaws, setBylaws] = useState([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [dialogOpen, setDialogOpen] = useState(false);
|
const [dialogOpen, setDialogOpen] = useState(false);
|
||||||
@@ -186,7 +184,6 @@ const AdminBylaws = () => {
|
|||||||
Manage LOAF governing bylaws and version history
|
Manage LOAF governing bylaws and version history
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{hasPermission('bylaws.create') && (
|
|
||||||
<Button
|
<Button
|
||||||
onClick={handleCreate}
|
onClick={handleCreate}
|
||||||
className="bg-[#664fa3] text-white hover:bg-[#533a82] rounded-full flex items-center gap-2"
|
className="bg-[#664fa3] text-white hover:bg-[#533a82] rounded-full flex items-center gap-2"
|
||||||
@@ -194,7 +191,6 @@ const AdminBylaws = () => {
|
|||||||
<Plus className="h-4 w-4" />
|
<Plus className="h-4 w-4" />
|
||||||
Add Version
|
Add Version
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Current Bylaws */}
|
{/* Current Bylaws */}
|
||||||
@@ -230,7 +226,6 @@ const AdminBylaws = () => {
|
|||||||
<ExternalLink className="h-4 w-4 mr-1" />
|
<ExternalLink className="h-4 w-4 mr-1" />
|
||||||
View
|
View
|
||||||
</Button>
|
</Button>
|
||||||
{hasPermission('bylaws.edit') && (
|
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
@@ -239,8 +234,6 @@ const AdminBylaws = () => {
|
|||||||
>
|
>
|
||||||
<Edit className="h-4 w-4" />
|
<Edit className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
|
||||||
{hasPermission('bylaws.delete') && (
|
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
@@ -249,7 +242,6 @@ const AdminBylaws = () => {
|
|||||||
>
|
>
|
||||||
<Trash2 className="h-4 w-4" />
|
<Trash2 className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-4 text-sm text-[#664fa3]">
|
<div className="flex items-center gap-4 text-sm text-[#664fa3]">
|
||||||
@@ -262,12 +254,10 @@ const AdminBylaws = () => {
|
|||||||
<Card className="p-12 text-center">
|
<Card className="p-12 text-center">
|
||||||
<Scale className="h-16 w-16 text-[#ddd8eb] mx-auto mb-4" />
|
<Scale className="h-16 w-16 text-[#ddd8eb] mx-auto mb-4" />
|
||||||
<p className="text-[#664fa3] text-lg mb-4">No current bylaws set</p>
|
<p className="text-[#664fa3] text-lg mb-4">No current bylaws set</p>
|
||||||
{hasPermission('bylaws.create') && (
|
|
||||||
<Button onClick={handleCreate} className="bg-[#664fa3] text-white">
|
<Button onClick={handleCreate} className="bg-[#664fa3] text-white">
|
||||||
<Plus className="h-4 w-4 mr-2" />
|
<Plus className="h-4 w-4 mr-2" />
|
||||||
Create Bylaws
|
Create Bylaws
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
|
||||||
</Card>
|
</Card>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -300,7 +290,6 @@ const AdminBylaws = () => {
|
|||||||
>
|
>
|
||||||
<ExternalLink className="h-4 w-4" />
|
<ExternalLink className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
{hasPermission('bylaws.edit') && (
|
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
@@ -309,8 +298,6 @@ const AdminBylaws = () => {
|
|||||||
>
|
>
|
||||||
<Edit className="h-4 w-4" />
|
<Edit className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
|
||||||
{hasPermission('bylaws.delete') && (
|
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
@@ -319,7 +306,6 @@ const AdminBylaws = () => {
|
|||||||
>
|
>
|
||||||
<Trash2 className="h-4 w-4" />
|
<Trash2 className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import api from '../../utils/api';
|
|||||||
import { Card } from '../../components/ui/card';
|
import { Card } from '../../components/ui/card';
|
||||||
import { Button } from '../../components/ui/button';
|
import { Button } from '../../components/ui/button';
|
||||||
import { Badge } from '../../components/ui/badge';
|
import { Badge } from '../../components/ui/badge';
|
||||||
import { Users, Calendar, Clock, CheckCircle, Mail, AlertCircle,Globe } from 'lucide-react';
|
import { Users, Calendar, Clock, CheckCircle, Mail, AlertCircle } from 'lucide-react';
|
||||||
|
|
||||||
const AdminDashboard = () => {
|
const AdminDashboard = () => {
|
||||||
const [stats, setStats] = useState({
|
const [stats, setStats] = useState({
|
||||||
@@ -56,7 +56,6 @@ const AdminDashboard = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className='flex justify-between items-center'>
|
|
||||||
<div className="mb-8">
|
<div className="mb-8">
|
||||||
<h1 className="text-4xl md:text-5xl font-semibold text-[#422268] mb-4" style={{ fontFamily: "'Inter', sans-serif" }}>
|
<h1 className="text-4xl md:text-5xl font-semibold text-[#422268] mb-4" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||||
Admin Dashboard
|
Admin Dashboard
|
||||||
@@ -65,15 +64,6 @@ const AdminDashboard = () => {
|
|||||||
Manage users, events, and membership applications.
|
Manage users, events, and membership applications.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Link to={'/'}>
|
|
||||||
<Button
|
|
||||||
className="bg-[#664fa3] text-white hover:bg-[#533a82] rounded-full flex items-center gap-2"
|
|
||||||
>
|
|
||||||
<Globe />
|
|
||||||
View Public Site
|
|
||||||
</Button>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Stats Grid */}
|
{/* Stats Grid */}
|
||||||
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6 mb-12">
|
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6 mb-12">
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { useAuth } from '../../context/AuthContext';
|
|
||||||
import { Card } from '../../components/ui/card';
|
import { Card } from '../../components/ui/card';
|
||||||
import { Button } from '../../components/ui/button';
|
import { Button } from '../../components/ui/button';
|
||||||
import { Input } from '../../components/ui/input';
|
import { Input } from '../../components/ui/input';
|
||||||
@@ -32,7 +31,6 @@ import {
|
|||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
|
|
||||||
const AdminDonations = () => {
|
const AdminDonations = () => {
|
||||||
const { hasPermission } = useAuth();
|
|
||||||
const [donations, setDonations] = useState([]);
|
const [donations, setDonations] = useState([]);
|
||||||
const [filteredDonations, setFilteredDonations] = useState([]);
|
const [filteredDonations, setFilteredDonations] = useState([]);
|
||||||
const [stats, setStats] = useState({});
|
const [stats, setStats] = useState({});
|
||||||
@@ -271,7 +269,6 @@ const AdminDonations = () => {
|
|||||||
className="pl-10 rounded-full border-2 border-[#ddd8eb] focus:border-[#664fa3]"
|
className="pl-10 rounded-full border-2 border-[#ddd8eb] focus:border-[#664fa3]"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{hasPermission('donations.export') && (
|
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
@@ -299,7 +296,6 @@ const AdminDonations = () => {
|
|||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Filters Row */}
|
{/* Filters Row */}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { useAuth } from '../../context/AuthContext';
|
|
||||||
import api from '../../utils/api';
|
import api from '../../utils/api';
|
||||||
import { Card } from '../../components/ui/card';
|
import { Card } from '../../components/ui/card';
|
||||||
import { Button } from '../../components/ui/button';
|
import { Button } from '../../components/ui/button';
|
||||||
@@ -32,7 +31,6 @@ import {
|
|||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
|
|
||||||
const AdminFinancials = () => {
|
const AdminFinancials = () => {
|
||||||
const { hasPermission } = useAuth();
|
|
||||||
const [reports, setReports] = useState([]);
|
const [reports, setReports] = useState([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [dialogOpen, setDialogOpen] = useState(false);
|
const [dialogOpen, setDialogOpen] = useState(false);
|
||||||
@@ -164,7 +162,6 @@ const AdminFinancials = () => {
|
|||||||
Manage annual financial reports
|
Manage annual financial reports
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{hasPermission('financials.create') && (
|
|
||||||
<Button
|
<Button
|
||||||
onClick={handleCreate}
|
onClick={handleCreate}
|
||||||
className="bg-[#664fa3] text-white hover:bg-[#533a82] rounded-full flex items-center gap-2"
|
className="bg-[#664fa3] text-white hover:bg-[#533a82] rounded-full flex items-center gap-2"
|
||||||
@@ -172,7 +169,6 @@ const AdminFinancials = () => {
|
|||||||
<Plus className="h-4 w-4" />
|
<Plus className="h-4 w-4" />
|
||||||
Add Report
|
Add Report
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Reports List */}
|
{/* Reports List */}
|
||||||
@@ -180,12 +176,10 @@ const AdminFinancials = () => {
|
|||||||
<Card className="p-12 text-center">
|
<Card className="p-12 text-center">
|
||||||
<TrendingUp className="h-16 w-16 text-[#ddd8eb] mx-auto mb-4" />
|
<TrendingUp className="h-16 w-16 text-[#ddd8eb] mx-auto mb-4" />
|
||||||
<p className="text-[#664fa3] text-lg mb-4">No financial reports yet</p>
|
<p className="text-[#664fa3] text-lg mb-4">No financial reports yet</p>
|
||||||
{hasPermission('financials.create') && (
|
|
||||||
<Button onClick={handleCreate} className="bg-[#664fa3] text-white">
|
<Button onClick={handleCreate} className="bg-[#664fa3] text-white">
|
||||||
<Plus className="h-4 w-4 mr-2" />
|
<Plus className="h-4 w-4 mr-2" />
|
||||||
Create First Report
|
Create First Report
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
|
||||||
</Card>
|
</Card>
|
||||||
) : (
|
) : (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
@@ -215,9 +209,7 @@ const AdminFinancials = () => {
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{(hasPermission('financials.edit') || hasPermission('financials.delete')) && (
|
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
{hasPermission('financials.edit') && (
|
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
@@ -226,8 +218,6 @@ const AdminFinancials = () => {
|
|||||||
>
|
>
|
||||||
<Edit className="h-4 w-4" />
|
<Edit className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
|
||||||
{hasPermission('financials.delete') && (
|
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
@@ -236,9 +226,7 @@ const AdminFinancials = () => {
|
|||||||
>
|
>
|
||||||
<Trash2 className="h-4 w-4" />
|
<Trash2 className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import React, { useState, useEffect, useRef } from 'react';
|
import React, { useState, useEffect, useRef } from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { useAuth } from '../../context/AuthContext';
|
|
||||||
import api from '../../utils/api';
|
import api from '../../utils/api';
|
||||||
import { Card } from '../../components/ui/card';
|
import { Card } from '../../components/ui/card';
|
||||||
import { Button } from '../../components/ui/button';
|
import { Button } from '../../components/ui/button';
|
||||||
@@ -21,7 +20,6 @@ import { toast } from 'sonner';
|
|||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
|
||||||
const AdminGallery = () => {
|
const AdminGallery = () => {
|
||||||
const { hasPermission } = useAuth();
|
|
||||||
const [events, setEvents] = useState([]);
|
const [events, setEvents] = useState([]);
|
||||||
const [selectedEvent, setSelectedEvent] = useState(null);
|
const [selectedEvent, setSelectedEvent] = useState(null);
|
||||||
const [galleryImages, setGalleryImages] = useState([]);
|
const [galleryImages, setGalleryImages] = useState([]);
|
||||||
@@ -208,7 +206,7 @@ const AdminGallery = () => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{selectedEvent && hasPermission('gallery.upload') && (
|
{selectedEvent && (
|
||||||
<div className="pt-4">
|
<div className="pt-4">
|
||||||
<input
|
<input
|
||||||
type="file"
|
type="file"
|
||||||
@@ -269,9 +267,7 @@ const AdminGallery = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Overlay with Actions */}
|
{/* Overlay with Actions */}
|
||||||
{(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">
|
<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
|
<Button
|
||||||
onClick={() => openEditCaption(image)}
|
onClick={() => openEditCaption(image)}
|
||||||
size="sm"
|
size="sm"
|
||||||
@@ -281,8 +277,6 @@ const AdminGallery = () => {
|
|||||||
<Edit className="h-4 w-4 mr-1" />
|
<Edit className="h-4 w-4 mr-1" />
|
||||||
Caption
|
Caption
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
|
||||||
{hasPermission('gallery.delete') && (
|
|
||||||
<Button
|
<Button
|
||||||
onClick={() => handleDeleteImage(image.id)}
|
onClick={() => handleDeleteImage(image.id)}
|
||||||
size="sm"
|
size="sm"
|
||||||
@@ -292,9 +286,7 @@ const AdminGallery = () => {
|
|||||||
<Trash2 className="h-4 w-4 mr-1" />
|
<Trash2 className="h-4 w-4 mr-1" />
|
||||||
Delete
|
Delete
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Caption Preview */}
|
{/* Caption Preview */}
|
||||||
{image.caption && (
|
{image.caption && (
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { useAuth } from '../../context/AuthContext';
|
|
||||||
import api from '../../utils/api';
|
import api from '../../utils/api';
|
||||||
import { Card } from '../../components/ui/card';
|
import { Card } from '../../components/ui/card';
|
||||||
import { Button } from '../../components/ui/button';
|
import { Button } from '../../components/ui/button';
|
||||||
@@ -33,7 +32,6 @@ import {
|
|||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
|
|
||||||
const AdminNewsletters = () => {
|
const AdminNewsletters = () => {
|
||||||
const { hasPermission } = useAuth();
|
|
||||||
const [newsletters, setNewsletters] = useState([]);
|
const [newsletters, setNewsletters] = useState([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [dialogOpen, setDialogOpen] = useState(false);
|
const [dialogOpen, setDialogOpen] = useState(false);
|
||||||
@@ -192,7 +190,6 @@ const AdminNewsletters = () => {
|
|||||||
Create and manage newsletter archive
|
Create and manage newsletter archive
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{hasPermission('newsletters.create') && (
|
|
||||||
<Button
|
<Button
|
||||||
onClick={handleCreate}
|
onClick={handleCreate}
|
||||||
className="bg-[#664fa3] text-white hover:bg-[#533a82] rounded-full flex items-center gap-2"
|
className="bg-[#664fa3] text-white hover:bg-[#533a82] rounded-full flex items-center gap-2"
|
||||||
@@ -200,7 +197,6 @@ const AdminNewsletters = () => {
|
|||||||
<Plus className="h-4 w-4" />
|
<Plus className="h-4 w-4" />
|
||||||
Add Newsletter
|
Add Newsletter
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Newsletters List */}
|
{/* Newsletters List */}
|
||||||
@@ -208,12 +204,10 @@ const AdminNewsletters = () => {
|
|||||||
<Card className="p-12 text-center">
|
<Card className="p-12 text-center">
|
||||||
<FileText className="h-16 w-16 text-[#ddd8eb] mx-auto mb-4" />
|
<FileText className="h-16 w-16 text-[#ddd8eb] mx-auto mb-4" />
|
||||||
<p className="text-[#664fa3] text-lg mb-4">No newsletters yet</p>
|
<p className="text-[#664fa3] text-lg mb-4">No newsletters yet</p>
|
||||||
{hasPermission('newsletters.create') && (
|
|
||||||
<Button onClick={handleCreate} className="bg-[#664fa3] text-white">
|
<Button onClick={handleCreate} className="bg-[#664fa3] text-white">
|
||||||
<Plus className="h-4 w-4 mr-2" />
|
<Plus className="h-4 w-4 mr-2" />
|
||||||
Create First Newsletter
|
Create First Newsletter
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
|
||||||
</Card>
|
</Card>
|
||||||
) : (
|
) : (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
@@ -252,9 +246,7 @@ const AdminNewsletters = () => {
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{(hasPermission('newsletters.edit') || hasPermission('newsletters.delete')) && (
|
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
{hasPermission('newsletters.edit') && (
|
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
@@ -263,8 +255,6 @@ const AdminNewsletters = () => {
|
|||||||
>
|
>
|
||||||
<Edit className="h-4 w-4" />
|
<Edit className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
|
||||||
{hasPermission('newsletters.delete') && (
|
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
@@ -273,9 +263,7 @@ const AdminNewsletters = () => {
|
|||||||
>
|
>
|
||||||
<Trash2 className="h-4 w-4" />
|
<Trash2 className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { useAuth } from '../../context/AuthContext';
|
|
||||||
import api from '../../utils/api';
|
import api from '../../utils/api';
|
||||||
import { Card } from '../../components/ui/card';
|
import { Card } from '../../components/ui/card';
|
||||||
import { Button } from '../../components/ui/button';
|
import { Button } from '../../components/ui/button';
|
||||||
@@ -25,7 +24,6 @@ import {
|
|||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
|
|
||||||
const AdminPlans = () => {
|
const AdminPlans = () => {
|
||||||
const { hasPermission } = useAuth();
|
|
||||||
const [plans, setPlans] = useState([]);
|
const [plans, setPlans] = useState([]);
|
||||||
const [filteredPlans, setFilteredPlans] = useState([]);
|
const [filteredPlans, setFilteredPlans] = useState([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
@@ -138,7 +136,6 @@ const AdminPlans = () => {
|
|||||||
Manage membership plans and pricing.
|
Manage membership plans and pricing.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{hasPermission('subscriptions.plans') && (
|
|
||||||
<Button
|
<Button
|
||||||
onClick={handleCreatePlan}
|
onClick={handleCreatePlan}
|
||||||
className="bg-[#DDD8EB] text-[#422268] hover:bg-white rounded-full px-6"
|
className="bg-[#DDD8EB] text-[#422268] hover:bg-white rounded-full px-6"
|
||||||
@@ -146,7 +143,6 @@ const AdminPlans = () => {
|
|||||||
<Plus className="h-4 w-4 mr-2" />
|
<Plus className="h-4 w-4 mr-2" />
|
||||||
Create Plan
|
Create Plan
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -290,7 +286,6 @@ const AdminPlans = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Actions */}
|
{/* Actions */}
|
||||||
{hasPermission('subscriptions.plans') && (
|
|
||||||
<div className="flex flex-col sm:flex-row gap-2 pt-4 border-t border-[#ddd8eb]">
|
<div className="flex flex-col sm:flex-row gap-2 pt-4 border-t border-[#ddd8eb]">
|
||||||
<Button
|
<Button
|
||||||
onClick={() => handleEditPlan(plan)}
|
onClick={() => handleEditPlan(plan)}
|
||||||
@@ -312,7 +307,6 @@ const AdminPlans = () => {
|
|||||||
Delete
|
Delete
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Warning for plans with subscribers */}
|
{/* Warning for plans with subscribers */}
|
||||||
{plan.subscriber_count > 0 && (
|
{plan.subscriber_count > 0 && (
|
||||||
@@ -334,7 +328,7 @@ const AdminPlans = () => {
|
|||||||
? 'Try adjusting your filters'
|
? 'Try adjusting your filters'
|
||||||
: 'Create your first subscription plan to get started'}
|
: 'Create your first subscription plan to get started'}
|
||||||
</p>
|
</p>
|
||||||
{!searchQuery && activeFilter === 'all' && hasPermission('subscriptions.plans') && (
|
{!searchQuery && activeFilter === 'all' && (
|
||||||
<Button
|
<Button
|
||||||
onClick={handleCreatePlan}
|
onClick={handleCreatePlan}
|
||||||
className="bg-[#DDD8EB] text-[#422268] hover:bg-white rounded-full px-8"
|
className="bg-[#DDD8EB] text-[#422268] hover:bg-white rounded-full px-8"
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import { UserCog, Search, Shield, UserPlus, Mail, Edit, Eye, Trash2, UserCheck,
|
|||||||
|
|
||||||
const AdminStaff = () => {
|
const AdminStaff = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { hasPermission, user } = useAuth();
|
const { hasPermission } = useAuth();
|
||||||
const [users, setUsers] = useState([]);
|
const [users, setUsers] = useState([]);
|
||||||
const [filteredUsers, setFilteredUsers] = useState([]);
|
const [filteredUsers, setFilteredUsers] = useState([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
@@ -142,7 +142,7 @@ const AdminStaff = () => {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-3">
|
<div className="flex gap-3">
|
||||||
{hasPermission('users.create') && (
|
{hasPermission('users.invite') && (
|
||||||
<Button
|
<Button
|
||||||
onClick={() => setInviteDialogOpen(true)}
|
onClick={() => setInviteDialogOpen(true)}
|
||||||
className="bg-[#664fa3] hover:bg-[#422268] text-white rounded-xl h-12 px-6"
|
className="bg-[#664fa3] hover:bg-[#422268] text-white rounded-xl h-12 px-6"
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { useAuth } from '../../context/AuthContext';
|
|
||||||
import { Card } from '../../components/ui/card';
|
import { Card } from '../../components/ui/card';
|
||||||
import { Button } from '../../components/ui/button';
|
import { Button } from '../../components/ui/button';
|
||||||
import { Input } from '../../components/ui/input';
|
import { Input } from '../../components/ui/input';
|
||||||
@@ -45,7 +44,6 @@ import {
|
|||||||
} from '../../components/ui/dropdown-menu';
|
} from '../../components/ui/dropdown-menu';
|
||||||
|
|
||||||
const AdminSubscriptions = () => {
|
const AdminSubscriptions = () => {
|
||||||
const { hasPermission } = useAuth();
|
|
||||||
const [subscriptions, setSubscriptions] = useState([]);
|
const [subscriptions, setSubscriptions] = useState([]);
|
||||||
const [filteredSubscriptions, setFilteredSubscriptions] = useState([]);
|
const [filteredSubscriptions, setFilteredSubscriptions] = useState([]);
|
||||||
const [plans, setPlans] = useState([]);
|
const [plans, setPlans] = useState([]);
|
||||||
@@ -414,7 +412,6 @@ Proceed with activation?`;
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Export Dropdown */}
|
{/* Export Dropdown */}
|
||||||
{hasPermission('subscriptions.export') && (
|
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
@@ -442,7 +439,6 @@ Proceed with activation?`;
|
|||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
@@ -507,7 +503,6 @@ Proceed with activation?`;
|
|||||||
|
|
||||||
{/* Actions */}
|
{/* Actions */}
|
||||||
<div className="flex gap-2 pt-2">
|
<div className="flex gap-2 pt-2">
|
||||||
{hasPermission('subscriptions.edit') && (
|
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
@@ -517,8 +512,7 @@ Proceed with activation?`;
|
|||||||
<Edit className="h-4 w-4 mr-2" />
|
<Edit className="h-4 w-4 mr-2" />
|
||||||
Edit
|
Edit
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
{sub.status === 'active' && (
|
||||||
{sub.status === 'active' && hasPermission('subscriptions.cancel') && (
|
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
@@ -613,7 +607,6 @@ Proceed with activation?`;
|
|||||||
</td>
|
</td>
|
||||||
<td className="p-4">
|
<td className="p-4">
|
||||||
<div className="flex items-center justify-center gap-2">
|
<div className="flex items-center justify-center gap-2">
|
||||||
{hasPermission('subscriptions.edit') && (
|
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
@@ -622,8 +615,7 @@ Proceed with activation?`;
|
|||||||
>
|
>
|
||||||
<Edit className="h-4 w-4" />
|
<Edit className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
{sub.status === 'active' && (
|
||||||
{sub.status === 'active' && hasPermission('subscriptions.cancel') && (
|
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { useAuth } from '../../context/AuthContext';
|
|
||||||
import api from '../../utils/api';
|
import api from '../../utils/api';
|
||||||
import { Card } from '../../components/ui/card';
|
import { Card } from '../../components/ui/card';
|
||||||
import { Button } from '../../components/ui/button';
|
import { Button } from '../../components/ui/button';
|
||||||
@@ -36,7 +35,6 @@ import ConfirmationDialog from '../../components/ConfirmationDialog';
|
|||||||
import RejectionDialog from '../../components/RejectionDialog';
|
import RejectionDialog from '../../components/RejectionDialog';
|
||||||
|
|
||||||
const AdminValidations = () => {
|
const AdminValidations = () => {
|
||||||
const { hasPermission } = useAuth();
|
|
||||||
const [pendingUsers, setPendingUsers] = useState([]);
|
const [pendingUsers, setPendingUsers] = useState([]);
|
||||||
const [filteredUsers, setFilteredUsers] = useState([]);
|
const [filteredUsers, setFilteredUsers] = useState([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
@@ -421,7 +419,6 @@ const AdminValidations = () => {
|
|||||||
</Button>
|
</Button>
|
||||||
) : user.status === 'pending_email' ? (
|
) : user.status === 'pending_email' ? (
|
||||||
<>
|
<>
|
||||||
{hasPermission('users.approve') && (
|
|
||||||
<Button
|
<Button
|
||||||
onClick={() => handleBypassAndValidateRequest(user)}
|
onClick={() => handleBypassAndValidateRequest(user)}
|
||||||
disabled={actionLoading === user.id}
|
disabled={actionLoading === user.id}
|
||||||
@@ -430,8 +427,6 @@ const AdminValidations = () => {
|
|||||||
>
|
>
|
||||||
{actionLoading === user.id ? 'Validating...' : 'Bypass & Validate'}
|
{actionLoading === user.id ? 'Validating...' : 'Bypass & Validate'}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
|
||||||
{hasPermission('users.approve') && (
|
|
||||||
<Button
|
<Button
|
||||||
onClick={() => handleRejectUser(user)}
|
onClick={() => handleRejectUser(user)}
|
||||||
disabled={actionLoading === user.id}
|
disabled={actionLoading === user.id}
|
||||||
@@ -442,11 +437,9 @@ const AdminValidations = () => {
|
|||||||
<X className="h-4 w-4 mr-1" />
|
<X className="h-4 w-4 mr-1" />
|
||||||
Reject
|
Reject
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
) : user.status === 'payment_pending' ? (
|
) : user.status === 'payment_pending' ? (
|
||||||
<>
|
<>
|
||||||
{hasPermission('subscriptions.activate') && (
|
|
||||||
<Button
|
<Button
|
||||||
onClick={() => handleActivatePayment(user)}
|
onClick={() => handleActivatePayment(user)}
|
||||||
size="sm"
|
size="sm"
|
||||||
@@ -455,8 +448,6 @@ const AdminValidations = () => {
|
|||||||
<CheckCircle className="h-4 w-4 mr-1" />
|
<CheckCircle className="h-4 w-4 mr-1" />
|
||||||
Activate Payment
|
Activate Payment
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
|
||||||
{hasPermission('users.approve') && (
|
|
||||||
<Button
|
<Button
|
||||||
onClick={() => handleRejectUser(user)}
|
onClick={() => handleRejectUser(user)}
|
||||||
disabled={actionLoading === user.id}
|
disabled={actionLoading === user.id}
|
||||||
@@ -467,11 +458,9 @@ const AdminValidations = () => {
|
|||||||
<X className="h-4 w-4 mr-1" />
|
<X className="h-4 w-4 mr-1" />
|
||||||
Reject
|
Reject
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{hasPermission('users.approve') && (
|
|
||||||
<Button
|
<Button
|
||||||
onClick={() => handleValidateRequest(user)}
|
onClick={() => handleValidateRequest(user)}
|
||||||
disabled={actionLoading === user.id}
|
disabled={actionLoading === user.id}
|
||||||
@@ -480,8 +469,6 @@ const AdminValidations = () => {
|
|||||||
>
|
>
|
||||||
{actionLoading === user.id ? 'Validating...' : 'Validate'}
|
{actionLoading === user.id ? 'Validating...' : 'Validate'}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
|
||||||
{hasPermission('users.approve') && (
|
|
||||||
<Button
|
<Button
|
||||||
onClick={() => handleRejectUser(user)}
|
onClick={() => handleRejectUser(user)}
|
||||||
disabled={actionLoading === user.id}
|
disabled={actionLoading === user.id}
|
||||||
@@ -492,7 +479,6 @@ const AdminValidations = () => {
|
|||||||
<X className="h-4 w-4 mr-1" />
|
<X className="h-4 w-4 mr-1" />
|
||||||
Reject
|
Reject
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -24,8 +24,6 @@ const MembersDirectory = () => {
|
|||||||
const [selectedMember, setSelectedMember] = useState(null);
|
const [selectedMember, setSelectedMember] = useState(null);
|
||||||
const [profileDialogOpen, setProfileDialogOpen] = useState(false);
|
const [profileDialogOpen, setProfileDialogOpen] = useState(false);
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const [currentPage, setCurrentPage] = useState(1);
|
|
||||||
const pageSize = 12;
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchMembers();
|
fetchMembers();
|
||||||
@@ -35,10 +33,6 @@ const MembersDirectory = () => {
|
|||||||
filterMembers();
|
filterMembers();
|
||||||
}, [searchQuery, members]);
|
}, [searchQuery, members]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setCurrentPage(1);
|
|
||||||
}, [searchQuery, members]);
|
|
||||||
|
|
||||||
const fetchMembers = async () => {
|
const fetchMembers = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await api.get('/members/directory');
|
const response = await api.get('/members/directory');
|
||||||
@@ -72,14 +66,6 @@ const MembersDirectory = () => {
|
|||||||
setFilteredMembers(filtered);
|
setFilteredMembers(filtered);
|
||||||
};
|
};
|
||||||
|
|
||||||
const totalPages = Math.max(1, Math.ceil(filteredMembers.length / pageSize));
|
|
||||||
|
|
||||||
const pageStart = (currentPage - 1) * pageSize;
|
|
||||||
|
|
||||||
const paginatedMembers = filteredMembers.slice(pageStart, pageStart + pageSize);
|
|
||||||
|
|
||||||
const totalMembers = members.length;
|
|
||||||
|
|
||||||
const getInitials = (firstName, lastName) => {
|
const getInitials = (firstName, lastName) => {
|
||||||
return `${firstName.charAt(0)}${lastName.charAt(0)}`.toUpperCase();
|
return `${firstName.charAt(0)}${lastName.charAt(0)}`.toUpperCase();
|
||||||
};
|
};
|
||||||
@@ -111,15 +97,9 @@ const MembersDirectory = () => {
|
|||||||
if (!dateString) return null;
|
if (!dateString) return null;
|
||||||
return new Date(dateString).toLocaleDateString('en-US', { month: 'long', day: 'numeric' });
|
return new Date(dateString).toLocaleDateString('en-US', { month: 'long', day: 'numeric' });
|
||||||
};
|
};
|
||||||
const Border = ({ yaxis = false }) => {
|
|
||||||
return (
|
|
||||||
yaxis ?
|
|
||||||
<div className=' border-2 w-full border-[#664FA3] my-24' />
|
|
||||||
: <div className=' border-2 w-full border-[#664FA3] mb-24' />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
const MemberCard = ({ member }) => (
|
const MemberCard = ({ member }) => (
|
||||||
<Card className="p-6 bg-white rounded-3xl border border-[#ddd8eb] hover:shadow-lg transition-all h-full">
|
<Card className="p-6 bg-white rounded-2xl border border-[#ddd8eb] hover:shadow-lg transition-all h-full">
|
||||||
{/* Profile Photo */}
|
{/* Profile Photo */}
|
||||||
<div className="flex justify-center mb-4">
|
<div className="flex justify-center mb-4">
|
||||||
{member.profile_photo_url ? (
|
{member.profile_photo_url ? (
|
||||||
@@ -279,34 +259,30 @@ const MembersDirectory = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gradient-to-bl from-[#F9FAFB] to-[#DDD8EB]">
|
<div className="min-h-screen bg-white">
|
||||||
<Navbar />
|
<Navbar />
|
||||||
|
|
||||||
<div className="max-w-7xl mx-auto py-12">
|
<div className="max-w-7xl mx-auto px-6 py-12">
|
||||||
|
|
||||||
{/* Header and Search bar */}
|
|
||||||
<div className='px-9'>
|
|
||||||
|
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="m-8 mt-14 flex flex-col sm:flex-row justify-between items-center ">
|
<div className="mb-8">
|
||||||
<h1 className="text-4xl md:text-5xl font-bold text-[#422268] mb-4" style={{ fontFamily: "'Poppins', sans-serif" }}>
|
<h1 className="text-4xl md:text-5xl font-semibold text-[#422268] mb-4" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||||
LOAF Members
|
Members Directory
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-lg " style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
<p className="text-lg text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||||
<span className='text-foreground'>Number of current memebers in the directory: </span> <span className='text-[#664fa3] font-medium'>{totalMembers}</span>
|
Connect with fellow LOAF members in our community.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Search Bar */}
|
{/* Search Bar */}
|
||||||
<div className="mb-24 mx-10">
|
<div className="mb-8">
|
||||||
<div className="relative w-full ">
|
<div className="relative max-w-xl">
|
||||||
<Search className="absolute left-4 top-1/2 transform -translate-y-1/2 h-5 w-5 text-[#664fa3]" />
|
<Search className="absolute left-4 top-1/2 transform -translate-y-1/2 h-5 w-5 text-[#664fa3]" />
|
||||||
<Input
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Search by name or bio..."
|
placeholder="Search by name or bio..."
|
||||||
value={searchQuery}
|
value={searchQuery}
|
||||||
onChange={(e) => setSearchQuery(e.target.value)}
|
onChange={(e) => setSearchQuery(e.target.value)}
|
||||||
className="pl-12 pr-4 py-6 text-3xl font-medium bg-background border-foreground rounded-full focus:border-[#664fa3] focus:ring-[#664fa3]"
|
className="pl-12 pr-4 py-6 text-lg border-[#ddd8eb] rounded-xl focus:border-[#664fa3] focus:ring-[#664fa3]"
|
||||||
style={{ fontFamily: "'Nunito Sans', sans-serif" }}
|
style={{ fontFamily: "'Nunito Sans', sans-serif" }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -317,11 +293,6 @@ const MembersDirectory = () => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
|
||||||
{/* Border Decoration */}
|
|
||||||
|
|
||||||
<Border />
|
|
||||||
|
|
||||||
{/* Members Grid */}
|
{/* Members Grid */}
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<div className="text-center py-20">
|
<div className="text-center py-20">
|
||||||
@@ -329,7 +300,7 @@ const MembersDirectory = () => {
|
|||||||
</div>
|
</div>
|
||||||
) : filteredMembers.length > 0 ? (
|
) : filteredMembers.length > 0 ? (
|
||||||
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-8">
|
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||||
{paginatedMembers.map((member) => (
|
{filteredMembers.map((member) => (
|
||||||
<MemberCard key={member.id} member={member} />
|
<MemberCard key={member.id} member={member} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -347,11 +318,6 @@ const MembersDirectory = () => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{/* Border Decoration */}
|
|
||||||
<Border yaxis="true" />
|
|
||||||
|
|
||||||
{/* Info Card */}
|
{/* Info Card */}
|
||||||
{!loading && members.length > 0 && (
|
{!loading && members.length > 0 && (
|
||||||
<Card className="mt-12 p-6 bg-[#F8F7FB] border-[#ddd8eb]">
|
<Card className="mt-12 p-6 bg-[#F8F7FB] border-[#ddd8eb]">
|
||||||
@@ -560,66 +526,6 @@ const MembersDirectory = () => {
|
|||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{/* Pagination */}
|
|
||||||
{!loading && filteredMembers.length > 0 && (
|
|
||||||
<div className="mt-10 flex flex-col items-center gap-4 pb-12">
|
|
||||||
<p className="text-sm text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
|
||||||
Showing {pageStart + 1}–{Math.min(pageStart + pageSize, filteredMembers.length)} of {filteredMembers.length}
|
|
||||||
</p>
|
|
||||||
<div className="flex flex-wrap items-center justify-center gap-2">
|
|
||||||
<Button
|
|
||||||
onClick={() => setCurrentPage(1)}
|
|
||||||
disabled={currentPage === 1}
|
|
||||||
className="bg-[#DDD8EB] rounded-full text-[#422268] hover:bg-[#664fa3] hover:text-white"
|
|
||||||
>
|
|
||||||
First Page
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
onClick={() => setCurrentPage((page) => Math.max(1, page - 1))}
|
|
||||||
disabled={currentPage === 1}
|
|
||||||
className="bg-[#DDD8EB] rounded-full text-[#422268] hover:bg-[#664fa3] hover:text-white"
|
|
||||||
>
|
|
||||||
Previous
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
{Array.from({ length: totalPages }, (_, index) => {
|
|
||||||
const pageNumber = index + 1;
|
|
||||||
const isActive = pageNumber === currentPage;
|
|
||||||
return (
|
|
||||||
<Button
|
|
||||||
key={pageNumber}
|
|
||||||
onClick={() => setCurrentPage(pageNumber)}
|
|
||||||
className={
|
|
||||||
isActive
|
|
||||||
? "bg-[#664fa3] text-white hover:bg-[#422268] rounded-full"
|
|
||||||
: "bg-[#DDD8EB] text-[#422268] hover:bg-[#664fa3] hover:text-white rounded-full"
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{pageNumber}
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
|
|
||||||
<Button
|
|
||||||
onClick={() => setCurrentPage((page) => Math.min(totalPages, page + 1))}
|
|
||||||
disabled={currentPage === totalPages}
|
|
||||||
className="bg-[#DDD8EB] text-[#422268] hover:bg-[#664fa3] rounded-full hover:text-white"
|
|
||||||
>
|
|
||||||
Next
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
onClick={() => setCurrentPage(totalPages)}
|
|
||||||
disabled={currentPage === totalPages}
|
|
||||||
className="bg-[#DDD8EB] text-[#422268] hover:bg-[#664fa3] rounded-full hover:text-white"
|
|
||||||
>
|
|
||||||
Last Page
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<MemberFooter />
|
<MemberFooter />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user