import classNames from 'classnames'; import React from 'react'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { defineMessages, injectIntl, useIntl, FormattedMessage, IntlShape } from 'react-intl'; import { spring } from 'react-motion'; import { openModal } from 'soapbox/actions/modals'; import { vote, fetchPoll } from 'soapbox/actions/polls'; import Icon from 'soapbox/components/icon'; import { Text } from 'soapbox/components/ui'; import Motion from 'soapbox/features/ui/util/optional_motion'; import RelativeTimestamp from './relative_timestamp'; import type { Poll as PollEntity, PollOption as PollOptionEntity } from 'soapbox/types/entities'; const messages = defineMessages({ closed: { id: 'poll.closed', defaultMessage: 'Closed' }, voted: { id: 'poll.voted', defaultMessage: 'You voted for this answer' }, votes: { id: 'poll.votes', defaultMessage: '{votes, plural, one {# vote} other {# votes}}' }, }); interface IPollPercentageBar { percent: number, leading: boolean, } const PollPercentageBar: React.FC = ({ percent, leading }): JSX.Element => { return ( {({ width }) =>( )} ); }; interface IPollOptionText extends IPollOption { percent: number, } const PollOptionText: React.FC = ({ poll, option, index, active, disabled, percent, showResults, onToggle }) => { const intl = useIntl(); const voted = poll.own_votes?.includes(index); const handleOptionChange = (): void => { onToggle(index); }; const handleOptionKeyPress = (e: React.KeyboardEvent): void => { if (e.key === 'Enter' || e.key === ' ') { onToggle(index); e.stopPropagation(); e.preventDefault(); } }; return ( ); }; interface IPollOption { poll: PollEntity, option: PollOptionEntity, index: number, showResults?: boolean, disabled?: boolean, active: boolean, onToggle: (value: number) => void, } const PollOption: React.FC = (props): JSX.Element | null => { const { poll, option, showResults } = props; if (!poll) return null; const percent = poll.votes_count === 0 ? 0 : (option.votes_count / poll.votes_count) * 100; const leading = poll.options.filterNot(other => other.title === option.title).every(other => option.votes_count >= other.votes_count); return (
  • {showResults && ( )}
  • ); }; interface IPoll { poll?: PollEntity, intl: IntlShape, dispatch?: Function, disabled?: boolean, me?: string | null | false | undefined, status?: string, } interface IPollState { selected: Record, } class Poll extends ImmutablePureComponent { state = { selected: {} as Record, }; toggleOption = (value: number) => { const { me, poll } = this.props; if (me) { if (poll?.multiple) { const tmp = { ...this.state.selected }; if (tmp[value]) { delete tmp[value]; } else { tmp[value] = true; } this.setState({ selected: tmp }); } else { const tmp: Record = {}; tmp[value] = true; this.setState({ selected: tmp }); } } else { this.openUnauthorizedModal(); } } handleVote = () => { const { disabled, dispatch, poll } = this.props; if (disabled || !dispatch || !poll) return; dispatch(vote(poll.id, Object.keys(this.state.selected))); }; openUnauthorizedModal = () => { const { dispatch, status } = this.props; if (!dispatch) return; dispatch(openModal('UNAUTHORIZED', { action: 'POLL_VOTE', ap_id: status, })); } handleRefresh = () => { const { disabled, dispatch, poll } = this.props; if (disabled || !poll || !dispatch) return; dispatch(fetchPoll(poll.id)); }; render() { const { poll, intl } = this.props; if (!poll) { return null; } const timeRemaining = poll.expired ? intl.formatMessage(messages.closed) : ; const showResults = poll.voted || poll.expired; const disabled = this.props.disabled || Object.entries(this.state.selected).every(item => !item); return (
      {poll.options.map((option, i) => ( ))}
    {!showResults && } {showResults && !this.props.disabled && ( · )} {poll.expires_at && · {timeRemaining}}
    ); } } export default injectIntl(Poll);