ui refactor

This commit is contained in:
Stijnus
2025-01-20 09:53:15 +01:00
parent 9230ef3b55
commit 436a8e54bf
41 changed files with 4749 additions and 1964 deletions

View File

@@ -4,3 +4,8 @@ export * from './useShortcuts';
export * from './useSnapScroll';
export * from './useEditChatDescription';
export { default } from './useViewport';
export { useUpdateCheck } from './useUpdateCheck';
export { useFeatures } from './useFeatures';
export { useNotifications } from './useNotifications';
export { useConnectionStatus } from './useConnectionStatus';
export { useDebugStatus } from './useDebugStatus';

View File

@@ -0,0 +1,61 @@
import { useState, useEffect } from 'react';
import { checkConnection } from '~/lib/api/connection';
const ACKNOWLEDGED_CONNECTION_ISSUE_KEY = 'bolt_acknowledged_connection_issue';
type ConnectionIssueType = 'disconnected' | 'high-latency' | null;
const getAcknowledgedIssue = (): string | null => {
try {
return localStorage.getItem(ACKNOWLEDGED_CONNECTION_ISSUE_KEY);
} catch {
return null;
}
};
export const useConnectionStatus = () => {
const [hasConnectionIssues, setHasConnectionIssues] = useState(false);
const [currentIssue, setCurrentIssue] = useState<ConnectionIssueType>(null);
const [acknowledgedIssue, setAcknowledgedIssue] = useState<string | null>(() => getAcknowledgedIssue());
const checkStatus = async () => {
try {
const status = await checkConnection();
const issue = !status.connected ? 'disconnected' : status.latency > 1000 ? 'high-latency' : null;
setCurrentIssue(issue);
// Only show issues if they're new or different from the acknowledged one
setHasConnectionIssues(issue !== null && issue !== acknowledgedIssue);
} catch (error) {
console.error('Failed to check connection:', error);
// Show connection issues if we can't even check the status
setCurrentIssue('disconnected');
setHasConnectionIssues(true);
}
};
useEffect(() => {
// Check immediately and then every 10 seconds
checkStatus();
const interval = setInterval(checkStatus, 10 * 1000);
return () => clearInterval(interval);
}, [acknowledgedIssue]);
const acknowledgeIssue = () => {
setAcknowledgedIssue(currentIssue);
setAcknowledgedIssue(currentIssue);
setHasConnectionIssues(false);
};
const resetAcknowledgment = () => {
setAcknowledgedIssue(null);
setAcknowledgedIssue(null);
checkStatus();
};
return { hasConnectionIssues, currentIssue, acknowledgeIssue, resetAcknowledgment };
};

View File

@@ -0,0 +1,89 @@
import { useState, useEffect } from 'react';
import { getDebugStatus, acknowledgeWarning, acknowledgeError, type DebugIssue } from '~/lib/api/debug';
const ACKNOWLEDGED_DEBUG_ISSUES_KEY = 'bolt_acknowledged_debug_issues';
const getAcknowledgedIssues = (): string[] => {
try {
const stored = localStorage.getItem(ACKNOWLEDGED_DEBUG_ISSUES_KEY);
return stored ? JSON.parse(stored) : [];
} catch {
return [];
}
};
const setAcknowledgedIssues = (issueIds: string[]) => {
try {
localStorage.setItem(ACKNOWLEDGED_DEBUG_ISSUES_KEY, JSON.stringify(issueIds));
} catch (error) {
console.error('Failed to persist acknowledged debug issues:', error);
}
};
export const useDebugStatus = () => {
const [hasActiveWarnings, setHasActiveWarnings] = useState(false);
const [activeIssues, setActiveIssues] = useState<DebugIssue[]>([]);
const [acknowledgedIssueIds, setAcknowledgedIssueIds] = useState<string[]>(() => getAcknowledgedIssues());
const checkDebugStatus = async () => {
try {
const status = await getDebugStatus();
const issues: DebugIssue[] = [
...status.warnings.map((w) => ({ ...w, type: 'warning' as const })),
...status.errors.map((e) => ({ ...e, type: 'error' as const })),
].filter((issue) => !acknowledgedIssueIds.includes(issue.id));
setActiveIssues(issues);
setHasActiveWarnings(issues.length > 0);
} catch (error) {
console.error('Failed to check debug status:', error);
}
};
useEffect(() => {
// Check immediately and then every 5 seconds
checkDebugStatus();
const interval = setInterval(checkDebugStatus, 5 * 1000);
return () => clearInterval(interval);
}, [acknowledgedIssueIds]);
const acknowledgeIssue = async (issue: DebugIssue) => {
try {
if (issue.type === 'warning') {
await acknowledgeWarning(issue.id);
} else {
await acknowledgeError(issue.id);
}
const newAcknowledgedIds = [...acknowledgedIssueIds, issue.id];
setAcknowledgedIssueIds(newAcknowledgedIds);
setAcknowledgedIssues(newAcknowledgedIds);
setActiveIssues((prev) => prev.filter((i) => i.id !== issue.id));
setHasActiveWarnings(activeIssues.length > 1);
} catch (error) {
console.error('Failed to acknowledge issue:', error);
}
};
const acknowledgeAllIssues = async () => {
try {
await Promise.all(
activeIssues.map((issue) =>
issue.type === 'warning' ? acknowledgeWarning(issue.id) : acknowledgeError(issue.id),
),
);
const newAcknowledgedIds = [...acknowledgedIssueIds, ...activeIssues.map((i) => i.id)];
setAcknowledgedIssueIds(newAcknowledgedIds);
setAcknowledgedIssues(newAcknowledgedIds);
setActiveIssues([]);
setHasActiveWarnings(false);
} catch (error) {
console.error('Failed to acknowledge all issues:', error);
}
};
return { hasActiveWarnings, activeIssues, acknowledgeIssue, acknowledgeAllIssues };
};

View File

@@ -0,0 +1,72 @@
import { useState, useEffect } from 'react';
import { getFeatureFlags, markFeatureViewed, type Feature } from '~/lib/api/features';
const VIEWED_FEATURES_KEY = 'bolt_viewed_features';
const getViewedFeatures = (): string[] => {
try {
const stored = localStorage.getItem(VIEWED_FEATURES_KEY);
return stored ? JSON.parse(stored) : [];
} catch {
return [];
}
};
const setViewedFeatures = (featureIds: string[]) => {
try {
localStorage.setItem(VIEWED_FEATURES_KEY, JSON.stringify(featureIds));
} catch (error) {
console.error('Failed to persist viewed features:', error);
}
};
export const useFeatures = () => {
const [hasNewFeatures, setHasNewFeatures] = useState(false);
const [unviewedFeatures, setUnviewedFeatures] = useState<Feature[]>([]);
const [viewedFeatureIds, setViewedFeatureIds] = useState<string[]>(() => getViewedFeatures());
useEffect(() => {
const checkNewFeatures = async () => {
try {
const features = await getFeatureFlags();
const unviewed = features.filter((feature) => !viewedFeatureIds.includes(feature.id));
setUnviewedFeatures(unviewed);
setHasNewFeatures(unviewed.length > 0);
} catch (error) {
console.error('Failed to check for new features:', error);
}
};
checkNewFeatures();
}, [viewedFeatureIds]);
const acknowledgeFeature = async (featureId: string) => {
try {
await markFeatureViewed(featureId);
const newViewedIds = [...viewedFeatureIds, featureId];
setViewedFeatureIds(newViewedIds);
setViewedFeatures(newViewedIds);
setUnviewedFeatures((prev) => prev.filter((feature) => feature.id !== featureId));
setHasNewFeatures(unviewedFeatures.length > 1);
} catch (error) {
console.error('Failed to acknowledge feature:', error);
}
};
const acknowledgeAllFeatures = async () => {
try {
await Promise.all(unviewedFeatures.map((feature) => markFeatureViewed(feature.id)));
const newViewedIds = [...viewedFeatureIds, ...unviewedFeatures.map((f) => f.id)];
setViewedFeatureIds(newViewedIds);
setViewedFeatures(newViewedIds);
setUnviewedFeatures([]);
setHasNewFeatures(false);
} catch (error) {
console.error('Failed to acknowledge all features:', error);
}
};
return { hasNewFeatures, unviewedFeatures, acknowledgeFeature, acknowledgeAllFeatures };
};

