"use client"; import { EventTypes } from "@/_utils/types"; import Image from "next/image"; import { useEffect, useState } from "react"; import LoadingIndicator from "@/_components/LoadingIndicator"; import type { PresenceItem, RoomResponse, VoteResponse } from "@/_utils/types"; import { useUser } from "@clerk/nextjs"; import { useChannel, usePresence } from "ably/react"; import { isAdmin, isVIP, jsonToCsv } from "app/_utils/helpers"; import { env } from "env.mjs"; import { useParams } from "next/navigation"; import { FaShieldAlt } from "react-icons/fa"; import { GiStarFormation } from "react-icons/gi"; import { IoCheckmarkCircleOutline, IoCopyOutline, IoDownloadOutline, IoEyeOffOutline, IoEyeOutline, IoHourglassOutline, IoReloadOutline, IoSaveOutline, } from "react-icons/io5"; import { RiVipCrownFill } from "react-icons/ri"; import NoRoomUI from "./NoRoomUI"; const VoteUI = () => { const params = useParams(); const roomId = params?.id as string; const { user } = useUser(); const [storyNameText, setStoryNameText] = useState(""); const [roomScale, setRoomScale] = useState(""); const [copied, setCopied] = useState(false); const [roomFromDb, setRoomFromDb] = useState(); const [votesFromDb, setVotesFromDb] = useState(undefined); const getRoomHandler = () => { fetch(`/api/internal/room/${roomId}`, { cache: "no-cache", method: "GET", }) .then(async (response) => { const dbRoom = (await response.json()) as RoomResponse; setRoomFromDb(dbRoom); }) .catch(() => { setRoomFromDb(null); }); }; const getVotesHandler = async () => { const dbVotesResponse = await fetch(`/api/internal/room/${roomId}/votes`, { cache: "no-cache", method: "GET", }); const dbVotes = (await dbVotesResponse.json()) as VoteResponse; setVotesFromDb(dbVotes); }; useChannel( { channelName: `${env.NEXT_PUBLIC_APP_ENV}-${roomId}`, }, ({ name }: { name: string }) => { if (name === EventTypes.ROOM_UPDATE) { void getVotesHandler(); void getRoomHandler(); } else if (name === EventTypes.VOTE_UPDATE) { void getVotesHandler(); } } ); const { presenceData } = usePresence( `${env.NEXT_PUBLIC_APP_ENV}-${roomId}`, { name: (user?.fullName ?? user?.username) || "", image: user?.imageUrl || "", client_id: user?.id || "unknown", isAdmin: isAdmin(user?.publicMetadata), isVIP: isVIP(user?.publicMetadata), } ); // Init Story name useEffect(() => { if (roomFromDb) { setStoryNameText(roomFromDb.storyName || ""); setRoomScale(roomFromDb.scale || "ERROR"); } else { void getRoomHandler(); void getVotesHandler(); } }, [roomFromDb, roomId, user]); // Helper functions const getVoteForCurrentUser = () => { if (roomFromDb) { return ( votesFromDb && votesFromDb.find((vote) => vote.userId === user?.id) ); } else { return null; } }; const setVoteHandler = async (value: string) => { if (roomFromDb) { await fetch(`/api/internal/room/${roomId}/vote`, { cache: "no-cache", method: "PUT", body: JSON.stringify({ value, }), }); } }; const setRoomHandler = async ( visible: boolean, reset = false, log = false ) => { if (roomFromDb) { await fetch(`/api/internal/room/${roomId}`, { cache: "no-cache", method: "PUT", body: JSON.stringify({ name: storyNameText, visible, scale: roomScale, reset, log, }), }); } }; const downloadLogs = () => { if (roomFromDb && votesFromDb) { const jsonObject = roomFromDb?.logs .map((item) => { return { id: item.id, created_at: item.created_at, userId: item.userId, roomId: item.roomId, roomName: item.roomName, storyName: item.storyName, scale: item.scale, votes: item.votes, }; }) .concat({ id: "LATEST", created_at: new Date(), userId: roomFromDb.userId, roomId: roomFromDb.id, roomName: roomFromDb.roomName, storyName: storyNameText, scale: roomScale, votes: votesFromDb.map((vote) => { return { value: vote.value, }; }), }); jsonToCsv(jsonObject, `sp_${roomId}.csv`); } }; const copyRoomURLHandler = () => { navigator.clipboard .writeText(window.location.href) .then(() => { console.log(`Copied Room Link to Clipboard!`); setCopied(true); setTimeout(() => { setCopied(false); }, 2000); }) .catch(() => { console.log(`Error Copying Room Link to Clipboard!`); }); }; const voteString = ( visible: boolean, votes: typeof votesFromDb, presenceItem: PresenceItem ) => { const matchedVote = votes?.find( (vote) => vote.userId === presenceItem.client_id ); if (visible) { if (!!matchedVote) { return
{matchedVote.value}
; } else { return ; } } else if (!!matchedVote) { return ; } else { return ( ); } }; // Room is loading if (roomFromDb === undefined) { return ; // Room has been loaded } else if (roomFromDb) { return (
{roomFromDb.roomName}
ID:
{roomFromDb.id}
{roomFromDb && (

Story: {roomFromDb.storyName}

    {presenceData && presenceData .filter( (value, index, self) => index === self.findIndex( (presenceItem) => presenceItem.clientId === value.clientId ) ) .map((presenceItem) => { return (
  • {`${presenceItem.data.name}'s

    {presenceItem.data.name}{" "} {presenceItem.data.isAdmin && ( )}{" "} {presenceItem.data.isVIP && ( )}{" "} {presenceItem.clientId === roomFromDb.userId && ( )} {" : "}

    {roomFromDb && votesFromDb && voteString( roomFromDb.visible, votesFromDb, presenceItem.data )}
  • ); })}
{roomFromDb.scale?.split(",").map((scaleItem, index) => { return ( ); })}
)} {!!roomFromDb && (roomFromDb.userId === user?.id || isAdmin(user?.publicMetadata)) && ( <>

Room Settings

{ setRoomScale(event.target.value); }} /> { setStoryNameText(event.target.value); }} />
{votesFromDb && (roomFromDb.logs.length > 0 || votesFromDb.length > 0) && (
)}
)}
); // Room does not exist } else { return ; } }; export default VoteUI;