Skip to content

Commit

Permalink
feat: dashboard: restrict signup/signin to verified emails only (#2610)
Browse files Browse the repository at this point in the history
resolves #2585
  • Loading branch information
onehassan committed Apr 3, 2024
1 parent 9f2bf9e commit 91c2bb6
Show file tree
Hide file tree
Showing 4 changed files with 121 additions and 4 deletions.
5 changes: 5 additions & 0 deletions .changeset/poor-adults-unite.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@nhost/dashboard': minor
---

feat: refactor sign-in and sign-up pages to enforce email verification
94 changes: 94 additions & 0 deletions dashboard/src/pages/email/verify.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { NavLink } from '@/components/common/NavLink';
import { UnauthenticatedLayout } from '@/components/layout/UnauthenticatedLayout';
import { Box } from '@/components/ui/v2/Box';
import { Button } from '@/components/ui/v2/Button';
import { Text } from '@/components/ui/v2/Text';
import { getToastStyleProps } from '@/utils/constants/settings';
import { useNhostClient } from '@nhost/nextjs';
import { useRouter } from 'next/router';
import { useEffect, useState, type ReactElement } from 'react';
import { toast } from 'react-hot-toast';

export default function VerifyEmailPage() {
const router = useRouter();
const nhost = useNhostClient();

const {
query: { email },
} = router;

const [resendVerificationEmailLoading, setResendVerificationEmailLoading] =
useState(false);

useEffect(() => {
if (!email) {
router.push('/signin');
}
}, [email, router]);

const resendVerificationEmail = async () => {
setResendVerificationEmailLoading(true);

try {
await nhost.auth.sendVerificationEmail({ email: email as string });

toast.success(
`An new email has been sent to ${email}. Please follow the link to verify your email address and to
complete your registration.`,
getToastStyleProps(),
);
} catch {
toast.error(
'An error occurred while sending the verification email. Please try again.',
getToastStyleProps(),
);
} finally {
setResendVerificationEmailLoading(false);
}
};

return (
<>
<Text
variant="h2"
component="h1"
className="text-center text-3.5xl font-semibold lg:text-4.5xl"
>
Verify your email
</Text>

<Box className="grid grid-flow-row gap-4 rounded-md border bg-transparent p-6 lg:p-12">
<div className="relative py-2">
<Text color="secondary" className="text-center text-sm">
Please check your inbox for the verification email. Follow the link
to verify your email address and complete your registration.
</Text>
</div>
<Button
className="!bg-white !text-black disabled:!text-black disabled:!text-opacity-60"
size="large"
disabled={resendVerificationEmailLoading}
loading={resendVerificationEmailLoading}
type="button"
onClick={resendVerificationEmail}
>
Resend verification email
</Button>

<div className="flex justify-center">
<NavLink href="/signin" color="white" className="font-medium">
Sign In
</NavLink>
</div>
</Box>
</>
);
}

VerifyEmailPage.getLayout = function getLayout(page: ReactElement) {
return (
<UnauthenticatedLayout title="Verify your email">
{page}
</UnauthenticatedLayout>
);
};
14 changes: 11 additions & 3 deletions dashboard/src/pages/signin/email.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import { getToastStyleProps } from '@/utils/constants/settings';
import { yupResolver } from '@hookform/resolvers/yup';
import { styled } from '@mui/material';
import { useSignInEmailPassword } from '@nhost/nextjs';
import type { ReactElement } from 'react';
import { useEffect } from 'react';
import { useRouter } from 'next/router';
import { useEffect, type ReactElement } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { toast } from 'react-hot-toast';
import * as Yup from 'yup';
Expand All @@ -31,6 +31,7 @@ const StyledInput = styled(Input)({

export default function EmailSignUpPage() {
const { signInEmailPassword, error } = useSignInEmailPassword();
const router = useRouter();

const form = useForm<EmailSignUpFormValues>({
reValidateMode: 'onSubmit',
Expand All @@ -56,7 +57,14 @@ export default function EmailSignUpPage() {

async function handleSubmit({ email, password }: EmailSignUpFormValues) {
try {
await signInEmailPassword(email, password);
const { needsEmailVerification } = await signInEmailPassword(
email,
password,
);

if (needsEmailVerification) {
router.push(`/email/verify?email=${email}`);
}
} catch {
toast.error(
'An error occurred while signing in. Please try again.',
Expand Down
12 changes: 11 additions & 1 deletion dashboard/src/pages/signup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { nhost } from '@/utils/nhost';
import { yupResolver } from '@hookform/resolvers/yup';
import { styled } from '@mui/material';
import { useSignUpEmailPassword } from '@nhost/nextjs';
import { useRouter } from 'next/router';
import type { ReactElement } from 'react';
import { useEffect, useState } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
Expand All @@ -36,6 +37,7 @@ const StyledInput = styled(Input)({
export default function SignUpPage() {
const { signUpEmailPassword, error } = useSignUpEmailPassword();
const [loading, setLoading] = useState(false);
const router = useRouter();

const form = useForm<SignUpFormValues>({
reValidateMode: 'onSubmit',
Expand Down Expand Up @@ -65,7 +67,15 @@ export default function SignUpPage() {
displayName,
}: SignUpFormValues) {
try {
await signUpEmailPassword(email, password, { displayName });
const { needsEmailVerification } = await signUpEmailPassword(
email,
password,
{ displayName },
);

if (needsEmailVerification) {
router.push(`/email/verify?email=${email}`);
}
} catch {
toast.error(
'An error occurred while signing up. Please try again.',
Expand Down

0 comments on commit 91c2bb6

Please sign in to comment.