- {member.directory_email && (
+ {isFieldEnabled('directory_email') && member.directory_email && (
)}
- {member.directory_phone && (
+ {isFieldEnabled('directory_phone') && member.directory_phone && (
)}
- {member.directory_address && (
+ {isFieldEnabled('directory_address') && member.directory_address && (
{/* Social Media Links */}
- {(member.social_media_facebook || member.social_media_instagram || member.social_media_twitter || member.social_media_linkedin) && (
+ {isFieldEnabled('social_media') && (member.social_media_facebook || member.social_media_instagram || member.social_media_twitter || member.social_media_linkedin) && (
{member.social_media_facebook && (
diff --git a/src/components/SettingsSidebar.js b/src/components/SettingsSidebar.js
index 2c25883..551c827 100644
--- a/src/components/SettingsSidebar.js
+++ b/src/components/SettingsSidebar.js
@@ -1,12 +1,13 @@
import React from 'react';
import { NavLink, useLocation } from 'react-router-dom';
-import { CreditCard, Shield, Star, Palette, FileEdit } from 'lucide-react';
+import { CreditCard, Shield, Star, Palette, FileEdit, BookUser } from 'lucide-react';
const settingsItems = [
{ label: 'Stripe', path: '/admin/settings/stripe', icon: CreditCard },
{ label: 'Permissions', path: '/admin/settings/permissions', icon: Shield },
{ label: 'Theme', path: '/admin/settings/theme', icon: Palette },
-
+ { label: 'Directory', path: '/admin/settings/directory', icon: BookUser },
+ // { label: 'Registration', path: '/admin/settings/registration', icon: FileEdit }, // Commented out for future fallback
];
const SettingsTabs = () => {
diff --git a/src/pages/Login.js b/src/pages/Login.js
index 5bab4b7..efd100b 100644
--- a/src/pages/Login.js
+++ b/src/pages/Login.js
@@ -1,5 +1,5 @@
-import React, { useState } from 'react';
-import { useNavigate, Link } from 'react-router-dom';
+import React, { useState, useEffect } from 'react';
+import { useNavigate, Link, useLocation, useSearchParams } from 'react-router-dom';
import { useAuth } from '../context/AuthContext';
import { Button } from '../components/ui/button';
import { Input } from '../components/ui/input';
@@ -13,6 +13,8 @@ import { ArrowRight, ArrowLeft } from 'lucide-react';
const Login = () => {
const navigate = useNavigate();
+ const location = useLocation();
+ const [searchParams] = useSearchParams();
const { login } = useAuth();
const [loading, setLoading] = useState(false);
const [formData, setFormData] = useState({
@@ -20,6 +22,30 @@ const Login = () => {
password: ''
});
+ // Show session expiry message on mount
+ useEffect(() => {
+ const sessionParam = searchParams.get('session');
+ const stateMessage = location.state?.message;
+
+ if (sessionParam === 'expired') {
+ toast.info('Your session has expired. Please log in again.', {
+ duration: 5000,
+ });
+ // Clean up URL
+ window.history.replaceState({}, '', '/login');
+ } else if (sessionParam === 'idle') {
+ toast.info('You were logged out due to inactivity. Please log in again.', {
+ duration: 5000,
+ });
+ // Clean up URL
+ window.history.replaceState({}, '', '/login');
+ } else if (stateMessage) {
+ toast.info(stateMessage, {
+ duration: 5000,
+ });
+ }
+ }, [searchParams, location.state]);
+
const handleInputChange = (e) => {
const { name, value } = e.target;
setFormData(prev => ({ ...prev, [name]: value }));
diff --git a/src/pages/Profile.js b/src/pages/Profile.js
index b60790a..bea4e3d 100644
--- a/src/pages/Profile.js
+++ b/src/pages/Profile.js
@@ -13,6 +13,7 @@ import { Avatar, AvatarImage, AvatarFallback } from '../components/ui/avatar';
import ChangePasswordDialog from '../components/ChangePasswordDialog';
import PaymentMethodsSection from '../components/PaymentMethodsSection';
import { useNavigate } from 'react-router-dom';
+import useDirectoryConfig from '../hooks/use-directory-config';
const Profile = () => {
const { user } = useAuth();
@@ -29,6 +30,7 @@ const Profile = () => {
const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);
const [initialFormData, setInitialFormData] = useState(null);
const navigate = useNavigate();
+ const { isFieldEnabled, loading: directoryConfigLoading } = useDirectoryConfig();
const [formData, setFormData] = useState({
first_name: '',
last_name: '',
@@ -427,107 +429,121 @@ const Profile = () => {
{/* Member Directory Settings */}
-
-
-
- Member Directory Settings
-
-
- Control your visibility and information in the member directory.
-
+ {isFieldEnabled('show_in_directory') && (
+
+
+
+ Member Directory Settings
+
+
+ Control your visibility and information in the member directory.
+
-
-
-
-
-
- {formData.show_in_directory && (
-
+
+ {formData.show_in_directory && (
+
+ )}
+
+ )}
);
@@ -664,34 +680,36 @@ const Profile = () => {
{/* Volunteer Interests */}
-
-
-
- Volunteer Interests
-
-
- Select areas where you'd like to volunteer and help our community.
-
-
- {volunteerOptions.map(option => (
-
- handleVolunteerToggle(option)}
- className="ui-checkbox"
- />
-
-
- ))}
+ {isFieldEnabled('volunteer_interests') && (
+
+
+
+ Volunteer Interests
+
+
+ Select areas where you'd like to volunteer and help our community.
+
+
+ {volunteerOptions.map(option => (
+
+ handleVolunteerToggle(option)}
+ className="ui-checkbox"
+ />
+
+
+ ))}
+
-
+ )}
);
diff --git a/src/pages/members/MembersDirectory.js b/src/pages/members/MembersDirectory.js
index 126a456..f3ad550 100644
--- a/src/pages/members/MembersDirectory.js
+++ b/src/pages/members/MembersDirectory.js
@@ -20,6 +20,7 @@ import MemberCard from '../../components/MemberCard';
import MemberBadge from '../../components/MemberBadge';
import useMembers from '../../hooks/use-members';
import useMemberTiers from '../../hooks/use-member-tiers';
+import useDirectoryConfig from '../../hooks/use-directory-config';
const MembersDirectory = () => {
const [selectedMember, setSelectedMember] = useState(null);
@@ -28,6 +29,7 @@ const MembersDirectory = () => {
const [currentPage, setCurrentPage] = useState(1);
const pageSize = 12;
const { tiers } = useMemberTiers();
+ const { isFieldEnabled } = useDirectoryConfig();
const allowedRoles = useMemo(() => [], []);
const normalizeStatus = useCallback((status) => {
if (typeof status === 'string') {
@@ -242,7 +244,7 @@ const MembersDirectory = () => {
{selectedMember.first_name} {selectedMember.last_name}
- {selectedMember.directory_partner_name && (
+ {isFieldEnabled('directory_partner_name') && selectedMember.directory_partner_name && (
Partner: {selectedMember.directory_partner_name}
@@ -252,7 +254,7 @@ const MembersDirectory = () => {
{/* Bio */}
- {selectedMember.directory_bio && (
+ {isFieldEnabled('directory_bio') && selectedMember.directory_bio && (
About
@@ -264,79 +266,81 @@ const MembersDirectory = () => {
)}
{/* Contact Information */}
-
-
- Contact Information
-
-
- {selectedMember.directory_email && (
-
-
-
+ {(isFieldEnabled('directory_email') || isFieldEnabled('directory_phone') || isFieldEnabled('directory_address') || isFieldEnabled('directory_dob')) && (
+
+
+ Contact Information
+
+
+ {isFieldEnabled('directory_email') && selectedMember.directory_email && (
+
-
-
- )}
+ )}
- {selectedMember.directory_phone && (
-
-
-
+ {isFieldEnabled('directory_phone') && selectedMember.directory_phone && (
+
-
-
- )}
+ )}
- {selectedMember.directory_address && (
-
-
-
+ {isFieldEnabled('directory_address') && selectedMember.directory_address && (
+
+
+
+
+
+
Address
+
+ {selectedMember.directory_address}
+
+
-
-
Address
-
- {selectedMember.directory_address}
-
-
-
- )}
+ )}
- {selectedMember.directory_dob && (
-
-
-
+ {isFieldEnabled('directory_dob') && selectedMember.directory_dob && (
+
+
+
+
+
+
Birthday
+
+ {formatDate(selectedMember.directory_dob)}
+
+
-
-
Birthday
-
- {formatDate(selectedMember.directory_dob)}
-
-
-
- )}
+ )}
+
-
+ )}
{/* Volunteer Interests */}
- {selectedMember.volunteer_interests && selectedMember.volunteer_interests.length > 0 && (
+ {isFieldEnabled('volunteer_interests') && selectedMember.volunteer_interests && selectedMember.volunteer_interests.length > 0 && (
Volunteer Interests
@@ -355,7 +359,7 @@ const MembersDirectory = () => {
)}
{/* Social Media */}
- {(selectedMember.social_media_facebook || selectedMember.social_media_instagram ||
+ {isFieldEnabled('social_media') && (selectedMember.social_media_facebook || selectedMember.social_media_instagram ||
selectedMember.social_media_twitter || selectedMember.social_media_linkedin) && (
diff --git a/src/utils/api.js b/src/utils/api.js
index b79e35b..fcf140a 100644
--- a/src/utils/api.js
+++ b/src/utils/api.js
@@ -30,6 +30,33 @@ api.interceptors.response.use(
async (error) => {
const config = error.config;
+ // Handle 401 Unauthorized - session expired
+ if (error.response && error.response.status === 401) {
+ // Don't redirect if it's a login request or auth check
+ const isAuthRequest = config?.url?.includes('/auth/login') ||
+ config?.url?.includes('/auth/me') ||
+ config?.url?.includes('/auth/permissions');
+
+ if (!isAuthRequest) {
+ console.warn('[API] Session expired - redirecting to login');
+
+ // Clear auth state
+ localStorage.removeItem('token');
+
+ // Dispatch custom event for components to react
+ window.dispatchEvent(new CustomEvent('auth:session-expired'));
+
+ // Redirect to login with session expired message
+ // Use replace to prevent back button issues
+ const currentPath = window.location.pathname;
+ if (!currentPath.includes('/login')) {
+ window.location.replace('/login?session=expired');
+ }
+ }
+
+ return Promise.reject(error);
+ }
+
// Don't retry if we've already retried or if it's a client error (4xx)
if (!config || config.__isRetry || (error.response && error.response.status < 500)) {
console.error('[API] Request failed:', {