humanizerx / src /components /SettingsPanel.tsx
mmrwinston001's picture
Upload 32 files (#3)
6ce679b verified
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;