feat: polls (#451)
Co-authored-by: silberengel <silberengel7@protonmail.com>
This commit is contained in:
parent
636ceacdad
commit
b35e0cf850
35 changed files with 1240 additions and 130 deletions
145
src/components/PostEditor/PollEditor.tsx
Normal file
145
src/components/PostEditor/PollEditor.tsx
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { Switch } from '@/components/ui/switch'
|
||||
import { normalizeUrl } from '@/lib/url'
|
||||
import { TPollCreateData } from '@/types'
|
||||
import dayjs from 'dayjs'
|
||||
import { AlertCircle, Eraser, X } from 'lucide-react'
|
||||
import { Dispatch, SetStateAction, useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
export default function PollEditor({
|
||||
pollCreateData,
|
||||
setPollCreateData,
|
||||
setIsPoll
|
||||
}: {
|
||||
pollCreateData: TPollCreateData
|
||||
setPollCreateData: Dispatch<SetStateAction<TPollCreateData>>
|
||||
setIsPoll: Dispatch<SetStateAction<boolean>>
|
||||
}) {
|
||||
const { t } = useTranslation()
|
||||
const [isMultipleChoice, setIsMultipleChoice] = useState(pollCreateData.isMultipleChoice)
|
||||
const [options, setOptions] = useState(pollCreateData.options)
|
||||
const [endsAt, setEndsAt] = useState(
|
||||
pollCreateData.endsAt ? dayjs(pollCreateData.endsAt * 1000).format('YYYY-MM-DDTHH:mm') : ''
|
||||
)
|
||||
const [relayUrls, setRelayUrls] = useState(pollCreateData.relays.join(', '))
|
||||
|
||||
useEffect(() => {
|
||||
setPollCreateData({
|
||||
isMultipleChoice,
|
||||
options,
|
||||
endsAt: endsAt ? dayjs(endsAt).startOf('minute').unix() : undefined,
|
||||
relays: relayUrls
|
||||
? relayUrls
|
||||
.split(',')
|
||||
.map((url) => normalizeUrl(url.trim()))
|
||||
.filter(Boolean)
|
||||
: []
|
||||
})
|
||||
}, [isMultipleChoice, options, endsAt, relayUrls])
|
||||
|
||||
const handleAddOption = () => {
|
||||
setOptions([...options, ''])
|
||||
}
|
||||
|
||||
const handleRemoveOption = (index: number) => {
|
||||
if (options.length > 2) {
|
||||
setOptions(options.filter((_, i) => i !== index))
|
||||
}
|
||||
}
|
||||
|
||||
const handleOptionChange = (index: number, value: string) => {
|
||||
const newOptions = [...options]
|
||||
newOptions[index] = value
|
||||
setOptions(newOptions)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-4 border rounded-lg p-3">
|
||||
<div className="space-y-2">
|
||||
{options.map((option, index) => (
|
||||
<div key={index} className="flex gap-2">
|
||||
<Input
|
||||
value={option}
|
||||
onChange={(e) => handleOptionChange(index, e.target.value)}
|
||||
placeholder={t('Option {{number}}', { number: index + 1 })}
|
||||
/>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost-destructive"
|
||||
size="icon"
|
||||
onClick={() => handleRemoveOption(index)}
|
||||
disabled={options.length <= 2}
|
||||
>
|
||||
<X />
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
<Button type="button" variant="outline" onClick={handleAddOption}>
|
||||
{t('Add Option')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-2">
|
||||
<Label htmlFor="multiple-choice">{t('Allow multiple choices')}</Label>
|
||||
<Switch
|
||||
id="multiple-choice"
|
||||
checked={isMultipleChoice}
|
||||
onCheckedChange={setIsMultipleChoice}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="ends-at">{t('End Date (optional)')}</Label>
|
||||
<div className="flex items-center gap-2">
|
||||
<Input
|
||||
id="ends-at"
|
||||
type="datetime-local"
|
||||
value={endsAt}
|
||||
onChange={(e) => setEndsAt(e.target.value)}
|
||||
/>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost-destructive"
|
||||
size="icon"
|
||||
onClick={() => setEndsAt('')}
|
||||
disabled={!endsAt}
|
||||
title={t('Clear end date')}
|
||||
>
|
||||
<Eraser />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="relay-urls">{t('Relay URLs (optional, comma-separated)')}</Label>
|
||||
<Input
|
||||
id="relay-urls"
|
||||
value={relayUrls}
|
||||
onChange={(e) => setRelayUrls(e.target.value)}
|
||||
placeholder="wss://relay1.com, wss://relay2.com"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-2">
|
||||
<div className="p-3 rounded-lg text-sm bg-destructive [&_svg]:size-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<AlertCircle />
|
||||
<div className="font-medium">{t('This is a poll note.')}</div>
|
||||
</div>
|
||||
<div className="pl-6">
|
||||
{t(
|
||||
'Unlike regular notes, polls are not widely supported and may not display on other clients.'
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button variant="ghost-destructive" className="w-full" onClick={() => setIsPoll(false)}>
|
||||
{t('Remove poll')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue