diff --git a/packages/common/src/schemas/sign-on/finishProfileSchema.ts b/packages/common/src/schemas/sign-on/finishProfileSchema.ts index 0f79dbb0e69..0df909a389a 100644 --- a/packages/common/src/schemas/sign-on/finishProfileSchema.ts +++ b/packages/common/src/schemas/sign-on/finishProfileSchema.ts @@ -15,3 +15,19 @@ export const finishProfileSchema = z.object({ }) .optional() }) + +export const finishReferralProfileSchema = z.object({ + displayName: z + .string({ required_error: 'Display name is required.' }) + .max(MAX_DISPLAY_NAME_LENGTH, ''), + profileImage: z + .object({ + url: z.string().optional() + }) + .optional(), + coverPhoto: z + .object({ + url: z.string().optional() + }) + .optional() +}) diff --git a/packages/common/src/utils/route.ts b/packages/common/src/utils/route.ts index 1fd269279cc..b21c4020e65 100644 --- a/packages/common/src/utils/route.ts +++ b/packages/common/src/utils/route.ts @@ -92,7 +92,8 @@ export enum SignUpPath { selectArtists = 'select-artists', loading = 'loading', appCta = 'app-cta', - completedRedirect = 'completed' + completedRedirect = 'completed', + completedReferrerRedirect = 'completed-referrer' } export const SIGN_UP_EMAIL_PAGE = `/signup/${SignUpPath.createEmail}` export const SIGN_UP_START_PAGE = SIGN_UP_EMAIL_PAGE // entry point for sign up if needing to redirect to the beginning @@ -106,6 +107,7 @@ export const SIGN_UP_ARTISTS_PAGE = `/signup/${SignUpPath.selectArtists}` export const SIGN_UP_APP_CTA_PAGE = `/signup/${SignUpPath.appCta}` export const SIGN_UP_LOADING_PAGE = `/signup/${SignUpPath.loading}` export const SIGN_UP_COMPLETED_REDIRECT = `/signup/${SignUpPath.completedRedirect}` +export const SIGN_UP_COMPLETED_REFERRER_REDIRECT = `/signup/${SignUpPath.completedReferrerRedirect}` // Param routes. export const NOTIFICATION_USERS_PAGE = '/notification/:notificationId/users' diff --git a/packages/web/src/common/store/pages/signon/sagas.ts b/packages/web/src/common/store/pages/signon/sagas.ts index 87ec6fb8b28..5650f75ee25 100644 --- a/packages/web/src/common/store/pages/signon/sagas.ts +++ b/packages/web/src/common/store/pages/signon/sagas.ts @@ -1014,6 +1014,7 @@ function* followArtists( ...(referrer == null ? [] : [referrer]) ]) ] + for (const userId of userIdsToFollow) { yield* put( socialActions.followUser(userId as number, FollowSource.SIGN_UP) diff --git a/packages/web/src/components/track/TrackTileStats.tsx b/packages/web/src/components/track/TrackTileStats.tsx index f0df874d7d9..1610e7c1374 100644 --- a/packages/web/src/components/track/TrackTileStats.tsx +++ b/packages/web/src/components/track/TrackTileStats.tsx @@ -36,7 +36,11 @@ export const TrackTileStats = (props: TrackTileStatsProps) => { ) if (isLoading || !track) { - return + return ( + + + + ) } const { is_unlisted } = track diff --git a/packages/web/src/components/track/mobile/PlaylistTile.tsx b/packages/web/src/components/track/mobile/PlaylistTile.tsx index cfbb20e9108..24e1b74396a 100644 --- a/packages/web/src/components/track/mobile/PlaylistTile.tsx +++ b/packages/web/src/components/track/mobile/PlaylistTile.tsx @@ -304,12 +304,14 @@ const PlaylistTile = (props: PlaylistTileProps & ExtraProps) => { - + + + { const userName = nameField ?? accountName const [isOpen, setIsOpen] = useModalState('Welcome') - const profileImage = profileImageField?.url ?? presavedProfilePic + const profileImage = + profileImageField?.url ?? (presavedProfilePic || imageProfilePicEmpty) const Root = isMobile ? Drawer : Modal const onClose = useCallback(() => { diff --git a/packages/web/src/pages/sign-up-page/SignUpPage.tsx b/packages/web/src/pages/sign-up-page/SignUpPage.tsx index 90899286102..4bfa94ba8fb 100644 --- a/packages/web/src/pages/sign-up-page/SignUpPage.tsx +++ b/packages/web/src/pages/sign-up-page/SignUpPage.tsx @@ -18,9 +18,11 @@ import { RouteContextProvider } from './utils/RouteContext' const { FEED_PAGE, + TRENDING_PAGE, SIGN_UP_APP_CTA_PAGE, SIGN_UP_ARTISTS_PAGE, SIGN_UP_COMPLETED_REDIRECT, + SIGN_UP_COMPLETED_REFERRER_REDIRECT: SIGN_UP_REFERRER_COMPLETED_REDIRECT, SIGN_UP_CREATE_LOGIN_DETAILS, SIGN_UP_EMAIL_PAGE, SIGN_UP_FINISH_PROFILE_PAGE, @@ -104,6 +106,9 @@ export const SignUpPage = () => { + + + diff --git a/packages/web/src/pages/sign-up-page/components/AccountHeader.tsx b/packages/web/src/pages/sign-up-page/components/AccountHeader.tsx index 6bacaa30fb5..58b61f9f80c 100644 --- a/packages/web/src/pages/sign-up-page/components/AccountHeader.tsx +++ b/packages/web/src/pages/sign-up-page/components/AccountHeader.tsx @@ -1,3 +1,4 @@ +import { imageProfilePicEmpty } from '@audius/common/assets' import { Name, SquareSizes } from '@audius/common/models' import { accountSelectors } from '@audius/common/store' import { @@ -113,7 +114,8 @@ export const AccountHeader = (props: AccountHeaderProps) => { const { isMobile } = useMedia() const isSmallSize = isEditing || isMobile || size === 'small' - const savedProfileImage = profileImageField?.url ?? accountProfilePic + const savedProfileImage = + profileImageField?.url || accountProfilePic || imageProfilePicEmpty return ( diff --git a/packages/web/src/pages/sign-up-page/components/SkipButton.tsx b/packages/web/src/pages/sign-up-page/components/SkipButton.tsx new file mode 100644 index 00000000000..c889236208d --- /dev/null +++ b/packages/web/src/pages/sign-up-page/components/SkipButton.tsx @@ -0,0 +1,17 @@ +import { useCallback } from 'react' + +import { route } from '@audius/common/utils' +import { PlainButton } from '@audius/harmony' + +import { useNavigateToPage } from 'hooks/useNavigateToPage' + +const { SIGN_UP_LOADING_PAGE } = route + +export const SkipButton = () => { + const navigate = useNavigateToPage() + const handleSkip = useCallback(() => { + navigate(SIGN_UP_LOADING_PAGE) + }, [navigate]) + + return Skip this step +} diff --git a/packages/web/src/pages/sign-up-page/pages/CreateEmailPage.tsx b/packages/web/src/pages/sign-up-page/pages/CreateEmailPage.tsx index 2047e441d25..de934a77592 100644 --- a/packages/web/src/pages/sign-up-page/pages/CreateEmailPage.tsx +++ b/packages/web/src/pages/sign-up-page/pages/CreateEmailPage.tsx @@ -133,7 +133,7 @@ export const CreateEmailPage = () => { > {({ isSubmitting, setFieldValue, submitForm }) => ( - + {isMobile || isSmallDesktop ? ( ) : ( diff --git a/packages/web/src/pages/sign-up-page/pages/FinishProfilePage.tsx b/packages/web/src/pages/sign-up-page/pages/FinishProfilePage.tsx index 41c07b45db5..a2e0d0fac01 100644 --- a/packages/web/src/pages/sign-up-page/pages/FinishProfilePage.tsx +++ b/packages/web/src/pages/sign-up-page/pages/FinishProfilePage.tsx @@ -2,7 +2,10 @@ import { useCallback, useRef } from 'react' import { finishProfilePageMessages } from '@audius/common/messages' import { Name } from '@audius/common/models' -import { finishProfileSchema } from '@audius/common/schemas' +import { + finishProfileSchema, + finishReferralProfileSchema +} from '@audius/common/schemas' import { MAX_DISPLAY_NAME_LENGTH } from '@audius/common/services' import { route } from '@audius/common/utils' import { Flex, Paper, PlainButton, Text, useTheme } from '@audius/harmony' @@ -25,7 +28,8 @@ import { getIsSocialConnected, getLinkedSocialOnFirstPage, getNameField, - getProfileImageField + getProfileImageField, + getReferrer } from 'common/store/pages/signon/selectors' import { HarmonyTextField } from 'components/form-fields/HarmonyTextField' import { useMedia } from 'hooks/useMedia' @@ -36,7 +40,7 @@ import { ImageFieldValue } from '../components/ImageField' import { OutOfText } from '../components/OutOfText' import { Heading, Page, PageFooter } from '../components/layout' -const { SIGN_UP_GENRES_PAGE } = route +const { SIGN_UP_GENRES_PAGE, SIGN_UP_LOADING_PAGE } = route export type FinishProfileValues = { profileImage?: ImageFieldValue @@ -45,6 +49,7 @@ export type FinishProfileValues = { } const formSchema = toFormikValidationSchema(finishProfileSchema) +const referralformSchema = toFormikValidationSchema(finishReferralProfileSchema) const ImageUploadErrorText = () => { const { errors } = useFormikContext() @@ -89,6 +94,7 @@ export const FinishProfilePage = () => { const linkedSocialOnFirstPage = useSelector(getLinkedSocialOnFirstPage) const savedCoverPhoto = useSelector(getCoverPhotoField) const savedProfileImage = useSelector(getProfileImageField) + const hasReferrer = useSelector(getReferrer) // If the user comes back from a later page we start with whats in the store const initialValues = { @@ -130,17 +136,21 @@ export const FinishProfilePage = () => { dispatch(setField('coverPhoto', coverPhoto)) } dispatch(setFinishedPhase1(true)) - navigate(SIGN_UP_GENRES_PAGE) dispatch(signUp()) + if (hasReferrer && isMobile) { + navigate(SIGN_UP_LOADING_PAGE) + } else { + navigate(SIGN_UP_GENRES_PAGE) + } }, - [navigate, dispatch] + [dispatch, hasReferrer, isMobile, navigate] ) return ( @@ -188,7 +198,9 @@ export const FinishProfilePage = () => { centered sticky buttonProps={{ disabled: !isValid }} - prefix={isMobile ? : null} + prefix={ + isMobile && !hasReferrer ? : null + } postfix={ isMobile || isSocialConnected ? null : ( diff --git a/packages/web/src/pages/sign-up-page/pages/LoadingAccountPage.tsx b/packages/web/src/pages/sign-up-page/pages/LoadingAccountPage.tsx index 6476aeaeaae..ad94ab79e54 100644 --- a/packages/web/src/pages/sign-up-page/pages/LoadingAccountPage.tsx +++ b/packages/web/src/pages/sign-up-page/pages/LoadingAccountPage.tsx @@ -4,9 +4,14 @@ import { route } from '@audius/common/utils' import { Flex } from '@audius/harmony' import { useSelector } from 'react-redux' -import { getStatus } from 'common/store/pages/signon/selectors' +import { + getStatus, + getReferrer, + getAccountReady +} from 'common/store/pages/signon/selectors' import { EditingStatus } from 'common/store/pages/signon/types' import LoadingSpinner from 'components/loading-spinner/LoadingSpinner' +import { useMedia } from 'hooks/useMedia' import { useNavigateToPage } from 'hooks/useNavigateToPage' import { Heading, Page } from '../components/layout' @@ -22,15 +27,23 @@ const messages = { // The user just waits here until the account is created and before being shown the welcome modal on the trending page export const LoadingAccountPage = () => { const navigate = useNavigateToPage() + const hasReferrer = useSelector(getReferrer) + const accountReady = useSelector(getAccountReady) + const { isMobile } = useMedia() const accountCreationStatus = useSelector(getStatus) + const isAccountReady = + hasReferrer && isMobile + ? accountReady + : accountCreationStatus === EditingStatus.SUCCESS + useEffect(() => { - if (accountCreationStatus === EditingStatus.SUCCESS) { + if (isAccountReady) { navigate(SIGN_UP_COMPLETED_REDIRECT) } // TODO: what to do in an error scenario? Any way to recover to a valid step? - }, [navigate, accountCreationStatus]) + }, [navigate, isAccountReady]) return ( diff --git a/packages/web/src/pages/sign-up-page/pages/PickHandlePage.tsx b/packages/web/src/pages/sign-up-page/pages/PickHandlePage.tsx index ba0a74cecb3..e8add74b6b1 100644 --- a/packages/web/src/pages/sign-up-page/pages/PickHandlePage.tsx +++ b/packages/web/src/pages/sign-up-page/pages/PickHandlePage.tsx @@ -20,7 +20,8 @@ import { import { getHandleField, getIsSocialConnected, - getLinkedSocialOnFirstPage + getLinkedSocialOnFirstPage, + getReferrer } from 'common/store/pages/signon/selectors' import { ToastContext } from 'components/toast/ToastContext' import { useMedia } from 'hooks/useMedia' @@ -117,18 +118,22 @@ export const PickHandlePage = () => { const { value: handle } = useSelector(getHandleField) const isLinkingSocialOnFirstPage = useSelector(getLinkedSocialOnFirstPage) const handleInputRef = useRef(null) + const hasReferrer = useSelector(getReferrer) const handleSubmit = useCallback( (values: PickHandleValues) => { const { handle } = values dispatch(setValueField('handle', handle)) + if (hasReferrer) { + dispatch(setValueField('name', handle)) + } navigate( isLinkingSocialOnFirstPage ? SIGN_UP_CREATE_LOGIN_DETAILS : SIGN_UP_FINISH_PROFILE_PAGE ) }, - [dispatch, navigate, isLinkingSocialOnFirstPage] + [dispatch, hasReferrer, navigate, isLinkingSocialOnFirstPage] ) const handleCompleteSocialMediaLogin = useCallback( diff --git a/packages/web/src/pages/sign-up-page/pages/SelectArtistsPage.tsx b/packages/web/src/pages/sign-up-page/pages/SelectArtistsPage.tsx index e9748ece871..1859ea42dda 100644 --- a/packages/web/src/pages/sign-up-page/pages/SelectArtistsPage.tsx +++ b/packages/web/src/pages/sign-up-page/pages/SelectArtistsPage.tsx @@ -20,7 +20,7 @@ import { addFollowArtists, completeFollowArtists } from 'common/store/pages/signon/actions' -import { getGenres } from 'common/store/pages/signon/selectors' +import { getGenres, getReferrer } from 'common/store/pages/signon/selectors' import { FollowArtistCard, FollowArtistTileSkeleton @@ -33,6 +33,7 @@ import { useSelector } from 'utils/reducer' import { AccountHeader } from '../components/AccountHeader' import { PreviewArtistHint } from '../components/PreviewArtistHint' +import { SkipButton } from '../components/SkipButton' import { Heading, HiddenLegend, @@ -71,6 +72,7 @@ export const SelectArtistsPage = () => { const navigate = useNavigateToPage() const { color } = useTheme() const headerContainerRef = useRef(null) + const hasReferrer = useSelector(getReferrer) const { isMobile } = useMedia() const handleChangeGenre = useCallback((e: ChangeEvent) => { @@ -262,6 +264,7 @@ export const SelectArtistsPage = () => { disabled: !isValid || isSubmitting, isLoading: isSubmitting || isValidating }} + prefix={isMobile && hasReferrer ? : null} postfix={ {selectArtistsPageMessages.selected}{' '} diff --git a/packages/web/src/pages/sign-up-page/pages/SelectGenresPage.tsx b/packages/web/src/pages/sign-up-page/pages/SelectGenresPage.tsx index 113b5cd7df7..803f445688d 100644 --- a/packages/web/src/pages/sign-up-page/pages/SelectGenresPage.tsx +++ b/packages/web/src/pages/sign-up-page/pages/SelectGenresPage.tsx @@ -11,12 +11,13 @@ import { toFormikValidationSchema } from 'zod-formik-adapter' import { make } from 'common/store/analytics/actions' import { setField } from 'common/store/pages/signon/actions' -import { getGenres } from 'common/store/pages/signon/selectors' +import { getGenres, getReferrer } from 'common/store/pages/signon/selectors' import { SelectablePillField } from 'components/form-fields/SelectablePillField' import { useMedia } from 'hooks/useMedia' import { useNavigateToPage } from 'hooks/useNavigateToPage' import { AccountHeader } from '../components/AccountHeader' +import { SkipButton } from '../components/SkipButton' import { Heading, Page, PageFooter, ScrollView } from '../components/layout' const { SIGN_UP_ARTISTS_PAGE } = route @@ -29,6 +30,8 @@ export const SelectGenresPage = () => { const [currentGenres, setCurrentGenres] = useState([]) const savedGenres = useSelector(getGenres) + const hasReferrer = useSelector(getReferrer) + const { isMobile } = useMedia() const initialValues: SelectGenresValue = { genres: (savedGenres as Genre[]) ?? [] @@ -64,8 +67,6 @@ export const SelectGenresPage = () => { } } - const { isMobile } = useMedia() - return ( @@ -114,7 +115,12 @@ export const SelectGenresPage = () => { })} - + : null} + /> )} diff --git a/packages/web/src/pages/sign-up-page/utils/useDetermineAllowedRoutes.ts b/packages/web/src/pages/sign-up-page/utils/useDetermineAllowedRoutes.ts index c2aad875cf0..c0f683ef5fa 100644 --- a/packages/web/src/pages/sign-up-page/utils/useDetermineAllowedRoutes.ts +++ b/packages/web/src/pages/sign-up-page/utils/useDetermineAllowedRoutes.ts @@ -5,9 +5,11 @@ import { useSelector } from 'react-redux' import { useModalState } from 'common/hooks/useModalState' import { getAccountAlreadyExisted, + getReferrer, getSignOn } from 'common/store/pages/signon/selectors' import { EditingStatus } from 'common/store/pages/signon/types' +import { useMedia } from 'hooks/useMedia' import { env } from 'services/env' const { FEED_PAGE, SignUpPath } = route @@ -28,6 +30,8 @@ export const useDetermineAllowedRoute = () => { const followeeCount = useSelector(getAccountFolloweeCount) const hasAccount = useSelector(getHasAccount) const hasAlreadySignedUp = useSelector(getAccountAlreadyExisted) + const hasReferrer = useSelector(getReferrer) + const { isMobile } = useMedia() const pastAccountPhase = signUpState.finishedPhase1 || hasAccount @@ -59,6 +63,14 @@ export const useDetermineAllowedRoute = () => { // Either way the user can't go back any more allowedRoutes = [SignUpPath.selectGenres] + if (isMobile && hasReferrer) { + allowedRoutes.push(SignUpPath.selectArtists) + allowedRoutes.push(SignUpPath.appCta) + allowedRoutes.push(SignUpPath.completedRedirect) + allowedRoutes.push(SignUpPath.completedReferrerRedirect) + allowedRoutes.push(SignUpPath.loading) + } + // TODO: These checks below here may need to fall under a different route umbrella separate from sign up if (signUpState.genres && signUpState.genres.length > 0) { // Already have genres selected