diff --git a/packages/api/src/routers/auth/auth_router.ts b/packages/api/src/routers/auth/auth_router.ts index 9a618279a..e46bcde8a 100644 --- a/packages/api/src/routers/auth/auth_router.ts +++ b/packages/api/src/routers/auth/auth_router.ts @@ -557,13 +557,13 @@ export function authRouter() { }) if (!user) { return res.redirect( - `${env.client.url}/auth/forgot-password?errorCodes=USER_NOT_FOUND` + `${env.client.url}/auth/auth/reset-sent` ) } if (user.status === StatusType.Pending) { return res.redirect( - `${env.client.url}/auth/email-login?errorCodes=PENDING_VERIFICATION` + `${env.client.url}/auth/auth/reset-sent` ) } @@ -573,7 +573,7 @@ export function authRouter() { ) } - res.redirect(`${env.client.url}/auth/forgot-password?message=SUCCESS`) + res.redirect(`${env.client.url}/auth/reset-sent`) } catch (e) { logger.info('forgot-password exception:', e) @@ -598,20 +598,20 @@ export function authRouter() { const claims = await getClaimsByToken(token) if (!claims) { return res.redirect( - `${env.client.url}/auth/reset-password?errorCodes=INVALID_TOKEN` + `${env.client.url}/auth/reset-password/${token}?errorCodes=INVALID_TOKEN` ) } - if (!password) { + if (!password || password.length < 8) { return res.redirect( - `${env.client.url}/auth/reset-password?errorCodes=INVALID_PASSWORD` + `${env.client.url}/auth/reset-password/${token}?errorCodes=INVALID_PASSWORD` ) } const user = await getRepository(User).findOneBy({ id: claims.uid }) if (!user) { return res.redirect( - `${env.client.url}/auth/reset-password?errorCodes=USER_NOT_FOUND` + `${env.client.url}/auth/reset-password/${token}?errorCodes=USER_NOT_FOUND` ) } @@ -632,11 +632,11 @@ export function authRouter() { ) if (!updated.affected) { return res.redirect( - `${env.client.url}/auth/reset-password?errorCodes=UNKNOWN` + `${env.client.url}/auth/reset-password/${token}?errorCodes=UNKNOWN` ) } - res.redirect(`${env.client.url}/auth/reset-password?message=SUCCESS`) + await handleSuccessfulLogin(req, res, user, false) } catch (e) { logger.info('reset-password exception:', e) if (e instanceof jwt.TokenExpiredError) { diff --git a/packages/web/components/templates/EmailForgotPassword.tsx b/packages/web/components/templates/EmailForgotPassword.tsx new file mode 100644 index 000000000..b82ba9674 --- /dev/null +++ b/packages/web/components/templates/EmailForgotPassword.tsx @@ -0,0 +1,92 @@ +import { SpanBox, VStack } from '../elements/LayoutPrimitives' +import { Button } from '../elements/Button' +import { StyledText } from '../elements/StyledText' +import { useEffect, useState } from 'react' +import { FormInput } from '../elements/FormElements' +import { fetchEndpoint } from '../../lib/appConfig' +import { logoutMutation } from '../../lib/networking/mutations/logoutMutation' +import { styled } from '@stitches/react' +import { useRouter } from 'next/router' +import { formatMessage } from '../../locales/en/messages' +import { parseErrorCodes } from '../../lib/queryParamParser' + +const BorderedFormInput = styled(FormInput, { + height: '40px', + paddingLeft: '6px', + borderRadius: '6px', + background: 'white', + color: '$omnivoreGray', + border: `1px solid 1px solid rgba(0, 0, 0, 0.06)`, +}) + +const FormLabel = styled('label', { + fontSize: '16px', + color: '$omnivoreGray', +}) + +export function EmailForgotPassword(): JSX.Element { + const router = useRouter() + const [email, setEmail] = useState('') + const [errorMessage, setErrorMessage] = useState(undefined) + + useEffect(() => { + if (!router.isReady) return + const errorCode = parseErrorCodes(router.query) + const errorMsg = errorCode + ? formatMessage({ id: `error.${errorCode}` }) + : undefined + setErrorMessage(errorMsg) + }, [router.isReady, router.query]) + + return ( +
+ + Reset your password + + + Email + { e.preventDefault(); setEmail(e.target.value); }} + /> + + + + {errorMessage && ( + {errorMessage} + )} + + + +
+ ) +} diff --git a/packages/web/components/templates/EmailResetPassword.tsx b/packages/web/components/templates/EmailResetPassword.tsx index 4d4b256db..af9a69431 100644 --- a/packages/web/components/templates/EmailResetPassword.tsx +++ b/packages/web/components/templates/EmailResetPassword.tsx @@ -10,14 +10,14 @@ import { styled } from '@stitches/react' import { useRouter } from 'next/router' import { formatMessage } from '../../locales/en/messages' import { parseErrorCodes } from '../../lib/queryParamParser' - -const StyledTextSpan = styled('span', StyledText) +import { LoadingView } from '../patterns/LoadingView' const BorderedFormInput = styled(FormInput, { height: '40px', paddingLeft: '6px', borderRadius: '6px', background: 'white', + color: '$omnivoreGray', border: `1px solid 1px solid rgba(0, 0, 0, 0.06)`, }) @@ -28,7 +28,8 @@ const FormLabel = styled('label', { export function EmailResetPassword(): JSX.Element { const router = useRouter() - const [email, setEmail] = useState(undefined) + const [token, setToken] = useState(undefined) + const [password, setPassword] = useState('') const [errorMessage, setErrorMessage] = useState(undefined) useEffect(() => { @@ -37,24 +38,34 @@ export function EmailResetPassword(): JSX.Element { const errorMsg = errorCode ? formatMessage({ id: `error.${errorCode}` }) : undefined + + console.log('errorCode', errorCode, errorMsg) + setErrorMessage(errorMsg) + setToken(router.query.token as string) }, [router.isReady, router.query]) + if (!token) { + return + } + return ( -
+ - Reset your password - Email + Enter new password { e.preventDefault(); setEmail(e.target.value); }} + type="password" + key="password" + name="password" + value={password} + placeholder="Password" + onChange={(e) => { e.preventDefault(); setPassword(e.target.value); }} /> + (Password must be at least 8 chars) + + @@ -62,31 +73,7 @@ export function EmailResetPassword(): JSX.Element { {errorMessage} )} -
diff --git a/packages/web/components/templates/ResetSent.tsx b/packages/web/components/templates/ResetSent.tsx new file mode 100644 index 000000000..5fd4741ae --- /dev/null +++ b/packages/web/components/templates/ResetSent.tsx @@ -0,0 +1,33 @@ +import { Box, HStack } from '../elements/LayoutPrimitives' +import type { LoginFormProps } from './LoginForm' + +export function ResetSent(props: LoginFormProps): JSX.Element { + return ( + <> + + +

Reset email sent

+ + If there is an account assosciated with the email specified we sent a + password reset link. Click the link to reset your password. You may need + to check your spam folder. + +
+
+ + ) +} diff --git a/packages/web/components/templates/VerifyEmail.tsx b/packages/web/components/templates/VerifyEmail.tsx index 28a9103c5..f57a8f7d0 100644 --- a/packages/web/components/templates/VerifyEmail.tsx +++ b/packages/web/components/templates/VerifyEmail.tsx @@ -24,30 +24,12 @@ export function VerifyEmail(props: LoginFormProps): JSX.Element { }}>

Verify your email address

- We sent a verification link to the email you provided. Click the link to verify your email. You may need to check your spam folder. + We sent a verification link to the email you provided. + Click the link to verify your email. You may need to check + your spam folder. - - - - - ) } diff --git a/packages/web/locales/en/messages.ts b/packages/web/locales/en/messages.ts index 8a1d2f65f..306e427c1 100644 --- a/packages/web/locales/en/messages.ts +++ b/packages/web/locales/en/messages.ts @@ -25,6 +25,7 @@ const errorMessages: Record = { "Your sign up page has timed out, you'll be redirected to Google sign in page to authenticate again.", 'error.USER_EXISTS': 'User with this email exists already', 'error.UNKNOWN': 'An unknown error occurred', + 'error.INVALID_PASSWORD': 'Invalid password. Password must be at least 8 chars.' } const loginPageMessages: Record = { diff --git a/packages/web/pages/auth/forgot-password.tsx b/packages/web/pages/auth/forgot-password.tsx index d1ee0a922..4257a80f7 100644 --- a/packages/web/pages/auth/forgot-password.tsx +++ b/packages/web/pages/auth/forgot-password.tsx @@ -1,24 +1,12 @@ import { PageMetaData } from '../../components/patterns/PageMetaData' import { ProfileLayout } from '../../components/templates/ProfileLayout' -import { EmailResetPassword } from '../../components/templates/EmailResetPassword' +import { EmailForgotPassword } from '../../components/templates/EmailForgotPassword' import { useEffect } from 'react' import { useRouter } from 'next/router' import toast, { Toaster } from 'react-hot-toast' import { showSuccessToast } from '../../lib/toastHelpers' export default function ForgotPassword(): JSX.Element { - const router = useRouter() - - useEffect(() => { - if (router && router.isReady && router.query.message === 'SUCCESS') { - showSuccessToast('Reset password email sent') - setTimeout(() => { - window.location.href = '/email-login' - }, 2000) - } - }, [router]) - - return ( <> @@ -28,7 +16,7 @@ export default function ForgotPassword(): JSX.Element { }} /> - +
diff --git a/packages/web/pages/auth/reset-password.tsx b/packages/web/pages/auth/reset-password.tsx deleted file mode 100644 index f2dff55ba..000000000 --- a/packages/web/pages/auth/reset-password.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { PageMetaData } from '../../components/patterns/PageMetaData' -import { ProfileLayout } from '../../components/templates/ProfileLayout' -import { EmailResetPassword } from '../../components/templates/EmailResetPassword' - -export default function EmailRegistrationPage(): JSX.Element { - return ( - <> - - - - -
- - ) -} diff --git a/packages/web/pages/auth/reset-password/[token].tsx b/packages/web/pages/auth/reset-password/[token].tsx new file mode 100644 index 000000000..df307ee1e --- /dev/null +++ b/packages/web/pages/auth/reset-password/[token].tsx @@ -0,0 +1,15 @@ +import { PageMetaData } from '../../../components/patterns/PageMetaData' +import { ProfileLayout } from '../../../components/templates/ProfileLayout' +import { EmailResetPassword } from '../../../components/templates/EmailResetPassword' + +export default function EmailRegistrationPage(): JSX.Element { + return ( + <> + + + + +
+ + ) +} diff --git a/packages/web/pages/auth/reset-sent.tsx b/packages/web/pages/auth/reset-sent.tsx new file mode 100644 index 000000000..47c054d55 --- /dev/null +++ b/packages/web/pages/auth/reset-sent.tsx @@ -0,0 +1,15 @@ +import { PageMetaData } from '../../components/patterns/PageMetaData' +import { ProfileLayout } from '../../components/templates/ProfileLayout' +import { ResetSent } from '../../components/templates/ResetSent' + +export default function EmailResetSent(): JSX.Element { + return ( + <> + + + + +
+ + ) +}