projects/deliberation_at_scale/packages/frontend/hooks/useRoomActions.ts (103 lines of code) (raw):

import { useAppSelector } from "@/state/store"; import useRoom from "./useRoom"; import { useEffect, useMemo, useState } from "react"; import dayjs from "dayjs"; import { OutcomeType, useSendRoomMessageMutation } from "@/generated/graphql"; import { IconDefinition } from "@fortawesome/fontawesome-svg-core"; import { useLingui } from "@lingui/react"; import { msg } from "@lingui/macro"; import { faDoorOpen, faForward } from "@fortawesome/free-solid-svg-icons"; import useLocalizedPush from "./useLocalizedPush"; import useGiveOpinion from "./useGiveOpinion"; import useRealtimeBroadcast from "./useRealtimeBroadcast"; const NEXT_STATEMENT_PRESSED_EVENT_NAME = 'next-statement-pressed'; export interface RoomAction { id: string; icon?: IconDefinition; title: string; onClick: () => void; } export default function useRoomActions() { const { _ } = useLingui(); const { roomId, isRoomEnded, getOutcomeByType, lastOutcome, participantId, participants, refetchMessages } = useRoom(); const { getGroupOpinions } = useGiveOpinion({ subjects: [lastOutcome], participantId, }); const [sendMessage, { loading: isSendingMessage }] = useSendRoomMessageMutation(); const lastOpenedAssistantAt = useAppSelector((state) => state.room.lastOpenedAssistantAt); const [nextStatementPressed, setNextStatementPressed] = useState(false); const { push } = useLocalizedPush(); const { sendToChannel } = useRealtimeBroadcast({ channelId: roomId, eventHandlers: [{ event: NEXT_STATEMENT_PRESSED_EVENT_NAME, handler: () => { setNextStatementPressed(true); }, }], }); const actions: RoomAction[] = useMemo(() => { const newActions: RoomAction[] = []; const latestConsensusOutcome = getOutcomeByType(OutcomeType.Consensus); const shouldNotify = (createdAt?: string) => { return createdAt && dayjs(createdAt).isAfter(lastOpenedAssistantAt); }; const shouldNotifyConsensus = shouldNotify(latestConsensusOutcome?.created_at); const hasEveryoneVoted = getGroupOpinions(lastOutcome?.id).length === participants?.length; const canSkipOutcome = hasEveryoneVoted && !nextStatementPressed; if (shouldNotifyConsensus) { newActions.push({ id: 'vote-for-consensus', title: _(msg`Go to voting`), onClick: () => { push(`/room/${roomId}`); }, }); } if (canSkipOutcome) { newActions.push({ id: 'can-skip-outcome', icon: faForward, title: _(msg`Next statement`), onClick: async () => { if (isSendingMessage) { return; } setNextStatementPressed(true); await Promise.allSettled([ sendToChannel(NEXT_STATEMENT_PRESSED_EVENT_NAME, { participantId, }), sendMessage({ variables: { roomId, content: _(msg`I would like to move on to the next statement.`), participantId, tags: 'next-statement', }, }), ]); // TMP: temporary until Realtime has better performance refetchMessages(); }, }); } if (isRoomEnded) { newActions.push({ id: 'end-room', icon: faDoorOpen, title: _(msg`Leave conversation`), onClick: () => { push(`/evaluate/${roomId}`); }, }); } return newActions; }, [getOutcomeByType, getGroupOpinions, lastOutcome?.id, participants?.length, nextStatementPressed, isRoomEnded, lastOpenedAssistantAt, _, push, roomId, isSendingMessage, sendToChannel, participantId, sendMessage, refetchMessages]); // allow next statement again useEffect(() => { setNextStatementPressed(false); }, [lastOutcome]); return { actions, }; }