feat: send email to user on successful password reset

This commit is contained in:
Ephraim Atta-Duncan
2023-09-18 14:31:04 +00:00
parent f561ef3cda
commit 166cbc150f
6 changed files with 57 additions and 15 deletions

View File

@ -5,6 +5,7 @@ import backgroundPattern from '~/assets/background-pattern.png';
import { ForgotPasswordForm } from '~/components/forms/forgot-password'; import { ForgotPasswordForm } from '~/components/forms/forgot-password';
export default function ForgotPasswordPage() { export default function ForgotPasswordPage() {
// TODO: Fix width reducing with screen size
return ( return (
<main className="bg-sand-100 relative flex min-h-screen flex-col items-center justify-center overflow-hidden px-4 py-12 md:p-12 lg:p-24"> <main className="bg-sand-100 relative flex min-h-screen flex-col items-center justify-center overflow-hidden px-4 py-12 md:p-12 lg:p-24">
<div className="relative flex w-1/5 items-center gap-x-24"> <div className="relative flex w-1/5 items-center gap-x-24">

View File

@ -43,10 +43,7 @@ export const ForgotPasswordForm = ({ className }: ForgotPasswordFormProps) => {
const { mutateAsync: forgotPassword } = trpc.profile.forgotPassword.useMutation(); const { mutateAsync: forgotPassword } = trpc.profile.forgotPassword.useMutation();
const onFormSubmit = async ({ email }: TForgotPasswordFormSchema) => { const onFormSubmit = async ({ email }: TForgotPasswordFormSchema) => {
// check if the email is available // TODO: Handle error with try/catch
// if not, throw an error
// if the email is available, create a password reset token and send an email
await forgotPassword({ await forgotPassword({
email, email,
}); });

View File

@ -3,8 +3,8 @@ import { Img, Section, Tailwind, Text } from '@react-email/components';
import * as config from '@documenso/tailwind-config'; import * as config from '@documenso/tailwind-config';
export interface TemplateResetPasswordProps { export interface TemplateResetPasswordProps {
inviterName: string; userName: string;
inviterEmail: string; userEmail: string;
assetBaseUrl: string; assetBaseUrl: string;
} }

View File

@ -23,9 +23,8 @@ import {
export type ResetPasswordTemplateProps = Partial<TemplateResetPasswordProps>; export type ResetPasswordTemplateProps = Partial<TemplateResetPasswordProps>;
export const ResetPasswordTemplate = ({ export const ResetPasswordTemplate = ({
inviterName = 'Lucas Smith', userName = 'Lucas Smith',
inviterEmail = 'lucas@documenso.com', userEmail = 'lucas@documenso.com',
assetBaseUrl = 'http://localhost:3002', assetBaseUrl = 'http://localhost:3002',
}: ResetPasswordTemplateProps) => { }: ResetPasswordTemplateProps) => {
const previewText = `Password Reset Successful`; const previewText = `Password Reset Successful`;
@ -58,8 +57,8 @@ export const ResetPasswordTemplate = ({
/> />
<TemplateResetPassword <TemplateResetPassword
inviterName={inviterName} userName={userName}
inviterEmail={inviterEmail} userEmail={userEmail}
assetBaseUrl={assetBaseUrl} assetBaseUrl={assetBaseUrl}
/> />
</Section> </Section>
@ -68,9 +67,9 @@ export const ResetPasswordTemplate = ({
<Container className="mx-auto mt-12 max-w-xl"> <Container className="mx-auto mt-12 max-w-xl">
<Section> <Section>
<Text className="my-4 text-base font-semibold"> <Text className="my-4 text-base font-semibold">
Hi, {inviterName}{' '} Hi, {userName}{' '}
<Link className="font-normal text-slate-400" href="mailto:{inviterEmail}"> <Link className="font-normal text-slate-400" href="mailto:{userEmail}">
({inviterEmail}) ({userEmail})
</Link> </Link>
</Text> </Text>

View File

@ -0,0 +1,44 @@
import { createElement } from 'react';
import { mailer } from '@documenso/email/mailer';
import { render } from '@documenso/email/render';
import { ResetPasswordTemplate } from '@documenso/email/templates/reset-password';
import { prisma } from '@documenso/prisma';
export interface SendResetPasswordOptions {
userId: number;
}
export const sendResetPassword = async ({ userId }: SendResetPasswordOptions) => {
const user = await prisma.user.findFirstOrThrow({
where: {
id: userId,
},
});
if (!user) {
throw new Error('User not found');
}
const assetBaseUrl = process.env.NEXT_PUBLIC_SITE_URL || 'http://localhost:3000';
const template = createElement(ResetPasswordTemplate, {
assetBaseUrl,
userEmail: user.email,
userName: user.name || '',
});
return await mailer.sendMail({
to: {
address: user.email,
name: user.name || '',
},
from: {
name: process.env.NEXT_PRIVATE_SMTP_FROM_NAME || 'Documenso',
address: process.env.NEXT_PRIVATE_SMTP_FROM_ADDRESS || 'noreply@documenso.com',
},
subject: 'Password Reset Success!',
html: render(template),
text: render(template, { plainText: true }),
});
};

View File

@ -3,6 +3,7 @@ import { compare, hash } from 'bcrypt';
import { prisma } from '@documenso/prisma'; import { prisma } from '@documenso/prisma';
import { SALT_ROUNDS } from '../../constants/auth'; import { SALT_ROUNDS } from '../../constants/auth';
import { sendResetPassword } from '../auth/send-reset-password';
export type ResetPasswordOptions = { export type ResetPasswordOptions = {
token: string; token: string;
@ -61,6 +62,6 @@ export const resetPassword = async ({ token, password }: ResetPasswordOptions) =
throw new Error('Unable to update password'); throw new Error('Unable to update password');
} }
// await sendResetPasswordSuccessMail(foundToken.User); await sendResetPassword({ userId: foundToken.userId });
return transactions; return transactions;
}; };