From ce1c9a9c47edf245ad04d54e4ede7a49ef9ef36d Mon Sep 17 00:00:00 2001 From: Dylan Jeffers Date: Thu, 5 Oct 2023 16:46:22 -0700 Subject: [PATCH] [C-3174] Add select genre page to sign up (#6241) --- package-lock.json | 4 +- packages/mobile/e2e/matchers.ts | 4 + packages/mobile/e2e/signUp.test.ts | 37 ++++++- .../SelectArtistCategoryButtons.tsx | 2 +- .../sign-up-screen/SignUpRootScreen.tsx | 4 + .../screens/CreatePasswordScreen.tsx | 2 +- .../screens/FinishProfileScreen.tsx | 16 ++- .../screens/PickHandleScreen.tsx | 7 +- .../screens/SelectArtistsScreen.tsx | 46 +++++++++ .../screens/SelectGenreScreen.tsx | 98 +++++++++++++++++++ .../sign-up-screen/screens/SignUpScreen.tsx | 2 +- .../src/screens/sign-up-screen/types.ts | 2 + .../src/screens/signon/ProfileManual.tsx | 11 +-- packages/mobile/src/store/store.ts | 8 +- packages/probers/cypress/e2e/signUp.cy.ts | 30 +++++- .../common/store/pages/feed/lineup/sagas.ts | 3 +- .../src/common/store/pages/signon/actions.ts | 2 +- .../common/store/pages/signon/selectors.js | 55 ----------- .../common/store/pages/signon/selectors.ts | 60 ++++++++++++ .../src/common/store/pages/signon/types.ts | 10 +- packages/web/src/pages/App.js | 1 - packages/web/src/pages/App.module.css | 11 --- packages/web/src/pages/sign-on/SignOn.tsx | 4 + .../web/src/pages/sign-on/SignOnProvider.tsx | 2 + .../src/pages/sign-up-page/SignUpRootPage.tsx | 10 +- .../sign-up-page/components/GenrePill.tsx | 17 ++++ .../sign-up-page/pages/FinishProfilePage.tsx | 8 +- .../sign-up-page/pages/PickHandlePage.tsx | 8 +- .../sign-up-page/pages/SelectArtistsPage.tsx | 44 +++++++++ .../sign-up-page/pages/SelectGenrePage.tsx | 85 +++++++++++++--- packages/web/src/store/types.ts | 4 +- 31 files changed, 491 insertions(+), 106 deletions(-) create mode 100644 packages/mobile/src/screens/sign-up-screen/screens/SelectArtistsScreen.tsx create mode 100644 packages/mobile/src/screens/sign-up-screen/screens/SelectGenreScreen.tsx delete mode 100644 packages/web/src/common/store/pages/signon/selectors.js create mode 100644 packages/web/src/common/store/pages/signon/selectors.ts create mode 100644 packages/web/src/pages/sign-up-page/components/GenrePill.tsx create mode 100644 packages/web/src/pages/sign-up-page/pages/SelectArtistsPage.tsx diff --git a/package-lock.json b/package-lock.json index 1853dd11c85..96076b480a6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -97813,7 +97813,7 @@ "storybook-addon-smart-knobs": "6.0.2", "tsconfig-paths-webpack-plugin": "3.5.2", "typescript": "4.9.4", - "typescript-plugin-css-modules": "^3.4.0", + "typescript-plugin-css-modules": "3.4.0", "webpack": "4.46.0" } }, @@ -103095,7 +103095,7 @@ }, "packages/libs": { "name": "@audius/sdk", - "version": "3.0.11-beta.5", + "version": "3.0.11-beta.21", "license": "Apache-2.0", "dependencies": { "@audius/hedgehog": "2.1.0", diff --git a/packages/mobile/e2e/matchers.ts b/packages/mobile/e2e/matchers.ts index f7f7e579b3b..fcbef0fa07c 100644 --- a/packages/mobile/e2e/matchers.ts +++ b/packages/mobile/e2e/matchers.ts @@ -18,6 +18,10 @@ export function byRole(role: Role, options: ByRoleOptions) { return labelOnly ? element(by.traits(['button']).and(by.label(name))) : element(by.traits(['button']).withDescendant(by.label(name))) + case 'checkbox': + return element(by.id(name)) + case 'radio': + return element(by.id(name)) default: return element(by.traits([role]).and(by.label(name))) } diff --git a/packages/mobile/e2e/signUp.test.ts b/packages/mobile/e2e/signUp.test.ts index fbede8dcd78..1782df76609 100644 --- a/packages/mobile/e2e/signUp.test.ts +++ b/packages/mobile/e2e/signUp.test.ts @@ -44,9 +44,10 @@ describe('Sign up', () => { await assertOnSignUp() }) - it.only('should create an account', async () => { + it('should create an account', async () => { const testUser = generateTestUser() const { email, password, handle, name } = testUser + await byRole('textbox', { name: /email/i }).typeText(email) await byRole('button', { name: /sign up free/i }).tap() @@ -95,5 +96,39 @@ describe('Sign up', () => { await byRole('textbox', { name: /display name/i }).typeText(name) await byRole('button', { name: /continue/i }).tap() + + await expect( + byRole('heading', { name: /select your genres/i }) + ).toBeVisible() + await expect( + byText(/start by picking some of your favorite genres./i) + ).toBeVisible() + + const genres = [/^acoustic/i, /^pop/i, /^lo-fi/i, /^electronic/i] + + for (const genre of genres) { + await byRole('checkbox', { name: genre }).tap() + await expect(byRole('checkbox', { name: genre })).toHaveValue( + 'checkbox, checked' + ) + } + + await element(by.id('genreScrollView')).scrollTo('bottom') + await byRole('button', { name: /continue/i }).tap() + + await expect( + byRole('heading', { name: /follow at least 3 artists/i }) + ).toBeVisible() + await expect( + byText(/curate your feed with tracks uploaded .*/i) + ).toBeVisible() + + expect(byRole('radio', { name: /featured/i })).toHaveValue( + 'radio button, checked' + ) + + for (const genre of genres) { + expect(byRole('radio', { name: genre })).toBeVisible() + } }) }) diff --git a/packages/mobile/src/components/suggested-follows/SelectArtistCategoryButtons.tsx b/packages/mobile/src/components/suggested-follows/SelectArtistCategoryButtons.tsx index 47f15873559..fe0ff979447 100644 --- a/packages/mobile/src/components/suggested-follows/SelectArtistCategoryButtons.tsx +++ b/packages/mobile/src/components/suggested-follows/SelectArtistCategoryButtons.tsx @@ -1,4 +1,4 @@ -import { artistCategories } from 'audius-client/src/common/store/pages/signon/types' +import { artistCategories } from 'common/store/pages/signon/types' import { View } from 'react-native' import { useSelector } from 'react-redux' diff --git a/packages/mobile/src/screens/sign-up-screen/SignUpRootScreen.tsx b/packages/mobile/src/screens/sign-up-screen/SignUpRootScreen.tsx index 3125bfa88c3..d9ce2435cf5 100644 --- a/packages/mobile/src/screens/sign-up-screen/SignUpRootScreen.tsx +++ b/packages/mobile/src/screens/sign-up-screen/SignUpRootScreen.tsx @@ -3,6 +3,8 @@ import { createNativeStackNavigator } from '@react-navigation/native-stack' import { CreatePasswordScreen } from './screens/CreatePasswordScreen' import { FinishProfileScreen } from './screens/FinishProfileScreen' import { PickHandleScreen } from './screens/PickHandleScreen' +import { SelectArtistsScreen } from './screens/SelectArtistsScreen' +import { SelectGenreScreen } from './screens/SelectGenreScreen' import { SignUpScreen } from './screens/SignUpScreen' const Stack = createNativeStackNavigator() @@ -15,6 +17,8 @@ export const SignUpRootScreen = () => { + + ) } diff --git a/packages/mobile/src/screens/sign-up-screen/screens/CreatePasswordScreen.tsx b/packages/mobile/src/screens/sign-up-screen/screens/CreatePasswordScreen.tsx index 0a06b041407..d01e469e539 100644 --- a/packages/mobile/src/screens/sign-up-screen/screens/CreatePasswordScreen.tsx +++ b/packages/mobile/src/screens/sign-up-screen/screens/CreatePasswordScreen.tsx @@ -1,6 +1,6 @@ import { useCallback } from 'react' -import { setValueField } from 'audius-client/src/common/store/pages/signon/actions' +import { setValueField } from 'common/store/pages/signon/actions' import { Formik } from 'formik' import { View } from 'react-native' import { useDispatch } from 'react-redux' diff --git a/packages/mobile/src/screens/sign-up-screen/screens/FinishProfileScreen.tsx b/packages/mobile/src/screens/sign-up-screen/screens/FinishProfileScreen.tsx index 925e9076aee..6da8c4a13f4 100644 --- a/packages/mobile/src/screens/sign-up-screen/screens/FinishProfileScreen.tsx +++ b/packages/mobile/src/screens/sign-up-screen/screens/FinishProfileScreen.tsx @@ -1,13 +1,17 @@ import { useCallback } from 'react' +import { setValueField } from 'common/store/pages/signon/actions' import { Formik } from 'formik' import { View } from 'react-native' +import { useDispatch } from 'react-redux' import { Button, Text } from 'app/components/core' import { TextField } from 'app/components/fields' +import { useNavigation } from 'app/hooks/useNavigation' import { CoverPhotoField } from '../components/CoverPhotoField' import { ProfilePictureField } from '../components/ProfilePictureField' +import type { SignUpScreenParamList } from '../types' const messages = { header: 'Finish Your Profile', @@ -28,7 +32,17 @@ type FinishProfileValues = { } export const FinishProfileScreen = () => { - const handleSubmit = useCallback((values: FinishProfileValues) => {}, []) + const navigation = useNavigation() + const dispatch = useDispatch() + + const handleSubmit = useCallback( + (values: FinishProfileValues) => { + const { displayName } = values + dispatch(setValueField('name', displayName)) + navigation.navigate('SelectGenre') + }, + [dispatch, navigation] + ) return ( diff --git a/packages/mobile/src/screens/sign-up-screen/screens/PickHandleScreen.tsx b/packages/mobile/src/screens/sign-up-screen/screens/PickHandleScreen.tsx index 705215d9fe6..6265e85b437 100644 --- a/packages/mobile/src/screens/sign-up-screen/screens/PickHandleScreen.tsx +++ b/packages/mobile/src/screens/sign-up-screen/screens/PickHandleScreen.tsx @@ -1,7 +1,9 @@ import { useCallback } from 'react' +import { setValueField } from 'common/store/pages/signon/actions' import { Formik } from 'formik' import { View } from 'react-native' +import { useDispatch } from 'react-redux' import { Button, Text } from 'app/components/core' import { TextField } from 'app/components/fields' @@ -27,12 +29,15 @@ type PickHandleValues = { export const PickHandleScreen = () => { const navigation = useNavigation() + const dispatch = useDispatch() const handleSubmit = useCallback( (values: PickHandleValues) => { + const { handle } = values + dispatch(setValueField('handle', handle)) navigation.navigate('FinishProfile') }, - [navigation] + [dispatch, navigation] ) return ( diff --git a/packages/mobile/src/screens/sign-up-screen/screens/SelectArtistsScreen.tsx b/packages/mobile/src/screens/sign-up-screen/screens/SelectArtistsScreen.tsx new file mode 100644 index 00000000000..17823cbecdb --- /dev/null +++ b/packages/mobile/src/screens/sign-up-screen/screens/SelectArtistsScreen.tsx @@ -0,0 +1,46 @@ +import { useState } from 'react' + +import { getGenres } from 'common/store/pages/signon/selectors' +import { Pressable, View } from 'react-native' +import { useSelector } from 'react-redux' + +import { Text } from 'app/components/core' + +const messages = { + header: 'Follow At Least 3 Artists', + description: + 'Curate your feed with tracks uploaded or reposted by anyone you follow. Click the artist’s photo to preview their music.', + continue: 'Continue' +} + +export const SelectArtistsScreen = () => { + const genres = useSelector((state: any) => ['Featured', ...getGenres(state)]) + const [currentGenre, setCurrentGenre] = useState('Featured') + + return ( + + + {messages.header} + {messages.description} + + + {genres.map((genre) => { + const checked = genre === currentGenre + return ( + setCurrentGenre(genre)} + style={{ backgroundColor: checked ? 'purple' : undefined }} + > + {genre} + + ) + })} + + + ) +} diff --git a/packages/mobile/src/screens/sign-up-screen/screens/SelectGenreScreen.tsx b/packages/mobile/src/screens/sign-up-screen/screens/SelectGenreScreen.tsx new file mode 100644 index 00000000000..913fb50ac14 --- /dev/null +++ b/packages/mobile/src/screens/sign-up-screen/screens/SelectGenreScreen.tsx @@ -0,0 +1,98 @@ +import { useCallback } from 'react' + +import { GENRES, convertGenreLabelToValue } from '@audius/common' +import { setField } from 'common/store/pages/signon/actions' +import { + getHandleField, + getNameField +} from 'common/store/pages/signon/selectors' +import { Formik } from 'formik' +import { Pressable, ScrollView, View } from 'react-native' +import { useDispatch, useSelector } from 'react-redux' + +import { Button, Text } from 'app/components/core' +import { useNavigation } from 'app/hooks/useNavigation' + +import type { SignUpScreenParamList } from '../types' + +const messages = { + header: 'Select Your Genres', + description: 'Start by picking some of your favorite genres.', + continue: 'Continue' +} + +const genres = GENRES.map((genre) => ({ + value: genre, + label: convertGenreLabelToValue(genre) +})) + +type SelectGenreValues = Record + +const initialValues = genres.reduce( + (acc, genre) => ({ + ...acc, + [genre.value]: false + }), + {} as SelectGenreValues +) + +export const SelectGenreScreen = () => { + const { value: displayName } = useSelector(getNameField) + const { value: handle } = useSelector(getHandleField) + const dispatch = useDispatch() + const navigation = useNavigation() + + const handleSubmit = useCallback( + (values: SelectGenreValues) => { + const genres = Object.keys(values).filter((genre) => values[genre]) + dispatch(setField('genres', genres)) + navigation.navigate('SelectArtists') + }, + [dispatch, navigation] + ) + + return ( + + + {displayName} + {handle} + + + {messages.header} + {messages.description} + + + {({ values, setValues, handleSubmit }) => ( + + {genres.map((genre) => { + const { label, value } = genre + const checked = !!values[value] + + const handleChange = () => { + setValues({ + ...values, + [value]: !checked + }) + } + + return ( + + {label} + + ) + })} +