JoinDialog.tsx raw
1 import { Button } from '@/components/ui/button'
2 import {
3 Dialog,
4 DialogContent,
5 DialogDescription,
6 DialogFooter,
7 DialogHeader,
8 DialogTitle
9 } from '@/components/ui/dialog'
10 import {
11 Drawer,
12 DrawerClose,
13 DrawerContent,
14 DrawerDescription,
15 DrawerFooter,
16 DrawerHeader,
17 DrawerTitle
18 } from '@/components/ui/drawer'
19 import { Input } from '@/components/ui/input'
20 import { Label } from '@/components/ui/label'
21 import { createJoinDraftEvent } from '@/lib/draft-event'
22 import { useNostr } from '@/providers/NostrProvider'
23 import { useScreenSize } from '@/providers/ScreenSizeProvider'
24 import relayMembershipService from '@/services/relay-membership.service'
25 import { TRelayInfo } from '@/types'
26 import { useState } from 'react'
27 import { useTranslation } from 'react-i18next'
28 import { toast } from 'sonner'
29
30 export default function JoinDialog({
31 relayInfo,
32 showJoinDialog,
33 setShowJoinDialog,
34 onMembershipStatusChange
35 }: {
36 relayInfo: TRelayInfo
37 showJoinDialog: boolean
38 setShowJoinDialog: (open: boolean) => void
39 onMembershipStatusChange: (status: boolean) => void
40 }) {
41 const { t } = useTranslation()
42 const { isSmallScreen } = useScreenSize()
43 const { publish } = useNostr()
44 const [inviteCode, setInviteCode] = useState('')
45 const [isLoading, setIsLoading] = useState(false)
46
47 const handleJoinSubmit = async () => {
48 setIsLoading(true)
49 try {
50 const draftEvent = createJoinDraftEvent(inviteCode)
51 const joinRequestEvent = await publish(draftEvent, {
52 specifiedRelayUrls: [relayInfo.url]
53 })
54 toast.success(t('Join request sent successfully'))
55 await relayMembershipService.addNewMember(relayInfo.url, joinRequestEvent.pubkey)
56 onMembershipStatusChange(true)
57 setInviteCode('')
58 setShowJoinDialog(false)
59 } catch (error) {
60 const errors = error instanceof AggregateError ? error.errors : [error]
61 errors.forEach((err) => {
62 toast.error(
63 `${t('Failed to send join request')}: ${err instanceof Error ? err.message : String(err)}`,
64 { duration: 10_000 }
65 )
66 console.error(err)
67 })
68 return
69 } finally {
70 setIsLoading(false)
71 }
72 }
73
74 const content = (
75 <div className="space-y-2">
76 <Label htmlFor="invite-code">{t('Invite Code')}</Label>
77 <Input
78 id="invite-code"
79 value={inviteCode}
80 onChange={(e) => setInviteCode(e.target.value)}
81 placeholder={t('Enter invite code')}
82 required
83 />
84 <p className="text-sm text-muted-foreground">
85 {t('You can get an invite code from a relay member.')}
86 </p>
87 </div>
88 )
89
90 if (isSmallScreen) {
91 return (
92 <Drawer open={showJoinDialog} onOpenChange={setShowJoinDialog}>
93 <DrawerContent>
94 <DrawerHeader>
95 <DrawerTitle>{t('Request to Join Relay')}</DrawerTitle>
96 <DrawerDescription>
97 {t('Enter the invite code you received from a relay member.')}
98 </DrawerDescription>
99 </DrawerHeader>
100 <div className="p-4">{content}</div>
101 <DrawerFooter>
102 <Button onClick={handleJoinSubmit} disabled={isLoading || !inviteCode.trim()}>
103 {isLoading ? t('Sending...') : t('Send Request')}
104 </Button>
105 <DrawerClose asChild>
106 <Button variant="outline">{t('Cancel')}</Button>
107 </DrawerClose>
108 </DrawerFooter>
109 </DrawerContent>
110 </Drawer>
111 )
112 }
113
114 return (
115 <Dialog open={showJoinDialog} onOpenChange={setShowJoinDialog}>
116 <DialogContent>
117 <DialogHeader>
118 <DialogTitle>{t('Request to Join Relay')}</DialogTitle>
119 <DialogDescription>
120 {t('Enter the invite code you received from a relay member.')}
121 </DialogDescription>
122 </DialogHeader>
123 {content}
124 <DialogFooter>
125 <Button
126 variant="ghost"
127 onClick={() => {
128 setShowJoinDialog(false)
129 setInviteCode('')
130 }}
131 >
132 {t('Cancel')}
133 </Button>
134 <Button onClick={handleJoinSubmit} disabled={isLoading || !inviteCode.trim()}>
135 {isLoading ? t('Sending...') : t('Send Request')}
136 </Button>
137 </DialogFooter>
138 </DialogContent>
139 </Dialog>
140 )
141 }
142