feat(settings): add "Refresh cached data" recovery action
When the mailbox view gets into a stale or wrong state, the only escape was the browser's "clear site data" — which also wipes the saved account list, forcing a re-login of every account. Add a non-destructive "Refresh cached data" button under Settings → Data. It clears the server-derived caches (contacts, calendars, identities, per-account snapshots) and reloads so they re-fetch fresh, while preserving accounts, sessions, settings, themes and user content (templates, S/MIME). Two-click confirm to avoid an accidental reload. English strings added across all locales (translation follow-up); unit tests cover the cache-clear (keeps account-registry/auth/prefs) and the reload.
This commit is contained in:
@@ -11,6 +11,7 @@ import { useUpdateStore } from '@/stores/update-store';
|
||||
import { ExternalLink } from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { getPathPrefix } from '@/lib/browser-navigation';
|
||||
import { clearCachedData } from '@/lib/clear-cached-data';
|
||||
import { SpamSiegeGame } from './spam-siege-game';
|
||||
|
||||
const APP_VERSION = process.env.NEXT_PUBLIC_APP_VERSION || "0.0.0";
|
||||
@@ -54,6 +55,7 @@ export function AboutDataSettings() {
|
||||
useSettingsStore();
|
||||
const { settingsSyncEnabled } = useConfig();
|
||||
const [showResetConfirm, setShowResetConfirm] = useState(false);
|
||||
const [showRefreshConfirm, setShowRefreshConfirm] = useState(false);
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
const { isFeatureEnabled } = usePolicyStore();
|
||||
const [showGame, setShowGame] = useState(false);
|
||||
@@ -105,6 +107,15 @@ export function AboutDataSettings() {
|
||||
reader.readAsText(file);
|
||||
};
|
||||
|
||||
const handleRefreshCache = () => {
|
||||
if (showRefreshConfirm) {
|
||||
clearCachedData(); // reloads the page
|
||||
} else {
|
||||
setShowRefreshConfirm(true);
|
||||
setTimeout(() => setShowRefreshConfirm(false), 5000);
|
||||
}
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
if (showResetConfirm) {
|
||||
resetToDefaults();
|
||||
@@ -187,6 +198,16 @@ export function AboutDataSettings() {
|
||||
</SettingItem>
|
||||
)}
|
||||
|
||||
<SettingItem label={t('refresh_cache.label')} description={t('refresh_cache.description')}>
|
||||
<Button
|
||||
variant={showRefreshConfirm ? 'default' : 'outline'}
|
||||
size="sm"
|
||||
onClick={handleRefreshCache}
|
||||
>
|
||||
{showRefreshConfirm ? tCommon('yes') : t('refresh_cache.button')}
|
||||
</Button>
|
||||
</SettingItem>
|
||||
|
||||
<SettingItem label={t('reset_settings.label')} description={t('reset_settings.description')}>
|
||||
<Button
|
||||
variant={showResetConfirm ? 'destructive' : 'outline'}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
|
||||
import { clearCachedData } from '../clear-cached-data';
|
||||
|
||||
describe('clearCachedData', () => {
|
||||
let reload: ReturnType<typeof vi.fn>;
|
||||
let originalLocation: Location;
|
||||
|
||||
beforeEach(() => {
|
||||
localStorage.clear();
|
||||
reload = vi.fn();
|
||||
originalLocation = window.location;
|
||||
Object.defineProperty(window, 'location', {
|
||||
configurable: true,
|
||||
value: { ...originalLocation, reload },
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
Object.defineProperty(window, 'location', { configurable: true, value: originalLocation });
|
||||
});
|
||||
|
||||
it('clears re-fetchable caches but keeps accounts, sessions and prefs', () => {
|
||||
localStorage.setItem('contact-storage', '1');
|
||||
localStorage.setItem('calendar-storage', '1');
|
||||
localStorage.setItem('identity-storage', '1');
|
||||
localStorage.setItem('calendar-notification-storage', '1');
|
||||
// Must survive — losing these is exactly the pain we're avoiding.
|
||||
localStorage.setItem('account-registry', 'accounts');
|
||||
localStorage.setItem('auth-storage', 'session');
|
||||
localStorage.setItem('settings-storage', 'prefs');
|
||||
localStorage.setItem('template-storage', 'my templates');
|
||||
|
||||
clearCachedData();
|
||||
|
||||
expect(localStorage.getItem('contact-storage')).toBeNull();
|
||||
expect(localStorage.getItem('calendar-storage')).toBeNull();
|
||||
expect(localStorage.getItem('identity-storage')).toBeNull();
|
||||
expect(localStorage.getItem('calendar-notification-storage')).toBeNull();
|
||||
|
||||
expect(localStorage.getItem('account-registry')).toBe('accounts');
|
||||
expect(localStorage.getItem('auth-storage')).toBe('session');
|
||||
expect(localStorage.getItem('settings-storage')).toBe('prefs');
|
||||
expect(localStorage.getItem('template-storage')).toBe('my templates');
|
||||
});
|
||||
|
||||
it('reloads so data is re-fetched fresh', () => {
|
||||
clearCachedData();
|
||||
expect(reload).toHaveBeenCalledOnce();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,45 @@
|
||||
import { evictAll } from '@/lib/account-state-manager';
|
||||
|
||||
// localStorage keys holding server-derived, re-fetchable caches. The "Refresh
|
||||
// cached data" action clears these so a stale or wrong-account view can be
|
||||
// fixed WITHOUT signing out (which would drop the whole account list).
|
||||
//
|
||||
// Deliberately excluded:
|
||||
// - 'account-registry' / 'auth-storage' → keep accounts + sessions
|
||||
// - 'settings-storage' / 'theme-storage' / 'locale-storage' → user prefs
|
||||
// - 'template-storage' / 'smime-preferences' → user-created content
|
||||
const CACHE_STORAGE_KEYS = [
|
||||
'identity-storage',
|
||||
'contact-storage',
|
||||
'calendar-storage',
|
||||
'calendar-notification-storage',
|
||||
];
|
||||
|
||||
/**
|
||||
* Clear cached, server-derived data (contacts, calendars, identities, and the
|
||||
* in-memory per-account snapshots), then reload so everything is re-fetched
|
||||
* fresh for the active account. Accounts and sessions are preserved — this is
|
||||
* the non-destructive alternative to the browser's "clear site data", which
|
||||
* also wipes the account list.
|
||||
*/
|
||||
export function clearCachedData(): void {
|
||||
// Drop the in-memory per-account store snapshots so a reload can't restore
|
||||
// stale cached state for any account.
|
||||
try {
|
||||
evictAll();
|
||||
} catch {
|
||||
/* snapshots are best-effort */
|
||||
}
|
||||
|
||||
if (typeof window === 'undefined') return;
|
||||
|
||||
for (const key of CACHE_STORAGE_KEYS) {
|
||||
try {
|
||||
window.localStorage.removeItem(key);
|
||||
} catch {
|
||||
/* ignore storage access errors */
|
||||
}
|
||||
}
|
||||
|
||||
window.location.reload();
|
||||
}
|
||||
@@ -1628,6 +1628,11 @@
|
||||
"description": "Zobrazit dostupné klávesové zkratky",
|
||||
"button": "Zobrazit zkratky"
|
||||
},
|
||||
"refresh_cache": {
|
||||
"label": "Refresh cached data",
|
||||
"description": "Reload contacts, calendars, and folders from the server. Keeps your accounts and sessions \u2014 fixes a stale or wrong view without signing out.",
|
||||
"button": "Refresh"
|
||||
},
|
||||
"reset_settings": {
|
||||
"label": "Resetovat nastavení",
|
||||
"description": "Obnovit všechna nastavení na výchozí hodnoty",
|
||||
|
||||
@@ -1631,6 +1631,11 @@
|
||||
"description": "Se tilgængelige tastaturgenveje",
|
||||
"button": "Vis genveje"
|
||||
},
|
||||
"refresh_cache": {
|
||||
"label": "Refresh cached data",
|
||||
"description": "Reload contacts, calendars, and folders from the server. Keeps your accounts and sessions \u2014 fixes a stale or wrong view without signing out.",
|
||||
"button": "Refresh"
|
||||
},
|
||||
"reset_settings": {
|
||||
"label": "Nulstil indstillinger",
|
||||
"description": "Gendan alle indstillinger til standardværdier",
|
||||
|
||||
@@ -1628,6 +1628,11 @@
|
||||
"description": "Verfügbare Tastaturkürzel anzeigen",
|
||||
"button": "Tastaturkürzel anzeigen"
|
||||
},
|
||||
"refresh_cache": {
|
||||
"label": "Refresh cached data",
|
||||
"description": "Reload contacts, calendars, and folders from the server. Keeps your accounts and sessions \u2014 fixes a stale or wrong view without signing out.",
|
||||
"button": "Refresh"
|
||||
},
|
||||
"reset_settings": {
|
||||
"label": "Einstellungen zurücksetzen",
|
||||
"description": "Alle Einstellungen auf Standardwerte zurücksetzen",
|
||||
|
||||
@@ -1637,6 +1637,11 @@
|
||||
"description": "View available keyboard shortcuts",
|
||||
"button": "View Shortcuts"
|
||||
},
|
||||
"refresh_cache": {
|
||||
"label": "Refresh cached data",
|
||||
"description": "Reload contacts, calendars, and folders from the server. Keeps your accounts and sessions \u2014 fixes a stale or wrong view without signing out.",
|
||||
"button": "Refresh"
|
||||
},
|
||||
"reset_settings": {
|
||||
"label": "Reset Settings",
|
||||
"description": "Restore all settings to default values",
|
||||
|
||||
@@ -1628,6 +1628,11 @@
|
||||
"description": "Ver atajos de teclado disponibles",
|
||||
"button": "Ver Atajos"
|
||||
},
|
||||
"refresh_cache": {
|
||||
"label": "Refresh cached data",
|
||||
"description": "Reload contacts, calendars, and folders from the server. Keeps your accounts and sessions \u2014 fixes a stale or wrong view without signing out.",
|
||||
"button": "Refresh"
|
||||
},
|
||||
"reset_settings": {
|
||||
"label": "Restablecer Configuración",
|
||||
"description": "Restaurar toda la configuración a los valores predeterminados",
|
||||
|
||||
@@ -1635,6 +1635,11 @@
|
||||
"description": "مشاهده میانبرهای صفحه کلید موجود",
|
||||
"button": "مشاهده میانبرها"
|
||||
},
|
||||
"refresh_cache": {
|
||||
"label": "Refresh cached data",
|
||||
"description": "Reload contacts, calendars, and folders from the server. Keeps your accounts and sessions \u2014 fixes a stale or wrong view without signing out.",
|
||||
"button": "Refresh"
|
||||
},
|
||||
"reset_settings": {
|
||||
"label": "بازنشانی تنظیمات",
|
||||
"description": "بازگردانی همه تنظیمات به مقادیر پیشفرض",
|
||||
|
||||
@@ -1628,6 +1628,11 @@
|
||||
"description": "Voir les raccourcis clavier disponibles",
|
||||
"button": "Voir les raccourcis"
|
||||
},
|
||||
"refresh_cache": {
|
||||
"label": "Refresh cached data",
|
||||
"description": "Reload contacts, calendars, and folders from the server. Keeps your accounts and sessions \u2014 fixes a stale or wrong view without signing out.",
|
||||
"button": "Refresh"
|
||||
},
|
||||
"reset_settings": {
|
||||
"label": "Réinitialiser les paramètres",
|
||||
"description": "Restaurer tous les paramètres par défaut",
|
||||
|
||||
@@ -1631,6 +1631,11 @@
|
||||
"description": "Elérhető billentyűparancsok megtekintése",
|
||||
"button": "Parancsok megtekintése"
|
||||
},
|
||||
"refresh_cache": {
|
||||
"label": "Refresh cached data",
|
||||
"description": "Reload contacts, calendars, and folders from the server. Keeps your accounts and sessions \u2014 fixes a stale or wrong view without signing out.",
|
||||
"button": "Refresh"
|
||||
},
|
||||
"reset_settings": {
|
||||
"label": "Beállítások visszaállítása",
|
||||
"description": "Összes beállítás visszaállítása az alapértelmezett értékekre",
|
||||
|
||||
@@ -1628,6 +1628,11 @@
|
||||
"description": "Visualizza le scorciatoie da tastiera disponibili",
|
||||
"button": "Visualizza scorciatoie"
|
||||
},
|
||||
"refresh_cache": {
|
||||
"label": "Refresh cached data",
|
||||
"description": "Reload contacts, calendars, and folders from the server. Keeps your accounts and sessions \u2014 fixes a stale or wrong view without signing out.",
|
||||
"button": "Refresh"
|
||||
},
|
||||
"reset_settings": {
|
||||
"label": "Ripristina impostazioni",
|
||||
"description": "Ripristina tutte le impostazioni ai valori predefiniti",
|
||||
|
||||
@@ -1628,6 +1628,11 @@
|
||||
"description": "利用可能なキーボードショートカットを表示",
|
||||
"button": "ショートカットを表示"
|
||||
},
|
||||
"refresh_cache": {
|
||||
"label": "Refresh cached data",
|
||||
"description": "Reload contacts, calendars, and folders from the server. Keeps your accounts and sessions \u2014 fixes a stale or wrong view without signing out.",
|
||||
"button": "Refresh"
|
||||
},
|
||||
"reset_settings": {
|
||||
"label": "設定をリセット",
|
||||
"description": "すべての設定をデフォルト値に戻す",
|
||||
|
||||
@@ -1628,6 +1628,11 @@
|
||||
"description": "사용 가능한 키보드 단축키를 확인해 보세요",
|
||||
"button": "단축키 보기"
|
||||
},
|
||||
"refresh_cache": {
|
||||
"label": "Refresh cached data",
|
||||
"description": "Reload contacts, calendars, and folders from the server. Keeps your accounts and sessions \u2014 fixes a stale or wrong view without signing out.",
|
||||
"button": "Refresh"
|
||||
},
|
||||
"reset_settings": {
|
||||
"label": "설정 초기화",
|
||||
"description": "모든 설정을 기본값으로 되돌려요",
|
||||
|
||||
@@ -1609,6 +1609,11 @@
|
||||
"description": "Skatīt pieejamos tastatūras īsinājumtaustiņus",
|
||||
"button": "Rādīt īsinājumtaustiņus"
|
||||
},
|
||||
"refresh_cache": {
|
||||
"label": "Refresh cached data",
|
||||
"description": "Reload contacts, calendars, and folders from the server. Keeps your accounts and sessions \u2014 fixes a stale or wrong view without signing out.",
|
||||
"button": "Refresh"
|
||||
},
|
||||
"reset_settings": {
|
||||
"label": "Atiestatīt iestatījumus",
|
||||
"description": "Atjaunot visus iestatījumus uz noklusējuma vērtībām",
|
||||
|
||||
@@ -1628,6 +1628,11 @@
|
||||
"description": "Bekijk beschikbare sneltoetsen",
|
||||
"button": "Sneltoetsen bekijken"
|
||||
},
|
||||
"refresh_cache": {
|
||||
"label": "Refresh cached data",
|
||||
"description": "Reload contacts, calendars, and folders from the server. Keeps your accounts and sessions \u2014 fixes a stale or wrong view without signing out.",
|
||||
"button": "Refresh"
|
||||
},
|
||||
"reset_settings": {
|
||||
"label": "Instellingen resetten",
|
||||
"description": "Herstel alle instellingen naar standaardwaarden",
|
||||
|
||||
@@ -1628,6 +1628,11 @@
|
||||
"description": "Wyświetl dostępne skróty klawiszowe",
|
||||
"button": "Pokaż skróty"
|
||||
},
|
||||
"refresh_cache": {
|
||||
"label": "Refresh cached data",
|
||||
"description": "Reload contacts, calendars, and folders from the server. Keeps your accounts and sessions \u2014 fixes a stale or wrong view without signing out.",
|
||||
"button": "Refresh"
|
||||
},
|
||||
"reset_settings": {
|
||||
"label": "Resetuj ustawienia",
|
||||
"description": "Przywróć wszystkie ustawienia do wartości domyślnych",
|
||||
|
||||
@@ -1628,6 +1628,11 @@
|
||||
"description": "Visualizar atalhos de teclado disponíveis",
|
||||
"button": "Ver Atalhos"
|
||||
},
|
||||
"refresh_cache": {
|
||||
"label": "Refresh cached data",
|
||||
"description": "Reload contacts, calendars, and folders from the server. Keeps your accounts and sessions \u2014 fixes a stale or wrong view without signing out.",
|
||||
"button": "Refresh"
|
||||
},
|
||||
"reset_settings": {
|
||||
"label": "Redefinir Configurações",
|
||||
"description": "Restaurar todas as configurações para valores padrão",
|
||||
|
||||
@@ -1635,6 +1635,11 @@
|
||||
"description": "Vedeți comenzile rapide de la tastatură disponibile",
|
||||
"button": "Comenzi rapide de vizualizare"
|
||||
},
|
||||
"refresh_cache": {
|
||||
"label": "Refresh cached data",
|
||||
"description": "Reload contacts, calendars, and folders from the server. Keeps your accounts and sessions \u2014 fixes a stale or wrong view without signing out.",
|
||||
"button": "Refresh"
|
||||
},
|
||||
"reset_settings": {
|
||||
"label": "Resetare setări",
|
||||
"description": "Restabiliți toate setările la valorile implicite",
|
||||
|
||||
@@ -1628,6 +1628,11 @@
|
||||
"description": "Просмотреть доступные сочетания клавиш",
|
||||
"button": "Показать сочетания"
|
||||
},
|
||||
"refresh_cache": {
|
||||
"label": "Refresh cached data",
|
||||
"description": "Reload contacts, calendars, and folders from the server. Keeps your accounts and sessions \u2014 fixes a stale or wrong view without signing out.",
|
||||
"button": "Refresh"
|
||||
},
|
||||
"reset_settings": {
|
||||
"label": "Сбросить настройки",
|
||||
"description": "Восстановить все настройки до значений по умолчанию",
|
||||
|
||||
@@ -1628,6 +1628,11 @@
|
||||
"description": "Mevcut klavye kısayollarını görüntüleyin",
|
||||
"button": "Kısayolları Görüntüle"
|
||||
},
|
||||
"refresh_cache": {
|
||||
"label": "Refresh cached data",
|
||||
"description": "Reload contacts, calendars, and folders from the server. Keeps your accounts and sessions \u2014 fixes a stale or wrong view without signing out.",
|
||||
"button": "Refresh"
|
||||
},
|
||||
"reset_settings": {
|
||||
"label": "Ayarları Sıfırla",
|
||||
"description": "Tüm ayarları varsayılan değerlerine geri yükle",
|
||||
|
||||
@@ -1628,6 +1628,11 @@
|
||||
"description": "Переглянути доступні комбінації клавіш",
|
||||
"button": "Перегляд ярликів"
|
||||
},
|
||||
"refresh_cache": {
|
||||
"label": "Refresh cached data",
|
||||
"description": "Reload contacts, calendars, and folders from the server. Keeps your accounts and sessions \u2014 fixes a stale or wrong view without signing out.",
|
||||
"button": "Refresh"
|
||||
},
|
||||
"reset_settings": {
|
||||
"label": "Скинути налаштування",
|
||||
"description": "Відновити всі налаштування до значень за замовчуванням",
|
||||
|
||||
@@ -1628,6 +1628,11 @@
|
||||
"description": "查看可用快捷键",
|
||||
"button": "查看快捷方式"
|
||||
},
|
||||
"refresh_cache": {
|
||||
"label": "Refresh cached data",
|
||||
"description": "Reload contacts, calendars, and folders from the server. Keeps your accounts and sessions \u2014 fixes a stale or wrong view without signing out.",
|
||||
"button": "Refresh"
|
||||
},
|
||||
"reset_settings": {
|
||||
"label": "重置设置",
|
||||
"description": "将所有设置恢复为默认值",
|
||||
|
||||
Reference in New Issue
Block a user