Files
cinny/src/app/features/room/PollCreator.tsx
T

282 lines
8.7 KiB
TypeScript
Raw Normal View History

import React, { useState } from 'react';
import { Room } from 'matrix-js-sdk';
import { Box, Icon, IconButton, Icons, Text, config } from 'folds';
import { useMatrixClient } from '../../hooks/useMatrixClient';
interface PollCreatorProps {
roomId: string;
room: Room;
onClose: () => void;
}
export function PollCreator({ roomId, onClose }: PollCreatorProps) {
const mx = useMatrixClient();
const [question, setQuestion] = useState('');
const [options, setOptions] = useState<string[]>(['', '']);
const [maxSelections, setMaxSelections] = useState<number>(1);
const [submitting, setSubmitting] = useState(false);
const [error, setError] = useState<string | null>(null);
const handleOptionChange = (index: number, value: string) => {
setOptions((prev) => {
const next = [...prev];
next[index] = value;
return next;
});
};
const handleAddOption = () => {
if (options.length >= 10) return;
setOptions((prev) => [...prev, '']);
};
const handleRemoveOption = (index: number) => {
if (options.length <= 2) return;
setOptions((prev) => prev.filter((_, i) => i !== index));
};
const handleSubmit = async () => {
const trimmedQuestion = question.trim();
if (!trimmedQuestion) {
setError('Please enter a question.');
return;
}
const filledOptions = options.map((o) => o.trim()).filter((o) => o.length > 0);
if (filledOptions.length < 2) {
setError('Please provide at least 2 answer options.');
return;
}
setError(null);
setSubmitting(true);
try {
await mx.sendEvent(roomId, 'm.poll.start' as any, {
'm.poll': {
question: { 'm.text': trimmedQuestion },
answers: filledOptions.map((o, i) => ({ 'm.id': `${i}`, 'm.text': o })),
max_selections: maxSelections,
kind: 'm.poll.undisclosed',
},
body: trimmedQuestion,
msgtype: 'm.text',
});
onClose();
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to send poll.');
setSubmitting(false);
}
};
return (
<div
style={{
position: 'fixed',
inset: 0,
zIndex: 1000,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
background: 'rgba(0,0,0,0.5)',
}}
onClick={(e) => {
if (e.target === e.currentTarget) onClose();
}}
>
<div
style={{
background: 'var(--bg-surface)',
borderRadius: config.radii.R400,
padding: config.space.S500,
width: '100%',
maxWidth: '420px',
display: 'flex',
flexDirection: 'column',
gap: config.space.S300,
boxShadow: '0 8px 32px rgba(0,0,0,0.24)',
}}
>
<Box direction="Row" alignItems="Center" justifyContent="SpaceBetween">
<Text size="H4">Create Poll</Text>
<IconButton
size="300"
radii="300"
variant="SurfaceVariant"
onClick={onClose}
aria-label="Close poll creator"
>
<Icon src={Icons.Cross} size="100" />
</IconButton>
</Box>
<div style={{ display: 'flex', flexDirection: 'column', gap: config.space.S100 }}>
<Text size="L400">Question</Text>
<input
style={{
background: 'var(--bg-surface-low)',
border: '1px solid var(--bg-surface-border)',
borderRadius: config.radii.R300,
padding: `${config.space.S200} ${config.space.S300}`,
color: 'var(--tc-surface-high)',
fontSize: '14px',
outline: 'none',
width: '100%',
boxSizing: 'border-box',
}}
type="text"
placeholder="Ask a question..."
value={question}
onChange={(e) => setQuestion(e.target.value)}
autoFocus
/>
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: config.space.S100 }}>
<Text size="L400">Options</Text>
{options.map((opt, index) => (
<div
key={index}
style={{
display: 'flex',
alignItems: 'center',
gap: config.space.S100,
}}
>
<input
style={{
flex: 1,
background: 'var(--bg-surface-low)',
border: '1px solid var(--bg-surface-border)',
borderRadius: config.radii.R300,
padding: `${config.space.S200} ${config.space.S300}`,
color: 'var(--tc-surface-high)',
fontSize: '14px',
outline: 'none',
boxSizing: 'border-box',
}}
type="text"
placeholder={`Option ${index + 1}`}
value={opt}
onChange={(e) => handleOptionChange(index, e.target.value)}
/>
<IconButton
size="300"
radii="300"
variant="SurfaceVariant"
onClick={() => handleRemoveOption(index)}
disabled={options.length <= 2}
aria-label={`Remove option ${index + 1}`}
>
<Icon src={Icons.Cross} size="100" />
</IconButton>
</div>
))}
{options.length < 10 && (
<button
type="button"
onClick={handleAddOption}
style={{
display: 'flex',
alignItems: 'center',
gap: config.space.S100,
background: 'none',
border: 'none',
cursor: 'pointer',
color: 'var(--tc-surface-low)',
fontSize: '13px',
padding: `${config.space.S100} 0`,
alignSelf: 'flex-start',
}}
>
<Icon src={Icons.Plus} size="100" />
<span>Add Option</span>
</button>
)}
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: config.space.S100 }}>
<Text size="L400">Selection Type</Text>
<div style={{ display: 'flex', gap: config.space.S200 }}>
<label
style={{
display: 'flex',
alignItems: 'center',
gap: config.space.S100,
cursor: 'pointer',
fontSize: '14px',
color: 'var(--tc-surface-high)',
}}
>
<input
type="radio"
name="pollType"
checked={maxSelections === 1}
onChange={() => setMaxSelections(1)}
/>
Single choice
</label>
<label
style={{
display: 'flex',
alignItems: 'center',
gap: config.space.S100,
cursor: 'pointer',
fontSize: '14px',
color: 'var(--tc-surface-high)',
}}
>
<input
type="radio"
name="pollType"
checked={maxSelections !== 1}
onChange={() => setMaxSelections(options.length)}
/>
Multiple choice
</label>
</div>
</div>
{error && (
<Text size="T200" style={{ color: 'var(--tc-danger-normal)' }}>
{error}
</Text>
)}
<Box direction="Row" justifyContent="End" gap="200">
<button
type="button"
onClick={onClose}
style={{
background: 'var(--bg-surface-low)',
border: '1px solid var(--bg-surface-border)',
borderRadius: config.radii.R300,
padding: `${config.space.S200} ${config.space.S400}`,
cursor: 'pointer',
color: 'var(--tc-surface-high)',
fontSize: '14px',
}}
>
Cancel
</button>
<button
type="button"
onClick={handleSubmit}
disabled={submitting}
style={{
background: 'var(--bg-primary-main)',
border: 'none',
borderRadius: config.radii.R300,
padding: `${config.space.S200} ${config.space.S400}`,
cursor: submitting ? 'not-allowed' : 'pointer',
color: 'var(--tc-primary-on-primary)',
fontSize: '14px',
opacity: submitting ? 0.7 : 1,
}}
>
{submitting ? 'Creating...' : 'Create Poll'}
</button>
</Box>
</div>
</div>
);
}