Merge pull request #1020 from omnivore-app/fix/email-login-pages

Add confirm email landing page
This commit is contained in:
Jackson Harper
2022-07-26 21:16:56 -07:00
committed by GitHub
21 changed files with 440 additions and 253 deletions

View File

@ -106,33 +106,6 @@ export function LoginForm(props: LoginFormProps): JSX.Element {
/>
</Box>
)}
{/*
<StyledText
style="caption"
css={{
pt: '16px',
maxWidth: '220px',
textAlign: 'left',
color: '$omnivoreLightGray',
}}
>
<SpanBox>
Use your email address to{' '}
<Link href="/email-login" passHref>
<StyledTextSpan style="captionLink" css={{ color: '$omnivoreGray' }}>
Login
</StyledTextSpan>
</Link>{' '}
or{' '}
<Link href="/email-signup" passHref>
<StyledTextSpan style="captionLink" css={{ color: '$omnivoreGray' }}>
Signup
</StyledTextSpan>
</Link>{' '}
with your email address.
</SpanBox>
</StyledText> */}
</VStack>
<TermAndConditionsFooter />
</VStack>

View File

@ -1,23 +1,21 @@
import { SpanBox, VStack } from '../elements/LayoutPrimitives'
import { Button } from '../elements/Button'
import { StyledText } from '../elements/StyledText'
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 { TermAndConditionsFooter } from './LoginForm'
import { fetchEndpoint } from '../../lib/appConfig'
import { logoutMutation } from '../../lib/networking/mutations/logoutMutation'
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 StyledTextSpan = styled('span', StyledText)
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)`,
})
@ -26,9 +24,9 @@ const FormLabel = styled('label', {
color: '$omnivoreGray',
})
export function EmailResetPassword(): JSX.Element {
export function EmailForgotPassword(): JSX.Element {
const router = useRouter()
const [email, setEmail] = useState<string | undefined>(undefined)
const [email, setEmail] = useState<string>('')
const [errorMessage, setErrorMessage] = useState<string | undefined>(undefined)
useEffect(() => {
@ -41,7 +39,7 @@ export function EmailResetPassword(): JSX.Element {
}, [router.isReady, router.query])
return (
<form action={`${fetchEndpoint}/auth/email-signup`} method="POST">
<form action={`${fetchEndpoint}/auth/forgot-password`} method="POST">
<VStack alignment="center" css={{ padding: '16px' }}>
<StyledText style="subHeadline">Reset your password</StyledText>
<VStack css={{ width: '100%', minWidth: '320px', gap: '16px', pb: '16px' }}>

View File

@ -1,15 +1,15 @@
import { HStack, SpanBox, VStack } from '../elements/LayoutPrimitives'
import { Button } from '../elements/Button'
import { StyledText } from '../elements/StyledText'
import { HStack, 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 { TermAndConditionsFooter } from './LoginForm'
import { fetchEndpoint } from '../../lib/appConfig'
import { logoutMutation } from '../../lib/networking/mutations/logoutMutation'
import { FormInput } from '../../elements/FormElements'
import { TermAndConditionsFooter } from '../LoginForm'
import { fetchEndpoint } from '../../../lib/appConfig'
import { logoutMutation } from '../../../lib/networking/mutations/logoutMutation'
import { styled } from '@stitches/react'
import { useRouter } from 'next/router'
import { parseErrorCodes } from '../../lib/queryParamParser'
import { formatMessage } from '../../locales/en/messages'
import { parseErrorCodes } from '../../../lib/queryParamParser'
import { formatMessage } from '../../../locales/en/messages'
import Link from 'next/link'
const StyledTextSpan = styled('span', StyledText)
@ -45,7 +45,7 @@ export function EmailLogin(): JSX.Element {
return (
<form action={`${fetchEndpoint}/auth/email-login`} method="POST">
<VStack alignment="center" css={{ padding: '16px' }}>
<StyledText style="subHeadline">Login</StyledText>
<StyledText style="subHeadline" css={{ color: '$omnivoreGray' }}>Login</StyledText>
<VStack css={{ width: '100%', minWidth: '320px', gap: '16px', pb: '16px' }}>
<SpanBox css={{ width: '100%' }}>
<FormLabel>Email</FormLabel>
@ -111,7 +111,7 @@ export function EmailLogin(): JSX.Element {
}}
>
Don&apos;t have an account? {' '}
<Link href="/email-signup" passHref>
<Link href="/auth/email-signup" passHref>
<StyledTextSpan style="captionLink" css={{ color: '$omnivoreGray' }}>Sign up</StyledTextSpan>
</Link>
</StyledText>
@ -126,7 +126,7 @@ export function EmailLogin(): JSX.Element {
}}
>
Forgot your password? {' '}
<Link href="/email-reset-password" passHref>
<Link href="/auth/forgot-password" passHref>
<StyledTextSpan style="captionLink" css={{ color: '$omnivoreGray' }}>Click here</StyledTextSpan>
</Link>
</StyledText>

View File

@ -0,0 +1,81 @@
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 { TermAndConditionsFooter } from '../LoginForm'
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'
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)`,
})
const FormLabel = styled('label', {
fontSize: '16px',
color: '$omnivoreGray',
})
export function EmailResetPassword(): JSX.Element {
const router = useRouter()
const [token, setToken] = useState<string | undefined>(undefined)
const [password, setPassword] = useState<string>('')
const [errorMessage, setErrorMessage] = useState<string | undefined>(undefined)
useEffect(() => {
if (!router.isReady) return
const errorCode = parseErrorCodes(router.query)
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 <LoadingView />
}
return (
<form action={`${fetchEndpoint}/auth/reset-password`} method="POST">
<VStack alignment="center" css={{ padding: '16px' }}>
<VStack css={{ width: '100%', minWidth: '320px', gap: '16px', pb: '16px' }}>
<SpanBox css={{ width: '100%' }}>
<FormLabel>Enter new password</FormLabel>
<BorderedFormInput
type="password"
key="password"
name="password"
value={password}
placeholder="Password"
onChange={(e) => { e.preventDefault(); setPassword(e.target.value); }}
/>
<FormLabel css={{ fontSize: '12px' }}>(Password must be at least 8 chars)</FormLabel>
<input type="hidden" name="token" value={token} />
</SpanBox>
</VStack>
{errorMessage && (
<StyledText style="error">{errorMessage}</StyledText>
)}
<Button type="submit" style="ctaDarkYellow" css={{ my: '$2' }}>
Update Password
</Button>
</VStack>
</form>
)
}

View File

@ -1,16 +1,16 @@
import { HStack, SpanBox, VStack } from '../elements/LayoutPrimitives'
import { Button } from '../elements/Button'
import { StyledText } from '../elements/StyledText'
import { HStack, SpanBox, VStack } from '../../elements/LayoutPrimitives'
import { Button } from '../../elements/Button'
import { StyledText } from '../../elements/StyledText'
import { useCallback, useEffect, useMemo, useState } from 'react'
import { FormInput } from '../elements/FormElements'
import { TermAndConditionsFooter } from './LoginForm'
import { fetchEndpoint } from '../../lib/appConfig'
import { useValidateUsernameQuery } from '../../lib/networking/queries/useValidateUsernameQuery'
import { logoutMutation } from '../../lib/networking/mutations/logoutMutation'
import { FormInput } from '../../elements/FormElements'
import { TermAndConditionsFooter } from '../LoginForm'
import { fetchEndpoint } from '../../../lib/appConfig'
import { useValidateUsernameQuery } from '../../../lib/networking/queries/useValidateUsernameQuery'
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'
import { formatMessage } from '../../../locales/en/messages'
import { parseErrorCodes } from '../../../lib/queryParamParser'
import Link from 'next/link'
const StyledTextSpan = styled('span', StyledText)
@ -173,7 +173,7 @@ export function EmailSignup(): JSX.Element {
}}
>
Already have an account? {' '}
<Link href="/email-login" passHref>
<Link href="/auth/email-login" passHref>
<StyledTextSpan style="captionLink" css={{ color: '$omnivoreGray' }}>Login instead</StyledTextSpan>
</Link>
</StyledText>

