| import { useState, useEffect } from "react"; |
| import { SettingsIcon, User, Sliders, LogOut, Lock } from "lucide-react" |
| import { useTranslation } from 'react-i18next'; |
| import { useAuth } from './context/AuthContext'; |
| import { useTheme } from './context/ThemeContext'; |
| import { useNavigate } from 'react-router-dom'; |
|
|
| function Toggle({ enabled, onToggle }) { |
| return ( |
| <button |
| onClick={onToggle} |
| className={`relative inline-flex w-12 h-6 rounded-full transition-colors duration-200 cursor-pointer focus:outline-none |
| ${enabled ? "bg-blue-500" : "bg-slate-600"}`} |
| > |
| <span |
| className={`absolute top-0.5 left-0.5 w-5 h-5 bg-white rounded-full shadow transition-transform duration-200 |
| ${enabled ? "translate-x-6" : "translate-x-0"}`} |
| /> |
| </button> |
| ); |
| } |
|
|
| export default function Settings() { |
| const { user, logout } = useAuth(); |
| const navigate = useNavigate(); |
| |
| const { t, i18n } = useTranslation(); |
| const [displayName, setDisplayName] = useState(""); |
| const [email, setEmail] = useState(""); |
| const [emailNotif, setEmailNotif] = useState(true); |
| const [desktopAlerts, setDesktopAlerts] = useState(false); |
| const [autoDelete, setAutoDelete] = useState(true); |
| const [apiKey] = useState("sk-••••••••••••••••••••••••"); |
| const [twoFactor, setTwoFactor] = useState(false); |
| const [language, setLanguage] = useState(i18n.language || "en"); |
| const [sensitivity, setSensitivity] = useState(75); |
|
|
| useEffect(() => { |
| i18n.changeLanguage(language); |
| }, [language, i18n]); |
|
|
| useEffect(() => { |
| const fetchProfile = async () => { |
| try { |
| const apiUrl = import.meta.env.VITE_API_URL || 'http://127.0.0.1:5000'; |
| const token = localStorage.getItem('token'); |
| |
| const response = await fetch(`${apiUrl}/api/profile`, { |
| method: 'GET', |
| headers: { |
| 'Content-Type': 'application/json', |
| ...(token ? { 'Authorization': `Bearer ${token}` } : {}) |
| } |
| }); |
| |
| const result = await response.json(); |
| if (result.status === 'success' && result.data?.user_aktif) { |
| setDisplayName(result.data.user_aktif.display_name); |
| setEmail(result.data.user_aktif.email); |
| } |
| } catch (error) { |
| console.error("Failed to fetch profile settings", error); |
| } |
| }; |
|
|
| fetchProfile(); |
| }, []); |
|
|
| return ( |
| <div className="flex min-h-screen bg-transparent transition-colors duration-300"> |
| {/* Main */} |
| <main className={`flex-1 flex flex-col gap-6 px-4 md:px-10 py-6 md:py-8 w-full max-w-[100vw] overflow-x-hidden relative ${!user ? 'overflow-y-hidden h-full max-h-[100vh]' : ''}`}> |
| |
| {/* Unauthenticated Overlay */} |
| {!user && ( |
| <div className="absolute inset-0 z-50 flex items-center justify-center bg-slate-50/50 dark:bg-[#0f1117]/60 backdrop-blur-md"> |
| <div className="bg-white dark:bg-[#161b27] border border-slate-200 dark:border-[#1e2538] flex flex-col items-center text-center p-8 max-w-sm w-full mx-4 rounded-2xl shadow-xl"> |
| <div className="w-16 h-16 bg-blue-100 dark:bg-blue-900/30 rounded-full flex items-center justify-center mb-5"> |
| <Lock className="text-blue-600 dark:text-blue-400" size={28} /> |
| </div> |
| <h2 className="text-xl font-bold text-slate-800 dark:text-white mb-2">{t('analysis.lockedTitle')}</h2> |
| <p className="text-slate-500 dark:text-slate-400 text-sm mb-6 leading-relaxed"> |
| {t('analysis.lockedMessage')} |
| </p> |
| <button |
| onClick={() => navigate('/login')} |
| className="w-full bg-blue-600 hover:bg-blue-500 text-white font-semibold py-3 rounded-lg transition-colors cursor-pointer shadow-md" |
| > |
| {t('analysis.loginNow')} |
| </button> |
| </div> |
| </div> |
| )} |
| {/* Header */} |
| <header className="flex items-center gap-4 pb-4 border-b border-slate-200 dark:border-[#1e2538]"> |
| <span className="text-cyan-500 dark:text-cyan-400 text-3xl"><SettingsIcon size={30}/></span> |
| <div> |
| <h1 className="text-lg lg:text-3xl font-bold text-slate-800 dark:text-slate-200">{t('settings.title')}</h1> |
| <p className="text-slate-500 dark:text-slate-500 text-sm mt-1">{t('settings.subtitle')}</p> |
| </div> |
| </header> |
| |
| {/* User Block with Theme & Logout */} |
| <div className="flex flex-col bg-white dark:bg-[#161b27] rounded-2xl p-4 gap-4 border border-slate-200 dark:border-[#1e2538] shadow-sm"> |
| <div className="flex items-center gap-4"> |
| <div className="w-12 h-12 flex-shrink-0 rounded-full bg-blue-600 flex items-center justify-center font-bold text-lg text-white"> |
| {user?.display_name ? user.display_name.charAt(0).toUpperCase() : 'AI'} |
| </div> |
| <div className="text-sm overflow-hidden flex-1"> |
| <div className="font-semibold text-base truncate text-slate-800 dark:text-slate-200">{user?.display_name || t('app.adminName', 'Admin User')}</div> |
| <div className="text-xs text-slate-500 truncate capitalize">{user?.role || t('app.adminRole', 'Administrator')}</div> |
| </div> |
| </div> |
| <div className="mt-1 pt-4 border-t border-slate-200 dark:border-[#1e2538]"> |
| <button |
| onClick={logout} |
| className="w-full flex items-center justify-center gap-2 p-2.5 rounded-lg text-red-600 dark:text-red-400 bg-red-50/50 dark:bg-red-900/10 hover:bg-red-50 dark:hover:bg-red-900/30 border border-transparent hover:border-red-100 dark:hover:border-red-900/30 transition-all cursor-pointer font-semibold shadow-sm" |
| title="Logout" |
| > |
| <LogOut size={18} strokeWidth={2.5} /> |
| {t('app.logout', 'Logout')} |
| </button> |
| </div> |
| </div> |
| |
| {/* Account Settings */} |
| <section className="bg-white dark:bg-[#161b27] border border-slate-200 dark:border-[#1e2538] rounded-2xl p-7 shadow-sm"> |
| <div className="flex items-center gap-3 mb-6"> |
| <span className="text-cyan-500 dark:text-cyan-400"><User size={24} /></span> |
| <h2 className="text-lg font-bold text-slate-800 dark:text-slate-200">{t('settings.accountSettings')}</h2> |
| </div> |
| |
| <div className="flex flex-col gap-5"> |
| <div> |
| <label className="block text-sm text-slate-500 dark:text-slate-400 mb-2">{t('settings.displayName')}</label> |
| <input |
| type="text" |
| value={displayName} |
| onChange={(e) => setDisplayName(e.target.value)} |
| className="w-full bg-slate-50 dark:bg-[#1e2538] border border-slate-200 dark:border-[#2d3748] rounded-lg px-4 py-3 text-sm text-slate-900 dark:text-slate-200 focus:outline-none focus:border-blue-500 transition-colors" |
| /> |
| </div> |
| <div> |
| <label className="block text-sm text-slate-500 dark:text-slate-400 mb-2">{t('settings.emailAddress')}</label> |
| <input |
| type="email" |
| value={email} |
| onChange={(e) => setEmail(e.target.value)} |
| className="w-full bg-slate-50 dark:bg-[#1e2538] border border-slate-200 dark:border-[#2d3748] rounded-lg px-4 py-3 text-sm text-slate-900 dark:text-slate-200 focus:outline-none focus:border-blue-500 transition-colors" |
| /> |
| </div> |
| <div className="flex justify-end"> |
| <button className="bg-blue-600 hover:bg-blue-500 text-white text-sm font-semibold px-5 py-2.5 rounded-lg transition-colors cursor-pointer"> |
| {t('settings.saveChanges')} |
| </button> |
| </div> |
| </div> |
| </section> |
| |
| {/*/!* Notifications *!/*/} |
| {/*<section className="bg-white dark:bg-[#161b27] border border-slate-200 dark:border-[#1e2538] rounded-2xl p-7 shadow-sm">*/} |
| {/* <div className="flex items-center gap-3 mb-6">*/} |
| {/* <span className="text-cyan-500 dark:text-cyan-400 text-xl">🔔</span>*/} |
| {/* <h2 className="text-lg font-bold text-slate-800 dark:text-slate-200">{t('settings.notifications')}</h2>*/} |
| {/* </div>*/} |
| |
| {/* <div className="flex flex-col gap-5">*/} |
| {/* <div className="flex items-center justify-between">*/} |
| {/* <div>*/} |
| {/* <div className="text-sm font-semibold text-slate-800 dark:text-slate-200">{t('settings.emailNotifications')}</div>*/} |
| {/* <div className="text-xs text-slate-500 mt-0.5">{t('settings.emailNotifDesc')}</div>*/} |
| {/* </div>*/} |
| {/* <Toggle enabled={emailNotif} onToggle={() => setEmailNotif(!emailNotif)} />*/} |
| {/* </div>*/} |
| {/* <div className="border-t border-slate-200 dark:border-[#1e2538]" />*/} |
| {/* <div className="flex items-center justify-between">*/} |
| {/* <div>*/} |
| {/* <div className="text-sm font-semibold text-slate-800 dark:text-slate-200">{t('settings.desktopAlerts')}</div>*/} |
| {/* <div className="text-xs text-slate-500 mt-0.5">{t('settings.desktopAlertsDesc')}</div>*/} |
| {/* </div>*/} |
| {/* <Toggle enabled={desktopAlerts} onToggle={() => setDesktopAlerts(!desktopAlerts)} />*/} |
| {/* </div>*/} |
| {/* </div>*/} |
| {/*</section>*/} |
| |
| {/*/!* Security & Privacy *!/*/} |
| {/*<section className="bg-white dark:bg-[#161b27] border border-slate-200 dark:border-[#1e2538] rounded-2xl p-7 shadow-sm">*/} |
| {/* <div className="flex items-center gap-3 mb-6">*/} |
| {/* <span className="text-cyan-500 dark:text-cyan-400 text-xl">🛡</span>*/} |
| {/* <h2 className="text-lg font-bold text-slate-800 dark:text-slate-200">{t('settings.securityPrivacy')}</h2>*/} |
| {/* </div>*/} |
| |
| {/* <div className="flex flex-col gap-5">*/} |
| {/* <div className="flex items-center justify-between">*/} |
| {/* <div>*/} |
| {/* <div className="text-sm font-semibold text-slate-800 dark:text-slate-200">{t('settings.autoDelete')}</div>*/} |
| {/* <div className="text-xs text-slate-500 mt-0.5">{t('settings.autoDeleteDesc')}</div>*/} |
| {/* </div>*/} |
| {/* <Toggle enabled={autoDelete} onToggle={() => setAutoDelete(!autoDelete)} />*/} |
| {/* </div>*/} |
| {/* <div className="border-t border-slate-200 dark:border-[#1e2538]" />*/} |
| {/* <div className="flex items-center justify-between">*/} |
| {/* <div>*/} |
| {/* <div className="text-sm font-semibold text-slate-800 dark:text-slate-200">{t('settings.twoFactor')}</div>*/} |
| {/* <div className="text-xs text-slate-500 mt-0.5">{t('settings.twoFactorDesc')}</div>*/} |
| {/* </div>*/} |
| {/* <Toggle enabled={twoFactor} onToggle={() => setTwoFactor(!twoFactor)} />*/} |
| {/* </div>*/} |
| {/* <div className="border-t border-slate-200 dark:border-[#1e2538]" />*/} |
| {/* <div>*/} |
| {/* <label className="block text-sm text-slate-500 dark:text-slate-400 mb-2">{t('settings.apiKey')}</label>*/} |
| {/* <div className="flex gap-3">*/} |
| {/* <input*/} |
| {/* type="text"*/} |
| {/* value={apiKey}*/} |
| {/* readOnly*/} |
| {/* className="flex-1 min-w-0 bg-slate-50 dark:bg-[#1e2538] border border-slate-200 dark:border-[#2d3748] rounded-lg px-4 py-3 text-sm text-slate-500 dark:text-slate-400 focus:outline-none"*/} |
| {/* />*/} |
| {/* <button className="shrink-0 bg-slate-100 dark:bg-[#1e2538] hover:bg-slate-200 dark:hover:bg-slate-700 border border-slate-200 dark:border-[#2d3748] text-slate-700 dark:text-slate-300 text-sm px-4 py-3 rounded-lg transition-colors cursor-pointer">*/} |
| {/* {t('settings.regenerate')}*/} |
| {/* </button>*/} |
| {/* </div>*/} |
| {/* </div>*/} |
| {/* <div className="border-t border-slate-200 dark:border-[#1e2538]" />*/} |
| {/* <div>*/} |
| {/* <button className="border border-red-200 dark:border-red-700 text-red-600 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-900/30 text-sm font-semibold px-5 py-2.5 rounded-lg transition-colors cursor-pointer">*/} |
| {/* {t('settings.clearHistory')}*/} |
| {/* </button>*/} |
| {/* </div>*/} |
| {/* </div>*/} |
| {/*</section>*/} |
| |
| {/* Preferences */} |
| <section className="bg-white dark:bg-[#161b27] border border-slate-200 dark:border-[#1e2538] rounded-2xl p-7 shadow-sm"> |
| <div className="flex items-center gap-3 mb-6"> |
| <span className="text-cyan-500 dark:text-cyan-400"><Sliders size={24} /></span> |
| <h2 className="text-lg font-bold text-slate-800 dark:text-slate-200">{t('settings.preferences')}</h2> |
| </div> |
| |
| <div className="flex flex-col gap-5"> |
| <div className="grid gap-5"> |
| <label className="block text-sm text-slate-500 dark:text-slate-400 mb-2">{t('settings.language')}</label> |
| <div className="relative"> |
| <select |
| value={language} |
| onChange={(e) => setLanguage(e.target.value)} |
| className="w-full bg-slate-50 dark:bg-[#1e2538] border border-slate-200 dark:border-[#2d3748] rounded-lg px-4 py-3 text-sm text-slate-900 dark:text-slate-200 focus:outline-none focus:border-blue-500 transition-colors cursor-pointer appearance-none" |
| > |
| <option value="en" className="text-slate-900 dark:text-slate-200 bg-white dark:bg-[#1e2538]">{t('settings.english')}</option> |
| <option value="id" className="text-slate-900 dark:text-slate-200 bg-white dark:bg-[#1e2538]">{t('settings.indonesian')}</option> |
| </select> |
| <div className="pointer-events-none absolute inset-y-0 right-4 flex items-center"> |
| <svg className="w-4 h-4 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" /> |
| </svg> |
| </div> |
| </div> |
| </div> |
| <div> |
| <label className="block text-sm text-slate-500 dark:text-slate-400 mb-2"> |
| {t('settings.detectionSensitivity')} — <span className="text-blue-600 dark:text-blue-400 font-semibold">{sensitivity}%</span> |
| </label> |
| <input |
| type="range" |
| min={0} |
| max={100} |
| value={sensitivity} |
| onChange={(e) => setSensitivity(e.target.value)} |
| className="w-full accent-blue-500 cursor-pointer" |
| /> |
| <div className="flex justify-between text-xs text-slate-500 mt-1"> |
| <span>{t('settings.low')}</span> |
| <span>{t('settings.high')}</span> |
| </div> |
| </div> |
| </div> |
| </section> |
| |
| {/* Help button */} |
| <div className="flex justify-end pb-2"> |
| <button className="w-8 h-8 rounded-full bg-slate-200 dark:bg-[#1e2538] text-slate-500 dark:text-slate-400 hover:text-slate-700 dark:hover:text-white hover:bg-slate-300 dark:hover:bg-slate-700 text-sm font-bold transition-colors cursor-pointer shadow-sm"> |
| ? |
| </button> |
| </div> |
| </main> |
| </div> |
| ); |
| } |