10 Commits

Author SHA1 Message Date
kayela
f71931d4a7 Merge branch 'dev' into templates 2026-01-06 12:25:55 -06:00
kayela
97cc5bdedf Add pagination buttons for first and last pages in Members Directory 2026-01-06 12:17:03 -06:00
kayela
8011913c4d Enhance Admin Dashboard and Members Directory with improved layout, pagination, and member details display.
Applied requested changes to UI
2026-01-06 12:05:34 -06:00
kayela
968eaccac2 fixed tabs styling 2026-01-05 14:34:23 -06:00
kayela
11de3d1eed styled chrome scrollbar 2026-01-05 14:16:05 -06:00
kayela
11142ec50e Merge branch 'dev' into templates 2026-01-05 12:51:33 -06:00
kayela
1d70ac4ec7 update Merge branch 'dev' into templates 2025-12-26 17:39:16 -06:00
kayela
6d777ed583 Update homepage and links in RegistrationStep4 component 2025-12-24 14:17:30 -06:00
kayela
99d65c917f Merge remote-tracking branch 'origin/dev' into templates 2025-12-24 14:06:12 -06:00
kayela
0f16264656 Remove unused imports from TermsOfService component 2025-12-24 14:04:35 -06:00
9 changed files with 445 additions and 317 deletions

View File

@@ -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,8 +269,7 @@ 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 ${ className={`object-contain transition-all duration-200 ${isOpen ? 'h-10 w-10' : 'h-8 w-8'
isOpen ? 'h-10 w-10' : 'h-8 w-8'
}`} }`}
/> />
{isOpen && ( {isOpen && (
@@ -300,7 +299,7 @@ const AdminSidebar = ({ isOpen, onToggle, isMobile }) => {
</div> </div>
{/* Navigation */} {/* Navigation */}
<nav className="flex-1 overflow-y-auto p-4"> <nav className="flex-1 overflow-y-auto p-4 scrollbar-dashboard scrollbar-x-dashboard">
{/* Dashboard - Standalone */} {/* Dashboard - Standalone */}
{renderNavItem(filteredNavItems.find(item => item.name === 'Dashboard'))} {renderNavItem(filteredNavItems.find(item => item.name === 'Dashboard'))}
@@ -370,7 +369,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"> <div className="px-4 py-3 mb-2 flex justify-between items-center">
<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]}
@@ -384,6 +383,8 @@ const AdminSidebar = ({ isOpen, onToggle, isMobile }) => {
</p> </p>
</div> </div>
</div> </div>
<Link to='/profile'><Settings size={16} />
</Link>
</div> </div>
)} )}
@@ -397,8 +398,7 @@ 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 ${ className={`h-2 rounded-full transition-all ${storagePercentage > 90 ? 'bg-red-500' :
storagePercentage > 90 ? 'bg-red-500' :
storagePercentage > 75 ? 'bg-yellow-500' : storagePercentage > 75 ? 'bg-yellow-500' :
'bg-[#81B29A]' 'bg-[#81B29A]'
}`} }`}
@@ -412,8 +412,7 @@ 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 ${ <HardDrive className={`h-5 w-5 ${storagePercentage > 90 ? 'text-red-500' :
storagePercentage > 90 ? 'text-red-500' :
storagePercentage > 75 ? 'text-yellow-500' : storagePercentage > 75 ? 'text-yellow-500' :
'text-[#664fa3]' 'text-[#664fa3]'
}`} /> }`} />

View File

@@ -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="/membership/terms-of-service" href="/become-a-member/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="/membership/privacy-policy" href="become-a-member/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"

View File

@@ -1,31 +1,33 @@
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-9 items-center justify-center rounded-lg bg-muted p-1 text-muted-foreground", "inline-flex h-full items-center justify-center rounded-lg gap-6 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 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", "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",
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
@@ -34,8 +36,9 @@ 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 };

View File

@@ -116,3 +116,27 @@ 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;
}
}
}

View File

@@ -12,10 +12,10 @@ const BoardOfDirectors = () => {
]; ];
const boardMembers = [ const boardMembers = [
{ name: 'Danita Cole' }, { name: 'Danita Cole', title: 'Director' },
{ name: 'Roxanne Cherico' }, { name: 'Roxanne Cherico', title: 'Director' },
{ name: 'Lucretia Copeland' }, { name: 'Lucretia Copeland', title: 'Director' },
{ name: 'Julie Fischer' } { name: 'Julie Fischer', title: 'Director' }
]; ];

View File

@@ -2,8 +2,6 @@ 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() {

View File

@@ -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 } from 'lucide-react'; import { Users, Calendar, Clock, CheckCircle, Mail, AlertCircle,Globe } from 'lucide-react';
const AdminDashboard = () => { const AdminDashboard = () => {
const [stats, setStats] = useState({ const [stats, setStats] = useState({
@@ -56,6 +56,7 @@ 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
@@ -64,6 +65,15 @@ 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">

View File

@@ -24,6 +24,8 @@ 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();
@@ -33,6 +35,10 @@ 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');
@@ -66,6 +72,14 @@ 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();
}; };
@@ -97,9 +111,15 @@ 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-2xl border border-[#ddd8eb] hover:shadow-lg transition-all h-full"> <Card className="p-6 bg-white rounded-3xl 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 ? (
@@ -259,30 +279,34 @@ const MembersDirectory = () => {
); );
return ( return (
<div className="min-h-screen bg-white"> <div className="min-h-screen bg-gradient-to-bl from-[#F9FAFB] to-[#DDD8EB]">
<Navbar /> <Navbar />
<div className="max-w-7xl mx-auto px-6 py-12"> <div className="max-w-7xl mx-auto py-12">
{/* Header and Search bar */}
<div className='px-9'>
{/* Header */} {/* Header */}
<div className="mb-8"> <div className="m-8 mt-14 flex flex-col sm:flex-row justify-between items-center ">
<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-bold text-[#422268] mb-4" style={{ fontFamily: "'Poppins', sans-serif" }}>
Members Directory LOAF Members
</h1> </h1>
<p className="text-lg text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}> <p className="text-lg " style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
Connect with fellow LOAF members in our community. <span className='text-foreground'>Number of current memebers in the directory: </span> <span className='text-[#664fa3] font-medium'>{totalMembers}</span>
</p> </p>
</div> </div>
{/* Search Bar */} {/* Search Bar */}
<div className="mb-8"> <div className="mb-24 mx-10">
<div className="relative max-w-xl"> <div className="relative w-full ">
<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-lg border-[#ddd8eb] rounded-xl focus:border-[#664fa3] focus:ring-[#664fa3]" className="pl-12 pr-4 py-6 text-3xl font-medium bg-background border-foreground rounded-full focus:border-[#664fa3] focus:ring-[#664fa3]"
style={{ fontFamily: "'Nunito Sans', sans-serif" }} style={{ fontFamily: "'Nunito Sans', sans-serif" }}
/> />
</div> </div>
@@ -293,6 +317,11 @@ 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">
@@ -300,7 +329,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">
{filteredMembers.map((member) => ( {paginatedMembers.map((member) => (
<MemberCard key={member.id} member={member} /> <MemberCard key={member.id} member={member} />
))} ))}
</div> </div>
@@ -318,6 +347,11 @@ 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]">
@@ -526,6 +560,66 @@ 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>
); );