/** * BoostMedia AI Content Generator Admin - Updates Page * * @package BoostMedia_AI * @license GPL-2.0-or-later */ import { useState, useEffect, useCallback } from 'react' import { Download, RefreshCw, CheckCircle, AlertTriangle, Loader2, Package, ExternalLink, } from 'lucide-react' import { Header } from '../components/layout/Header' import { Card, CardContent, Button } from '../components/common' import { useUpdateCheck } from '../hooks/useUpdateCheck' import { t, tf, getDateLocale } from '../lib/i18n' function sanitizeHtml(html: string): string { const div = document.createElement('div') div.innerHTML = html div.querySelectorAll('script, iframe, object, embed, form').forEach(el => el.remove()) div.querySelectorAll('*').forEach(el => { Array.from(el.attributes).forEach(attr => { if (attr.name.startsWith('on') || attr.value.startsWith('javascript:')) { el.removeAttribute(attr.name) } }) }) return div.innerHTML } interface UpdateCheckResult { current_version: string latest_version: string update_available: boolean download_url: string changelog: string release_date: string last_checked: string } function BoostContentIcon({ className }: { className?: string }) { return ( ) } function ToggleSwitch({ checked, onChange, disabled }: { checked: boolean onChange: () => void disabled?: boolean }) { return ( ) } export default function UpdatesPage() { const [checkResult, setCheckResult] = useState(null) const [isChecking, setIsChecking] = useState(false) const [isUpdating, setIsUpdating] = useState(false) const [error, setError] = useState(null) const [successMessage, setSuccessMessage] = useState(null) const [autoUpdate, setAutoUpdate] = useState(true) const [autoUpdateLoading, setAutoUpdateLoading] = useState(false) const { clearCache } = useUpdateCheck() const currentVersion = window.bmaiSettings?.version || '1.0.0' const rawApiUrl = window.bmaiSettings?.apiUrl || '/wp-json/bmai/v1/' const apiUrl = rawApiUrl.replace(/\/+$/, '') + '/' const nonce = window.bmaiSettings?.nonce || '' useEffect(() => { checkForUpdates() fetchAutoUpdateSetting() }, []) const fetchAutoUpdateSetting = useCallback(async () => { try { const response = await fetch(`${apiUrl}updates/auto-update`, { method: 'GET', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': nonce }, credentials: 'same-origin', }) if (response.ok) { const data = await response.json() if (data.success && data.data) { setAutoUpdate(data.data.auto_update) } } } catch { /* default stays true */ } }, [apiUrl, nonce]) const toggleAutoUpdate = useCallback(async () => { const newValue = !autoUpdate setAutoUpdateLoading(true) try { const response = await fetch(`${apiUrl}updates/auto-update`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': nonce }, credentials: 'same-origin', body: JSON.stringify({ auto_update: newValue }), }) if (response.ok) { const data = await response.json() if (data.success && data.data) { setAutoUpdate(data.data.auto_update) } } } catch { /* revert on error */ } finally { setAutoUpdateLoading(false) } }, [autoUpdate, apiUrl, nonce]) const [updaterUnavailable, setUpdaterUnavailable] = useState(false) async function checkForUpdates() { setIsChecking(true) setError(null) setSuccessMessage(null) try { const response = await fetch(`${apiUrl}updates/check`, { method: 'GET', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': nonce }, credentials: 'same-origin', }) if (response.status === 403 || response.status === 404) { setUpdaterUnavailable(true) return } if (!response.ok) throw new Error(t('Error checking updates') + ` (${response.status})`) const data = await response.json() if (data.success !== false) { setCheckResult(data.data || data) } else { throw new Error(data.message || t('Error checking updates')) } } catch (err: any) { setError(err.message || t('Cannot check updates. Try again later.')) } finally { setIsChecking(false) } } async function installUpdate() { if (!checkResult?.update_available || !checkResult?.download_url?.startsWith('https://')) return setIsUpdating(true) setError(null) setSuccessMessage(null) try { const response = await fetch(`${apiUrl}updates/install`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': nonce }, credentials: 'same-origin', body: JSON.stringify({ download_url: checkResult.download_url, version: checkResult.latest_version }), }) if (!response.ok) throw new Error(t('Error installing update') + ` (${response.status})`) const data = await response.json() if (data.success !== false) { const backendMessage = typeof data.message === 'string' && data.message ? `${t(data.message)} ` : '' setSuccessMessage(`${backendMessage}${tf('Updated successfully to version %s! Refresh the page.', checkResult.latest_version)}`.trim()) setCheckResult(prev => prev ? { ...prev, update_available: false, current_version: prev.latest_version } : null) clearCache() } else { throw new Error(data.message || t('Error installing update')) } } catch (err: any) { setError(err.message || t('Cannot install update. Try manually.')) } finally { setIsUpdating(false) } } const versionAvailableParts = t('Version %s available').split('%s') if (updaterUnavailable) { return (

{t('Updates are managed through WordPress.org')}

{t('Check for plugin updates in the WordPress Dashboard under Plugins or Dashboard → Updates.')}

) } return (
{/* Status Messages */} {error && (

{t('Error')}

{error}

)} {successMessage && (

{t('Success')}

{successMessage}

)} {/* Current Version + Update Status */}
{/* Current Version Card */}

{t('Current Version')}

v{checkResult?.current_version || currentVersion}

BoostMedia AI Content Generator for WordPress

{checkResult?.last_checked && (

{t('Last check:')} {new Date(checkResult.last_checked).toLocaleString(getDateLocale())}

)}
{/* Update Status Card */}

{t('Update Status')}

{isChecking ? (

{t('Checking for updates...')}

) : checkResult?.update_available ? (

{t('Update available!')}

{versionAvailableParts[0]}{checkResult.latest_version}{versionAvailableParts[1]}

{checkResult.release_date && (

{t('Released:')} {new Date(checkResult.release_date).toLocaleDateString(getDateLocale())}

)} {checkResult.download_url?.startsWith('https://') && ( {t('Manual download')} )}
) : checkResult ? (

{t('Your version is up to date!')}

{tf('BoostMedia AI Content Generator v%s is the latest version', checkResult.current_version)}

) : (

{t('Click the refresh button to check for updates')}

)}
{/* Changelog */} {checkResult?.changelog && checkResult.update_available && (

{tf("What's new in version v%s", checkResult.latest_version)}

)} {/* Auto-update Toggle */}

{t('Automatic Updates')}

{t('When enabled, WordPress will install new versions of BoostMedia AI Content Generator automatically in the background.')}

{autoUpdate ? t('Automatic updates enabled. New versions will be installed automatically.') : t('Automatic updates disabled. You will need to update manually from this page.')}
{/* Info Card */}

{t('About updates')}

  • {t('Updates are checked automatically from time to time')}
  • {t('You can also check manually from this page at any time')}
  • {t('It is recommended to back up the site before updating')}
  • {t('After updating, clear browser cache and refresh the admin page')}
) }