View File

@ -0,0 +1,33 @@
import { Box, HStack } from '../../elements/LayoutPrimitives'
import type { LoginFormProps } from '../LoginForm'
export function ResetSent(props: LoginFormProps): JSX.Element {
return (
<>
<HStack
alignment="center"
distribution="start"
css={{
width: '100vw',
height: '100vh',
bg: '$omnivoreYellow',
overflowY: 'clip'
}}
>
<Box css={{
width: '100%',
margin: '40px',
color: '$omnivoreGray',
'@xl': { margin: '138px' },
}}>
<h1>Reset email sent</h1>
<Box>
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.
</Box>
</Box>
</HStack>
</>
)
}

View File

@ -0,0 +1,35 @@
import { Box, HStack, MediumBreakpointBox, SpanBox, VStack } from '../../elements/LayoutPrimitives'
import type { LoginFormProps } from '../LoginForm'
import { OmnivoreNameLogo } from '../../elements/images/OmnivoreNameLogo'
import { theme } from '../../tokens/stitches.config'
export function VerifyEmail(props: LoginFormProps): JSX.Element {
return (
<>
<HStack
alignment="center"
distribution="start"
css={{
width: '100vw',
height: '100vh',
bg: '$omnivoreYellow',
overflowY: 'clip'
}}
>
<Box css={{
width: '100%',
margin: '40px',
color: '$omnivoreGray',
'@xl': { margin: '138px' },
}}>
<h1>Verify your email address</h1>
<Box>
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.
</Box>
</Box>
</HStack>
</>
)
}

