LogoutButton.tsx raw
1 import { cn } from '@/lib/utils'
2 import { useNostr } from '@/providers/NostrProvider'
3 import { LogOut } from 'lucide-react'
4 import { useCallback, useRef, useState } from 'react'
5 import { useTranslation } from 'react-i18next'
6 import SidebarItem from './SidebarItem'
7
8 const HOLD_DURATION = 3000
9
10 export default function LogoutButton({ collapse }: { collapse: boolean }) {
11 const { t } = useTranslation()
12 const { account, removeAccount } = useNostr()
13 const [progress, setProgress] = useState(0)
14 const [isHolding, setIsHolding] = useState(false)
15 const animationRef = useRef<number | null>(null)
16 const startTimeRef = useRef<number | null>(null)
17
18 const startHold = useCallback(() => {
19 if (!account) return
20 setIsHolding(true)
21 startTimeRef.current = Date.now()
22
23 const animate = () => {
24 if (!startTimeRef.current) return
25 const elapsed = Date.now() - startTimeRef.current
26 const newProgress = Math.min((elapsed / HOLD_DURATION) * 100, 100)
27 setProgress(newProgress)
28
29 if (newProgress >= 100) {
30 // Logout triggered
31 removeAccount(account)
32 setProgress(0)
33 setIsHolding(false)
34 startTimeRef.current = null
35 return
36 }
37
38 animationRef.current = requestAnimationFrame(animate)
39 }
40
41 animationRef.current = requestAnimationFrame(animate)
42 }, [account, removeAccount])
43
44 const cancelHold = useCallback(() => {
45 if (animationRef.current) {
46 cancelAnimationFrame(animationRef.current)
47 animationRef.current = null
48 }
49 startTimeRef.current = null
50 setProgress(0)
51 setIsHolding(false)
52 }, [])
53
54 if (!account) return null
55
56 return (
57 <div className="relative">
58 <SidebarItem
59 title={t('Logout')}
60 collapse={collapse}
61 onTouchStart={startHold}
62 onTouchEnd={cancelHold}
63 onTouchCancel={cancelHold}
64 onMouseDown={startHold}
65 onMouseUp={cancelHold}
66 onMouseLeave={cancelHold}
67 className={cn('select-none', isHolding && 'text-destructive')}
68 >
69 <LogOut />
70 </SidebarItem>
71 {isHolding && (
72 <div className="absolute bottom-0 left-0 right-0 h-1 bg-muted rounded-full overflow-hidden">
73 <div
74 className="h-full bg-destructive transition-none"
75 style={{ width: `${progress}%` }}
76 />
77 </div>
78 )}
79 </div>
80 )
81 }
82