import { useUIState } from "src/useUIState";
import { BlockProps } from "./Block";
import { ButtonPrimary, ButtonSecondary } from "components/Buttons";
import { useCallback, useEffect, useState } from "react";
import { focusElement, Input } from "components/Input";
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 { create } from "zustand";
import { elementId } from "src/utils/elementId";
import { CheckTiny } from "components/Icons/CheckTiny";
import { CloseTiny } from "components/Icons/CloseTiny";
import { PublicationPollBlock } from "./PublicationPollBlock";
export let usePollBlockUIState = create(
() =>
({}) as {
[entity: string]: { state: "editing" | "voting" | "results" } | undefined;
},
);
export const PollBlock = (props: BlockProps) => {
let { data: pub } = useLeafletPublicationData();
if (!pub) return ;
return ;
};
export const LeafletPollBlock = (props: BlockProps) => {
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 (
);
};