Skip to content

Commit

Permalink
[C-3271] Implement new sign up flow email page (desktop web) (#6464)
Browse files Browse the repository at this point in the history
  • Loading branch information
DejayJD authored Oct 27, 2023
1 parent 4262e71 commit 1fc8456
Show file tree
Hide file tree
Showing 13 changed files with 229 additions and 39 deletions.
1 change: 1 addition & 0 deletions packages/common/src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ export * from './purchases'
export * from './utils'
export * from './topArtists'
export * from './account'
export * from './signUp'
4 changes: 3 additions & 1 deletion packages/common/src/api/reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { favoritesApiReducer } from './favorites'
import { libraryApiReducer } from './library'
import { purchasesApiReducer } from './purchases'
import { relatedArtistsApiReducer } from './relatedArtists'
import { signUpReducer } from './signUp'
import { topArtistsApiReducer } from './topArtists'
import { trackApiReducer } from './track'
import { trendingApiReducer } from './trending'
Expand All @@ -23,5 +24,6 @@ export default combineReducers({
libraryApi: libraryApiReducer,
purchasesApi: purchasesApiReducer,
topArtistsApi: topArtistsApiReducer,
accountApi: accountApiReducer
accountApi: accountApiReducer,
signUpApi: signUpReducer
})
17 changes: 17 additions & 0 deletions packages/common/src/api/signUp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { createApi } from 'audius-query'

const signUpApi = createApi({
reducerPath: 'signUpApi',
endpoints: {
isEmailInUse: {
fetch: async ({ email }, { audiusBackend }) => {
return await audiusBackend.emailInUse(email)
},
options: {}
}
}
})

export const { useIsEmailInUse } = signUpApi.hooks
export const signUpReducer = signUpApi.reducer
export const signUpFetch = signUpApi.fetch
Original file line number Diff line number Diff line change
Expand Up @@ -1710,7 +1710,7 @@ export const audiusBackend = ({
try {
const { exists: emailExists } =
await audiusLibs.Account.checkIfEmailRegistered(email)
return emailExists
return emailExists as boolean
} catch (error) {
console.error(getErrorMessage(error))
throw error
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
/* The contentContainer is the root container of all things that aren't assistive text */
.contentContainer {
display: flex;
width: 100%;
align-items: center;
/* Dont need Y padding since the flex centering takes care of it */
padding: 0 var(--harmony-spacing-l);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,13 @@ export const TextInput = forwardRef<HTMLInputElement, TextInputProps>(
)

return (
<Flex className={cn(styles.root, className)} direction='column' gap='xs'>
<Flex
className={cn(styles.root, className)}
direction='column'
gap='xs'
alignItems='flex-start'
w='100%'
>
<label
htmlFor={id}
className={cn(
Expand Down
15 changes: 13 additions & 2 deletions packages/web/src/components/form-fields/HarmonyTextField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,17 @@ import { useField } from 'formik'

export type TextFieldProps = TextInputProps & {
name: string
/**
* Clears out field errors while the input is being changed for a small UX improvement
* @default true
*/
clearErrorOnChange?: boolean
}

// TODO: rename to TextField and replace old usages
export const HarmonyTextField = (props: TextFieldProps) => {
const { name, ...other } = props
const [field, { touched, error }] = useField(name)
const { name, clearErrorOnChange = true, ...other } = props
const [field, { touched, error }, { setError }] = useField(name)

const hasError = Boolean(touched && error)

Expand All @@ -18,6 +23,12 @@ export const HarmonyTextField = (props: TextFieldProps) => {
{...field}
error={hasError}
helperText={hasError ? error : undefined}
onChange={(e) => {
if (clearErrorOnChange) {
setError(undefined)
}
field.onChange(e)
}}
{...other}
/>
)
Expand Down
1 change: 0 additions & 1 deletion packages/web/src/pages/sign-up-page/SignUpRootPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ export const SignUpRootPage = () => {
) : null}
{signUpState.stage === 'create-password' ? (
<CreatePasswordPage
params={signUpState.params}
onPrevious={setSignUpState}
onNext={setSignUpState}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,6 @@ const messages = {

export type CreatePasswordState = {
stage: 'create-password'
params: {
email: string
}
}

const initialValues = {
Expand All @@ -38,14 +35,13 @@ type CreatePasswordValues = {
}

type CreatePasswordPageProps = {
params: { email: string }
onPrevious: (state: SignUpState) => void
onNext: (state: PickHandleState) => void
}

export const CreatePasswordPage = (props: CreatePasswordPageProps) => {
const { params, onNext } = props
const { email } = params
const { onNext } = props
const { email = '' } = {}
const dispatch = useDispatch()

const handleSubmit = useCallback(
Expand Down
19 changes: 19 additions & 0 deletions packages/web/src/pages/sign-up-page/pages/SignUpPage.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
.logo {
max-height: 160px;
max-width: 160px;
height: 100%;
width: 100%;
object-fit: contain;
}

.leftAlignText {
text-align: left;
}

.flex1 {
flex: 1;
}

.w100 {
width: 100%;
}
181 changes: 159 additions & 22 deletions packages/web/src/pages/sign-up-page/pages/SignUpPage.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,43 @@
import { useCallback } from 'react'
import { useCallback, useContext } from 'react'

import { Button, ButtonType, Text } from '@audius/harmony'
import { Form, Formik } from 'formik'
import { AudiusQueryContext, signUpFetch } from '@audius/common'
import {
Button,
ButtonType,
Divider,
Flex,
IconArrowRight,
Text,
SocialButton
} from '@audius/harmony'
import { Form, Formik, FormikHelpers } from 'formik'
import { useDispatch } from 'react-redux'
import { z } from 'zod'
import { toFormikValidationSchema } from 'zod-formik-adapter'

import { TextField } from 'components/form-fields'
import audiusLogoColored from 'assets/img/audiusLogoColored.png'
import { setValueField } from 'common/store/pages/signon/actions'
import { HarmonyTextField } from 'components/form-fields/HarmonyTextField'
import { Link } from 'components/link'
import PreloadImage from 'components/preload-image/PreloadImage'
import { useNavigateToPage } from 'hooks/useNavigateToPage'
import { EMAIL_REGEX } from 'utils/email'
import { SIGN_IN_PAGE } from 'utils/route'

import { CreatePasswordState } from './CreatePasswordPage'
import styles from './SignUpPage.module.css'

const messages = {
header: 'Sign Up For Audius',
title: 'Sign Up For Audius',
emailLabel: 'Email',
signUp: 'Sign Up Free',
haveAccount: 'Already have an account?',
signIn: 'Sign In'
signIn: 'Sign In',
subHeader:
'Join the revolution in music streaming! Discover, connect, and create on Audius.',
socialsDividerText: 'Or, get started with one of your socials',
invalidEmail: 'Please enter a valid email.',
unknownError: 'Unknown error occurred.'
}

export type SignUpState = {
Expand All @@ -34,30 +57,144 @@ type SignUpEmailValues = {
email: string
}

const FormSchema = z.object({
email: z
.string({ required_error: messages.invalidEmail })
.regex(EMAIL_REGEX, { message: messages.invalidEmail })
})

export const SignUpPage = (props: SignUpPageProps) => {
const { onNext } = props

const handleSubmit = useCallback(
(values: SignUpEmailValues) => {
const dispatch = useDispatch()
const navigate = useNavigateToPage()
const queryContext = useContext(AudiusQueryContext)
const submitHandler = useCallback(
async (
values: SignUpEmailValues,
{ setErrors }: FormikHelpers<SignUpEmailValues>
) => {
const { email } = values
onNext({ stage: 'create-password', params: { email } })
if (queryContext !== null) {
try {
// Check identity API for existing emails
const emailExists = await signUpFetch.isEmailInUse(
{ email },
queryContext
)
// Set the email in the store
dispatch(setValueField('email', values.email))
if (emailExists) {
// Redirect to sign in if the email exists already
navigate(SIGN_IN_PAGE)
} else {
// Move onto the password page
onNext({ stage: 'create-password' })
}
} catch (e) {
// Unknown error state ¯\_(ツ)_/¯
setErrors({ email: messages.unknownError })
}
}
},
[onNext]
[dispatch, navigate, onNext, queryContext]
)

return (
<>
<Formik initialValues={initialValues} onSubmit={handleSubmit}>
<Formik
validationSchema={toFormikValidationSchema(FormSchema)}
initialValues={initialValues}
onSubmit={submitHandler}
validateOnBlur
validateOnChange={false}
>
{({ isSubmitting }) => (
<Form>
<h1>{messages.header}</h1>
<TextField name='email' label={messages.emailLabel} />
<Button variant={ButtonType.PRIMARY} type='submit'>
{messages.signUp}
</Button>
<Flex
direction='column'
alignItems='center'
ph='2xl'
pv='4xl'
gap='2xl'
>
<PreloadImage
src={audiusLogoColored}
className={styles.logo}
alt='Audius Colored Logo'
/>
<Flex
direction='column'
gap='l'
alignItems='flex-start'
w='100%'
className={styles.leftAlignText}
>
<Text color='heading' size='l' variant='heading' tag='h1'>
{messages.title}
</Text>
<Text color='default' size='l' variant='body' tag='h2'>
{messages.subHeader}
</Text>
</Flex>
<Flex direction='column' gap='l' w='100%' alignItems='flex-start'>
<HarmonyTextField
name='email'
autoFocus
autoComplete='email'
label={messages.emailLabel}
/>
<Flex w='100%' alignItems='center' gap='s'>
<Divider className={styles.flex1} />
<Text variant='body' size='m' tag='p' color='subdued'>
{messages.socialsDividerText}
</Text>
<Divider className={styles.flex1} />
</Flex>
<Flex direction='row' gap='s' w='100%'>
<SocialButton
socialType='twitter'
className={styles.flex1}
aria-label='Sign up with Twitter'
/>
<SocialButton
socialType='instagram'
className={styles.flex1}
aria-label='Sign up with Instagram'
/>
<SocialButton
socialType='tiktok'
className={styles.flex1}
aria-label='Sign up with TikTok'
/>
</Flex>
</Flex>
<Flex direction='column' gap='l' alignItems='flex-start' w='100%'>
<Button
variant={ButtonType.PRIMARY}
type='submit'
fullWidth
iconRight={IconArrowRight}
isLoading={isSubmitting}
>
{messages.signUp}
</Button>

<Text size='l'>
{messages.haveAccount}{' '}
{/* TODO [C-3278]: Update with Harmony Link */}
<Link
to={SIGN_IN_PAGE}
variant='body'
size='medium'
strength='strong'
color='secondary'
>
{messages.signIn}
</Link>
</Text>
</Flex>
</Flex>
</Form>
</Formik>
<Text>{messages.haveAccount}</Text>
<Link to={SIGN_IN_PAGE}>{messages.signIn}</Link>
</>
)}
</Formik>
)
}
5 changes: 0 additions & 5 deletions packages/web/src/utils/email.js

This file was deleted.

6 changes: 6 additions & 0 deletions packages/web/src/utils/email.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export const EMAIL_REGEX =
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/

export const isValidEmailString = (email: string) => {
return EMAIL_REGEX.test(String(email).toLowerCase())
}

0 comments on commit 1fc8456

Please sign in to comment.