MessageInfoModal.tsx raw
1 import {
2 Dialog,
3 DialogContent,
4 DialogHeader,
5 DialogTitle
6 } from '@/components/ui/dialog'
7 import { Button } from '@/components/ui/button'
8 import dmService from '@/services/dm.service'
9 import { TDirectMessage } from '@/types'
10 import { Loader2, RefreshCw, Server } from 'lucide-react'
11 import { useState } from 'react'
12 import { useTranslation } from 'react-i18next'
13
14 interface MessageInfoModalProps {
15 message: TDirectMessage | null
16 open: boolean
17 onOpenChange: (open: boolean) => void
18 onRelaysUpdated?: (relays: string[]) => void
19 }
20
21 export default function MessageInfoModal({
22 message,
23 open,
24 onOpenChange,
25 onRelaysUpdated
26 }: MessageInfoModalProps) {
27 const { t } = useTranslation()
28 const [isChecking, setIsChecking] = useState(false)
29 const [additionalRelays, setAdditionalRelays] = useState<string[]>([])
30
31 if (!message) return null
32
33 const allRelays = [...(message.seenOnRelays || []), ...additionalRelays]
34 const uniqueRelays = [...new Set(allRelays)]
35
36 const handleCheckOtherRelays = async () => {
37 setIsChecking(true)
38 try {
39 const foundRelays = await dmService.checkOtherRelaysForEvent(
40 message.id,
41 uniqueRelays
42 )
43 if (foundRelays.length > 0) {
44 const newRelays = [...additionalRelays, ...foundRelays]
45 setAdditionalRelays(newRelays)
46 onRelaysUpdated?.([...(message.seenOnRelays || []), ...newRelays])
47 }
48 } catch (error) {
49 console.error('Failed to check other relays:', error)
50 } finally {
51 setIsChecking(false)
52 }
53 }
54
55 const formatRelayUrl = (url: string) => {
56 try {
57 const parsed = new URL(url)
58 return parsed.hostname + (parsed.pathname !== '/' ? parsed.pathname : '')
59 } catch {
60 return url
61 }
62 }
63
64 return (
65 <Dialog open={open} onOpenChange={onOpenChange}>
66 <DialogContent className="max-w-sm">
67 <DialogHeader>
68 <DialogTitle className="flex items-center gap-2">
69 <Server className="size-4" />
70 {t('Message Info')}
71 </DialogTitle>
72 </DialogHeader>
73
74 <div className="space-y-4">
75 {/* Protocol */}
76 <div>
77 <span className="text-sm font-medium text-muted-foreground">
78 {t('Encryption')}
79 </span>
80 <p className="text-sm mt-1">
81 {message.encryptionType === 'nip17' ? 'NIP-44 (Gift Wrap)' : 'NIP-04 (Legacy)'}
82 </p>
83 </div>
84
85 {/* Relays */}
86 <div>
87 <span className="text-sm font-medium text-muted-foreground">
88 {t('Seen on relays')}
89 </span>
90 {uniqueRelays.length > 0 ? (
91 <ul className="mt-1 space-y-1">
92 {uniqueRelays.map((relay) => (
93 <li
94 key={relay}
95 className="text-sm font-mono bg-muted px-2 py-1 rounded truncate"
96 title={relay}
97 >
98 {formatRelayUrl(relay)}
99 </li>
100 ))}
101 </ul>
102 ) : (
103 <p className="text-sm text-muted-foreground mt-1">
104 {t('No relay information available')}
105 </p>
106 )}
107 </div>
108
109 {/* Check other relays button */}
110 <Button
111 variant="outline"
112 size="sm"
113 className="w-full"
114 onClick={handleCheckOtherRelays}
115 disabled={isChecking}
116 >
117 {isChecking ? (
118 <>
119 <Loader2 className="size-4 mr-2 animate-spin" />
120 {t('Checking...')}
121 </>
122 ) : (
123 <>
124 <RefreshCw className="size-4 mr-2" />
125 {t('Check for other relays')}
126 </>
127 )}
128 </Button>
129 </div>
130 </DialogContent>
131 </Dialog>
132 )
133 }
134