PollEditor.tsx raw

   1  import { Button } from '@/components/ui/button'
   2  import { Input } from '@/components/ui/input'
   3  import { Label } from '@/components/ui/label'
   4  import { Switch } from '@/components/ui/switch'
   5  import { normalizeUrl } from '@/lib/url'
   6  import { TPollCreateData } from '@/types'
   7  import dayjs from 'dayjs'
   8  import { Eraser, X } from 'lucide-react'
   9  import { Dispatch, SetStateAction, useEffect, useState } from 'react'
  10  import { useTranslation } from 'react-i18next'
  11  import InfoCard from '../InfoCard'
  12  
  13  export default function PollEditor({
  14    pollCreateData,
  15    setPollCreateData,
  16    setIsPoll
  17  }: {
  18    pollCreateData: TPollCreateData
  19    setPollCreateData: Dispatch<SetStateAction<TPollCreateData>>
  20    setIsPoll: Dispatch<SetStateAction<boolean>>
  21  }) {
  22    const { t } = useTranslation()
  23    const [isMultipleChoice, setIsMultipleChoice] = useState(pollCreateData.isMultipleChoice)
  24    const [options, setOptions] = useState(pollCreateData.options)
  25    const [endsAt, setEndsAt] = useState(
  26      pollCreateData.endsAt ? dayjs(pollCreateData.endsAt * 1000).format('YYYY-MM-DDTHH:mm') : ''
  27    )
  28    const [relayUrls, setRelayUrls] = useState(pollCreateData.relays.join(', '))
  29  
  30    useEffect(() => {
  31      setPollCreateData({
  32        isMultipleChoice,
  33        options,
  34        endsAt: endsAt ? dayjs(endsAt).startOf('minute').unix() : undefined,
  35        relays: relayUrls
  36          ? relayUrls
  37              .split(',')
  38              .map((url) => normalizeUrl(url.trim()))
  39              .filter(Boolean)
  40          : []
  41      })
  42    }, [isMultipleChoice, options, endsAt, relayUrls])
  43  
  44    const handleAddOption = () => {
  45      setOptions([...options, ''])
  46    }
  47  
  48    const handleRemoveOption = (index: number) => {
  49      if (options.length > 2) {
  50        setOptions(options.filter((_, i) => i !== index))
  51      }
  52    }
  53  
  54    const handleOptionChange = (index: number, value: string) => {
  55      const newOptions = [...options]
  56      newOptions[index] = value
  57      setOptions(newOptions)
  58    }
  59  
  60    return (
  61      <div className="space-y-4 border rounded-lg p-3">
  62        <div className="space-y-2">
  63          {options.map((option, index) => (
  64            <div key={index} className="flex gap-2">
  65              <Input
  66                value={option}
  67                onChange={(e) => handleOptionChange(index, e.target.value)}
  68                placeholder={t('Option {{number}}', { number: index + 1 })}
  69              />
  70              <Button
  71                type="button"
  72                variant="ghost-destructive"
  73                size="icon"
  74                onClick={() => handleRemoveOption(index)}
  75                disabled={options.length <= 2}
  76              >
  77                <X />
  78              </Button>
  79            </div>
  80          ))}
  81          <Button type="button" variant="outline" onClick={handleAddOption}>
  82            {t('Add Option')}
  83          </Button>
  84        </div>
  85  
  86        <div className="flex items-center space-x-2">
  87          <Label htmlFor="multiple-choice">{t('Allow multiple choices')}</Label>
  88          <Switch
  89            id="multiple-choice"
  90            checked={isMultipleChoice}
  91            onCheckedChange={setIsMultipleChoice}
  92          />
  93        </div>
  94  
  95        <div className="grid gap-2">
  96          <Label htmlFor="ends-at">{t('End Date (optional)')}</Label>
  97          <div className="flex items-center gap-2">
  98            <Input
  99              id="ends-at"
 100              type="datetime-local"
 101              value={endsAt}
 102              onChange={(e) => setEndsAt(e.target.value)}
 103            />
 104            <Button
 105              type="button"
 106              variant="ghost-destructive"
 107              size="icon"
 108              onClick={() => setEndsAt('')}
 109              disabled={!endsAt}
 110              title={t('Clear end date')}
 111            >
 112              <Eraser />
 113            </Button>
 114          </div>
 115        </div>
 116  
 117        <div className="grid gap-2">
 118          <Label htmlFor="relay-urls">{t('Relay URLs (optional, comma-separated)')}</Label>
 119          <Input
 120            id="relay-urls"
 121            value={relayUrls}
 122            onChange={(e) => setRelayUrls(e.target.value)}
 123            placeholder="wss://relay1.com, wss://relay2.com"
 124          />
 125        </div>
 126  
 127        <div className="grid gap-2">
 128          <InfoCard
 129            variant="alert"
 130            title={t('This is a poll note.')}
 131            content={t(
 132              'Unlike regular notes, polls are not widely supported and may not display on other clients.'
 133            )}
 134          />
 135  
 136          <Button variant="ghost-destructive" className="w-full" onClick={() => setIsPoll(false)}>
 137            {t('Remove poll')}
 138          </Button>
 139        </div>
 140      </div>
 141    )
 142  }
 143