diff --git a/src/components/sidebar/view/modals/VersionUpgradeModal.tsx b/src/components/sidebar/view/modals/VersionUpgradeModal.tsx new file mode 100644 index 0000000..891bd44 --- /dev/null +++ b/src/components/sidebar/view/modals/VersionUpgradeModal.tsx @@ -0,0 +1,219 @@ +import { useCallback, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { authenticatedFetch } from "../../../../utils/api"; +import { ReleaseInfo } from "../../../../types/sharedTypes"; + +interface VersionUpgradeModalProps { + isOpen: boolean; + onClose: () => void; + releaseInfo: ReleaseInfo | null; + currentVersion: string; + latestVersion: string | null; +} + +export default function VersionUpgradeModal({ + isOpen, + onClose, + releaseInfo, + currentVersion, + latestVersion +}: VersionUpgradeModalProps) { + const { t } = useTranslation('common'); + const [isUpdating, setIsUpdating] = useState(false); + const [updateOutput, setUpdateOutput] = useState(''); + const [updateError, setUpdateError] = useState(''); + + const handleUpdateNow = useCallback(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); + } + }, []); + + if (!isOpen) return null; + + return ( +
+ {/* Backdrop */} + +
+ + {/* Version Info */} +
+
+ {t('versionUpdate.currentVersion')} + {currentVersion} +
+
+ {t('versionUpdate.latestVersion')} + {latestVersion} +
+
+ + {/* Changelog */} + {releaseInfo?.body && ( +
+
+

{t('versionUpdate.whatsNew')}

+ {releaseInfo?.htmlUrl && ( + + {t('versionUpdate.viewFullRelease')} + + + + + )} +
+
+
+ {cleanChangelog(releaseInfo.body)} +
+
+
+ )} + + {/* Update Output */} + {(updateOutput || updateError) && ( +
+

{t('versionUpdate.updateProgress')}

+
+
{updateOutput}
+
+ {updateError && ( +
+ {updateError} +
+ )} +
+ )} + + {/* Upgrade Instructions */} + {!isUpdating && !updateOutput && ( +
+

{t('versionUpdate.manualUpgrade')}

+
+ + git checkout main && git pull && npm install + +
+

+ {t('versionUpdate.manualUpgradeHint')} +

+
+ )} + + {/* Actions */} +
+ + {!updateOutput && ( + <> + + + + )} +
+ + + ); +}; + +// 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(); +}; diff --git a/src/components/sidebar/view/subcomponents/SidebarModals.tsx b/src/components/sidebar/view/subcomponents/SidebarModals.tsx index a93d11b..22f63e2 100644 --- a/src/components/sidebar/view/subcomponents/SidebarModals.tsx +++ b/src/components/sidebar/view/subcomponents/SidebarModals.tsx @@ -4,8 +4,8 @@ import { AlertTriangle, Trash2 } from 'lucide-react'; import type { TFunction } from 'i18next'; import { Button } from '../../../ui/button'; import ProjectCreationWizard from '../../../ProjectCreationWizard'; -import Settings from '../../../Settings'; -import VersionUpgradeModal from '../../../modals/VersionUpgradeModal'; +import Settings from '../../../settings/Settings'; +import VersionUpgradeModal from '../modals/VersionUpgradeModal'; import type { Project } from '../../../../types/app'; import type { ReleaseInfo } from '../../../../types/sharedTypes'; import { normalizeProjectForSettings } from '../../utils/utils';