diff --git a/src/hooks/use-directory-config.js b/src/hooks/use-directory-config.js new file mode 100644 index 0000000..c7cb88c --- /dev/null +++ b/src/hooks/use-directory-config.js @@ -0,0 +1,92 @@ +import { useState, useEffect, useCallback } from 'react'; +import api from '../utils/api'; + +/** + * Default directory configuration - used as fallback if API fails + */ +const DEFAULT_DIRECTORY_CONFIG = { + fields: { + show_in_directory: { enabled: true, label: 'Show in Directory', required: false }, + directory_email: { enabled: true, label: 'Directory Email', required: false }, + directory_bio: { enabled: true, label: 'Bio', required: false }, + directory_address: { enabled: true, label: 'Address', required: false }, + directory_phone: { enabled: true, label: 'Phone', required: false }, + directory_dob: { enabled: true, label: 'Birthday', required: false }, + directory_partner_name: { enabled: true, label: 'Partner Name', required: false }, + volunteer_interests: { enabled: true, label: 'Volunteer Interests', required: false }, + social_media: { enabled: true, label: 'Social Media Links', required: false }, + } +}; + +/** + * Hook to fetch and manage directory field configuration + * @returns {Object} - { config, loading, error, isFieldEnabled, getFieldLabel, refetch } + */ +const useDirectoryConfig = () => { + const [config, setConfig] = useState(DEFAULT_DIRECTORY_CONFIG); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + const fetchConfig = useCallback(async () => { + try { + setLoading(true); + setError(null); + const response = await api.get('/directory/config'); + setConfig(response.data || DEFAULT_DIRECTORY_CONFIG); + } catch (err) { + console.error('Failed to fetch directory config:', err); + setError(err); + // Use default config on error + setConfig(DEFAULT_DIRECTORY_CONFIG); + } finally { + setLoading(false); + } + }, []); + + useEffect(() => { + fetchConfig(); + }, [fetchConfig]); + + /** + * Check if a field is enabled in the config + * @param {string} fieldId - The field ID to check (e.g., 'directory_email') + * @returns {boolean} - Whether the field is enabled + */ + const isFieldEnabled = useCallback((fieldId) => { + const field = config?.fields?.[fieldId]; + return field?.enabled !== false; // Default to true if not specified + }, [config]); + + /** + * Get the label for a field + * @param {string} fieldId - The field ID + * @param {string} defaultLabel - Default label if not in config + * @returns {string} - The field label + */ + const getFieldLabel = useCallback((fieldId, defaultLabel = '') => { + const field = config?.fields?.[fieldId]; + return field?.label || defaultLabel; + }, [config]); + + /** + * Check if a field is required + * @param {string} fieldId - The field ID + * @returns {boolean} - Whether the field is required + */ + const isFieldRequired = useCallback((fieldId) => { + const field = config?.fields?.[fieldId]; + return field?.required === true; + }, [config]); + + return { + config, + loading, + error, + isFieldEnabled, + getFieldLabel, + isFieldRequired, + refetch: fetchConfig + }; +}; + +export default useDirectoryConfig; diff --git a/src/pages/admin/AdminDirectorySettings.js b/src/pages/admin/AdminDirectorySettings.js new file mode 100644 index 0000000..812e1b2 --- /dev/null +++ b/src/pages/admin/AdminDirectorySettings.js @@ -0,0 +1,241 @@ +import React, { useState, useEffect } from 'react'; +import api from '../../utils/api'; +import { Card } from '../../components/ui/card'; +import { Button } from '../../components/ui/button'; +import { Switch } from '../../components/ui/switch'; +import { Label } from '../../components/ui/label'; +import { toast } from 'sonner'; +import { BookUser, Save, RotateCcw, Loader2, AlertCircle } from 'lucide-react'; +import { + Alert, + AlertDescription, +} from '../../components/ui/alert'; + +const AdminDirectorySettings = () => { + const [config, setConfig] = useState(null); + const [loading, setLoading] = useState(true); + const [saving, setSaving] = useState(false); + const [hasChanges, setHasChanges] = useState(false); + const [initialConfig, setInitialConfig] = useState(null); + + useEffect(() => { + fetchConfig(); + }, []); + + useEffect(() => { + if (initialConfig && config) { + const changed = JSON.stringify(config) !== JSON.stringify(initialConfig); + setHasChanges(changed); + } + }, [config, initialConfig]); + + const fetchConfig = async () => { + try { + setLoading(true); + const response = await api.get('/admin/directory/config'); + setConfig(response.data.config); + setInitialConfig(response.data.config); + } catch (error) { + console.error('Failed to fetch directory config:', error); + toast.error('Failed to load directory configuration'); + } finally { + setLoading(false); + } + }; + + const handleToggleField = (fieldId) => { + // Don't allow disabling show_in_directory - it's required + if (fieldId === 'show_in_directory') { + toast.error('The "Show in Directory" field cannot be disabled'); + return; + } + + setConfig(prev => ({ + ...prev, + fields: { + ...prev.fields, + [fieldId]: { + ...prev.fields[fieldId], + enabled: !prev.fields[fieldId].enabled + } + } + })); + }; + + const handleSave = async () => { + try { + setSaving(true); + await api.put('/admin/directory/config', { config }); + setInitialConfig(config); + setHasChanges(false); + toast.success('Directory configuration saved successfully'); + } catch (error) { + console.error('Failed to save directory config:', error); + toast.error('Failed to save directory configuration'); + } finally { + setSaving(false); + } + }; + + const handleReset = async () => { + if (!window.confirm('Are you sure you want to reset to default settings? This will enable all directory fields.')) { + return; + } + + try { + setSaving(true); + const response = await api.post('/admin/directory/config/reset'); + setConfig(response.data.config); + setInitialConfig(response.data.config); + setHasChanges(false); + toast.success('Directory configuration reset to defaults'); + } catch (error) { + console.error('Failed to reset directory config:', error); + toast.error('Failed to reset directory configuration'); + } finally { + setSaving(false); + } + }; + + const handleCancel = () => { + setConfig(initialConfig); + setHasChanges(false); + }; + + // Field descriptions for better UX + const fieldDescriptions = { + show_in_directory: 'Master toggle for members to opt-in to the directory (always enabled)', + directory_email: 'Email address visible to other members in the directory', + directory_bio: 'Short biography shown in directory profile and member cards', + directory_address: 'Physical address visible to other members', + directory_phone: 'Phone number visible to other members', + directory_dob: 'Birthday shown in directory profiles', + directory_partner_name: 'Partner name displayed in directory', + volunteer_interests: 'Volunteer interest badges shown in profiles', + social_media: 'Social media links (Facebook, Instagram, Twitter, LinkedIn)', + }; + + // Field icons for better UX + const fieldLabels = { + show_in_directory: 'Show in Directory Toggle', + directory_email: 'Directory Email', + directory_bio: 'Bio / About', + directory_address: 'Address', + directory_phone: 'Phone Number', + directory_dob: 'Birthday', + directory_partner_name: 'Partner Name', + volunteer_interests: 'Volunteer Interests', + social_media: 'Social Media Links', + }; + + if (loading) { + return ( +
+ +
+ ); + } + + return ( +
+ {/* Header */} +
+
+

+ + Directory Field Settings +

+

+ Configure which fields are available in member profiles and the directory +

+
+
+ + {/* Info Alert */} + + + + These settings control which fields appear in the Profile page and Member Directory. + Disabling a field will hide it from both locations. Existing data will be preserved but not displayed. + + + + {/* Fields Configuration */} + +
+ {config && Object.entries(config.fields).map(([fieldId, fieldData]) => ( +
+
+ +

+ {fieldDescriptions[fieldId] || fieldData.description} +

+
+ handleToggleField(fieldId)} + disabled={fieldId === 'show_in_directory'} + className="data-[state=checked]:bg-brand-purple" + /> +
+ ))} +
+
+ + {/* Action Buttons */} +
+ + +
+ {hasChanges && ( + + + Unsaved changes + + )} + + +
+
+
+ ); +}; + +export default AdminDirectorySettings;