|
import React, { useState, useEffect } from 'react'; |
|
import { motion } from 'framer-motion'; |
|
import { toast } from 'react-toastify'; |
|
import { classNames } from '~/utils/classNames'; |
|
import { Switch } from '~/components/ui/Switch'; |
|
import type { UserProfile } from '~/components/@settings/core/types'; |
|
import { isMac } from '~/utils/os'; |
|
|
|
|
|
const getModifierSymbol = (modifier: string): string => { |
|
switch (modifier) { |
|
case 'meta': |
|
return isMac ? '⌘' : 'Win'; |
|
case 'alt': |
|
return isMac ? '⌥' : 'Alt'; |
|
case 'shift': |
|
return '⇧'; |
|
default: |
|
return modifier; |
|
} |
|
}; |
|
|
|
export default function SettingsTab() { |
|
const [currentTimezone, setCurrentTimezone] = useState(''); |
|
const [settings, setSettings] = useState<UserProfile>(() => { |
|
const saved = localStorage.getItem('bolt_user_profile'); |
|
return saved |
|
? JSON.parse(saved) |
|
: { |
|
notifications: true, |
|
language: 'en', |
|
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone, |
|
}; |
|
}); |
|
|
|
useEffect(() => { |
|
setCurrentTimezone(Intl.DateTimeFormat().resolvedOptions().timeZone); |
|
}, []); |
|
|
|
|
|
useEffect(() => { |
|
try { |
|
|
|
const existingProfile = JSON.parse(localStorage.getItem('bolt_user_profile') || '{}'); |
|
|
|
|
|
const updatedProfile = { |
|
...existingProfile, |
|
notifications: settings.notifications, |
|
language: settings.language, |
|
timezone: settings.timezone, |
|
}; |
|
|
|
localStorage.setItem('bolt_user_profile', JSON.stringify(updatedProfile)); |
|
toast.success('Settings updated'); |
|
} catch (error) { |
|
console.error('Error saving settings:', error); |
|
toast.error('Failed to update settings'); |
|
} |
|
}, [settings]); |
|
|
|
return ( |
|
<div className="space-y-4"> |
|
{/* Language & Notifications */} |
|
<motion.div |
|
className="bg-white dark:bg-[#0A0A0A] rounded-lg shadow-sm dark:shadow-none p-4 space-y-4" |
|
initial={{ opacity: 0, y: 20 }} |
|
animate={{ opacity: 1, y: 0 }} |
|
transition={{ delay: 0.1 }} |
|
> |
|
<div className="flex items-center gap-2 mb-4"> |
|
<div className="i-ph:palette-fill w-4 h-4 text-purple-500" /> |
|
<span className="text-sm font-medium text-bolt-elements-textPrimary">Preferences</span> |
|
</div> |
|
|
|
<div> |
|
<div className="flex items-center gap-2 mb-2"> |
|
<div className="i-ph:translate-fill w-4 h-4 text-bolt-elements-textSecondary" /> |
|
<label className="block text-sm text-bolt-elements-textSecondary">Language</label> |
|
</div> |
|
<select |
|
value={settings.language} |
|
onChange={(e) => setSettings((prev) => ({ ...prev, language: e.target.value }))} |
|
className={classNames( |
|
'w-full px-3 py-2 rounded-lg text-sm', |
|
'bg-[#FAFAFA] dark:bg-[#0A0A0A]', |
|
'border border-[#E5E5E5] dark:border-[#1A1A1A]', |
|
'text-bolt-elements-textPrimary', |
|
'focus:outline-none focus:ring-2 focus:ring-purple-500/30', |
|
'transition-all duration-200', |
|
)} |
|
> |
|
<option value="en">English</option> |
|
<option value="es">Español</option> |
|
<option value="fr">Français</option> |
|
<option value="de">Deutsch</option> |
|
<option value="it">Italiano</option> |
|
<option value="pt">Português</option> |
|
<option value="ru">Русский</option> |
|
<option value="zh">中文</option> |
|
<option value="ja">日本語</option> |
|
<option value="ko">한국어</option> |
|
</select> |
|
</div> |
|
|
|
<div> |
|
<div className="flex items-center gap-2 mb-2"> |
|
<div className="i-ph:bell-fill w-4 h-4 text-bolt-elements-textSecondary" /> |
|
<label className="block text-sm text-bolt-elements-textSecondary">Notifications</label> |
|
</div> |
|
<div className="flex items-center justify-between"> |
|
<span className="text-sm text-bolt-elements-textSecondary"> |
|
{settings.notifications ? 'Notifications are enabled' : 'Notifications are disabled'} |
|
</span> |
|
<Switch |
|
checked={settings.notifications} |
|
onCheckedChange={(checked) => { |
|
// Update local state |
|
setSettings((prev) => ({ ...prev, notifications: checked })); |
|
|
|
// Update localStorage immediately |
|
const existingProfile = JSON.parse(localStorage.getItem('bolt_user_profile') || '{}'); |
|
const updatedProfile = { |
|
...existingProfile, |
|
notifications: checked, |
|
}; |
|
localStorage.setItem('bolt_user_profile', JSON.stringify(updatedProfile)); |
|
|
|
// Dispatch storage event for other components |
|
window.dispatchEvent( |
|
new StorageEvent('storage', { |
|
key: 'bolt_user_profile', |
|
newValue: JSON.stringify(updatedProfile), |
|
}), |
|
); |
|
|
|
toast.success(`Notifications ${checked ? 'enabled' : 'disabled'}`); |
|
}} |
|
/> |
|
</div> |
|
</div> |
|
</motion.div> |
|
|
|
{/* Timezone */} |
|
<motion.div |
|
className="bg-white dark:bg-[#0A0A0A] rounded-lg shadow-sm dark:shadow-none p-4" |
|
initial={{ opacity: 0, y: 20 }} |
|
animate={{ opacity: 1, y: 0 }} |
|
transition={{ delay: 0.2 }} |
|
> |
|
<div className="flex items-center gap-2 mb-4"> |
|
<div className="i-ph:clock-fill w-4 h-4 text-purple-500" /> |
|
<span className="text-sm font-medium text-bolt-elements-textPrimary">Time Settings</span> |
|
</div> |
|
|
|
<div> |
|
<div className="flex items-center gap-2 mb-2"> |
|
<div className="i-ph:globe-fill w-4 h-4 text-bolt-elements-textSecondary" /> |
|
<label className="block text-sm text-bolt-elements-textSecondary">Timezone</label> |
|
</div> |
|
<select |
|
value={settings.timezone} |
|
onChange={(e) => setSettings((prev) => ({ ...prev, timezone: e.target.value }))} |
|
className={classNames( |
|
'w-full px-3 py-2 rounded-lg text-sm', |
|
'bg-[#FAFAFA] dark:bg-[#0A0A0A]', |
|
'border border-[#E5E5E5] dark:border-[#1A1A1A]', |
|
'text-bolt-elements-textPrimary', |
|
'focus:outline-none focus:ring-2 focus:ring-purple-500/30', |
|
'transition-all duration-200', |
|
)} |
|
> |
|
<option value={currentTimezone}>{currentTimezone}</option> |
|
</select> |
|
</div> |
|
</motion.div> |
|
|
|
{/* Simplified Keyboard Shortcuts */} |
|
<motion.div |
|
className="bg-white dark:bg-[#0A0A0A] rounded-lg shadow-sm dark:shadow-none p-4" |
|
initial={{ opacity: 0, y: 20 }} |
|
animate={{ opacity: 1, y: 0 }} |
|
transition={{ delay: 0.3 }} |
|
> |
|
<div className="flex items-center gap-2 mb-4"> |
|
<div className="i-ph:keyboard-fill w-4 h-4 text-purple-500" /> |
|
<span className="text-sm font-medium text-bolt-elements-textPrimary">Keyboard Shortcuts</span> |
|
</div> |
|
|
|
<div className="space-y-2"> |
|
<div className="flex items-center justify-between p-2 rounded-lg bg-[#FAFAFA] dark:bg-[#1A1A1A]"> |
|
<div className="flex flex-col"> |
|
<span className="text-sm text-bolt-elements-textPrimary">Toggle Theme</span> |
|
<span className="text-xs text-bolt-elements-textSecondary">Switch between light and dark mode</span> |
|
</div> |
|
<div className="flex items-center gap-1"> |
|
<kbd className="px-2 py-1 text-xs font-semibold text-bolt-elements-textSecondary bg-white dark:bg-[#0A0A0A] border border-[#E5E5E5] dark:border-[#1A1A1A] rounded shadow-sm"> |
|
{getModifierSymbol('meta')} |
|
</kbd> |
|
<kbd className="px-2 py-1 text-xs font-semibold text-bolt-elements-textSecondary bg-white dark:bg-[#0A0A0A] border border-[#E5E5E5] dark:border-[#1A1A1A] rounded shadow-sm"> |
|
{getModifierSymbol('alt')} |
|
</kbd> |
|
<kbd className="px-2 py-1 text-xs font-semibold text-bolt-elements-textSecondary bg-white dark:bg-[#0A0A0A] border border-[#E5E5E5] dark:border-[#1A1A1A] rounded shadow-sm"> |
|
{getModifierSymbol('shift')} |
|
</kbd> |
|
<kbd className="px-2 py-1 text-xs font-semibold text-bolt-elements-textSecondary bg-white dark:bg-[#0A0A0A] border border-[#E5E5E5] dark:border-[#1A1A1A] rounded shadow-sm"> |
|
D |
|
</kbd> |
|
</div> |
|
</div> |
|
</div> |
|
</motion.div> |
|
</div> |
|
); |
|
} |
|
|