BillTracker/client/components/ReleaseNotesDialog.jsx

70 lines
2.8 KiB
React
Raw Normal View History

2026-05-14 21:00:07 -05:00
import { useEffect, useRef, useState } from 'react';
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from '@/components/ui/dialog';
2026-05-03 19:51:57 -05:00
import { Button } from '@/components/ui/button';
import { APP_VERSION, RELEASE_NOTES } from '@/lib/version';
import { Sparkles } from 'lucide-react';
2026-05-14 21:00:07 -05:00
import { useAuth } from '@/hooks/useAuth';
import { api } from '@/api';
2026-05-03 19:51:57 -05:00
2026-05-14 21:00:07 -05:00
// Written on close so the dialog doesn't flash during the brief window before
// /me resolves on repeat visits. The backend is authoritative; this is a cache.
const LS_KEY = `bt-release-seen-${APP_VERSION}`;
2026-05-03 19:51:57 -05:00
export function ReleaseNotesDialog() {
2026-05-14 21:00:07 -05:00
const { hasNewVersion, setHasNewVersion } = useAuth();
2026-05-03 19:51:57 -05:00
const [open, setOpen] = useState(false);
const titleRef = useRef(null);
2026-05-03 19:51:57 -05:00
useEffect(() => {
2026-05-14 21:00:07 -05:00
if (hasNewVersion) setOpen(true);
}, [hasNewVersion]);
2026-05-03 19:51:57 -05:00
const handleClose = () => {
2026-05-14 21:00:07 -05:00
localStorage.setItem(LS_KEY, '1');
2026-05-03 19:51:57 -05:00
setOpen(false);
2026-05-14 21:00:07 -05:00
setHasNewVersion(false); // optimistic — don't wait for the server
api.acknowledgeVersion().catch(() => {}); // fire-and-forget
const prev = document.activeElement;
if (prev?.focus) setTimeout(() => prev.focus(), 0);
2026-05-03 19:51:57 -05:00
};
return (
2026-05-14 21:00:07 -05:00
<Dialog open={open} onOpenChange={v => { if (!v) handleClose(); }}>
<DialogContent className="max-w-md">
2026-05-03 19:51:57 -05:00
<DialogHeader>
<div className="flex items-center gap-2 mb-1">
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-primary/10">
<Sparkles className="h-4 w-4 text-primary" />
</div>
<span className="text-xs font-medium text-muted-foreground uppercase tracking-wider">
2026-05-14 21:00:07 -05:00
v{RELEASE_NOTES.version} · {RELEASE_NOTES.date}
2026-05-03 19:51:57 -05:00
</span>
</div>
2026-05-14 21:00:07 -05:00
<DialogTitle ref={titleRef} className="text-xl">What's new</DialogTitle>
<DialogDescription className="sr-only">
Release highlights for BillTracker v{RELEASE_NOTES.version}
</DialogDescription>
2026-05-03 19:51:57 -05:00
</DialogHeader>
<div className="mt-2 space-y-3" role="list" aria-label="Release highlights">
2026-05-03 19:51:57 -05:00
{RELEASE_NOTES.highlights.map((item, i) => (
<div key={i} className="flex gap-3 items-start" role="listitem">
<span className="text-lg leading-none mt-0.5" aria-hidden="true">{item.icon}</span>
2026-05-03 19:51:57 -05:00
<div>
<p className="text-sm font-medium text-foreground">{item.title}</p>
2026-05-14 21:00:07 -05:00
<p className="text-xs text-muted-foreground mt-0.5 leading-relaxed">{item.desc}</p>
2026-05-03 19:51:57 -05:00
</div>
</div>
))}
</div>
2026-05-14 21:00:07 -05:00
<div className="mt-4 pt-4 border-t border-border flex items-center justify-end">
2026-05-03 19:51:57 -05:00
<Button size="sm" onClick={handleClose}>
2026-05-14 21:00:07 -05:00
Got it
2026-05-03 19:51:57 -05:00
</Button>
</div>
</DialogContent>
</Dialog>
);
}