diff --git a/next.config.js b/next.config.js index d3cb7fbf4..53d06b744 100644 --- a/next.config.js +++ b/next.config.js @@ -40,18 +40,6 @@ const nextConfig = { source: "/", destination: "/home", }, - { - source: "/p/:urlId", - destination: "/poll?urlId=:urlId", - }, - { - source: "/admin/:urlId", - destination: "/poll?urlId=:urlId", - }, - { - source: "/verify/:urlId/code/:code", - destination: "/poll?urlId=:urlId&code=:code", - }, ]; }, sentry: { diff --git a/src/components/create-poll.tsx b/src/components/create-poll.tsx index 1f972d46a..3f9444ea7 100644 --- a/src/components/create-poll.tsx +++ b/src/components/create-poll.tsx @@ -18,7 +18,7 @@ import { UserDetailsData, UserDetailsForm, } from "./forms"; -import StandardLayout from "./standard-layout"; +import StandardLayout from "./layouts/standard-layout"; import Steps from "./steps"; import { useUser } from "./user-provider"; diff --git a/src/components/discussion/discussion.tsx b/src/components/discussion/discussion.tsx index 81563b8ff..6100b60c2 100644 --- a/src/components/discussion/discussion.tsx +++ b/src/components/discussion/discussion.tsx @@ -28,7 +28,7 @@ const Discussion: React.VoidFunctionComponent = () => { const { dayjs } = useDayjs(); const queryClient = trpc.useContext(); const { t } = useTranslation("app"); - const { poll } = usePoll(); + const { poll, admin } = usePoll(); const pollId = poll.id; @@ -93,7 +93,7 @@ const Discussion: React.VoidFunctionComponent = () => { {comments.map((comment) => { const canDelete = - poll.admin || session.ownsObject(comment) || isUnclaimed(comment); + admin || session.ownsObject(comment) || isUnclaimed(comment); return ( { const { t } = useTranslation("homepage"); diff --git a/src/components/page-layout.tsx b/src/components/layouts/page-layout.tsx similarity index 98% rename from src/components/page-layout.tsx rename to src/components/layouts/page-layout.tsx index 38d3cb6f7..d2b174571 100644 --- a/src/components/page-layout.tsx +++ b/src/components/layouts/page-layout.tsx @@ -8,8 +8,8 @@ import DotsVertical from "@/components/icons/dots-vertical.svg"; import Github from "@/components/icons/github.svg"; import Logo from "~/public/logo.svg"; +import Popover from "../popover"; import Footer from "./page-layout/footer"; -import Popover from "./popover"; export interface PageLayoutProps { children?: React.ReactNode; diff --git a/src/components/page-layout/footer.tsx b/src/components/layouts/page-layout/footer.tsx similarity index 98% rename from src/components/page-layout/footer.tsx rename to src/components/layouts/page-layout/footer.tsx index c22c7006a..a223a6df8 100644 --- a/src/components/page-layout/footer.tsx +++ b/src/components/layouts/page-layout/footer.tsx @@ -12,7 +12,7 @@ import Logo from "~/public/logo.svg"; import Sentry from "~/public/sentry.svg"; import Vercel from "~/public/vercel-logotype-dark.svg"; -import { LanguageSelect } from "../poll/language-selector"; +import { LanguageSelect } from "../../poll/language-selector"; const Footer: React.VoidFunctionComponent = () => { const { t } = useTranslation("common"); diff --git a/src/components/standard-layout.tsx b/src/components/layouts/standard-layout.tsx similarity index 93% rename from src/components/standard-layout.tsx rename to src/components/layouts/standard-layout.tsx index 174dac331..36220bf58 100644 --- a/src/components/standard-layout.tsx +++ b/src/components/layouts/standard-layout.tsx @@ -4,31 +4,32 @@ import Link from "next/link"; import { useTranslation } from "next-i18next"; import React from "react"; +import { LoginLink, useLoginModal } from "@/components/auth/login-modal"; +import Dropdown, { DropdownItem, DropdownProps } from "@/components/dropdown"; +import Adjustments from "@/components/icons/adjustments.svg"; +import Cash from "@/components/icons/cash.svg"; +import Discord from "@/components/icons/discord.svg"; +import DotsVertical from "@/components/icons/dots-vertical.svg"; +import Github from "@/components/icons/github.svg"; +import Login from "@/components/icons/login.svg"; +import Logout from "@/components/icons/logout.svg"; import Menu from "@/components/icons/menu.svg"; +import Pencil from "@/components/icons/pencil.svg"; +import Question from "@/components/icons/question-mark-circle.svg"; +import Spinner from "@/components/icons/spinner.svg"; +import Support from "@/components/icons/support.svg"; +import Twitter from "@/components/icons/twitter.svg"; import User from "@/components/icons/user.svg"; import UserCircle from "@/components/icons/user-circle.svg"; +import ModalProvider, { + useModalContext, +} from "@/components/modal/modal-provider"; +import Popover from "@/components/popover"; +import Preferences from "@/components/preferences"; +import { useUser } from "@/components/user-provider"; +import { DayjsProvider } from "@/utils/dayjs"; import Logo from "~/public/logo.svg"; -import { DayjsProvider } from "../utils/dayjs"; -import { LoginLink, useLoginModal } from "./auth/login-modal"; -import Dropdown, { DropdownItem, DropdownProps } from "./dropdown"; -import Adjustments from "./icons/adjustments.svg"; -import Cash from "./icons/cash.svg"; -import Discord from "./icons/discord.svg"; -import DotsVertical from "./icons/dots-vertical.svg"; -import Github from "./icons/github.svg"; -import Login from "./icons/login.svg"; -import Logout from "./icons/logout.svg"; -import Pencil from "./icons/pencil.svg"; -import Question from "./icons/question-mark-circle.svg"; -import Spinner from "./icons/spinner.svg"; -import Support from "./icons/support.svg"; -import Twitter from "./icons/twitter.svg"; -import ModalProvider, { useModalContext } from "./modal/modal-provider"; -import Popover from "./popover"; -import Preferences from "./preferences"; -import { useUser } from "./user-provider"; - const HomeLink = () => { return ( diff --git a/src/components/poll.tsx b/src/components/poll.tsx index ab5d71893..b20a8d58a 100644 --- a/src/components/poll.tsx +++ b/src/components/poll.tsx @@ -1,5 +1,4 @@ import { AnimatePresence, motion } from "framer-motion"; -import { NextPage } from "next"; import Head from "next/head"; import { useRouter } from "next/router"; import { useTranslation } from "next-i18next"; @@ -16,7 +15,7 @@ import DesktopPoll from "@/components/poll/desktop-poll"; import MobilePoll from "@/components/poll/mobile-poll"; import { preventWidows } from "@/utils/prevent-widows"; -import { trpc } from "../utils/trpc"; +import { trpc, trpcNext } from "../utils/trpc"; import { useParticipants } from "./participants-provider"; import ManagePoll from "./poll/manage-poll"; import { useUpdatePollMutation } from "./poll/mutations"; @@ -31,28 +30,62 @@ import { usePoll } from "./poll-context"; import Sharing from "./sharing"; import { useUser } from "./user-provider"; -const PollPage: NextPage = () => { - const { poll, urlId, admin } = usePoll(); - const { participants } = useParticipants(); - const router = useRouter(); +const checkIfWideScreen = () => window.innerWidth > 640; - useTouchBeacon(poll.id); +const useWideScreen = () => { + const [isWideScreen, setIsWideScreen] = React.useState(checkIfWideScreen); + React.useEffect(() => { + const listener = () => setIsWideScreen(checkIfWideScreen()); + + window.addEventListener("resize", listener); + + return () => { + window.removeEventListener("resize", listener); + }; + }, []); + + return isWideScreen; +}; + +export const AdminControls = () => { + const { poll, urlId } = usePoll(); const { t } = useTranslation("app"); + const isWideScreen = useWideScreen(); + + const router = useRouter(); + const [isSharingVisible, setSharingVisible] = React.useState( + !!router.query.sharing, + ); + + const queryClient = trpcNext.useContext(); + const session = useUser(); - const queryClient = trpc.useContext(); - const { mutate: updatePollMutation } = useUpdatePollMutation(); + React.useEffect(() => { + if (router.query.unsubscribe) { + updatePollMutation( + { urlId: urlId, notifications: false }, + { + onSuccess: () => { + toast.success(t("notificationsDisabled")); + posthog.capture("unsubscribed from notifications"); + }, + }, + ); + router.replace(`/admin/${router.query.urlId}`, undefined, { + shallow: true, + }); + } + }, [urlId, router, updatePollMutation, t]); + const verifyEmail = trpc.useMutation(["polls.verification.verify"], { onSuccess: () => { toast.success(t("pollHasBeenVerified")); - queryClient.setQueryData(["polls.get", { urlId, admin }], { - ...poll, - verified: true, - }); + queryClient.poll.invalidate(); session.refresh(); posthog.capture("verified email"); }, @@ -73,27 +106,74 @@ const PollPage: NextPage = () => { } }); - React.useEffect(() => { - if (router.query.unsubscribe) { - updatePollMutation( - { urlId: urlId, notifications: false }, - { - onSuccess: () => { - toast.success(t("notificationsDisabled")); - posthog.capture("unsubscribed from notifications"); - }, - }, - ); - router.replace(`/admin/${router.query.urlId}`, undefined, { - shallow: true, - }); - } - }, [urlId, router, updatePollMutation, t]); + return ( + <> +
+ + + +
+ + {isSharingVisible ? ( + + { + setSharingVisible(false); + }} + /> + + ) : null} + + {poll.verified === false ? ( +
+ +
+ ) : null} + + ); +}; + +export const Poll = (props: { children?: React.ReactNode }) => { + const { t } = useTranslation("app"); + const { poll } = usePoll(); + + useTouchBeacon(poll.id); + + const { participants } = useParticipants(); + const names = React.useMemo( + () => participants?.map(({ name }) => name) ?? [], + [participants], + ); const checkIfWideScreen = () => window.innerWidth > 640; - const [isWideScreen, setIsWideScreen] = React.useState(checkIfWideScreen); - React.useEffect(() => { const listener = () => setIsWideScreen(checkIfWideScreen()); @@ -104,16 +184,9 @@ const PollPage: NextPage = () => { }; }, []); + const [isWideScreen, setIsWideScreen] = React.useState(checkIfWideScreen); const PollComponent = isWideScreen ? DesktopPoll : MobilePoll; - const names = React.useMemo( - () => participants?.map(({ name }) => name) ?? [], - [participants], - ); - - const [isSharingVisible, setSharingVisible] = React.useState( - !!router.query.sharing, - ); return (
@@ -127,70 +200,7 @@ const PollPage: NextPage = () => { width: Math.max(768, poll.options.length * 95 + 200 + 160), }} > - {admin ? ( - <> -
- - - -
- - {isSharingVisible ? ( - - { - setSharingVisible(false); - }} - /> - - ) : null} - - {poll.verified === false ? ( -
- -
- ) : null} - - ) : null} - {!poll.admin && poll.adminUrlId ? ( -
-
- {t("pollOwnerNotice", { name: poll.user.name })} -
- - {t("goToAdmin")} → - -
- ) : null} + {props.children} {poll.closed ? (
@@ -255,7 +265,6 @@ const PollPage: NextPage = () => { {participants ? : null}
- {t("loading")}
}> @@ -264,5 +273,3 @@ const PollPage: NextPage = () => { ); }; - -export default PollPage; diff --git a/src/components/poll/desktop-poll/participant-row.tsx b/src/components/poll/desktop-poll/participant-row.tsx index c02309d8d..43dffd1e3 100644 --- a/src/components/poll/desktop-poll/participant-row.tsx +++ b/src/components/poll/desktop-poll/participant-row.tsx @@ -109,13 +109,13 @@ const ParticipantRow: React.VoidFunctionComponent = ({ const confirmDeleteParticipant = useDeleteParticipantModal(); const session = useUser(); - const { poll, getVote, options } = usePoll(); + const { poll, getVote, options, admin } = usePoll(); const isYou = session.user && session.ownsObject(participant) ? true : false; const isUnclaimed = !participant.userId; - const canEdit = !poll.closed && (poll.admin || isYou || isUnclaimed); + const canEdit = !poll.closed && (admin || isYou || isUnclaimed); if (editMode) { return ( diff --git a/src/components/poll/mobile-poll.tsx b/src/components/poll/mobile-poll.tsx index 7014adc32..5ccf6de87 100644 --- a/src/components/poll/mobile-poll.tsx +++ b/src/components/poll/mobile-poll.tsx @@ -39,6 +39,7 @@ const MobilePoll: React.VoidFunctionComponent = () => { const { poll, + admin, targetTimeZone, setTargetTimeZone, getParticipantById, @@ -63,7 +64,7 @@ const MobilePoll: React.VoidFunctionComponent = () => { const [selectedParticipantId, setSelectedParticipantId] = React.useState< string | undefined >(() => { - if (poll.admin) { + if (admin) { // don't select a particpant if admin return; } @@ -81,7 +82,7 @@ const MobilePoll: React.VoidFunctionComponent = () => { : undefined; const [isEditing, setIsEditing] = React.useState( - !userAlreadyVoted && !poll.closed && !poll.admin, + !userAlreadyVoted && !poll.closed && !admin, ); const formRef = React.useRef(null); @@ -214,7 +215,7 @@ const MobilePoll: React.VoidFunctionComponent = () => { disabled={ poll.closed || // if user is participant (not admin) - (!poll.admin && + (!admin && // and does not own this participant !session.ownsObject(selectedParticipant) && // and the participant has been claimed by a different user @@ -239,7 +240,7 @@ const MobilePoll: React.VoidFunctionComponent = () => { disabled={ poll.closed || // if user is participant (not admin) - (!poll.admin && + (!admin && // and does not own this participant !session.ownsObject(selectedParticipant) && // or the participant has been claimed by a different user diff --git a/src/components/poll/mutations.ts b/src/components/poll/mutations.ts index 19e8c86bd..009099579 100644 --- a/src/components/poll/mutations.ts +++ b/src/components/poll/mutations.ts @@ -1,7 +1,6 @@ import posthog from "posthog-js"; -import { trpc } from "../../utils/trpc"; -import { usePoll } from "../poll-context"; +import { trpc, trpcNext } from "../../utils/trpc"; import { ParticipantForm } from "./types"; export const normalizeVotes = ( @@ -83,11 +82,10 @@ export const useDeleteParticipantMutation = () => { }; export const useUpdatePollMutation = () => { - const { urlId, admin } = usePoll(); - const queryClient = trpc.useContext(); + const queryClient = trpcNext.useContext(); return trpc.useMutation(["polls.update"], { onSuccess: (data) => { - queryClient.setQueryData(["polls.get", { urlId, admin }], data); + queryClient.poll.invalidate(); posthog.capture("updated poll", { id: data.id, }); diff --git a/src/components/poll/poll-subheader.tsx b/src/components/poll/poll-subheader.tsx index aca68fd1a..469f51c84 100644 --- a/src/components/poll/poll-subheader.tsx +++ b/src/components/poll/poll-subheader.tsx @@ -7,7 +7,7 @@ import { usePoll } from "../poll-context"; import Tooltip from "../tooltip"; const PollSubheader: React.VoidFunctionComponent = () => { - const { poll } = usePoll(); + const { poll, admin } = usePoll(); const { t } = useTranslation("app"); const { dayjs } = useDayjs(); return ( @@ -23,7 +23,7 @@ const PollSubheader: React.VoidFunctionComponent = () => { b: , }} /> - {poll.legacy && poll.admin ? ( + {poll.legacy && admin ? ( { - const { query, asPath } = useRouter(); + const { query } = useRouter(); const { t } = useTranslation("app"); const urlId = query.urlId as string; - const [notFound, setNotFound] = React.useState(false); - const admin = /^\/admin/.test(asPath); - const pollQuery = trpc.useQuery(["polls.get", { urlId, admin }], { - onError: () => { - setNotFound(true); + const pollQuery = trpcNext.poll.getByAdminUrlId.useQuery( + { urlId }, + { + retry: false, }, - retry: false, - }); + ); const poll = pollQuery.data; @@ -35,23 +31,28 @@ const PollPageLoader: NextPage = () => { return ( - - + + + + ); } - if (notFound) { - return ; - } - return {t("loading")}; }; export const getServerSideProps: GetServerSideProps = withSessionSsr( withPageTranslations(["common", "app", "errors"]), + { + onPrefetch: async (ssg, ctx) => { + await ssg.poll.getByAdminUrlId.fetch({ + urlId: ctx.params?.urlId as string, + }); + }, + }, ); export default withSession(PollPageLoader); diff --git a/src/pages/p/[urlId].tsx b/src/pages/p/[urlId].tsx new file mode 100644 index 000000000..c032f00af --- /dev/null +++ b/src/pages/p/[urlId].tsx @@ -0,0 +1,58 @@ +import { GetServerSideProps, NextPage } from "next"; +import { useRouter } from "next/router"; +import { useTranslation } from "next-i18next"; +import React from "react"; + +import FullPageLoader from "@/components/full-page-loader"; +import { ParticipantsProvider } from "@/components/participants-provider"; +import { Poll } from "@/components/poll"; +import { PollContextProvider } from "@/components/poll-context"; +import { withSession } from "@/components/user-provider"; +import { withSessionSsr } from "@/utils/auth"; +import { trpcNext } from "@/utils/trpc"; +import { withPageTranslations } from "@/utils/with-page-translations"; + +import StandardLayout from "../../components/layouts/standard-layout"; +import ModalProvider from "../../components/modal/modal-provider"; +import { DayjsProvider } from "../../utils/dayjs"; + +const PollPageLoader: NextPage = () => { + const { query } = useRouter(); + const { t } = useTranslation("app"); + const urlId = query.urlId as string; + + const pollQuery = trpcNext.poll.getByParticipantUrlId.useQuery({ urlId }); + + const poll = pollQuery.data; + + if (poll) { + return ( + + + + + + + + + + + + ); + } + + return {t("loading")}; +}; + +export const getServerSideProps: GetServerSideProps = withSessionSsr( + withPageTranslations(["common", "app", "errors"]), + { + onPrefetch: async (ssg, ctx) => { + await ssg.poll.getByParticipantUrlId.fetch({ + urlId: ctx.params?.urlId as string, + }); + }, + }, +); + +export default withSession(PollPageLoader); diff --git a/src/pages/privacy-policy.tsx b/src/pages/privacy-policy.tsx index e037ca14e..79f4c4e01 100644 --- a/src/pages/privacy-policy.tsx +++ b/src/pages/privacy-policy.tsx @@ -1,7 +1,7 @@ import { GetStaticProps } from "next"; import { serverSideTranslations } from "next-i18next/serverSideTranslations"; -import PageLayout from "@/components/page-layout"; +import PageLayout from "@/components/layouts/page-layout"; const PrivacyPolicy = () => { return ( diff --git a/src/pages/profile.tsx b/src/pages/profile.tsx index c5fdbf61e..ce20468ee 100644 --- a/src/pages/profile.tsx +++ b/src/pages/profile.tsx @@ -2,8 +2,8 @@ import { NextPage } from "next"; import { withSessionSsr } from "@/utils/auth"; +import StandardLayout from "../components/layouts/standard-layout"; import { Profile } from "../components/profile"; -import StandardLayout from "../components/standard-layout"; import { withSession } from "../components/user-provider"; import { withPageTranslations } from "../utils/with-page-translations"; diff --git a/src/server/routers/_app.ts b/src/server/routers/_app.ts index 070974e59..6299d62aa 100644 --- a/src/server/routers/_app.ts +++ b/src/server/routers/_app.ts @@ -2,20 +2,21 @@ import { createRouter } from "../createRouter"; import { mergeRouters, router } from "../trpc"; import { auth } from "./auth"; import { login } from "./login"; -import { polls } from "./polls"; +import { legacyPolls, poll } from "./polls"; import { user } from "./user"; import { whoami } from "./whoami"; const legacyRouter = createRouter() .merge("user.", user) .merge(login) - .merge("polls.", polls); + .merge("polls.", legacyPolls); export const appRouter = mergeRouters( legacyRouter.interop(), router({ whoami, auth, + poll, }), ); diff --git a/src/server/routers/polls.ts b/src/server/routers/polls.ts index f985889d0..338ebe58a 100644 --- a/src/server/routers/polls.ts +++ b/src/server/routers/polls.ts @@ -11,6 +11,7 @@ import { createToken } from "../../utils/auth"; import { nanoid } from "../../utils/nanoid"; import { GetPollApiResponse } from "../../utils/trpc/types"; import { createRouter } from "../createRouter"; +import { publicProcedure, router } from "../trpc"; import { comments } from "./polls/comments"; import { demo } from "./polls/demo"; import { participants } from "./polls/participants"; @@ -24,8 +25,8 @@ const defaultSelectFields: { location: true; description: true; createdAt: true; - participantUrlId: true; adminUrlId: true; + participantUrlId: true; verified: true; closed: true; legacy: true; @@ -45,8 +46,8 @@ const defaultSelectFields: { location: true, description: true, createdAt: true, - participantUrlId: true, adminUrlId: true, + participantUrlId: true, verified: true, closed: true, legacy: true, @@ -76,7 +77,7 @@ const getPollIdFromAdminUrlId = async (urlId: string) => { return res.id; }; -export const polls = createRouter() +export const legacyPolls = createRouter() .merge("demo.", demo) .merge("participants.", participants) .merge("comments.", comments) @@ -189,37 +190,6 @@ export const polls = createRouter() return { urlId: adminUrlId }; }, }) - .query("get", { - input: z.object({ - urlId: z.string(), - admin: z.boolean(), - }), - resolve: async ({ input, ctx }): Promise => { - const poll = await prisma.poll.findFirst({ - select: defaultSelectFields, - where: input.admin - ? { - adminUrlId: input.urlId, - } - : { - participantUrlId: input.urlId, - }, - }); - - if (!poll) { - throw new TRPCError({ - code: "NOT_FOUND", - }); - } - - // We want to keep the adminUrlId in if the user is view - if (!input.admin && ctx.session.user?.id !== poll.user.id) { - return { ...poll, admin: input.admin, adminUrlId: "" }; - } - - return { ...poll, admin: input.admin }; - }, - }) .mutation("update", { input: z.object({ urlId: z.string(), @@ -270,7 +240,7 @@ export const polls = createRouter() }, }); - return { ...poll, admin: true }; + return { ...poll }; }, }) .mutation("delete", { @@ -297,3 +267,58 @@ export const polls = createRouter() }); }, }); + +export const poll = router({ + getByAdminUrlId: publicProcedure + .input( + z.object({ + urlId: z.string(), + }), + ) + .query(async ({ input }) => { + const res = await prisma.poll.findUnique({ + select: defaultSelectFields, + where: { + adminUrlId: input.urlId, + }, + rejectOnNotFound: false, + }); + + if (!res) { + throw new TRPCError({ + code: "NOT_FOUND", + message: "Poll not found", + }); + } + + return res; + }), + getByParticipantUrlId: publicProcedure + .input( + z.object({ + urlId: z.string(), + }), + ) + .query(async ({ input, ctx }) => { + const res = await prisma.poll.findUnique({ + select: defaultSelectFields, + where: { + participantUrlId: input.urlId, + }, + rejectOnNotFound: false, + }); + + if (!res) { + throw new TRPCError({ + code: "NOT_FOUND", + message: "Poll not found", + }); + } + + if (ctx.user.id === res.user.id) { + return res; + } else { + return { ...res, adminUrlId: "" }; + } + }), +}); diff --git a/src/utils/auth.ts b/src/utils/auth.ts index b04940114..be46fb9d9 100644 --- a/src/utils/auth.ts +++ b/src/utils/auth.ts @@ -5,7 +5,11 @@ import { unsealData, } from "iron-session"; import { withIronSessionApiRoute, withIronSessionSsr } from "iron-session/next"; -import { GetServerSideProps, NextApiHandler } from "next"; +import { + GetServerSideProps, + GetServerSidePropsContext, + NextApiHandler, +} from "next"; import { prisma } from "~/prisma/db"; @@ -72,23 +76,56 @@ export function withSessionRoute(handler: NextApiHandler) { }, sessionOptions); } -export function withSessionSsr(handler: GetServerSideProps) { - return withIronSessionSsr(async (context) => { - const { req } = context; +const compose = (...fns: GetServerSideProps[]): GetServerSideProps => { + return async (ctx) => { + const res = { props: {} }; + for (const getServerSideProps of fns) { + const fnRes = await getServerSideProps(ctx); - await setUser(req.session); + if ("props" in fnRes) { + res.props = { + ...res.props, + ...fnRes.props, + }; + } else { + return { notFound: true }; + } + } - const ssg = await createSSGHelperFromContext(context); - await ssg.whoami.get.prefetch(); + return res; + }; +}; - const res = await handler(context); +export function withSessionSsr( + handler: GetServerSideProps | GetServerSideProps[], + options?: { + onPrefetch?: ( + ssg: Awaited>, + ctx: GetServerSidePropsContext, + ) => Promise; + }, +): GetServerSideProps { + const composedHandler = Array.isArray(handler) + ? compose(...handler) + : handler; + return withIronSessionSsr(async (ctx) => { + const ssg = await createSSGHelperFromContext(ctx); + await ssg.whoami.get.prefetch(); // always prefetch user + if (options?.onPrefetch) { + try { + await options.onPrefetch(ssg, ctx); + } catch { + return { + notFound: true, + }; + } + } + const res = await composedHandler(ctx); if ("props" in res) { return { - ...res, props: { ...res.props, - user: req.session.user, trpcState: ssg.dehydrate(), }, }; @@ -156,11 +193,7 @@ export const mergeGuestsIntoUser = async ( export const getCurrentUser = async ( session: IronSession, ): Promise<{ isGuest: boolean; id: string }> => { - const user = session.user; + await setUser(session); - if (!user) { - throw new Error("Tried to get user but no user found."); - } - - return user; + return session.user; }; diff --git a/src/utils/trpc/types.ts b/src/utils/trpc/types.ts index 18ba130bb..36906c3e6 100644 --- a/src/utils/trpc/types.ts +++ b/src/utils/trpc/types.ts @@ -13,7 +13,6 @@ export type GetPollApiResponse = { participantUrlId: string; verified: boolean; closed: boolean; - admin: boolean; legacy: boolean; demo: boolean; notifications: boolean;