From 1244e7502018178465092198d3bbf85e431c3111 Mon Sep 17 00:00:00 2001 From: escobarjonatan Date: Mon, 6 May 2024 13:31:39 -0500 Subject: [PATCH] STUD-223: Add country to signup flow and make it editable in profile (#609) * STUD-223: Add country to signup flow and make it editable in profile * STUD-223: Modify country validation * STUD-223: Fix location redirect issue * STUD-223: Add stagename and reinitialize to signup * STUD-223: Add tooltip copy to location in profile --- apps/studio/src/common/formUtils.ts | 1 + .../createProfile/AddProfileInformation.tsx | 9 +- apps/studio/src/modules/content/api.ts | 25 ++++- apps/studio/src/modules/content/types.ts | 11 ++ apps/studio/src/modules/session/types.ts | 1 + .../src/pages/createProfile/CreateProfile.tsx | 28 ++++- .../pages/createProfile/SelectLocation.tsx | 102 ++++++++++++++++++ apps/studio/src/pages/home/Home.tsx | 14 ++- .../studio/src/pages/home/profile/Profile.tsx | 33 ++++-- .../elements/src/lib/styled/NoResultsText.ts | 6 +- 10 files changed, 201 insertions(+), 29 deletions(-) create mode 100644 apps/studio/src/pages/createProfile/SelectLocation.tsx diff --git a/apps/studio/src/common/formUtils.ts b/apps/studio/src/common/formUtils.ts index 4a5e2c293..4babd9fc9 100644 --- a/apps/studio/src/common/formUtils.ts +++ b/apps/studio/src/common/formUtils.ts @@ -159,6 +159,7 @@ export const commonYupValidation = { .trim() .max(20, "Must be 20 characters or less") .required("Last name is required"), + location: Yup.string().required("This field is required"), moods: Yup.array().max(5, "Maximum of 5 moods allowed"), newPassword: Yup.string() .test( diff --git a/apps/studio/src/components/createProfile/AddProfileInformation.tsx b/apps/studio/src/components/createProfile/AddProfileInformation.tsx index fe7473426..c704d135a 100644 --- a/apps/studio/src/components/createProfile/AddProfileInformation.tsx +++ b/apps/studio/src/components/createProfile/AddProfileInformation.tsx @@ -1,11 +1,10 @@ import { FunctionComponent, useEffect, useRef } from "react"; -import { Box, Stack, useTheme } from "@mui/material"; +import { Box, Stack, Typography, useTheme } from "@mui/material"; import { FormikValues, useFormikContext } from "formik"; import { Button, FilteredTagsField, GradientTextInputField, - Typography, } from "@newm-web/elements"; import { useUserDevice, useWindowDimensions } from "@newm-web/utils"; import { ResponsiveNEWMLogo } from "../../components"; @@ -153,11 +152,7 @@ const AddProfileInformation: FunctionComponent = ({ Next - + or press Enter diff --git a/apps/studio/src/modules/content/api.ts b/apps/studio/src/modules/content/api.ts index 14f901358..8c21e4cfa 100644 --- a/apps/studio/src/modules/content/api.ts +++ b/apps/studio/src/modules/content/api.ts @@ -1,9 +1,31 @@ -import { Genre, Language, Role } from "./types"; +import { Country, Genre, Language, Role } from "./types"; import { Tags, newmApi } from "../../api"; import { setToastMessage } from "../../modules/ui"; export const extendedApi = newmApi.injectEndpoints({ endpoints: (build) => ({ + getCountries: build.query, void>({ + async onQueryStarted(body, { dispatch, queryFulfilled }) { + try { + await queryFulfilled; + } catch (error) { + dispatch( + setToastMessage({ + message: "An error occurred while fetching countries", + severity: "error", + }) + ); + } + }, + + query: () => ({ method: "GET", url: "v1/distribution/countries" }), + transformResponse: (response: Country[]) => { + const extracted = response.map((country) => country.country_name); + + // Sort alphabetically + return extracted.sort((a, b) => a.localeCompare(b)); + }, + }), getGenres: build.query({ async onQueryStarted(body, { dispatch, queryFulfilled }) { try { @@ -103,6 +125,7 @@ export const extendedApi = newmApi.injectEndpoints({ }); export const { + useGetCountriesQuery, useGetGenresQuery, useGetISRCCountryCodesQuery, useGetLanguagesQuery, diff --git a/apps/studio/src/modules/content/types.ts b/apps/studio/src/modules/content/types.ts index 36302af5e..2e97a2510 100644 --- a/apps/studio/src/modules/content/types.ts +++ b/apps/studio/src/modules/content/types.ts @@ -12,3 +12,14 @@ export interface Language { readonly language_code: string; readonly language_name: string; } + +interface State { + readonly state_code: string; + readonly state_name: string; +} + +export interface Country { + readonly country_code: string; + readonly country_name: string; + readonly state?: State[]; +} diff --git a/apps/studio/src/modules/session/types.ts b/apps/studio/src/modules/session/types.ts index 68f0e000e..13797b9b6 100644 --- a/apps/studio/src/modules/session/types.ts +++ b/apps/studio/src/modules/session/types.ts @@ -84,6 +84,7 @@ export interface ProfileFormValues readonly email?: string; readonly firstName?: string; readonly lastName?: string; + readonly location?: string; readonly pictureUrl?: string | File; readonly role?: string; } diff --git a/apps/studio/src/pages/createProfile/CreateProfile.tsx b/apps/studio/src/pages/createProfile/CreateProfile.tsx index bc9aed94e..b65a7c820 100644 --- a/apps/studio/src/pages/createProfile/CreateProfile.tsx +++ b/apps/studio/src/pages/createProfile/CreateProfile.tsx @@ -2,22 +2,29 @@ import { FunctionComponent } from "react"; import { Box, Container, useTheme } from "@mui/material"; import { WizardForm } from "@newm-web/elements"; import * as Yup from "yup"; +import { getUpdatedValues } from "@newm-web/utils"; import Begin from "./Begin"; import SelectNickname from "./SelectNickname"; import SelectRole from "./SelectRole"; import Complete from "./Complete"; import AddFirstName from "./AddFirstName"; import AddLastName from "./AddLastName"; +import SelectLocation from "./SelectLocation"; import { useGetRolesQuery } from "../../modules/content"; import { commonYupValidation } from "../../common"; import { ProfileFormValues, + emptyProfile, + useGetProfileQuery, useUpdateInitialProfileThunk, } from "../../modules/session"; const CreateProfile: FunctionComponent = () => { const theme = useTheme(); const { data: roles = [] } = useGetRolesQuery(); + const { + data: { firstName, lastName, role, location, nickname } = emptyProfile, + } = useGetProfileQuery(); const [updateInitialProfile] = useUpdateInitialProfileThunk(); @@ -25,9 +32,11 @@ const CreateProfile: FunctionComponent = () => { * Initial form values. */ const initialValues: ProfileFormValues = { - firstName: "", - lastName: "", - role: "", + firstName, + lastName, + location, + nickname, + role, }; /** @@ -36,6 +45,7 @@ const CreateProfile: FunctionComponent = () => { const validations = { firstName: commonYupValidation.firstName, lastName: commonYupValidation.lastName, + location: commonYupValidation.location, nickname: commonYupValidation.nickname, role: commonYupValidation.role(roles), }; @@ -44,7 +54,9 @@ const CreateProfile: FunctionComponent = () => { * Submits the form when on the last route of the form. */ const handleSubmit = (values: ProfileFormValues) => { - updateInitialProfile({ ...values }); + const updatedValues = getUpdatedValues(initialValues, values); + + updateInitialProfile(updatedValues); }; return ( @@ -61,6 +73,7 @@ const CreateProfile: FunctionComponent = () => { > { role: validations.role, }), }, + { + element: , + path: "what-is-your-location", + validationSchema: Yup.object().shape({ + location: validations.location, + }), + }, { element: , path: "complete", diff --git a/apps/studio/src/pages/createProfile/SelectLocation.tsx b/apps/studio/src/pages/createProfile/SelectLocation.tsx new file mode 100644 index 000000000..da94acf2b --- /dev/null +++ b/apps/studio/src/pages/createProfile/SelectLocation.tsx @@ -0,0 +1,102 @@ +import { FunctionComponent, useEffect } from "react"; +import { Box, Stack, Typography } from "@mui/material"; +import { Button, DropdownSelectField } from "@newm-web/elements"; +import theme from "@newm-web/theme"; +import { useWindowDimensions } from "@newm-web/utils"; +import { useFormikContext } from "formik"; +import { ResponsiveNEWMLogo } from "../../components"; +import { ProfileFormValues } from "../../modules/session"; +import { useGetCountriesQuery } from "../../modules/content"; + +const SelectLocation: FunctionComponent = () => { + const windowWidth = useWindowDimensions()?.width; + const { isValid, handleSubmit } = useFormikContext(); + const { data: locations = [] } = useGetCountriesQuery(); + + /** + * Add an event listener to submit the form when enter is pressed. + * Without this Formik will only submit the form when enter is pressed + * while an input is focused. + */ + useEffect(() => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const handleKeyDown = (event: any) => { + if (event.key === "Enter") { + event.preventDefault(); + handleSubmit(); + } + }; + + document.addEventListener("keydown", handleKeyDown); + + return () => { + document.removeEventListener("keydown", handleKeyDown); + }; + }, [handleSubmit]); + + return ( + + + + + + Finally, what is your country of residence? + + + Providing this info will make it easier for streaming platforms to make + your music easier to find for your fans & listeners. + + + + + + + + + + + or press Enter + + + + + ); +}; + +export default SelectLocation; diff --git a/apps/studio/src/pages/home/Home.tsx b/apps/studio/src/pages/home/Home.tsx index 13ad3e35a..5626bdf46 100644 --- a/apps/studio/src/pages/home/Home.tsx +++ b/apps/studio/src/pages/home/Home.tsx @@ -20,16 +20,22 @@ const Home: FunctionComponent = () => { const [isMobileOpen, setMobileOpen] = useState(false); const { - data: { firstName = "", lastName = "", role } = emptyProfile, + data: { firstName = "", lastName = "", role, location } = emptyProfile, isLoading, } = useGetProfileQuery(); - const hasBasicDetails = !!(firstName && lastName && role); + const hasBasicDetails = !!(firstName && lastName && role && location); if (!hasBasicDetails && !isLoading) { - navigate("/create-profile"); + if (!firstName || !lastName || !role) { + navigate("/create-profile"); + return null; + } - return null; + if (!location) { + navigate("/create-profile/what-is-your-location"); + return null; + } } if (isLoading) return null; diff --git a/apps/studio/src/pages/home/profile/Profile.tsx b/apps/studio/src/pages/home/profile/Profile.tsx index 514431a3c..dd315bb59 100644 --- a/apps/studio/src/pages/home/profile/Profile.tsx +++ b/apps/studio/src/pages/home/profile/Profile.tsx @@ -40,7 +40,10 @@ import { commonYupValidation, useAppDispatch, } from "../../../common"; -import { useGetRolesQuery } from "../../../modules/content"; +import { + useGetCountriesQuery, + useGetRolesQuery, +} from "../../../modules/content"; import { ProfileFormValues, UpdateProfileRequest, @@ -58,6 +61,7 @@ const Profile: FunctionComponent = () => { const dispatch = useAppDispatch(); const { state } = useLocation(); + const locationRef = useRef(null); const companyNameRef = useRef(null); const emailRef = useRef(null); const firstNameRef = useRef(null); @@ -69,6 +73,7 @@ const Profile: FunctionComponent = () => { const windowWidth = useWindowDimensions()?.width; const { data: roles = [] } = useGetRolesQuery(); + const { data: locations = [] } = useGetCountriesQuery(); const { data: { @@ -164,6 +169,7 @@ const Profile: FunctionComponent = () => { ipi: commonYupValidation.ipi, isni: commonYupValidation.isni, lastName: commonYupValidation.lastName, + location: commonYupValidation.location, nickname: commonYupValidation.nickname, role: commonYupValidation.role(roles), soundCloudProfile: Yup.string() @@ -293,6 +299,7 @@ const Profile: FunctionComponent = () => { > { ({ dirty, errors, handleReset, isSubmitting }) => { scrollToError(errors, isSubmitting, [ + { element: locationRef.current, error: errors.location }, { element: roleRef.current, error: errors.role }, { element: firstNameRef.current, error: errors.firstName }, { element: lastNameRef.current, error: errors.lastName }, @@ -357,6 +364,7 @@ const Profile: FunctionComponent = () => { { ) : null } - { location ? ( - - ) : null } + diff --git a/packages/elements/src/lib/styled/NoResultsText.ts b/packages/elements/src/lib/styled/NoResultsText.ts index a9db34075..9cc7b5a93 100644 --- a/packages/elements/src/lib/styled/NoResultsText.ts +++ b/packages/elements/src/lib/styled/NoResultsText.ts @@ -17,8 +17,10 @@ const NoResultsText = styled("span")` padding: 12px 12px; position: absolute; width: 100%; - z-index: 1; - + z-index: 10; + bottom: -52px; + right: 0; + left: 0; ${({ widthType }: Props) => widthType !== "full" && css`