Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[PAY-2332] Add new stems and downloads upload UI #7333

Merged
merged 18 commits into from
Jan 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion packages/common/src/models/TrackAvailabilityType.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
export enum TrackAvailabilityType {
export enum StreamTrackAvailabilityType {
PUBLIC = 'PUBLIC',
USDC_PURCHASE = 'USDC_PURCHASE',
SPECIAL_ACCESS = 'SPECIAL_ACCESS',
COLLECTIBLE_GATED = 'COLLECTIBLE_GATED',
HIDDEN = 'HIDDEN'
}

export enum DownloadTrackAvailabilityType {
PUBLIC = 'PUBLIC',
FOLLOWERS = 'FOLLOWERS',
USDC_PURCHASE = 'USDC_PURCHASE'
}
3 changes: 0 additions & 3 deletions packages/harmony/src/assets/icons/PenSquare.svg

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export const FilterButton = forwardRef<HTMLButtonElement, FilterButtonProps>(
label,
options,
onSelect,
isDisabled,
variant = 'fillContainer',
size = 'default',
iconRight = IconCaretDown,
Expand Down Expand Up @@ -142,10 +143,12 @@ export const FilterButton = forwardRef<HTMLButtonElement, FilterButtonProps>(
const handleButtonClick = useCallback(() => {
if (variant === 'fillContainer' && selection !== null) {
setSelection(null)
// @ts-ignore
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why ts-ignore here?

onSelect?.(null)
} else {
setIsOpen((isOpen: boolean) => !isOpen)
}
}, [selection, variant, setIsOpen, setSelection])
}, [selection, variant, setIsOpen, setSelection, onSelect])

const handleOptionSelect = useCallback(
(option: FilterButtonOption) => {
Expand All @@ -170,6 +173,7 @@ export const FilterButton = forwardRef<HTMLButtonElement, FilterButtonProps>(
? IconCloseAlt
: iconRight
}
disabled={isDisabled}
aria-haspopup='listbox'
aria-expanded={isOpen}
>
Expand Down
7 changes: 6 additions & 1 deletion packages/harmony/src/components/button/FilterButton/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export type FilterButtonProps = {
/**
* The selected value
*/
selection?: string
selection?: string | null

/**
* The button size
Expand Down Expand Up @@ -65,6 +65,11 @@ export type FilterButtonProps = {
*/
onSelect?: (label: string) => void

/**
* Whether interaction is disabled
*/
isDisabled?: boolean

/**
* Popup anchor origin
* @default { horizontal: 'center', vertical: 'bottom' }
Expand Down
2 changes: 0 additions & 2 deletions packages/harmony/src/icons/utilityIcons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@ import IconNoteSVG from '../assets/icons/Note.svg'
import IconNotificationOffSVG from '../assets/icons/NotificationOff.svg'
import IconNotificationOnSVG from '../assets/icons/NotificationOn.svg'
import IconPauseSVG from '../assets/icons/Pause.svg'
import IconPenSquareSVG from '../assets/icons/PenSquare.svg'
import IconPencilSVG from '../assets/icons/Pencil.svg'
import IconPlaySVG from '../assets/icons/Play.svg'
import IconPlaylistsSVG from '../assets/icons/Playlists.svg'
Expand Down Expand Up @@ -217,7 +216,6 @@ export const IconCloudDownloadQueued =
export const IconPause = IconPauseSVG as IconComponent
export const IconTurntable = IconTurntableSVG as IconComponent
export const IconCloudUpload = IconCloudUploadSVG as IconComponent
export const IconPenSquare = IconPenSquareSVG as IconComponent
export const IconPencil = IconPencilSVG as IconComponent
export const IconUser = IconUserSVG as IconComponent
export const IconCollectible = IconCollectibleSVG as IconComponent
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@
font-size: 15px;
font-weight: var(--font-demi-bold);
position: relative;
text-align: center;
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
}

.tab.isMobile {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ export const SegmentedControl = <T extends string>(
[styles.isMobile]: props.isMobile
})}
>
{option.icon}
<input
type='radio'
checked={option.key === selectedOption}
Expand Down
1 change: 1 addition & 0 deletions packages/stems/src/components/SegmentedControl/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export type Option<T> = {
key: T
text: string
icon?: React.ReactNode
}

export type SegmentedControlProps<T extends string> = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
TipGatedConditions,
USDCPurchaseConditions,
Track,
TrackAvailabilityType,
StreamTrackAvailabilityType,
accountSelectors,
isContentCollectibleGated,
isContentFollowGated,
Expand All @@ -31,21 +31,21 @@ import { useSelector } from 'react-redux'
import { toFormikValidationSchema } from 'zod-formik-adapter'

import { defaultFieldVisibility } from 'pages/track-page/utils'
import { AccessAndSaleFormSchema } from 'pages/upload-page/fields/AccessAndSaleField'
import { AccessAndSaleMenuFields } from 'pages/upload-page/fields/AccessAndSaleMenuFields'
import { getCombinedDefaultGatedConditionValues } from 'pages/upload-page/fields/helpers'
import {
AVAILABILITY_TYPE,
AccessAndSaleFormSchema,
AccessAndSaleFormValues,
AccessAndSaleMenuFields,
FIELD_VISIBILITY,
IS_STREAM_GATED,
IS_UNLISTED,
STREAM_CONDITIONS,
PREVIEW,
PRICE_HUMANIZED,
SPECIAL_ACCESS_TYPE,
getCombinedDefaultGatedConditionValues
} from 'pages/upload-page/fields/AccessAndSaleField'
import { SpecialAccessType } from 'pages/upload-page/fields/availability/SpecialAccessFields'
STREAM_AVAILABILITY_TYPE,
STREAM_CONDITIONS,
SpecialAccessType
} from 'pages/upload-page/fields/types'

import styles from './AccessAndSaleTriggerLegacy.module.css'
import { ContextualMenu } from './ContextualMenu'
Expand Down Expand Up @@ -148,9 +148,9 @@ export const AccessAndSaleTriggerLegacy = (
set(initialValues, IS_STREAM_GATED, isStreamGated)
set(initialValues, STREAM_CONDITIONS, tempStreamConditions)

let availabilityType = TrackAvailabilityType.PUBLIC
let availabilityType = StreamTrackAvailabilityType.PUBLIC
if (isUsdcGated) {
availabilityType = TrackAvailabilityType.USDC_PURCHASE
availabilityType = StreamTrackAvailabilityType.USDC_PURCHASE
set(
initialValues,
PRICE_HUMANIZED,
Expand All @@ -160,15 +160,15 @@ export const AccessAndSaleTriggerLegacy = (
)
}
if (isFollowGated || isTipGated) {
availabilityType = TrackAvailabilityType.SPECIAL_ACCESS
availabilityType = StreamTrackAvailabilityType.SPECIAL_ACCESS
}
if (isCollectibleGated) {
availabilityType = TrackAvailabilityType.COLLECTIBLE_GATED
availabilityType = StreamTrackAvailabilityType.COLLECTIBLE_GATED
}
if (isUnlisted && !isScheduledRelease) {
availabilityType = TrackAvailabilityType.HIDDEN
availabilityType = StreamTrackAvailabilityType.HIDDEN
}
set(initialValues, AVAILABILITY_TYPE, availabilityType)
set(initialValues, STREAM_AVAILABILITY_TYPE, availabilityType)
set(initialValues, FIELD_VISIBILITY, fieldVisibility)
set(initialValues, PREVIEW, preview)
set(
Expand All @@ -192,7 +192,7 @@ export const AccessAndSaleTriggerLegacy = (
])

const onSubmit = (values: AccessAndSaleFormValues) => {
const availabilityType = get(values, AVAILABILITY_TYPE)
const availabilityType = get(values, STREAM_AVAILABILITY_TYPE)
const preview = get(values, PREVIEW)
const specialAccessType = get(values, SPECIAL_ACCESS_TYPE)
const fieldVisibility = get(values, FIELD_VISIBILITY)
Expand All @@ -210,7 +210,7 @@ export const AccessAndSaleTriggerLegacy = (

// For gated options, extract the correct stream conditions based on the selected availability type
switch (availabilityType) {
case TrackAvailabilityType.USDC_PURCHASE: {
case StreamTrackAvailabilityType.USDC_PURCHASE: {
newState.preview_start_seconds = preview ?? 0
const {
usdc_purchase: { price }
Expand All @@ -222,7 +222,7 @@ export const AccessAndSaleTriggerLegacy = (
newState.is_stream_gated = true
break
}
case TrackAvailabilityType.SPECIAL_ACCESS: {
case StreamTrackAvailabilityType.SPECIAL_ACCESS: {
if (specialAccessType === SpecialAccessType.FOLLOW) {
const { follow_user_id } = streamConditions as FollowGatedConditions
newState.stream_conditions = { follow_user_id }
Expand All @@ -233,14 +233,14 @@ export const AccessAndSaleTriggerLegacy = (
newState.is_stream_gated = true
break
}
case TrackAvailabilityType.COLLECTIBLE_GATED: {
case StreamTrackAvailabilityType.COLLECTIBLE_GATED: {
const { nft_collection } =
streamConditions as CollectibleGatedConditions
newState.stream_conditions = { nft_collection }
newState.is_stream_gated = true
break
}
case TrackAvailabilityType.HIDDEN: {
case StreamTrackAvailabilityType.HIDDEN: {
newState = {
...newState,
...(fieldVisibility ?? undefined),
Expand All @@ -249,7 +249,7 @@ export const AccessAndSaleTriggerLegacy = (
}
break
}
case TrackAvailabilityType.PUBLIC: {
case StreamTrackAvailabilityType.PUBLIC: {
break
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,9 @@
border: 1px solid var(---border-strong);
background: var(--neutral-light-8);
}

.footer {
display: flex;
flex-direction: column;
align-items: center;
}
65 changes: 61 additions & 4 deletions packages/web/src/components/data-entry/ContextualMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { ReactNode, ReactElement, useCallback } from 'react'
import { ReactNode, ReactElement, useCallback, useMemo, useEffect } from 'react'

import { Nullable } from '@audius/common'
import { Box, Text as HarmonyText } from '@audius/harmony'
import {
Button,
ButtonType,
Expand All @@ -15,6 +17,7 @@ import {
Form,
Formik,
FormikConfig,
FormikErrors,
FormikHelpers,
FormikValues,
useFormikContext
Expand All @@ -32,23 +35,51 @@ const messages = {
save: 'Save'
}

export enum MenuFormCallbackStatus {
OPEN_ACCESS_AND_SALE = 'OPEN_ACCESS_AND_SALE'
}

type MenuFormProps = {
isOpen: boolean
onClose: () => void
label: string
icon: ReactNode
menuFields: ReactNode
closeMenuCallback?: (data?: any) => void
displayMenuErrorMessage?: (errors: FormikErrors<any>) => Nullable<string>
}

const MenuForm = (props: MenuFormProps) => {
const { isOpen, onClose, label, icon, menuFields } = props
const { resetForm } = useFormikContext()
const {
isOpen,
onClose,
label,
icon,
menuFields,
closeMenuCallback,
displayMenuErrorMessage
} = props
const { resetForm, errors, initialStatus, status, setStatus } =
useFormikContext()

const handleCancel = useCallback(() => {
resetForm()
onClose()
}, [resetForm, onClose])

const errorMessage = useMemo(() => {
if (errors && displayMenuErrorMessage) {
return displayMenuErrorMessage(errors)
}
}, [displayMenuErrorMessage, errors])

useEffect(() => {
if (!isOpen) {
closeMenuCallback?.(status)
setStatus(initialStatus)
}
}, [isOpen, closeMenuCallback, status, setStatus, initialStatus])

return (
<Modal onClose={handleCancel} isOpen={isOpen} size='medium'>
<ModalHeader>
Expand All @@ -57,7 +88,14 @@ const MenuForm = (props: MenuFormProps) => {
<ModalContent>
<Form id={label}>{menuFields}</Form>
</ModalContent>
<ModalFooter>
<ModalFooter className={styles.footer}>
{errorMessage ? (
<Box pb='l' ph='xl'>
<HarmonyText variant='body' color='danger' size='s'>
{errorMessage}
</HarmonyText>
</Box>
) : null}
<Button
form={label}
type={ButtonType.PRIMARY}
Expand Down Expand Up @@ -101,8 +139,14 @@ type ContextualMenuProps<FormValues extends FormikValues> = {
icon: ReactElement
renderValue: () => JSX.Element | null
menuFields: ReactNode
closeMenuCallback?: (data?: any) => void
forceOpen?: boolean
setForceOpen?: (value: boolean) => void
error?: boolean
errorMessage?: string
displayMenuErrorMessage?: (
errors: FormikErrors<FormValues>
) => Nullable<string>
previewOverride?: (toggleMenu: () => void) => ReactNode
} & FormikConfig<FormValues>

Expand All @@ -116,13 +160,24 @@ export const ContextualMenu = <FormValues extends FormikValues = FormikValues>(
menuFields,
renderValue,
onSubmit,
forceOpen,
setForceOpen,
closeMenuCallback,
error,
errorMessage,
displayMenuErrorMessage,
previewOverride,
...formikProps
} = props
const [isMenuOpen, toggleMenu] = useToggle(false)

useEffect(() => {
if (forceOpen && setForceOpen) {
setForceOpen(false)
toggleMenu()
}
}, [forceOpen, setForceOpen, toggleMenu])

const preview = previewOverride ? (
previewOverride(toggleMenu)
) : (
Expand Down Expand Up @@ -159,6 +214,8 @@ export const ContextualMenu = <FormValues extends FormikValues = FormikValues>(
isOpen={isMenuOpen}
onClose={toggleMenu}
menuFields={menuFields}
closeMenuCallback={closeMenuCallback}
displayMenuErrorMessage={displayMenuErrorMessage}
/>
</Formik>
</>
Expand Down
Loading