import { useUIState } from "src/useUIState"; import { BlockProps, BlockLayout } from "../Block"; import { ButtonPrimary, ButtonSecondary } from "components/Buttons"; import { useCallback, useEffect, useState } from "react"; import { Input } from "components/Input"; import { focusElement } from "src/utils/focusElement"; import { Separator } from "components/Layout"; import { useEntitySetContext } from "components/EntitySetProvider"; import { theme } from "tailwind.config"; import { useEntity, useReplicache } from "src/replicache"; import { v7 } from "uuid"; import { useLeafletPublicationData, usePollData, } from "components/PageSWRDataProvider"; import { voteOnPoll } from "actions/pollActions"; import { elementId } from "src/utils/elementId"; import { CheckTiny } from "components/Icons/CheckTiny"; import { CloseTiny } from "components/Icons/CloseTiny"; import { PublicationPollBlock } from "../PublicationPollBlock"; import { usePollBlockUIState } from "./pollBlockState"; export const PollBlock = ( props: BlockProps & { areYouSure?: boolean; setAreYouSure?: (value: boolean) => void; }, ) => { let { data: pub } = useLeafletPublicationData(); if (!pub) return ; return ; }; export const LeafletPollBlock = ( props: BlockProps & { areYouSure?: boolean; setAreYouSure?: (value: boolean) => void; }, ) => { let isSelected = useUIState((s) => s.selectedBlocks.find((b) => b.value === props.entityID), ); let { permissions } = useEntitySetContext(); let { data: pollData } = usePollData(); let hasVoted = pollData?.voter_token && pollData.polls.find( (v) => v.poll_votes_on_entity.voter_token === pollData.voter_token && v.poll_votes_on_entity.poll_entity === props.entityID, ); let pollState = usePollBlockUIState((s) => s[props.entityID]?.state); if (!pollState) { if (hasVoted) pollState = "results"; else pollState = "voting"; } const setPollState = useCallback( (state: "editing" | "voting" | "results") => { usePollBlockUIState.setState((s) => ({ [props.entityID]: { state } })); }, [], ); let votes = pollData?.polls.filter( (v) => v.poll_votes_on_entity.poll_entity === props.entityID, ) || []; let totalVotes = votes.length; return ( {pollState === "editing" ? ( v.poll_votes_on_entity)} entityID={props.entityID} close={() => { if (hasVoted) setPollState("results"); else setPollState("voting"); }} /> ) : pollState === "results" ? ( ) : ( setPollState("results")} pollState={pollState} setPollState={setPollState} hasVoted={!!hasVoted} /> )} ); }; const PollVote = (props: { entityID: string; onSubmit: () => void; pollState: "editing" | "voting" | "results"; setPollState: (pollState: "editing" | "voting" | "results") => void; hasVoted: boolean; }) => { let { data, mutate } = usePollData(); let { permissions } = useEntitySetContext(); let pollOptions = useEntity(props.entityID, "poll/options"); let currentVotes = data?.voter_token ? data.polls .filter( (p) => p.poll_votes_on_entity.poll_entity === props.entityID && p.poll_votes_on_entity.voter_token === data.voter_token, ) .map((v) => v.poll_votes_on_entity.option_entity) : []; let [selectedPollOptions, setSelectedPollOptions] = useState(currentVotes); return ( <> {pollOptions.map((option, index) => ( setSelectedPollOptions((s) => s.includes(option.data.value) ? s.filter((s) => s !== option.data.value) : [...s, option.data.value], ) } entityID={option.data.value} /> ))}
{permissions.write && ( )} {permissions.write && }
{ await voteOnPoll(props.entityID, selectedPollOptions); mutate((oldState) => { if (!oldState || !oldState.voter_token) return; return { ...oldState, polls: [ ...oldState.polls.filter( (p) => !( p.poll_votes_on_entity.voter_token === oldState.voter_token && p.poll_votes_on_entity.poll_entity == props.entityID ), ), ...selectedPollOptions.map((option_entity) => ({ poll_votes_on_entity: { option_entity, entities: { set: "" }, poll_entity: props.entityID, voter_token: oldState.voter_token!, }, })), ], }; }); props.onSubmit(); }} disabled={ selectedPollOptions.length === 0 || (selectedPollOptions.length === currentVotes.length && selectedPollOptions.every((s) => currentVotes.includes(s))) } > Vote!
); }; const PollVoteButton = (props: { entityID: string; selected: boolean; toggleSelected: () => void; }) => { let optionName = useEntity(props.entityID, "poll-option/name")?.data.value; if (!optionName) return null; if (props.selected) return (
{ props.toggleSelected(); }} > {optionName}
); return (
{ props.toggleSelected(); }} > {optionName}
); }; const PollResults = (props: { entityID: string; pollState: "editing" | "voting" | "results"; setPollState: (pollState: "editing" | "voting" | "results") => void; hasVoted: boolean; }) => { let { data } = usePollData(); let { permissions } = useEntitySetContext(); let pollOptions = useEntity(props.entityID, "poll/options"); let pollData = data?.pollVotes.find((p) => p.poll_entity === props.entityID); let votesByOptions = pollData?.votesByOption || {}; let highestVotes = Math.max(...Object.values(votesByOptions)); let winningOptionEntities = Object.entries(votesByOptions).reduce( (winningEntities, [entity, votes]) => { if (votes === highestVotes) winningEntities.push(entity); return winningEntities; }, [], ); return ( <> {pollOptions.map((p) => ( ))}
{permissions.write && ( )} {permissions.write && }
); }; const PollResult = (props: { entityID: string; votes: number; totalVotes: number; winner: boolean; }) => { let optionName = useEntity(props.entityID, "poll-option/name")?.data.value; return (
{optionName}
{props.votes}
); }; const EditPoll = (props: { votes: { option_entity: string }[]; totalVotes: number; entityID: string; close: () => void; }) => { let pollOptions = useEntity(props.entityID, "poll/options"); let { rep } = useReplicache(); let permission_set = useEntitySetContext(); let [localPollOptionNames, setLocalPollOptionNames] = useState<{ [k: string]: string; }>({}); return ( <> {props.totalVotes > 0 && (
You can't edit options people already voted for!
)} {pollOptions.length === 0 && (
no options yet...
)} {pollOptions.map((p) => ( v.option_entity === p.data.value)} localNameState={localPollOptionNames[p.data.value]} setLocalNameState={setLocalPollOptionNames} /> ))}
{ // remove any poll options that have no name // look through the localPollOptionNames object and remove any options that have no name let emptyOptions = Object.entries(localPollOptionNames).filter( ([optionEntity, optionName]) => optionName === "", ); await Promise.all( emptyOptions.map( async ([entity]) => await rep?.mutate.removePollOption({ optionEntity: entity, }), ), ); await rep?.mutate.assertFact( Object.entries(localPollOptionNames) .filter(([, name]) => !!name) .map(([entity, name]) => ({ entity, attribute: "poll-option/name", data: { type: "string", value: name }, })), ); props.close(); }} > Save ); }; const EditPollOption = (props: { entityID: string; pollEntity: string; localNameState: string | undefined; setLocalNameState: ( s: (s: { [k: string]: string }) => { [k: string]: string }, ) => void; disabled: boolean; }) => { let { rep } = useReplicache(); let optionName = useEntity(props.entityID, "poll-option/name")?.data.value; useEffect(() => { props.setLocalNameState((s) => ({ ...s, [props.entityID]: optionName || "", })); }, [optionName, props.setLocalNameState, props.entityID]); return (
{ props.setLocalNameState((s) => ({ ...s, [props.entityID]: e.target.value, })); }} onKeyDown={(e) => { if (e.key === "Backspace" && !e.currentTarget.value) { e.preventDefault(); rep?.mutate.removePollOption({ optionEntity: props.entityID }); } }} />
); }; const PollStateToggle = (props: { setPollState: (pollState: "editing" | "voting" | "results") => void; hasVoted: boolean; pollState: "editing" | "voting" | "results"; }) => { return ( ); };