mirror of
https://github.com/lukevella/rallly.git
synced 2025-08-03 16:38:34 +02:00
Use locale to format dates and times (#123)
* Use deployment url in demo description. Close #131
This commit is contained in:
parent
8263926168
commit
8aec24308e
40 changed files with 1240 additions and 864 deletions
217
pages/poll.tsx
217
pages/poll.tsx
|
@ -1,41 +1,20 @@
|
|||
import axios from "axios";
|
||||
import { GetServerSideProps, NextPage } from "next";
|
||||
import Head from "next/head";
|
||||
import dynamic from "next/dynamic";
|
||||
import { useRouter } from "next/router";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
|
||||
import { usePlausible } from "next-plausible";
|
||||
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 { useQuery } from "react-query";
|
||||
|
||||
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 TruncatedLinkify from "@/components/poll/truncated-linkify";
|
||||
import { UserAvatarProvider } from "@/components/poll/user-avatar";
|
||||
import { PollContextProvider, usePoll } from "@/components/poll-context";
|
||||
import Popover from "@/components/popover";
|
||||
import Sharing from "@/components/sharing";
|
||||
import StandardLayout from "@/components/standard-layout";
|
||||
import { useUserName } from "@/components/user-name-context";
|
||||
|
||||
import { GetPollResponse } from "../api-client/get-poll";
|
||||
import Custom404 from "./404";
|
||||
|
||||
const Discussion = React.lazy(() => import("@/components/discussion"));
|
||||
|
||||
const Poll = React.lazy(() => import("@/components/poll"));
|
||||
const PollPage = dynamic(() => import("@/components/poll"), { ssr: false });
|
||||
|
||||
const PollPageLoader: NextPage = () => {
|
||||
const { query } = useRouter();
|
||||
|
@ -99,195 +78,7 @@ const PollPageLoader: NextPage = () => {
|
|||
return !poll ? (
|
||||
<FullPageLoader>{t("loading")}</FullPageLoader>
|
||||
) : (
|
||||
<PollContextProvider value={poll}>
|
||||
<PollPage />
|
||||
</PollContextProvider>
|
||||
);
|
||||
};
|
||||
|
||||
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 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
|
||||
poll.options.forEach((option) => {
|
||||
if (option.votes.length > highScore) {
|
||||
highScore = option.votes.length;
|
||||
}
|
||||
});
|
||||
|
||||
const names = React.useMemo(
|
||||
() => poll.participants.map(({ name }) => name),
|
||||
[poll.participants],
|
||||
);
|
||||
|
||||
return (
|
||||
<UserAvatarProvider seed={poll.pollId} names={names}>
|
||||
<StandardLayout>
|
||||
<div className="relative max-w-full bg-gray-50 py-4 md:px-4 lg:w-[1024px] lg:px-8">
|
||||
<Head>
|
||||
<title>{poll.title}</title>
|
||||
<meta name="robots" content="noindex,nofollow" />
|
||||
</Head>
|
||||
<div
|
||||
className="max-w-full"
|
||||
style={{
|
||||
width: Math.max(600, poll.options.length * 95 + 200 + 160),
|
||||
}}
|
||||
>
|
||||
<div className="mb-6">
|
||||
<div className="mb-3 items-start px-4 md:flex md:space-x-4">
|
||||
<div className="mb-3 grow md:mb-0">
|
||||
<div className="flex flex-col-reverse md:flex-row">
|
||||
<h1 className="mb-2 grow text-3xl leading-tight">
|
||||
{preventWidows(poll.title)}
|
||||
</h1>
|
||||
{poll.role === "admin" ? (
|
||||
<div className="mb-4 flex space-x-2 md:mb-2">
|
||||
<NotificationsToggle />
|
||||
<ManagePoll
|
||||
placement={
|
||||
isWideScreen ? "bottom-end" : "bottom-start"
|
||||
}
|
||||
/>
|
||||
<div>
|
||||
<Popover
|
||||
trigger={
|
||||
<Button type="primary" icon={<Share />}>
|
||||
Share
|
||||
</Button>
|
||||
}
|
||||
placement={isWideScreen ? "bottom-end" : undefined}
|
||||
>
|
||||
<Sharing links={poll.links} />
|
||||
</Popover>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<PollSubheader />
|
||||
</div>
|
||||
</div>
|
||||
{poll.description ? (
|
||||
<div className="mb-4 whitespace-pre-line bg-white px-4 py-3 text-lg leading-relaxed text-slate-600 shadow-sm md:w-fit md:rounded-xl md:bg-white">
|
||||
<TruncatedLinkify>
|
||||
{preventWidows(poll.description)}
|
||||
</TruncatedLinkify>
|
||||
</div>
|
||||
) : null}
|
||||
{poll.location ? (
|
||||
<div className="mb-4 flex items-center px-4">
|
||||
<div>
|
||||
<LocationMarker
|
||||
width={20}
|
||||
className="mr-2 text-slate-400"
|
||||
/>
|
||||
</div>
|
||||
<TruncatedLinkify>{poll.location}</TruncatedLinkify>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
{poll.closed ? (
|
||||
<div className="mb-4 flex items-center bg-sky-100 py-3 px-4 text-sky-700 shadow-sm md:rounded-lg">
|
||||
<div className="mr-3 rounded-md">
|
||||
<LockClosed className="w-5" />
|
||||
</div>
|
||||
This poll has been locked (voting is disabled)
|
||||
</div>
|
||||
) : null}
|
||||
<React.Suspense fallback={<div>Loading…</div>}>
|
||||
<div className="mb-4 lg:mb-8">
|
||||
<PollComponent pollId={poll.urlId} highScore={highScore} />
|
||||
</div>
|
||||
<Discussion
|
||||
pollId={poll.urlId}
|
||||
canDelete={poll.role === "admin"}
|
||||
/>
|
||||
</React.Suspense>
|
||||
</div>
|
||||
</div>
|
||||
</StandardLayout>
|
||||
</UserAvatarProvider>
|
||||
<PollPage poll={poll} />
|
||||
);
|
||||
};
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue