diff --git a/package.json b/package.json
index baadf0087..9dc263258 100644
--- a/package.json
+++ b/package.json
@@ -20,6 +20,7 @@
"@next/bundle-analyzer": "^12.3.4",
"@next/font": "^13.1.3",
"@prisma/client": "^4.9.0",
+ "@radix-ui/react-popover": "^1.0.3",
"@sentry/nextjs": "^7.33.0",
"@svgr/webpack": "^6.2.1",
"@tailwindcss/forms": "^0.4.0",
@@ -60,6 +61,7 @@
"spacetime": "^7.1.4",
"superjson": "^1.9.1",
"tailwindcss": "^3.2.4",
+ "tailwindcss-animate": "^1.0.5",
"timezone-soft": "^1.3.1",
"typescript": "^4.9.4",
"zod": "^3.20.2"
diff --git a/public/locales/en/app.json b/public/locales/en/app.json
index 4d47780fa..d26fe0921 100644
--- a/public/locales/en/app.json
+++ b/public/locales/en/app.json
@@ -67,7 +67,10 @@
"name": "Name",
"namePlaceholder": "Jessie Smith",
"new": "New",
+ "adminPollTitle": "{{title}}: Admin",
"newPoll": "New poll",
+ "createNew": "Create new",
+ "home": "Home",
"next": "Next",
"nextMonth": "Next month",
"no": "No",
diff --git a/src/components/admin-control.tsx b/src/components/admin-control.tsx
new file mode 100644
index 000000000..d505c133c
--- /dev/null
+++ b/src/components/admin-control.tsx
@@ -0,0 +1,134 @@
+import { AnimatePresence, motion } from "framer-motion";
+import { useRouter } from "next/router";
+import { useTranslation } from "next-i18next";
+import posthog from "posthog-js";
+import React from "react";
+import toast from "react-hot-toast";
+import { useMount } from "react-use";
+
+import { Button } from "@/components/button";
+import Share from "@/components/icons/share.svg";
+
+import { trpc, trpcNext } from "../utils/trpc";
+import { useParticipants } from "./participants-provider";
+import ManagePoll from "./poll/manage-poll";
+import { useUpdatePollMutation } from "./poll/mutations";
+import NotificationsToggle from "./poll/notifications-toggle";
+import { UnverifiedPollNotice } from "./poll/unverified-poll-notice";
+import { usePoll } from "./poll-context";
+import Sharing from "./sharing";
+import { useUser } from "./user-provider";
+
+export const AdminControls = (props: { children?: React.ReactNode }) => {
+ const { poll, urlId } = usePoll();
+ const { t } = useTranslation("app");
+
+ const router = useRouter();
+
+ const queryClient = trpcNext.useContext();
+
+ const session = useUser();
+
+ 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.poll.invalidate();
+ session.refresh();
+ posthog.capture("verified email");
+ },
+ onError: () => {
+ toast.error(t("linkHasExpired"));
+ },
+ onSettled: () => {
+ router.replace(`/admin/${router.query.urlId}`, undefined, {
+ shallow: true,
+ });
+ },
+ });
+
+ const { participants } = useParticipants();
+
+ const [isSharingVisible, setIsSharingVisible] = React.useState(
+ participants.length === 0,
+ );
+
+ useMount(() => {
+ const { code } = router.query;
+ if (typeof code === "string" && !poll.verified) {
+ verifyEmail.mutate({ code, pollId: poll.id });
+ }
+ });
+
+ return (
+
+
+
+
+
+ }
+ onClick={() => {
+ setIsSharingVisible(!isSharingVisible);
+ }}
+ >
+ {t("share")}
+
+
+
+
+ {isSharingVisible ? (
+
+ {
+ setIsSharingVisible(false);
+ }}
+ />
+
+ ) : null}
+
+
+ {poll.verified === false ? : null}
+ {props.children}
+
+
+ );
+};
diff --git a/src/components/auth/auth-layout.tsx b/src/components/auth/auth-layout.tsx
index 81c5c0a23..4f5620e9e 100644
--- a/src/components/auth/auth-layout.tsx
+++ b/src/components/auth/auth-layout.tsx
@@ -4,11 +4,11 @@ import Logo from "~/public/logo.svg";
export const AuthLayout = ({ children }: { children?: React.ReactNode }) => {
return (
-
+
diff --git a/src/components/auth/login-modal.tsx b/src/components/auth/login-modal.tsx
index 35bbc6946..7f2994092 100644
--- a/src/components/auth/login-modal.tsx
+++ b/src/components/auth/login-modal.tsx
@@ -1,8 +1,7 @@
import Link from "next/link";
import React from "react";
-import Logo from "~/public/logo.svg";
-
+import { Logo } from "../logo";
import { useModalContext } from "../modal/modal-provider";
import { useUser } from "../user-provider";
import { LoginForm, RegisterForm } from "./login-form";
@@ -16,7 +15,7 @@ export const LoginModal: React.VoidFunctionComponent<{
return (
-
+
{hasAccount ? (
diff --git a/src/components/button.tsx b/src/components/button.tsx
index cf39ca394..f1d649bfc 100644
--- a/src/components/button.tsx
+++ b/src/components/button.tsx
@@ -56,6 +56,7 @@ export const Button = React.forwardRef
(
className,
)}
{...passThroughProps}
+ role="button"
disabled={disabled || loading}
>
{loading ? (
diff --git a/src/components/create-poll.tsx b/src/components/create-poll.tsx
index 3f9444ea7..9b5a6bbc6 100644
--- a/src/components/create-poll.tsx
+++ b/src/components/create-poll.tsx
@@ -1,5 +1,4 @@
import { NextPage } from "next";
-import Head from "next/head";
import { useRouter } from "next/router";
import { useTranslation } from "next-i18next";
import posthog from "posthog-js";
@@ -100,7 +99,7 @@ const Page: NextPage = ({
optionsView: formData?.options?.view,
});
setPersistedFormData(initialNewEventData);
- router.replace(`/admin/${res.urlId}?sharing=true`);
+ router.replace(`/admin/${res.urlId}`);
},
});
@@ -146,76 +145,76 @@ const Page: NextPage = ({
return (
-
- {formData?.eventDetails?.title ?? t("newPoll")}
-
-
-
-
-
-
{t("newPoll")}
-
-
-
- {(() => {
- switch (currentStepName) {
- case "eventDetails":
- return (
-
- );
- case "options":
- return (
-
- );
- case "userDetails":
- return (
-
- );
- }
- })()}
-
- {currentStepIndex > 0 ? (
+
+
+
+
+
+ {t("createNew")}
+
+
+
+
+ {(() => {
+ switch (currentStepName) {
+ case "eventDetails":
+ return (
+
+ );
+ case "options":
+ return (
+
+ );
+ case "userDetails":
+ return (
+
+ );
+ }
+ })()}
+
+ {currentStepIndex > 0 ? (
+
+ ) : null}
- ) : null}
-
+
diff --git a/src/components/date-card.tsx b/src/components/date-card.tsx
index 5ac7820e2..eb59bdb46 100644
--- a/src/components/date-card.tsx
+++ b/src/components/date-card.tsx
@@ -5,7 +5,6 @@ export interface DateCardProps {
annotation?: React.ReactNode;
day: string;
month: string;
- dow: string;
className?: string;
}
@@ -13,27 +12,23 @@ const DateCard: React.VoidFunctionComponent
= ({
annotation,
className,
day,
- dow,
month,
}) => {
return (
{annotation ? (
{annotation}
) : null}
-
-
- {dow.substring(0, 3)}
-
-
-
{day}
-
);
diff --git a/src/components/discussion/discussion.tsx b/src/components/discussion/discussion.tsx
index 6100b60c2..787514670 100644
--- a/src/components/discussion/discussion.tsx
+++ b/src/components/discussion/discussion.tsx
@@ -1,5 +1,4 @@
import clsx from "clsx";
-import { AnimatePresence, motion } from "framer-motion";
import { useTranslation } from "next-i18next";
import posthog from "posthog-js";
import * as React from "react";
@@ -81,77 +80,64 @@ const Discussion: React.VoidFunctionComponent = () => {
}
return (
-
-
+
+
0,
+ "bg-pattern space-y-3 border-b p-3": comments.length > 0,
})}
>
-
- {comments.map((comment) => {
- const canDelete =
- admin || session.ownsObject(comment) || isUnclaimed(comment);
+ {comments.map((comment) => {
+ const canDelete =
+ admin || session.ownsObject(comment) || isUnclaimed(comment);
- return (
-
+
-
-
-
+
+
+ •
+
+ {dayjs(new Date(comment.createdAt)).fromNow()}
+
+
+ }
+ >
+ {
+ deleteComment.mutate({
+ commentId: comment.id,
+ pollId,
+ });
+ }}
/>
-
- •
-
- {dayjs(new Date(comment.createdAt)).fromNow()}
-
-
- }
- >
- {
- deleteComment.mutate({
- commentId: comment.id,
- pollId,
- });
- }}
- />
-
-
-
- {comment.content}
-
-
-
- );
- })}
-
+
+
+
+ {comment.content}
+
+
+
+ );
+ })}
{description}
-
+
{t("goToHome")}
} onClick={showCrispChat}>
diff --git a/src/components/forms/poll-details-form.tsx b/src/components/forms/poll-details-form.tsx
index ad01913f8..6f107d39e 100644
--- a/src/components/forms/poll-details-form.tsx
+++ b/src/components/forms/poll-details-form.tsx
@@ -36,7 +36,6 @@ export const PollDetailsForm: React.VoidFunctionComponent<