View File

@@ -0,0 +1,77 @@
import { useState, useEffect } from 'react';
import { getNotifications, markNotificationRead, type Notification } from '~/lib/api/notifications';
const READ_NOTIFICATIONS_KEY = 'bolt_read_notifications';
const getReadNotifications = (): string[] => {
try {
const stored = localStorage.getItem(READ_NOTIFICATIONS_KEY);
return stored ? JSON.parse(stored) : [];
} catch {
return [];
}
};
const setReadNotifications = (notificationIds: string[]) => {
try {
localStorage.setItem(READ_NOTIFICATIONS_KEY, JSON.stringify(notificationIds));
} catch (error) {
console.error('Failed to persist read notifications:', error);
}
};
export const useNotifications = () => {
const [hasUnreadNotifications, setHasUnreadNotifications] = useState(false);
const [unreadNotifications, setUnreadNotifications] = useState<Notification[]>([]);
const [readNotificationIds, setReadNotificationIds] = useState<string[]>(() => getReadNotifications());
const checkNotifications = async () => {
try {
const notifications = await getNotifications();
const unread = notifications.filter((n) => !readNotificationIds.includes(n.id));
setUnreadNotifications(unread);
setHasUnreadNotifications(unread.length > 0);
} catch (error) {
console.error('Failed to check notifications:', error);
}
};
useEffect(() => {
// Check immediately and then every minute
checkNotifications();
const interval = setInterval(checkNotifications, 60 * 1000);
return () => clearInterval(interval);
}, [readNotificationIds]);
const markAsRead = async (notificationId: string) => {
try {
await markNotificationRead(notificationId);
const newReadIds = [...readNotificationIds, notificationId];
setReadNotificationIds(newReadIds);
setReadNotifications(newReadIds);
setUnreadNotifications((prev) => prev.filter((n) => n.id !== notificationId));
setHasUnreadNotifications(unreadNotifications.length > 1);
} catch (error) {
console.error('Failed to mark notification as read:', error);
}
};
const markAllAsRead = async () => {
try {
await Promise.all(unreadNotifications.map((n) => markNotificationRead(n.id)));
const newReadIds = [...readNotificationIds, ...unreadNotifications.map((n) => n.id)];
setReadNotificationIds(newReadIds);
setReadNotifications(newReadIds);
setUnreadNotifications([]);
setHasUnreadNotifications(false);
} catch (error) {
console.error('Failed to mark all notifications as read:', error);
}
};
return { hasUnreadNotifications, unreadNotifications, markAsRead, markAllAsRead };
};

View File

