Skip to content

Commit

Permalink
Merge pull request #384 from projectNEWM/bugfix/minor-bug-fixes-impro…
Browse files Browse the repository at this point in the history
…vements

Update regex for inputs and sort dropdown options and other misc updates
  • Loading branch information
escobarjonatan committed Oct 6, 2023
2 parents 15cb39e + ba1b596 commit e073801
Show file tree
Hide file tree
Showing 13 changed files with 78 additions and 59 deletions.
28 changes: 14 additions & 14 deletions src/common/arrayUtils.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,30 @@
import { useMemo } from "react";

/**
* Extracts the specified property from an array of items.
* This hook extracts the specified property from an array of items.
* Each item is expected to have the specified property of type string.
*
* @param {T[]} items - An array of items to extract property from.
* @param {K} property - The name of the property to extract.
* @param {boolean} shouldSort - Whether the result should be sorted.
* @return {string[]} An array of extracted property values.
*
* @template T
* @typedef {object} T
* @property {string} K
*/
export const extractProperty = <T, K extends keyof T>(
export const useExtractProperty = <
T extends Record<K, string>,
K extends keyof T
>(
items: T[],
property: K
): T[K][] => items.map((item) => item[property]);

export const useExtractProperty = <T, K extends keyof T>(
items: T[],
property: K
property: K,
shouldSort = true
): T[K][] => {
const memoized = useMemo(
() => extractProperty(items, property),
[items, property]
);

return memoized;
return useMemo(() => {
const extracted = items.map((item) => item[property]);
return shouldSort
? extracted.sort((a, b) => a.localeCompare(b))
: extracted;
}, [items, property, shouldSort]);
};
8 changes: 7 additions & 1 deletion src/common/formUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ import { FormikErrors, FormikValues } from "formik";
import { BarcodeConfig, BarcodeType, FieldOptions } from "./types";
import {
REGEX_9_TO_11_DIGITS,
REGEX_EVEARA_PROHIBITED_CHARACTERS,
REGEX_EXACTLY_12_DIGITS,
REGEX_EXACTLY_13_DIGITS,
REGEX_ISRC_FORMAT,
REGEX_ISWC_FORMAT,
REGEX_JAN_FORMAT,
REGEX_PASSWORD_REQUIREMENTS,
REGEX_SONG_TITLE,
} from "./regex";
import {
MAX_CHARACTER_COUNT,
Expand Down Expand Up @@ -70,9 +72,11 @@ export const commonYupValidation = {
.email("Please enter a vaild email")
.required("Email is required"),
firstName: Yup.string()
.trim()
.max(15, "Must be 15 characters or less")
.required("First name is required"),
lastName: Yup.string()
.trim()
.max(20, "Must be 20 characters or less")
.required("Last name is required"),
role: (roles: string[]) =>
Expand Down Expand Up @@ -123,11 +127,13 @@ export const commonYupValidation = {
),
coverArtUrl: Yup.mixed().required("This field is required"),
title: Yup.string()
.trim()
.required("This field is required")
.matches(
/^[^%,*=#<>{}~@\\\\/;:?$"]*$/,
REGEX_EVEARA_PROHIBITED_CHARACTERS,
"Cannot contain special characters like %,*=#<>{}~@\\/;:?$\""
)
.matches(REGEX_SONG_TITLE, "Cannot contain special characters")
.max(
MAX_CHARACTER_COUNT,
`Must be ${MAX_CHARACTER_COUNT} characters or less`
Expand Down
24 changes: 24 additions & 0 deletions src/common/regex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,27 @@ export const REGEX_APPLE_MUSIC_PROFILE = /^\d+$/;
* For example, it would match "yourSoundCloudArtistName" or "artist-name_123".
*/
export const REGEX_SOUNDCLOUD_PROFILE = /^[0-9a-z_-]+$/;

/**
* Matches strings containing alphanumeric characters (both lower and uppercase),
* spaces, and most ASCII punctuation characters. It excludes some special characters like `%,*,=#<>{}~@\\/;:?$"`.
* Main purpose is to prevent emojis.
*
* - `\w`: Matches any word character (alphanumeric + underscore).
* - `\s`: Matches any whitespace character (spaces, tabs, line breaks).
* - `!-/:-@[-``{-~`: Matches most ASCII punctuation characters.
*/
export const REGEX_SONG_TITLE = /^[\w\s!-/:-@[-`{-~]*$/;

/**
* A regular expression pattern that matches strings excluding specific special characters
* like %,*=#<>{}~@\\/;:?$".
*
* The pattern breaks down as follows:
*
* - `^`: Asserts position at start of a line.
* - `[^%,*=#<>{}~@\\\\/;:?$"]`: Matches any character that is not in this character set.
* - `*`: Matches the previous token (the character set) between zero and unlimited times.
* - `$`: Asserts position at end of a line.
*/
export const REGEX_EVEARA_PROHIBITED_CHARACTERS = /^[^%,*=#<>{}~@\\\\/;:?$"]*$/;
7 changes: 3 additions & 4 deletions src/components/minting/AddOwnerModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
SwitchInputField,
TextInputField,
} from "components";
import { commonYupValidation, useExtractProperty } from "common";
import { commonYupValidation } from "common";
import { useGetRolesQuery } from "modules/content";
import { CollaborationStatus } from "modules/song";

Expand All @@ -26,7 +26,6 @@ const AddOwnerModal: FunctionComponent<AddOwnerModalProps> = ({
onSubmit,
}) => {
const { data: roles = [] } = useGetRolesQuery();
const roleOptions = useExtractProperty(roles, "name");

const initialValues = {
email: "",
Expand All @@ -51,7 +50,7 @@ const AddOwnerModal: FunctionComponent<AddOwnerModalProps> = ({
),
isRightsOwner: Yup.boolean(),
isCreator: Yup.boolean(),
role: commonYupValidation.role(roleOptions),
role: commonYupValidation.role(roles),
});

return (
Expand Down Expand Up @@ -121,7 +120,7 @@ const AddOwnerModal: FunctionComponent<AddOwnerModalProps> = ({
label="ROLE"
isOptional={ false }
name="role"
options={ roleOptions }
options={ roles }
placeholder="Select role"
widthType="full"
/>
Expand Down
2 changes: 1 addition & 1 deletion src/components/minting/ConfirmContract.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ const FormContent: FunctionComponent<FormContentProps> = ({
/>
</Stack>

<Stack direction="column" mt={ 3 } spacing={ 2 }>
<Stack direction="column" mt={ 3 } spacing={ 2 } textAlign="left">
<CheckboxField
name="isCreator"
label={
Expand Down
4 changes: 1 addition & 3 deletions src/components/minting/Creditors.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { FunctionComponent } from "react";
import CloseIcon from "@mui/icons-material/Close";
import { Box, Stack, useTheme } from "@mui/material";
import { Button, Typography } from "elements";
import { useExtractProperty } from "common";
import {
Creditor,
MintingStatus,
Expand Down Expand Up @@ -35,7 +34,6 @@ const Creditors: FunctionComponent<CreditorsProps> = ({
}) => {
const theme = useTheme();
const { data: roles = [] } = useGetRolesQuery();
const roleOptions = useExtractProperty(roles, "name");
const emails = creditors.map((creditor) => creditor.email);

const { data: collaborators } = useGetCollaboratorsQuery(
Expand Down Expand Up @@ -89,7 +87,7 @@ const Creditors: FunctionComponent<CreditorsProps> = ({
<DropdownSelectField
isOptional={ false }
name={ `creditors[${idx}].role` }
options={ roleOptions }
options={ roles }
placeholder="Select role"
widthType="full"
/>
Expand Down
16 changes: 14 additions & 2 deletions src/modules/content/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Genre, Language, Role } from "./types";

export const extendedApi = api.injectEndpoints({
endpoints: (build) => ({
getGenres: build.query<Genre[], void>({
getGenres: build.query<string[], void>({
query: () => ({ url: "v1/distribution/genres", method: "GET" }),
providesTags: [Tags.Genres],

Expand All @@ -20,8 +20,14 @@ export const extendedApi = api.injectEndpoints({
);
}
},
transformResponse: (response: Genre[]) => {
const extracted = response.map((genre) => genre.name);

// Sort alphabetically
return extracted.sort((a, b) => a.localeCompare(b));
},
}),
getRoles: build.query<Role[], void>({
getRoles: build.query<string[], void>({
query: () => ({ url: "v1/distribution/roles", method: "GET" }),
providesTags: [Tags.Roles],

Expand All @@ -37,6 +43,12 @@ export const extendedApi = api.injectEndpoints({
);
}
},
transformResponse: (response: Role[]) => {
const extracted = response.map((role) => role.name);

// Sort alphabetically
return extracted.sort((a, b) => a.localeCompare(b));
},
}),
getMoods: build.query<Array<string>, void>({
query: () => ({ url: "contents/predefined-moods.json", method: "GET" }),
Expand Down
5 changes: 2 additions & 3 deletions src/pages/createProfile/CreateProfile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
useUpdateInitialProfileThunk,
} from "modules/session";
import { WizardForm } from "components";
import { commonYupValidation, useExtractProperty } from "common";
import { commonYupValidation } from "common";
import * as Yup from "yup";
import { useGetRolesQuery } from "modules/content";
import Begin from "./Begin";
Expand All @@ -18,7 +18,6 @@ import AddLastName from "./AddLastName";
const CreateProfile: FunctionComponent = () => {
const theme = useTheme();
const { data: roles = [] } = useGetRolesQuery();
const roleOptions = useExtractProperty(roles, "name");

const [updateInitialProfile] = useUpdateInitialProfileThunk();

Expand All @@ -36,7 +35,7 @@ const CreateProfile: FunctionComponent = () => {
*/
const validations = {
nickname: commonYupValidation.nickname,
role: commonYupValidation.role(roleOptions),
role: commonYupValidation.role(roles),
firstName: commonYupValidation.firstName,
lastName: commonYupValidation.lastName,
};
Expand Down
5 changes: 1 addition & 4 deletions src/pages/createProfile/SelectRole.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { FunctionComponent } from "react";
import { AddProfileInformation } from "components";
import { useExtractProperty } from "common";
import { useFormikContext } from "formik";
import { useGetRolesQuery } from "modules/content";
import { ProfileFormValues } from "modules/session";
Expand All @@ -12,15 +11,13 @@ const SelectRole: FunctionComponent = () => {
values: { firstName, nickname },
} = useFormikContext<ProfileFormValues>();

const roleOptions = useExtractProperty(roles, "name");

return (
<AddProfileInformation
fieldName="role"
helperText="Type or select a role"
placeholder="role"
prompt={ `What's your main role, ${nickname ? nickname : firstName}?` }
tags={ roleOptions }
tags={ roles }
/>
);
};
Expand Down
19 changes: 5 additions & 14 deletions src/pages/home/library/EditSong.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,8 @@ import {
usePatchSongThunk,
} from "modules/song";
import { setToastMessage } from "modules/ui";
import {
Genre,
Language,
useGetGenresQuery,
useGetLanguagesQuery,
} from "modules/content";
import { commonYupValidation, extractProperty } from "common";
import { useGetGenresQuery, useGetLanguagesQuery } from "modules/content";
import { commonYupValidation, useExtractProperty } from "common";
import AdvancedSongDetails from "pages/home/uploadSong/AdvancedSongDetails";
import BasicSongDetails from "pages/home/uploadSong/BasicSongDetails";
import ConfirmAgreement from "pages/home/uploadSong/ConfirmAgreement";
Expand All @@ -53,10 +48,8 @@ const EditSong: FunctionComponent = () => {
} = emptyProfile,
} = useGetProfileQuery();
const { data: languages = [] } = useGetLanguagesQuery();
const languageCodes = extractProperty<Language, "language_code">(
languages,
"language_code"
);
const languageCodes = useExtractProperty(languages, "language_code", false);

const { data: collaborations = [] } = useGetCollaborationsQuery({
songIds: songId,
});
Expand Down Expand Up @@ -251,13 +244,11 @@ const EditSong: FunctionComponent = () => {
// eslint-disable-next-line
}, []);

const genreOptions = extractProperty<Genre, "name">(genres, "name");

const validations = {
coverArtUrl: commonYupValidation.coverArtUrl,
title: commonYupValidation.title,
description: commonYupValidation.description,
genres: commonYupValidation.genres(genreOptions),
genres: commonYupValidation.genres(genres),
moods: commonYupValidation.moods,
owners: commonYupValidation.owners,
consentsToContract: commonYupValidation.consentsToContract,
Expand Down
11 changes: 3 additions & 8 deletions src/pages/home/profile/Profile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,9 @@ import {
getUpdatedValues,
scrollToError,
useAppDispatch,
useExtractProperty,
useWindowDimensions,
} from "common";
import { useGetGenresQuery, useGetRolesQuery } from "modules/content";
import { useGetRolesQuery } from "modules/content";
import {
ProfileFormValues,
UpdateProfileRequest,
Expand All @@ -51,9 +50,6 @@ const Profile: FunctionComponent = () => {

const windowWidth = useWindowDimensions()?.width;
const { data: roles = [] } = useGetRolesQuery();
const { data: genres = [] } = useGetGenresQuery();
const genreOptions = useExtractProperty(genres, "name");
const roleOptions = useExtractProperty(roles, "name");

const {
data: {
Expand Down Expand Up @@ -138,8 +134,7 @@ const Profile: FunctionComponent = () => {
companyIpRights: Yup.bool(),
lastName: commonYupValidation.lastName,
nickname: commonYupValidation.nickname,
role: commonYupValidation.role(roleOptions),
genre: commonYupValidation.genre(genreOptions),
role: commonYupValidation.role(roles),
twitterUrl: Yup.string().url("Please enter a valid url"),
websiteUrl: Yup.string().url("Please enter a valid url"),
spotifyProfile: Yup.string().matches(
Expand Down Expand Up @@ -343,7 +338,7 @@ const Profile: FunctionComponent = () => {
isOptional={ false }
label="MAIN ROLE"
name="role"
options={ roleOptions }
options={ roles }
placeholder="Main role"
/>
<TextInputField
Expand Down
3 changes: 1 addition & 2 deletions src/pages/home/uploadSong/BasicSongDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,6 @@ const BasicSongDetails: FunctionComponent<BasicDonDetailsProps> = ({
const { data: languages = [] } = useGetLanguagesQuery();
const { songId } = useParams<"songId">() as SongRouteParams;

const genreOptions = useExtractProperty(genres, "name");
const languageOptions = useExtractProperty(languages, "language_name");

const windowWidth = useWindowDimensions()?.width;
Expand Down Expand Up @@ -215,7 +214,7 @@ const BasicSongDetails: FunctionComponent<BasicDonDetailsProps> = ({
isOptional={ false }
name="genres"
placeholder="Select all that apply"
options={ genreOptions }
options={ genres }
/>

<DropdownSelectField
Expand Down
5 changes: 2 additions & 3 deletions src/pages/home/uploadSong/UploadSong.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ const UploadSong: FunctionComponent = () => {
} = emptyProfile,
} = useGetProfileQuery();
const { data: languages = [] } = useGetLanguagesQuery();
const languageCodes = useExtractProperty(languages, "language_code");
const languageCodes = useExtractProperty(languages, "language_code", false);
const { data: { date: earliestReleaseDate } = {} } =
useGetEarliestReleaseDateQuery(undefined, {
// endpoint throws error if user hasn't added first name
Expand Down Expand Up @@ -138,14 +138,13 @@ const UploadSong: FunctionComponent = () => {
// to only run on mount.
// eslint-disable-next-line
}, []);
const genreOptions = useExtractProperty(genres, "name");

const validations = {
coverArtUrl: commonYupValidation.coverArtUrl,
audio: commonYupValidation.audio,
title: commonYupValidation.title,
description: commonYupValidation.description,
genres: commonYupValidation.genres(genreOptions),
genres: commonYupValidation.genres(genres),
moods: commonYupValidation.moods,
owners: commonYupValidation.owners,
consentsToContract: commonYupValidation.consentsToContract,
Expand Down

0 comments on commit e073801

Please sign in to comment.