v0.12.29 various bug fixes

This commit is contained in:
2026-03-26 09:46:35 -04:00
parent 92dbcf2780
commit 13e5e3a627
8 changed files with 132 additions and 34 deletions

View File

@@ -83,6 +83,9 @@
color: var(--text-secondary);
margin-bottom: 3px;
padding: 0 12px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* Reply preview */

View File

@@ -1,13 +1,18 @@
import { useState } from 'react';
import { useState, useEffect } from 'react';
import { useAuth } from '../contexts/AuthContext.jsx';
import { useToast } from '../contexts/ToastContext.jsx';
import { api } from '../utils/api.js';
import Avatar from './Avatar.jsx';
const LS_FONT_KEY = 'rosterchirp_font_scale';
const MIN_SCALE = 0.8;
const MAX_SCALE = 2.0;
export default function ProfileModal({ onClose }) {
const { user, updateUser } = useAuth();
const toast = useToast();
const [isMobile, setIsMobile] = useState(() => window.innerWidth < 768);
const [displayName, setDisplayName] = useState(user?.display_name || '');
const [savedDisplayName, setSavedDisplayName] = useState(user?.display_name || '');
const [displayNameWarning, setDisplayNameWarning] = useState('');
@@ -16,7 +21,7 @@ export default function ProfileModal({ onClose }) {
const [newPw, setNewPw] = useState('');
const [confirmPw, setConfirmPw] = useState('');
const [loading, setLoading] = useState(false);
const [tab, setTab] = useState('profile'); // 'profile' | 'password' | 'notifications'
const [tab, setTab] = useState('profile'); // 'profile' | 'password' | 'notifications' | 'appearance'
const [pushTesting, setPushTesting] = useState(false);
const [pushResult, setPushResult] = useState(null);
const [notifPermission, setNotifPermission] = useState(
@@ -25,6 +30,23 @@ export default function ProfileModal({ onClose }) {
const [hideAdminTag, setHideAdminTag] = useState(!!user?.hide_admin_tag);
const [allowDm, setAllowDm] = useState(user?.allow_dm !== 0);
const savedScale = parseFloat(localStorage.getItem(LS_FONT_KEY));
const [fontScale, setFontScale] = useState(
(savedScale >= MIN_SCALE && savedScale <= MAX_SCALE) ? savedScale : 1.0
);
useEffect(() => {
const onResize = () => setIsMobile(window.innerWidth < 768);
window.addEventListener('resize', onResize);
return () => window.removeEventListener('resize', onResize);
}, []);
const applyFontScale = (val) => {
setFontScale(val);
document.documentElement.style.setProperty('--font-scale', val);
localStorage.setItem(LS_FONT_KEY, val);
};
const handleSaveProfile = async () => {
if (displayNameWarning) return toast('Display name is already in use', 'error');
setLoading(true);
@@ -102,12 +124,27 @@ export default function ProfileModal({ onClose }) {
</div>
</div>
{/* Tabs */}
<div className="flex gap-2" style={{ marginBottom: 20 }}>
<button className={`btn btn-sm ${tab === 'profile' ? 'btn-primary' : 'btn-secondary'}`} onClick={() => setTab('profile')}>Profile</button>
<button className={`btn btn-sm ${tab === 'password' ? 'btn-primary' : 'btn-secondary'}`} onClick={() => setTab('password')}>Change Password</button>
<button className={`btn btn-sm ${tab === 'notifications' ? 'btn-primary' : 'btn-secondary'}`} onClick={() => { setTab('notifications'); setPushResult(null); }}>Notifications</button>
</div>
{/* Tabs — select on mobile, buttons on desktop */}
{isMobile ? (
<select
className="input"
value={tab}
onChange={e => { setTab(e.target.value); setPushResult(null); }}
style={{ marginBottom: 20 }}
>
<option value="profile">Profile</option>
<option value="password">Change Password</option>
<option value="notifications">Notifications</option>
<option value="appearance">Appearance</option>
</select>
) : (
<div className="flex gap-2" style={{ marginBottom: 20 }}>
<button className={`btn btn-sm ${tab === 'profile' ? 'btn-primary' : 'btn-secondary'}`} onClick={() => setTab('profile')}>Profile</button>
<button className={`btn btn-sm ${tab === 'password' ? 'btn-primary' : 'btn-secondary'}`} onClick={() => setTab('password')}>Change Password</button>
<button className={`btn btn-sm ${tab === 'notifications' ? 'btn-primary' : 'btn-secondary'}`} onClick={() => { setTab('notifications'); setPushResult(null); }}>Notifications</button>
<button className={`btn btn-sm ${tab === 'appearance' ? 'btn-primary' : 'btn-secondary'}`} onClick={() => setTab('appearance')}>Appearance</button>
</div>
)}
{tab === 'profile' && (
<div className="flex-col gap-3">
@@ -287,6 +324,40 @@ export default function ProfileModal({ onClose }) {
</button>
</div>
)}
{tab === 'appearance' && (
<div className="flex-col gap-3">
<div className="flex-col gap-2">
<label className="text-sm font-medium" style={{ color: 'var(--text-secondary)' }}>Message Font Size</label>
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
<span style={{ fontSize: 12, color: 'var(--text-tertiary)', flexShrink: 0 }}>A</span>
<input
type="range"
min={MIN_SCALE}
max={MAX_SCALE}
step={0.05}
value={fontScale}
onChange={e => applyFontScale(parseFloat(e.target.value))}
style={{ flex: 1, accentColor: 'var(--primary)' }}
/>
<span style={{ fontSize: 18, color: 'var(--text-tertiary)', flexShrink: 0 }}>A</span>
<span style={{ fontSize: 13, color: 'var(--text-secondary)', minWidth: 40, textAlign: 'right', flexShrink: 0 }}>
{Math.round(fontScale * 100)}%
</span>
</div>
<span style={{ fontSize: 12, color: 'var(--text-tertiary)', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
Pinch to zoom in the chat window also adjusts this setting.
</span>
</div>
<button
className="btn btn-secondary btn-sm"
style={{ alignSelf: 'flex-start' }}
onClick={() => applyFontScale(1.0)}
>
Reset to Default
</button>
</div>
)}
</div>
</div>
);