2026-05-15 13:37:03 -04:00
|
|
|
import React, { useState } from 'react';
|
2026-05-15 00:47:21 -04:00
|
|
|
import { Box, Text } from 'folds';
|
2026-05-15 13:37:03 -04:00
|
|
|
import { useMatrixClient } from '../../../hooks/useMatrixClient';
|
2026-05-15 00:47:21 -04:00
|
|
|
|
|
|
|
|
type PollTextValue = Array<{ body: string }> | string;
|
|
|
|
|
|
|
|
|
|
function extractText(val: PollTextValue | undefined): string {
|
|
|
|
|
if (!val) return '';
|
|
|
|
|
if (typeof val === 'string') return val;
|
|
|
|
|
return val[0]?.body ?? '';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type PollAnswer = {
|
|
|
|
|
'm.id'?: string;
|
|
|
|
|
id?: string;
|
|
|
|
|
'm.text'?: PollTextValue;
|
|
|
|
|
'org.matrix.msc3381.poll.answer'?: { body: string };
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
type PollData = {
|
|
|
|
|
question?: { body?: string; 'm.text'?: PollTextValue };
|
|
|
|
|
answers?: PollAnswer[];
|
|
|
|
|
};
|
|
|
|
|
|
2026-05-15 13:37:03 -04:00
|
|
|
export function PollContent({
|
|
|
|
|
content,
|
|
|
|
|
roomId,
|
|
|
|
|
eventId,
|
|
|
|
|
}: {
|
|
|
|
|
content: Record<string, unknown>;
|
|
|
|
|
roomId?: string;
|
|
|
|
|
eventId?: string;
|
|
|
|
|
}) {
|
|
|
|
|
const mx = useMatrixClient();
|
|
|
|
|
const [myVote, setMyVote] = useState<string | null>(null);
|
|
|
|
|
const isStable = !!content['m.poll'];
|
|
|
|
|
|
2026-05-15 00:47:21 -04:00
|
|
|
const poll = (
|
|
|
|
|
content['m.poll'] ?? content['org.matrix.msc3381.poll.start']
|
|
|
|
|
) as PollData | undefined;
|
|
|
|
|
|
|
|
|
|
if (!poll) {
|
|
|
|
|
return (
|
|
|
|
|
<Text style={{ opacity: 0.6 }}>
|
|
|
|
|
<i>Poll (unreadable format)</i>
|
|
|
|
|
</Text>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const questionText =
|
|
|
|
|
extractText((poll.question as any)?.['m.text']) ||
|
|
|
|
|
(poll.question as any)?.body ||
|
|
|
|
|
'Untitled poll';
|
|
|
|
|
|
2026-05-15 13:37:03 -04:00
|
|
|
const canVote = !!roomId && !!eventId;
|
|
|
|
|
|
|
|
|
|
const handleVote = (answerId: string) => {
|
|
|
|
|
if (!roomId || !eventId) return;
|
|
|
|
|
setMyVote(answerId);
|
|
|
|
|
if (isStable) {
|
|
|
|
|
mx.sendEvent(roomId, 'm.poll.response' as any, {
|
|
|
|
|
'm.relates_to': { rel_type: 'm.reference', event_id: eventId },
|
|
|
|
|
'm.responses': [{ 'm.id': answerId }],
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
mx.sendEvent(roomId, 'org.matrix.msc3381.poll.response' as any, {
|
|
|
|
|
'm.relates_to': { rel_type: 'm.reference', event_id: eventId },
|
|
|
|
|
'org.matrix.msc3381.poll.response': { answers: [answerId] },
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2026-05-15 00:47:21 -04:00
|
|
|
return (
|
2026-05-15 13:37:03 -04:00
|
|
|
<Box
|
|
|
|
|
direction="Column"
|
|
|
|
|
gap="200"
|
|
|
|
|
style={{ maxWidth: '340px', paddingTop: '2px', paddingBottom: '4px' }}
|
|
|
|
|
>
|
2026-05-15 00:47:21 -04:00
|
|
|
<Box
|
|
|
|
|
alignItems="Center"
|
|
|
|
|
gap="100"
|
|
|
|
|
style={{
|
|
|
|
|
fontSize: '0.68rem',
|
|
|
|
|
fontWeight: 700,
|
|
|
|
|
letterSpacing: '0.12em',
|
|
|
|
|
textTransform: 'uppercase',
|
|
|
|
|
opacity: 0.55,
|
|
|
|
|
marginBottom: '2px',
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
◉ Poll
|
|
|
|
|
</Box>
|
|
|
|
|
<Text size="T400" style={{ fontWeight: 600 }}>
|
|
|
|
|
{questionText}
|
|
|
|
|
</Text>
|
|
|
|
|
<Box direction="Column" gap="100" style={{ marginTop: '2px' }}>
|
|
|
|
|
{(poll.answers ?? []).map((answer, i) => {
|
|
|
|
|
const text =
|
|
|
|
|
extractText((answer as any)['m.text']) ||
|
|
|
|
|
(answer as any)['org.matrix.msc3381.poll.answer']?.body ||
|
|
|
|
|
`Option ${i + 1}`;
|
|
|
|
|
const id = answer['m.id'] ?? answer.id ?? String(i);
|
2026-05-15 13:37:03 -04:00
|
|
|
const selected = myVote === id;
|
2026-05-15 00:47:21 -04:00
|
|
|
return (
|
2026-05-15 13:37:03 -04:00
|
|
|
<button
|
2026-05-15 00:47:21 -04:00
|
|
|
key={id}
|
2026-05-15 13:37:03 -04:00
|
|
|
type="button"
|
|
|
|
|
onClick={canVote ? () => handleVote(id) : undefined}
|
2026-05-15 00:47:21 -04:00
|
|
|
style={{
|
|
|
|
|
padding: '7px 12px',
|
|
|
|
|
borderRadius: '8px',
|
2026-05-15 13:37:03 -04:00
|
|
|
background: selected ? 'var(--bg-surface-active)' : 'var(--bg-surface-low)',
|
|
|
|
|
border: `1px solid ${selected ? 'var(--text-primary)' : 'var(--bg-surface-border)'}`,
|
2026-05-15 00:47:21 -04:00
|
|
|
fontSize: '0.88rem',
|
|
|
|
|
lineHeight: 1.4,
|
2026-05-15 13:37:03 -04:00
|
|
|
textAlign: 'left',
|
|
|
|
|
cursor: canVote ? 'pointer' : 'default',
|
|
|
|
|
color: 'var(--text-primary)',
|
|
|
|
|
display: 'flex',
|
|
|
|
|
alignItems: 'center',
|
|
|
|
|
gap: '8px',
|
|
|
|
|
width: '100%',
|
2026-05-15 00:47:21 -04:00
|
|
|
}}
|
|
|
|
|
>
|
2026-05-15 13:37:03 -04:00
|
|
|
<span style={{ flexGrow: 1 }}>{text}</span>
|
|
|
|
|
{selected && (
|
|
|
|
|
<span style={{ opacity: 0.8, fontSize: '1rem', flexShrink: 0 }}>✓</span>
|
|
|
|
|
)}
|
|
|
|
|
</button>
|
2026-05-15 00:47:21 -04:00
|
|
|
);
|
|
|
|
|
})}
|
|
|
|
|
</Box>
|
2026-05-15 13:37:03 -04:00
|
|
|
{canVote ? (
|
|
|
|
|
<Text size="T200" style={{ opacity: 0.5, marginTop: '2px' }}>
|
|
|
|
|
<i>{myVote ? 'Vote cast — click another to change' : 'Click an option to vote'}</i>
|
|
|
|
|
</Text>
|
|
|
|
|
) : (
|
|
|
|
|
<Text size="T200" style={{ opacity: 0.4, marginTop: '2px' }}>
|
|
|
|
|
<i>Open in Element to vote</i>
|
|
|
|
|
</Text>
|
|
|
|
|
)}
|
2026-05-15 00:47:21 -04:00
|
|
|
</Box>
|
|
|
|
|
);
|
|
|
|
|
}
|