Merge pull request #173 from graycyrus/develop

Feat: Log Out & Clear App Data
This commit is contained in:
Cyrus Gray
2026-03-13 20:11:31 +05:30
committed by GitHub
2 changed files with 150 additions and 6 deletions
+112 -6
View File
@@ -1,18 +1,49 @@
import { clearToken } from '../../store/authSlice';
import { useAppDispatch } from '../../store/hooks';
import { useState } from 'react';
import SettingsHeader from './components/SettingsHeader';
import SettingsMenuItem from './components/SettingsMenuItem';
import { useSettingsNavigation } from './hooks/useSettingsNavigation';
import { persistor } from '../../store';
import { skillManager } from '../../lib/skills/manager';
const SettingsHome = () => {
const dispatch = useAppDispatch();
const { navigateToSettings, closeSettings } = useSettingsNavigation();
const { navigateToSettings } = useSettingsNavigation();
const [showLogoutAndClearModal, setShowLogoutAndClearModal] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const handleLogout = async () => {
await dispatch(clearToken());
closeSettings();
// Simple logout without clearing data - redirect to login
window.location.href = '/login';
};
const clearAllAppData = async () => {
await persistor.purge();
window.localStorage.clear();
window.sessionStorage.clear();
// NEW: Clear skills databases (emails, chats, cached files)
try {
await skillManager.clearAllSkillsData();
} catch (error) {
console.warn('Failed to clear skills data:', error);
// Continue with logout even if skills clearing fails
}
// Complete reset - redirect to login for fresh start
window.location.href = '/login';
}
const handleLogoutAndClearData = async () => {
try {
setIsLoading(true);
setError(null);
await clearAllAppData(); // This will redirect to login
} catch (_error) {
setError('Failed to clear data and logout. Please try again.');
setIsLoading(false);
}
}
// const handleViewEncryptionKey = () => {
// // TODO: Show encryption key in a secure modal
// console.log('View encryption key');
@@ -244,6 +275,18 @@ const SettingsHome = () => {
onClick: handleDeleteAllData,
dangerous: true,
},
{
id: 'logout-and-clear',
title: 'Log Out & Clear App Data',
description: 'Sign out and permanently clear all local data',
icon: (
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1" />
</svg>
),
onClick: () => setShowLogoutAndClearModal(true),
dangerous: true
},
{
id: 'logout',
title: 'Log out',
@@ -302,6 +345,69 @@ const SettingsHome = () => {
</div>
</div>
</div>
{/* Log Out & Clear Data Confirmation Modal */}
{showLogoutAndClearModal && (
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/60">
<div className="bg-stone-900 rounded-2xl max-w-md w-full p-6 border border-stone-700/50">
<div className="flex items-center gap-3 mb-4">
<div className="w-10 h-10 rounded-lg bg-amber-500/20 flex items-center justify-center">
<svg className="w-5 h-5 text-amber-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1" />
</svg>
</div>
<div>
<h3 className="text-lg font-semibold text-white">Log Out & Clear App Data</h3>
</div>
</div>
<div className="mb-6">
<p className="text-stone-300 text-sm leading-relaxed">
This will sign you out and permanently delete ALL data including:
App settings and conversations
Email data from Gmail
Chat history from Telegram
Cached files from Notion
All other skills data
<br /><br />
This action cannot be undone and may take a few moments to complete.
</p>
{error && (
<div className="mt-3 p-3 rounded-lg bg-coral-500/10 border border-coral-500/20">
<p className="text-coral-400 text-sm">{error}</p>
</div>
)}
</div>
<div className="flex gap-3">
<button
onClick={() => {
setShowLogoutAndClearModal(false);
setError(null);
}}
disabled={isLoading}
className="flex-1 px-4 py-2 rounded-lg border border-stone-600 text-stone-300 hover:bg-stone-800 transition-colors disabled:opacity-50"
>
Cancel
</button>
<button
onClick={handleLogoutAndClearData}
disabled={isLoading}
className="flex-1 px-4 py-2 rounded-lg bg-amber-600 hover:bg-amber-500 text-white transition-colors disabled:opacity-50 flex items-center justify-center gap-2"
>
{isLoading && (
<svg className="w-4 h-4 animate-spin" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" />
</svg>
)}
{isLoading ? 'Clearing All Data...' : 'Log Out & Clear Everything'}
</button>
</div>
</div>
</div>
)}
</div>
);
};
+38
View File
@@ -527,6 +527,44 @@ class SkillManager {
throw new Error(`Unknown reverse RPC method: ${method}`);
}
}
/**
* Clear all skills databases and cached data.
* Used for nuclear reset functionality.
*/
async clearAllSkillsData(): Promise<void> {
try {
// Stop all running skills first
await this.stopAll();
// Get all skill IDs from Redux state
const state = store.getState();
const skillIds = Object.keys(state.skills.skills);
// Clear data for each skill
const clearPromises = skillIds.map(async (skillId) => {
try {
// Get skill data directory path
const dataDir = await invoke<string>("runtime_skill_data_dir", { skillId });
// Note: We don't directly delete directories here since there's no exposed
// Tauri command for that. Instead, we rely on the backend to handle
// clearing when skills are disabled/reset via Redux state clearing.
console.log(`[SkillManager] Skill ${skillId} data directory: ${dataDir}`);
} catch (err) {
console.warn(`[SkillManager] Failed to get data directory for skill ${skillId}:`, err);
}
});
await Promise.all(clearPromises);
console.log("[SkillManager] Skills data clearing initiated");
} catch (error) {
console.error("[SkillManager] Failed to clear skills data:", error);
throw new Error("Failed to clear skills databases");
}
}
}
// Export singleton