@@ -0,0 +1,222 @@
import { useStore } from '@nanostores/react';
import {
isDebugMode,
isEventLogsEnabled,
isLocalModelsEnabled,
LOCAL_PROVIDERS,
promptStore,
providersStore,
latestBranchStore,
autoSelectStarterTemplate,
enableContextOptimizationStore,
tabConfigurationStore,
updateTabConfiguration as updateTabConfig,
resetTabConfiguration as resetTabConfig,
} from '~/lib/stores/settings';
import { useCallback, useEffect, useState } from 'react';
import Cookies from 'js-cookie';
import type { IProviderSetting, ProviderInfo, IProviderConfig } from '~/types/model';
import type { TabWindowConfig, TabVisibilityConfig } from '~/components/settings/settings.types';
import { logStore } from '~/lib/stores/logs';
import { getLocalStorage, setLocalStorage } from '~/utils/localStorage';
export interface Settings {
theme: 'light' | 'dark' | 'system';
language: string;
notifications: boolean;
eventLogs: boolean;
timezone: string;
tabConfiguration: TabWindowConfig;
}
export interface UseSettingsReturn {
// Theme and UI settings
setTheme: (theme: Settings['theme']) => void;
setLanguage: (language: string) => void;
setNotifications: (enabled: boolean) => void;
setEventLogs: (enabled: boolean) => void;
setTimezone: (timezone: string) => void;
settings: Settings;
// Provider settings
providers: Record<string, IProviderConfig>;
activeProviders: ProviderInfo[];
updateProviderSettings: (provider: string, config: IProviderSetting) => void;
isLocalModel: boolean;
enableLocalModels: (enabled: boolean) => void;
// Debug and development settings
debug: boolean;
enableDebugMode: (enabled: boolean) => void;
eventLogs: boolean;
promptId: string;
setPromptId: (promptId: string) => void;
isLatestBranch: boolean;
enableLatestBranch: (enabled: boolean) => void;
autoSelectTemplate: boolean;
setAutoSelectTemplate: (enabled: boolean) => void;
contextOptimizationEnabled: boolean;
enableContextOptimization: (enabled: boolean) => void;
// Tab configuration
tabConfiguration: TabWindowConfig;
updateTabConfiguration: (config: TabVisibilityConfig) => void;
resetTabConfiguration: () => void;
}
export function useSettings(): UseSettingsReturn {
const providers = useStore(providersStore) as Record<string, IProviderConfig>;
const debug = useStore(isDebugMode);
const eventLogs = useStore(isEventLogsEnabled);
const promptId = useStore(promptStore);
const isLocalModel = useStore(isLocalModelsEnabled);
const isLatestBranch = useStore(latestBranchStore);
const autoSelectTemplate = useStore(autoSelectStarterTemplate);
const [activeProviders, setActiveProviders] = useState<ProviderInfo[]>([]);
const contextOptimizationEnabled = useStore(enableContextOptimizationStore);
const tabConfiguration = useStore(tabConfigurationStore);
const [settings, setSettings] = useState<Settings>(() => {
const storedSettings = getLocalStorage('settings');
return {
theme: storedSettings?.theme || 'system',
language: storedSettings?.language || 'en',
notifications: storedSettings?.notifications ?? true,
eventLogs: storedSettings?.eventLogs ?? true,
timezone: storedSettings?.timezone || Intl.DateTimeFormat().resolvedOptions().timeZone,
tabConfiguration,
};
});
// writing values to cookies on change
useEffect(() => {
const providers = providersStore.get();
const providerSetting: Record<string, IProviderSetting> = {};
Object.keys(providers).forEach((provider) => {
providerSetting[provider] = providers[provider].settings;
});
Cookies.set('providers', JSON.stringify(providerSetting));
}, [providers]);
useEffect(() => {
let active = Object.entries(providers)
.filter(([_key, provider]) => provider.settings.enabled)
.map(([_k, p]) => p);
if (!isLocalModel) {
active = active.filter((p) => !LOCAL_PROVIDERS.includes(p.name));
}
setActiveProviders(active);
}, [providers, isLocalModel]);
const saveSettings = useCallback((newSettings: Partial<Settings>) => {
setSettings((prev) => {
const updated = { ...prev, ...newSettings };
setLocalStorage('settings', updated);
return updated;
});
}, []);
const updateProviderSettings = useCallback((provider: string, config: IProviderSetting) => {
providersStore.setKey(provider, { settings: config } as IProviderConfig);
}, []);
const enableDebugMode = useCallback((enabled: boolean) => {
isDebugMode.set(enabled);
logStore.logSystem(`Debug mode ${enabled ? 'enabled' : 'disabled'}`);
Cookies.set('isDebugEnabled', String(enabled));
}, []);
const setEventLogs = useCallback((enabled: boolean) => {
isEventLogsEnabled.set(enabled);
logStore.logSystem(`Event logs ${enabled ? 'enabled' : 'disabled'}`);
Cookies.set('isEventLogsEnabled', String(enabled));
}, []);
const enableLocalModels = useCallback((enabled: boolean) => {
isLocalModelsEnabled.set(enabled);
logStore.logSystem(`Local models ${enabled ? 'enabled' : 'disabled'}`);
Cookies.set('isLocalModelsEnabled', String(enabled));
}, []);
const setPromptId = useCallback((promptId: string) => {
promptStore.set(promptId);
Cookies.set('promptId', promptId);
}, []);
const enableLatestBranch = useCallback((enabled: boolean) => {
latestBranchStore.set(enabled);
logStore.logSystem(`Main branch updates ${enabled ? 'enabled' : 'disabled'}`);
Cookies.set('isLatestBranch', String(enabled));
}, []);
const setAutoSelectTemplate = useCallback((enabled: boolean) => {
autoSelectStarterTemplate.set(enabled);
logStore.logSystem(`Auto select template ${enabled ? 'enabled' : 'disabled'}`);
Cookies.set('autoSelectTemplate', String(enabled));
}, []);
const enableContextOptimization = useCallback((enabled: boolean) => {
enableContextOptimizationStore.set(enabled);
logStore.logSystem(`Context optimization ${enabled ? 'enabled' : 'disabled'}`);
Cookies.set('contextOptimizationEnabled', String(enabled));
}, []);
const setTheme = useCallback(
(theme: Settings['theme']) => {
saveSettings({ theme });
},
[saveSettings],
);
const setLanguage = useCallback(
(language: string) => {
saveSettings({ language });
},
[saveSettings],
);
const setNotifications = useCallback(
(enabled: boolean) => {
saveSettings({ notifications: enabled });
},
[saveSettings],
);
const setTimezone = useCallback(
(timezone: string) => {
saveSettings({ timezone });
},
[saveSettings],
);
return {
...settings,
providers,
activeProviders,
updateProviderSettings,
isLocalModel,
enableLocalModels,
debug,
enableDebugMode,
eventLogs,
setEventLogs,
promptId,
setPromptId,
isLatestBranch,
enableLatestBranch,
autoSelectTemplate,
setAutoSelectTemplate,
contextOptimizationEnabled,
enableContextOptimization,
setTheme,
setLanguage,
setNotifications,
setTimezone,
settings,
tabConfiguration,
updateTabConfiguration: updateTabConfig,
resetTabConfiguration: resetTabConfig,
};
}

