Spaces:
Configuration error
Configuration error
| import React, { useState, useContext, useEffect } from 'react'; | |
| import { | |
| Bell, Globe, Moon, Sun, Monitor, Lock, | |
| Smartphone, Mail, LogOut, Trash2 | |
| } from 'lucide-react'; | |
| import { UserProfile, UserPreferences } from '../types/user'; | |
| import { ThemeContext } from '../contexts/ThemeContext'; | |
| interface SettingsPanelProps { | |
| user: UserProfile | null; | |
| onUpdatePreferences: (preferences: Partial<UserPreferences>) => void; | |
| onSignOut: () => void; | |
| onDeleteAccount: () => void; | |
| } | |
| const SettingsPanel: React.FC<SettingsPanelProps> = ({ | |
| user, | |
| onUpdatePreferences, | |
| onSignOut, | |
| onDeleteAccount | |
| }) => { | |
| const [activeSection, setActiveSection] = useState('general'); | |
| const { theme, preference, setPreference } = useContext(ThemeContext); | |
| const [localPreferences, setLocalPreferences] = useState<UserPreferences>({ | |
| timezone: 'UTC', | |
| emailNotifications: false, | |
| pushNotifications: false, | |
| marketingEmails: false, | |
| twoFactorEnabled: false, | |
| profileVisibility: 'public', | |
| theme: preference || 'dark', | |
| ...(user?.preferences || {}), | |
| }); | |
| const [showSignOutModal, setShowSignOutModal] = useState(false); | |
| const [showDeleteModal, setShowDeleteModal] = useState(false); | |
| // Wait for user | |
| if (!user) return <div className="p-6 text-gray-700">Loading...</div>; | |
| // Sync system theme if system is selected | |
| useEffect(() => { | |
| if (localPreferences.theme !== 'system') return; | |
| const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); | |
| const listener = () => setPreference('system'); | |
| mediaQuery.addEventListener('change', listener); | |
| return () => mediaQuery.removeEventListener('change', listener); | |
| }, [localPreferences.theme, setPreference]); | |
| const handleSelectTheme = (value: 'light' | 'dark' | 'system') => { | |
| setLocalPreferences(prev => ({ ...prev, theme: value })); | |
| setPreference(value); | |
| onUpdatePreferences({ theme: value }); | |
| }; | |
| const handleToggle = (key: keyof UserPreferences) => { | |
| if (typeof localPreferences[key] === 'boolean') { | |
| const updated = { ...localPreferences, [key]: !localPreferences[key] }; | |
| setLocalPreferences(updated); | |
| onUpdatePreferences({ [key]: updated[key] }); | |
| } | |
| }; | |
| const sections = [ | |
| { id: 'general', label: 'General', icon: Globe }, | |
| { id: 'notifications', label: 'Notifications', icon: Bell }, | |
| { id: 'account', label: 'Account', icon: Lock } | |
| ]; | |
| const bgClass = theme === 'dark' ? 'bg-gray-900' : 'bg-white'; | |
| const borderClass = theme === 'dark' ? 'border-gray-700' : 'border-gray-200'; | |
| const textClass = theme === 'dark' ? 'text-white' : 'text-gray-900'; | |
| const secondaryTextClass = theme === 'dark' ? 'text-gray-300' : 'text-gray-700'; | |
| const hoverBgClass = theme === 'dark' ? 'hover:bg-gray-800' : 'hover:bg-gray-100'; | |
| // ===== Sections ===== | |
| const renderGeneralSettings = () => ( | |
| <div className="space-y-6"> | |
| <div> | |
| <h3 className={`text-lg font-semibold mb-4 ${textClass}`}>Appearance</h3> | |
| <div className="grid grid-cols-3 gap-3"> | |
| {[ | |
| { value: 'light', label: 'Light', icon: Sun }, | |
| { value: 'dark', label: 'Dark', icon: Moon }, | |
| { value: 'system', label: 'System', icon: Monitor } | |
| ].map(({ value, label, icon: Icon }) => ( | |
| <button | |
| key={value} | |
| onClick={() => handleSelectTheme(value as 'light' | 'dark' | 'system')} | |
| className={`flex items-center gap-2 p-3 rounded-lg border transition ${ | |
| localPreferences.theme === value | |
| ? 'border-blue-500 bg-blue-600 text-white' | |
| : `${borderClass} ${hoverBgClass} ${textClass}` | |
| }`} | |
| > | |
| <Icon className="w-4 h-4" /> | |
| <span className="text-sm font-medium">{label}</span> | |
| </button> | |
| ))} | |
| </div> | |
| </div> | |
| <div> | |
| <h3 className={`text-lg font-semibold mb-4 ${textClass}`}>Timezone</h3> | |
| <select | |
| value={localPreferences.timezone} | |
| onChange={e => { | |
| const updated = { ...localPreferences, timezone: e.target.value }; | |
| setLocalPreferences(updated); | |
| onUpdatePreferences({ timezone: e.target.value }); | |
| }} | |
| className={`w-full px-4 py-3 border rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent ${borderClass} ${bgClass} ${textClass}`} | |
| > | |
| <option value="UTC">UTC</option> | |
| <option value="America/New_York">Eastern Time</option> | |
| <option value="America/Chicago">Central Time</option> | |
| <option value="America/Denver">Mountain Time</option> | |
| <option value="America/Los_Angeles">Pacific Time</option> | |
| <option value="Europe/London">London</option> | |
| <option value="Europe/Paris">Paris</option> | |
| <option value="Asia/Tokyo">Tokyo</option> | |
| </select> | |
| </div> | |
| </div> | |
| ); | |
| const renderNotificationSettings = () => ( | |
| <div className="space-y-6"> | |
| <h3 className={`text-lg font-semibold mb-4 ${textClass}`}>Notifications</h3> | |
| {[ | |
| { label: 'Email Notifications', key: 'emailNotifications', icon: Mail }, | |
| { label: 'Push Notifications', key: 'pushNotifications', icon: Smartphone }, | |
| { label: 'Marketing Emails', key: 'marketingEmails', icon: Bell } | |
| ].map(({ label, key, icon: Icon }) => ( | |
| <div key={key} className={`flex items-center justify-between p-4 border rounded-lg ${borderClass}`}> | |
| <div className="flex items-center gap-3"> | |
| <Icon className={`w-5 h-5 ${secondaryTextClass}`} /> | |
| <p className={`font-medium ${textClass}`}>{label}</p> | |
| </div> | |
| <button | |
| onClick={() => handleToggle(key as keyof UserPreferences)} | |
| className={`relative inline-flex h-6 w-11 items-center rounded-full transition ${ | |
| localPreferences[key as keyof UserPreferences] ? 'bg-blue-600' : 'bg-gray-500' | |
| }`} | |
| > | |
| <span | |
| className={`inline-block h-4 w-4 transform rounded-full bg-white transition ${ | |
| localPreferences[key as keyof UserPreferences] ? 'translate-x-6' : 'translate-x-1' | |
| }`} | |
| /> | |
| </button> | |
| </div> | |
| ))} | |
| </div> | |
| ); | |
| const renderAccountSettings = () => ( | |
| <div className="space-y-6"> | |
| <h3 className={`text-lg font-semibold mb-4 ${textClass}`}>Account Information</h3> | |
| <div className={`rounded-lg p-4 space-y-3 ${bgClass} ${borderClass}`}> | |
| <div className="flex justify-between"> | |
| <span className={`text-sm font-medium ${secondaryTextClass}`}>Email</span> | |
| <span className="text-sm font-medium">{user.email}</span> | |
| </div> | |
| <div className="flex justify-between"> | |
| <span className={`text-sm font-medium ${secondaryTextClass}`}>Username</span> | |
| <span className="text-sm font-medium">@{user.username}</span> | |
| </div> | |
| <div className="flex justify-between"> | |
| <span className={`text-sm font-medium ${secondaryTextClass}`}>Member Since</span> | |
| <span className="text-sm font-medium">{user.joinDate}</span> | |
| </div> | |
| </div> | |
| <h3 className={`text-lg font-semibold mb-4 mt-6 ${textClass}`}>Danger Zone</h3> | |
| <div className={`rounded-lg p-4 space-y-4 ${borderClass}`}> | |
| <button | |
| onClick={() => setShowSignOutModal(true)} | |
| className="w-full flex items-center justify-center gap-2 px-4 py-3 rounded-lg bg-indigo-600 text-white hover:bg-indigo-700 transition" | |
| > | |
| <LogOut className="w-4 h-4" /> Sign Out | |
| </button> | |
| <button | |
| onClick={() => setShowDeleteModal(true)} | |
| className="w-full flex items-center justify-center gap-2 px-4 py-3 rounded-lg bg-red-600 text-white hover:bg-red-700 transition" | |
| > | |
| <Trash2 className="w-4 h-4" /> Delete Account | |
| </button> | |
| </div> | |
| </div> | |
| ); | |
| const renderContent = () => { | |
| switch (activeSection) { | |
| case 'general': return renderGeneralSettings(); | |
| case 'notifications': return renderNotificationSettings(); | |
| case 'account': return renderAccountSettings(); | |
| default: return renderGeneralSettings(); | |
| } | |
| }; | |
| return ( | |
| <div className={`rounded-xl border overflow-hidden ${bgClass} ${borderClass}`}> | |
| <div className={`border-b ${borderClass}`}> | |
| <nav className="flex space-x-8 px-6"> | |
| {sections.map(section => { | |
| const Icon = section.icon; | |
| return ( | |
| <button | |
| key={section.id} | |
| onClick={() => setActiveSection(section.id)} | |
| className={`flex items-center gap-2 py-4 px-2 border-b-2 font-medium text-sm transition ${ | |
| activeSection === section.id | |
| ? 'border-blue-500 text-blue-500' | |
| : `border-transparent ${secondaryTextClass} hover:text-gray-300 dark:hover:text-gray-300` | |
| }`} | |
| > | |
| <Icon className="w-4 h-4" /> | |
| {section.label} | |
| </button> | |
| ); | |
| })} | |
| </nav> | |
| </div> | |
| <div className="p-6">{renderContent()}</div> | |
| {/* Sign Out Modal */} | |
| {showSignOutModal && ( | |
| <div className="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50 z-50"> | |
| <div className="bg-white dark:bg-gray-800 p-6 rounded-xl shadow-xl w-80 text-center"> | |
| <h2 className="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4"> | |
| Confirm Sign Out | |
| </h2> | |
| <p className="text-gray-700 dark:text-gray-300 mb-6"> | |
| Are you sure you want to sign out? | |
| </p> | |
| <div className="flex justify-center gap-4"> | |
| <button | |
| onClick={() => { | |
| onSignOut(); | |
| setShowSignOutModal(false); | |
| }} | |
| className="px-4 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700 transition" | |
| > | |
| Yes, Sign Out | |
| </button> | |
| <button | |
| onClick={() => setShowSignOutModal(false)} | |
| className="px-4 py-2 bg-gray-300 dark:bg-gray-700 text-gray-800 dark:text-gray-200 rounded-lg hover:bg-gray-400 dark:hover:bg-gray-600 transition" | |
| > | |
| Cancel | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| )} | |
| {/* Delete Account Modal */} | |
| {showDeleteModal && ( | |
| <div className="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50 z-50"> | |
| <div className="bg-white dark:bg-gray-800 p-6 rounded-xl shadow-xl w-80 text-center"> | |
| <h2 className="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4"> | |
| Confirm Delete Account | |
| </h2> | |
| <p className="text-gray-700 dark:text-gray-300 mb-6"> | |
| Are you sure you want to delete your account? This action cannot be undone. | |
| </p> | |
| <div className="flex justify-center gap-4"> | |
| <button | |
| onClick={() => { | |
| onDeleteAccount(); | |
| setShowDeleteModal(false); | |
| }} | |
| className="px-4 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700 transition" | |
| > | |
| Yes, Delete | |
| </button> | |
| <button | |
| onClick={() => setShowDeleteModal(false)} | |
| className="px-4 py-2 bg-gray-300 dark:bg-gray-700 text-gray-800 dark:text-gray-200 rounded-lg hover:bg-gray-400 dark:hover:bg-gray-600 transition" | |
| > | |
| Cancel | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| )} | |
| </div> | |
| ); | |
| }; | |
| export default SettingsPanel; | |