diff --git a/src/App.jsx b/src/App.jsx
index 3ded1e6..2be8595 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -26,6 +26,7 @@ import MainContent from './components/MainContent';
import MobileNav from './components/MobileNav';
import Settings from './components/Settings';
import QuickSettingsPanel from './components/QuickSettingsPanel';
+import VersionUpgradeModal from './components/modals/VersionUpgradeModal';
import { ThemeProvider } from './contexts/ThemeContext';
import { AuthProvider } from './contexts/AuthContext';
@@ -580,202 +581,6 @@ function AppContent() {
}
}, []);
- // Version Upgrade Modal Component
- const VersionUpgradeModal = () => {
- const { t } = useTranslation('common');
- const [isUpdating, setIsUpdating] = useState(false);
- const [updateOutput, setUpdateOutput] = useState('');
- const [updateError, setUpdateError] = useState('');
-
- if (!showVersionModal) return null;
-
- // Clean up changelog by removing GitHub-specific metadata
- const cleanChangelog = (body) => {
- if (!body) return '';
-
- return body
- // Remove full commit hashes (40 character hex strings)
- .replace(/\b[0-9a-f]{40}\b/gi, '')
- // Remove short commit hashes (7-10 character hex strings at start of line or after dash/space)
- .replace(/(?:^|\s|-)([0-9a-f]{7,10})\b/gi, '')
- // Remove "Full Changelog" links
- .replace(/\*\*Full Changelog\*\*:.*$/gim, '')
- // Remove compare links (e.g., https://github.com/.../compare/v1.0.0...v1.0.1)
- .replace(/https?:\/\/github\.com\/[^\/]+\/[^\/]+\/compare\/[^\s)]+/gi, '')
- // Clean up multiple consecutive empty lines
- .replace(/\n\s*\n\s*\n/g, '\n\n')
- // Trim whitespace
- .trim();
- };
-
- const handleUpdateNow = async () => {
- setIsUpdating(true);
- setUpdateOutput('Starting update...\n');
- setUpdateError('');
-
- try {
- // Call the backend API to run the update command
- const response = await authenticatedFetch('/api/system/update', {
- method: 'POST',
- });
-
- const data = await response.json();
-
- if (response.ok) {
- setUpdateOutput(prev => prev + data.output + '\n');
- setUpdateOutput(prev => prev + '\n✅ Update completed successfully!\n');
- setUpdateOutput(prev => prev + 'Please restart the server to apply changes.\n');
- } else {
- setUpdateError(data.error || 'Update failed');
- setUpdateOutput(prev => prev + '\n❌ Update failed: ' + (data.error || 'Unknown error') + '\n');
- }
- } catch (error) {
- setUpdateError(error.message);
- setUpdateOutput(prev => prev + '\n❌ Update failed: ' + error.message + '\n');
- } finally {
- setIsUpdating(false);
- }
- };
-
- return (
-
- {/* Backdrop */}
-
- );
- };
return (
@@ -977,7 +782,7 @@ function AppContent() {
/>
{/* Version Upgrade Modal */}
-
+
);
}
diff --git a/src/components/modals/VersionUpgradeModal.tsx b/src/components/modals/VersionUpgradeModal.tsx
new file mode 100644
index 0000000..bb7e459
--- /dev/null
+++ b/src/components/modals/VersionUpgradeModal.tsx
@@ -0,0 +1,206 @@
+import { useState } from "react";
+import { useTranslation } from "react-i18next";
+import { authenticatedFetch } from "../../utils/api";
+import { useVersionCheck } from "../../hooks/useVersionCheck";
+
+interface VersionUpgradeModalProps {
+ showVersionModal: boolean;
+ setShowVersionModal: (show: boolean) => void;
+}
+
+export default function VersionUpgradeModal({ showVersionModal, setShowVersionModal }: VersionUpgradeModalProps) {
+ const { t } = useTranslation('common');
+ const [isUpdating, setIsUpdating] = useState(false);
+ const [updateOutput, setUpdateOutput] = useState('');
+ const [updateError, setUpdateError] = useState('');
+ const { latestVersion, currentVersion, releaseInfo } = useVersionCheck('siteboon', 'claudecodeui');
+
+ if (!showVersionModal) return null;
+
+ const handleUpdateNow = async () => {
+ setIsUpdating(true);
+ setUpdateOutput('Starting update...\n');
+ setUpdateError('');
+
+ try {
+ // Call the backend API to run the update command
+ const response = await authenticatedFetch('/api/system/update', {
+ method: 'POST',
+ });
+
+ const data = await response.json();
+
+ if (response.ok) {
+ setUpdateOutput(prev => prev + data.output + '\n');
+ setUpdateOutput(prev => prev + '\n✅ Update completed successfully!\n');
+ setUpdateOutput(prev => prev + 'Please restart the server to apply changes.\n');
+ } else {
+ setUpdateError(data.error || 'Update failed');
+ setUpdateOutput(prev => prev + '\n❌ Update failed: ' + (data.error || 'Unknown error') + '\n');
+ }
+ } catch (error: any) {
+ setUpdateError(error.message);
+ setUpdateOutput(prev => prev + '\n❌ Update failed: ' + error.message + '\n');
+ } finally {
+ setIsUpdating(false);
+ }
+ };
+
+ return (
+
+ {/* Backdrop */}
+
setShowVersionModal(false)}
+ aria-label={t('versionUpdate.ariaLabels.closeModal')}
+ />
+
+ {/* Modal */}
+
+ {/* Header */}
+
+
+
+
+
{t('versionUpdate.title')}
+
+ {releaseInfo?.title || t('versionUpdate.newVersionReady')}
+
+
+
+
setShowVersionModal(false)}
+ className="p-2 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 rounded-md hover:bg-gray-100 dark:hover:bg-gray-700"
+ >
+
+
+
+
+ {/* Version Info */}
+
+
+ {t('versionUpdate.currentVersion')}
+ {currentVersion}
+
+
+ {t('versionUpdate.latestVersion')}
+ {latestVersion}
+
+
+
+ {/* Changelog */}
+ {releaseInfo?.body && (
+
+
+
+
+ {cleanChangelog(releaseInfo.body)}
+
+
+
+ )}
+
+ {/* Update Output */}
+ {updateOutput && (
+
+
{t('versionUpdate.updateProgress')}
+
+
+ )}
+
+ {/* Upgrade Instructions */}
+ {!isUpdating && !updateOutput && (
+
+
{t('versionUpdate.manualUpgrade')}
+
+
+ git checkout main && git pull && npm install
+
+
+
+ {t('versionUpdate.manualUpgradeHint')}
+
+
+ )}
+
+ {/* Actions */}
+
+
setShowVersionModal(false)}
+ className="flex-1 px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 bg-gray-100 dark:bg-gray-700 hover:bg-gray-200 dark:hover:bg-gray-600 rounded-md transition-colors"
+ >
+ {updateOutput ? t('versionUpdate.buttons.close') : t('versionUpdate.buttons.later')}
+
+ {!updateOutput && (
+ <>
+
{
+ navigator.clipboard.writeText('git checkout main && git pull && npm install');
+ }}
+ className="flex-1 px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 bg-gray-100 dark:bg-gray-700 hover:bg-gray-200 dark:hover:bg-gray-600 rounded-md transition-colors"
+ >
+ {t('versionUpdate.buttons.copyCommand')}
+
+
+ {isUpdating ? (
+ <>
+
+ {t('versionUpdate.buttons.updating')}
+ >
+ ) : (
+ t('versionUpdate.buttons.updateNow')
+ )}
+
+ >
+ )}
+
+
+
+ );
+};
+
+// Clean up changelog by removing GitHub-specific metadata
+const cleanChangelog = (body: string) => {
+ if (!body) return '';
+
+ return body
+ // Remove full commit hashes (40 character hex strings)
+ .replace(/\b[0-9a-f]{40}\b/gi, '')
+ // Remove short commit hashes (7-10 character hex strings at start of line or after dash/space)
+ .replace(/(?:^|\s|-)([0-9a-f]{7,10})\b/gi, '')
+ // Remove "Full Changelog" links
+ .replace(/\*\*Full Changelog\*\*:.*$/gim, '')
+ // Remove compare links (e.g., https://github.com/.../compare/v1.0.0...v1.0.1)
+ .replace(/https?:\/\/github\.com\/[^\/]+\/[^\/]+\/compare\/[^\s)]+/gi, '')
+ // Clean up multiple consecutive empty lines
+ .replace(/\n\s*\n\s*\n/g, '\n\n')
+ // Trim whitespace
+ .trim();
+};
\ No newline at end of file