feat: update connectiontab and datatab security fix (#1614)

* feat: update connectiontab and datatab security fix

# Connection Components and Diagnostics Updates

## GitHub Connection Component Changes
- Updated the disconnect button styling to match Vercel's design:
  - Changed from `<Button>` component to native `<button>` element
  - Added red background (`bg-red-500`) with hover effect (`hover:bg-red-600`)
  - Updated icon from `i-ph:sign-out` to `i-ph:plug`
  - Simplified text to just "Disconnect"
  - Added connection status indicator with check-circle icon and "Connected to GitHub" text

## ConnectionDiagnostics Tab Updates
### Added New Connection Diagnostics
- Implemented diagnostics for Vercel and Supabase connections
- Added new helper function `safeJsonParse` for safer JSON parsing operations

### Diagnostic Checks Added
- **Vercel Diagnostics:**
  - LocalStorage token verification
  - API endpoint connectivity test
  - Connection status validation
  - Reset functionality for Vercel connection

- **Supabase Diagnostics:**
  - LocalStorage credentials verification
  - API endpoint connectivity test
  - Connection status validation
  - Reset functionality for Supabase connection

### UI Enhancements
- Added new status cards for Vercel and Supabase
- Implemented reset buttons with consistent styling
- Added loading states during diagnostics
- Enhanced error handling and user feedback

### Function Updates
- Extended `runDiagnostics` function to include Vercel and Supabase checks
- Added new reset helper functions for each connection type
- Improved error handling and status reporting
- Enhanced toast notifications for better user feedback

### Visual Consistency
- Matched styling of new diagnostic cards with existing GitHub and Netlify cards
- Consistent use of icons and status indicators
- Uniform button styling across all connection types
- Maintained consistent spacing and layout patterns

### Code Structure
- Organized diagnostic checks into clear, separate sections
- Improved error handling and type safety
- Enhanced code readability and maintainability
- Added comprehensive status compilation for all connections

These changes ensure a consistent user experience across all connection types while providing robust diagnostic capabilities for troubleshooting connection issues.

# DataTab.tsx Changes

## Code Cleanup
- Removed unused variables from useDataOperations hook:
  - Removed `handleExportAPIKeys`
  - Removed `handleUndo`
  - Removed `lastOperation`

This change improves code quality by removing unused variables and resolves ESLint warnings without affecting any functionality.

* Test commit to verify pre-commit hook
This commit is contained in:
Stijnus
2025-04-08 13:06:43 +02:00
committed by GitHub
parent 8c70dd6123
commit 552f08acea
11 changed files with 757 additions and 363 deletions

View File

@@ -59,7 +59,15 @@ export function useDataOperations({
const showProgress = useCallback((message: string, percent: number) => {
setProgressMessage(message);
setProgressPercent(percent);
toast.loading(`${message} (${percent}%)`, { toastId: 'operation-progress' });
// Dismiss any existing progress toast before showing a new one
toast.dismiss('progress-toast');
toast.loading(`${message} (${percent}%)`, {
position: 'bottom-right',
autoClose: 3000,
toastId: 'progress-toast', // Use the same ID for all progress messages
});
}, []);
/**
@@ -68,7 +76,15 @@ export function useDataOperations({
const handleExportSettings = useCallback(async () => {
setIsExporting(true);
setProgressPercent(0);
toast.loading('Preparing settings export...', { toastId: 'operation-progress' });
// Dismiss any existing toast first
toast.dismiss('progress-toast');
toast.loading('Preparing settings export...', {
position: 'bottom-right',
autoClose: 3000,
toastId: 'progress-toast',
});
try {
// Step 1: Export settings
@@ -97,14 +113,26 @@ export function useDataOperations({
// Step 4: Complete
showProgress('Completing export', 100);
toast.success('Settings exported successfully', { toastId: 'operation-progress' });
// Dismiss progress toast before showing success toast
toast.dismiss('progress-toast');
toast.success('Settings exported successfully', {
position: 'bottom-right',
autoClose: 3000,
});
// Save operation for potential undo
setLastOperation({ type: 'export-settings', data: settingsData });
} catch (error) {
console.error('Error exporting settings:', error);
// Dismiss progress toast before showing error toast
toast.dismiss('progress-toast');
toast.error(`Failed to export settings: ${error instanceof Error ? error.message : 'Unknown error'}`, {
toastId: 'operation-progress',
position: 'bottom-right',
autoClose: 3000,
});
} finally {
setIsExporting(false);
@@ -120,14 +148,23 @@ export function useDataOperations({
const handleExportSelectedSettings = useCallback(
async (categoryIds: string[]) => {
if (!categoryIds || categoryIds.length === 0) {
toast.error('No settings categories selected');
toast.error('No settings categories selected', {
position: 'bottom-right',
autoClose: 3000,
});
return;
}
setIsExporting(true);
setProgressPercent(0);
// Dismiss any existing toast first
toast.dismiss('progress-toast');
toast.loading(`Preparing export of ${categoryIds.length} settings categories...`, {
toastId: 'operation-progress',
position: 'bottom-right',
autoClose: 3000,
toastId: 'progress-toast',
});
try {
@@ -163,7 +200,7 @@ export function useDataOperations({
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'bolt-settings-selected.json';
a.download = `bolt-settings-${categoryIds.join('-')}.json`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
@@ -171,16 +208,29 @@ export function useDataOperations({
// Step 5: Complete
showProgress('Completing export', 100);
// Dismiss progress toast before showing success toast
toast.dismiss('progress-toast');
toast.success(`${categoryIds.length} settings categories exported successfully`, {
toastId: 'operation-progress',
position: 'bottom-right',
autoClose: 3000,
});
// Save operation for potential undo
setLastOperation({ type: 'export-selected-settings', data: { categoryIds, settings: filteredSettings } });
setLastOperation({
type: 'export-selected-settings',
data: { settings: filteredSettings, categories: categoryIds },
});
} catch (error) {
console.error('Error exporting selected settings:', error);
toast.error(`Failed to export selected settings: ${error instanceof Error ? error.message : 'Unknown error'}`, {
toastId: 'operation-progress',
// Dismiss progress toast before showing error toast
toast.dismiss('progress-toast');
toast.error(`Failed to export settings: ${error instanceof Error ? error.message : 'Unknown error'}`, {
position: 'bottom-right',
autoClose: 3000,
});
} finally {
setIsExporting(false);
@@ -196,7 +246,10 @@ export function useDataOperations({
*/
const handleExportAllChats = useCallback(async () => {
if (!db) {
toast.error('Database not available');
toast.error('Database not available', {
position: 'bottom-right',
autoClose: 3000,
});
return;
}
@@ -208,7 +261,15 @@ export function useDataOperations({
setIsExporting(true);
setProgressPercent(0);
toast.loading('Preparing chats export...', { toastId: 'operation-progress' });
// Dismiss any existing toast first
toast.dismiss('progress-toast');
toast.loading('Preparing chats export...', {
position: 'bottom-right',
autoClose: 3000,
toastId: 'progress-toast',
});
try {
// Step 1: Export chats
@@ -250,6 +311,8 @@ export function useDataOperations({
exportDate: new Date().toISOString(),
};
console.log(`Preparing to export ${exportData.chats.length} chats`);
// Step 2: Create blob
showProgress('Creating file', 50);
@@ -271,14 +334,26 @@ export function useDataOperations({
// Step 4: Complete
showProgress('Completing export', 100);
toast.success(`${exportData.chats.length} chats exported successfully`, { toastId: 'operation-progress' });
// Dismiss progress toast before showing success toast
toast.dismiss('progress-toast');
toast.success(`${exportData.chats.length} chats exported successfully`, {
position: 'bottom-right',
autoClose: 3000,
});
// Save operation for potential undo
setLastOperation({ type: 'export-all-chats', data: exportData });
setLastOperation({ type: 'export-chats', data: exportData });
} catch (error) {
console.error('Error exporting chats:', error);
// Dismiss progress toast before showing error toast
toast.dismiss('progress-toast');
toast.error(`Failed to export chats: ${error instanceof Error ? error.message : 'Unknown error'}`, {
toastId: 'operation-progress',
position: 'bottom-right',
autoClose: 3000,
});
} finally {
setIsExporting(false);
@@ -294,105 +369,102 @@ export function useDataOperations({
const handleExportSelectedChats = useCallback(
async (chatIds: string[]) => {
if (!db) {
toast.error('Database not available');
toast.error('Database not available', {
position: 'bottom-right',
autoClose: 3000,
});
return;
}
if (!chatIds || chatIds.length === 0) {
toast.error('No chats selected');
toast.error('No chats selected', {
position: 'bottom-right',
autoClose: 3000,
});
return;
}
setIsExporting(true);
setProgressPercent(0);
toast.loading(`Preparing export of ${chatIds.length} chats...`, { toastId: 'operation-progress' });
// Dismiss any existing toast first
toast.dismiss('progress-toast');
toast.loading(`Preparing export of ${chatIds.length} chats...`, {
position: 'bottom-right',
autoClose: 3000,
toastId: 'progress-toast',
});
try {
// Step 1: Directly query each selected chat from database
showProgress('Retrieving selected chats from database', 20);
// Step 1: Get chats from database
showProgress('Retrieving chats from database', 25);
console.log('Database details for selected chats:', {
name: db.name,
version: db.version,
objectStoreNames: Array.from(db.objectStoreNames),
const transaction = db.transaction(['chats'], 'readonly');
const store = transaction.objectStore('chats');
// Create an array to store the promises for getting each chat
const chatPromises = chatIds.map((chatId) => {
return new Promise<any>((resolve, reject) => {
const request = store.get(chatId);
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
});
// Query each chat directly from the database
const selectedChats = await Promise.all(
chatIds.map(async (chatId) => {
return new Promise<any>((resolve, reject) => {
try {
const transaction = db.transaction(['chats'], 'readonly');
const store = transaction.objectStore('chats');
const request = store.get(chatId);
// Wait for all promises to resolve
const chats = await Promise.all(chatPromises);
const filteredChats = chats.filter(Boolean); // Remove any null/undefined results
request.onsuccess = () => {
if (request.result) {
console.log(`Found chat with ID ${chatId}:`, {
id: request.result.id,
messageCount: request.result.messages?.length || 0,
});
} else {
console.log(`Chat with ID ${chatId} not found`);
}
resolve(request.result || null);
};
request.onerror = () => {
console.error(`Error retrieving chat ${chatId}:`, request.error);
reject(request.error);
};
} catch (err) {
console.error(`Error in transaction for chat ${chatId}:`, err);
reject(err);
}
});
}),
);
// Filter out any null results (chats that weren't found)
const filteredChats = selectedChats.filter((chat) => chat !== null);
console.log(`Found ${filteredChats.length} selected chats out of ${chatIds.length} requested`);
// Step 2: Prepare export data
showProgress('Preparing export data', 40);
console.log(`Retrieved ${filteredChats.length} chats for export`);
// Create export data
const exportData = {
chats: filteredChats,
exportDate: new Date().toISOString(),
};
// Step 3: Create blob
showProgress('Creating file', 60);
// Step 2: Create blob
showProgress('Creating file', 50);
const blob = new Blob([JSON.stringify(exportData, null, 2)], {
type: 'application/json',
});
// Step 4: Download file
showProgress('Downloading file', 80);
// Step 3: Download file
showProgress('Downloading file', 75);
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'bolt-chats-selected.json';
a.download = 'bolt-selected-chats.json';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
// Step 5: Complete
// Step 4: Complete
showProgress('Completing export', 100);
toast.success(`${filteredChats.length} chats exported successfully`, { toastId: 'operation-progress' });
// Dismiss progress toast before showing success toast
toast.dismiss('progress-toast');
toast.success(`${filteredChats.length} chats exported successfully`, {
position: 'bottom-right',
autoClose: 3000,
});
// Save operation for potential undo
setLastOperation({ type: 'export-selected-chats', data: { chatIds, chats: filteredChats } });
} catch (error) {
console.error('Error exporting selected chats:', error);
// Dismiss progress toast before showing error toast
toast.dismiss('progress-toast');
toast.error(`Failed to export selected chats: ${error instanceof Error ? error.message : 'Unknown error'}`, {
toastId: 'operation-progress',
position: 'bottom-right',
autoClose: 3000,
});
} finally {
setIsExporting(false);
@@ -411,7 +483,15 @@ export function useDataOperations({
async (file: File) => {
setIsImporting(true);
setProgressPercent(0);
toast.loading(`Importing settings from ${file.name}...`, { toastId: 'operation-progress' });
// Dismiss any existing toast first
toast.dismiss('progress-toast');
toast.loading(`Importing settings from ${file.name}...`, {
position: 'bottom-right',
autoClose: 3000,
toastId: 'progress-toast',
});
try {
// Step 1: Read file
@@ -437,15 +517,27 @@ export function useDataOperations({
// Step 5: Complete
showProgress('Completing import', 100);
toast.success('Settings imported successfully', { toastId: 'operation-progress' });
// Dismiss progress toast before showing success toast
toast.dismiss('progress-toast');
toast.success('Settings imported successfully', {
position: 'bottom-right',
autoClose: 3000,
});
if (onReloadSettings) {
onReloadSettings();
}
} catch (error) {
console.error('Error importing settings:', error);
// Dismiss progress toast before showing error toast
toast.dismiss('progress-toast');
toast.error(`Failed to import settings: ${error instanceof Error ? error.message : 'Unknown error'}`, {
toastId: 'operation-progress',
position: 'bottom-right',
autoClose: 3000,
});
} finally {
setIsImporting(false);
@@ -463,13 +555,24 @@ export function useDataOperations({
const handleImportChats = useCallback(
async (file: File) => {
if (!db) {
toast.error('Database not available');
toast.error('Database not available', {
position: 'bottom-right',
autoClose: 3000,
});
return;
}
setIsImporting(true);
setProgressPercent(0);
toast.loading(`Importing chats from ${file.name}...`, { toastId: 'operation-progress' });
// Dismiss any existing toast first
toast.dismiss('progress-toast');
toast.loading(`Importing chats from ${file.name}...`, {
position: 'bottom-right',
autoClose: 3000,
toastId: 'progress-toast',
});
try {
// Step 1: Read file
@@ -553,15 +656,27 @@ export function useDataOperations({
// Step 6: Complete
showProgress('Completing import', 100);
toast.success(`${validatedChats.length} chats imported successfully`, { toastId: 'operation-progress' });
// Dismiss progress toast before showing success toast
toast.dismiss('progress-toast');
toast.success(`${validatedChats.length} chats imported successfully`, {
position: 'bottom-right',
autoClose: 3000,
});
if (onReloadChats) {
onReloadChats();
}
} catch (error) {
console.error('Error importing chats:', error);
// Dismiss progress toast before showing error toast
toast.dismiss('progress-toast');
toast.error(`Failed to import chats: ${error instanceof Error ? error.message : 'Unknown error'}`, {
toastId: 'operation-progress',
position: 'bottom-right',
autoClose: 3000,
});
} finally {
setIsImporting(false);
@@ -580,7 +695,15 @@ export function useDataOperations({
async (file: File) => {
setIsImporting(true);
setProgressPercent(0);
toast.loading(`Importing API keys from ${file.name}...`, { toastId: 'operation-progress' });
// Dismiss any existing toast first
toast.dismiss('progress-toast');
toast.loading(`Importing API keys from ${file.name}...`, {
position: 'bottom-right',
autoClose: 3000,
toastId: 'progress-toast',
});
try {
// Step 1: Read file
@@ -611,6 +734,9 @@ export function useDataOperations({
// Step 5: Complete
showProgress('Completing import', 100);
// Dismiss progress toast before showing success toast
toast.dismiss('progress-toast');
// Count how many keys were imported
const keyCount = Object.keys(newKeys).length;
const newKeyCount = Object.keys(newKeys).filter(
@@ -620,7 +746,7 @@ export function useDataOperations({
toast.success(
`${keyCount} API keys imported successfully (${newKeyCount} new/updated)\n` +
'Note: Keys are stored in browser cookies. For server-side usage, add them to your .env.local file.',
{ toastId: 'operation-progress', autoClose: 5000 },
{ position: 'bottom-right', autoClose: 5000 },
);
if (onReloadSettings) {
@@ -628,8 +754,13 @@ export function useDataOperations({
}
} catch (error) {
console.error('Error importing API keys:', error);
// Dismiss progress toast before showing error toast
toast.dismiss('progress-toast');
toast.error(`Failed to import API keys: ${error instanceof Error ? error.message : 'Unknown error'}`, {
toastId: 'operation-progress',
position: 'bottom-right',
autoClose: 3000,
});
} finally {
setIsImporting(false);
@@ -646,7 +777,15 @@ export function useDataOperations({
const handleResetSettings = useCallback(async () => {
setIsResetting(true);
setProgressPercent(0);
toast.loading('Resetting settings...', { toastId: 'operation-progress' });
// Dismiss any existing toast first
toast.dismiss('progress-toast');
toast.loading('Resetting settings...', {
position: 'bottom-right',
autoClose: 3000,
toastId: 'progress-toast',
});
try {
if (db) {
@@ -662,18 +801,36 @@ export function useDataOperations({
// Step 3: Complete
showProgress('Completing reset', 100);
toast.success('Settings reset successfully', { toastId: 'operation-progress' });
// Dismiss progress toast before showing success toast
toast.dismiss('progress-toast');
toast.success('Settings reset successfully', {
position: 'bottom-right',
autoClose: 3000,
});
if (onResetSettings) {
onResetSettings();
}
} else {
toast.error('Database not available', { toastId: 'operation-progress' });
// Dismiss progress toast before showing error toast
toast.dismiss('progress-toast');
toast.error('Database not available', {
position: 'bottom-right',
autoClose: 3000,
});
}
} catch (error) {
console.error('Error resetting settings:', error);
// Dismiss progress toast before showing error toast
toast.dismiss('progress-toast');
toast.error(`Failed to reset settings: ${error instanceof Error ? error.message : 'Unknown error'}`, {
toastId: 'operation-progress',
position: 'bottom-right',
autoClose: 3000,
});
} finally {
setIsResetting(false);
@@ -687,13 +844,24 @@ export function useDataOperations({
*/
const handleResetChats = useCallback(async () => {
if (!db) {
toast.error('Database not available');
toast.error('Database not available', {
position: 'bottom-right',
autoClose: 3000,
});
return;
}
setIsResetting(true);
setProgressPercent(0);
toast.loading('Deleting all chats...', { toastId: 'operation-progress' });
// Dismiss any existing toast first
toast.dismiss('progress-toast');
toast.loading('Deleting all chats...', {
position: 'bottom-right',
autoClose: 3000,
toastId: 'progress-toast',
});
try {
// Step 1: Save current chats for potential undo
@@ -708,15 +876,27 @@ export function useDataOperations({
// Step 3: Complete
showProgress('Completing deletion', 100);
toast.success('All chats deleted successfully', { toastId: 'operation-progress' });
// Dismiss progress toast before showing success toast
toast.dismiss('progress-toast');
toast.success('All chats deleted successfully', {
position: 'bottom-right',
autoClose: 3000,
});
if (onResetChats) {
onResetChats();
}
} catch (error) {
console.error('Error resetting chats:', error);
// Dismiss progress toast before showing error toast
toast.dismiss('progress-toast');
toast.error(`Failed to delete chats: ${error instanceof Error ? error.message : 'Unknown error'}`, {
toastId: 'operation-progress',
position: 'bottom-right',
autoClose: 3000,
});
} finally {
setIsResetting(false);
@@ -731,7 +911,15 @@ export function useDataOperations({
const handleDownloadTemplate = useCallback(async () => {
setIsDownloadingTemplate(true);
setProgressPercent(0);
toast.loading('Preparing API keys template...', { toastId: 'operation-progress' });
// Dismiss any existing toast first
toast.dismiss('progress-toast');
toast.loading('Creating API keys template...', {
position: 'bottom-right',
autoClose: 3000,
toastId: 'progress-toast',
});
try {
// Step 1: Create template
@@ -756,11 +944,23 @@ export function useDataOperations({
// Step 3: Complete
showProgress('Completing download', 100);
toast.success('API keys template downloaded successfully', { toastId: 'operation-progress' });
// Dismiss progress toast before showing success toast
toast.dismiss('progress-toast');
toast.success('Template downloaded successfully', {
position: 'bottom-right',
autoClose: 3000,
});
} catch (error) {
console.error('Error downloading template:', error);
// Dismiss progress toast before showing error toast
toast.dismiss('progress-toast');
toast.error(`Failed to download template: ${error instanceof Error ? error.message : 'Unknown error'}`, {
toastId: 'operation-progress',
position: 'bottom-right',
autoClose: 3000,
});
} finally {
setIsDownloadingTemplate(false);
@@ -775,7 +975,15 @@ export function useDataOperations({
const handleExportAPIKeys = useCallback(async () => {
setIsExporting(true);
setProgressPercent(0);
toast.loading('Preparing API keys export...', { toastId: 'operation-progress' });
// Dismiss any existing toast first
toast.dismiss('progress-toast');
toast.loading('Exporting API keys...', {
position: 'bottom-right',
autoClose: 3000,
toastId: 'progress-toast',
});
try {
// Step 1: Get API keys from all sources
@@ -811,14 +1019,26 @@ export function useDataOperations({
// Step 4: Complete
showProgress('Completing export', 100);
toast.success('API keys exported successfully', { toastId: 'operation-progress' });
// Dismiss progress toast before showing success toast
toast.dismiss('progress-toast');
toast.success('API keys exported successfully', {
position: 'bottom-right',
autoClose: 3000,
});
// Save operation for potential undo
setLastOperation({ type: 'export-api-keys', data: apiKeys });
} catch (error) {
console.error('Error exporting API keys:', error);
// Dismiss progress toast before showing error toast
toast.dismiss('progress-toast');
toast.error(`Failed to export API keys: ${error instanceof Error ? error.message : 'Unknown error'}`, {
toastId: 'operation-progress',
position: 'bottom-right',
autoClose: 3000,
});
} finally {
setIsExporting(false);
@@ -832,18 +1052,35 @@ export function useDataOperations({
*/
const handleUndo = useCallback(async () => {
if (!lastOperation || !db) {
toast.error('Nothing to undo');
toast.error('Nothing to undo', {
position: 'bottom-right',
autoClose: 3000,
});
return;
}
toast.loading('Attempting to undo last operation...', { toastId: 'operation-progress' });
// Dismiss any existing toast first
toast.dismiss('progress-toast');
toast.loading('Processing undo operation...', {
position: 'bottom-right',
autoClose: 3000,
toastId: 'progress-toast',
});
try {
switch (lastOperation.type) {
case 'import-settings': {
// Restore previous settings
await ImportExportService.importSettings(lastOperation.data.previous);
toast.success('Settings import undone', { toastId: 'operation-progress' });
// Dismiss progress toast before showing success toast
toast.dismiss('progress-toast');
toast.success('Operation undone successfully', {
position: 'bottom-right',
autoClose: 3000,
});
if (onReloadSettings) {
onReloadSettings();
@@ -869,7 +1106,13 @@ export function useDataOperations({
transaction.onerror = reject;
});
toast.success('Chats import undone', { toastId: 'operation-progress' });
// Dismiss progress toast before showing success toast
toast.dismiss('progress-toast');
toast.success('Operation undone successfully', {
position: 'bottom-right',
autoClose: 3000,
});
if (onReloadChats) {
onReloadChats();
@@ -881,7 +1124,14 @@ export function useDataOperations({
case 'reset-settings': {
// Restore previous settings
await ImportExportService.importSettings(lastOperation.data.previous);
toast.success('Settings reset undone', { toastId: 'operation-progress' });
// Dismiss progress toast before showing success toast
toast.dismiss('progress-toast');
toast.success('Operation undone successfully', {
position: 'bottom-right',
autoClose: 3000,
});
if (onReloadSettings) {
onReloadSettings();
@@ -904,7 +1154,13 @@ export function useDataOperations({
chatTransaction.onerror = reject;
});
toast.success('Chats deletion undone', { toastId: 'operation-progress' });
// Dismiss progress toast before showing success toast
toast.dismiss('progress-toast');
toast.success('Operation undone successfully', {
position: 'bottom-right',
autoClose: 3000,
});
if (onReloadChats) {
onReloadChats();
@@ -919,7 +1175,14 @@ export function useDataOperations({
const newKeys = ImportExportService.importAPIKeys(previousAPIKeys);
const apiKeysJson = JSON.stringify(newKeys);
document.cookie = `apiKeys=${apiKeysJson}; path=/; max-age=31536000`;
toast.success('API keys import undone', { toastId: 'operation-progress' });
// Dismiss progress toast before showing success toast
toast.dismiss('progress-toast');
toast.success('Operation undone successfully', {
position: 'bottom-right',
autoClose: 3000,
});
if (onReloadSettings) {
onReloadSettings();
@@ -929,15 +1192,26 @@ export function useDataOperations({
}
default:
toast.error('Cannot undo this operation', { toastId: 'operation-progress' });
// Dismiss progress toast before showing error toast
toast.dismiss('progress-toast');
toast.error('Cannot undo this operation', {
position: 'bottom-right',
autoClose: 3000,
});
}
// Clear the last operation after undoing
setLastOperation(null);
} catch (error) {
console.error('Error undoing operation:', error);
// Dismiss progress toast before showing error toast
toast.dismiss('progress-toast');
toast.error(`Failed to undo: ${error instanceof Error ? error.message : 'Unknown error'}`, {
toastId: 'operation-progress',
position: 'bottom-right',
autoClose: 3000,
});
}
}, [lastOperation, db, onReloadSettings, onReloadChats]);

View File

@@ -153,20 +153,20 @@ export class PreviewsStore {
try {
// Watch for file changes
const watcher = await webcontainer.fs.watch('**/*', { persistent: true });
webcontainer.internal.watchPaths(
{ include: ['**/*'], exclude: ['**/node_modules', '.git'], includeContent: true },
async (_events) => {
const previews = this.previews.get();
// Use the native watch events
(watcher as any).addEventListener('change', async () => {
const previews = this.previews.get();
for (const preview of previews) {
const previewId = this.getPreviewId(preview.baseUrl);
for (const preview of previews) {
const previewId = this.getPreviewId(preview.baseUrl);
if (previewId) {
this.broadcastFileChange(previewId);
if (previewId) {
this.broadcastFileChange(previewId);
}
}
}
});
},
);
// Watch for DOM changes that might affect storage
if (typeof window !== 'undefined') {