index.tsx raw
1 import { Button } from '@/components/ui/button'
2 import { createJoinDraftEvent, createLeaveDraftEvent } from '@/lib/draft-event'
3 import { checkNip43Support } from '@/lib/relay'
4 import { useNostr } from '@/providers/NostrProvider'
5 import relayMembershipService from '@/services/relay-membership.service'
6 import { TRelayInfo } from '@/types'
7 import { LogIn, LogOut, Mail } from 'lucide-react'
8 import { useEffect, useMemo, useState } from 'react'
9 import { useTranslation } from 'react-i18next'
10 import { toast } from 'sonner'
11 import InviteCodeDialog from './InviteCodeDialog'
12 import JoinDialog from './JoinDialog'
13
14 interface RelayMembershipControlProps {
15 relayInfo: TRelayInfo
16 onMembershipStatusChange?: (status: boolean) => void
17 }
18
19 export default function RelayMembershipControl({
20 relayInfo,
21 onMembershipStatusChange
22 }: RelayMembershipControlProps) {
23 const { t } = useTranslation()
24 const { pubkey, checkLogin, publish } = useNostr()
25 const [isMember, setIsMember] = useState(false)
26 const [isLoading, setIsLoading] = useState(false)
27 const [isChecking, setIsChecking] = useState(false)
28 const [showJoinDialog, setShowJoinDialog] = useState(false)
29 const [showInviteCodeDialog, setShowInviteCodeDialog] = useState(false)
30 const supportsNip43 = useMemo(() => checkNip43Support(relayInfo), [relayInfo])
31
32 useEffect(() => {
33 if (!supportsNip43 || !pubkey) {
34 setIsMember(false)
35 return
36 }
37
38 const checkMembership = async () => {
39 try {
40 setIsChecking(true)
41 const status = await relayMembershipService.checkMembership(
42 relayInfo.url,
43 pubkey,
44 relayInfo.pubkey
45 )
46 setIsMember(status)
47 } finally {
48 setIsChecking(false)
49 }
50 }
51
52 checkMembership()
53 }, [relayInfo.url, relayInfo.pubkey, pubkey, supportsNip43])
54
55 useEffect(() => {
56 if (onMembershipStatusChange) {
57 onMembershipStatusChange(isMember)
58 }
59 }, [isMember, onMembershipStatusChange])
60
61 if (!supportsNip43 || isChecking) {
62 return null
63 }
64
65 const submitJoinRequest = async () => {
66 setIsLoading(true)
67 try {
68 const draftEvent = createJoinDraftEvent('')
69 const joinRequestEvent = await publish(draftEvent, {
70 specifiedRelayUrls: [relayInfo.url]
71 })
72 toast.success(t('Join request sent successfully'))
73 await relayMembershipService.addNewMember(relayInfo.url, joinRequestEvent.pubkey)
74 onMembershipStatusChange?.(true)
75 } catch {
76 setShowJoinDialog(true)
77 } finally {
78 setIsLoading(false)
79 }
80 }
81
82 const handleGetInviteCodeClick = () => {
83 setShowInviteCodeDialog(true)
84 }
85
86 const handleLeaveClick = async () => {
87 if (!confirm(t('Are you sure you want to leave this relay?'))) {
88 return
89 }
90
91 setIsLoading(true)
92 try {
93 const draftEvent = createLeaveDraftEvent()
94 const leaveRequestEvent = await publish(draftEvent, {
95 specifiedRelayUrls: [relayInfo.url]
96 })
97 toast.success(t('Leave request sent successfully'))
98 await relayMembershipService.removeMember(relayInfo.url, leaveRequestEvent.pubkey)
99 setIsMember(false)
100 } catch (error: any) {
101 const errors = error instanceof AggregateError ? error.errors : [error]
102 errors.forEach((err) => {
103 toast.error(
104 `${t('Failed to send leave request')}: ${err instanceof Error ? err.message : String(err)}`,
105 { duration: 10_000 }
106 )
107 console.error(err)
108 })
109 return
110 } finally {
111 setIsLoading(false)
112 }
113 }
114
115 return (
116 <>
117 {isMember ? (
118 <div className="grid grid-cols-2 gap-2">
119 <Button
120 variant="secondary"
121 className="w-full"
122 onClick={handleGetInviteCodeClick}
123 disabled={isLoading}
124 >
125 <Mail className="w-4 h-4 mr-2" />
126 {t('Get Invite Code')}
127 </Button>
128 <Button
129 variant="outline"
130 className="w-full"
131 onClick={handleLeaveClick}
132 disabled={isLoading}
133 >
134 <LogOut className="w-4 h-4 mr-2" />
135 {t('Leave')}
136 </Button>
137 </div>
138 ) : (
139 <Button
140 variant="default"
141 className="w-full"
142 onClick={() => {
143 checkLogin(() => submitJoinRequest())
144 }}
145 disabled={isLoading}
146 >
147 <LogIn className="w-4 h-4 mr-2" />
148 {t('Request to Join Relay')}
149 </Button>
150 )}
151
152 <JoinDialog
153 relayInfo={relayInfo}
154 showJoinDialog={showJoinDialog}
155 setShowJoinDialog={setShowJoinDialog}
156 onMembershipStatusChange={setIsMember}
157 />
158
159 <InviteCodeDialog
160 relayInfo={relayInfo}
161 showInviteCodeDialog={showInviteCodeDialog}
162 setShowInviteCodeDialog={setShowInviteCodeDialog}
163 />
164 </>
165 )
166 }
167