mirror of
https://github.com/lukevella/rallly.git
synced 2025-04-29 10:16:32 +02:00
Enable resending verification emails
This commit is contained in:
parent
e9ac2a8e71
commit
a6e39cc2c3
5 changed files with 87 additions and 21 deletions
|
@ -37,7 +37,9 @@ const NotificationsToggle: React.VoidFunctionComponent<NotificationsToggleProps>
|
||||||
email: poll.user.email,
|
email: poll.user.email,
|
||||||
}}
|
}}
|
||||||
components={{
|
components={{
|
||||||
b: <span className="email" />,
|
b: (
|
||||||
|
<span className="text-indigo-300 whitespace-nowrap font-medium font-mono " />
|
||||||
|
),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,14 +1,25 @@
|
||||||
import { formatRelative } from "date-fns";
|
import { formatRelative } from "date-fns";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { Trans, useTranslation } from "next-i18next";
|
import { Trans, useTranslation } from "next-i18next";
|
||||||
import Tooltip from "../tooltip";
|
import Button from "../button";
|
||||||
import { usePoll } from "../use-poll";
|
import { usePoll } from "../use-poll";
|
||||||
|
import Popover from "../popover";
|
||||||
|
import { useMutation } from "react-query";
|
||||||
|
import axios from "axios";
|
||||||
|
|
||||||
export interface PollSubheaderProps {}
|
export interface PollSubheaderProps {}
|
||||||
|
|
||||||
const PollSubheader: React.VoidFunctionComponent<PollSubheaderProps> = () => {
|
const PollSubheader: React.VoidFunctionComponent<PollSubheaderProps> = () => {
|
||||||
const poll = usePoll();
|
const poll = usePoll();
|
||||||
const { t } = useTranslation("app");
|
const { t } = useTranslation("app");
|
||||||
|
|
||||||
|
const {
|
||||||
|
mutate: sendVerificationEmail,
|
||||||
|
isLoading: isSendingVerificationEmail,
|
||||||
|
isSuccess: didSendVerificationEmail,
|
||||||
|
} = useMutation(async () => {
|
||||||
|
await axios.post(`/api/poll/${poll.urlId}/verify`);
|
||||||
|
});
|
||||||
return (
|
return (
|
||||||
<div className="text-slate-500">
|
<div className="text-slate-500">
|
||||||
<div className="md:inline">
|
<div className="md:inline">
|
||||||
|
@ -37,22 +48,40 @@ const PollSubheader: React.VoidFunctionComponent<PollSubheaderProps> = () => {
|
||||||
Verified
|
Verified
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
<Tooltip
|
<Popover
|
||||||
content={
|
trigger={
|
||||||
<div className="max-w-sm">
|
<button className="badge transition-colors hover:text-indigo-500 hover:border-slate-300 hover:bg-slate-100 cursor-pointer text-slate-400">
|
||||||
|
Unverified
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div className="max-w-sm">
|
||||||
|
<div className="mb-4">
|
||||||
<Trans
|
<Trans
|
||||||
t={t}
|
t={t}
|
||||||
i18nKey="unverifiedMessage"
|
i18nKey="unverifiedMessage"
|
||||||
values={{ email: poll.user.email }}
|
values={{ email: poll.user.email }}
|
||||||
components={{
|
components={{
|
||||||
b: <span className="email" />,
|
b: (
|
||||||
|
<span className="text-indigo-500 font-medium font-mono whitespace-nowrap" />
|
||||||
|
),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
}
|
{didSendVerificationEmail ? (
|
||||||
>
|
<div className="text-green-500">Verification email sent.</div>
|
||||||
<span className="badge text-slate-400">Unverified</span>
|
) : (
|
||||||
</Tooltip>
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
sendVerificationEmail();
|
||||||
|
}}
|
||||||
|
loading={isSendingVerificationEmail}
|
||||||
|
>
|
||||||
|
Resend verification email
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Popover>
|
||||||
)
|
)
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -34,7 +34,7 @@ const Popover: React.VoidFunctionComponent<PopoverProps> = ({
|
||||||
|
|
||||||
const portal = document.getElementById("portal");
|
const portal = document.getElementById("portal");
|
||||||
return (
|
return (
|
||||||
<HeadlessPopover>
|
<HeadlessPopover as={React.Fragment}>
|
||||||
<HeadlessPopover.Button
|
<HeadlessPopover.Button
|
||||||
ref={setReferenceElement}
|
ref={setReferenceElement}
|
||||||
as="div"
|
as="div"
|
||||||
|
|
|
@ -1,12 +1,49 @@
|
||||||
|
import absoluteUrl from "utils/absolute-url";
|
||||||
import { prisma } from "../../../../db";
|
import { prisma } from "../../../../db";
|
||||||
import { withLink } from "../../../../utils/api-utils";
|
import { sendEmailTemplate, withLink } from "../../../../utils/api-utils";
|
||||||
|
|
||||||
export default withLink(async (req, res, link) => {
|
export default withLink(async (req, res, link) => {
|
||||||
if (req.method === "POST") {
|
if (req.method === "POST") {
|
||||||
if (link.role !== "admin") {
|
if (link.role !== "admin") {
|
||||||
return res.status(401).end();
|
return res
|
||||||
|
.status(401)
|
||||||
|
.json({ status: 401, message: "Only admins can verify polls" });
|
||||||
|
}
|
||||||
|
const verificationCode = req.body ? req.body.verificationCode : undefined;
|
||||||
|
if (!verificationCode) {
|
||||||
|
const poll = await prisma.poll.findUnique({
|
||||||
|
where: {
|
||||||
|
urlId: link.pollId,
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
user: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!poll) {
|
||||||
|
return res.status(404).json({ status: 404, message: "Poll not found" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const homePageUrl = absoluteUrl(req).origin;
|
||||||
|
const pollUrl = `${homePageUrl}/admin/${link.urlId}`;
|
||||||
|
const verifyEmailUrl = `${pollUrl}?code=${poll.verificationCode}`;
|
||||||
|
|
||||||
|
await sendEmailTemplate({
|
||||||
|
templateName: "new-poll",
|
||||||
|
to: poll.user.email,
|
||||||
|
subject: `Rallly: ${poll.title} - Verify your email address`,
|
||||||
|
templateVars: {
|
||||||
|
title: poll.title,
|
||||||
|
name: poll.user.name,
|
||||||
|
pollUrl,
|
||||||
|
verifyEmailUrl,
|
||||||
|
homePageUrl,
|
||||||
|
supportEmail: process.env.NEXT_PUBLIC_SUPPORT_EMAIL,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.send("ok");
|
||||||
}
|
}
|
||||||
const { verificationCode } = req.body;
|
|
||||||
try {
|
try {
|
||||||
await prisma.poll.update({
|
await prisma.poll.update({
|
||||||
where: {
|
where: {
|
||||||
|
@ -19,14 +56,16 @@ export default withLink(async (req, res, link) => {
|
||||||
verified: true,
|
verified: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
return res.end();
|
return res.send("ok");
|
||||||
} catch {
|
} catch {
|
||||||
console.error(
|
console.error(
|
||||||
`Failed to verify poll "${link.pollId}" with code ${verificationCode}`,
|
`Failed to verify poll "${link.pollId}" with code ${verificationCode}`,
|
||||||
);
|
);
|
||||||
return res.status(500).end();
|
return res
|
||||||
|
.status(500)
|
||||||
|
.json({ status: 500, message: "Could not verify poll" });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return res.status(405).end();
|
return res.status(405).json({ status: 405, message: "Invalid http method" });
|
||||||
});
|
});
|
||||||
|
|
|
@ -134,10 +134,6 @@
|
||||||
[data-popper-placement="bottom"] .tooltip-arrow {
|
[data-popper-placement="bottom"] .tooltip-arrow {
|
||||||
@apply bottom-full border-b-slate-700;
|
@apply bottom-full border-b-slate-700;
|
||||||
}
|
}
|
||||||
|
|
||||||
.email {
|
|
||||||
@apply font-medium font-mono whitespace-nowrap text-indigo-300;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@layer components {
|
@layer components {
|
||||||
|
|
Loading…
Add table
Reference in a new issue