Implement UI enhancements, change password settings, theme toggle, and custom grid size slider
All checks were successful
Automated Container Build / build-and-push (push) Successful in 1m19s

This commit is contained in:
Elijah 2026-05-22 18:22:21 -07:00
parent 425598c278
commit c3b1fe8de1
5 changed files with 226 additions and 26 deletions

View file

@ -7,6 +7,7 @@ import api from '@/lib/api';
interface SettingsData {
theme: string;
grid_size: string;
thumbnail_images: string;
thumbnail_videos: string;
max_versions: string;
@ -20,6 +21,13 @@ export default function SettingsPage() {
const [error, setError] = useState<string | null>(null);
const [success, setSuccess] = useState(false);
const [oldPassword, setOldPassword] = useState('');
const [newPassword, setNewPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
const [pwdSaving, setPwdSaving] = useState(false);
const [pwdError, setPwdError] = useState<string | null>(null);
const [pwdSuccess, setPwdSuccess] = useState(false);
useEffect(() => {
loadSettings();
}, []);
@ -43,6 +51,7 @@ export default function SettingsPage() {
const s = data.settings || data;
const loaded = {
theme: s.theme || 'dark',
grid_size: s.grid_size || '200',
thumbnail_images: s.thumbnail_images || 'true',
thumbnail_videos: s.thumbnail_videos || 'true',
max_versions: s.max_versions || '1',
@ -76,6 +85,33 @@ export default function SettingsPage() {
}
}
async function handlePasswordChange(e: React.FormEvent) {
e.preventDefault();
if (newPassword !== confirmPassword) {
setPwdError("New passwords do not match.");
return;
}
if (newPassword.length < 8) {
setPwdError("Password must be at least 8 characters.");
return;
}
setPwdSaving(true);
setPwdError(null);
setPwdSuccess(false);
try {
await api.changePassword(oldPassword, newPassword);
setPwdSuccess(true);
setOldPassword('');
setNewPassword('');
setConfirmPassword('');
setTimeout(() => setPwdSuccess(false), 3000);
} catch (err: any) {
setPwdError(err.message || 'Failed to change password');
} finally {
setPwdSaving(false);
}
}
if (loading) {
return (
<div className="flex-1 flex items-center justify-center">
@ -94,20 +130,74 @@ export default function SettingsPage() {
content: (
<div className="space-y-4">
<div>
<label className="block text-sm font-medium mb-2" style={{ color: 'var(--color-text-primary)' }}>Theme</label>
<select
value={settings.theme}
onChange={(e) => setSettings({ ...settings, theme: e.target.value })}
className="w-full max-w-xs px-3 py-2 rounded-lg text-sm outline-none"
style={{ backgroundColor: 'var(--color-bg-primary)', border: '1px solid var(--color-border)', color: 'var(--color-text-primary)' }}
>
<option value="dark">Dark Mode</option>
<option value="light">Light Mode</option>
</select>
<label className="block text-sm font-medium mb-2" style={{ color: 'var(--color-text-primary)' }}>Grid Tile Size ({settings.grid_size}px)</label>
<input
type="range"
min="120"
max="250"
step="10"
value={settings.grid_size}
onChange={(e) => setSettings({ ...settings, grid_size: e.target.value })}
className="w-full max-w-xs accent-[var(--color-accent)]"
/>
</div>
</div>
)
},
{
id: 'security',
title: 'Security',
icon: <Shield className="w-5 h-5" />,
content: (
<form onSubmit={handlePasswordChange} className="space-y-4 max-w-xs">
<div>
<label className="block text-sm font-medium mb-1" style={{ color: 'var(--color-text-primary)' }}>Current Password</label>
<input
type="password"
value={oldPassword}
onChange={e => setOldPassword(e.target.value)}
required
className="w-full px-3 py-2 rounded-lg text-sm outline-none"
style={{ backgroundColor: 'var(--color-bg-primary)', border: '1px solid var(--color-border)', color: 'var(--color-text-primary)' }}
/>
</div>
<div>
<label className="block text-sm font-medium mb-1" style={{ color: 'var(--color-text-primary)' }}>New Password</label>
<input
type="password"
value={newPassword}
onChange={e => setNewPassword(e.target.value)}
required
minLength={8}
className="w-full px-3 py-2 rounded-lg text-sm outline-none"
style={{ backgroundColor: 'var(--color-bg-primary)', border: '1px solid var(--color-border)', color: 'var(--color-text-primary)' }}
/>
</div>
<div>
<label className="block text-sm font-medium mb-1" style={{ color: 'var(--color-text-primary)' }}>Confirm New Password</label>
<input
type="password"
value={confirmPassword}
onChange={e => setConfirmPassword(e.target.value)}
required
minLength={8}
className="w-full px-3 py-2 rounded-lg text-sm outline-none"
style={{ backgroundColor: 'var(--color-bg-primary)', border: '1px solid var(--color-border)', color: 'var(--color-text-primary)' }}
/>
</div>
{pwdError && <p className="text-sm" style={{ color: 'var(--color-error)' }}>{pwdError}</p>}
{pwdSuccess && <p className="text-sm" style={{ color: 'var(--color-success)' }}>Password changed successfully!</p>}
<button
type="submit"
disabled={pwdSaving}
className="px-4 py-2 rounded-lg text-sm font-medium text-white transition-opacity disabled:opacity-50"
style={{ backgroundColor: 'var(--color-accent)' }}
>
{pwdSaving ? 'Saving...' : 'Change Password'}
</button>
</form>
)
},
{
id: 'thumbnails',
title: 'Media & Thumbnails',