View File

@ -24,6 +24,8 @@ const errorMessages: Record<string, string> = {
'error.EXPIRED_TOKEN':
"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<string, string> = {

View File

@ -0,0 +1,40 @@
import { useEffect, useRef, useState } from 'react'
import { useRouter } from 'next/router'
import { HStack } from '../../../components/elements/LayoutPrimitives'
import { fetchEndpoint } from '../../../lib/appConfig'
import { LoadingView } from '../../../components/patterns/LoadingView'
import { PageMetaData } from '../../../components/patterns/PageMetaData'
import { ProfileLayout } from '../../../components/templates/ProfileLayout'
export default function ConfirmEmail(): JSX.Element {
const authForm = useRef<HTMLFormElement | null>(null)
const router = useRouter()
useEffect(() => {
if (!router || !router.isReady || !authForm.current) {
return
}
authForm.current?.submit()
}, [router, authForm])
return (
<>
<PageMetaData title="Confirm Email - Omnivore" path="/confirm-email" />
<ProfileLayout>
<form
ref={authForm}
method="POST"
action={`${fetchEndpoint}/auth/confirm-email`}
>
<input type="hidden" name="token" value={router.query.token} />
</form>
<HStack css={{ bg: '$grayBg', padding: '24px', width: '100%', height: '100%'}}>
<LoadingView />
</HStack>
</ProfileLayout>
<div data-testid="confirm-email-page-tag" />
</>
)
}

View File

@ -0,0 +1,15 @@
import { PageMetaData } from '../../components/patterns/PageMetaData'
import { ProfileLayout } from '../../components/templates/ProfileLayout'
import { EmailLogin } from '../../components/templates/auth/EmailLogin'
export default function EmailLoginPage(): JSX.Element {
return (
<>
<PageMetaData title="Login - Omnivore" path="/email-login" />
<ProfileLayout>
<EmailLogin />
</ProfileLayout>
<div data-testid="email-login-page-tag" />
</>
)
}

View File

@ -0,0 +1,15 @@
import { PageMetaData } from '../../components/patterns/PageMetaData'
import { ProfileLayout } from '../../components/templates/ProfileLayout'
import { EmailSignup } from '../../components/templates/auth/EmailSignup'
export default function EmailRegistrationPage(): JSX.Element {
return (
<>
<PageMetaData title="Sign up with Email - Omnivore" path="/auth-signup" />
<ProfileLayout>
<EmailSignup />
</ProfileLayout>
<div data-testid="auth-signup-page-tag" />
</>
)
}

View File

@ -0,0 +1,24 @@
import { PageMetaData } from '../../components/patterns/PageMetaData'
import { ProfileLayout } from '../../components/templates/ProfileLayout'
import { EmailForgotPassword } from '../../components/templates/auth/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 {
return (
<>
<PageMetaData title="Reset your password - Omnivore" path="/auth-forgot-password" />
<Toaster
containerStyle={{
top: '5rem',
}}
/>
<ProfileLayout>
<EmailForgotPassword />
</ProfileLayout>
<div data-testid="auth-forgot-password-page-tag" />
</>
)
}