View File

@@ -1,229 +0,0 @@
import { useStore } from '@nanostores/react';
import {
isDebugMode,
isEventLogsEnabled,
isLocalModelsEnabled,
LOCAL_PROVIDERS,
promptStore,
providersStore,
latestBranchStore,
autoSelectStarterTemplate,
enableContextOptimizationStore,
} from '~/lib/stores/settings';
import { useCallback, useEffect, useState } from 'react';
import Cookies from 'js-cookie';
import type { IProviderSetting, ProviderInfo } from '~/types/model';
import { logStore } from '~/lib/stores/logs'; // assuming logStore is imported from this location
interface CommitData {
commit: string;
version?: string;
}
const versionData: CommitData = {
commit: __COMMIT_HASH,
version: __APP_VERSION,
};
export function useSettings() {
const providers = useStore(providersStore);
const debug = useStore(isDebugMode);
const eventLogs = useStore(isEventLogsEnabled);
const promptId = useStore(promptStore);
const isLocalModel = useStore(isLocalModelsEnabled);
const isLatestBranch = useStore(latestBranchStore);
const autoSelectTemplate = useStore(autoSelectStarterTemplate);
const [activeProviders, setActiveProviders] = useState<ProviderInfo[]>([]);
const contextOptimizationEnabled = useStore(enableContextOptimizationStore);
// Function to check if we're on stable version
const checkIsStableVersion = async () => {
try {
const response = await fetch(
`https://api.github.com/repos/stackblitz-labs/bolt.diy/git/refs/tags/v${versionData.version}`,
);
const data: { object: { sha: string } } = await response.json();
return versionData.commit.slice(0, 7) === data.object.sha.slice(0, 7);
} catch (error) {
console.warn('Error checking stable version:', error);
return false;
}
};
// reading values from cookies on mount
useEffect(() => {
const savedProviders = Cookies.get('providers');
if (savedProviders) {
try {
const parsedProviders: Record<string, IProviderSetting> = JSON.parse(savedProviders);
Object.keys(providers).forEach((provider) => {
const currentProviderSettings = parsedProviders[provider];
if (currentProviderSettings) {
providersStore.setKey(provider, {
...providers[provider],
settings: {
...currentProviderSettings,
enabled: currentProviderSettings.enabled ?? true,
},
});
}
});
} catch (error) {
console.error('Failed to parse providers from cookies:', error);
}
}
// load debug mode from cookies
const savedDebugMode = Cookies.get('isDebugEnabled');
if (savedDebugMode) {
isDebugMode.set(savedDebugMode === 'true');
}
// load event logs from cookies
const savedEventLogs = Cookies.get('isEventLogsEnabled');
if (savedEventLogs) {
isEventLogsEnabled.set(savedEventLogs === 'true');
}
// load local models from cookies
const savedLocalModels = Cookies.get('isLocalModelsEnabled');
if (savedLocalModels) {
isLocalModelsEnabled.set(savedLocalModels === 'true');
}
const promptId = Cookies.get('promptId');
if (promptId) {
promptStore.set(promptId);
}
// load latest branch setting from cookies or determine based on version
const savedLatestBranch = Cookies.get('isLatestBranch');
let checkCommit = Cookies.get('commitHash');
if (checkCommit === undefined) {
checkCommit = versionData.commit;
}
if (savedLatestBranch === undefined || checkCommit !== versionData.commit) {
// If setting hasn't been set by user, check version
checkIsStableVersion().then((isStable) => {
const shouldUseLatest = !isStable;
latestBranchStore.set(shouldUseLatest);
Cookies.set('isLatestBranch', String(shouldUseLatest));
Cookies.set('commitHash', String(versionData.commit));
});
} else {
latestBranchStore.set(savedLatestBranch === 'true');
}
const autoSelectTemplate = Cookies.get('autoSelectTemplate');
if (autoSelectTemplate) {
autoSelectStarterTemplate.set(autoSelectTemplate === 'true');
}
const savedContextOptimizationEnabled = Cookies.get('contextOptimizationEnabled');
if (savedContextOptimizationEnabled) {
enableContextOptimizationStore.set(savedContextOptimizationEnabled === 'true');
}
}, []);
// writing values to cookies on change
useEffect(() => {
const providers = providersStore.get();
const providerSetting: Record<string, IProviderSetting> = {};
Object.keys(providers).forEach((provider) => {
providerSetting[provider] = providers[provider].settings;
});
Cookies.set('providers', JSON.stringify(providerSetting));
}, [providers]);
useEffect(() => {
let active = Object.entries(providers)
.filter(([_key, provider]) => provider.settings.enabled)
.map(([_k, p]) => p);
if (!isLocalModel) {
active = active.filter((p) => !LOCAL_PROVIDERS.includes(p.name));
}
setActiveProviders(active);
}, [providers, isLocalModel]);
// helper function to update settings
const updateProviderSettings = useCallback(
(provider: string, config: IProviderSetting) => {
const settings = providers[provider].settings;
providersStore.setKey(provider, { ...providers[provider], settings: { ...settings, ...config } });
},
[providers],
);
const enableDebugMode = useCallback((enabled: boolean) => {
isDebugMode.set(enabled);
logStore.logSystem(`Debug mode ${enabled ? 'enabled' : 'disabled'}`);
Cookies.set('isDebugEnabled', String(enabled));
}, []);
const enableEventLogs = useCallback((enabled: boolean) => {
isEventLogsEnabled.set(enabled);
logStore.logSystem(`Event logs ${enabled ? 'enabled' : 'disabled'}`);
Cookies.set('isEventLogsEnabled', String(enabled));
}, []);
const enableLocalModels = useCallback((enabled: boolean) => {
isLocalModelsEnabled.set(enabled);
logStore.logSystem(`Local models ${enabled ? 'enabled' : 'disabled'}`);
Cookies.set('isLocalModelsEnabled', String(enabled));
}, []);
const setPromptId = useCallback((promptId: string) => {
promptStore.set(promptId);
Cookies.set('promptId', promptId);
}, []);
const enableLatestBranch = useCallback((enabled: boolean) => {
latestBranchStore.set(enabled);
logStore.logSystem(`Main branch updates ${enabled ? 'enabled' : 'disabled'}`);
Cookies.set('isLatestBranch', String(enabled));
}, []);
const setAutoSelectTemplate = useCallback((enabled: boolean) => {
autoSelectStarterTemplate.set(enabled);
logStore.logSystem(`Auto select template ${enabled ? 'enabled' : 'disabled'}`);
Cookies.set('autoSelectTemplate', String(enabled));
}, []);
const enableContextOptimization = useCallback((enabled: boolean) => {
enableContextOptimizationStore.set(enabled);
logStore.logSystem(`Context optimization ${enabled ? 'enabled' : 'disabled'}`);
Cookies.set('contextOptimizationEnabled', String(enabled));
}, []);
return {
providers,
activeProviders,
updateProviderSettings,
debug,
enableDebugMode,
eventLogs,
enableEventLogs,
isLocalModel,
enableLocalModels,
promptId,
setPromptId,
isLatestBranch,
enableLatestBranch,
autoSelectTemplate,
setAutoSelectTemplate,
contextOptimizationEnabled,
enableContextOptimization,
};
}

