Compare commits

...

3 Commits

5 changed files with 101 additions and 10 deletions
+1 -1
View File
@@ -1 +1 @@
0.1.0.65
0.1.0.66
+53
View File
@@ -3632,6 +3632,59 @@ html.theme-cupertino .events-scroll-container {
.stats-kpi-value {
font-size: 20px;
}
.profile-stats-section.form-card {
padding: 20px 16px;
}
.profile-stats-section .form-header {
margin-bottom: 12px;
gap: 10px;
}
.profile-stats-section .form-header h2 {
font-size: 18px;
}
.profile-stats-section .stats-subtitle {
font-size: 12px;
line-height: 1.35;
}
.profile-stats-kpi-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 8px;
margin-top: 0;
}
.profile-stats-kpi-grid .stats-kpi-card {
padding: 10px;
gap: 8px;
align-items: center;
}
.profile-stats-kpi-grid .stats-kpi-icon {
margin-top: 0;
}
.profile-stats-kpi-grid .stats-kpi-icon svg {
width: 16px;
height: 16px;
}
.profile-stats-kpi-grid .stats-kpi-label {
font-size: 11px;
margin-bottom: 2px;
line-height: 1.25;
}
.profile-stats-kpi-grid .stats-kpi-value {
font-size: 16px;
}
.profile-stats-kpi-grid .stats-kpi-unit {
font-size: 11px;
}
}
.signature-grid {
+2 -2
View File
@@ -726,7 +726,7 @@ export default function UserProfilePage({ onBack, onLogout }: UserProfilePagePro
</div>
</section>
<section className="form-card">
<section className="form-card profile-stats-section">
<div className="form-header">
<BarChart2 size={24} className="form-icon" />
<div>
@@ -736,7 +736,7 @@ export default function UserProfilePage({ onBack, onLogout }: UserProfilePagePro
</div>
{(statsTotals || profile) && (
<div className="stats-kpi-grid">
<div className="stats-kpi-grid profile-stats-kpi-grid">
<KpiCard
icon={<BookOpen size={20} />}
label={t('profile.stats_logbooks')}
@@ -69,4 +69,28 @@ describe('appearancePrefs', () => {
await saveAppearancePrefsToServer('ocean', 'light')
expect(mockedApiJson).not.toHaveBeenCalled()
})
it('syncAppearancePrefs skips server sync when userId does not match active session', async () => {
localStorage.setItem('active_userid', 'session-user')
setThemePreference('other-user', 'ocean')
mockedApiJson.mockResolvedValue({
theme: 'material',
colorScheme: 'dark',
persisted: true
})
await syncAppearancePrefs('other-user')
expect(mockedApiJson).not.toHaveBeenCalled()
expect(localStorage.getItem('user_pref_theme_other-user')).toBe('ocean')
})
it('syncAppearancePrefs skips server sync when active session is missing', async () => {
setThemePreference(USER_ID, 'ocean')
await syncAppearancePrefs(USER_ID)
expect(mockedApiJson).not.toHaveBeenCalled()
expect(localStorage.getItem(`user_pref_theme_${USER_ID}`)).toBe('ocean')
})
})
+21 -7
View File
@@ -23,16 +23,30 @@ function hasLocalAppearancePrefs(userId: string): boolean {
)
}
export async function fetchAppearancePrefs(): Promise<AppearancePrefs> {
if (!getActiveUserId()) {
function resolveSyncedUserId(userId?: string | null): string | null {
const id = userId?.trim() || getActiveUserId()?.trim() || null
if (!id) return null
const activeId = getActiveUserId()?.trim() || null
if (!activeId || activeId !== id) return null
return id
}
export async function fetchAppearancePrefs(userId?: string | null): Promise<AppearancePrefs> {
if (!resolveSyncedUserId(userId)) {
return { theme: 'auto', colorScheme: 'auto', persisted: false }
}
return apiJson<AppearancePrefs>(API_BASE)
}
export async function saveAppearancePrefsToServer(theme: string, colorScheme: string): Promise<void> {
if (!getActiveUserId()) return
export async function saveAppearancePrefsToServer(
theme: string,
colorScheme: string,
userId?: string | null
): Promise<void> {
if (!resolveSyncedUserId(userId)) return
await apiJson<AppearancePrefs>(API_BASE, {
method: 'PUT',
@@ -42,17 +56,17 @@ export async function saveAppearancePrefsToServer(theme: string, colorScheme: st
/** Merge server-stored appearance with local cache (server wins after cache wipe). */
export async function syncAppearancePrefs(userId?: string | null): Promise<void> {
const id = userId?.trim() || getActiveUserId()
const id = resolveSyncedUserId(userId)
if (!id) return
try {
const server = await fetchAppearancePrefs()
const server = await fetchAppearancePrefs(id)
if (server.persisted) {
setThemePreference(id, server.theme)
setColorSchemePreference(id, server.colorScheme)
} else if (hasLocalAppearancePrefs(id)) {
await saveAppearancePrefsToServer(getThemePreference(id), getColorSchemePreference(id))
await saveAppearancePrefsToServer(getThemePreference(id), getColorSchemePreference(id), id)
}
} catch (err) {
console.warn('Failed to sync appearance preferences:', err)