feat: add Claude Code changelog link to version notifiers (#820)
* feat: add Claude Code changelog link to version notifiers Add link to Claude Code Changelog in both: - Claude Code CLI status badge popover - App Update Notification dialog The link opens https://github.com/anthropics/claude-code/blob/main/CHANGELOG.md in external browser, allowing users to check what's new in Claude Code. Also converts AppUpdateNotification to use i18n translations. Fixes #817 * refactor: improve AppUpdateNotification code quality - Extract CLAUDE_CODE_CHANGELOG_URL to named constant - Remove unused "common" namespace from useTranslation hook --------- Co-authored-by: StillKnotKnown <stillknotknown@users.noreply.github.com>
This commit is contained in:
@@ -1,24 +1,20 @@
|
||||
import { useState, useEffect, useMemo } from 'react';
|
||||
import {
|
||||
Download,
|
||||
RefreshCw,
|
||||
CheckCircle2,
|
||||
AlertCircle
|
||||
} from 'lucide-react';
|
||||
import { Button } from './ui/button';
|
||||
import { Progress } from './ui/progress';
|
||||
import { useState, useEffect, useMemo } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Download, RefreshCw, CheckCircle2, AlertCircle, ExternalLink } from "lucide-react";
|
||||
import { Button } from "./ui/button";
|
||||
import { Progress } from "./ui/progress";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle
|
||||
} from './ui/dialog';
|
||||
import type {
|
||||
AppUpdateAvailableEvent,
|
||||
AppUpdateProgress
|
||||
} from '../../shared/types';
|
||||
DialogTitle,
|
||||
} from "./ui/dialog";
|
||||
import type { AppUpdateAvailableEvent, AppUpdateProgress } from "../../shared/types";
|
||||
|
||||
const CLAUDE_CODE_CHANGELOG_URL =
|
||||
"https://github.com/anthropics/claude-code/blob/main/CHANGELOG.md";
|
||||
|
||||
/**
|
||||
* Simple markdown renderer for release notes
|
||||
@@ -28,23 +24,32 @@ function ReleaseNotesRenderer({ markdown }: { markdown: string }) {
|
||||
const html = useMemo(() => {
|
||||
const result = markdown
|
||||
// Escape HTML
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
// Headers (### Header -> <h3>)
|
||||
.replace(/^### (.+)$/gm, '<h4 class="text-sm font-semibold text-foreground mt-3 mb-1.5 first:mt-0">$1</h4>')
|
||||
.replace(/^## (.+)$/gm, '<h3 class="text-sm font-semibold text-foreground mt-3 mb-1.5 first:mt-0">$1</h3>')
|
||||
.replace(
|
||||
/^### (.+)$/gm,
|
||||
'<h4 class="text-sm font-semibold text-foreground mt-3 mb-1.5 first:mt-0">$1</h4>'
|
||||
)
|
||||
.replace(
|
||||
/^## (.+)$/gm,
|
||||
'<h3 class="text-sm font-semibold text-foreground mt-3 mb-1.5 first:mt-0">$1</h3>'
|
||||
)
|
||||
// Bold (**text** -> <strong>)
|
||||
.replace(/\*\*([^*]+)\*\*/g, '<strong class="text-foreground font-medium">$1</strong>')
|
||||
// Inline code (`code` -> <code>)
|
||||
.replace(/`([^`]+)`/g, '<code class="px-1 py-0.5 bg-muted rounded text-xs">$1</code>')
|
||||
// List items (- item -> <li>)
|
||||
.replace(/^- (.+)$/gm, '<li class="ml-4 text-muted-foreground before:content-[\'•\'] before:mr-2 before:text-muted-foreground/60">$1</li>')
|
||||
.replace(
|
||||
/^- (.+)$/gm,
|
||||
"<li class=\"ml-4 text-muted-foreground before:content-['•'] before:mr-2 before:text-muted-foreground/60\">$1</li>"
|
||||
)
|
||||
// Wrap consecutive list items
|
||||
.replace(/(<li[^>]*>.*?<\/li>\n?)+/g, '<ul class="space-y-1 my-2">$&</ul>')
|
||||
// Line breaks for remaining lines
|
||||
.replace(/\n\n/g, '<div class="h-2"></div>')
|
||||
.replace(/\n/g, '<br/>');
|
||||
.replace(/\n/g, "<br/>");
|
||||
|
||||
return result;
|
||||
}, [markdown]);
|
||||
@@ -62,6 +67,7 @@ function ReleaseNotesRenderer({ markdown }: { markdown: string }) {
|
||||
* Shows when a new app version is available and handles download/install workflow
|
||||
*/
|
||||
export function AppUpdateNotification() {
|
||||
const { t } = useTranslation(["dialogs"]);
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [updateInfo, setUpdateInfo] = useState<AppUpdateAvailableEvent | null>(null);
|
||||
const [downloadProgress, setDownloadProgress] = useState<AppUpdateProgress | null>(null);
|
||||
@@ -109,12 +115,14 @@ export function AppUpdateNotification() {
|
||||
try {
|
||||
const result = await window.electronAPI.downloadAppUpdate();
|
||||
if (!result.success) {
|
||||
setDownloadError(result.error || 'Failed to download update');
|
||||
setDownloadError(
|
||||
result.error || t("dialogs:appUpdate.downloadError", "Failed to download update")
|
||||
);
|
||||
setIsDownloading(false);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to download app update:', error);
|
||||
setDownloadError('Failed to download update');
|
||||
console.error("Failed to download app update:", error);
|
||||
setDownloadError(t("dialogs:appUpdate.downloadError", "Failed to download update"));
|
||||
setIsDownloading(false);
|
||||
}
|
||||
};
|
||||
@@ -137,10 +145,13 @@ export function AppUpdateNotification() {
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<Download className="h-5 w-5" />
|
||||
App Update Available
|
||||
{t("dialogs:appUpdate.title", "App Update Available")}
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
A new version of Auto Claude is ready to download
|
||||
{t(
|
||||
"dialogs:appUpdate.description",
|
||||
"A new version of Auto Claude is ready to download"
|
||||
)}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
@@ -150,14 +161,13 @@ export function AppUpdateNotification() {
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-xs text-muted-foreground uppercase tracking-wider mb-1">
|
||||
New Version
|
||||
</p>
|
||||
<p className="text-base font-medium text-foreground">
|
||||
{updateInfo.version}
|
||||
{t("dialogs:appUpdate.newVersion", "New Version")}
|
||||
</p>
|
||||
<p className="text-base font-medium text-foreground">{updateInfo.version}</p>
|
||||
{updateInfo.releaseDate && (
|
||||
<p className="text-xs text-muted-foreground mt-1">
|
||||
Released {new Date(updateInfo.releaseDate).toLocaleDateString()}
|
||||
{t("dialogs:appUpdate.released", "Released")}{" "}
|
||||
{new Date(updateInfo.releaseDate).toLocaleDateString()}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
@@ -178,18 +188,36 @@ export function AppUpdateNotification() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Claude Code Changelog Link */}
|
||||
<Button
|
||||
variant="link"
|
||||
size="sm"
|
||||
className="w-full text-xs text-muted-foreground gap-1"
|
||||
onClick={() => window.electronAPI?.openExternal?.(CLAUDE_CODE_CHANGELOG_URL)}
|
||||
aria-label={t(
|
||||
"dialogs:appUpdate.claudeCodeChangelogAriaLabel",
|
||||
"View Claude Code Changelog (opens in new window)"
|
||||
)}
|
||||
>
|
||||
{t("dialogs:appUpdate.claudeCodeChangelog", "View Claude Code Changelog")}
|
||||
<ExternalLink className="h-3 w-3" aria-hidden="true" />
|
||||
</Button>
|
||||
|
||||
{/* Download Progress */}
|
||||
{isDownloading && downloadProgress && (
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
<span className="text-muted-foreground">Downloading...</span>
|
||||
<span className="text-muted-foreground">
|
||||
{t("dialogs:appUpdate.downloading", "Downloading...")}
|
||||
</span>
|
||||
<span className="text-foreground font-medium">
|
||||
{Math.round(downloadProgress.percent)}%
|
||||
</span>
|
||||
</div>
|
||||
<Progress value={downloadProgress.percent} className="h-2" />
|
||||
<p className="text-xs text-muted-foreground text-right">
|
||||
{(downloadProgress.transferred / 1024 / 1024).toFixed(2)} MB / {(downloadProgress.total / 1024 / 1024).toFixed(2)} MB
|
||||
{(downloadProgress.transferred / 1024 / 1024).toFixed(2)} MB /{" "}
|
||||
{(downloadProgress.total / 1024 / 1024).toFixed(2)} MB
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
@@ -206,39 +234,39 @@ export function AppUpdateNotification() {
|
||||
{isDownloaded && (
|
||||
<div className="flex items-center gap-3 text-sm text-success bg-success/10 border border-success/30 rounded-lg p-3">
|
||||
<CheckCircle2 className="h-5 w-5 shrink-0" />
|
||||
<span>Update downloaded successfully! Click Install to restart and apply the update.</span>
|
||||
<span>
|
||||
{t(
|
||||
"dialogs:appUpdate.updateDownloaded",
|
||||
"Update downloaded successfully! Click Install to restart and apply the update."
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<DialogFooter className="flex flex-col sm:flex-row gap-3">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={handleDismiss}
|
||||
disabled={isDownloading}
|
||||
>
|
||||
{isDownloaded ? 'Install Later' : 'Remind Me Later'}
|
||||
<Button variant="outline" onClick={handleDismiss} disabled={isDownloading}>
|
||||
{isDownloaded
|
||||
? t("dialogs:appUpdate.installLater", "Install Later")
|
||||
: t("dialogs:appUpdate.remindMeLater", "Remind Me Later")}
|
||||
</Button>
|
||||
|
||||
{isDownloaded ? (
|
||||
<Button onClick={handleInstall}>
|
||||
<RefreshCw className="mr-2 h-4 w-4" />
|
||||
Install and Restart
|
||||
{t("dialogs:appUpdate.installAndRestart", "Install and Restart")}
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
onClick={handleDownload}
|
||||
disabled={isDownloading}
|
||||
>
|
||||
<Button onClick={handleDownload} disabled={isDownloading}>
|
||||
{isDownloading ? (
|
||||
<>
|
||||
<RefreshCw className="mr-2 h-4 w-4 animate-spin" />
|
||||
Downloading...
|
||||
{t("dialogs:appUpdate.downloading", "Downloading...")}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Download className="mr-2 h-4 w-4" />
|
||||
Download Update
|
||||
{t("dialogs:appUpdate.downloadUpdate", "Download Update")}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Terminal, Check, AlertTriangle, X, Loader2, Download, RefreshCw, ExternalLink } from 'lucide-react';
|
||||
import { Button } from './ui/button';
|
||||
import { useState, useEffect, useCallback } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger
|
||||
} from './ui/popover';
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger
|
||||
} from './ui/tooltip';
|
||||
Terminal,
|
||||
Check,
|
||||
AlertTriangle,
|
||||
X,
|
||||
Loader2,
|
||||
Download,
|
||||
RefreshCw,
|
||||
ExternalLink,
|
||||
} from "lucide-react";
|
||||
import { Button } from "./ui/button";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover";
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip";
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
@@ -20,16 +21,16 @@ import {
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle
|
||||
} from './ui/alert-dialog';
|
||||
import { cn } from '../lib/utils';
|
||||
import type { ClaudeCodeVersionInfo } from '../../shared/types/cli';
|
||||
AlertDialogTitle,
|
||||
} from "./ui/alert-dialog";
|
||||
import { cn } from "../lib/utils";
|
||||
import type { ClaudeCodeVersionInfo } from "../../shared/types/cli";
|
||||
|
||||
interface ClaudeCodeStatusBadgeProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
type StatusType = 'loading' | 'installed' | 'outdated' | 'not-found' | 'error';
|
||||
type StatusType = "loading" | "installed" | "outdated" | "not-found" | "error";
|
||||
|
||||
// Check every 24 hours
|
||||
const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
||||
@@ -39,8 +40,8 @@ const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
||||
* Shows installation status and provides quick access to install/update.
|
||||
*/
|
||||
export function ClaudeCodeStatusBadge({ className }: ClaudeCodeStatusBadgeProps) {
|
||||
const { t } = useTranslation(['common', 'navigation']);
|
||||
const [status, setStatus] = useState<StatusType>('loading');
|
||||
const { t } = useTranslation(["common", "navigation"]);
|
||||
const [status, setStatus] = useState<StatusType>("loading");
|
||||
const [versionInfo, setVersionInfo] = useState<ClaudeCodeVersionInfo | null>(null);
|
||||
const [isInstalling, setIsInstalling] = useState(false);
|
||||
const [lastChecked, setLastChecked] = useState<Date | null>(null);
|
||||
@@ -51,7 +52,7 @@ export function ClaudeCodeStatusBadge({ className }: ClaudeCodeStatusBadgeProps)
|
||||
const checkVersion = useCallback(async () => {
|
||||
try {
|
||||
if (!window.electronAPI?.checkClaudeCodeVersion) {
|
||||
setStatus('error');
|
||||
setStatus("error");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -62,18 +63,18 @@ export function ClaudeCodeStatusBadge({ className }: ClaudeCodeStatusBadgeProps)
|
||||
setLastChecked(new Date());
|
||||
|
||||
if (!result.data.installed) {
|
||||
setStatus('not-found');
|
||||
setStatus("not-found");
|
||||
} else if (result.data.isOutdated) {
|
||||
setStatus('outdated');
|
||||
setStatus("outdated");
|
||||
} else {
|
||||
setStatus('installed');
|
||||
setStatus("installed");
|
||||
}
|
||||
} else {
|
||||
setStatus('error');
|
||||
setStatus("error");
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to check Claude Code version:', err);
|
||||
setStatus('error');
|
||||
console.error("Failed to check Claude Code version:", err);
|
||||
setStatus("error");
|
||||
}
|
||||
}, []);
|
||||
|
||||
@@ -107,7 +108,7 @@ export function ClaudeCodeStatusBadge({ className }: ClaudeCodeStatusBadgeProps)
|
||||
}, 5000);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to install Claude Code:', err);
|
||||
console.error("Failed to install Claude Code:", err);
|
||||
} finally {
|
||||
setIsInstalling(false);
|
||||
}
|
||||
@@ -115,7 +116,7 @@ export function ClaudeCodeStatusBadge({ className }: ClaudeCodeStatusBadgeProps)
|
||||
|
||||
// Handle install/update button click
|
||||
const handleInstall = () => {
|
||||
if (status === 'outdated') {
|
||||
if (status === "outdated") {
|
||||
// Show warning for updates since it will close running Claude sessions
|
||||
setShowUpdateWarning(true);
|
||||
} else {
|
||||
@@ -127,30 +128,30 @@ export function ClaudeCodeStatusBadge({ className }: ClaudeCodeStatusBadgeProps)
|
||||
// Get status indicator color
|
||||
const getStatusColor = () => {
|
||||
switch (status) {
|
||||
case 'installed':
|
||||
return 'bg-green-500';
|
||||
case 'outdated':
|
||||
return 'bg-yellow-500';
|
||||
case 'not-found':
|
||||
case 'error':
|
||||
return 'bg-destructive';
|
||||
case "installed":
|
||||
return "bg-green-500";
|
||||
case "outdated":
|
||||
return "bg-yellow-500";
|
||||
case "not-found":
|
||||
case "error":
|
||||
return "bg-destructive";
|
||||
default:
|
||||
return 'bg-muted-foreground';
|
||||
return "bg-muted-foreground";
|
||||
}
|
||||
};
|
||||
|
||||
// Get status icon
|
||||
const getStatusIcon = () => {
|
||||
switch (status) {
|
||||
case 'loading':
|
||||
case "loading":
|
||||
return <Loader2 className="h-3 w-3 animate-spin" />;
|
||||
case 'installed':
|
||||
case "installed":
|
||||
return <Check className="h-3 w-3" />;
|
||||
case 'outdated':
|
||||
case "outdated":
|
||||
return <AlertTriangle className="h-3 w-3" />;
|
||||
case 'not-found':
|
||||
case "not-found":
|
||||
return <X className="h-3 w-3" />;
|
||||
case 'error':
|
||||
case "error":
|
||||
return <AlertTriangle className="h-3 w-3" />;
|
||||
}
|
||||
};
|
||||
@@ -158,16 +159,16 @@ export function ClaudeCodeStatusBadge({ className }: ClaudeCodeStatusBadgeProps)
|
||||
// Get tooltip text
|
||||
const getTooltipText = () => {
|
||||
switch (status) {
|
||||
case 'loading':
|
||||
return t('navigation:claudeCode.checking', 'Checking Claude Code...');
|
||||
case 'installed':
|
||||
return t('navigation:claudeCode.upToDate', 'Claude Code is up to date');
|
||||
case 'outdated':
|
||||
return t('navigation:claudeCode.updateAvailable', 'Claude Code update available');
|
||||
case 'not-found':
|
||||
return t('navigation:claudeCode.notInstalled', 'Claude Code not installed');
|
||||
case 'error':
|
||||
return t('navigation:claudeCode.error', 'Error checking Claude Code');
|
||||
case "loading":
|
||||
return t("navigation:claudeCode.checking", "Checking Claude Code...");
|
||||
case "installed":
|
||||
return t("navigation:claudeCode.upToDate", "Claude Code is up to date");
|
||||
case "outdated":
|
||||
return t("navigation:claudeCode.updateAvailable", "Claude Code update available");
|
||||
case "not-found":
|
||||
return t("navigation:claudeCode.notInstalled", "Claude Code not installed");
|
||||
case "error":
|
||||
return t("navigation:claudeCode.error", "Error checking Claude Code");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -180,36 +181,36 @@ export function ClaudeCodeStatusBadge({ className }: ClaudeCodeStatusBadgeProps)
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className={cn(
|
||||
'w-full justify-start gap-2 text-xs',
|
||||
status === 'not-found' || status === 'error' ? 'text-destructive' : '',
|
||||
status === 'outdated' ? 'text-yellow-600 dark:text-yellow-500' : '',
|
||||
"w-full justify-start gap-2 text-xs",
|
||||
status === "not-found" || status === "error" ? "text-destructive" : "",
|
||||
status === "outdated" ? "text-yellow-600 dark:text-yellow-500" : "",
|
||||
className
|
||||
)}
|
||||
>
|
||||
<div className="relative">
|
||||
<Terminal className="h-4 w-4" />
|
||||
<span className={cn(
|
||||
'absolute -bottom-0.5 -right-0.5 h-2 w-2 rounded-full',
|
||||
getStatusColor()
|
||||
)} />
|
||||
<span
|
||||
className={cn(
|
||||
"absolute -bottom-0.5 -right-0.5 h-2 w-2 rounded-full",
|
||||
getStatusColor()
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<span className="truncate">Claude Code</span>
|
||||
{status === 'outdated' && (
|
||||
{status === "outdated" && (
|
||||
<span className="ml-auto text-[10px] bg-yellow-500/20 text-yellow-600 dark:text-yellow-400 px-1.5 py-0.5 rounded">
|
||||
{t('common:update', 'Update')}
|
||||
{t("common:update", "Update")}
|
||||
</span>
|
||||
)}
|
||||
{status === 'not-found' && (
|
||||
{status === "not-found" && (
|
||||
<span className="ml-auto text-[10px] bg-destructive/20 text-destructive px-1.5 py-0.5 rounded">
|
||||
{t('common:install', 'Install')}
|
||||
{t("common:install", "Install")}
|
||||
</span>
|
||||
)}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right">
|
||||
{getTooltipText()}
|
||||
</TooltipContent>
|
||||
<TooltipContent side="right">{getTooltipText()}</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
<PopoverContent side="right" align="end" className="w-72">
|
||||
@@ -223,33 +224,37 @@ export function ClaudeCodeStatusBadge({ className }: ClaudeCodeStatusBadgeProps)
|
||||
<h4 className="text-sm font-medium">Claude Code CLI</h4>
|
||||
<p className="text-xs text-muted-foreground flex items-center gap-1">
|
||||
{getStatusIcon()}
|
||||
{status === 'installed' && t('navigation:claudeCode.installed', 'Installed')}
|
||||
{status === 'outdated' && t('navigation:claudeCode.outdated', 'Update available')}
|
||||
{status === 'not-found' && t('navigation:claudeCode.missing', 'Not installed')}
|
||||
{status === 'loading' && t('navigation:claudeCode.checking', 'Checking...')}
|
||||
{status === 'error' && t('navigation:claudeCode.error', 'Error')}
|
||||
{status === "installed" && t("navigation:claudeCode.installed", "Installed")}
|
||||
{status === "outdated" && t("navigation:claudeCode.outdated", "Update available")}
|
||||
{status === "not-found" && t("navigation:claudeCode.missing", "Not installed")}
|
||||
{status === "loading" && t("navigation:claudeCode.checking", "Checking...")}
|
||||
{status === "error" && t("navigation:claudeCode.error", "Error")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Version info */}
|
||||
{versionInfo && status !== 'loading' && (
|
||||
{versionInfo && status !== "loading" && (
|
||||
<div className="text-xs space-y-1 p-2 bg-muted rounded-md">
|
||||
{versionInfo.installed && (
|
||||
<div className="flex justify-between">
|
||||
<span className="text-muted-foreground">{t('navigation:claudeCode.current', 'Current')}:</span>
|
||||
<span className="text-muted-foreground">
|
||||
{t("navigation:claudeCode.current", "Current")}:
|
||||
</span>
|
||||
<span className="font-mono">{versionInfo.installed}</span>
|
||||
</div>
|
||||
)}
|
||||
{versionInfo.latest && versionInfo.latest !== 'unknown' && (
|
||||
{versionInfo.latest && versionInfo.latest !== "unknown" && (
|
||||
<div className="flex justify-between">
|
||||
<span className="text-muted-foreground">{t('navigation:claudeCode.latest', 'Latest')}:</span>
|
||||
<span className="text-muted-foreground">
|
||||
{t("navigation:claudeCode.latest", "Latest")}:
|
||||
</span>
|
||||
<span className="font-mono">{versionInfo.latest}</span>
|
||||
</div>
|
||||
)}
|
||||
{lastChecked && (
|
||||
<div className="flex justify-between text-muted-foreground">
|
||||
<span>{t('navigation:claudeCode.lastChecked', 'Last checked')}:</span>
|
||||
<span>{t("navigation:claudeCode.lastChecked", "Last checked")}:</span>
|
||||
<span>{lastChecked.toLocaleTimeString()}</span>
|
||||
</div>
|
||||
)}
|
||||
@@ -258,7 +263,7 @@ export function ClaudeCodeStatusBadge({ className }: ClaudeCodeStatusBadgeProps)
|
||||
|
||||
{/* Actions */}
|
||||
<div className="flex gap-2">
|
||||
{(status === 'not-found' || status === 'outdated') && (
|
||||
{(status === "not-found" || status === "outdated") && (
|
||||
<Button
|
||||
size="sm"
|
||||
className="flex-1 gap-1"
|
||||
@@ -270,10 +275,9 @@ export function ClaudeCodeStatusBadge({ className }: ClaudeCodeStatusBadgeProps)
|
||||
) : (
|
||||
<Download className="h-3 w-3" />
|
||||
)}
|
||||
{status === 'outdated'
|
||||
? t('common:update', 'Update')
|
||||
: t('common:install', 'Install')
|
||||
}
|
||||
{status === "outdated"
|
||||
? t("common:update", "Update")
|
||||
: t("common:install", "Install")}
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
@@ -281,10 +285,10 @@ export function ClaudeCodeStatusBadge({ className }: ClaudeCodeStatusBadgeProps)
|
||||
size="sm"
|
||||
className="gap-1"
|
||||
onClick={() => checkVersion()}
|
||||
disabled={status === 'loading'}
|
||||
disabled={status === "loading"}
|
||||
>
|
||||
<RefreshCw className={cn('h-3 w-3', status === 'loading' && 'animate-spin')} />
|
||||
{t('common:refresh', 'Refresh')}
|
||||
<RefreshCw className={cn("h-3 w-3", status === "loading" && "animate-spin")} />
|
||||
{t("common:refresh", "Refresh")}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -293,10 +297,32 @@ export function ClaudeCodeStatusBadge({ className }: ClaudeCodeStatusBadgeProps)
|
||||
variant="link"
|
||||
size="sm"
|
||||
className="w-full text-xs text-muted-foreground gap-1"
|
||||
onClick={() => window.electronAPI?.openExternal?.('https://claude.ai/code')}
|
||||
aria-label={t('navigation:claudeCode.learnMoreAriaLabel', 'Learn more about Claude Code (opens in new window)')}
|
||||
onClick={() => window.electronAPI?.openExternal?.("https://claude.ai/code")}
|
||||
aria-label={t(
|
||||
"navigation:claudeCode.learnMoreAriaLabel",
|
||||
"Learn more about Claude Code (opens in new window)"
|
||||
)}
|
||||
>
|
||||
{t('navigation:claudeCode.learnMore', 'Learn more about Claude Code')}
|
||||
{t("navigation:claudeCode.learnMore", "Learn more about Claude Code")}
|
||||
<ExternalLink className="h-3 w-3" aria-hidden="true" />
|
||||
</Button>
|
||||
|
||||
{/* Changelog link */}
|
||||
<Button
|
||||
variant="link"
|
||||
size="sm"
|
||||
className="w-full text-xs text-muted-foreground gap-1"
|
||||
onClick={() =>
|
||||
window.electronAPI?.openExternal?.(
|
||||
"https://github.com/anthropics/claude-code/blob/main/CHANGELOG.md"
|
||||
)
|
||||
}
|
||||
aria-label={t(
|
||||
"navigation:claudeCode.viewChangelogAriaLabel",
|
||||
"View Claude Code Changelog (opens in new window)"
|
||||
)}
|
||||
>
|
||||
{t("navigation:claudeCode.viewChangelog", "View Claude Code Changelog")}
|
||||
<ExternalLink className="h-3 w-3" aria-hidden="true" />
|
||||
</Button>
|
||||
</div>
|
||||
@@ -307,18 +333,19 @@ export function ClaudeCodeStatusBadge({ className }: ClaudeCodeStatusBadgeProps)
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>
|
||||
{t('navigation:claudeCode.updateWarningTitle', 'Update Claude Code?')}
|
||||
{t("navigation:claudeCode.updateWarningTitle", "Update Claude Code?")}
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
{t('navigation:claudeCode.updateWarningDescription', 'Updating will close all running Claude Code sessions. Any unsaved work in those sessions may be lost. Make sure to save your work before proceeding.')}
|
||||
{t(
|
||||
"navigation:claudeCode.updateWarningDescription",
|
||||
"Updating will close all running Claude Code sessions. Any unsaved work in those sessions may be lost. Make sure to save your work before proceeding."
|
||||
)}
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>
|
||||
{t('common:cancel', 'Cancel')}
|
||||
</AlertDialogCancel>
|
||||
<AlertDialogCancel>{t("common:cancel", "Cancel")}</AlertDialogCancel>
|
||||
<AlertDialogAction onClick={performInstall}>
|
||||
{t('navigation:claudeCode.updateAnyway', 'Update Anyway')}
|
||||
{t("navigation:claudeCode.updateAnyway", "Update Anyway")}
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
|
||||
@@ -133,5 +133,20 @@
|
||||
"cancel": "Cancel",
|
||||
"remove": "Remove",
|
||||
"error": "Failed to remove project"
|
||||
},
|
||||
"appUpdate": {
|
||||
"title": "App Update Available",
|
||||
"description": "A new version of Auto Claude is ready to download",
|
||||
"newVersion": "New Version",
|
||||
"released": "Released",
|
||||
"downloading": "Downloading...",
|
||||
"downloadUpdate": "Download Update",
|
||||
"installAndRestart": "Install and Restart",
|
||||
"installLater": "Install Later",
|
||||
"remindMeLater": "Remind Me Later",
|
||||
"updateDownloaded": "Update downloaded successfully! Click Install to restart and apply the update.",
|
||||
"downloadError": "Failed to download update",
|
||||
"claudeCodeChangelog": "View Claude Code Changelog",
|
||||
"claudeCodeChangelogAriaLabel": "View Claude Code Changelog (opens in new window)"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,6 +44,8 @@
|
||||
"lastChecked": "Last checked",
|
||||
"learnMore": "Learn more about Claude Code",
|
||||
"learnMoreAriaLabel": "Learn more about Claude Code (opens in new window)",
|
||||
"viewChangelog": "View Claude Code Changelog",
|
||||
"viewChangelogAriaLabel": "View Claude Code Changelog (opens in new window)",
|
||||
"updateWarningTitle": "Update Claude Code?",
|
||||
"updateWarningDescription": "Updating will close all running Claude Code sessions. Any unsaved work in those sessions may be lost. Make sure to save your work before proceeding.",
|
||||
"updateAnyway": "Update Anyway"
|
||||
|
||||
@@ -133,5 +133,20 @@
|
||||
"cancel": "Annuler",
|
||||
"remove": "Retirer",
|
||||
"error": "Échec de la suppression du projet"
|
||||
},
|
||||
"appUpdate": {
|
||||
"title": "Mise à jour de l'application disponible",
|
||||
"description": "Une nouvelle version d'Auto Claude est prête à être téléchargée",
|
||||
"newVersion": "Nouvelle version",
|
||||
"released": "Publiée",
|
||||
"downloading": "Téléchargement...",
|
||||
"downloadUpdate": "Télécharger la mise à jour",
|
||||
"installAndRestart": "Installer et redémarrer",
|
||||
"installLater": "Installer plus tard",
|
||||
"remindMeLater": "Me rappeler plus tard",
|
||||
"updateDownloaded": "Mise à jour téléchargée avec succès ! Cliquez sur Installer pour redémarrer et appliquer la mise à jour.",
|
||||
"downloadError": "Échec du téléchargement de la mise à jour",
|
||||
"claudeCodeChangelog": "Voir le journal des modifications Claude Code",
|
||||
"claudeCodeChangelogAriaLabel": "Voir le journal des modifications Claude Code (s'ouvre dans une nouvelle fenêtre)"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,6 +44,8 @@
|
||||
"lastChecked": "Dernière vérification",
|
||||
"learnMore": "En savoir plus sur Claude Code",
|
||||
"learnMoreAriaLabel": "En savoir plus sur Claude Code (s'ouvre dans une nouvelle fenêtre)",
|
||||
"viewChangelog": "Voir le journal des modifications Claude Code",
|
||||
"viewChangelogAriaLabel": "Voir le journal des modifications Claude Code (s'ouvre dans une nouvelle fenêtre)",
|
||||
"updateWarningTitle": "Mettre à jour Claude Code ?",
|
||||
"updateWarningDescription": "La mise à jour fermera toutes les sessions Claude Code en cours. Tout travail non sauvegardé dans ces sessions pourrait être perdu. Assurez-vous de sauvegarder votre travail avant de continuer.",
|
||||
"updateAnyway": "Mettre à jour quand même"
|
||||
|
||||
Reference in New Issue
Block a user