import Button from "@/components/button"; import ErrorPage from "@/components/error-page"; import FullPageLoader from "@/components/full-page-loader"; import LocationMarker from "@/components/icons/location-marker.svg"; import LockClosed from "@/components/icons/lock-closed.svg"; import Share from "@/components/icons/share.svg"; import ManagePoll from "@/components/poll/manage-poll"; import MobilePoll from "@/components/poll/mobile-poll"; import { useUpdatePollMutation } from "@/components/poll/mutations"; import NotificationsToggle from "@/components/poll/notifications-toggle"; import PollSubheader from "@/components/poll/poll-subheader"; import { UserAvatarProvider } from "@/components/poll/user-avatar"; import Popover from "@/components/popover"; import Sharing from "@/components/sharing"; import StandardLayout from "@/components/standard-layout"; import { PollContext, usePoll } from "@/components/use-poll"; import { useUserName } from "@/components/user-name-context"; import axios from "axios"; import { GetServerSideProps, NextPage } from "next"; import { useTranslation } from "next-i18next"; import { serverSideTranslations } from "next-i18next/serverSideTranslations"; import { usePlausible } from "next-plausible"; import Head from "next/head"; import { useRouter } from "next/router"; import React from "react"; import { toast } from "react-hot-toast"; import { useMutation, useQuery, useQueryClient } from "react-query"; import { useMount } from "react-use"; import { preventWidows } from "utils/prevent-widows"; import { GetPollResponse } from "../api-client/get-poll"; import { getBrowserTimeZone } from "../utils/date-time-utils"; import Custom404 from "./404"; import Linkify from "react-linkify"; import TruncatedLinkify from "@/components/poll/truncated-linkify"; const Discussion = React.lazy(() => import("@/components/discussion")); const Poll = React.lazy(() => import("@/components/poll")); const PollPageLoader: NextPage = () => { const { query } = useRouter(); const { t } = useTranslation("app"); const urlId = query.urlId as string; const [didError, setDidError] = React.useState(false); const [didFailToConvertLegacyPoll, setDidFailToConvertLegacyPoll] = React.useState(false); const plausible = usePlausible(); const { data: poll } = useQuery( ["getPoll", urlId], async () => { try { const { data } = await axios.get(`/api/poll/${urlId}`); return data; } catch (error) { if (axios.isAxiosError(error) && error.response?.status === 404) { try { // check if poll exists from the legacy endpoint and convert it if it does const { data } = await axios.get( `/api/legacy/${urlId}`, ); plausible("Converted legacy event"); return data; } catch (err) { if (axios.isAxiosError(err) && err.response?.status === 500) { // failed to convert legacy poll setDidFailToConvertLegacyPoll(true); } throw err; } } else { throw error; } } }, { onError: () => { setDidError(true); }, retry: false, }, ); if (didFailToConvertLegacyPoll) { return ( ); } if (didError) { return ; } return !poll ? ( {t("loading")} ) : ( ); }; const PollPage: NextPage = () => { const poll = usePoll(); const router = useRouter(); useMount(() => { const path = poll.role === "admin" ? "admin" : "p"; if (!new RegExp(`^/${path}`).test(router.asPath)) { router.replace(`/${path}/${poll.urlId}`, undefined, { shallow: true }); } }); const [, setUserName] = useUserName(); const queryClient = useQueryClient(); const plausible = usePlausible(); const { mutate: updatePollMutation } = useUpdatePollMutation(); const { mutate: verifyEmail } = useMutation( async (verificationCode: string) => { await axios.post(`/api/poll/${poll.urlId}/verify`, { verificationCode, }); }, { onSuccess: () => { toast.success("Your poll has been verified"); router.replace(`/admin/${router.query.urlId}`, undefined, { shallow: true, }); queryClient.setQueryData(["getPoll", poll.urlId], { ...poll, verified: true, }); plausible("Verified email"); setUserName(poll.authorName); }, }, ); React.useEffect(() => { // TODO (Luke Vella) [2022-03-29]: stop looking for "verificationCode". We switched to // "code" for compatability with v1 and it's generally better since it's more concise const verificationCode = router.query.verificationCode ?? router.query.code; if (typeof verificationCode === "string") { verifyEmail(verificationCode); } }, [router, verifyEmail]); React.useEffect(() => { if (router.query.unsubscribe) { updatePollMutation( { notifications: false }, { onSuccess: () => { toast.success("Notifications have been disabled"); plausible("Unsubscribed from notifications"); }, }, ); router.replace(`/admin/${router.query.urlId}`, undefined, { shallow: true, }); } }, [plausible, router, updatePollMutation]); const [targetTimeZone, setTargetTimeZone] = React.useState(getBrowserTimeZone); const sortedOptions = React.useMemo( () => poll.options.sort((a, b) => a.value < b.value ? -1 : a.value > b.value ? 1 : 0, ), [poll.options], ); const checkIfWideScreen = () => window.innerWidth > 640; const [isWideScreen, setIsWideScreen] = React.useState(checkIfWideScreen); React.useEffect(() => { const listener = () => setIsWideScreen(checkIfWideScreen()); window.addEventListener("resize", listener); return () => { window.removeEventListener("resize", listener); }; }, []); const PollComponent = isWideScreen ? Poll : MobilePoll; let highScore = 1; // set to one because we don't want to highlight sortedOptions.forEach((option) => { if (option.votes.length > highScore) { highScore = option.votes.length; } }); const names = React.useMemo( () => poll.participants.map(({ name }) => name), [poll.participants], ); return (
{poll.title}

{preventWidows(poll.title)}

{poll.role === "admin" ? (
}> Share } placement={isWideScreen ? "bottom-end" : undefined} >
) : null}
{poll.description ? (
{preventWidows(poll.description)}
) : null} {poll.location ? (
{poll.location}
) : null}
{poll.closed ? (
This poll has been locked (voting is disabled)
) : null} Loading…
}>
); }; export const getServerSideProps: GetServerSideProps = async ({ locale = "en", }) => { try { return { props: { ...(await serverSideTranslations(locale, ["app"])), }, }; } catch { return { notFound: true }; } }; export default PollPageLoader;