View File

@@ -0,0 +1,58 @@
import { useState, useEffect } from 'react';
import { checkForUpdates, acknowledgeUpdate } from '~/lib/api/updates';
const LAST_ACKNOWLEDGED_VERSION_KEY = 'bolt_last_acknowledged_version';
export const useUpdateCheck = () => {
const [hasUpdate, setHasUpdate] = useState(false);
const [currentVersion, setCurrentVersion] = useState<string>('');
const [lastAcknowledgedVersion, setLastAcknowledgedVersion] = useState<string | null>(() => {
try {
return localStorage.getItem(LAST_ACKNOWLEDGED_VERSION_KEY);
} catch {
return null;
}
});
useEffect(() => {
const checkUpdate = async () => {
try {
const { available, version } = await checkForUpdates();
setCurrentVersion(version);
// Only show update if it's a new version and hasn't been acknowledged
setHasUpdate(available && version !== lastAcknowledgedVersion);
} catch (error) {
console.error('Failed to check for updates:', error);
}
};
// Check immediately and then every 30 minutes
checkUpdate();
const interval = setInterval(checkUpdate, 30 * 60 * 1000);
return () => clearInterval(interval);
}, [lastAcknowledgedVersion]);
const handleAcknowledgeUpdate = async () => {
try {
const { version } = await checkForUpdates();
await acknowledgeUpdate(version);
// Store in localStorage
try {
localStorage.setItem(LAST_ACKNOWLEDGED_VERSION_KEY, version);
} catch (error) {
console.error('Failed to persist acknowledged version:', error);
}
setLastAcknowledgedVersion(version);
setHasUpdate(false);
} catch (error) {
console.error('Failed to acknowledge update:', error);
}
};
return { hasUpdate, currentVersion, acknowledgeUpdate: handleAcknowledgeUpdate };
};