mirror of
https://github.com/lukevella/rallly.git
synced 2025-06-14 08:31:53 +02:00
Refactor admin and participant page (#464)
This commit is contained in:
parent
875e48f1fe
commit
d397654de7
20 changed files with 348 additions and 236 deletions
|
@ -40,18 +40,6 @@ const nextConfig = {
|
||||||
source: "/",
|
source: "/",
|
||||||
destination: "/home",
|
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: {
|
sentry: {
|
||||||
|
|
|
@ -18,7 +18,7 @@ import {
|
||||||
UserDetailsData,
|
UserDetailsData,
|
||||||
UserDetailsForm,
|
UserDetailsForm,
|
||||||
} from "./forms";
|
} from "./forms";
|
||||||
import StandardLayout from "./standard-layout";
|
import StandardLayout from "./layouts/standard-layout";
|
||||||
import Steps from "./steps";
|
import Steps from "./steps";
|
||||||
import { useUser } from "./user-provider";
|
import { useUser } from "./user-provider";
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,7 @@ const Discussion: React.VoidFunctionComponent = () => {
|
||||||
const { dayjs } = useDayjs();
|
const { dayjs } = useDayjs();
|
||||||
const queryClient = trpc.useContext();
|
const queryClient = trpc.useContext();
|
||||||
const { t } = useTranslation("app");
|
const { t } = useTranslation("app");
|
||||||
const { poll } = usePoll();
|
const { poll, admin } = usePoll();
|
||||||
|
|
||||||
const pollId = poll.id;
|
const pollId = poll.id;
|
||||||
|
|
||||||
|
@ -93,7 +93,7 @@ const Discussion: React.VoidFunctionComponent = () => {
|
||||||
<AnimatePresence initial={false}>
|
<AnimatePresence initial={false}>
|
||||||
{comments.map((comment) => {
|
{comments.map((comment) => {
|
||||||
const canDelete =
|
const canDelete =
|
||||||
poll.admin || session.ownsObject(comment) || isUnclaimed(comment);
|
admin || session.ownsObject(comment) || isUnclaimed(comment);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<motion.div
|
<motion.div
|
||||||
|
|
|
@ -5,7 +5,7 @@ import React from "react";
|
||||||
import Bonus from "./home/bonus";
|
import Bonus from "./home/bonus";
|
||||||
import Features from "./home/features";
|
import Features from "./home/features";
|
||||||
import Hero from "./home/hero";
|
import Hero from "./home/hero";
|
||||||
import PageLayout from "./page-layout";
|
import PageLayout from "./layouts/page-layout";
|
||||||
|
|
||||||
const Home: React.VoidFunctionComponent = () => {
|
const Home: React.VoidFunctionComponent = () => {
|
||||||
const { t } = useTranslation("homepage");
|
const { t } = useTranslation("homepage");
|
||||||
|
|
|
@ -8,8 +8,8 @@ import DotsVertical from "@/components/icons/dots-vertical.svg";
|
||||||
import Github from "@/components/icons/github.svg";
|
import Github from "@/components/icons/github.svg";
|
||||||
import Logo from "~/public/logo.svg";
|
import Logo from "~/public/logo.svg";
|
||||||
|
|
||||||
|
import Popover from "../popover";
|
||||||
import Footer from "./page-layout/footer";
|
import Footer from "./page-layout/footer";
|
||||||
import Popover from "./popover";
|
|
||||||
|
|
||||||
export interface PageLayoutProps {
|
export interface PageLayoutProps {
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
|
@ -12,7 +12,7 @@ import Logo from "~/public/logo.svg";
|
||||||
import Sentry from "~/public/sentry.svg";
|
import Sentry from "~/public/sentry.svg";
|
||||||
import Vercel from "~/public/vercel-logotype-dark.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 Footer: React.VoidFunctionComponent = () => {
|
||||||
const { t } = useTranslation("common");
|
const { t } = useTranslation("common");
|
|
@ -4,31 +4,32 @@ import Link from "next/link";
|
||||||
import { useTranslation } from "next-i18next";
|
import { useTranslation } from "next-i18next";
|
||||||
import React from "react";
|
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 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 User from "@/components/icons/user.svg";
|
||||||
import UserCircle from "@/components/icons/user-circle.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 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 = () => {
|
const HomeLink = () => {
|
||||||
return (
|
return (
|
||||||
<Link href="/">
|
<Link href="/">
|
|
@ -1,5 +1,4 @@
|
||||||
import { AnimatePresence, motion } from "framer-motion";
|
import { AnimatePresence, motion } from "framer-motion";
|
||||||
import { NextPage } from "next";
|
|
||||||
import Head from "next/head";
|
import Head from "next/head";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { useTranslation } from "next-i18next";
|
import { useTranslation } from "next-i18next";
|
||||||
|
@ -16,7 +15,7 @@ import DesktopPoll from "@/components/poll/desktop-poll";
|
||||||
import MobilePoll from "@/components/poll/mobile-poll";
|
import MobilePoll from "@/components/poll/mobile-poll";
|
||||||
import { preventWidows } from "@/utils/prevent-widows";
|
import { preventWidows } from "@/utils/prevent-widows";
|
||||||
|
|
||||||
import { trpc } from "../utils/trpc";
|
import { trpc, trpcNext } from "../utils/trpc";
|
||||||
import { useParticipants } from "./participants-provider";
|
import { useParticipants } from "./participants-provider";
|
||||||
import ManagePoll from "./poll/manage-poll";
|
import ManagePoll from "./poll/manage-poll";
|
||||||
import { useUpdatePollMutation } from "./poll/mutations";
|
import { useUpdatePollMutation } from "./poll/mutations";
|
||||||
|
@ -31,28 +30,62 @@ import { usePoll } from "./poll-context";
|
||||||
import Sharing from "./sharing";
|
import Sharing from "./sharing";
|
||||||
import { useUser } from "./user-provider";
|
import { useUser } from "./user-provider";
|
||||||
|
|
||||||
const PollPage: NextPage = () => {
|
const checkIfWideScreen = () => window.innerWidth > 640;
|
||||||
const { poll, urlId, admin } = usePoll();
|
|
||||||
const { participants } = useParticipants();
|
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
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 { 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 session = useUser();
|
||||||
|
|
||||||
const queryClient = trpc.useContext();
|
|
||||||
|
|
||||||
const { mutate: updatePollMutation } = useUpdatePollMutation();
|
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"], {
|
const verifyEmail = trpc.useMutation(["polls.verification.verify"], {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
toast.success(t("pollHasBeenVerified"));
|
toast.success(t("pollHasBeenVerified"));
|
||||||
queryClient.setQueryData(["polls.get", { urlId, admin }], {
|
queryClient.poll.invalidate();
|
||||||
...poll,
|
|
||||||
verified: true,
|
|
||||||
});
|
|
||||||
session.refresh();
|
session.refresh();
|
||||||
posthog.capture("verified email");
|
posthog.capture("verified email");
|
||||||
},
|
},
|
||||||
|
@ -73,27 +106,74 @@ const PollPage: NextPage = () => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
React.useEffect(() => {
|
return (
|
||||||
if (router.query.unsubscribe) {
|
<>
|
||||||
updatePollMutation(
|
<div className="mb-4 flex space-x-2 px-4 md:justify-end md:px-0">
|
||||||
{ urlId: urlId, notifications: false },
|
<NotificationsToggle />
|
||||||
{
|
<ManagePoll placement={isWideScreen ? "bottom-end" : "bottom-start"} />
|
||||||
onSuccess: () => {
|
<Button
|
||||||
toast.success(t("notificationsDisabled"));
|
type="primary"
|
||||||
posthog.capture("unsubscribed from notifications");
|
icon={<Share />}
|
||||||
},
|
onClick={() => {
|
||||||
},
|
setSharingVisible((value) => !value);
|
||||||
);
|
}}
|
||||||
router.replace(`/admin/${router.query.urlId}`, undefined, {
|
>
|
||||||
shallow: true,
|
{t("share")}
|
||||||
});
|
</Button>
|
||||||
}
|
</div>
|
||||||
}, [urlId, router, updatePollMutation, t]);
|
<AnimatePresence initial={false}>
|
||||||
|
{isSharingVisible ? (
|
||||||
|
<motion.div
|
||||||
|
initial={{
|
||||||
|
opacity: 0,
|
||||||
|
scale: 0.8,
|
||||||
|
height: 0,
|
||||||
|
}}
|
||||||
|
animate={{
|
||||||
|
opacity: 1,
|
||||||
|
scale: 1,
|
||||||
|
height: "auto",
|
||||||
|
marginBottom: 16,
|
||||||
|
}}
|
||||||
|
exit={{
|
||||||
|
opacity: 0,
|
||||||
|
scale: 0.8,
|
||||||
|
height: 0,
|
||||||
|
marginBottom: 0,
|
||||||
|
}}
|
||||||
|
className="overflow-hidden"
|
||||||
|
>
|
||||||
|
<Sharing
|
||||||
|
onHide={() => {
|
||||||
|
setSharingVisible(false);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</motion.div>
|
||||||
|
) : null}
|
||||||
|
</AnimatePresence>
|
||||||
|
{poll.verified === false ? (
|
||||||
|
<div className="m-4 overflow-hidden rounded-lg border p-4 md:mx-0 md:mt-0">
|
||||||
|
<UnverifiedPollNotice />
|
||||||
|
</div>
|
||||||
|
) : 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 checkIfWideScreen = () => window.innerWidth > 640;
|
||||||
|
|
||||||
const [isWideScreen, setIsWideScreen] = React.useState(checkIfWideScreen);
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
const listener = () => setIsWideScreen(checkIfWideScreen());
|
const listener = () => setIsWideScreen(checkIfWideScreen());
|
||||||
|
|
||||||
|
@ -104,16 +184,9 @@ const PollPage: NextPage = () => {
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const [isWideScreen, setIsWideScreen] = React.useState(checkIfWideScreen);
|
||||||
const PollComponent = isWideScreen ? DesktopPoll : MobilePoll;
|
const PollComponent = isWideScreen ? DesktopPoll : MobilePoll;
|
||||||
|
|
||||||
const names = React.useMemo(
|
|
||||||
() => participants?.map(({ name }) => name) ?? [],
|
|
||||||
[participants],
|
|
||||||
);
|
|
||||||
|
|
||||||
const [isSharingVisible, setSharingVisible] = React.useState(
|
|
||||||
!!router.query.sharing,
|
|
||||||
);
|
|
||||||
return (
|
return (
|
||||||
<UserAvatarProvider seed={poll.id} names={names}>
|
<UserAvatarProvider seed={poll.id} names={names}>
|
||||||
<div className="relative max-w-full py-4 md:px-4">
|
<div className="relative max-w-full py-4 md:px-4">
|
||||||
|
@ -127,70 +200,7 @@ const PollPage: NextPage = () => {
|
||||||
width: Math.max(768, poll.options.length * 95 + 200 + 160),
|
width: Math.max(768, poll.options.length * 95 + 200 + 160),
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{admin ? (
|
{props.children}
|
||||||
<>
|
|
||||||
<div className="mb-4 flex space-x-2 px-4 md:justify-end md:px-0">
|
|
||||||
<NotificationsToggle />
|
|
||||||
<ManagePoll
|
|
||||||
placement={isWideScreen ? "bottom-end" : "bottom-start"}
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
type="primary"
|
|
||||||
icon={<Share />}
|
|
||||||
onClick={() => {
|
|
||||||
setSharingVisible((value) => !value);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t("share")}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<AnimatePresence initial={false}>
|
|
||||||
{isSharingVisible ? (
|
|
||||||
<motion.div
|
|
||||||
initial={{
|
|
||||||
opacity: 0,
|
|
||||||
scale: 0.8,
|
|
||||||
height: 0,
|
|
||||||
}}
|
|
||||||
animate={{
|
|
||||||
opacity: 1,
|
|
||||||
scale: 1,
|
|
||||||
height: "auto",
|
|
||||||
marginBottom: 16,
|
|
||||||
}}
|
|
||||||
exit={{
|
|
||||||
opacity: 0,
|
|
||||||
scale: 0.8,
|
|
||||||
height: 0,
|
|
||||||
marginBottom: 0,
|
|
||||||
}}
|
|
||||||
className="overflow-hidden"
|
|
||||||
>
|
|
||||||
<Sharing
|
|
||||||
onHide={() => {
|
|
||||||
setSharingVisible(false);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</motion.div>
|
|
||||||
) : null}
|
|
||||||
</AnimatePresence>
|
|
||||||
{poll.verified === false ? (
|
|
||||||
<div className="m-4 overflow-hidden rounded-lg border p-4 md:mx-0 md:mt-0">
|
|
||||||
<UnverifiedPollNotice />
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
</>
|
|
||||||
) : null}
|
|
||||||
{!poll.admin && poll.adminUrlId ? (
|
|
||||||
<div className="mb-4 items-center justify-between rounded-lg px-4 md:flex md:space-x-4 md:border md:p-2 md:pl-4">
|
|
||||||
<div className="mb-4 font-medium md:mb-0">
|
|
||||||
{t("pollOwnerNotice", { name: poll.user.name })}
|
|
||||||
</div>
|
|
||||||
<a href={`/admin/${poll.adminUrlId}`} className="btn-default">
|
|
||||||
{t("goToAdmin")} →
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
{poll.closed ? (
|
{poll.closed ? (
|
||||||
<div className="flex bg-sky-100 py-3 px-4 text-sky-700 md:mb-4 md:rounded-lg md:shadow-sm">
|
<div className="flex bg-sky-100 py-3 px-4 text-sky-700 md:mb-4 md:rounded-lg md:shadow-sm">
|
||||||
<div className="mr-2 rounded-md">
|
<div className="mr-2 rounded-md">
|
||||||
|
@ -255,7 +265,6 @@ const PollPage: NextPage = () => {
|
||||||
{participants ? <PollComponent /> : null}
|
{participants ? <PollComponent /> : null}
|
||||||
</React.Suspense>
|
</React.Suspense>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<React.Suspense fallback={<div className="p-4">{t("loading")}</div>}>
|
<React.Suspense fallback={<div className="p-4">{t("loading")}</div>}>
|
||||||
<Discussion />
|
<Discussion />
|
||||||
</React.Suspense>
|
</React.Suspense>
|
||||||
|
@ -264,5 +273,3 @@ const PollPage: NextPage = () => {
|
||||||
</UserAvatarProvider>
|
</UserAvatarProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default PollPage;
|
|
||||||
|
|
|
@ -109,13 +109,13 @@ const ParticipantRow: React.VoidFunctionComponent<ParticipantRowProps> = ({
|
||||||
const confirmDeleteParticipant = useDeleteParticipantModal();
|
const confirmDeleteParticipant = useDeleteParticipantModal();
|
||||||
|
|
||||||
const session = useUser();
|
const session = useUser();
|
||||||
const { poll, getVote, options } = usePoll();
|
const { poll, getVote, options, admin } = usePoll();
|
||||||
|
|
||||||
const isYou = session.user && session.ownsObject(participant) ? true : false;
|
const isYou = session.user && session.ownsObject(participant) ? true : false;
|
||||||
|
|
||||||
const isUnclaimed = !participant.userId;
|
const isUnclaimed = !participant.userId;
|
||||||
|
|
||||||
const canEdit = !poll.closed && (poll.admin || isYou || isUnclaimed);
|
const canEdit = !poll.closed && (admin || isYou || isUnclaimed);
|
||||||
|
|
||||||
if (editMode) {
|
if (editMode) {
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -39,6 +39,7 @@ const MobilePoll: React.VoidFunctionComponent = () => {
|
||||||
|
|
||||||
const {
|
const {
|
||||||
poll,
|
poll,
|
||||||
|
admin,
|
||||||
targetTimeZone,
|
targetTimeZone,
|
||||||
setTargetTimeZone,
|
setTargetTimeZone,
|
||||||
getParticipantById,
|
getParticipantById,
|
||||||
|
@ -63,7 +64,7 @@ const MobilePoll: React.VoidFunctionComponent = () => {
|
||||||
const [selectedParticipantId, setSelectedParticipantId] = React.useState<
|
const [selectedParticipantId, setSelectedParticipantId] = React.useState<
|
||||||
string | undefined
|
string | undefined
|
||||||
>(() => {
|
>(() => {
|
||||||
if (poll.admin) {
|
if (admin) {
|
||||||
// don't select a particpant if admin
|
// don't select a particpant if admin
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -81,7 +82,7 @@ const MobilePoll: React.VoidFunctionComponent = () => {
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
const [isEditing, setIsEditing] = React.useState(
|
const [isEditing, setIsEditing] = React.useState(
|
||||||
!userAlreadyVoted && !poll.closed && !poll.admin,
|
!userAlreadyVoted && !poll.closed && !admin,
|
||||||
);
|
);
|
||||||
|
|
||||||
const formRef = React.useRef<HTMLFormElement>(null);
|
const formRef = React.useRef<HTMLFormElement>(null);
|
||||||
|
@ -214,7 +215,7 @@ const MobilePoll: React.VoidFunctionComponent = () => {
|
||||||
disabled={
|
disabled={
|
||||||
poll.closed ||
|
poll.closed ||
|
||||||
// if user is participant (not admin)
|
// if user is participant (not admin)
|
||||||
(!poll.admin &&
|
(!admin &&
|
||||||
// and does not own this participant
|
// and does not own this participant
|
||||||
!session.ownsObject(selectedParticipant) &&
|
!session.ownsObject(selectedParticipant) &&
|
||||||
// and the participant has been claimed by a different user
|
// and the participant has been claimed by a different user
|
||||||
|
@ -239,7 +240,7 @@ const MobilePoll: React.VoidFunctionComponent = () => {
|
||||||
disabled={
|
disabled={
|
||||||
poll.closed ||
|
poll.closed ||
|
||||||
// if user is participant (not admin)
|
// if user is participant (not admin)
|
||||||
(!poll.admin &&
|
(!admin &&
|
||||||
// and does not own this participant
|
// and does not own this participant
|
||||||
!session.ownsObject(selectedParticipant) &&
|
!session.ownsObject(selectedParticipant) &&
|
||||||
// or the participant has been claimed by a different user
|
// or the participant has been claimed by a different user
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import posthog from "posthog-js";
|
import posthog from "posthog-js";
|
||||||
|
|
||||||
import { trpc } from "../../utils/trpc";
|
import { trpc, trpcNext } from "../../utils/trpc";
|
||||||
import { usePoll } from "../poll-context";
|
|
||||||
import { ParticipantForm } from "./types";
|
import { ParticipantForm } from "./types";
|
||||||
|
|
||||||
export const normalizeVotes = (
|
export const normalizeVotes = (
|
||||||
|
@ -83,11 +82,10 @@ export const useDeleteParticipantMutation = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useUpdatePollMutation = () => {
|
export const useUpdatePollMutation = () => {
|
||||||
const { urlId, admin } = usePoll();
|
const queryClient = trpcNext.useContext();
|
||||||
const queryClient = trpc.useContext();
|
|
||||||
return trpc.useMutation(["polls.update"], {
|
return trpc.useMutation(["polls.update"], {
|
||||||
onSuccess: (data) => {
|
onSuccess: (data) => {
|
||||||
queryClient.setQueryData(["polls.get", { urlId, admin }], data);
|
queryClient.poll.invalidate();
|
||||||
posthog.capture("updated poll", {
|
posthog.capture("updated poll", {
|
||||||
id: data.id,
|
id: data.id,
|
||||||
});
|
});
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { usePoll } from "../poll-context";
|
||||||
import Tooltip from "../tooltip";
|
import Tooltip from "../tooltip";
|
||||||
|
|
||||||
const PollSubheader: React.VoidFunctionComponent = () => {
|
const PollSubheader: React.VoidFunctionComponent = () => {
|
||||||
const { poll } = usePoll();
|
const { poll, admin } = usePoll();
|
||||||
const { t } = useTranslation("app");
|
const { t } = useTranslation("app");
|
||||||
const { dayjs } = useDayjs();
|
const { dayjs } = useDayjs();
|
||||||
return (
|
return (
|
||||||
|
@ -23,7 +23,7 @@ const PollSubheader: React.VoidFunctionComponent = () => {
|
||||||
b: <span />,
|
b: <span />,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{poll.legacy && poll.admin ? (
|
{poll.legacy && admin ? (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
width={400}
|
width={400}
|
||||||
content="This poll was created with an older version of Rallly. Some features might not work."
|
content="This poll was created with an older version of Rallly. Some features might not work."
|
||||||
|
|
|
@ -4,30 +4,26 @@ import { useTranslation } from "next-i18next";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
import FullPageLoader from "@/components/full-page-loader";
|
import FullPageLoader from "@/components/full-page-loader";
|
||||||
import PollPage from "@/components/poll";
|
import StandardLayout from "@/components/layouts/standard-layout";
|
||||||
|
import { ParticipantsProvider } from "@/components/participants-provider";
|
||||||
|
import { AdminControls, Poll } from "@/components/poll";
|
||||||
import { PollContextProvider } from "@/components/poll-context";
|
import { PollContextProvider } from "@/components/poll-context";
|
||||||
|
import { withSession } from "@/components/user-provider";
|
||||||
import { ParticipantsProvider } from "../components/participants-provider";
|
import { withSessionSsr } from "@/utils/auth";
|
||||||
import StandardLayout from "../components/standard-layout";
|
import { trpcNext } from "@/utils/trpc";
|
||||||
import { withSession } from "../components/user-provider";
|
import { withPageTranslations } from "@/utils/with-page-translations";
|
||||||
import { withSessionSsr } from "../utils/auth";
|
|
||||||
import { trpc } from "../utils/trpc";
|
|
||||||
import { withPageTranslations } from "../utils/with-page-translations";
|
|
||||||
import Custom404 from "./404";
|
|
||||||
|
|
||||||
const PollPageLoader: NextPage = () => {
|
const PollPageLoader: NextPage = () => {
|
||||||
const { query, asPath } = useRouter();
|
const { query } = useRouter();
|
||||||
const { t } = useTranslation("app");
|
const { t } = useTranslation("app");
|
||||||
const urlId = query.urlId as string;
|
const urlId = query.urlId as string;
|
||||||
const [notFound, setNotFound] = React.useState(false);
|
|
||||||
|
|
||||||
const admin = /^\/admin/.test(asPath);
|
const pollQuery = trpcNext.poll.getByAdminUrlId.useQuery(
|
||||||
const pollQuery = trpc.useQuery(["polls.get", { urlId, admin }], {
|
{ urlId },
|
||||||
onError: () => {
|
{
|
||||||
setNotFound(true);
|
retry: false,
|
||||||
},
|
},
|
||||||
retry: false,
|
);
|
||||||
});
|
|
||||||
|
|
||||||
const poll = pollQuery.data;
|
const poll = pollQuery.data;
|
||||||
|
|
||||||
|
@ -35,23 +31,28 @@ const PollPageLoader: NextPage = () => {
|
||||||
return (
|
return (
|
||||||
<ParticipantsProvider pollId={poll.id}>
|
<ParticipantsProvider pollId={poll.id}>
|
||||||
<StandardLayout>
|
<StandardLayout>
|
||||||
<PollContextProvider poll={poll} urlId={urlId} admin={admin}>
|
<PollContextProvider poll={poll} urlId={urlId} admin={true}>
|
||||||
<PollPage />
|
<Poll>
|
||||||
|
<AdminControls />
|
||||||
|
</Poll>
|
||||||
</PollContextProvider>
|
</PollContextProvider>
|
||||||
</StandardLayout>
|
</StandardLayout>
|
||||||
</ParticipantsProvider>
|
</ParticipantsProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (notFound) {
|
|
||||||
return <Custom404 />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return <FullPageLoader>{t("loading")}</FullPageLoader>;
|
return <FullPageLoader>{t("loading")}</FullPageLoader>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getServerSideProps: GetServerSideProps = withSessionSsr(
|
export const getServerSideProps: GetServerSideProps = withSessionSsr(
|
||||||
withPageTranslations(["common", "app", "errors"]),
|
withPageTranslations(["common", "app", "errors"]),
|
||||||
|
{
|
||||||
|
onPrefetch: async (ssg, ctx) => {
|
||||||
|
await ssg.poll.getByAdminUrlId.fetch({
|
||||||
|
urlId: ctx.params?.urlId as string,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
export default withSession(PollPageLoader);
|
export default withSession(PollPageLoader);
|
58
src/pages/p/[urlId].tsx
Normal file
58
src/pages/p/[urlId].tsx
Normal file
|
@ -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 (
|
||||||
|
<ModalProvider>
|
||||||
|
<DayjsProvider>
|
||||||
|
<ParticipantsProvider pollId={poll.id}>
|
||||||
|
<StandardLayout>
|
||||||
|
<PollContextProvider poll={poll} urlId={urlId} admin={false}>
|
||||||
|
<Poll />
|
||||||
|
</PollContextProvider>
|
||||||
|
</StandardLayout>
|
||||||
|
</ParticipantsProvider>
|
||||||
|
</DayjsProvider>
|
||||||
|
</ModalProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <FullPageLoader>{t("loading")}</FullPageLoader>;
|
||||||
|
};
|
||||||
|
|
||||||
|
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);
|
|
@ -1,7 +1,7 @@
|
||||||
import { GetStaticProps } from "next";
|
import { GetStaticProps } from "next";
|
||||||
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
|
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
|
||||||
|
|
||||||
import PageLayout from "@/components/page-layout";
|
import PageLayout from "@/components/layouts/page-layout";
|
||||||
|
|
||||||
const PrivacyPolicy = () => {
|
const PrivacyPolicy = () => {
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -2,8 +2,8 @@ import { NextPage } from "next";
|
||||||
|
|
||||||
import { withSessionSsr } from "@/utils/auth";
|
import { withSessionSsr } from "@/utils/auth";
|
||||||
|
|
||||||
|
import StandardLayout from "../components/layouts/standard-layout";
|
||||||
import { Profile } from "../components/profile";
|
import { Profile } from "../components/profile";
|
||||||
import StandardLayout from "../components/standard-layout";
|
|
||||||
import { withSession } from "../components/user-provider";
|
import { withSession } from "../components/user-provider";
|
||||||
import { withPageTranslations } from "../utils/with-page-translations";
|
import { withPageTranslations } from "../utils/with-page-translations";
|
||||||
|
|
||||||
|
|
|
@ -2,20 +2,21 @@ import { createRouter } from "../createRouter";
|
||||||
import { mergeRouters, router } from "../trpc";
|
import { mergeRouters, router } from "../trpc";
|
||||||
import { auth } from "./auth";
|
import { auth } from "./auth";
|
||||||
import { login } from "./login";
|
import { login } from "./login";
|
||||||
import { polls } from "./polls";
|
import { legacyPolls, poll } from "./polls";
|
||||||
import { user } from "./user";
|
import { user } from "./user";
|
||||||
import { whoami } from "./whoami";
|
import { whoami } from "./whoami";
|
||||||
|
|
||||||
const legacyRouter = createRouter()
|
const legacyRouter = createRouter()
|
||||||
.merge("user.", user)
|
.merge("user.", user)
|
||||||
.merge(login)
|
.merge(login)
|
||||||
.merge("polls.", polls);
|
.merge("polls.", legacyPolls);
|
||||||
|
|
||||||
export const appRouter = mergeRouters(
|
export const appRouter = mergeRouters(
|
||||||
legacyRouter.interop(),
|
legacyRouter.interop(),
|
||||||
router({
|
router({
|
||||||
whoami,
|
whoami,
|
||||||
auth,
|
auth,
|
||||||
|
poll,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ import { createToken } from "../../utils/auth";
|
||||||
import { nanoid } from "../../utils/nanoid";
|
import { nanoid } from "../../utils/nanoid";
|
||||||
import { GetPollApiResponse } from "../../utils/trpc/types";
|
import { GetPollApiResponse } from "../../utils/trpc/types";
|
||||||
import { createRouter } from "../createRouter";
|
import { createRouter } from "../createRouter";
|
||||||
|
import { publicProcedure, router } from "../trpc";
|
||||||
import { comments } from "./polls/comments";
|
import { comments } from "./polls/comments";
|
||||||
import { demo } from "./polls/demo";
|
import { demo } from "./polls/demo";
|
||||||
import { participants } from "./polls/participants";
|
import { participants } from "./polls/participants";
|
||||||
|
@ -24,8 +25,8 @@ const defaultSelectFields: {
|
||||||
location: true;
|
location: true;
|
||||||
description: true;
|
description: true;
|
||||||
createdAt: true;
|
createdAt: true;
|
||||||
participantUrlId: true;
|
|
||||||
adminUrlId: true;
|
adminUrlId: true;
|
||||||
|
participantUrlId: true;
|
||||||
verified: true;
|
verified: true;
|
||||||
closed: true;
|
closed: true;
|
||||||
legacy: true;
|
legacy: true;
|
||||||
|
@ -45,8 +46,8 @@ const defaultSelectFields: {
|
||||||
location: true,
|
location: true,
|
||||||
description: true,
|
description: true,
|
||||||
createdAt: true,
|
createdAt: true,
|
||||||
participantUrlId: true,
|
|
||||||
adminUrlId: true,
|
adminUrlId: true,
|
||||||
|
participantUrlId: true,
|
||||||
verified: true,
|
verified: true,
|
||||||
closed: true,
|
closed: true,
|
||||||
legacy: true,
|
legacy: true,
|
||||||
|
@ -76,7 +77,7 @@ const getPollIdFromAdminUrlId = async (urlId: string) => {
|
||||||
return res.id;
|
return res.id;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const polls = createRouter()
|
export const legacyPolls = createRouter()
|
||||||
.merge("demo.", demo)
|
.merge("demo.", demo)
|
||||||
.merge("participants.", participants)
|
.merge("participants.", participants)
|
||||||
.merge("comments.", comments)
|
.merge("comments.", comments)
|
||||||
|
@ -189,37 +190,6 @@ export const polls = createRouter()
|
||||||
return { urlId: adminUrlId };
|
return { urlId: adminUrlId };
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.query("get", {
|
|
||||||
input: z.object({
|
|
||||||
urlId: z.string(),
|
|
||||||
admin: z.boolean(),
|
|
||||||
}),
|
|
||||||
resolve: async ({ input, ctx }): Promise<GetPollApiResponse> => {
|
|
||||||
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", {
|
.mutation("update", {
|
||||||
input: z.object({
|
input: z.object({
|
||||||
urlId: z.string(),
|
urlId: z.string(),
|
||||||
|
@ -270,7 +240,7 @@ export const polls = createRouter()
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return { ...poll, admin: true };
|
return { ...poll };
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.mutation("delete", {
|
.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: "" };
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
|
@ -5,7 +5,11 @@ import {
|
||||||
unsealData,
|
unsealData,
|
||||||
} from "iron-session";
|
} from "iron-session";
|
||||||
import { withIronSessionApiRoute, withIronSessionSsr } from "iron-session/next";
|
import { withIronSessionApiRoute, withIronSessionSsr } from "iron-session/next";
|
||||||
import { GetServerSideProps, NextApiHandler } from "next";
|
import {
|
||||||
|
GetServerSideProps,
|
||||||
|
GetServerSidePropsContext,
|
||||||
|
NextApiHandler,
|
||||||
|
} from "next";
|
||||||
|
|
||||||
import { prisma } from "~/prisma/db";
|
import { prisma } from "~/prisma/db";
|
||||||
|
|
||||||
|
@ -72,23 +76,56 @@ export function withSessionRoute(handler: NextApiHandler) {
|
||||||
}, sessionOptions);
|
}, sessionOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function withSessionSsr(handler: GetServerSideProps) {
|
const compose = (...fns: GetServerSideProps[]): GetServerSideProps => {
|
||||||
return withIronSessionSsr(async (context) => {
|
return async (ctx) => {
|
||||||
const { req } = context;
|
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);
|
return res;
|
||||||
await ssg.whoami.get.prefetch();
|
};
|
||||||
|
};
|
||||||
|
|
||||||
const res = await handler(context);
|
export function withSessionSsr(
|
||||||
|
handler: GetServerSideProps | GetServerSideProps[],
|
||||||
|
options?: {
|
||||||
|
onPrefetch?: (
|
||||||
|
ssg: Awaited<ReturnType<typeof createSSGHelperFromContext>>,
|
||||||
|
ctx: GetServerSidePropsContext,
|
||||||
|
) => Promise<void>;
|
||||||
|
},
|
||||||
|
): 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) {
|
if ("props" in res) {
|
||||||
return {
|
return {
|
||||||
...res,
|
|
||||||
props: {
|
props: {
|
||||||
...res.props,
|
...res.props,
|
||||||
user: req.session.user,
|
|
||||||
trpcState: ssg.dehydrate(),
|
trpcState: ssg.dehydrate(),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -156,11 +193,7 @@ export const mergeGuestsIntoUser = async (
|
||||||
export const getCurrentUser = async (
|
export const getCurrentUser = async (
|
||||||
session: IronSession,
|
session: IronSession,
|
||||||
): Promise<{ isGuest: boolean; id: string }> => {
|
): Promise<{ isGuest: boolean; id: string }> => {
|
||||||
const user = session.user;
|
await setUser(session);
|
||||||
|
|
||||||
if (!user) {
|
return session.user;
|
||||||
throw new Error("Tried to get user but no user found.");
|
|
||||||
}
|
|
||||||
|
|
||||||
return user;
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -13,7 +13,6 @@ export type GetPollApiResponse = {
|
||||||
participantUrlId: string;
|
participantUrlId: string;
|
||||||
verified: boolean;
|
verified: boolean;
|
||||||
closed: boolean;
|
closed: boolean;
|
||||||
admin: boolean;
|
|
||||||
legacy: boolean;
|
legacy: boolean;
|
||||||
demo: boolean;
|
demo: boolean;
|
||||||
notifications: boolean;
|
notifications: boolean;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue