index.tsx raw

   1  import { Button } from '@/components/ui/button'
   2  import { normalizeUrl } from '@/lib/url'
   3  import { useNostr } from '@/providers/NostrProvider'
   4  import { TMailboxRelay, TMailboxRelayScope } from '@/types'
   5  import { useEffect, useState } from 'react'
   6  import { useTranslation } from 'react-i18next'
   7  import {
   8    DndContext,
   9    closestCenter,
  10    KeyboardSensor,
  11    PointerSensor,
  12    TouchSensor,
  13    useSensor,
  14    useSensors,
  15    DragEndEvent
  16  } from '@dnd-kit/core'
  17  import {
  18    arrayMove,
  19    SortableContext,
  20    sortableKeyboardCoordinates,
  21    verticalListSortingStrategy
  22  } from '@dnd-kit/sortable'
  23  import { restrictToVerticalAxis, restrictToParentElement } from '@dnd-kit/modifiers'
  24  import MailboxRelay from './MailboxRelay'
  25  import NewMailboxRelayInput from './NewMailboxRelayInput'
  26  import RelayCountWarning from './RelayCountWarning'
  27  import SaveButton from './SaveButton'
  28  
  29  export default function MailboxSetting() {
  30    const { t } = useTranslation()
  31    const { pubkey, relayList, checkLogin } = useNostr()
  32    const [relays, setRelays] = useState<TMailboxRelay[]>([])
  33    const [hasChange, setHasChange] = useState(false)
  34  
  35    const sensors = useSensors(
  36      useSensor(PointerSensor, {
  37        activationConstraint: {
  38          distance: 8
  39        }
  40      }),
  41      useSensor(TouchSensor, {
  42        activationConstraint: {
  43          delay: 200,
  44          tolerance: 8
  45        }
  46      }),
  47      useSensor(KeyboardSensor, {
  48        coordinateGetter: sortableKeyboardCoordinates
  49      })
  50    )
  51  
  52    function handleDragEnd(event: DragEndEvent) {
  53      const { active, over } = event
  54  
  55      if (active.id !== over?.id) {
  56        const oldIndex = relays.findIndex((relay) => relay.url === active.id)
  57        const newIndex = relays.findIndex((relay) => relay.url === over?.id)
  58  
  59        if (oldIndex !== -1 && newIndex !== -1) {
  60          setRelays((relays) => arrayMove(relays, oldIndex, newIndex))
  61          setHasChange(true)
  62        }
  63      }
  64    }
  65  
  66    useEffect(() => {
  67      if (!relayList) return
  68  
  69      setRelays(relayList.originalRelays)
  70    }, [relayList])
  71  
  72    if (!pubkey) {
  73      return (
  74        <div className="flex flex-col w-full items-center">
  75          <Button size="lg" onClick={() => checkLogin()}>
  76            {t('Login to set')}
  77          </Button>
  78        </div>
  79      )
  80    }
  81  
  82    if (!relayList) {
  83      return <div className="text-center text-sm text-muted-foreground">{t('loading...')}</div>
  84    }
  85  
  86    const changeMailboxRelayScope = (url: string, scope: TMailboxRelayScope) => {
  87      setRelays((prev) => prev.map((r) => (r.url === url ? { ...r, scope } : r)))
  88      setHasChange(true)
  89    }
  90  
  91    const removeMailboxRelay = (url: string) => {
  92      setRelays((prev) => prev.filter((r) => r.url !== url))
  93      setHasChange(true)
  94    }
  95  
  96    const saveNewMailboxRelay = (url: string) => {
  97      if (url === '') return null
  98      const normalizedUrl = normalizeUrl(url)
  99      if (!normalizedUrl) {
 100        return t('Invalid relay URL')
 101      }
 102      if (relays.some((r) => r.url === normalizedUrl)) {
 103        return t('Relay already exists')
 104      }
 105      setRelays([...relays, { url: normalizedUrl, scope: 'both' }])
 106      setHasChange(true)
 107      return null
 108    }
 109  
 110    return (
 111      <div className="space-y-4">
 112        <div className="text-xs text-muted-foreground space-y-1">
 113          <div>{t('read relays description')}</div>
 114          <div>{t('write relays description')}</div>
 115          <div>{t('read & write relays notice')}</div>
 116        </div>
 117        <RelayCountWarning relays={relays} />
 118        <SaveButton mailboxRelays={relays} hasChange={hasChange} setHasChange={setHasChange} />
 119        <DndContext
 120          sensors={sensors}
 121          collisionDetection={closestCenter}
 122          onDragEnd={handleDragEnd}
 123          modifiers={[restrictToVerticalAxis, restrictToParentElement]}
 124        >
 125          <SortableContext items={relays.map((r) => r.url)} strategy={verticalListSortingStrategy}>
 126            <div className="space-y-2">
 127              {relays.map((relay) => (
 128                <MailboxRelay
 129                  key={relay.url}
 130                  mailboxRelay={relay}
 131                  changeMailboxRelayScope={changeMailboxRelayScope}
 132                  removeMailboxRelay={removeMailboxRelay}
 133                />
 134              ))}
 135            </div>
 136          </SortableContext>
 137        </DndContext>
 138        <NewMailboxRelayInput saveNewMailboxRelay={saveNewMailboxRelay} />
 139      </div>
 140    )
 141  }
 142