2026-05-14 21:00:07 -05:00
import { useEffect , useRef , useState } from 'react' ;
2026-05-10 00:18:36 -05:00
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
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 ) ;
2026-05-10 00:18:36 -05:00
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 = ( ) => {
setOpen ( false ) ;
2026-05-14 21:00:07 -05:00
setHasNewVersion ( false ) ; // optimistic — don't wait for the server
2026-05-31 15:06:10 -05:00
api . acknowledgeVersion ( ) . catch ( err => console . error ( '[ReleaseNotesDialog] failed to acknowledge version' , err ) ) ; // backend stores the seen release version
2026-05-14 21:00:07 -05:00
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 ( ) ; } } >
2026-05-15 22:45:38 -05:00
< DialogContent className = "max-h-[92dvh] max-w-md overflow-y-auto sm:max-w-lg" >
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 >
2026-05-10 00:18:36 -05:00
< 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 ) => (
2026-05-10 00:18:36 -05:00
< 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-15 22:45:38 -05:00
{ RELEASE _NOTES . image && (
< figure className = "mt-5 flex justify-center" >
< div className = "w-full max-w-sm overflow-hidden rounded-xl border border-border bg-muted/20 shadow-sm sm:max-w-md" >
< img
src = { RELEASE _NOTES . image . src }
alt = { RELEASE _NOTES . image . alt }
loading = "lazy"
className = "aspect-[16/10] w-full object-contain"
/ >
< / div >
< / figure >
) }
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 >
) ;
}