c878970180
Post-update UX for the next release (0.1.2): - What's New: after a version change, greet the user with a toast that opens an in-app release-notes screen (collapsible Added/Changed/Fixed sections) sourced from the bundled CHANGELOG.md, reopenable from Settings -> Updates. Backed by a last_seen_version settings file (get/set_last_seen_version commands) that distinguishes upgrades from fresh installs. - Updater: the download/install progress toast now reappears when an update is started from the title-bar indicator or Settings after the prompt was dismissed; Settings -> Updates gains a real progress bar with a percentage. - Light theme: fix the recurring subtle-light breakage. Neutral surfaces now rely on the CSS-variable remap instead of light-theme:bg-white (which forced surfaces dark because --color-white is remapped dark); the green action buttons drop the broken light-theme:hover:bg-emerald-200 (remapped dark, unreadable on hover) for an override-free emerald-500 tint that auto-themes, across the updater, What's New, and onboarding. - Debug panel: add What's New triggers (toast / modal / reset).
68 lines
2.8 KiB
TypeScript
68 lines
2.8 KiB
TypeScript
import { AnimatePresence, motion } from "framer-motion";
|
|
import { useGalleryStore } from "../store";
|
|
|
|
export function UpdateToast() {
|
|
const updateStatus = useGalleryStore((s) => s.updateStatus);
|
|
const updateVersion = useGalleryStore((s) => s.updateVersion);
|
|
const updateProgress = useGalleryStore((s) => s.updateProgress);
|
|
const updateDismissed = useGalleryStore((s) => s.updateDismissed);
|
|
const installUpdate = useGalleryStore((s) => s.installUpdate);
|
|
const dismissUpdate = useGalleryStore((s) => s.dismissUpdate);
|
|
|
|
const visible =
|
|
!updateDismissed &&
|
|
(updateStatus === "available" || updateStatus === "downloading" || updateStatus === "installing");
|
|
|
|
return (
|
|
<AnimatePresence>
|
|
{visible ? (
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 12 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
exit={{ opacity: 0, y: 12 }}
|
|
transition={{ duration: 0.18 }}
|
|
className="fixed bottom-4 right-4 z-50 w-80 rounded-lg border border-white/10 bg-gray-900 p-4 shadow-xl"
|
|
>
|
|
{updateStatus === "available" ? (
|
|
<>
|
|
<p className="text-sm font-medium text-white">Update available</p>
|
|
<p className="mt-1 text-xs text-gray-500">
|
|
Phokus v{updateVersion} is ready to download and install.
|
|
</p>
|
|
<div className="mt-3 flex items-center gap-2">
|
|
<button
|
|
className="rounded-md border border-emerald-400/35 bg-emerald-500/15 px-3 py-1.5 text-xs text-emerald-200 transition-colors hover:bg-emerald-500/25"
|
|
onClick={() => void installUpdate()}
|
|
>
|
|
Install & restart
|
|
</button>
|
|
<button
|
|
className="rounded-md border border-transparent px-3 py-1.5 text-xs text-gray-500 transition-colors hover:bg-white/[0.06] hover:text-gray-200"
|
|
onClick={dismissUpdate}
|
|
>
|
|
Later
|
|
</button>
|
|
</div>
|
|
</>
|
|
) : (
|
|
<>
|
|
<p className="text-sm font-medium text-white">
|
|
{updateStatus === "installing" ? "Installing update..." : "Downloading update..."}
|
|
</p>
|
|
<div className="mt-3 h-1 overflow-hidden rounded-full bg-white/10">
|
|
<div
|
|
className={`h-full rounded-full bg-emerald-400/80 transition-[width] duration-200 ${
|
|
updateProgress === null ? "w-full animate-pulse" : ""
|
|
}`}
|
|
style={updateProgress !== null ? { width: `${Math.round(updateProgress * 100)}%` } : undefined}
|
|
/>
|
|
</div>
|
|
<p className="mt-2 text-xs text-gray-600">The app will restart when it finishes.</p>
|
|
</>
|
|
)}
|
|
</motion.div>
|
|
) : null}
|
|
</AnimatePresence>
|
|
);
|
|
}
|