@@ -175,7 +175,7 @@ const BecomeMember = () => {
className="text-base sm:text-lg font-medium text-[#48286e] leading-[1.6]"
style={{ fontFamily: "'Nunito Sans', sans-serif", fontVariationSettings: "'YTLC' 500, 'wdth' 100" }}
>
- Once we know that you are indeed you, an admin will approve your application and you will receive an email prompting you to login to your user profile and pay the annual administrative fee.
+ Once we know that you are indeed you, an admin will validate your application and you will receive an email prompting you to login to your user profile and pay the annual administrative fee.
diff --git a/src/pages/Dashboard.js b/src/pages/Dashboard.js
index 3f203a0..55d4054 100644
--- a/src/pages/Dashboard.js
+++ b/src/pages/Dashboard.js
@@ -59,11 +59,14 @@ const Dashboard = () => {
const getStatusBadge = (status) => {
const statusConfig = {
pending_email: { icon: Clock, label: 'Pending Email', className: 'bg-orange-100 text-orange-700' },
- pending_approval: { icon: Clock, label: 'Pending Approval', className: 'bg-gray-200 text-gray-700' },
- pre_approved: { icon: CheckCircle, label: 'Pre-Approved', className: 'bg-[#81B29A] text-white' },
+ pending_validation: { icon: Clock, label: 'Pending Validation', className: 'bg-gray-200 text-gray-700' },
+ pre_validated: { icon: CheckCircle, label: 'Pre-Validated', className: 'bg-[#81B29A] text-white' },
payment_pending: { icon: AlertCircle, label: 'Payment Pending', className: 'bg-orange-500 text-white' },
active: { icon: CheckCircle, label: 'Active', className: 'bg-[#81B29A] text-white' },
- inactive: { icon: AlertCircle, label: 'Inactive', className: 'bg-gray-400 text-white' }
+ inactive: { icon: AlertCircle, label: 'Inactive', className: 'bg-gray-400 text-white' },
+ canceled: { icon: AlertCircle, label: 'Canceled', className: 'bg-red-100 text-red-700' },
+ expired: { icon: Clock, label: 'Expired', className: 'bg-red-500 text-white' },
+ abandoned: { icon: AlertCircle, label: 'Abandoned', className: 'bg-gray-300 text-gray-600' }
};
const config = statusConfig[status] || statusConfig.inactive;
@@ -80,11 +83,14 @@ const Dashboard = () => {
const getStatusMessage = (status) => {
const messages = {
pending_email: 'Please check your email to verify your account.',
- pending_approval: 'Your application is under review by our admin team.',
- pre_approved: 'Your application is under review by our admin team.',
+ pending_validation: 'Your application is under review by our admin team.',
+ pre_validated: 'Your application is under review by our admin team.',
payment_pending: 'Please complete your payment to activate your membership.',
active: 'Your membership is active! Enjoy all member benefits.',
- inactive: 'Your membership is currently inactive.'
+ inactive: 'Your membership is currently inactive.',
+ canceled: 'Your membership has been canceled. Contact us to rejoin.',
+ expired: 'Your membership has expired. Please renew to regain access.',
+ abandoned: 'Your application was not completed. Contact us to restart the process.'
};
return messages[status] || '';
@@ -254,14 +260,14 @@ const Dashboard = () => {
{/* CTA Section */}
- {user?.status === 'pending_approval' && (
+ {user?.status === 'pending_validation' && (
Application Under Review
- Your membership application is being reviewed by our admin team. You'll be notified once approved!
+ Your membership application is being reviewed by our admin team. You'll be notified once validated!
- Great news! Your membership application has been approved. Complete your payment to activate your membership and gain full access to all member benefits.
+ Great news! Your membership application has been validated. Complete your payment to activate your membership and gain full access to all member benefits.
Note:{' '}
- Your membership application is still approved. You can complete payment whenever you're ready.
+ Your membership application is still validated. You can complete payment whenever you're ready.
diff --git a/src/pages/Plans.js b/src/pages/Plans.js
index 8459c55..a6fc137 100644
--- a/src/pages/Plans.js
+++ b/src/pages/Plans.js
@@ -47,16 +47,16 @@ const Plans = () => {
canView: true,
canSubscribe: false
},
- pending_approval: {
+ pending_validation: {
title: "Application Under Review",
- message: "Your application is being reviewed by our admin team. You'll receive an email once approved to proceed with payment.",
+ message: "Your application is being reviewed by our admin team. You'll receive an email once validated to proceed with payment.",
action: null,
canView: true,
canSubscribe: false
},
- pre_approved: {
+ pre_validated: {
title: "Application Under Review",
- message: "Your application is being reviewed by our admin team. You'll receive an email once approved to proceed with payment.",
+ message: "Your application is being reviewed by our admin team. You'll receive an email once validated to proceed with payment.",
action: null,
canView: true,
canSubscribe: false
@@ -77,10 +77,31 @@ const Plans = () => {
},
inactive: {
title: "Membership Inactive",
- message: "Your membership has expired. Please select a plan below to renew your membership.",
+ message: "Your membership is currently inactive. Please contact support for assistance.",
+ action: null,
+ canView: true,
+ canSubscribe: false
+ },
+ canceled: {
+ title: "Membership Canceled",
+ message: "Your membership was canceled. You can rejoin by selecting a plan below.",
action: null,
canView: true,
canSubscribe: true
+ },
+ expired: {
+ title: "Membership Expired",
+ message: "Your membership has expired. Please renew by selecting a plan below.",
+ action: null,
+ canView: true,
+ canSubscribe: true
+ },
+ abandoned: {
+ title: "Application Incomplete",
+ message: "Your application was not completed. Please contact support to restart the registration process.",
+ action: null,
+ canView: true,
+ canSubscribe: false
}
};
@@ -315,7 +336,7 @@ const Plans = () => {
Processing...
>
) : statusInfo && !statusInfo.canSubscribe ? (
- 'Approval Required'
+ 'Validation Required'
) : (
'Choose Amount & Subscribe'
)}
diff --git a/src/pages/PrivacyPolicy.js b/src/pages/PrivacyPolicy.js
new file mode 100644
index 0000000..f800eaa
--- /dev/null
+++ b/src/pages/PrivacyPolicy.js
@@ -0,0 +1,249 @@
+import React from 'react';
+import { Link } from 'react-router-dom';
+import PublicNavbar from '../components/PublicNavbar';
+import PublicFooter from '../components/PublicFooter';
+
+export default function PrivacyPolicy() {
+ return (
+ <>
+
+
+
+ {/* Header */}
+
+
+ Privacy Policy
+
+
+ LOAFers, Inc. Website Privacy Policy
+
+
+
+ {/* Content */}
+
+
+ {/* Introduction */}
+
+
+
+ This Privacy Policy ("Policy") applies to Membership Applications, and LOAFers, Inc. ("Company") and governs data collection and usage. The Company's application is a Membership request, Membership online profile, and Consent to receive eNewsletters. By using the Company application, you consent to the data practices described in the statement.
+
+
+ We reserve the right to change this policy at any given time, of which you will be promptly updated. If you want to make sure that you are up to date with the latest changes, we advise you to frequently visit this page.
+
+
+
+
+ {/* Section 1: What User Data We Collect */}
+
+
+ 💻 What User Data We Collect
+
+
+
+ When you visit the Site, we may collect the following data:
+
+
+
Your IP address
+
Your contact information and email address
+
+
+
+ When you apply for membership, we collect the following data:
+
+
+
First and last name
+
Mailing address
+
Email
+
Phone number
+
Birthday
+
+
+
+ If you choose to pay your membership administrative fee online, we have access to:
+
+
+
Partial credit card information
+
+
+
+ You may also choose to provide the following:
+
+
+
Partner's name
+
Photo
+
Self-bio
+
Consent to receive our eNewsletter
+
Consent to display an online profile visible only to membership
+
+
+
+
+ {/* Section 2: Why We Collect Your Data */}
+
+
+ 🎯 Why We Collect Your Data
+
+
+
+
To send you announcement emails containing the information about our events and information we think you will find interesting.
+
To contact you to fill out surveys about our membership.
+
To customize our blog according to your online behavior and personal preferences.
+
+
+
+
+ {/* Section 3: Sharing Information with Third Parties */}
+
+
+ 🤝 Sharing Information with Third Parties
+
+
+
+ The Company does not sell, rent, or lease personal data to third parties.
+
+
+ The Company may share data with trusted partners to help perform statistical analysis, provide customer support.
+
+
+ The Company uses Stripe to process online payments at which time users would no longer be governed by the Company's Privacy Policy.
+
+
+ The Company may disclose your personal information, without notice, if required to do so by law.
+
+
+
+
+ {/* Section 4: Safeguarding and Securing the Data */}
+
+
+ 🔒 Safeguarding and Securing the Data
+
+
+
+ LOAFers, Inc. is committed to securing your data and keeping it confidential. LOAFers, Inc. has done all in its power to prevent data theft, unauthorized access, and disclosure by implementing the latest technologies and software, which help us safeguard all the information we collect online.
+
+
+
+
+ {/* Section 5: Our Cookie Policy */}
+
+
+ 🍪 Our Cookie Policy
+
+
+
+ Once you agree to allow our blog to use cookies, you also agree to use the data it collects regarding your online behavior (analyze web traffic, web pages you visit and spend the most time on).
+
+
+ The data we collect by using cookies is used to customize our blog to your needs. After we use the data for statistical analysis, the data is completely removed from our systems.
+
+
+ Please note that cookies don't allow us to gain control of your computer in any way. They are strictly used to monitor which pages you find useful and which you do not so that we can provide a better experience for you.
+
+
+ If you want to disable cookies, you can do it by accessing the settings of your internet browser. You can visit{' '}
+
+ https://www.internetcookies.com
+ , which contains comprehensive information on how to do this on a wide variety of browsers and devices.
+
+
+
+
+ {/* Section 6: Links to Other Websites */}
+
+
+ 🔗 Links to Other Websites
+
+
+
+ Our blog contains links that lead to other websites. If you click on these links LOAFers, Inc. is not held responsible for your data and privacy protection. Visiting those websites is not governed by this privacy policy agreement. Make sure to read the privacy policy documentation of the website you go to from our website.
+
+
+
+
+ {/* Section 7: Restricting the Collection of your Personal Data */}
+
+
+ 🚫 Restricting the Collection of your Personal Data
+
+
+
+ At some point, you might wish to restrict the use and collection of your personal data. You can achieve this by doing the following:
+
+
+
Log in to your online profile and make any changes you wish to your profile information.
+
If you have already agreed to share your information with us, feel free to contact us via email and we will be more than happy to change this for you.
+
+
+
+
+ {/* Section 8: Children Under Thirteen */}
+
+
+ 👶 Children Under Thirteen
+
+
+
+ The Company does not knowingly collect information from children under the age of 13.
+
+
+
+
+ {/* Section 9: Changes to this Statement */}
+
+
+ 🗓️ Changes to this Statement
+
+
+
+ The Company may make changes to this Policy. When this occurs the effective date of this policy will be updated.
+
+
+
+
+ {/* Section 10: Contact Information */}
+
+
+ 📧 Contact Information
+
+
+
+ If you have any questions, please contact LOAFers, Inc. at:
+
+
+ >
+ );
+}
diff --git a/src/pages/TermsOfService.js b/src/pages/TermsOfService.js
new file mode 100644
index 0000000..a76430d
--- /dev/null
+++ b/src/pages/TermsOfService.js
@@ -0,0 +1,317 @@
+import React from 'react';
+import { Link } from 'react-router-dom';
+import PublicNavbar from '../components/PublicNavbar';
+import PublicFooter from '../components/PublicFooter';
+
+export default function TermsOfService() {
+ return (
+ <>
+
+
+
+ {/* Header */}
+
+
+ Terms of Service
+
+
+ Last Updated: January 2025
+
+
+
+ {/* Content */}
+
+
+ {/* Section 1: Agreement to Terms */}
+
+
+ 1. Agreement to Terms
+
+
+
+ These Terms of Service constitute a legally binding agreement made between you, whether personally or on behalf of an entity ("you") and LOAFers, Inc. ("Company", "we", "us", or "our"), concerning your access to and use of the https://loaftx.org website as well as any other media form, media channel, mobile website or mobile application related, linked, or otherwise connected thereto (collectively, the "Site").
+
+
+ You agree that by accessing the Site, you have read, understood, and agree to be bound by all of these Terms of Service. If you do not agree with all of these Terms of Service, then you are expressly prohibited from using the Site and you must discontinue use immediately.
+
+ Unless otherwise indicated, the Site is our proprietary property and all source code, databases, functionality, software, website designs, audio, video, text, photographs, and graphics on the Site (collectively, the "Content") and the trademarks, service marks, and logos contained therein (the "Marks") are owned or controlled by us or licensed to us, and are protected by copyright and trademark laws and various other intellectual property rights and unfair competition laws of the United States, foreign jurisdictions, and international conventions.
+
+
+
+
+ {/* Section 3: User Representations */}
+
+
+ 3. User Representations
+
+
+
+ By using the Site, you represent and warrant that:
+
+
+
All registration information you submit will be true, accurate, current, and complete
+
You will maintain the accuracy of such information and promptly update such registration information as necessary
+
You have the legal capacity and you agree to comply with these Terms of Service
+
You are not under the age of 13
+
Not a minor in the jurisdiction in which you reside, or if a minor, you have received parental permission to use the Site
+
You will not access the Site through automated or non-human means
+
You will not use the Site for any illegal or unauthorized purpose
+
Your use of the Site will not violate any applicable law or regulation
+ You may not access or use the Site for any purpose other than that for which we make the Site available. The Site may not be used in connection with any commercial endeavors except those that are specifically endorsed or approved by us. As a user of the Site, you agree not to:
+
+
+
Systematically retrieve data or other content from the Site to create or compile, directly or indirectly, a collection, compilation, database, or directory without written permission from us
+
Make any unauthorized use of the Site, including collecting usernames and/or email addresses of users by electronic or other means for the purpose of sending unsolicited email, or creating user accounts by automated means or under false pretenses
+
Circumvent, disable, or otherwise interfere with security-related features of the Site
+
Engage in unauthorized framing of or linking to the Site
+
Trick, defraud, or mislead us and other users, especially in any attempt to learn sensitive account information such as user passwords
+
Make improper use of our support services or submit false reports of abuse or misconduct
+
Engage in any automated use of the system, such as using scripts to send comments or messages
+
Interfere with, disrupt, or create an undue burden on the Site or the networks or services connected to the Site
+ The Site may invite you to chat, contribute to, or participate in blogs, message boards, online forums, and other functionality, and may provide you with the opportunity to create, submit, post, display, transmit, perform, publish, distribute, or broadcast content and materials to us or on the Site.
+
+
+
+
+ {/* Section 6: Contribution License */}
+
+
+ 6. Contribution License
+
+
+
+ By posting your Contributions to any part of the Site, you automatically grant, and you represent and warrant that you have the right to grant, to us an unrestricted, unlimited, irrevocable, perpetual, non-exclusive, transferable, royalty-free, fully-paid, worldwide right, and license to host, use, copy, reproduce, disclose, sell, resell, publish, broadcast, retitle, archive, store, cache, publicly perform, publicly display, reformat, translate, transmit, excerpt (in whole or in part), and distribute such Contributions.
+
+
+
+
+ {/* Section 7: Submissions */}
+
+
+ 7. Submissions
+
+
+
+ You acknowledge and agree that any questions, comments, suggestions, ideas, feedback, or other information regarding the Site ("Submissions") provided by you to us are non-confidential and shall become our sole property.
+
+
+
+
+ {/* Section 8: Site Management */}
+
+
+ 8. Site Management
+
+
+
+ We reserve the right, but not the obligation, to: (1) monitor the Site for violations of these Terms of Service; (2) take appropriate legal action against anyone who, in our sole discretion, violates the law or these Terms of Service; (3) refuse, restrict access to, limit the availability of, or disable (to the extent technologically feasible) any of your Contributions; (4) remove from the Site or otherwise disable all files and content that are excessive in size or are in any way burdensome to our systems.
+
+
+
+
+ {/* Section 9: Term and Termination */}
+
+
+ 9. Term and Termination
+
+
+
+ These Terms of Service shall remain in full force and effect while you use the Site. Without limiting any other provision of these Terms of Service, we reserve the right to, in our sole discretion and without notice or liability, deny access to and use of the Site to any person for any reason or for no reason.
+
+ We reserve the right to change, modify, or remove the contents of the Site at any time or for any reason at our sole discretion without notice. We also reserve the right to modify or discontinue all or part of the Site without notice at any time.
+
+
+
+
+ {/* Section 11: Governing Law */}
+
+
+ 11. Governing Law
+
+
+
+ These Terms of Service and your use of the Site are governed by and construed in accordance with the laws of the State of Texas applicable to agreements made and to be entirely performed within the State of Texas, without regard to its conflict of law principles.
+
+
+
+
+ {/* Section 12: Dispute Resolution */}
+
+
+ 12. Dispute Resolution
+
+
+
+ Any legal action of whatever nature brought by either you or us shall be commenced or prosecuted in the state and federal courts located in Harris County, Texas, and the parties hereby consent to, and waive all defenses of lack of personal jurisdiction and forum non conveniens with respect to venue and jurisdiction in such state and federal courts.
+
+
+
+
+ {/* Section 13: Corrections */}
+
+
+ 13. Corrections
+
+
+
+ There may be information on the Site that contains typographical errors, inaccuracies, or omissions that may relate to the Site, including descriptions, pricing, availability, and various other information. We reserve the right to correct any errors, inaccuracies, or omissions and to change or update the information on the Site at any time, without prior notice.
+
+
+
+
+ {/* Section 14: Disclaimer */}
+
+
+ 14. Disclaimer
+
+
+
+ The Site is provided on an as-is and as-available basis. You agree that your use of the Site and our services will be at your sole risk. To the fullest extent permitted by law, we disclaim all warranties, express or implied, in connection with the Site and your use thereof.
+
+ In no event will we or our directors, employees, or agents be liable to you or any third party for any direct, indirect, consequential, exemplary, incidental, special, or punitive damages, including lost profit, lost revenue, loss of data, or other damages arising from your use of the Site.
+
+
+
+
+ {/* Section 16: Indemnification */}
+
+
+ 16. Indemnification
+
+
+
+ You agree to defend, indemnify, and hold us harmless, including our subsidiaries, affiliates, and all of our respective officers, agents, partners, and employees, from and against any loss, damage, liability, claim, or demand, including reasonable attorneys' fees and expenses, made by any third party due to or arising out of your use of the Site or breach of these Terms of Service.
+
+
+
+
+ {/* Section 17: User Data */}
+
+
+ 17. User Data
+
+
+
+ We will maintain certain data that you transmit to the Site for the purpose of managing the Site, as well as data relating to your use of the Site. Although we perform regular routine backups of data, you are solely responsible for all data that you transmit or that relates to any activity you have undertaken using the Site.
+
+ 18. Electronic Communications, Transactions, and Signatures
+
+
+
+ Visiting the Site, sending us emails, and completing online forms constitute electronic communications. You consent to receive electronic communications, and you agree that all agreements, notices, disclosures, and other communications we provide to you electronically, via email and on the Site, satisfy any legal requirement that such communication be in writing.
+
+
+
+
+ {/* Section 19: Contact Us */}
+
+
+ 19. Contact Us
+
+
+
+ In order to resolve a complaint regarding the Site or to receive further information regarding use of the Site, please contact us at:
+
+ 💡 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/AdminEvents.js b/src/pages/admin/AdminEvents.js
index 7eb418d..9c8b89c 100644
--- a/src/pages/admin/AdminEvents.js
+++ b/src/pages/admin/AdminEvents.js
@@ -1,4 +1,5 @@
import React, { useEffect, useState } from 'react';
+import { useAuth } from '../../context/AuthContext';
import api from '../../utils/api';
import { Card } from '../../components/ui/card';
import { Button } from '../../components/ui/button';
@@ -10,6 +11,7 @@ import { Calendar, MapPin, Users, Plus, Edit, Trash2, Eye, EyeOff } from 'lucide
import { AttendanceDialog } from '../../components/AttendanceDialog';
const AdminEvents = () => {
+ const { hasPermission } = useAuth();
const [events, setEvents] = useState([]);
const [loading, setLoading] = useState(true);
const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false);
@@ -141,6 +143,7 @@ const AdminEvents = () => {
+ {(hasPermission('events.create') || hasPermission('events.edit')) && (
+ )}
{/* Events List */}
diff --git a/src/pages/admin/AdminMembers.js b/src/pages/admin/AdminMembers.js
index f1ad30d..b581e6b 100644
--- a/src/pages/admin/AdminMembers.js
+++ b/src/pages/admin/AdminMembers.js
@@ -1,18 +1,30 @@
import React, { useEffect, useState } from 'react';
import { useNavigate, useLocation, 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';
import { Badge } from '../../components/ui/badge';
import { Input } from '../../components/ui/input';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../../components/ui/select';
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuTrigger,
+} from '../../components/ui/dropdown-menu';
import { toast } from 'sonner';
-import { Users, Search, User, CreditCard, Eye, CheckCircle } from 'lucide-react';
+import { Users, Search, User, CreditCard, Eye, CheckCircle, Calendar, AlertCircle, Clock, Mail, UserPlus, Upload, Download, FileDown, ChevronDown } from 'lucide-react';
import PaymentActivationDialog from '../../components/PaymentActivationDialog';
+import ConfirmationDialog from '../../components/ConfirmationDialog';
+import CreateMemberDialog from '../../components/CreateMemberDialog';
+import InviteStaffDialog from '../../components/InviteStaffDialog';
+import ImportMembersDialog from '../../components/ImportMembersDialog';
const AdminMembers = () => {
const navigate = useNavigate();
const location = useLocation();
+ const { hasPermission } = useAuth();
const [users, setUsers] = useState([]);
const [filteredUsers, setFilteredUsers] = useState([]);
const [loading, setLoading] = useState(true);
@@ -20,6 +32,13 @@ const AdminMembers = () => {
const [statusFilter, setStatusFilter] = useState('active');
const [paymentDialogOpen, setPaymentDialogOpen] = useState(false);
const [selectedUserForPayment, setSelectedUserForPayment] = useState(null);
+ const [statusChanging, setStatusChanging] = useState(null);
+ const [confirmDialogOpen, setConfirmDialogOpen] = useState(false);
+ const [pendingStatusChange, setPendingStatusChange] = useState(null);
+ const [createDialogOpen, setCreateDialogOpen] = useState(false);
+ const [inviteDialogOpen, setInviteDialogOpen] = useState(false);
+ const [importDialogOpen, setImportDialogOpen] = useState(false);
+ const [exporting, setExporting] = useState(false);
useEffect(() => {
fetchMembers();
@@ -70,14 +89,127 @@ const AdminMembers = () => {
fetchMembers(); // Refresh list
};
+ const handleStatusChangeRequest = (userId, currentStatus, newStatus, user) => {
+ // Skip confirmation if status didn't actually change
+ if (currentStatus === newStatus) return;
+
+ setPendingStatusChange({ userId, newStatus, user });
+ setConfirmDialogOpen(true);
+ };
+
+ const confirmStatusChange = async () => {
+ if (!pendingStatusChange) return;
+
+ const { userId, newStatus } = pendingStatusChange;
+ setStatusChanging(userId);
+ setConfirmDialogOpen(false);
+
+ try {
+ await api.put(`/admin/users/${userId}/status`, { status: newStatus });
+ toast.success('Member status updated successfully');
+ fetchMembers(); // Refresh list
+ } catch (error) {
+ toast.error(error.response?.data?.detail || 'Failed to update status');
+ } finally {
+ setStatusChanging(null);
+ setPendingStatusChange(null);
+ }
+ };
+
+ const handleExport = async (filterType) => {
+ setExporting(true);
+ try {
+ let params = {};
+ if (filterType === 'current') {
+ if (statusFilter && statusFilter !== 'all') {
+ params.status = statusFilter;
+ }
+ if (searchQuery) {
+ params.search = searchQuery;
+ }
+ }
+ // filterType === 'all' will export all members without filters
+
+ const response = await api.get('/admin/users/export', {
+ params,
+ responseType: 'blob'
+ });
+
+ // Create download link
+ const url = window.URL.createObjectURL(new Blob([response.data]));
+ const link = document.createElement('a');
+ link.href = url;
+ link.setAttribute('download', `members_export_${new Date().toISOString().split('T')[0]}.csv`);
+ document.body.appendChild(link);
+ link.click();
+ link.remove();
+
+ toast.success('Members exported successfully');
+ } catch (error) {
+ toast.error('Failed to export members');
+ } finally {
+ setExporting(false);
+ }
+ };
+
+ const getStatusChangeMessage = () => {
+ if (!pendingStatusChange) return {};
+
+ const { newStatus, user } = pendingStatusChange;
+ const userName = `${user.first_name} ${user.last_name}`;
+
+ const messages = {
+ payment_pending: {
+ title: 'Revert to Payment Pending?',
+ description: `This will change ${userName}'s status back to Payment Pending. They will need to complete payment again to become active.`,
+ variant: 'warning',
+ confirmText: 'Yes, Revert Status',
+ },
+ active: {
+ title: 'Activate Member?',
+ description: `This will activate ${userName}'s membership. They will gain full access to member features and resources.`,
+ variant: 'success',
+ confirmText: 'Yes, Activate',
+ },
+ inactive: {
+ title: 'Deactivate Member?',
+ description: `This will deactivate ${userName}'s membership. They will lose access to member-only features but their data will be preserved.`,
+ variant: 'warning',
+ confirmText: 'Yes, Deactivate',
+ },
+ canceled: {
+ title: 'Cancel Membership?',
+ description: `This will mark ${userName}'s membership as canceled. This indicates they voluntarily ended their membership. Their subscription will not auto-renew.`,
+ variant: 'danger',
+ confirmText: 'Yes, Cancel Membership',
+ },
+ expired: {
+ title: 'Mark Membership as Expired?',
+ description: `This will mark ${userName}'s membership as expired. This indicates their subscription period has ended without renewal.`,
+ variant: 'warning',
+ confirmText: 'Yes, Mark as Expired',
+ },
+ };
+
+ return messages[newStatus] || {
+ title: 'Confirm Status Change',
+ description: `Are you sure you want to change ${userName}'s status to ${newStatus}?`,
+ variant: 'warning',
+ confirmText: 'Confirm',
+ };
+ };
+
const getStatusBadge = (status) => {
const config = {
pending_email: { label: 'Pending Email', className: 'bg-orange-100 text-orange-700' },
- pending_approval: { label: 'Pending Approval', className: 'bg-gray-200 text-gray-700' },
- pre_approved: { label: 'Pre-Approved', className: 'bg-[#81B29A] text-white' },
+ pending_validation: { label: 'Pending Validation', className: 'bg-gray-200 text-gray-700' },
+ pre_validated: { label: 'Pre-Validated', className: 'bg-[#81B29A] text-white' },
payment_pending: { label: 'Payment Pending', className: 'bg-orange-500 text-white' },
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' },
+ canceled: { label: 'Canceled', className: 'bg-red-100 text-red-700' },
+ expired: { label: 'Expired', className: 'bg-red-500 text-white' },
+ abandoned: { label: 'Abandoned', className: 'bg-gray-300 text-gray-600' }
};
const statusConfig = config[status] || config.inactive;
@@ -88,15 +220,102 @@ const AdminMembers = () => {
);
};
+ const getReminderInfo = (user) => {
+ const emailReminders = user.email_verification_reminders_sent || 0;
+ const eventReminders = user.event_attendance_reminders_sent || 0;
+ const paymentReminders = user.payment_reminders_sent || 0;
+ const renewalReminders = user.renewal_reminders_sent || 0;
+ const totalReminders = emailReminders + eventReminders + paymentReminders + renewalReminders;
+
+ return {
+ emailReminders,
+ eventReminders,
+ paymentReminders,
+ renewalReminders,
+ totalReminders,
+ lastReminderAt: user.last_email_verification_reminder_at ||
+ user.last_event_attendance_reminder_at ||
+ user.last_payment_reminder_at ||
+ user.last_renewal_reminder_at
+ };
+ };
+
return (
<>
-
- Members Management
-
-
- Manage paying members and their subscriptions.
-
+
+
+
+ Members Management
+
+
+ Manage paying members and their subscriptions.
+
+
+ {/* Create Role Modal */}
+
+
+ {/* Edit Role Modal */}
+
+
+ {/* Manage Permissions Modal */}
+
+
+ {/* Delete Confirmation */}
+
+
+
+ Delete Role?
+
+ Are you sure you want to delete the role "{selectedRole?.name}"?
+ This action cannot be undone. Users with this role will need to be reassigned.
+
+
+
+ Cancel
+
+ Delete Role
+
+
+
+
+