Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added public/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
95 changes: 95 additions & 0 deletions src/components/about-dialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { useEffect } from "react";
import { openUrl } from "@tauri-apps/plugin-opener";

interface AboutDialogProps {
version: string;
onClose: () => void;
}

function ExternalLink({
href,
children,
}: {
href: string;
children: React.ReactNode;
}) {
const handleClick = () => {
openUrl(href).catch(console.error);
};

return (
<button
type="button"
onClick={handleClick}
className="text-primary hover:underline focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 rounded-sm"
>
{children}
</button>
);
}

export function AboutDialog({ version, onClose }: AboutDialogProps) {
// Close on ESC key
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === "Escape") {
onClose();
}
};
document.addEventListener("keydown", handleKeyDown);
return () => document.removeEventListener("keydown", handleKeyDown);
}, [onClose]);

// Close when panel hides (loses visibility)
useEffect(() => {
const handleVisibilityChange = () => {
if (document.hidden) {
onClose();
}
};
document.addEventListener("visibilitychange", handleVisibilityChange);
return () => document.removeEventListener("visibilitychange", handleVisibilityChange);
}, [onClose]);

// Close on backdrop click
const handleBackdropClick = (e: React.MouseEvent) => {
if (e.target === e.currentTarget) {
onClose();
}
};

return (
<div
className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm"
onClick={handleBackdropClick}
>
<div className="bg-card rounded-lg border shadow-xl p-6 max-w-xs w-full mx-4 text-center animate-in fade-in zoom-in-95 duration-200">
<img
src="/icon.png"
alt="OpenUsage"
className="w-16 h-16 mx-auto mb-3 rounded-xl"
/>

<h2 className="text-xl font-semibold mb-1">OpenUsage</h2>

<span className="inline-block text-xs text-muted-foreground bg-muted px-2 py-0.5 rounded-full mb-4">
v{version}
</span>

<div className="text-sm text-muted-foreground space-y-1">
<p>
Built by{" "}
<ExternalLink href="https://itsbyrob.in/x">Robin Ebers</ExternalLink>
</p>
<p>
Open source on{" "}
<ExternalLink href="https://github.com/robinebers/openusage">
GitHub
</ExternalLink>
</p>
<p className="text-xs pt-1">Contributions welcome</p>
</div>
</div>
</div>
);
}
38 changes: 26 additions & 12 deletions src/components/panel-footer.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useEffect, useMemo, useState } from "react";
import { Button } from "@/components/ui/button";
import { AboutDialog } from "@/components/about-dialog";
import type { UpdateStatus } from "@/hooks/use-app-update";

interface PanelFooterProps {
Expand All @@ -13,10 +14,12 @@ function VersionDisplay({
version,
updateStatus,
onUpdateInstall,
onVersionClick,
}: {
version: string;
updateStatus: UpdateStatus;
onUpdateInstall: () => void;
onVersionClick: () => void;
}) {
switch (updateStatus.status) {
case "downloading":
Expand Down Expand Up @@ -49,9 +52,13 @@ function VersionDisplay({
);
default:
return (
<span className="text-xs text-muted-foreground">
<button
type="button"
onClick={onVersionClick}
className="text-xs text-muted-foreground hover:text-foreground transition-colors cursor-pointer"
>
OpenUsage {version}
</span>
</button>
);
}
}
Expand All @@ -63,6 +70,7 @@ export function PanelFooter({
onUpdateInstall,
}: PanelFooterProps) {
const [now, setNow] = useState(() => Date.now());
const [showAbout, setShowAbout] = useState(false);

useEffect(() => {
if (!autoUpdateNextAt) return undefined;
Expand All @@ -83,15 +91,21 @@ export function PanelFooter({
}, [autoUpdateNextAt, now]);

return (
<div className="flex justify-between items-center h-8 pt-1.5 border-t">
<VersionDisplay
version={version}
updateStatus={updateStatus}
onUpdateInstall={onUpdateInstall}
/>
<span className="text-xs text-muted-foreground tabular-nums">
{countdownLabel}
</span>
</div>
<>
<div className="flex justify-between items-center h-8 pt-1.5 border-t">
<VersionDisplay
version={version}
updateStatus={updateStatus}
onUpdateInstall={onUpdateInstall}
onVersionClick={() => setShowAbout(true)}
/>
<span className="text-xs text-muted-foreground tabular-nums">
{countdownLabel}
</span>
</div>
{showAbout && (
<AboutDialog version={version} onClose={() => setShowAbout(false)} />
)}
</>
);
}