From d17716820f4d07c48f93fb7afe7d06b15c37d032 Mon Sep 17 00:00:00 2001 From: amendelsohn Date: Thu, 1 Aug 2024 14:09:09 -0700 Subject: [PATCH 01/12] [PAY-3311] Create one_to_many_dms flag --- packages/common/src/services/remote-config/feature-flags.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/common/src/services/remote-config/feature-flags.ts b/packages/common/src/services/remote-config/feature-flags.ts index 3cf545da38f..5874c59f651 100644 --- a/packages/common/src/services/remote-config/feature-flags.ts +++ b/packages/common/src/services/remote-config/feature-flags.ts @@ -58,7 +58,8 @@ export enum FeatureFlags { PREFETCH_STREAM_URLS = 'prefetch_stream_urls', HIDDEN_PAID_SCHEDULED = 'hidden_paid_scheduled', EDITABLE_ACCESS_ENABLED = 'editable_access_enabled', - PAID_SCHEDULED = 'paid_scheduled' + PAID_SCHEDULED = 'paid_scheduled', + ONE_TO_MANY_DMS = 'one_to_many_dms' } type FlagDefaults = Record @@ -132,5 +133,6 @@ export const flagDefaults: FlagDefaults = { [FeatureFlags.PREFETCH_STREAM_URLS]: false, [FeatureFlags.HIDDEN_PAID_SCHEDULED]: false, [FeatureFlags.EDITABLE_ACCESS_ENABLED]: false, - [FeatureFlags.PAID_SCHEDULED]: false + [FeatureFlags.PAID_SCHEDULED]: false, + [FeatureFlags.ONE_TO_MANY_DMS]: false } From 5bd093e0dbd710b269e118e1e394f5217cecb83a Mon Sep 17 00:00:00 2001 From: amendelsohn Date: Thu, 1 Aug 2024 14:28:29 -0700 Subject: [PATCH 02/12] wip --- .../search-users-modal/SearchUsersModal.tsx | 4 ++++ .../pages/chat-page/components/CreateChatModal.tsx | 11 ++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/packages/web/src/components/search-users-modal/SearchUsersModal.tsx b/packages/web/src/components/search-users-modal/SearchUsersModal.tsx index 0f18e70a03b..5812fcd2b5e 100644 --- a/packages/web/src/components/search-users-modal/SearchUsersModal.tsx +++ b/packages/web/src/components/search-users-modal/SearchUsersModal.tsx @@ -21,6 +21,7 @@ import { IconClose, IconSearch, Modal, + ModalFooter, ModalHeader, ModalProps, ModalTitle, @@ -65,6 +66,7 @@ type UsersSearchProps = { type SearchUsersModalProps = { titleProps: ModalTitleProps onCancel?: () => void + footer?: ReactNode } & Omit & Omit @@ -206,6 +208,7 @@ export const SearchUsersModal = ({ onClosed, onClose, isOpen, + footer, ...rest }: SearchUsersModalProps) => { const [query, setQuery] = useState('') @@ -236,6 +239,7 @@ export const SearchUsersModal = ({ onClose={handleClose} {...rest} /> + {footer || null} ) } diff --git a/packages/web/src/pages/chat-page/components/CreateChatModal.tsx b/packages/web/src/pages/chat-page/components/CreateChatModal.tsx index a91903629da..f3c404fd4a8 100644 --- a/packages/web/src/pages/chat-page/components/CreateChatModal.tsx +++ b/packages/web/src/pages/chat-page/components/CreateChatModal.tsx @@ -1,6 +1,8 @@ import { useCallback, useEffect } from 'react' +import { useFeatureFlag } from '@audius/common/hooks' import { User } from '@audius/common/models' +import { FeatureFlags } from '@audius/common/services' import { accountSelectors, chatActions, @@ -14,14 +16,16 @@ import { followersUserListSelectors, FOLLOWERS_USER_LIST_TAG } from '@audius/common/store' -import { IconCompose } from '@audius/harmony' +import { Flex, IconCompose, Paper } from '@audius/harmony' import { useDispatch } from 'react-redux' import { useSelector } from 'common/hooks/useSelector' +import FeatureFlag from 'components/feature-flag/FeatureFlag' import { SearchUsersModal } from 'components/search-users-modal/SearchUsersModal' import { CreateChatUserResult } from 'pages/chat-page/components/CreateChatUserResult' import { CreateChatEmptyResults } from './CreateChatEmptyResults' +import { TargetedMessageCTA } from './TargetedMessageCTA' const messages = { title: 'New Message' @@ -33,6 +37,10 @@ const { getUserList: getChatsUserList } = chatSelectors const { fetchBlockers, fetchMoreChats } = chatActions export const CreateChatModal = () => { + const { isEnabled: isOneToManyDmsEnabled } = useFeatureFlag( + FeatureFlags.ONE_TO_MANY_DMS + ) + const dispatch = useDispatch() const currentUser = useSelector(getAccountUser) const { isOpen, onClose, onClosed, data } = useCreateChatModal() @@ -108,6 +116,7 @@ export const CreateChatModal = () => { onClose={onClose} onClosed={onClosed} onCancel={handleCancel} + footer={isOneToManyDmsEnabled ? : undefined} /> ) From 5382a004e986f96960b4d2ca69cc6d1ee785d39a Mon Sep 17 00:00:00 2001 From: amendelsohn Date: Thu, 1 Aug 2024 14:44:40 -0700 Subject: [PATCH 03/12] Add IconTowerBroadcast to harmony --- .../harmony/src/assets/icons/TowerBroadcast.svg | 3 +++ packages/harmony/src/icons/Changelog.stories.mdx | 14 ++++++++++++++ .../harmony/src/icons/UtilityIcons.stories.mdx | 1 + packages/harmony/src/icons/utilityIcons.ts | 2 ++ packages/mobile/src/harmony-native/icons.ts | 1 + 5 files changed, 21 insertions(+) create mode 100644 packages/harmony/src/assets/icons/TowerBroadcast.svg diff --git a/packages/harmony/src/assets/icons/TowerBroadcast.svg b/packages/harmony/src/assets/icons/TowerBroadcast.svg new file mode 100644 index 00000000000..8101804ec6b --- /dev/null +++ b/packages/harmony/src/assets/icons/TowerBroadcast.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/harmony/src/icons/Changelog.stories.mdx b/packages/harmony/src/icons/Changelog.stories.mdx index 53542c60c60..3ba943a75ca 100644 --- a/packages/harmony/src/icons/Changelog.stories.mdx +++ b/packages/harmony/src/icons/Changelog.stories.mdx @@ -28,6 +28,20 @@ export function IconList({ children }) { ) } + + +## 8/1/24 + +Added TowerBroadcast + + + + + + + + + ## 9/6/23 diff --git a/packages/harmony/src/icons/UtilityIcons.stories.mdx b/packages/harmony/src/icons/UtilityIcons.stories.mdx index 0ba66f1940f..efd9dd56a10 100644 --- a/packages/harmony/src/icons/UtilityIcons.stories.mdx +++ b/packages/harmony/src/icons/UtilityIcons.stories.mdx @@ -171,6 +171,7 @@ Utility icons are simple, single-color glyphs that identify labels and actions a | | ThumbsDown | Thumbs | Down | - | - | | | ThumbsUp | Thumbs | Up | - | - | | | Tipping | Give | Audio | - | - | +| | TowerBroadcast | Tower | Broadcast | - | - | | | Transaction | Arrows | Bidirectional | - | - | | | Trash | Trash | - | - | - | | | Trending | Line | Trend | Up | - | diff --git a/packages/harmony/src/icons/utilityIcons.ts b/packages/harmony/src/icons/utilityIcons.ts index df68f3ef201..97cece1e932 100644 --- a/packages/harmony/src/icons/utilityIcons.ts +++ b/packages/harmony/src/icons/utilityIcons.ts @@ -118,6 +118,7 @@ import IconTastemakerSVG from '../assets/icons/Tastemaker.svg' import IconThumbsDownSVG from '../assets/icons/ThumbsDown.svg' import IconThumbsUpSVG from '../assets/icons/ThumbsUp.svg' import IconTippingSVG from '../assets/icons/Tipping.svg' +import IconTowerBroadcastSVG from '../assets/icons/TowerBroadcast.svg' import IconTransactionSVG from '../assets/icons/Transaction.svg' import IconTrashSVG from '../assets/icons/Trash.svg' import IconTrendingSVG from '../assets/icons/Trending.svg' @@ -215,6 +216,7 @@ export const IconTipping = IconTippingSVG as IconComponent export const IconCloudDownload = IconCloudDownloadSVG as IconComponent export const IconNoWifi = IconNoWifiSVG as IconComponent export const IconTransaction = IconTransactionSVG as IconComponent +export const IconTowerBroadcast = IconTowerBroadcastSVG as IconComponent export const IconCloudDownloadError = IconCloudDownloadErrorSVG as IconComponent export const IconNote = IconNoteSVG as IconComponent export const IconTrash = IconTrashSVG as IconComponent diff --git a/packages/mobile/src/harmony-native/icons.ts b/packages/mobile/src/harmony-native/icons.ts index 35ace641618..ebea5544ad8 100644 --- a/packages/mobile/src/harmony-native/icons.ts +++ b/packages/mobile/src/harmony-native/icons.ts @@ -74,6 +74,7 @@ export { default as IconMood } from '@audius/harmony/src/assets/icons/Mood.svg' export { default as IconTipping } from '@audius/harmony/src/assets/icons/Tipping.svg' export { default as IconCloudDownload } from '@audius/harmony/src/assets/icons/CloudDownload.svg' export { default as IconNoWifi } from '@audius/harmony/src/assets/icons/NoWifi.svg' +export { default as IconTowerBroadcast } from '@audius/harmony/src/assets/icons/TowerBroadcast.svg' export { default as IconTransaction } from '@audius/harmony/src/assets/icons/Transaction.svg' export { default as IconCloudDownloadError } from '@audius/harmony/src/assets/icons/CloudDownloadError.svg' export { default as IconNote } from '@audius/harmony/src/assets/icons/Note.svg' From dc163b1cc54406e03526368a03dd6539d6aa0420 Mon Sep 17 00:00:00 2001 From: amendelsohn Date: Thu, 1 Aug 2024 14:46:11 -0700 Subject: [PATCH 04/12] changeset --- .changeset/nasty-rice-judge.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/nasty-rice-judge.md diff --git a/.changeset/nasty-rice-judge.md b/.changeset/nasty-rice-judge.md new file mode 100644 index 00000000000..3fd42f1a393 --- /dev/null +++ b/.changeset/nasty-rice-judge.md @@ -0,0 +1,5 @@ +--- +'@audius/harmony': patch +--- + +Add TowerBroadcast icon From 2fe205435520e4cf55d9d13713a84188e978a833 Mon Sep 17 00:00:00 2001 From: amendelsohn Date: Thu, 1 Aug 2024 15:13:48 -0700 Subject: [PATCH 05/12] layout and styling for CTA --- .../chat-page/components/CreateChatModal.tsx | 3 +- .../components/TargetedMessageCTA.tsx | 97 +++++++++++++++++++ 2 files changed, 98 insertions(+), 2 deletions(-) create mode 100644 packages/web/src/pages/chat-page/components/TargetedMessageCTA.tsx diff --git a/packages/web/src/pages/chat-page/components/CreateChatModal.tsx b/packages/web/src/pages/chat-page/components/CreateChatModal.tsx index f3c404fd4a8..2f51997843d 100644 --- a/packages/web/src/pages/chat-page/components/CreateChatModal.tsx +++ b/packages/web/src/pages/chat-page/components/CreateChatModal.tsx @@ -16,11 +16,10 @@ import { followersUserListSelectors, FOLLOWERS_USER_LIST_TAG } from '@audius/common/store' -import { Flex, IconCompose, Paper } from '@audius/harmony' +import { IconCompose } from '@audius/harmony' import { useDispatch } from 'react-redux' import { useSelector } from 'common/hooks/useSelector' -import FeatureFlag from 'components/feature-flag/FeatureFlag' import { SearchUsersModal } from 'components/search-users-modal/SearchUsersModal' import { CreateChatUserResult } from 'pages/chat-page/components/CreateChatUserResult' diff --git a/packages/web/src/pages/chat-page/components/TargetedMessageCTA.tsx b/packages/web/src/pages/chat-page/components/TargetedMessageCTA.tsx new file mode 100644 index 00000000000..83101e82d55 --- /dev/null +++ b/packages/web/src/pages/chat-page/components/TargetedMessageCTA.tsx @@ -0,0 +1,97 @@ +import React from 'react' + +import { + Box, + Flex, + Text, + IconTowerBroadcast, + IconCaretRight, + useTheme, + IconVerified, + IconTokenBronze +} from '@audius/harmony' + +const messages = { + title: 'Send a Targeted Message', + description: 'Send messages to your fans in bulk.', + badgeRequired: 'Badge Required', + or: 'or' +} + +export const TargetedMessageCTA = () => { + const { color } = useTheme() + + const handleClick = () => { + // TODO: open modal + } + + // TODO: Implement userMeetsRequirements + const userMeetsRequirements = false + if (!userMeetsRequirements) { + return + } + + return ( + + + + + + {messages.title} + {messages.description} + + + + + + ) +} + +const TargetedMessageDisabled = () => { + return ( + + + + {messages.title} + + + + + {messages.badgeRequired} + + + + + {messages.or} + + + + + ) +} From 7659fce4ad7a2912091ad8c45b11baa68889f38c Mon Sep 17 00:00:00 2001 From: amendelsohn Date: Thu, 1 Aug 2024 15:25:04 -0700 Subject: [PATCH 06/12] badge or verified check --- .../src/pages/chat-page/components/TargetedMessageCTA.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/web/src/pages/chat-page/components/TargetedMessageCTA.tsx b/packages/web/src/pages/chat-page/components/TargetedMessageCTA.tsx index 83101e82d55..0d523edfb49 100644 --- a/packages/web/src/pages/chat-page/components/TargetedMessageCTA.tsx +++ b/packages/web/src/pages/chat-page/components/TargetedMessageCTA.tsx @@ -1,5 +1,7 @@ import React from 'react' +import { useGetCurrentUserId } from '@audius/common/api' +import { useSelectTierInfo } from '@audius/common/hooks' import { Box, Flex, @@ -25,8 +27,9 @@ export const TargetedMessageCTA = () => { // TODO: open modal } - // TODO: Implement userMeetsRequirements - const userMeetsRequirements = false + const { data: userId } = useGetCurrentUserId({}) + const { tierNumber, isVerified } = useSelectTierInfo(userId ?? 0) ?? {} + const userMeetsRequirements = isVerified || (tierNumber && tierNumber > 0) if (!userMeetsRequirements) { return } From 4ba2355db5e872a8c75d3d3c5ad2bd7a93b5d42d Mon Sep 17 00:00:00 2001 From: amendelsohn Date: Thu, 1 Aug 2024 16:11:51 -0700 Subject: [PATCH 07/12] TargetedMessageModal layout --- .../create-targeted-message-modal/index.ts | 17 ++++ .../common/src/store/ui/modals/parentSlice.ts | 1 + .../common/src/store/ui/modals/reducers.ts | 2 + packages/common/src/store/ui/modals/types.ts | 3 + .../search-users-modal/SearchUsersModal.tsx | 1 - .../chat-page/components/CreateChatModal.tsx | 8 +- .../components/TargetedMessageCTA.tsx | 23 ++++-- .../components/TargetedMessageModal.tsx | 80 +++++++++++++++++++ packages/web/src/pages/modals/Modals.tsx | 2 + 9 files changed, 128 insertions(+), 9 deletions(-) create mode 100644 packages/common/src/store/ui/modals/create-targeted-message-modal/index.ts create mode 100644 packages/web/src/pages/chat-page/components/TargetedMessageModal.tsx diff --git a/packages/common/src/store/ui/modals/create-targeted-message-modal/index.ts b/packages/common/src/store/ui/modals/create-targeted-message-modal/index.ts new file mode 100644 index 00000000000..d1d4caa4390 --- /dev/null +++ b/packages/common/src/store/ui/modals/create-targeted-message-modal/index.ts @@ -0,0 +1,17 @@ +import { createModal } from '../createModal' + +export type TargetedMessageModalState = {} + +const targetedMessageModal = createModal({ + reducerPath: 'TargetedMessageModal', + initialState: { + isOpen: false + }, + sliceSelector: (state) => state.ui.modals +}) + +export const { + hook: useTargetedMessageModal, + actions: targetedMessageModalActions, + reducer: targetedMessageModalReducer +} = targetedMessageModal diff --git a/packages/common/src/store/ui/modals/parentSlice.ts b/packages/common/src/store/ui/modals/parentSlice.ts index 4f67f14f03e..2c0f404e31b 100644 --- a/packages/common/src/store/ui/modals/parentSlice.ts +++ b/packages/common/src/store/ui/modals/parentSlice.ts @@ -50,6 +50,7 @@ export const initialState: BasicModalsState = { AiAttributionSettings: { isOpen: false }, PremiumContentPurchaseModal: { isOpen: false }, CreateChatModal: { isOpen: false }, + TargetedMessageModal: { isOpen: false }, LeavingAudiusModal: { isOpen: false }, InboxUnavailableModal: { isOpen: false }, UploadConfirmation: { isOpen: false }, diff --git a/packages/common/src/store/ui/modals/reducers.ts b/packages/common/src/store/ui/modals/reducers.ts index 5a7ab67849e..5ec41cdd5d6 100644 --- a/packages/common/src/store/ui/modals/reducers.ts +++ b/packages/common/src/store/ui/modals/reducers.ts @@ -6,6 +6,7 @@ import { artistPickModalReducer } from './artist-pick-modal' import { coinflowOnrampModalReducer } from './coinflow-onramp-modal' import { coinflowWithdrawModalReducer } from './coinflow-withdraw-modal' import { createChatModalReducer } from './create-chat-modal' +import { targetedMessageModalReducer } from './create-targeted-message-modal' import { inboxUnavailableModalReducer } from './inbox-unavailable-modal' import { leavingAudiusModalReducer } from './leaving-audius-modal' import parentReducer, { initialState } from './parentSlice' @@ -34,6 +35,7 @@ const noOpReducers = Object.keys(initialState).reduce((prev, curr) => { const combinedReducers = combineReducers({ ...noOpReducers, CreateChatModal: createChatModalReducer, + TargetedMessageModal: targetedMessageModalReducer, InboxUnavailableModal: inboxUnavailableModalReducer, LeavingAudiusModal: leavingAudiusModalReducer, WithdrawUSDCModal: withdrawUSDCModalReducer, diff --git a/packages/common/src/store/ui/modals/types.ts b/packages/common/src/store/ui/modals/types.ts index 58f8cca66f9..ba8d10a9d5e 100644 --- a/packages/common/src/store/ui/modals/types.ts +++ b/packages/common/src/store/ui/modals/types.ts @@ -7,6 +7,7 @@ import { AlbumTrackRemoveConfirmationModalState } from './album-track-remove-con import { ArtistPickModalState } from './artist-pick-modal' import { CoinflowOnrampModalState } from './coinflow-onramp-modal' import { CoinflowWithdrawModalState } from './coinflow-withdraw-modal' +import { TargetedMessageModalState } from './create-targeted-message-modal' import { InboxUnavailableModalState } from './inbox-unavailable-modal' import { LeavingAudiusModalState } from './leaving-audius-modal' import { PremiumContentPurchaseModalState } from './premium-content-purchase-modal' @@ -70,6 +71,7 @@ export type Modals = | 'DuplicateAddConfirmation' | 'PremiumContentPurchaseModal' | 'CreateChatModal' + | 'TargetedMessageModal' | 'InboxUnavailableModal' | 'LeavingAudiusModal' | 'UploadConfirmation' @@ -95,6 +97,7 @@ export type BasicModalsState = { export type StatefulModalsState = { CoinflowOnramp: CoinflowOnrampModalState CreateChatModal: CreateChatModalState + TargetedMessageModal: TargetedMessageModalState InboxUnavailableModal: InboxUnavailableModalState LeavingAudiusModal: LeavingAudiusModalState WithdrawUSDCModal: WithdrawUSDCModalState diff --git a/packages/web/src/components/search-users-modal/SearchUsersModal.tsx b/packages/web/src/components/search-users-modal/SearchUsersModal.tsx index 5812fcd2b5e..174fd82b5f5 100644 --- a/packages/web/src/components/search-users-modal/SearchUsersModal.tsx +++ b/packages/web/src/components/search-users-modal/SearchUsersModal.tsx @@ -21,7 +21,6 @@ import { IconClose, IconSearch, Modal, - ModalFooter, ModalHeader, ModalProps, ModalTitle, diff --git a/packages/web/src/pages/chat-page/components/CreateChatModal.tsx b/packages/web/src/pages/chat-page/components/CreateChatModal.tsx index 2f51997843d..bed826ea9fc 100644 --- a/packages/web/src/pages/chat-page/components/CreateChatModal.tsx +++ b/packages/web/src/pages/chat-page/components/CreateChatModal.tsx @@ -36,7 +36,7 @@ const { getUserList: getChatsUserList } = chatSelectors const { fetchBlockers, fetchMoreChats } = chatActions export const CreateChatModal = () => { - const { isEnabled: isOneToManyDmsEnabled } = useFeatureFlag( + const { isEnabled: isTargetedMessagesEnabled } = useFeatureFlag( FeatureFlags.ONE_TO_MANY_DMS ) @@ -115,7 +115,11 @@ export const CreateChatModal = () => { onClose={onClose} onClosed={onClosed} onCancel={handleCancel} - footer={isOneToManyDmsEnabled ? : undefined} + footer={ + isTargetedMessagesEnabled ? ( + + ) : undefined + } /> ) diff --git a/packages/web/src/pages/chat-page/components/TargetedMessageCTA.tsx b/packages/web/src/pages/chat-page/components/TargetedMessageCTA.tsx index 0d523edfb49..58d0f541836 100644 --- a/packages/web/src/pages/chat-page/components/TargetedMessageCTA.tsx +++ b/packages/web/src/pages/chat-page/components/TargetedMessageCTA.tsx @@ -1,7 +1,8 @@ -import React from 'react' +import React, { useCallback } from 'react' import { useGetCurrentUserId } from '@audius/common/api' import { useSelectTierInfo } from '@audius/common/hooks' +import { useTargetedMessageModal } from '@audius/common/src/store/ui/modals/create-targeted-message-modal' import { Box, Flex, @@ -20,17 +21,27 @@ const messages = { or: 'or' } -export const TargetedMessageCTA = () => { +type TargetedMessageCTAProps = { + onClick: () => void +} + +export const TargetedMessageCTA = (props: TargetedMessageCTAProps) => { + const { onClick } = props const { color } = useTheme() - const handleClick = () => { - // TODO: open modal - } + const { onOpen: openTargetedMessageModal } = useTargetedMessageModal() const { data: userId } = useGetCurrentUserId({}) const { tierNumber, isVerified } = useSelectTierInfo(userId ?? 0) ?? {} const userMeetsRequirements = isVerified || (tierNumber && tierNumber > 0) - if (!userMeetsRequirements) { + + const handleClick = useCallback(() => { + onClick() + openTargetedMessageModal() + }, [onClick, openTargetedMessageModal]) + + // DEBUG + if (!userMeetsRequirements && !true) { return } diff --git a/packages/web/src/pages/chat-page/components/TargetedMessageModal.tsx b/packages/web/src/pages/chat-page/components/TargetedMessageModal.tsx new file mode 100644 index 00000000000..835db89e248 --- /dev/null +++ b/packages/web/src/pages/chat-page/components/TargetedMessageModal.tsx @@ -0,0 +1,80 @@ +import { useTargetedMessageModal } from '@audius/common/src/store/ui/modals/create-targeted-message-modal' +import { + Flex, + IconTowerBroadcast, + Modal, + ModalHeader, + Text, + ModalTitle, + Radio, + Button, + RadioGroup, + ModalFooter, + IconCaretLeft, + ModalContent +} from '@audius/harmony' + +const messages = { + title: 'Target Audience', + back: 'Back', + continue: 'Continue' +} + +const targetAudienceOptions = [ + { + label: 'My Followers', + value: 'followers' + }, + { + label: 'Tip Supporters', + value: 'supporters' + }, + { + label: 'Past Purchasers', + value: 'purchasers' + }, + { + label: 'Remix Creators', + value: 'remix_creators' + } +] + +export const TargetedMessageModal = () => { + const { isOpen, onClose } = useTargetedMessageModal() + return ( + + + } title={messages.title} /> + + + {/* */} + + + {targetAudienceOptions.map(({ label, value }) => ( + + + + {label} + + + ))} + + + + + + + + + + + ) +} diff --git a/packages/web/src/pages/modals/Modals.tsx b/packages/web/src/pages/modals/Modals.tsx index fa84d8610bf..41e85a2975d 100644 --- a/packages/web/src/pages/modals/Modals.tsx +++ b/packages/web/src/pages/modals/Modals.tsx @@ -50,6 +50,7 @@ import { ClaimAllRewardsModal } from 'pages/audio-rewards-page/components/modals import TopAPIModal from 'pages/audio-rewards-page/components/modals/TopAPI' import TransferAudioMobileDrawer from 'pages/audio-rewards-page/components/modals/TransferAudioMobileDrawer' import { VipDiscordModal } from 'pages/audio-rewards-page/components/modals/VipDiscordModal' +import { TargetedMessageModal } from 'pages/chat-page/components/TargetedMessageModal' import { AppModal } from './AppModal' @@ -102,6 +103,7 @@ const commonModalsMap: { [Modal in ModalTypes]?: ComponentType } = { PremiumContentPurchaseModal, LeavingAudiusModal, CreateChatModal, + TargetedMessageModal, InboxUnavailableModal, WithdrawUSDCModal, CoinflowOnramp: CoinflowOnrampModal, From 950938f54bbe88d0d958a546d5a722052519b697 Mon Sep 17 00:00:00 2001 From: amendelsohn Date: Fri, 2 Aug 2024 14:23:29 -0700 Subject: [PATCH 08/12] ready for PR --- .../components/TargetedMessageCTA.tsx | 2 +- .../components/TargetedMessageModal.tsx | 110 +++++++++++++----- 2 files changed, 80 insertions(+), 32 deletions(-) diff --git a/packages/web/src/pages/chat-page/components/TargetedMessageCTA.tsx b/packages/web/src/pages/chat-page/components/TargetedMessageCTA.tsx index 58d0f541836..151427cffdf 100644 --- a/packages/web/src/pages/chat-page/components/TargetedMessageCTA.tsx +++ b/packages/web/src/pages/chat-page/components/TargetedMessageCTA.tsx @@ -41,7 +41,7 @@ export const TargetedMessageCTA = (props: TargetedMessageCTAProps) => { }, [onClick, openTargetedMessageModal]) // DEBUG - if (!userMeetsRequirements && !true) { + if (!userMeetsRequirements) { return } diff --git a/packages/web/src/pages/chat-page/components/TargetedMessageModal.tsx b/packages/web/src/pages/chat-page/components/TargetedMessageModal.tsx index 835db89e248..764375aae89 100644 --- a/packages/web/src/pages/chat-page/components/TargetedMessageModal.tsx +++ b/packages/web/src/pages/chat-page/components/TargetedMessageModal.tsx @@ -13,6 +13,7 @@ import { IconCaretLeft, ModalContent } from '@audius/harmony' +import { Formik, FormikValues, useField } from 'formik' const messages = { title: 'Target Audience', @@ -41,40 +42,87 @@ const targetAudienceOptions = [ export const TargetedMessageModal = () => { const { isOpen, onClose } = useTargetedMessageModal() + + const initialValues = { + target_audience: undefined + } + + const handleSubmit = (values: FormikValues) => { + window.alert(JSON.stringify(values)) + switch (values.target_audience) { + case 'followers': + // do something + break + case 'supporters': + // do something + break + case 'purchasers': + // do something + break + case 'remix_creators': + // do something + break + default: + break + } + } + return ( - - } title={messages.title} /> - - - {/* */} - - - {targetAudienceOptions.map(({ label, value }) => ( - - - - {label} - + + {({ submitForm }) => ( + <> + + } + title={messages.title} + /> + + + + + + + + - ))} - - - - - - - - - + + + )} + ) } + +const TargetedMessagesFields = () => { + const [field] = useField('target_audience') + + return ( + + + {targetAudienceOptions.map(({ label, value }) => ( + + + + {label} + + + ))} + + + ) +} From b44998a284b05ad946c37defc72a9308f3f0dc55 Mon Sep 17 00:00:00 2001 From: amendelsohn Date: Mon, 5 Aug 2024 11:23:23 -0700 Subject: [PATCH 09/12] wip --- .../components/TargetedMessageCTA.tsx | 2 +- .../components/TargetedMessageModal.tsx | 217 ++++++++++++++++-- 2 files changed, 196 insertions(+), 23 deletions(-) diff --git a/packages/web/src/pages/chat-page/components/TargetedMessageCTA.tsx b/packages/web/src/pages/chat-page/components/TargetedMessageCTA.tsx index 151427cffdf..58d0f541836 100644 --- a/packages/web/src/pages/chat-page/components/TargetedMessageCTA.tsx +++ b/packages/web/src/pages/chat-page/components/TargetedMessageCTA.tsx @@ -41,7 +41,7 @@ export const TargetedMessageCTA = (props: TargetedMessageCTAProps) => { }, [onClick, openTargetedMessageModal]) // DEBUG - if (!userMeetsRequirements) { + if (!userMeetsRequirements && !true) { return } diff --git a/packages/web/src/pages/chat-page/components/TargetedMessageModal.tsx b/packages/web/src/pages/chat-page/components/TargetedMessageModal.tsx index 764375aae89..f59da1f35ca 100644 --- a/packages/web/src/pages/chat-page/components/TargetedMessageModal.tsx +++ b/packages/web/src/pages/chat-page/components/TargetedMessageModal.tsx @@ -1,3 +1,7 @@ +import { useMemo } from 'react' + +import { useGetCurrentUser, useGetUserTracksByHandle } from '@audius/common/api' +import { isContentUSDCPurchaseGated } from '@audius/common/models' import { useTargetedMessageModal } from '@audius/common/src/store/ui/modals/create-targeted-message-modal' import { Flex, @@ -11,40 +15,41 @@ import { RadioGroup, ModalFooter, IconCaretLeft, - ModalContent + ModalContent, + Select } from '@audius/harmony' import { Formik, FormikValues, useField } from 'formik' const messages = { title: 'Target Audience', back: 'Back', - continue: 'Continue' -} - -const targetAudienceOptions = [ - { + continue: 'Continue', + followers: { label: 'My Followers', - value: 'followers' + description: 'Send a bulk message to all of your followers.' }, - { + supporters: { label: 'Tip Supporters', - value: 'supporters' + description: 'Send a bulk message to everyone who has tipped you.' }, - { + purchasers: { label: 'Past Purchasers', - value: 'purchasers' + description: + 'Send a bulk message to everyone who has purchased content from you on Audius.' }, - { + remixCreators: { label: 'Remix Creators', - value: 'remix_creators' + description: 'Send a bulk message to creators who have remixed your tracks.' } -] +} export const TargetedMessageModal = () => { const { isOpen, onClose } = useTargetedMessageModal() const initialValues = { - target_audience: undefined + target_audience: undefined, + purchased_track_id: undefined, + remixed_track_id: undefined } const handleSubmit = (values: FormikValues) => { @@ -114,15 +119,183 @@ const TargetedMessagesFields = () => { return ( - {targetAudienceOptions.map(({ label, value }) => ( - - - - {label} + + + + + + + ) +} + +const FollowersMessageField = () => { + const { data: user } = useGetCurrentUser() + const { follower_count: followerCount } = user ?? {} + const [{ value }] = useField('target_audience') + const selected = value === 'followers' + return ( + + + + + + {messages.followers.label} + + {selected ? ( + + ({followerCount ?? 0}) + + ) : null} + + {selected ? ( + {messages.followers.description} + ) : null} + + + ) +} + +const TipSupportersMessageField = () => { + const { data: user } = useGetCurrentUser() + const { supporter_count: supporterCount } = user ?? {} + const [{ value }] = useField('target_audience') + const selected = value === 'supporters' + return ( + + + + + + {messages.supporters.label} + + {selected ? ( + + ({supporterCount ?? 0}) + + ) : null} + + {selected ? ( + {messages.supporters.description} + ) : null} + + + ) +} + +const PastPurchasersMessageField = () => { + const { data: user } = useGetCurrentUser() + const { + // TODO: Need to add a new endpoint to get the list of past purchasers + supporter_count: supporterCount, + handle, + user_id: currentUserId + } = user ?? {} + const [{ value }] = useField('target_audience') + const [purchasedTrackField] = useField({ + name: 'purchased_track_id', + type: 'select' + }) + + const isSelected = value === 'purchasers' + + const { data: tracks } = useGetUserTracksByHandle({ handle, currentUserId }) + const premiumTrackOptions = useMemo( + () => + (tracks ?? []) + .filter((track) => isContentUSDCPurchaseGated(track.stream_conditions)) + .map((track) => ({ + value: track.track_id.toString(), + label: track.title + })), + [tracks] + ) + + return ( + + + + + + {messages.purchasers.label} + + {isSelected ? ( + + ({supporterCount ?? 0}) + ) : null} + + {isSelected ? ( + + {messages.purchasers.description} + + + ) : null} + + ) } From f78137d46cb5c01756ec77af00ae9a65b218cf19 Mon Sep 17 00:00:00 2001 From: amendelsohn Date: Mon, 5 Aug 2024 14:19:35 -0700 Subject: [PATCH 10/12] check download_conditions too --- .../pages/chat-page/components/TargetedMessageModal.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/web/src/pages/chat-page/components/TargetedMessageModal.tsx b/packages/web/src/pages/chat-page/components/TargetedMessageModal.tsx index f59da1f35ca..46dc7f971c8 100644 --- a/packages/web/src/pages/chat-page/components/TargetedMessageModal.tsx +++ b/packages/web/src/pages/chat-page/components/TargetedMessageModal.tsx @@ -1,6 +1,7 @@ import { useMemo } from 'react' import { useGetCurrentUser, useGetUserTracksByHandle } from '@audius/common/api' +import { useAllPaginatedQuery } from '@audius/common/audius-query' import { isContentUSDCPurchaseGated } from '@audius/common/models' import { useTargetedMessageModal } from '@audius/common/src/store/ui/modals/create-targeted-message-modal' import { @@ -202,7 +203,11 @@ const PastPurchasersMessageField = () => { const premiumTrackOptions = useMemo( () => (tracks ?? []) - .filter((track) => isContentUSDCPurchaseGated(track.stream_conditions)) + .filter( + (track) => + isContentUSDCPurchaseGated(track.stream_conditions) || + isContentUSDCPurchaseGated(track.download_conditions) + ) .map((track) => ({ value: track.track_id.toString(), label: track.title From f13b3722945434881ff3a20c1fb02b04a895cf92 Mon Sep 17 00:00:00 2001 From: amendelsohn Date: Tue, 6 Aug 2024 16:02:59 -0700 Subject: [PATCH 11/12] cleanup --- .../components/TargetedMessageModal.tsx | 56 +++++++++++-------- 1 file changed, 33 insertions(+), 23 deletions(-) diff --git a/packages/web/src/pages/chat-page/components/TargetedMessageModal.tsx b/packages/web/src/pages/chat-page/components/TargetedMessageModal.tsx index 46dc7f971c8..85323dcb93d 100644 --- a/packages/web/src/pages/chat-page/components/TargetedMessageModal.tsx +++ b/packages/web/src/pages/chat-page/components/TargetedMessageModal.tsx @@ -1,7 +1,6 @@ import { useMemo } from 'react' import { useGetCurrentUser, useGetUserTracksByHandle } from '@audius/common/api' -import { useAllPaginatedQuery } from '@audius/common/audius-query' import { isContentUSDCPurchaseGated } from '@audius/common/models' import { useTargetedMessageModal } from '@audius/common/src/store/ui/modals/create-targeted-message-modal' import { @@ -36,19 +35,30 @@ const messages = { purchasers: { label: 'Past Purchasers', description: - 'Send a bulk message to everyone who has purchased content from you on Audius.' + 'Send a bulk message to everyone who has purchased content from you on Audius.', + placeholder: 'Premium Content' }, remixCreators: { label: 'Remix Creators', - description: 'Send a bulk message to creators who have remixed your tracks.' + description: + 'Send a bulk message to creators who have remixed your tracks.', + placeholder: 'Tracks with Remixes' } } +const TARGET_AUDIENCE_FIELD = 'target_audience' + +type TargetedMessageFormValues = { + target_audience: 'followers' | 'supporters' | 'purchasers' | 'remix_creators' + purchased_track_id?: string + remixed_track_id?: string +} + export const TargetedMessageModal = () => { const { isOpen, onClose } = useTargetedMessageModal() - const initialValues = { - target_audience: undefined, + const initialValues: TargetedMessageFormValues = { + target_audience: 'followers', purchased_track_id: undefined, remixed_track_id: undefined } @@ -75,7 +85,10 @@ export const TargetedMessageModal = () => { return ( - + + initialValues={initialValues} + onSubmit={handleSubmit} + > {({ submitForm }) => ( <> @@ -115,7 +128,7 @@ export const TargetedMessageModal = () => { } const TargetedMessagesFields = () => { - const [field] = useField('target_audience') + const [field] = useField(TARGET_AUDIENCE_FIELD) return ( @@ -132,7 +145,7 @@ const TargetedMessagesFields = () => { const FollowersMessageField = () => { const { data: user } = useGetCurrentUser() const { follower_count: followerCount } = user ?? {} - const [{ value }] = useField('target_audience') + const [{ value }] = useField(TARGET_AUDIENCE_FIELD) const selected = value === 'followers' return ( @@ -159,7 +172,7 @@ const FollowersMessageField = () => { const TipSupportersMessageField = () => { const { data: user } = useGetCurrentUser() const { supporter_count: supporterCount } = user ?? {} - const [{ value }] = useField('target_audience') + const [{ value }] = useField(TARGET_AUDIENCE_FIELD) const selected = value === 'supporters' return ( @@ -191,8 +204,8 @@ const PastPurchasersMessageField = () => { handle, user_id: currentUserId } = user ?? {} - const [{ value }] = useField('target_audience') - const [purchasedTrackField] = useField({ + const [{ value }] = useField(TARGET_AUDIENCE_FIELD) + const [purchasedTrackField, , { setValue: setPurchasedTrackId }] = useField({ name: 'purchased_track_id', type: 'select' }) @@ -235,8 +248,8 @@ const PastPurchasersMessageField = () => { ) : null} From 5d71cd0fdc9d41ec6375da33994d06f4b24cdf2f Mon Sep 17 00:00:00 2001 From: amendelsohn Date: Tue, 6 Aug 2024 16:14:00 -0700 Subject: [PATCH 12/12] remove debug logic --- .../web/src/pages/chat-page/components/TargetedMessageCTA.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/web/src/pages/chat-page/components/TargetedMessageCTA.tsx b/packages/web/src/pages/chat-page/components/TargetedMessageCTA.tsx index e9a4a9408b3..cd615e3b037 100644 --- a/packages/web/src/pages/chat-page/components/TargetedMessageCTA.tsx +++ b/packages/web/src/pages/chat-page/components/TargetedMessageCTA.tsx @@ -41,7 +41,7 @@ export const TargetedMessageCTA = (props: TargetedMessageCTAProps) => { }, [onClick, openTargetedMessageModal]) // DEBUG - if (!userMeetsRequirements && !true) { + if (!userMeetsRequirements) { return }