Skip to content

Commit bd956fb

Browse files
committed
[C-2879] Add validation to single track upload flow (#3855)
1 parent 6159f0a commit bd956fb

12 files changed

+319
-100
lines changed

apps/audius-client/packages/web/src/components/ai-attribution-modal/AiAttributionDropdown.tsx

+5-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,10 @@ const selectSearchResults = createSelector(getSearchResults, (results) => {
4747
return { items }
4848
})
4949

50-
type AiAttributionDropdownProps = SelectProps
50+
type AiAttributionDropdownProps = SelectProps & {
51+
error?: string | false
52+
helperText?: string | false
53+
}
5154

5255
export const AiAttributionDropdown = (props: AiAttributionDropdownProps) => {
5356
const dispatch = useDispatch()
@@ -69,6 +72,7 @@ export const AiAttributionDropdown = (props: AiAttributionDropdownProps) => {
6972
size='large'
7073
input={searchInput}
7174
onSearch={handleSearch}
75+
layout='vertical'
7276
{...props}
7377
/>
7478
)

apps/audius-client/packages/web/src/components/ai-attribution-modal/DropdownInput.js

+5
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import cn from 'classnames'
55
import PropTypes from 'prop-types'
66

77
import { ReactComponent as IconCaretDown } from 'assets/img/iconCaretDown.svg'
8+
import { HelperText } from 'components/data-entry/HelperText'
89

910
import styles from './DropdownInput.module.css'
1011

@@ -37,6 +38,7 @@ class DropdownInput extends Component {
3738
labelStyle,
3839
dropdownStyle,
3940
dropdownInputStyle,
41+
helperText,
4042
layout,
4143
size,
4244
variant,
@@ -155,6 +157,9 @@ class DropdownInput extends Component {
155157
</Select>
156158
<IconCaretDown className={styles.arrow} />
157159
</div>
160+
{helperText ? (
161+
<HelperText error={error}>{helperText}</HelperText>
162+
) : null}
158163
</div>
159164
)
160165
}

apps/audius-client/packages/web/src/components/data-entry/ContextualMenu.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -164,9 +164,9 @@ export const ContextualMenu = <
164164
const handleSubmit = useCallback(
165165
(values: FormValues, helpers: FormikHelpers<FormValues>) => {
166166
onSubmit(values, helpers)
167-
toggleMenu()
167+
if (!error) toggleMenu()
168168
},
169-
[onSubmit, toggleMenu]
169+
[error, onSubmit, toggleMenu]
170170
)
171171

172172
return (

apps/audius-client/packages/web/src/components/data-entry/HelperText.tsx

-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ export const HelperText = (props: HelperTextProps) => {
1414
return (
1515
<div className={styles.root}>
1616
<Text
17-
variant='body'
1817
size='xSmall'
1918
strength='default'
2019
// @ts-expect-error

apps/audius-client/packages/web/src/pages/upload-page/components/EditPageNew.tsx

+115-13
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
import { useCallback, useMemo } from 'react'
22

3+
import {
4+
Genre,
5+
HashId,
6+
Mood,
7+
PremiumConditionsFollowUserId,
8+
PremiumConditionsNFTCollection,
9+
PremiumConditionsTipUserId
10+
} from '@audius/sdk'
311
import {
412
HarmonyButton,
513
HarmonyButtonType,
@@ -9,7 +17,8 @@ import {
917
import cn from 'classnames'
1018
import { Form, Formik, FormikProps, useField } from 'formik'
1119
import moment from 'moment'
12-
import * as Yup from 'yup'
20+
import { z } from 'zod'
21+
import { toFormikValidationSchema } from 'zod-formik-adapter'
1322

1423
import { ReactComponent as IconCaretLeft } from 'assets/img/iconCaretLeft.svg'
1524
import layoutStyles from 'components/layout/layout.module.css'
@@ -32,7 +41,11 @@ const messages = {
3241
multiTrackCount: (index: number, total: number) =>
3342
`TRACK ${index} of ${total}`,
3443
prev: 'Prev',
35-
next: 'Next Track'
44+
next: 'Next Track',
45+
titleRequiredError: 'Your track must have a name',
46+
artworkRequiredError: 'Artwork is required',
47+
genreRequiredError: 'Genre is required',
48+
invalidReleaseDateError: 'Release date should no be in the future'
3649
}
3750

3851
type EditPageProps = {
@@ -41,27 +54,115 @@ type EditPageProps = {
4154
onContinue: () => void
4255
}
4356

44-
const EditTrackSchema = Yup.object().shape({
45-
title: Yup.string().required(messages.titleError),
46-
artwork: Yup.object({
47-
url: Yup.string()
48-
}).required(messages.artworkError),
49-
trackArtwork: Yup.string().nullable(),
50-
genre: Yup.string().required(messages.genreError),
51-
description: Yup.string().max(1000).nullable()
52-
})
57+
// TODO: KJ - Need to update the schema in sdk and then import here
58+
const createUploadTrackMetadataSchema = () =>
59+
z.object({
60+
aiAttributionUserId: z.optional(HashId),
61+
description: z.optional(z.string().max(1000)),
62+
download: z.optional(
63+
z
64+
.object({
65+
cid: z.string(),
66+
isDownloadable: z.boolean(),
67+
requiresFollow: z.boolean()
68+
})
69+
.strict()
70+
.nullable()
71+
),
72+
fieldVisibility: z.optional(
73+
z.object({
74+
mood: z.optional(z.boolean()),
75+
tags: z.optional(z.boolean()),
76+
genre: z.optional(z.boolean()),
77+
share: z.optional(z.boolean()),
78+
playCount: z.optional(z.boolean()),
79+
remixes: z.optional(z.boolean())
80+
})
81+
),
82+
genre: z
83+
.enum(Object.values(Genre) as [Genre, ...Genre[]])
84+
.nullable()
85+
.refine((val) => val !== null, {
86+
message: messages.genreRequiredError
87+
}),
88+
isPremium: z.optional(z.boolean()),
89+
isrc: z.optional(z.string().nullable()),
90+
isUnlisted: z.optional(z.boolean()),
91+
iswc: z.optional(z.string().nullable()),
92+
license: z.optional(z.string().nullable()),
93+
mood: z
94+
.optional(z.enum(Object.values(Mood) as [Mood, ...Mood[]]))
95+
.nullable(),
96+
premiumConditions: z.optional(
97+
z.union([
98+
PremiumConditionsNFTCollection,
99+
PremiumConditionsFollowUserId,
100+
PremiumConditionsTipUserId
101+
])
102+
),
103+
releaseDate: z.optional(
104+
z.date().max(new Date(), { message: messages.invalidReleaseDateError })
105+
),
106+
remixOf: z.optional(
107+
z
108+
.object({
109+
tracks: z
110+
.array(
111+
z.object({
112+
parentTrackId: HashId
113+
})
114+
)
115+
.min(1)
116+
})
117+
.strict()
118+
),
119+
tags: z.optional(z.string()),
120+
title: z.string({
121+
required_error: messages.titleRequiredError
122+
}),
123+
previewStartSeconds: z.optional(z.number()),
124+
audioUploadId: z.optional(z.string()),
125+
previewCid: z.optional(z.string())
126+
})
127+
128+
const createTrackMetadataSchema = () => {
129+
return createUploadTrackMetadataSchema()
130+
.merge(
131+
z.object({
132+
artwork: z
133+
.object({
134+
url: z.string()
135+
})
136+
.nullable()
137+
})
138+
)
139+
.refine((form) => form.artwork !== null, {
140+
message: messages.artworkRequiredError,
141+
path: ['artwork']
142+
})
143+
}
144+
145+
export type TrackMetadataValues = z.input<
146+
ReturnType<typeof createTrackMetadataSchema>
147+
>
148+
149+
const EditFormValidationSchema = () =>
150+
z.object({
151+
trackMetadatas: z.array(createTrackMetadataSchema())
152+
})
53153

54154
export const EditPageNew = (props: EditPageProps) => {
55155
const { tracks, setTracks, onContinue } = props
56156

157+
// @ts-ignore - Slight differences in the sdk vs common track metadata types
57158
const initialValues: TrackEditFormValues = useMemo(
58159
() => ({
59160
trackMetadatasIndex: 0,
60161
trackMetadatas: tracks.map((track) => ({
61162
...track.metadata,
62163
artwork: null,
63164
description: '',
64-
releaseDate: moment().startOf('day'),
165+
releaseDate: new Date(moment().startOf('day').toString()),
65166
tags: '',
66167
field_visibility: {
67168
...defaultHiddenFields,
@@ -93,7 +194,8 @@ export const EditPageNew = (props: EditPageProps) => {
93194
<Formik<TrackEditFormValues>
94195
initialValues={initialValues}
95196
onSubmit={onSubmit}
96-
validationSchema={EditTrackSchema}
197+
// @ts-ignore - There are slight mismatches between the sdk and common track metadata types
198+
validationSchema={toFormikValidationSchema(EditFormValidationSchema())}
97199
>
98200
{TrackEditForm}
99201
</Formik>

apps/audius-client/packages/web/src/pages/upload-page/fields/ModalField.tsx

+9-8
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import React, { PropsWithChildren, ReactElement, useState } from 'react'
1+
import { PropsWithChildren, ReactElement, useState } from 'react'
22

33
import {
4-
Button,
5-
ButtonType,
4+
HarmonyButton,
5+
HarmonyButtonType,
66
IconCaretRight,
77
Modal,
88
ModalContent,
@@ -11,6 +11,7 @@ import {
1111
ModalTitle
1212
} from '@audius/stems'
1313
import { useFormikContext } from 'formik'
14+
import { isEmpty } from 'lodash'
1415

1516
import styles from './ModalField.module.css'
1617

@@ -27,7 +28,7 @@ type ModalFieldProps = PropsWithChildren & {
2728
export const ModalField = (props: ModalFieldProps) => {
2829
const { children, title, icon, preview } = props
2930
const [isModalOpen, setIsModalOpen] = useState(false)
30-
const { submitForm, resetForm } = useFormikContext()
31+
const { submitForm, resetForm, errors } = useFormikContext()
3132

3233
const open = () => setIsModalOpen(true)
3334
const close = () => setIsModalOpen(false)
@@ -45,14 +46,14 @@ export const ModalField = (props: ModalFieldProps) => {
4546
</ModalHeader>
4647
<ModalContent>{children}</ModalContent>
4748
<ModalFooter>
48-
<Button
49-
type={ButtonType.PRIMARY}
49+
<HarmonyButton
50+
variant={HarmonyButtonType.PRIMARY}
5051
text={messages.save}
5152
onClick={() => {
5253
submitForm()
53-
close()
54+
isEmpty(errors) && close()
5455
}}
55-
buttonType='submit'
56+
type='submit'
5657
/>
5758
</ModalFooter>
5859
</Modal>

apps/audius-client/packages/web/src/pages/upload-page/fields/ReleaseDateField.tsx

+3-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,9 @@ export const ReleaseDateField = () => {
3737

3838
const onSubmit = useCallback(
3939
(values: ReleaseDateFormValues) => {
40-
setValue(values[RELEASE_DATE])
40+
const date = new Date(values[RELEASE_DATE].toString())
41+
// @ts-ignore - There are slight differences in the sdk and common track metadata types
42+
setValue(date)
4143
},
4244
[setValue]
4345
)

apps/audius-client/packages/web/src/pages/upload-page/fields/TrackMetadataFields.module.css

+6-1
Original file line numberDiff line numberDiff line change
@@ -12,23 +12,28 @@
1212
flex: 1 auto;
1313
min-width: 335px;
1414
}
15+
1516
.fields > div {
1617
margin-bottom: var(--unit-4);
1718
}
19+
1820
.fields > div.description > .textArea > textarea {
1921
min-height: 90px;
2022
}
23+
2124
.fields > div.description {
2225
margin-bottom: var(--unit-4);
2326
}
2427

2528
.categorization {
2629
display: flex;
27-
align-items: center;
30+
align-items: flex-start;
2831
}
32+
2933
.categorization > div {
3034
flex: 1 auto;
3135
}
36+
3237
.categorization > div:first-child {
3338
margin-right: var(--unit-2);
3439
}

apps/audius-client/packages/web/src/pages/upload-page/forms/AttributionModalForm.module.css

+5
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,8 @@
3131
.licenseIcons {
3232
gap: 6px;
3333
}
34+
35+
.textFieldContainer {
36+
flex: 1 1 0;
37+
align-self: flex-start;
38+
}

0 commit comments

Comments
 (0)