Clean up login and invite page UI
This commit is contained in:
@ -33,10 +33,27 @@ export const FormInput = styled('input', {
|
||||
},
|
||||
})
|
||||
|
||||
export const FormLabel = styled('label', {
|
||||
fontSize: '16px',
|
||||
color: '$omnivoreGray',
|
||||
})
|
||||
|
||||
export const BorderedFormInput = styled(FormInput, {
|
||||
height: '40px',
|
||||
margin: '0',
|
||||
padding: '4px 11px',
|
||||
color: 'rgba(0,0,0,.88)',
|
||||
fontSize: '14px',
|
||||
lineHeight: '1.6',
|
||||
listStyle: 'none',
|
||||
width: '100%',
|
||||
minWidth: '0',
|
||||
backgroundColor: '#fff',
|
||||
borderWidth: '1px',
|
||||
borderStyle: 'solid',
|
||||
borderColor: '#d9d9d9',
|
||||
borderRadius: '6px',
|
||||
border: `1px solid $grayBorder`,
|
||||
p: '$3',
|
||||
transition: 'all .2s',
|
||||
})
|
||||
|
||||
export function GeneralFormInput(props: FormInputProps): JSX.Element {
|
||||
@ -53,7 +70,7 @@ export function GeneralFormInput(props: FormInputProps): JSX.Element {
|
||||
return (
|
||||
<VStack>
|
||||
{input.options?.map((label, index) => (
|
||||
<HStack key={index} alignment='center'>
|
||||
<HStack key={index} alignment="center">
|
||||
<Checkbox
|
||||
key={index}
|
||||
checked={input.value[index]}
|
||||
@ -73,9 +90,19 @@ export function GeneralFormInput(props: FormInputProps): JSX.Element {
|
||||
)
|
||||
} else if (props.type === 'select') {
|
||||
return (
|
||||
<select onChange={input.onChange} style={{ padding: '8px', height: '38px', borderRadius: '6px', minWidth: '196px' }}>
|
||||
<select
|
||||
onChange={input.onChange}
|
||||
style={{
|
||||
padding: '8px',
|
||||
height: '38px',
|
||||
borderRadius: '6px',
|
||||
minWidth: '196px',
|
||||
}}
|
||||
>
|
||||
{input.options?.map((label, index) => (
|
||||
<option key={index} value={label}>{label}</option>
|
||||
<option key={index} value={label}>
|
||||
{label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
)
|
||||
|
||||
@ -166,6 +166,8 @@ export const StyledText = styled('p', {
|
||||
},
|
||||
})
|
||||
|
||||
export const StyledTextSpan = styled('span', StyledText)
|
||||
|
||||
export const StyledListElement = styled('li', {
|
||||
fontFamily: 'Inter',
|
||||
fontWeight: 'normal',
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import Link from 'next/link'
|
||||
import { StyledText } from '../elements/StyledText'
|
||||
import { StyledText, StyledTextSpan } from '../elements/StyledText'
|
||||
import { VStack, Box, SpanBox } from '../elements/LayoutPrimitives'
|
||||
import { styled } from '../tokens/stitches.config'
|
||||
import {
|
||||
@ -10,8 +10,6 @@ import {
|
||||
import AppleLogin from 'react-apple-login'
|
||||
import { AppleIdButton } from './auth/AppleIdButton'
|
||||
|
||||
const StyledTextSpan = styled('span', StyledText)
|
||||
|
||||
export type LoginFormProps = {
|
||||
errorMessage?: string
|
||||
}
|
||||
@ -41,25 +39,30 @@ export function LoginForm(props: LoginFormProps): JSX.Element {
|
||||
<StyledTextHeadline>
|
||||
Read-it-later for serious readers.
|
||||
</StyledTextHeadline>
|
||||
<StyledText css={{ fontStyle: 'normal',
|
||||
fontWeight: '400',
|
||||
fontSize: '18px',
|
||||
lineHeight: '120%',
|
||||
m: '0px',
|
||||
color: '$omnivoreGray',
|
||||
}}>
|
||||
<StyledText
|
||||
css={{
|
||||
fontStyle: 'normal',
|
||||
fontWeight: '400',
|
||||
fontSize: '18px',
|
||||
lineHeight: '120%',
|
||||
m: '0px',
|
||||
color: '$omnivoreGray',
|
||||
}}
|
||||
>
|
||||
Save articles and read them later in our distraction-free reader.
|
||||
</StyledText>
|
||||
<Link passHref href="/about">
|
||||
<a style={{ textDecoration: 'none' }}>
|
||||
<StyledText css={{
|
||||
fontStyle: 'normal',
|
||||
fontWeight: '400',
|
||||
fontSize: '18px',
|
||||
lineHeight: '120%',
|
||||
m: '0px',
|
||||
color: '$omnivoreGray',
|
||||
}}>
|
||||
<StyledText
|
||||
css={{
|
||||
fontStyle: 'normal',
|
||||
fontWeight: '400',
|
||||
fontSize: '18px',
|
||||
lineHeight: '120%',
|
||||
m: '0px',
|
||||
color: '$omnivoreGray',
|
||||
}}
|
||||
>
|
||||
Learn More ->
|
||||
</StyledText>
|
||||
</a>
|
||||
@ -68,41 +71,44 @@ export function LoginForm(props: LoginFormProps): JSX.Element {
|
||||
<SpanBox css={{ height: '24px' }} />
|
||||
|
||||
<VStack alignment="start" distribution="center">
|
||||
{googleID && (
|
||||
<Box
|
||||
style={{
|
||||
cursor: 'pointer',
|
||||
display: 'inline-flex',
|
||||
height: '40px',
|
||||
}}
|
||||
>
|
||||
<GoogleAuthButton />
|
||||
</Box>
|
||||
)}
|
||||
{googleID && (
|
||||
<Box
|
||||
style={{
|
||||
cursor: 'pointer',
|
||||
display: 'inline-flex',
|
||||
height: '40px',
|
||||
}}
|
||||
>
|
||||
<GoogleAuthButton />
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<Box style={{ height: '16px' }}></Box>
|
||||
<Box style={{ height: '16px' }}></Box>
|
||||
|
||||
{appleAuthRedirectURI && (
|
||||
<Box
|
||||
style={{
|
||||
cursor: 'pointer',
|
||||
display: 'inline-flex',
|
||||
width: '210px',
|
||||
height: '40px',
|
||||
}}
|
||||
>
|
||||
<AppleIdButton
|
||||
clientId="app.omnivore"
|
||||
scope="name email"
|
||||
state="web:login"
|
||||
redirectURI={appleAuthRedirectURI}
|
||||
responseMode="form_post"
|
||||
responseType="code id_token"
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
{appleAuthRedirectURI && (
|
||||
<Box
|
||||
style={{
|
||||
cursor: 'pointer',
|
||||
display: 'inline-flex',
|
||||
width: '210px',
|
||||
height: '40px',
|
||||
}}
|
||||
>
|
||||
<AppleIdButton
|
||||
clientId="app.omnivore"
|
||||
scope="name email"
|
||||
state="web:login"
|
||||
redirectURI={appleAuthRedirectURI}
|
||||
responseMode="form_post"
|
||||
responseType="code id_token"
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
<Link href="/auth/email-login" passHref>
|
||||
<StyledTextSpan style="actionLink" css={{ color: '$omnivoreGray', pt: '12px' }}>
|
||||
<StyledTextSpan
|
||||
style="actionLink"
|
||||
css={{ color: '$omnivoreGray', pt: '12px' }}
|
||||
>
|
||||
Continue with Email
|
||||
</StyledTextSpan>
|
||||
</Link>
|
||||
@ -114,7 +120,7 @@ export function LoginForm(props: LoginFormProps): JSX.Element {
|
||||
|
||||
function GoogleAuthButton() {
|
||||
return (
|
||||
<Box css={{ overflow: 'hidden' }}>
|
||||
<Box css={{ overflow: 'hidden' }}>
|
||||
<div
|
||||
id="g_id_onload"
|
||||
data-client_id={googleID}
|
||||
@ -122,7 +128,6 @@ function GoogleAuthButton() {
|
||||
data-ux_mode="popup"
|
||||
data-login_uri={gauthRedirectURI}
|
||||
data-auto_prompt="false"
|
||||
|
||||
/>
|
||||
|
||||
<div
|
||||
|
||||
@ -2,32 +2,19 @@ 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 { BorderedFormInput, FormLabel } 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<string>('')
|
||||
const [errorMessage, setErrorMessage] = useState<string | undefined>(undefined)
|
||||
const [errorMessage, setErrorMessage] = useState<string | undefined>(
|
||||
undefined
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (!router.isReady) return
|
||||
@ -40,9 +27,24 @@ export function EmailForgotPassword(): JSX.Element {
|
||||
|
||||
return (
|
||||
<form action={`${fetchEndpoint}/auth/forgot-password`} method="POST">
|
||||
<VStack alignment="center" css={{ padding: '16px' }}>
|
||||
<StyledText style="subHeadline" css={{ color: '$omnivoreGray' }}>Reset your password</StyledText>
|
||||
<VStack css={{ width: '100%', minWidth: '320px', gap: '16px', pb: '16px' }}>
|
||||
<VStack
|
||||
alignment="center"
|
||||
css={{
|
||||
padding: '16px',
|
||||
background: 'white',
|
||||
minWidth: '340px',
|
||||
width: '70vw',
|
||||
maxWidth: '576px',
|
||||
borderRadius: '8px',
|
||||
boxShadow: 'rgb(224 224 224) 9px 9px 9px -9px',
|
||||
}}
|
||||
>
|
||||
<StyledText style="subHeadline" css={{ color: '$omnivoreGray' }}>
|
||||
Reset your password
|
||||
</StyledText>
|
||||
<VStack
|
||||
css={{ width: '100%', minWidth: '320px', gap: '16px', pb: '16px' }}
|
||||
>
|
||||
<SpanBox css={{ width: '100%' }}>
|
||||
<FormLabel>Email</FormLabel>
|
||||
<BorderedFormInput
|
||||
@ -52,15 +54,16 @@ export function EmailForgotPassword(): JSX.Element {
|
||||
value={email}
|
||||
placeholder="Email"
|
||||
css={{ bg: 'white', color: 'black' }}
|
||||
onChange={(e) => { e.preventDefault(); setEmail(e.target.value); }}
|
||||
onChange={(e) => {
|
||||
e.preventDefault()
|
||||
setEmail(e.target.value)
|
||||
}}
|
||||
/>
|
||||
</SpanBox>
|
||||
</VStack>
|
||||
|
||||
{errorMessage && (
|
||||
<StyledText style="error">{errorMessage}</StyledText>
|
||||
)}
|
||||
<Button type="submit" style="ctaDarkYellow" css={{ my: '$2' }}>
|
||||
|
||||
{errorMessage && <StyledText style="error">{errorMessage}</StyledText>}
|
||||
<Button type="submit" style="ctaDarkYellow" css={{ my: '$2' }}>
|
||||
Reset Password
|
||||
</Button>
|
||||
<Button
|
||||
|
||||
@ -1,9 +1,8 @@
|
||||
import { HStack, SpanBox, VStack } from '../../elements/LayoutPrimitives'
|
||||
import { Button } from '../../elements/Button'
|
||||
import { StyledText } from '../../elements/StyledText'
|
||||
import { StyledText, StyledTextSpan } from '../../elements/StyledText'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { FormInput } from '../../elements/FormElements'
|
||||
import { TermAndConditionsFooter } from '../LoginForm'
|
||||
import { BorderedFormInput, FormLabel } from '../../elements/FormElements'
|
||||
import { fetchEndpoint } from '../../../lib/appConfig'
|
||||
import { logoutMutation } from '../../../lib/networking/mutations/logoutMutation'
|
||||
import { styled } from '@stitches/react'
|
||||
@ -12,26 +11,13 @@ import { parseErrorCodes } from '../../../lib/queryParamParser'
|
||||
import { formatMessage } from '../../../locales/en/messages'
|
||||
import Link from 'next/link'
|
||||
|
||||
const StyledTextSpan = styled('span', StyledText)
|
||||
|
||||
const BorderedFormInput = styled(FormInput, {
|
||||
height: '40px',
|
||||
paddingLeft: '6px',
|
||||
borderRadius: '6px',
|
||||
background: 'white',
|
||||
border: `1px solid 1px solid rgba(0, 0, 0, 0.06)`,
|
||||
})
|
||||
|
||||
const FormLabel = styled('label', {
|
||||
fontSize: '16px',
|
||||
color: '$omnivoreGray',
|
||||
})
|
||||
|
||||
export function EmailLogin(): JSX.Element {
|
||||
const router = useRouter()
|
||||
const [email, setEmail] = useState<string | undefined>(undefined)
|
||||
const [password, setPassword] = useState<string | undefined>(undefined)
|
||||
const [errorMessage, setErrorMessage] = useState<string | undefined>(undefined)
|
||||
const [errorMessage, setErrorMessage] = useState<string | undefined>(
|
||||
undefined
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (!router.isReady) return
|
||||
@ -44,9 +30,24 @@ export function EmailLogin(): JSX.Element {
|
||||
|
||||
return (
|
||||
<form action={`${fetchEndpoint}/auth/email-login`} method="POST">
|
||||
<VStack alignment="center" css={{ padding: '16px' }}>
|
||||
<StyledText style="subHeadline" css={{ color: '$omnivoreGray' }}>Login</StyledText>
|
||||
<VStack css={{ width: '100%', minWidth: '320px', gap: '16px', pb: '16px' }}>
|
||||
<VStack
|
||||
alignment="center"
|
||||
css={{
|
||||
padding: '16px',
|
||||
background: 'white',
|
||||
minWidth: '340px',
|
||||
width: '70vw',
|
||||
maxWidth: '576px',
|
||||
borderRadius: '8px',
|
||||
boxShadow: 'rgb(224 224 224) 9px 9px 9px -9px',
|
||||
}}
|
||||
>
|
||||
<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>
|
||||
<BorderedFormInput
|
||||
@ -56,7 +57,10 @@ export function EmailLogin(): JSX.Element {
|
||||
value={email}
|
||||
placeholder="Email"
|
||||
css={{ backgroundColor: 'white', color: 'black' }}
|
||||
onChange={(e) => { e.preventDefault(); setEmail(e.target.value); }}
|
||||
onChange={(e) => {
|
||||
e.preventDefault()
|
||||
setEmail(e.target.value)
|
||||
}}
|
||||
/>
|
||||
</SpanBox>
|
||||
|
||||
@ -73,10 +77,8 @@ export function EmailLogin(): JSX.Element {
|
||||
/>
|
||||
</SpanBox>
|
||||
</VStack>
|
||||
|
||||
{errorMessage && (
|
||||
<StyledText style="error">{errorMessage}</StyledText>
|
||||
)}
|
||||
|
||||
{errorMessage && <StyledText style="error">{errorMessage}</StyledText>}
|
||||
|
||||
<HStack
|
||||
alignment="center"
|
||||
@ -87,7 +89,11 @@ export function EmailLogin(): JSX.Element {
|
||||
height: '80px',
|
||||
}}
|
||||
>
|
||||
<Button style={'ctaOutlineYellow'} css={{ color: '$omnivoreGray', borderColor: '$omnivoreLightGray' }} type="button" onClick={async (event) => {
|
||||
<Button
|
||||
style={'ctaOutlineYellow'}
|
||||
css={{ color: '$omnivoreGray', borderColor: '$omnivoreLightGray' }}
|
||||
type="button"
|
||||
onClick={async (event) => {
|
||||
window.localStorage.removeItem('authVerified')
|
||||
window.localStorage.removeItem('authToken')
|
||||
try {
|
||||
@ -100,7 +106,9 @@ export function EmailLogin(): JSX.Element {
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button type="submit" style={'ctaDarkYellow'}>Login</Button>
|
||||
<Button type="submit" style={'ctaDarkYellow'}>
|
||||
Login
|
||||
</Button>
|
||||
</HStack>
|
||||
<StyledText
|
||||
style="action"
|
||||
@ -109,12 +117,15 @@ export function EmailLogin(): JSX.Element {
|
||||
pt: '16px',
|
||||
width: '100%',
|
||||
color: '$omnivoreLightGray',
|
||||
textAlign: 'center'
|
||||
textAlign: 'center',
|
||||
whiteSpace: 'normal',
|
||||
}}
|
||||
>
|
||||
Don't have an account? {' '}
|
||||
Don't have an account?{' '}
|
||||
<Link href="/auth/email-signup" passHref>
|
||||
<StyledTextSpan style="actionLink" css={{ color: '$omnivoreGray' }}>Sign up</StyledTextSpan>
|
||||
<StyledTextSpan style="actionLink" css={{ color: '$omnivoreGray' }}>
|
||||
Sign up
|
||||
</StyledTextSpan>
|
||||
</Link>
|
||||
</StyledText>
|
||||
<StyledText
|
||||
@ -124,12 +135,15 @@ export function EmailLogin(): JSX.Element {
|
||||
pt: '4px',
|
||||
width: '100%',
|
||||
color: '$omnivoreLightGray',
|
||||
textAlign: 'center'
|
||||
textAlign: 'center',
|
||||
whiteSpace: 'normal',
|
||||
}}
|
||||
>
|
||||
Forgot your password? {' '}
|
||||
Forgot your password?{' '}
|
||||
<Link href="/auth/forgot-password" passHref>
|
||||
<StyledTextSpan style="actionLink" css={{ color: '$omnivoreGray' }}>Click here</StyledTextSpan>
|
||||
<StyledTextSpan style="actionLink" css={{ color: '$omnivoreGray' }}>
|
||||
Click here
|
||||
</StyledTextSpan>
|
||||
</Link>
|
||||
</StyledText>
|
||||
</VStack>
|
||||
|
||||
@ -2,35 +2,20 @@ 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 { BorderedFormInput, FormLabel } 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'
|
||||
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)
|
||||
const [errorMessage, setErrorMessage] = useState<string | undefined>(
|
||||
undefined
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (!router.isReady) return
|
||||
@ -38,7 +23,7 @@ export function EmailResetPassword(): JSX.Element {
|
||||
const errorMsg = errorCode
|
||||
? formatMessage({ id: `error.${errorCode}` })
|
||||
: undefined
|
||||
|
||||
|
||||
console.log('errorCode', errorCode, errorMsg)
|
||||
|
||||
setErrorMessage(errorMsg)
|
||||
@ -51,8 +36,21 @@ export function EmailResetPassword(): JSX.Element {
|
||||
|
||||
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' }}>
|
||||
<VStack
|
||||
alignment="center"
|
||||
css={{
|
||||
padding: '16px',
|
||||
background: 'white',
|
||||
minWidth: '340px',
|
||||
width: '70vw',
|
||||
maxWidth: '576px',
|
||||
borderRadius: '8px',
|
||||
boxShadow: 'rgb(224 224 224) 9px 9px 9px -9px',
|
||||
}}
|
||||
>
|
||||
<VStack
|
||||
css={{ width: '100%', minWidth: '320px', gap: '16px', pb: '16px' }}
|
||||
>
|
||||
<SpanBox css={{ width: '100%' }}>
|
||||
<FormLabel>Enter new password</FormLabel>
|
||||
<BorderedFormInput
|
||||
@ -62,18 +60,21 @@ export function EmailResetPassword(): JSX.Element {
|
||||
value={password}
|
||||
placeholder="Password"
|
||||
css={{ bg: 'white', color: 'black' }}
|
||||
onChange={(e) => { e.preventDefault(); setPassword(e.target.value); }}
|
||||
onChange={(e) => {
|
||||
e.preventDefault()
|
||||
setPassword(e.target.value)
|
||||
}}
|
||||
/>
|
||||
<FormLabel css={{ fontSize: '12px' }}>(Password must be at least 8 chars)</FormLabel>
|
||||
<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' }}>
|
||||
|
||||
{errorMessage && <StyledText style="error">{errorMessage}</StyledText>}
|
||||
<Button type="submit" style="ctaDarkYellow" css={{ my: '$2' }}>
|
||||
Update Password
|
||||
</Button>
|
||||
</VStack>
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
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 { StyledText, StyledTextSpan } from '../../elements/StyledText'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { BorderedFormInput, FormLabel } from '../../elements/FormElements'
|
||||
import { TermAndConditionsFooter } from '../LoginForm'
|
||||
import { fetchEndpoint } from '../../../lib/appConfig'
|
||||
import { useValidateUsernameQuery } from '../../../lib/networking/queries/useValidateUsernameQuery'
|
||||
@ -13,29 +13,18 @@ import { formatMessage } from '../../../locales/en/messages'
|
||||
import { parseErrorCodes } from '../../../lib/queryParamParser'
|
||||
import Link from 'next/link'
|
||||
|
||||
const StyledTextSpan = styled('span', StyledText)
|
||||
|
||||
const BorderedFormInput = styled(FormInput, {
|
||||
height: '40px',
|
||||
paddingLeft: '6px',
|
||||
borderRadius: '6px',
|
||||
background: 'white',
|
||||
border: `1px solid 1px solid rgba(0, 0, 0, 0.06)`,
|
||||
})
|
||||
|
||||
const FormLabel = styled('label', {
|
||||
fontSize: '16px',
|
||||
color: '$omnivoreGray',
|
||||
})
|
||||
|
||||
export function EmailSignup(): JSX.Element {
|
||||
const router = useRouter()
|
||||
const [email, setEmail] = useState<string | undefined>(undefined)
|
||||
const [password, setPassword] = useState<string | undefined>(undefined)
|
||||
const [fullname, setFullname] = useState<string | undefined>(undefined)
|
||||
const [username, setUsername] = useState<string | undefined>(undefined)
|
||||
const [debouncedUsername, setDebouncedUsername] = useState<string | undefined>(undefined)
|
||||
const [errorMessage, setErrorMessage] = useState<string | undefined>(undefined)
|
||||
const [debouncedUsername, setDebouncedUsername] = useState<
|
||||
string | undefined
|
||||
>(undefined)
|
||||
const [errorMessage, setErrorMessage] = useState<string | undefined>(
|
||||
undefined
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (!router.isReady) return
|
||||
@ -62,9 +51,24 @@ export function EmailSignup(): JSX.Element {
|
||||
|
||||
return (
|
||||
<form action={`${fetchEndpoint}/auth/email-signup`} method="POST">
|
||||
<VStack alignment="center" css={{ padding: '16px' }}>
|
||||
<StyledText style="subHeadline" css={{ color: '$omnivoreGray' }}>Sign Up</StyledText>
|
||||
<VStack css={{ width: '100%', minWidth: '320px', gap: '16px', pb: '16px' }}>
|
||||
<VStack
|
||||
alignment="center"
|
||||
css={{
|
||||
padding: '16px',
|
||||
background: 'white',
|
||||
minWidth: '340px',
|
||||
width: '70vw',
|
||||
maxWidth: '576px',
|
||||
borderRadius: '8px',
|
||||
boxShadow: 'rgb(224 224 224) 9px 9px 9px -9px',
|
||||
}}
|
||||
>
|
||||
<StyledText style="subHeadline" css={{ color: '$omnivoreGray' }}>
|
||||
Sign Up
|
||||
</StyledText>
|
||||
<VStack
|
||||
css={{ width: '100%', minWidth: '320px', gap: '16px', pb: '16px' }}
|
||||
>
|
||||
<SpanBox css={{ width: '100%' }}>
|
||||
<FormLabel>Email</FormLabel>
|
||||
<BorderedFormInput
|
||||
@ -74,7 +78,10 @@ export function EmailSignup(): JSX.Element {
|
||||
value={email}
|
||||
placeholder="Email"
|
||||
css={{ backgroundColor: 'white', color: 'black' }}
|
||||
onChange={(e) => { e.preventDefault(); setEmail(e.target.value); }}
|
||||
onChange={(e) => {
|
||||
e.preventDefault()
|
||||
setEmail(e.target.value)
|
||||
}}
|
||||
/>
|
||||
</SpanBox>
|
||||
|
||||
@ -132,17 +139,20 @@ export function EmailSignup(): JSX.Element {
|
||||
{isUsernameValid && (
|
||||
<StyledText
|
||||
style="caption"
|
||||
css={{ m: 0, pl: '$2', alignSelf: 'flex-start', color: '$omnivoreGray' }}
|
||||
css={{
|
||||
m: 0,
|
||||
pl: '$2',
|
||||
alignSelf: 'flex-start',
|
||||
color: '$omnivoreGray',
|
||||
}}
|
||||
>
|
||||
Username is available.
|
||||
</StyledText>
|
||||
)}
|
||||
</VStack>
|
||||
|
||||
{errorMessage && (
|
||||
<StyledText style="error">{errorMessage}</StyledText>
|
||||
)}
|
||||
|
||||
|
||||
{errorMessage && <StyledText style="error">{errorMessage}</StyledText>}
|
||||
|
||||
<HStack
|
||||
alignment="center"
|
||||
distribution="end"
|
||||
@ -152,7 +162,11 @@ export function EmailSignup(): JSX.Element {
|
||||
height: '80px',
|
||||
}}
|
||||
>
|
||||
<Button style={'ctaOutlineYellow'} css={{ color: '$omnivoreGray', borderColor: 'rgba(0, 0, 0, 0.06)' }} type="button" onClick={async (event) => {
|
||||
<Button
|
||||
style={'ctaOutlineYellow'}
|
||||
css={{ color: '$omnivoreGray', borderColor: 'rgba(0, 0, 0, 0.06)' }}
|
||||
type="button"
|
||||
onClick={async (event) => {
|
||||
window.localStorage.removeItem('authVerified')
|
||||
window.localStorage.removeItem('authToken')
|
||||
try {
|
||||
@ -165,7 +179,9 @@ export function EmailSignup(): JSX.Element {
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button type="submit" style={'ctaDarkYellow'}>Sign Up</Button>
|
||||
<Button type="submit" style={'ctaDarkYellow'}>
|
||||
Sign Up
|
||||
</Button>
|
||||
</HStack>
|
||||
|
||||
<StyledText
|
||||
@ -173,12 +189,14 @@ export function EmailSignup(): JSX.Element {
|
||||
css={{
|
||||
pt: '16px',
|
||||
color: '$omnivoreLightGray',
|
||||
textAlign: 'center'
|
||||
textAlign: 'center',
|
||||
}}
|
||||
>
|
||||
Already have an account? {' '}
|
||||
Already have an account?{' '}
|
||||
<Link href="/auth/email-login" passHref>
|
||||
<StyledTextSpan style="actionLink" css={{ color: '$omnivoreGray' }}>Login instead</StyledTextSpan>
|
||||
<StyledTextSpan style="actionLink" css={{ color: '$omnivoreGray' }}>
|
||||
Login instead
|
||||
</StyledTextSpan>
|
||||
</Link>
|
||||
</StyledText>
|
||||
<TermAndConditionsFooter />
|
||||
|
||||
@ -1,7 +1,5 @@
|
||||
import { Box, HStack, MediumBreakpointBox, SpanBox, VStack } from '../../elements/LayoutPrimitives'
|
||||
import { Box, HStack } 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 (
|
||||
@ -13,23 +11,24 @@ export function VerifyEmail(props: LoginFormProps): JSX.Element {
|
||||
width: '100vw',
|
||||
height: '100vh',
|
||||
bg: '$omnivoreYellow',
|
||||
overflowY: 'clip'
|
||||
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
|
||||
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>
|
||||
</Box>
|
||||
</HStack>
|
||||
</HStack>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,45 +1,170 @@
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { Button } from '../../components/elements/Button'
|
||||
import { HStack, VStack } from '../../components/elements/LayoutPrimitives'
|
||||
import {
|
||||
StyledText,
|
||||
StyledTextSpan,
|
||||
} from '../../components/elements/StyledText'
|
||||
import { PageMetaData } from '../../components/patterns/PageMetaData'
|
||||
import { ProfileLayout } from '../../components/templates/ProfileLayout'
|
||||
import { joinGroupMutation } from '../../lib/networking/mutations/joinGroupMutation'
|
||||
import { useGetViewerQuery } from '../../lib/networking/queries/useGetViewerQuery'
|
||||
import { showSuccessToast } from '../../lib/toastHelpers'
|
||||
|
||||
export default function InvitePage(): JSX.Element {
|
||||
const router = useRouter()
|
||||
const { viewerData, viewerDataError, isLoading } = useGetViewerQuery()
|
||||
const { inviteCode } = router.query
|
||||
const [errorMessage, setErrorMessage] = useState<string | undefined>(
|
||||
undefined
|
||||
)
|
||||
|
||||
const [error, setError] = useState<string | undefined>(undefined)
|
||||
|
||||
// Check if the user is logged in and display an error message if they are not
|
||||
useEffect(() => {
|
||||
if (!router.isReady) {
|
||||
return
|
||||
}
|
||||
if (!inviteCode || typeof inviteCode != 'string') {
|
||||
setError('No invite code provided')
|
||||
}
|
||||
|
||||
const joinGroup = async () => {
|
||||
try {
|
||||
const result = await joinGroupMutation(inviteCode as string)
|
||||
if (!result) {
|
||||
throw 'Unknown error occurred.'
|
||||
}
|
||||
showSuccessToast(`You have been added to the ${result.name} group`)
|
||||
} catch (error) {
|
||||
setError('Unable to join group')
|
||||
if (!isLoading && router.isReady) {
|
||||
if (viewerDataError || !viewerData?.me) {
|
||||
setErrorMessage(
|
||||
'You are not logged in. You must log in or signup before accepting your invite.'
|
||||
)
|
||||
}
|
||||
}
|
||||
}, [isLoading, router, viewerData, viewerDataError])
|
||||
|
||||
const acceptClicked = useCallback(
|
||||
(event) => {
|
||||
event?.stopPropagation()
|
||||
|
||||
if (!router.isReady) {
|
||||
return
|
||||
}
|
||||
if (!inviteCode || typeof inviteCode != 'string') {
|
||||
setErrorMessage('No invite code provided')
|
||||
}
|
||||
|
||||
const joinGroup = async () => {
|
||||
try {
|
||||
const result = await joinGroupMutation(inviteCode as string)
|
||||
if (!result) {
|
||||
throw 'Unknown error occurred.'
|
||||
}
|
||||
showSuccessToast(`You have been added to the ${result.name} group`)
|
||||
router.push(`/home`)
|
||||
} catch (error) {
|
||||
console.log('error', error)
|
||||
setErrorMessage('Unable to join group')
|
||||
}
|
||||
}
|
||||
|
||||
joinGroup().catch((error) => {
|
||||
setErrorMessage(error)
|
||||
})
|
||||
},
|
||||
[router, inviteCode]
|
||||
)
|
||||
|
||||
joinGroup().catch((error) => {
|
||||
setError(error)
|
||||
})
|
||||
}, [router, inviteCode])
|
||||
return (
|
||||
<>
|
||||
<PageMetaData title="Accept Invite - Omnivore" path="/invite" />
|
||||
<ProfileLayout>Accepting invite to join</ProfileLayout>
|
||||
<div data-testid="invite-page-tag" />
|
||||
<ProfileLayout>
|
||||
<VStack
|
||||
alignment="center"
|
||||
css={{
|
||||
padding: '16px',
|
||||
background: 'white',
|
||||
minWidth: '340px',
|
||||
width: '70vw',
|
||||
maxWidth: '576px',
|
||||
borderRadius: '8px',
|
||||
boxShadow: 'rgb(224 224 224) 9px 9px 9px -9px',
|
||||
}}
|
||||
>
|
||||
<StyledText style="subHeadline" css={{ color: '$omnivoreGray' }}>
|
||||
You're invited
|
||||
</StyledText>
|
||||
|
||||
<StyledText
|
||||
style="action"
|
||||
css={{
|
||||
mt: '0px',
|
||||
pt: '4px',
|
||||
width: '100%',
|
||||
color: '$omnivoreLightGray',
|
||||
textAlign: 'center',
|
||||
whiteSpace: 'normal',
|
||||
}}
|
||||
>
|
||||
You have been invited to join a recommendation group on Omnivore.
|
||||
Recommendation groups allow you to share articles with other group
|
||||
members.
|
||||
</StyledText>
|
||||
{errorMessage && (
|
||||
<StyledText
|
||||
style="error"
|
||||
css={{
|
||||
mt: '0px',
|
||||
pt: '4px',
|
||||
width: '100%',
|
||||
textAlign: 'center',
|
||||
whiteSpace: 'normal',
|
||||
}}
|
||||
>
|
||||
{errorMessage}
|
||||
</StyledText>
|
||||
)}
|
||||
|
||||
<HStack
|
||||
alignment="center"
|
||||
distribution="center"
|
||||
css={{
|
||||
gap: '10px',
|
||||
width: '100%',
|
||||
height: '80px',
|
||||
}}
|
||||
>
|
||||
{viewerData?.me ? (
|
||||
<Button
|
||||
type="submit"
|
||||
style={'ctaDarkYellow'}
|
||||
onClick={acceptClicked}
|
||||
>
|
||||
Accept Invite
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
type="submit"
|
||||
style={'ctaDarkYellow'}
|
||||
onClick={() => router.push('/login')}
|
||||
>
|
||||
Login
|
||||
</Button>
|
||||
)}
|
||||
</HStack>
|
||||
<StyledText
|
||||
style="action"
|
||||
css={{
|
||||
m: '0px',
|
||||
pt: '16px',
|
||||
width: '100%',
|
||||
color: '$omnivoreLightGray',
|
||||
textAlign: 'center',
|
||||
whiteSpace: 'normal',
|
||||
}}
|
||||
>
|
||||
Don't have an Omnivore account?{' '}
|
||||
<Link href="/login" passHref>
|
||||
<StyledTextSpan
|
||||
style="actionLink"
|
||||
css={{ color: '$omnivoreGray' }}
|
||||
>
|
||||
Signup
|
||||
</StyledTextSpan>
|
||||
</Link>
|
||||
</StyledText>
|
||||
</VStack>
|
||||
<div data-testid="invite-page-tag" />
|
||||
</ProfileLayout>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user