diff --git a/packages/web/components/elements/FormElements.tsx b/packages/web/components/elements/FormElements.tsx index c94389250..1e5caa098 100644 --- a/packages/web/components/elements/FormElements.tsx +++ b/packages/web/components/elements/FormElements.tsx @@ -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 ( {input.options?.map((label, index) => ( - + + ) diff --git a/packages/web/components/elements/StyledText.tsx b/packages/web/components/elements/StyledText.tsx index 9b2bde36d..1f7b3a4f3 100644 --- a/packages/web/components/elements/StyledText.tsx +++ b/packages/web/components/elements/StyledText.tsx @@ -166,6 +166,8 @@ export const StyledText = styled('p', { }, }) +export const StyledTextSpan = styled('span', StyledText) + export const StyledListElement = styled('li', { fontFamily: 'Inter', fontWeight: 'normal', diff --git a/packages/web/components/templates/LoginForm.tsx b/packages/web/components/templates/LoginForm.tsx index 2068d0c2c..2812c8428 100644 --- a/packages/web/components/templates/LoginForm.tsx +++ b/packages/web/components/templates/LoginForm.tsx @@ -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 { Read-it-later for serious readers. - + Save articles and read them later in our distraction-free reader. - + Learn More -> @@ -68,41 +71,44 @@ export function LoginForm(props: LoginFormProps): JSX.Element { - {googleID && ( - - - - )} + {googleID && ( + + + + )} - + - {appleAuthRedirectURI && ( - - - - )} + {appleAuthRedirectURI && ( + + + + )} - + Continue with Email @@ -114,7 +120,7 @@ export function LoginForm(props: LoginFormProps): JSX.Element { function GoogleAuthButton() { return ( - +
('') - const [errorMessage, setErrorMessage] = useState(undefined) + const [errorMessage, setErrorMessage] = useState( + undefined + ) useEffect(() => { if (!router.isReady) return @@ -40,9 +27,24 @@ export function EmailForgotPassword(): JSX.Element { return (
- - Reset your password - + + + Reset your password + + Email { e.preventDefault(); setEmail(e.target.value); }} + onChange={(e) => { + e.preventDefault() + setEmail(e.target.value) + }} /> - - {errorMessage && ( - {errorMessage} - )} - - + - Don't have an account? {' '} + Don't have an account?{' '} - Sign up + + Sign up + - Forgot your password? {' '} + Forgot your password?{' '} - Click here + + Click here + diff --git a/packages/web/components/templates/auth/EmailResetPassword.tsx b/packages/web/components/templates/auth/EmailResetPassword.tsx index e58dbbf75..eafc284b2 100644 --- a/packages/web/components/templates/auth/EmailResetPassword.tsx +++ b/packages/web/components/templates/auth/EmailResetPassword.tsx @@ -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(undefined) const [password, setPassword] = useState('') - const [errorMessage, setErrorMessage] = useState(undefined) + const [errorMessage, setErrorMessage] = useState( + 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 ( - - + + Enter new password { e.preventDefault(); setPassword(e.target.value); }} + onChange={(e) => { + e.preventDefault() + setPassword(e.target.value) + }} /> - (Password must be at least 8 chars) + + (Password must be at least 8 chars) + - - {errorMessage && ( - {errorMessage} - )} - diff --git a/packages/web/components/templates/auth/EmailSignup.tsx b/packages/web/components/templates/auth/EmailSignup.tsx index 9a8a9b9c5..dbad57555 100644 --- a/packages/web/components/templates/auth/EmailSignup.tsx +++ b/packages/web/components/templates/auth/EmailSignup.tsx @@ -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(undefined) const [password, setPassword] = useState(undefined) const [fullname, setFullname] = useState(undefined) const [username, setUsername] = useState(undefined) - const [debouncedUsername, setDebouncedUsername] = useState(undefined) - const [errorMessage, setErrorMessage] = useState(undefined) + const [debouncedUsername, setDebouncedUsername] = useState< + string | undefined + >(undefined) + const [errorMessage, setErrorMessage] = useState( + undefined + ) useEffect(() => { if (!router.isReady) return @@ -62,9 +51,24 @@ export function EmailSignup(): JSX.Element { return ( - - Sign Up - + + + Sign Up + + Email { e.preventDefault(); setEmail(e.target.value); }} + onChange={(e) => { + e.preventDefault() + setEmail(e.target.value) + }} /> @@ -132,17 +139,20 @@ export function EmailSignup(): JSX.Element { {isUsernameValid && ( Username is available. )} - - {errorMessage && ( - {errorMessage} - )} - + + {errorMessage && {errorMessage}} + - - + - Already have an account? {' '} + Already have an account?{' '} - Login instead + + Login instead + diff --git a/packages/web/components/templates/auth/VerifyEmail.tsx b/packages/web/components/templates/auth/VerifyEmail.tsx index b7f0279f5..31e5c1427 100644 --- a/packages/web/components/templates/auth/VerifyEmail.tsx +++ b/packages/web/components/templates/auth/VerifyEmail.tsx @@ -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', }} > - -

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. + +

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. +
-
- + ) } diff --git a/packages/web/pages/invite/[inviteCode].tsx b/packages/web/pages/invite/[inviteCode].tsx index 6e68a1684..82202704d 100644 --- a/packages/web/pages/invite/[inviteCode].tsx +++ b/packages/web/pages/invite/[inviteCode].tsx @@ -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( + undefined + ) - const [error, setError] = useState(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 ( <> - Accepting invite to join -
+ + + + You're invited + + + + You have been invited to join a recommendation group on Omnivore. + Recommendation groups allow you to share articles with other group + members. + + {errorMessage && ( + + {errorMessage} + + )} + + + {viewerData?.me ? ( + + ) : ( + + )} + + + Don't have an Omnivore account?{' '} + + + Signup + + + + +
+ ) }