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 { useState, useEffect, useMemo } from "react";
|
||||||
import {
|
import { useTranslation } from "react-i18next";
|
||||||
Download,
|
import { Download, RefreshCw, CheckCircle2, AlertCircle, ExternalLink } from "lucide-react";
|
||||||
RefreshCw,
|
import { Button } from "./ui/button";
|
||||||
CheckCircle2,
|
import { Progress } from "./ui/progress";
|
||||||
AlertCircle
|
|
||||||
} from 'lucide-react';
|
|
||||||
import { Button } from './ui/button';
|
|
||||||
import { Progress } from './ui/progress';
|
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogDescription,
|
DialogDescription,
|
||||||
DialogFooter,
|
DialogFooter,
|
||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle
|
DialogTitle,
|
||||||
} from './ui/dialog';
|
} from "./ui/dialog";
|
||||||
import type {
|
import type { AppUpdateAvailableEvent, AppUpdateProgress } from "../../shared/types";
|
||||||
AppUpdateAvailableEvent,
|
|
||||||
AppUpdateProgress
|
const CLAUDE_CODE_CHANGELOG_URL =
|
||||||
} from '../../shared/types';
|
"https://github.com/anthropics/claude-code/blob/main/CHANGELOG.md";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Simple markdown renderer for release notes
|
* Simple markdown renderer for release notes
|
||||||
@@ -28,23 +24,32 @@ function ReleaseNotesRenderer({ markdown }: { markdown: string }) {
|
|||||||
const html = useMemo(() => {
|
const html = useMemo(() => {
|
||||||
const result = markdown
|
const result = markdown
|
||||||
// Escape HTML
|
// Escape HTML
|
||||||
.replace(/&/g, '&')
|
.replace(/&/g, "&")
|
||||||
.replace(/</g, '<')
|
.replace(/</g, "<")
|
||||||
.replace(/>/g, '>')
|
.replace(/>/g, ">")
|
||||||
// Headers (### Header -> <h3>)
|
// Headers (### Header -> <h3>)
|
||||||
.replace(/^### (.+)$/gm, '<h4 class="text-sm font-semibold text-foreground mt-3 mb-1.5 first:mt-0">$1</h4>')
|
.replace(
|
||||||
.replace(/^## (.+)$/gm, '<h3 class="text-sm font-semibold text-foreground mt-3 mb-1.5 first:mt-0">$1</h3>')
|
/^### (.+)$/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>)
|
// Bold (**text** -> <strong>)
|
||||||
.replace(/\*\*([^*]+)\*\*/g, '<strong class="text-foreground font-medium">$1</strong>')
|
.replace(/\*\*([^*]+)\*\*/g, '<strong class="text-foreground font-medium">$1</strong>')
|
||||||
// Inline code (`code` -> <code>)
|
// Inline code (`code` -> <code>)
|
||||||
.replace(/`([^`]+)`/g, '<code class="px-1 py-0.5 bg-muted rounded text-xs">$1</code>')
|
.replace(/`([^`]+)`/g, '<code class="px-1 py-0.5 bg-muted rounded text-xs">$1</code>')
|
||||||
// List items (- item -> <li>)
|
// 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
|
// Wrap consecutive list items
|
||||||
.replace(/(<li[^>]*>.*?<\/li>\n?)+/g, '<ul class="space-y-1 my-2">$&</ul>')
|
.replace(/(<li[^>]*>.*?<\/li>\n?)+/g, '<ul class="space-y-1 my-2">$&</ul>')
|
||||||
// Line breaks for remaining lines
|
// Line breaks for remaining lines
|
||||||
.replace(/\n\n/g, '<div class="h-2"></div>')
|
.replace(/\n\n/g, '<div class="h-2"></div>')
|
||||||
.replace(/\n/g, '<br/>');
|
.replace(/\n/g, "<br/>");
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}, [markdown]);
|
}, [markdown]);
|
||||||
@@ -62,6 +67,7 @@ function ReleaseNotesRenderer({ markdown }: { markdown: string }) {
|
|||||||
* Shows when a new app version is available and handles download/install workflow
|
* Shows when a new app version is available and handles download/install workflow
|
||||||
*/
|
*/
|
||||||
export function AppUpdateNotification() {
|
export function AppUpdateNotification() {
|
||||||
|
const { t } = useTranslation(["dialogs"]);
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const [updateInfo, setUpdateInfo] = useState<AppUpdateAvailableEvent | null>(null);
|
const [updateInfo, setUpdateInfo] = useState<AppUpdateAvailableEvent | null>(null);
|
||||||
const [downloadProgress, setDownloadProgress] = useState<AppUpdateProgress | null>(null);
|
const [downloadProgress, setDownloadProgress] = useState<AppUpdateProgress | null>(null);
|
||||||
@@ -109,12 +115,14 @@ export function AppUpdateNotification() {
|
|||||||
try {
|
try {
|
||||||
const result = await window.electronAPI.downloadAppUpdate();
|
const result = await window.electronAPI.downloadAppUpdate();
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
setDownloadError(result.error || 'Failed to download update');
|
setDownloadError(
|
||||||
|
result.error || t("dialogs:appUpdate.downloadError", "Failed to download update")
|
||||||
|
);
|
||||||
setIsDownloading(false);
|
setIsDownloading(false);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to download app update:', error);
|
console.error("Failed to download app update:", error);
|
||||||
setDownloadError('Failed to download update');
|
setDownloadError(t("dialogs:appUpdate.downloadError", "Failed to download update"));
|
||||||
setIsDownloading(false);
|
setIsDownloading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -137,10 +145,13 @@ export function AppUpdateNotification() {
|
|||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle className="flex items-center gap-2">
|
<DialogTitle className="flex items-center gap-2">
|
||||||
<Download className="h-5 w-5" />
|
<Download className="h-5 w-5" />
|
||||||
App Update Available
|
{t("dialogs:appUpdate.title", "App Update Available")}
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogDescription>
|
<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>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
@@ -150,14 +161,13 @@ export function AppUpdateNotification() {
|
|||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-xs text-muted-foreground uppercase tracking-wider mb-1">
|
<p className="text-xs text-muted-foreground uppercase tracking-wider mb-1">
|
||||||
New Version
|
{t("dialogs:appUpdate.newVersion", "New Version")}
|
||||||
</p>
|
|
||||||
<p className="text-base font-medium text-foreground">
|
|
||||||
{updateInfo.version}
|
|
||||||
</p>
|
</p>
|
||||||
|
<p className="text-base font-medium text-foreground">{updateInfo.version}</p>
|
||||||
{updateInfo.releaseDate && (
|
{updateInfo.releaseDate && (
|
||||||
<p className="text-xs text-muted-foreground mt-1">
|
<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>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -178,18 +188,36 @@ export function AppUpdateNotification() {
|
|||||||
</div>
|
</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 */}
|
{/* Download Progress */}
|
||||||
{isDownloading && downloadProgress && (
|
{isDownloading && downloadProgress && (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="flex items-center justify-between text-sm">
|
<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">
|
<span className="text-foreground font-medium">
|
||||||
{Math.round(downloadProgress.percent)}%
|
{Math.round(downloadProgress.percent)}%
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<Progress value={downloadProgress.percent} className="h-2" />
|
<Progress value={downloadProgress.percent} className="h-2" />
|
||||||
<p className="text-xs text-muted-foreground text-right">
|
<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>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -206,39 +234,39 @@ export function AppUpdateNotification() {
|
|||||||
{isDownloaded && (
|
{isDownloaded && (
|
||||||
<div className="flex items-center gap-3 text-sm text-success bg-success/10 border border-success/30 rounded-lg p-3">
|
<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" />
|
<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>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DialogFooter className="flex flex-col sm:flex-row gap-3">
|
<DialogFooter className="flex flex-col sm:flex-row gap-3">
|
||||||
<Button
|
<Button variant="outline" onClick={handleDismiss} disabled={isDownloading}>
|
||||||
variant="outline"
|
{isDownloaded
|
||||||
onClick={handleDismiss}
|
? t("dialogs:appUpdate.installLater", "Install Later")
|
||||||
disabled={isDownloading}
|
: t("dialogs:appUpdate.remindMeLater", "Remind Me Later")}
|
||||||
>
|
|
||||||
{isDownloaded ? 'Install Later' : 'Remind Me Later'}
|
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
{isDownloaded ? (
|
{isDownloaded ? (
|
||||||
<Button onClick={handleInstall}>
|
<Button onClick={handleInstall}>
|
||||||
<RefreshCw className="mr-2 h-4 w-4" />
|
<RefreshCw className="mr-2 h-4 w-4" />
|
||||||
Install and Restart
|
{t("dialogs:appUpdate.installAndRestart", "Install and Restart")}
|
||||||
</Button>
|
</Button>
|
||||||
) : (
|
) : (
|
||||||
<Button
|
<Button onClick={handleDownload} disabled={isDownloading}>
|
||||||
onClick={handleDownload}
|
|
||||||
disabled={isDownloading}
|
|
||||||
>
|
|
||||||
{isDownloading ? (
|
{isDownloading ? (
|
||||||
<>
|
<>
|
||||||
<RefreshCw className="mr-2 h-4 w-4 animate-spin" />
|
<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 className="mr-2 h-4 w-4" />
|
||||||
Download Update
|
{t("dialogs:appUpdate.downloadUpdate", "Download Update")}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -1,17 +1,18 @@
|
|||||||
import { useState, useEffect, useCallback } from 'react';
|
import { useState, useEffect, useCallback } from "react";
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from "react-i18next";
|
||||||
import { Terminal, Check, AlertTriangle, X, Loader2, Download, RefreshCw, ExternalLink } from 'lucide-react';
|
|
||||||
import { Button } from './ui/button';
|
|
||||||
import {
|
import {
|
||||||
Popover,
|
Terminal,
|
||||||
PopoverContent,
|
Check,
|
||||||
PopoverTrigger
|
AlertTriangle,
|
||||||
} from './ui/popover';
|
X,
|
||||||
import {
|
Loader2,
|
||||||
Tooltip,
|
Download,
|
||||||
TooltipContent,
|
RefreshCw,
|
||||||
TooltipTrigger
|
ExternalLink,
|
||||||
} from './ui/tooltip';
|
} from "lucide-react";
|
||||||
|
import { Button } from "./ui/button";
|
||||||
|
import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover";
|
||||||
|
import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip";
|
||||||
import {
|
import {
|
||||||
AlertDialog,
|
AlertDialog,
|
||||||
AlertDialogAction,
|
AlertDialogAction,
|
||||||
@@ -20,16 +21,16 @@ import {
|
|||||||
AlertDialogDescription,
|
AlertDialogDescription,
|
||||||
AlertDialogFooter,
|
AlertDialogFooter,
|
||||||
AlertDialogHeader,
|
AlertDialogHeader,
|
||||||
AlertDialogTitle
|
AlertDialogTitle,
|
||||||
} from './ui/alert-dialog';
|
} from "./ui/alert-dialog";
|
||||||
import { cn } from '../lib/utils';
|
import { cn } from "../lib/utils";
|
||||||
import type { ClaudeCodeVersionInfo } from '../../shared/types/cli';
|
import type { ClaudeCodeVersionInfo } from "../../shared/types/cli";
|
||||||
|
|
||||||
interface ClaudeCodeStatusBadgeProps {
|
interface ClaudeCodeStatusBadgeProps {
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
type StatusType = 'loading' | 'installed' | 'outdated' | 'not-found' | 'error';
|
type StatusType = "loading" | "installed" | "outdated" | "not-found" | "error";
|
||||||
|
|
||||||
// Check every 24 hours
|
// Check every 24 hours
|
||||||
const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
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.
|
* Shows installation status and provides quick access to install/update.
|
||||||
*/
|
*/
|
||||||
export function ClaudeCodeStatusBadge({ className }: ClaudeCodeStatusBadgeProps) {
|
export function ClaudeCodeStatusBadge({ className }: ClaudeCodeStatusBadgeProps) {
|
||||||
const { t } = useTranslation(['common', 'navigation']);
|
const { t } = useTranslation(["common", "navigation"]);
|
||||||
const [status, setStatus] = useState<StatusType>('loading');
|
const [status, setStatus] = useState<StatusType>("loading");
|
||||||
const [versionInfo, setVersionInfo] = useState<ClaudeCodeVersionInfo | null>(null);
|
const [versionInfo, setVersionInfo] = useState<ClaudeCodeVersionInfo | null>(null);
|
||||||
const [isInstalling, setIsInstalling] = useState(false);
|
const [isInstalling, setIsInstalling] = useState(false);
|
||||||
const [lastChecked, setLastChecked] = useState<Date | null>(null);
|
const [lastChecked, setLastChecked] = useState<Date | null>(null);
|
||||||
@@ -51,7 +52,7 @@ export function ClaudeCodeStatusBadge({ className }: ClaudeCodeStatusBadgeProps)
|
|||||||
const checkVersion = useCallback(async () => {
|
const checkVersion = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
if (!window.electronAPI?.checkClaudeCodeVersion) {
|
if (!window.electronAPI?.checkClaudeCodeVersion) {
|
||||||
setStatus('error');
|
setStatus("error");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,18 +63,18 @@ export function ClaudeCodeStatusBadge({ className }: ClaudeCodeStatusBadgeProps)
|
|||||||
setLastChecked(new Date());
|
setLastChecked(new Date());
|
||||||
|
|
||||||
if (!result.data.installed) {
|
if (!result.data.installed) {
|
||||||
setStatus('not-found');
|
setStatus("not-found");
|
||||||
} else if (result.data.isOutdated) {
|
} else if (result.data.isOutdated) {
|
||||||
setStatus('outdated');
|
setStatus("outdated");
|
||||||
} else {
|
} else {
|
||||||
setStatus('installed');
|
setStatus("installed");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
setStatus('error');
|
setStatus("error");
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Failed to check Claude Code version:', err);
|
console.error("Failed to check Claude Code version:", err);
|
||||||
setStatus('error');
|
setStatus("error");
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@@ -107,7 +108,7 @@ export function ClaudeCodeStatusBadge({ className }: ClaudeCodeStatusBadgeProps)
|
|||||||
}, 5000);
|
}, 5000);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Failed to install Claude Code:', err);
|
console.error("Failed to install Claude Code:", err);
|
||||||
} finally {
|
} finally {
|
||||||
setIsInstalling(false);
|
setIsInstalling(false);
|
||||||
}
|
}
|
||||||
@@ -115,7 +116,7 @@ export function ClaudeCodeStatusBadge({ className }: ClaudeCodeStatusBadgeProps)
|
|||||||
|
|
||||||
// Handle install/update button click
|
// Handle install/update button click
|
||||||
const handleInstall = () => {
|
const handleInstall = () => {
|
||||||
if (status === 'outdated') {
|
if (status === "outdated") {
|
||||||
// Show warning for updates since it will close running Claude sessions
|
// Show warning for updates since it will close running Claude sessions
|
||||||
setShowUpdateWarning(true);
|
setShowUpdateWarning(true);
|
||||||
} else {
|
} else {
|
||||||
@@ -127,30 +128,30 @@ export function ClaudeCodeStatusBadge({ className }: ClaudeCodeStatusBadgeProps)
|
|||||||
// Get status indicator color
|
// Get status indicator color
|
||||||
const getStatusColor = () => {
|
const getStatusColor = () => {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case 'installed':
|
case "installed":
|
||||||
return 'bg-green-500';
|
return "bg-green-500";
|
||||||
case 'outdated':
|
case "outdated":
|
||||||
return 'bg-yellow-500';
|
return "bg-yellow-500";
|
||||||
case 'not-found':
|
case "not-found":
|
||||||
case 'error':
|
case "error":
|
||||||
return 'bg-destructive';
|
return "bg-destructive";
|
||||||
default:
|
default:
|
||||||
return 'bg-muted-foreground';
|
return "bg-muted-foreground";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Get status icon
|
// Get status icon
|
||||||
const getStatusIcon = () => {
|
const getStatusIcon = () => {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case 'loading':
|
case "loading":
|
||||||
return <Loader2 className="h-3 w-3 animate-spin" />;
|
return <Loader2 className="h-3 w-3 animate-spin" />;
|
||||||
case 'installed':
|
case "installed":
|
||||||
return <Check className="h-3 w-3" />;
|
return <Check className="h-3 w-3" />;
|
||||||
case 'outdated':
|
case "outdated":
|
||||||
return <AlertTriangle className="h-3 w-3" />;
|
return <AlertTriangle className="h-3 w-3" />;
|
||||||
case 'not-found':
|
case "not-found":
|
||||||
return <X className="h-3 w-3" />;
|
return <X className="h-3 w-3" />;
|
||||||
case 'error':
|
case "error":
|
||||||
return <AlertTriangle className="h-3 w-3" />;
|
return <AlertTriangle className="h-3 w-3" />;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -158,16 +159,16 @@ export function ClaudeCodeStatusBadge({ className }: ClaudeCodeStatusBadgeProps)
|
|||||||
// Get tooltip text
|
// Get tooltip text
|
||||||
const getTooltipText = () => {
|
const getTooltipText = () => {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case 'loading':
|
case "loading":
|
||||||
return t('navigation:claudeCode.checking', 'Checking Claude Code...');
|
return t("navigation:claudeCode.checking", "Checking Claude Code...");
|
||||||
case 'installed':
|
case "installed":
|
||||||
return t('navigation:claudeCode.upToDate', 'Claude Code is up to date');
|
return t("navigation:claudeCode.upToDate", "Claude Code is up to date");
|
||||||
case 'outdated':
|
case "outdated":
|
||||||
return t('navigation:claudeCode.updateAvailable', 'Claude Code update available');
|
return t("navigation:claudeCode.updateAvailable", "Claude Code update available");
|
||||||
case 'not-found':
|
case "not-found":
|
||||||
return t('navigation:claudeCode.notInstalled', 'Claude Code not installed');
|
return t("navigation:claudeCode.notInstalled", "Claude Code not installed");
|
||||||
case 'error':
|
case "error":
|
||||||
return t('navigation:claudeCode.error', 'Error checking Claude Code');
|
return t("navigation:claudeCode.error", "Error checking Claude Code");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -180,36 +181,36 @@ export function ClaudeCodeStatusBadge({ className }: ClaudeCodeStatusBadgeProps)
|
|||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
className={cn(
|
className={cn(
|
||||||
'w-full justify-start gap-2 text-xs',
|
"w-full justify-start gap-2 text-xs",
|
||||||
status === 'not-found' || status === 'error' ? 'text-destructive' : '',
|
status === "not-found" || status === "error" ? "text-destructive" : "",
|
||||||
status === 'outdated' ? 'text-yellow-600 dark:text-yellow-500' : '',
|
status === "outdated" ? "text-yellow-600 dark:text-yellow-500" : "",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<Terminal className="h-4 w-4" />
|
<Terminal className="h-4 w-4" />
|
||||||
<span className={cn(
|
<span
|
||||||
'absolute -bottom-0.5 -right-0.5 h-2 w-2 rounded-full',
|
className={cn(
|
||||||
getStatusColor()
|
"absolute -bottom-0.5 -right-0.5 h-2 w-2 rounded-full",
|
||||||
)} />
|
getStatusColor()
|
||||||
|
)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<span className="truncate">Claude Code</span>
|
<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">
|
<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>
|
</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">
|
<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>
|
</span>
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent side="right">
|
<TooltipContent side="right">{getTooltipText()}</TooltipContent>
|
||||||
{getTooltipText()}
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
<PopoverContent side="right" align="end" className="w-72">
|
<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>
|
<h4 className="text-sm font-medium">Claude Code CLI</h4>
|
||||||
<p className="text-xs text-muted-foreground flex items-center gap-1">
|
<p className="text-xs text-muted-foreground flex items-center gap-1">
|
||||||
{getStatusIcon()}
|
{getStatusIcon()}
|
||||||
{status === 'installed' && t('navigation:claudeCode.installed', 'Installed')}
|
{status === "installed" && t("navigation:claudeCode.installed", "Installed")}
|
||||||
{status === 'outdated' && t('navigation:claudeCode.outdated', 'Update available')}
|
{status === "outdated" && t("navigation:claudeCode.outdated", "Update available")}
|
||||||
{status === 'not-found' && t('navigation:claudeCode.missing', 'Not installed')}
|
{status === "not-found" && t("navigation:claudeCode.missing", "Not installed")}
|
||||||
{status === 'loading' && t('navigation:claudeCode.checking', 'Checking...')}
|
{status === "loading" && t("navigation:claudeCode.checking", "Checking...")}
|
||||||
{status === 'error' && t('navigation:claudeCode.error', 'Error')}
|
{status === "error" && t("navigation:claudeCode.error", "Error")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Version info */}
|
{/* Version info */}
|
||||||
{versionInfo && status !== 'loading' && (
|
{versionInfo && status !== "loading" && (
|
||||||
<div className="text-xs space-y-1 p-2 bg-muted rounded-md">
|
<div className="text-xs space-y-1 p-2 bg-muted rounded-md">
|
||||||
{versionInfo.installed && (
|
{versionInfo.installed && (
|
||||||
<div className="flex justify-between">
|
<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>
|
<span className="font-mono">{versionInfo.installed}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{versionInfo.latest && versionInfo.latest !== 'unknown' && (
|
{versionInfo.latest && versionInfo.latest !== "unknown" && (
|
||||||
<div className="flex justify-between">
|
<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>
|
<span className="font-mono">{versionInfo.latest}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{lastChecked && (
|
{lastChecked && (
|
||||||
<div className="flex justify-between text-muted-foreground">
|
<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>
|
<span>{lastChecked.toLocaleTimeString()}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -258,7 +263,7 @@ export function ClaudeCodeStatusBadge({ className }: ClaudeCodeStatusBadgeProps)
|
|||||||
|
|
||||||
{/* Actions */}
|
{/* Actions */}
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
{(status === 'not-found' || status === 'outdated') && (
|
{(status === "not-found" || status === "outdated") && (
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
className="flex-1 gap-1"
|
className="flex-1 gap-1"
|
||||||
@@ -270,10 +275,9 @@ export function ClaudeCodeStatusBadge({ className }: ClaudeCodeStatusBadgeProps)
|
|||||||
) : (
|
) : (
|
||||||
<Download className="h-3 w-3" />
|
<Download className="h-3 w-3" />
|
||||||
)}
|
)}
|
||||||
{status === 'outdated'
|
{status === "outdated"
|
||||||
? t('common:update', 'Update')
|
? t("common:update", "Update")
|
||||||
: t('common:install', 'Install')
|
: t("common:install", "Install")}
|
||||||
}
|
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
<Button
|
<Button
|
||||||
@@ -281,10 +285,10 @@ export function ClaudeCodeStatusBadge({ className }: ClaudeCodeStatusBadgeProps)
|
|||||||
size="sm"
|
size="sm"
|
||||||
className="gap-1"
|
className="gap-1"
|
||||||
onClick={() => checkVersion()}
|
onClick={() => checkVersion()}
|
||||||
disabled={status === 'loading'}
|
disabled={status === "loading"}
|
||||||
>
|
>
|
||||||
<RefreshCw className={cn('h-3 w-3', status === 'loading' && 'animate-spin')} />
|
<RefreshCw className={cn("h-3 w-3", status === "loading" && "animate-spin")} />
|
||||||
{t('common:refresh', 'Refresh')}
|
{t("common:refresh", "Refresh")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -293,10 +297,32 @@ export function ClaudeCodeStatusBadge({ className }: ClaudeCodeStatusBadgeProps)
|
|||||||
variant="link"
|
variant="link"
|
||||||
size="sm"
|
size="sm"
|
||||||
className="w-full text-xs text-muted-foreground gap-1"
|
className="w-full text-xs text-muted-foreground gap-1"
|
||||||
onClick={() => window.electronAPI?.openExternal?.('https://claude.ai/code')}
|
onClick={() => window.electronAPI?.openExternal?.("https://claude.ai/code")}
|
||||||
aria-label={t('navigation:claudeCode.learnMoreAriaLabel', 'Learn more about Claude Code (opens in new window)')}
|
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" />
|
<ExternalLink className="h-3 w-3" aria-hidden="true" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@@ -307,18 +333,19 @@ export function ClaudeCodeStatusBadge({ className }: ClaudeCodeStatusBadgeProps)
|
|||||||
<AlertDialogContent>
|
<AlertDialogContent>
|
||||||
<AlertDialogHeader>
|
<AlertDialogHeader>
|
||||||
<AlertDialogTitle>
|
<AlertDialogTitle>
|
||||||
{t('navigation:claudeCode.updateWarningTitle', 'Update Claude Code?')}
|
{t("navigation:claudeCode.updateWarningTitle", "Update Claude Code?")}
|
||||||
</AlertDialogTitle>
|
</AlertDialogTitle>
|
||||||
<AlertDialogDescription>
|
<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>
|
</AlertDialogDescription>
|
||||||
</AlertDialogHeader>
|
</AlertDialogHeader>
|
||||||
<AlertDialogFooter>
|
<AlertDialogFooter>
|
||||||
<AlertDialogCancel>
|
<AlertDialogCancel>{t("common:cancel", "Cancel")}</AlertDialogCancel>
|
||||||
{t('common:cancel', 'Cancel')}
|
|
||||||
</AlertDialogCancel>
|
|
||||||
<AlertDialogAction onClick={performInstall}>
|
<AlertDialogAction onClick={performInstall}>
|
||||||
{t('navigation:claudeCode.updateAnyway', 'Update Anyway')}
|
{t("navigation:claudeCode.updateAnyway", "Update Anyway")}
|
||||||
</AlertDialogAction>
|
</AlertDialogAction>
|
||||||
</AlertDialogFooter>
|
</AlertDialogFooter>
|
||||||
</AlertDialogContent>
|
</AlertDialogContent>
|
||||||
|
|||||||
@@ -133,5 +133,20 @@
|
|||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
"remove": "Remove",
|
"remove": "Remove",
|
||||||
"error": "Failed to remove project"
|
"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",
|
"lastChecked": "Last checked",
|
||||||
"learnMore": "Learn more about Claude Code",
|
"learnMore": "Learn more about Claude Code",
|
||||||
"learnMoreAriaLabel": "Learn more about Claude Code (opens in new window)",
|
"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?",
|
"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.",
|
"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"
|
"updateAnyway": "Update Anyway"
|
||||||
|
|||||||
@@ -133,5 +133,20 @@
|
|||||||
"cancel": "Annuler",
|
"cancel": "Annuler",
|
||||||
"remove": "Retirer",
|
"remove": "Retirer",
|
||||||
"error": "Échec de la suppression du projet"
|
"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",
|
"lastChecked": "Dernière vérification",
|
||||||
"learnMore": "En savoir plus sur Claude Code",
|
"learnMore": "En savoir plus sur Claude Code",
|
||||||
"learnMoreAriaLabel": "En savoir plus sur Claude Code (s'ouvre dans une nouvelle fenêtre)",
|
"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 ?",
|
"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.",
|
"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"
|
"updateAnyway": "Mettre à jour quand même"
|
||||||
|
|||||||
Reference in New Issue
Block a user