-
-
-
- Members Needing Personal Outreach
-
-
- These members have received multiple reminder emails. Consider calling them directly.
-
-
+ {/* Users Needing Attention Widget */}
+ {usersNeedingAttention.length > 0 && (
+
+
+
+
-
-
- {usersNeedingAttention.map(user => (
-
-
-
-
-
-
- {user.first_name} {user.last_name}
-
-
- {user.totalReminders} reminder{user.totalReminders !== 1 ? 's' : ''}
-
-
-
-
Email: {user.email}
-
Phone: {user.phone || 'N/A'}
-
Status: {user.status.replace('_', ' ')}
- {user.email_verification_reminders_sent > 0 && (
-
-
- {user.email_verification_reminders_sent} email verification reminder{user.email_verification_reminders_sent !== 1 ? 's' : ''}
-
- )}
- {user.event_attendance_reminders_sent > 0 && (
-
-
- {user.event_attendance_reminders_sent} event reminder{user.event_attendance_reminders_sent !== 1 ? 's' : ''}
-
- )}
- {user.payment_reminders_sent > 0 && (
-
-
- {user.payment_reminders_sent} payment reminder{user.payment_reminders_sent !== 1 ? 's' : ''}
-
- )}
-
-
-
-
-
-
- ))}
-
-
-
+
+
+ Members Needing Personal Outreach
+
- 💡 Tip for helping older members: Many of our members are older ladies who may struggle with email.
- A friendly phone call can help them complete the registration process and feel more welcomed to the community.
+ These members have received multiple reminder emails. Consider calling them directly.
-
-
- )}
+
+
+
+ {usersNeedingAttention.map(user => (
+
+
+
+
+
+
+ {user.first_name} {user.last_name}
+
+
+ {user.totalReminders} reminder{user.totalReminders !== 1 ? 's' : ''}
+
+
+
+
Email: {user.email}
+
Phone: {user.phone || 'N/A'}
+
Status: {user.status.replace('_', ' ')}
+ {user.email_verification_reminders_sent > 0 && (
+
+
+ {user.email_verification_reminders_sent} email verification reminder{user.email_verification_reminders_sent !== 1 ? 's' : ''}
+
+ )}
+ {user.event_attendance_reminders_sent > 0 && (
+
+
+ {user.event_attendance_reminders_sent} event reminder{user.event_attendance_reminders_sent !== 1 ? 's' : ''}
+
+ )}
+ {user.payment_reminders_sent > 0 && (
+
+
+ {user.payment_reminders_sent} payment reminder{user.payment_reminders_sent !== 1 ? 's' : ''}
+
+ )}
+
+
+
+
+
+
+ ))}
+
+
+
+
+ 💡 Tip for helping older members: Many of our members are older ladies who may struggle with email.
+ A friendly phone call can help them complete the registration process and feel more welcomed to the community.
+
+
+
+
+ )}
>
);
};
diff --git a/src/pages/admin/AdminStaff.js b/src/pages/admin/AdminStaff.js
index 63e8e1e..7e90aa0 100644
--- a/src/pages/admin/AdminStaff.js
+++ b/src/pages/admin/AdminStaff.js
@@ -118,7 +118,7 @@ const AdminStaff = () => {
const getStatusBadge = (status) => {
const config = {
active: { label: 'Active', className: 'bg-[#81B29A] text-white' },
- inactive: { label: 'Inactive', className: 'bg-gray-400 text-white' }
+ inactive: { label: 'Inactive', className: 'bg-gray-400 text-white ' }
};
const statusConfig = config[status] || config.inactive;
diff --git a/src/pages/members/MembersDirectory.js b/src/pages/members/MembersDirectory.js
index 01940a6..b8666b9 100644
--- a/src/pages/members/MembersDirectory.js
+++ b/src/pages/members/MembersDirectory.js
@@ -24,6 +24,8 @@ const MembersDirectory = () => {
const [selectedMember, setSelectedMember] = useState(null);
const [profileDialogOpen, setProfileDialogOpen] = useState(false);
const { toast } = useToast();
+ const [currentPage, setCurrentPage] = useState(1);
+ const pageSize = 12;
useEffect(() => {
fetchMembers();
@@ -33,6 +35,10 @@ const MembersDirectory = () => {
filterMembers();
}, [searchQuery, members]);
+ useEffect(() => {
+ setCurrentPage(1);
+ }, [searchQuery, members]);
+
const fetchMembers = async () => {
try {
const response = await api.get('/members/directory');
@@ -66,6 +72,14 @@ const MembersDirectory = () => {
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) => {
return `${firstName.charAt(0)}${lastName.charAt(0)}`.toUpperCase();
};
@@ -97,9 +111,15 @@ const MembersDirectory = () => {
if (!dateString) return null;
return new Date(dateString).toLocaleDateString('en-US', { month: 'long', day: 'numeric' });
};
-
+ const Border = ({ yaxis = false }) => {
+ return (
+ yaxis ?
+
+ :
+ )
+ }
const MemberCard = ({ member }) => (
-
+
{/* Profile Photo */}
{member.profile_photo_url ? (
@@ -259,39 +279,48 @@ const MembersDirectory = () => {
);
return (
-
+
-
- {/* Header */}
-
-
- Members Directory
-
-
- Connect with fellow LOAF members in our community.
-
-
+
- {/* Search Bar */}
-
-
-
- setSearchQuery(e.target.value)}
- 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" }}
- />
-
- {searchQuery && (
-
- Found {filteredMembers.length} {filteredMembers.length === 1 ? 'member' : 'members'}
+ {/* Header and Search bar */}
+
+
+ {/* Header */}
+
+
+ LOAF Members
+
+
+ Number of current memebers in the directory: {totalMembers}
- )}
+
+
+ {/* Search Bar */}
+
+
+
+ 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]"
+ style={{ fontFamily: "'Nunito Sans', sans-serif" }}
+ />
+
+ {searchQuery && (
+
+ Found {filteredMembers.length} {filteredMembers.length === 1 ? 'member' : 'members'}
+
+ )}
+
+
+ {/* Border Decoration */}
+
+
{/* Members Grid */}
{loading ? (
@@ -300,7 +329,7 @@ const MembersDirectory = () => {
) : filteredMembers.length > 0 ? (
- {filteredMembers.map((member) => (
+ {paginatedMembers.map((member) => (
))}
@@ -318,6 +347,11 @@ const MembersDirectory = () => {
)}
+
+
+ {/* Border Decoration */}
+
+
{/* Info Card */}
{!loading && members.length > 0 && (
@@ -465,67 +499,127 @@ const MembersDirectory = () => {
{/* Social Media */}
{(selectedMember.social_media_facebook || selectedMember.social_media_instagram ||
selectedMember.social_media_twitter || selectedMember.social_media_linkedin) && (
-
-
- Connect on Social Media
-
-
- {selectedMember.social_media_facebook && (
-
-
-
- )}
+
+
+ Connect on Social Media
+
+
+ {selectedMember.social_media_facebook && (
+
+
+
+ )}
- {selectedMember.social_media_instagram && (
-
-
-
- )}
+ {selectedMember.social_media_instagram && (
+
+
+
+ )}
- {selectedMember.social_media_twitter && (
-
-
-
- )}
+ {selectedMember.social_media_twitter && (
+
+
+
+ )}
- {selectedMember.social_media_linkedin && (
-
-
-
- )}
+ {selectedMember.social_media_linkedin && (
+
+
+
+ )}
+
-
- )}
+ )}
>
)}
+
+
+ {/* Pagination */}
+ {!loading && filteredMembers.length > 0 && (
+
+
+ Showing {pageStart + 1}–{Math.min(pageStart + pageSize, filteredMembers.length)} of {filteredMembers.length}
+
+
+
+
+
+ {Array.from({ length: totalPages }, (_, index) => {
+ const pageNumber = index + 1;
+ const isActive = pageNumber === currentPage;
+ return (
+
+ );
+ })}
+
+
+
+
+
+ )}
+
);
diff --git a/src/utils/logger.js b/src/utils/logger.js
new file mode 100644
index 0000000..608c442
--- /dev/null
+++ b/src/utils/logger.js
@@ -0,0 +1,66 @@
+/**
+ * Production-safe logging utility
+ *
+ * In production (NODE_ENV=production), logs are disabled by default
+ * to prevent exposing sensitive information in browser console.
+ *
+ * In development, all logs are shown for debugging.
+ *
+ * Usage:
+ * import logger from '../utils/logger';
+ * logger.log('[Component]', 'message', data);
+ * logger.error('[Component]', 'error message', error);
+ * logger.warn('[Component]', 'warning message');
+ */
+
+const isDevelopment = process.env.NODE_ENV === 'development';
+
+// Force enable logs with REACT_APP_DEBUG_LOGS=true in .env
+const debugEnabled = process.env.REACT_APP_DEBUG_LOGS === 'true';
+
+const shouldLog = isDevelopment || debugEnabled;
+
+const logger = {
+ log: (...args) => {
+ if (shouldLog) {
+ console.log(...args);
+ }
+ },
+
+ error: (...args) => {
+ // Always log errors, but sanitize in production
+ if (shouldLog) {
+ console.error(...args);
+ } else {
+ // In production, only log error type without details
+ console.error('An error occurred. Enable debug logs for details.');
+ }
+ },
+
+ warn: (...args) => {
+ if (shouldLog) {
+ console.warn(...args);
+ }
+ },
+
+ info: (...args) => {
+ if (shouldLog) {
+ console.info(...args);
+ }
+ },
+
+ debug: (...args) => {
+ if (shouldLog) {
+ console.debug(...args);
+ }
+ },
+
+ // Special method for sensitive data - NEVER logs in production
+ sensitive: (...args) => {
+ if (isDevelopment) {
+ console.log('[SENSITIVE]', ...args);
+ }
+ }
+};
+
+export default logger;