View File

@ -0,0 +1,15 @@
import { PageMetaData } from '../../../components/patterns/PageMetaData'
import { ProfileLayout } from '../../../components/templates/ProfileLayout'
import { EmailResetPassword } from '../../../components/templates/auth/EmailResetPassword'
export default function EmailRegistrationPage(): JSX.Element {
return (
<>
<PageMetaData title="Reset your password - Omnivore" path="/auth-forgot-password" />
<ProfileLayout>
<EmailResetPassword />
</ProfileLayout>
<div data-testid="auth-forgot-password-page-tag" />
</>
)
}

View File

@ -0,0 +1,15 @@
import { PageMetaData } from '../../components/patterns/PageMetaData'
import { ProfileLayout } from '../../components/templates/ProfileLayout'
import { ResetSent } from '../../components/templates/auth/ResetSent'
export default function EmailResetSent(): JSX.Element {
return (
<>
<PageMetaData title="Reset password email sent - Omnivore" path="/auth-reset-sent" />
<ProfileLayout>
<ResetSent />
</ProfileLayout>
<div data-testid="auth-reset-sent-page-tag" />
</>
)
}

View File

@ -0,0 +1,12 @@
import { PageMetaData } from '../../components/patterns/PageMetaData'
import { VerifyEmail } from '../../components/templates/auth/VerifyEmail'
export default function VerifyEmailPage(): JSX.Element {
return (
<>
<PageMetaData title="Verify Email" path="/auth/verify-email" />
<VerifyEmail />
<div data-testid="auth/verify-email-page-tag" />
</>
)
}

View File

@ -1,56 +0,0 @@
import { PageMetaData } from '../components/patterns/PageMetaData'
import { ProfileLayout } from '../components/templates/ProfileLayout'
import { EmailLogin } from '../components/templates/EmailLogin'
export default function EmailLoginPage(): JSX.Element {
return (
<>
<PageMetaData title="Login - Omnivore" path="/email-login" />
<ProfileLayout>
<EmailLogin />
</ProfileLayout>
<div data-testid="email-login-page-tag" />
</>
)
}
// export default function EmailLogin(): JSX.Element {
// const [errorMessage, setErrorMessage] = useState<string | undefined>(
// undefined
// )
// const [message, setMessage] = useState<string | undefined>(undefined)
// const router = useRouter()
// useEffect(() => {
// if (!router.isReady) return
// const errorCode = parseErrorCodes(router.query)
// const errorMsg = errorCode
// ? formatMessage({ id: `error.${errorCode}` })
// : undefined
// setErrorMessage(errorMsg)
// const message = router.query.message
// ? formatMessage({ id: `login.${router.query.message}` })
// : undefined
// setMessage(message)
// }, [router.isReady, router.query])
// return (
// <PrimaryLayout pageTestId="email-login">
// {message && <StyledText style={'headline'}>{message}</StyledText>}
// <h1>Email Login</h1>
// <form action={`${fetchEndpoint}/auth/email-login`} method="POST">
// <div>
// <label>Email</label>
// <input type="email" name={'email'} required />
// </div>
// <div>
// <label>Password</label>
// <input type="password" name={'password'} required />
// </div>
// {errorMessage && <StyledText style="error">{errorMessage}</StyledText>}
// <button type="submit">Login</button>
// </form>
// </PrimaryLayout>
// )
// }

View File

@ -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 (
<>
<PageMetaData title="Reset your password - Omnivore" path="/email-reset-password" />
<ProfileLayout>
<EmailResetPassword />
</ProfileLayout>
<div data-testid="email-reset-password-page-tag" />
</>
)
}

View File

@ -1,15 +0,0 @@
import { PageMetaData } from '../components/patterns/PageMetaData'
import { ProfileLayout } from '../components/templates/ProfileLayout'
import { EmailSignup } from '../components/templates/EmailSignup'
export default function EmailRegistrationPage(): JSX.Element {
return (
<>
<PageMetaData title="Sign up with Email - Omnivore" path="/email-signup" />
<ProfileLayout>
<EmailSignup />
</ProfileLayout>
<div data-testid="email-signup-page-tag" />
</>
)
}