From 9d233a0e1df5488f92595b63cec673e58ac943aa Mon Sep 17 00:00:00 2001 From: amendelsohn Date: Wed, 2 Oct 2024 17:14:14 -0700 Subject: [PATCH 1/4] Add purchasers and remixers UserList types --- packages/common/src/store/reducers.ts | 4 + packages/common/src/store/user-list/index.ts | 8 ++ .../src/store/user-list/purchasers/actions.ts | 31 ++++++ .../store/user-list/purchasers/reducers.ts | 39 ++++++++ .../store/user-list/purchasers/selectors.ts | 18 ++++ .../src/store/user-list/purchasers/types.ts | 17 ++++ .../src/store/user-list/remixers/actions.ts | 22 +++++ .../src/store/user-list/remixers/reducers.ts | 37 +++++++ .../src/store/user-list/remixers/selectors.ts | 13 +++ .../src/store/user-list/remixers/types.ts | 15 +++ .../store/user-list/purchasers/errorSagas.ts | 25 +++++ .../store/user-list/purchasers/sagas.ts | 97 +++++++++++++++++++ .../store/user-list/remixers/errorSagas.ts | 25 +++++ .../common/store/user-list/remixers/sagas.ts | 88 +++++++++++++++++ .../components/UserListModal.tsx | 40 +++++++- .../application/ui/userListModal/sagas.ts | 10 +- .../application/ui/userListModal/types.ts | 4 +- 17 files changed, 488 insertions(+), 5 deletions(-) create mode 100644 packages/common/src/store/user-list/purchasers/actions.ts create mode 100644 packages/common/src/store/user-list/purchasers/reducers.ts create mode 100644 packages/common/src/store/user-list/purchasers/selectors.ts create mode 100644 packages/common/src/store/user-list/purchasers/types.ts create mode 100644 packages/common/src/store/user-list/remixers/actions.ts create mode 100644 packages/common/src/store/user-list/remixers/reducers.ts create mode 100644 packages/common/src/store/user-list/remixers/selectors.ts create mode 100644 packages/common/src/store/user-list/remixers/types.ts create mode 100644 packages/web/src/common/store/user-list/purchasers/errorSagas.ts create mode 100644 packages/web/src/common/store/user-list/purchasers/sagas.ts create mode 100644 packages/web/src/common/store/user-list/remixers/errorSagas.ts create mode 100644 packages/web/src/common/store/user-list/remixers/sagas.ts diff --git a/packages/common/src/store/reducers.ts b/packages/common/src/store/reducers.ts index e4daa440ddc..c7129273748 100644 --- a/packages/common/src/store/reducers.ts +++ b/packages/common/src/store/reducers.ts @@ -126,7 +126,9 @@ import followersUserListReducer from './user-list/followers/reducers' import followingUserListReducer from './user-list/following/reducers' import mutualsUserListReducer from './user-list/mutuals/reducers' import notificationsUserListReducer from './user-list/notifications/reducers' +import purchasersUserListReducer from './user-list/purchasers/reducers' import relatedArtistsListReducer from './user-list/related-artists/reducers' +import remixersUserListReducer from './user-list/remixers/reducers' import repostsUserListReducer from './user-list/reposts/reducers' import supportingUserListReducer from './user-list/supporting/reducers' import topSupportersUserListReducer from './user-list/top-supporters/reducers' @@ -338,6 +340,8 @@ export type CommonState = { topSupporters: ReturnType supporting: ReturnType relatedArtists: ReturnType + purchasers: ReturnType + remixers: ReturnType } theme: ThemeState vipDiscordModal: VipDiscordModalState diff --git a/packages/common/src/store/user-list/index.ts b/packages/common/src/store/user-list/index.ts index ca08bc9f12c..40760ba6ac6 100644 --- a/packages/common/src/store/user-list/index.ts +++ b/packages/common/src/store/user-list/index.ts @@ -41,3 +41,11 @@ export * from './favorites/types' export * as relatedArtistsUserListActions from './related-artists/actions' export * as relatedArtistsUserListSelectors from './related-artists/selectors' export * from './related-artists/types' + +export * as purchasersUserListActions from './purchasers/actions' +export * as purchasersUserListSelectors from './purchasers/selectors' +export * from './purchasers/types' + +export * as remixersUserListActions from './remixers/actions' +export * as remixersUserListSelectors from './remixers/selectors' +export * from './remixers/types' diff --git a/packages/common/src/store/user-list/purchasers/actions.ts b/packages/common/src/store/user-list/purchasers/actions.ts new file mode 100644 index 00000000000..3019612a824 --- /dev/null +++ b/packages/common/src/store/user-list/purchasers/actions.ts @@ -0,0 +1,31 @@ +import { createCustomAction } from 'typesafe-actions' + +import type { PurchaseableContentType } from '~/store/purchase-content' + +import { ID } from '../../../models' + +export const SET_PURCHASERS = 'PURCHASERS_USER_PAGE/SET_PURCHASERS' +export const GET_PURCHASERS_ERROR = 'PURCHASERS_USER_PAGE/GET_PURCHASERS_ERROR' + +export const setPurchasers = createCustomAction( + SET_PURCHASERS, + (id: ID, contentType?: PurchaseableContentType, contentId?: ID) => ({ + id, + contentType, + contentId + }) +) +export const getPurchasersError = createCustomAction( + GET_PURCHASERS_ERROR, + ( + id: ID, + error: string, + contentType?: PurchaseableContentType, + contentId?: ID + ) => ({ + id, + contentType, + contentId, + error + }) +) diff --git a/packages/common/src/store/user-list/purchasers/reducers.ts b/packages/common/src/store/user-list/purchasers/reducers.ts new file mode 100644 index 00000000000..7c0e0c687f3 --- /dev/null +++ b/packages/common/src/store/user-list/purchasers/reducers.ts @@ -0,0 +1,39 @@ +import { combineReducers } from 'redux' +import { createReducer, ActionType } from 'typesafe-actions' + +import { UserListReducerFactory } from '../reducer' + +import * as actions from './actions' +import { PurchasersOwnState, PURCHASERS_USER_LIST_TAG } from './types' + +type PurchasersActions = ActionType + +const userListReducer = UserListReducerFactory.createReducer({ + tag: PURCHASERS_USER_LIST_TAG, + pageSize: 15 +}) + +const initialState = { + id: null, + contentType: undefined, + contentId: undefined +} + +const purchasersPageReducer = createReducer< + PurchasersOwnState, + PurchasersActions +>(initialState, { + [actions.SET_PURCHASERS](state, action) { + return { + ...state, + id: action.id, + contentType: action.contentType, + contentId: action.contentId + } + } +}) + +export default combineReducers({ + purchasersPage: purchasersPageReducer, + userList: userListReducer +}) diff --git a/packages/common/src/store/user-list/purchasers/selectors.ts b/packages/common/src/store/user-list/purchasers/selectors.ts new file mode 100644 index 00000000000..fcd36cd30cc --- /dev/null +++ b/packages/common/src/store/user-list/purchasers/selectors.ts @@ -0,0 +1,18 @@ +import { CommonState } from '~/store/commonStore' +import { PurchaseableContentType } from '~/store/purchase-content' + +import { ID } from '../../../models/Identifiers' +import { UserListStoreState } from '../types' + +export const getId = (state: CommonState): ID | null => + state.ui.userList.purchasers.purchasersPage.id +export const getUserList = (state: CommonState): UserListStoreState => + state.ui.userList.purchasers.userList +export const getUserIds = (state: CommonState): ID[] => + state.ui.userList.purchasers.userList.userIds +export const getContentId = (state: CommonState): ID | undefined => + state.ui.userList.purchasers.purchasersPage.contentId +export const getContentType = ( + state: CommonState +): PurchaseableContentType | undefined => + state.ui.userList.purchasers.purchasersPage.contentType diff --git a/packages/common/src/store/user-list/purchasers/types.ts b/packages/common/src/store/user-list/purchasers/types.ts new file mode 100644 index 00000000000..d8853ce91dc --- /dev/null +++ b/packages/common/src/store/user-list/purchasers/types.ts @@ -0,0 +1,17 @@ +import { PurchaseableContentType } from '~/store/purchase-content' +import { UserListStoreState } from '~/store/user-list/types' + +import { ID } from '../../../models' + +export type PurchasersOwnState = { + id: ID | null + contentType?: PurchaseableContentType + contentId?: ID +} + +export type PurchasersPageState = { + purchasersPage: PurchasersOwnState + userList: UserListStoreState +} + +export const PURCHASERS_USER_LIST_TAG = 'PURCHASERS' diff --git a/packages/common/src/store/user-list/remixers/actions.ts b/packages/common/src/store/user-list/remixers/actions.ts new file mode 100644 index 00000000000..b454e0abd62 --- /dev/null +++ b/packages/common/src/store/user-list/remixers/actions.ts @@ -0,0 +1,22 @@ +import { createCustomAction } from 'typesafe-actions' + +import { ID } from '../../../models' + +export const SET_REMIXERS = 'REMIXERS_USER_PAGE/SET_REMIXERS' +export const GET_REMIXERS_ERROR = 'REMIXERS_USER_PAGE/GET_REMIXERS_ERROR' + +export const setRemixers = createCustomAction( + SET_REMIXERS, + (id: ID, trackId?: ID) => ({ + id, + trackId + }) +) +export const getRemixersError = createCustomAction( + GET_REMIXERS_ERROR, + (id: ID, error: string, trackId?: ID) => ({ + id, + trackId, + error + }) +) diff --git a/packages/common/src/store/user-list/remixers/reducers.ts b/packages/common/src/store/user-list/remixers/reducers.ts new file mode 100644 index 00000000000..366307d1d90 --- /dev/null +++ b/packages/common/src/store/user-list/remixers/reducers.ts @@ -0,0 +1,37 @@ +import { combineReducers } from 'redux' +import { createReducer, ActionType } from 'typesafe-actions' + +import { UserListReducerFactory } from '../reducer' + +import * as actions from './actions' +import { RemixersOwnState, REMIXERS_USER_LIST_TAG } from './types' + +type RemixersActions = ActionType + +const userListReducer = UserListReducerFactory.createReducer({ + tag: REMIXERS_USER_LIST_TAG, + pageSize: 15 +}) + +const initialState = { + id: null, + trackId: undefined +} + +const remixersPageReducer = createReducer( + initialState, + { + [actions.SET_REMIXERS](state, action) { + return { + ...state, + id: action.id, + trackId: action.trackId + } + } + } +) + +export default combineReducers({ + remixersPage: remixersPageReducer, + userList: userListReducer +}) diff --git a/packages/common/src/store/user-list/remixers/selectors.ts b/packages/common/src/store/user-list/remixers/selectors.ts new file mode 100644 index 00000000000..b768a9e9025 --- /dev/null +++ b/packages/common/src/store/user-list/remixers/selectors.ts @@ -0,0 +1,13 @@ +import { CommonState } from '~/store/commonStore' + +import { ID } from '../../../models/Identifiers' +import { UserListStoreState } from '../types' + +export const getId = (state: CommonState): ID | null => + state.ui.userList.remixers.remixersPage.id +export const getUserList = (state: CommonState): UserListStoreState => + state.ui.userList.remixers.userList +export const getUserIds = (state: CommonState): ID[] => + state.ui.userList.remixers.userList.userIds +export const getTrackId = (state: CommonState): ID | undefined => + state.ui.userList.remixers.remixersPage.trackId diff --git a/packages/common/src/store/user-list/remixers/types.ts b/packages/common/src/store/user-list/remixers/types.ts new file mode 100644 index 00000000000..9de68c42718 --- /dev/null +++ b/packages/common/src/store/user-list/remixers/types.ts @@ -0,0 +1,15 @@ +import { UserListStoreState } from '~/store/user-list/types' + +import { ID } from '../../../models' + +export type RemixersOwnState = { + id: ID | null + trackId?: ID +} + +export type RemixersPageState = { + remixersPage: RemixersOwnState + userList: UserListStoreState +} + +export const REMIXERS_USER_LIST_TAG = 'REMIXERS' diff --git a/packages/web/src/common/store/user-list/purchasers/errorSagas.ts b/packages/web/src/common/store/user-list/purchasers/errorSagas.ts new file mode 100644 index 00000000000..d54fb9260df --- /dev/null +++ b/packages/web/src/common/store/user-list/purchasers/errorSagas.ts @@ -0,0 +1,25 @@ +import { purchasersUserListActions } from '@audius/common/store' +import { put, takeEvery } from 'redux-saga/effects' + +import * as errorActions from 'store/errors/actions' +const { GET_PURCHASERS_ERROR, getPurchasersError } = purchasersUserListActions + +type HandlePurchasersError = ReturnType + +export function* handlePurchasersError(action: HandlePurchasersError) { + yield put( + errorActions.handleError({ + message: action.type, + shouldRedirect: true, + shouldReport: true, + additionalInfo: { + errorMessage: action.error, + id: action.id + } + }) + ) +} + +export function* watchPurchasersError() { + yield takeEvery([GET_PURCHASERS_ERROR], handlePurchasersError) +} diff --git a/packages/web/src/common/store/user-list/purchasers/sagas.ts b/packages/web/src/common/store/user-list/purchasers/sagas.ts new file mode 100644 index 00000000000..cc4267800a9 --- /dev/null +++ b/packages/web/src/common/store/user-list/purchasers/sagas.ts @@ -0,0 +1,97 @@ +import { + userMetadataFromSDK, + transformAndCleanList +} from '@audius/common/adapters' +import { ID, OptionalId } from '@audius/common/models' +import { + getContentId, + getContentType +} from '@audius/common/src/store/user-list/purchasers/selectors' +import { + accountSelectors, + UserListSagaFactory, + purchasersUserListActions, + purchasersUserListSelectors, + PURCHASERS_USER_LIST_TAG, + getSDK, + PurchaseableContentType +} from '@audius/common/store' +import { call, put, select } from 'typed-redux-saga' + +import { watchPurchasersError } from './errorSagas' + +export const MAX_PURCHASERS = 50 + +const { getPurchasersError } = purchasersUserListActions +const { getId, getUserList } = purchasersUserListSelectors + +type FetchPurchasersArgs = { + id: ID + contentId?: ID + contentType?: PurchaseableContentType + currentPage: number + pageSize: number +} + +function* fetchPurchasers({ + id, + contentId, + contentType, + currentPage, + pageSize +}: FetchPurchasersArgs) { + const offset = currentPage * pageSize + const sdk = yield* getSDK() + const currentUserId = yield* select(accountSelectors.getUserId) + + const { data } = yield* call([sdk.full.users, sdk.full.users.getPurchasers], { + id: id.toString(), + limit: MAX_PURCHASERS, + offset, + userId: OptionalId.parse(currentUserId), + contentType, + contentId: contentId?.toString() + }) + + const users = transformAndCleanList(data, userMetadataFromSDK) + + const userIds = users.map((user) => user.user_id) + return { + userIds, + hasMore: false + } +} + +function* errorDispatcher(error: Error) { + const id = yield* select(getId) + if (id) { + yield* put(getPurchasersError(id, error.message)) + } +} + +function* getPurchasers(currentPage: number, pageSize: number) { + const id = yield* select(getId) + if (!id) return { userIds: [], hasMore: false } + + const contentType = yield* select(getContentType) + const contentId = yield* select(getContentId) + + return yield* call(fetchPurchasers, { + id, + contentId, + contentType, + currentPage, + pageSize + }) +} + +const userListSagas = UserListSagaFactory.createSagas({ + tag: PURCHASERS_USER_LIST_TAG, + fetchUsers: getPurchasers, + stateSelector: getUserList, + errorDispatcher +}) + +export default function sagas() { + return [userListSagas, watchPurchasersError] +} diff --git a/packages/web/src/common/store/user-list/remixers/errorSagas.ts b/packages/web/src/common/store/user-list/remixers/errorSagas.ts new file mode 100644 index 00000000000..9e306681858 --- /dev/null +++ b/packages/web/src/common/store/user-list/remixers/errorSagas.ts @@ -0,0 +1,25 @@ +import { remixersUserListActions } from '@audius/common/store' +import { put, takeEvery } from 'redux-saga/effects' + +import * as errorActions from 'store/errors/actions' +const { GET_REMIXERS_ERROR, getRemixersError } = remixersUserListActions + +type HandleRemixersError = ReturnType + +export function* handleRemixersError(action: HandleRemixersError) { + yield put( + errorActions.handleError({ + message: action.type, + shouldRedirect: true, + shouldReport: true, + additionalInfo: { + errorMessage: action.error, + id: action.id + } + }) + ) +} + +export function* watchRemixersError() { + yield takeEvery([GET_REMIXERS_ERROR], handleRemixersError) +} diff --git a/packages/web/src/common/store/user-list/remixers/sagas.ts b/packages/web/src/common/store/user-list/remixers/sagas.ts new file mode 100644 index 00000000000..eff603cc1e4 --- /dev/null +++ b/packages/web/src/common/store/user-list/remixers/sagas.ts @@ -0,0 +1,88 @@ +import { + userMetadataFromSDK, + transformAndCleanList +} from '@audius/common/adapters' +import { ID, OptionalId } from '@audius/common/models' +import { getTrackId } from '@audius/common/src/store/user-list/remixers/selectors' +import { + accountSelectors, + UserListSagaFactory, + remixersUserListActions, + remixersUserListSelectors, + REMIXERS_USER_LIST_TAG, + getSDK +} from '@audius/common/store' +import { call, put, select } from 'typed-redux-saga' + +import { watchRemixersError } from './errorSagas' + +export const MAX_REMIXERS = 50 + +const { getRemixersError } = remixersUserListActions +const { getId, getUserList } = remixersUserListSelectors + +type FetchRemixersArgs = { + id: ID + trackId?: ID + currentPage: number + pageSize: number +} + +function* fetchRemixers({ + id, + trackId, + currentPage, + pageSize +}: FetchRemixersArgs) { + const offset = currentPage * pageSize + const sdk = yield* getSDK() + const currentUserId = yield* select(accountSelectors.getUserId) + + const { data } = yield* call([sdk.full.users, sdk.full.users.getRemixers], { + id: id.toString(), + limit: MAX_REMIXERS, + offset, + userId: OptionalId.parse(currentUserId), + trackId: trackId?.toString() + }) + + const users = transformAndCleanList(data, userMetadataFromSDK) + + const userIds = users.map((user) => user.user_id) + return { + userIds, + hasMore: false + } +} + +function* errorDispatcher(error: Error) { + const id = yield* select(getId) + if (id) { + yield* put(getRemixersError(id, error.message)) + } +} + +function* getRemixers(currentPage: number, pageSize: number) { + const id = yield* select(getId) + if (!id) return { userIds: [], hasMore: false } + + const trackId = yield* select(getTrackId) + + return yield* call(fetchRemixers, { + id, + trackId, + currentPage, + pageSize + }) +} + +const userListSagas = UserListSagaFactory.createSagas({ + tag: REMIXERS_USER_LIST_TAG, + fetchUsers: getRemixers, + stateSelector: getUserList, + errorDispatcher +}) + +export default function sagas() { + return [userListSagas, watchRemixersError] +} diff --git a/packages/web/src/components/user-list-modal/components/UserListModal.tsx b/packages/web/src/components/user-list-modal/components/UserListModal.tsx index 674e820c1da..0cb879e70fa 100644 --- a/packages/web/src/components/user-list-modal/components/UserListModal.tsx +++ b/packages/web/src/components/user-list-modal/components/UserListModal.tsx @@ -24,8 +24,12 @@ import { FOLLOWERS_USER_LIST_TAG as FOLLOWER_TAG, FAVORITES_USER_LIST_TAG as FAVORITES_TAG, RELATED_ARTISTS_USER_LIST_TAG as RELATED_ARTISTS_TAG, + PURCHASERS_USER_LIST_TAG as PURCHASERS_TAG, + REMIXERS_USER_LIST_TAG as REMIXERS_TAG, UserListStoreState, - CommonState + CommonState, + purchasersUserListSelectors, + remixersUserListSelectors } from '@audius/common/store' import { Modal, @@ -34,7 +38,9 @@ import { IconUser, IconUserGroup, IconTrophy, - IconUserFollowing as IconFollowing + IconUserFollowing as IconFollowing, + IconCart, + IconRemix } from '@audius/harmony' import { ChatBlastAudience } from '@audius/sdk' @@ -60,6 +66,10 @@ const { getUserList: supportingSelector, getId: getSupportingId } = supportingUserListSelectors const { getUserList: topSupportersSelector, getId: getSupportersId } = topSupportersUserListSelectors +const { getUserList: purchasersSelector, getId: getPurchasersId } = + purchasersUserListSelectors +const { getUserList: remixersSelector, getId: getRemixersId } = + remixersUserListSelectors const { getUser } = cacheUsersSelectors const { getProfileUser } = profilePageSelectors @@ -77,7 +87,9 @@ const messages = { topSupporters: 'Top Supporters', supporting: 'Supporting', relatedArtists: 'Related Artists', - mutuals: 'Mutuals' + mutuals: 'Mutuals', + purchasers: 'Purchasers', + remixers: 'Remixers' } const UserListModal = ({ @@ -193,6 +205,28 @@ const UserListModal = ({ ) break + case UserListType.PURCHASER: + tag = PURCHASERS_TAG + selector = purchasersSelector + userIdSelector = getPurchasersId + title = ( +
+ + {messages.purchasers} +
+ ) + break + case UserListType.REMIXER: + tag = REMIXERS_TAG + selector = remixersSelector + userIdSelector = getRemixersId + title = ( +
+ + {messages.remixers} +
+ ) + break // Should not happen but typescript doesn't seem to be // smart enough to pass props to components below default: diff --git a/packages/web/src/store/application/ui/userListModal/sagas.ts b/packages/web/src/store/application/ui/userListModal/sagas.ts index e4e62c5026e..45913bfb03f 100644 --- a/packages/web/src/store/application/ui/userListModal/sagas.ts +++ b/packages/web/src/store/application/ui/userListModal/sagas.ts @@ -9,7 +9,9 @@ import { followersUserListActions as followerActions, favoritesUserListActions as favoritesActions, relatedArtistsUserListActions, - RepostType + RepostType, + remixersUserListActions, + purchasersUserListActions } from '@audius/common/store' import { takeEvery, put } from 'redux-saga/effects' @@ -67,6 +69,12 @@ function* watchSetUsers() { case UserListType.SUPPORTING: yield put(supportingActions.setSupporting(id)) break + case UserListType.REMIXER: + yield put(remixersUserListActions.setRemixers(id)) + break + case UserListType.PURCHASER: + yield put(purchasersUserListActions.setPurchasers(id)) + break default: break } diff --git a/packages/web/src/store/application/ui/userListModal/types.ts b/packages/web/src/store/application/ui/userListModal/types.ts index 8c70eb84a5e..6765c4de97e 100644 --- a/packages/web/src/store/application/ui/userListModal/types.ts +++ b/packages/web/src/store/application/ui/userListModal/types.ts @@ -7,7 +7,9 @@ export enum UserListType { NOTIFICATION = 'NOTIFICATION', SUPPORTER = 'SUPPORTER', SUPPORTING = 'SUPPORTING', - RELATED_ARTISTS = 'RELATED_ARTISTS' + RELATED_ARTISTS = 'RELATED_ARTISTS', + PURCHASER = 'PURCHASER', + REMIXER = 'REMIXER' } export enum UserListEntityType { From 08dbb71f7c1b3e8cdd5206eff7b67cf889578069 Mon Sep 17 00:00:00 2001 From: amendelsohn Date: Thu, 3 Oct 2024 18:16:04 -0700 Subject: [PATCH 2/4] working for followers but not purchase/remixers --- packages/common/src/store/reducers.ts | 4 +- packages/mobile/src/store/sagas.ts | 4 ++ .../components/UserProfilePictureList.tsx | 52 +++++++++++++++++++ .../components/ChatBlastAudienceDisplay.tsx | 30 ++++++++++- packages/web/src/store/sagas.ts | 4 ++ 5 files changed, 91 insertions(+), 3 deletions(-) diff --git a/packages/common/src/store/reducers.ts b/packages/common/src/store/reducers.ts index c7129273748..e01b6d47e37 100644 --- a/packages/common/src/store/reducers.ts +++ b/packages/common/src/store/reducers.ts @@ -212,7 +212,9 @@ export const reducers = (storage: Storage, history?: History) => ({ supporting: supportingUserListReducer, mutuals: mutualsUserListReducer, notifications: notificationsUserListReducer, - relatedArtists: relatedArtistsListReducer + relatedArtists: relatedArtistsListReducer, + purchasers: purchasersUserListReducer, + remixers: remixersUserListReducer }), theme, vipDiscordModal: vipDiscordModalReducer, diff --git a/packages/mobile/src/store/sagas.ts b/packages/mobile/src/store/sagas.ts index 2a940f3655f..a584a1837c3 100644 --- a/packages/mobile/src/store/sagas.ts +++ b/packages/mobile/src/store/sagas.ts @@ -66,7 +66,9 @@ import followersPageSagas from 'common/store/user-list/followers/sagas' import followingPageSagas from 'common/store/user-list/following/sagas' import mutualsPageSagas from 'common/store/user-list/mutuals/sagas' import notificationUsersPageSagas from 'common/store/user-list/notifications/sagas' +import purchasersPageSagas from 'common/store/user-list/purchasers/sagas' import relatedArtistsPageSagas from 'common/store/user-list/related-artists/sagas' +import remixersPageSagas from 'common/store/user-list/remixers/sagas' import repostPageSagas from 'common/store/user-list/reposts/sagas' import supportingPageSagas from 'common/store/user-list/supporting/sagas' import topSupportersPageSagas from 'common/store/user-list/top-supporters/sagas' @@ -170,6 +172,8 @@ export default function* rootSaga() { ...settingsSagas(), ...aiSagas(), ...premiumTracksSagas(), + ...purchasersPageSagas(), + ...remixersPageSagas(), // Cast ...castSagas(), diff --git a/packages/web/src/components/notification/Notification/components/UserProfilePictureList.tsx b/packages/web/src/components/notification/Notification/components/UserProfilePictureList.tsx index 9aeca0b6619..aa868567438 100644 --- a/packages/web/src/components/notification/Notification/components/UserProfilePictureList.tsx +++ b/packages/web/src/components/notification/Notification/components/UserProfilePictureList.tsx @@ -1,8 +1,21 @@ +import { useEffect } from 'react' + +import { useGetCurrentUserId } from '@audius/common/api' import { User } from '@audius/common/models' import { formatCount } from '@audius/common/utils' +import { Button } from 'antd' import cn from 'classnames' +import { useDispatch } from 'react-redux' import Tooltip from 'components/tooltip/Tooltip' +import { + setUsers as setUserListUsers, + setVisibility as openUserListModal +} from 'store/application/ui/userListModal/slice' +import { + UserListEntityType, + UserListType +} from 'store/application/ui/userListModal/types' import { USER_LENGTH_LIMIT } from '../utils' @@ -30,6 +43,8 @@ export type UserProfileListProps = { disableProfileClick?: boolean disablePopover?: boolean stopPropagation?: boolean + userListType?: UserListType + userListEntityType?: UserListEntityType profilePictureClassname?: string } @@ -40,9 +55,13 @@ export const UserProfilePictureList = ({ disableProfileClick = false, disablePopover = false, stopPropagation = false, + userListType, + userListEntityType, profilePictureClassname }: UserProfileListProps) => { + const dispatch = useDispatch() const showUserListModal = totalUserCount > limit + const { data: currentUserId } = useGetCurrentUserId({}) /** * We add a +1 because the remaining users count includes * the tile that has the +N itself. @@ -58,6 +77,36 @@ export const UserProfilePictureList = ({ const sliceLimit = showUserListModal ? limit - 1 : limit const lastUser = users[limit - 1] + useEffect(() => { + if ( + userListType && + currentUserId && + userListEntityType && + users.length > 0 + ) { + dispatch( + setUserListUsers({ + userListType, + id: currentUserId, + entityType: userListEntityType + }) + ) + } + }, [ + userListType, + disableProfileClick, + dispatch, + currentUserId, + userListEntityType, + users.length + ]) + + const handleClick = () => { + if (userListType && !disableProfileClick) { + dispatch(openUserListModal(true)) + } + } + return (
{users @@ -81,6 +130,7 @@ export const UserProfilePictureList = ({ className={cn(styles.profilePictureExtraRoot, { [styles.disabled]: disableProfileClick })} + onClick={handleClick} > ) : null} + {/* debug */} +
) } diff --git a/packages/web/src/pages/chat-page/components/ChatBlastAudienceDisplay.tsx b/packages/web/src/pages/chat-page/components/ChatBlastAudienceDisplay.tsx index a17bc2e6530..a85013b907c 100644 --- a/packages/web/src/pages/chat-page/components/ChatBlastAudienceDisplay.tsx +++ b/packages/web/src/pages/chat-page/components/ChatBlastAudienceDisplay.tsx @@ -10,10 +10,14 @@ import { Flex, IconInfo } from '@audius/harmony' -import { ChatBlast } from '@audius/sdk' +import { ChatBlast, ChatBlastAudience } from '@audius/sdk' import { UserProfilePictureList } from 'components/notification/Notification/components/UserProfilePictureList' import { Tooltip } from 'components/tooltip' +import { + UserListEntityType, + UserListType +} from 'store/application/ui/userListModal/types' const USER_LIST_LIMIT = 10 @@ -37,6 +41,25 @@ export const ChatBlastAudienceDisplay = ( // Add 1 to the limit to ensure we have a bg photo for the overflow count const users = useAudienceUsers(chat, USER_LIST_LIMIT + 1) + const audienceType = chat.audience + let userListType + switch (audienceType) { + case ChatBlastAudience.FOLLOWERS: + userListType = UserListType.FOLLOWER + break + case ChatBlastAudience.TIPPERS: + userListType = UserListType.SUPPORTER + break + case ChatBlastAudience.CUSTOMERS: + userListType = UserListType.PURCHASER + break + case ChatBlastAudience.REMIXERS: + userListType = UserListType.REMIXER + break + default: + userListType = UserListType.FOLLOWER + } + return ( ) : null} diff --git a/packages/web/src/store/sagas.ts b/packages/web/src/store/sagas.ts index 17da6fb2909..d6f4ebaa38c 100644 --- a/packages/web/src/store/sagas.ts +++ b/packages/web/src/store/sagas.ts @@ -72,7 +72,9 @@ import followersPageSagas from 'common/store/user-list/followers/sagas' import followingPageSagas from 'common/store/user-list/following/sagas' import mutualsPageSagas from 'common/store/user-list/mutuals/sagas' import notificationUsersPageSagas from 'common/store/user-list/notifications/sagas' +import purchasersPageSagas from 'common/store/user-list/purchasers/sagas' import relatedArtistsPageSagas from 'common/store/user-list/related-artists/sagas' +import remixersPageSagas from 'common/store/user-list/remixers/sagas' import repostPageSagas from 'common/store/user-list/reposts/sagas' import supportingPageSagas from 'common/store/user-list/supporting/sagas' import topSupportersPageSagas from 'common/store/user-list/top-supporters/sagas' @@ -204,6 +206,8 @@ export default function* rootSaga() { userListModalSagas(), vipDiscordModalSagas(), commonReachabilitySagas(), + purchasersPageSagas(), + remixersPageSagas(), // Remote config remoteConfigSagas(), From 945c633e3e21320d8d1a93e1c6f6f82fb216e3a2 Mon Sep 17 00:00:00 2001 From: amendelsohn Date: Mon, 7 Oct 2024 15:42:17 -0700 Subject: [PATCH 3/4] fix sdk call; don't show list on chats pages --- .../src/common/store/user-list/purchasers/sagas.ts | 6 +++--- .../web/src/common/store/user-list/remixers/sagas.ts | 6 +++--- .../components/UserProfilePictureList.tsx | 3 --- .../user-list-modal/components/UserListModal.tsx | 11 +++++++++++ .../chat-page/components/ChatBlastAudienceDisplay.tsx | 3 +-- 5 files changed, 18 insertions(+), 11 deletions(-) diff --git a/packages/web/src/common/store/user-list/purchasers/sagas.ts b/packages/web/src/common/store/user-list/purchasers/sagas.ts index cc4267800a9..632504b5018 100644 --- a/packages/web/src/common/store/user-list/purchasers/sagas.ts +++ b/packages/web/src/common/store/user-list/purchasers/sagas.ts @@ -2,7 +2,7 @@ import { userMetadataFromSDK, transformAndCleanList } from '@audius/common/adapters' -import { ID, OptionalId } from '@audius/common/models' +import { Id, ID, OptionalId } from '@audius/common/models' import { getContentId, getContentType @@ -45,12 +45,12 @@ function* fetchPurchasers({ const currentUserId = yield* select(accountSelectors.getUserId) const { data } = yield* call([sdk.full.users, sdk.full.users.getPurchasers], { - id: id.toString(), + id: Id.parse(id), limit: MAX_PURCHASERS, offset, userId: OptionalId.parse(currentUserId), contentType, - contentId: contentId?.toString() + contentId: OptionalId.parse(contentId?.toString()) }) const users = transformAndCleanList(data, userMetadataFromSDK) diff --git a/packages/web/src/common/store/user-list/remixers/sagas.ts b/packages/web/src/common/store/user-list/remixers/sagas.ts index eff603cc1e4..da3b056f2c9 100644 --- a/packages/web/src/common/store/user-list/remixers/sagas.ts +++ b/packages/web/src/common/store/user-list/remixers/sagas.ts @@ -2,7 +2,7 @@ import { userMetadataFromSDK, transformAndCleanList } from '@audius/common/adapters' -import { ID, OptionalId } from '@audius/common/models' +import { Id, ID, OptionalId } from '@audius/common/models' import { getTrackId } from '@audius/common/src/store/user-list/remixers/selectors' import { accountSelectors, @@ -39,11 +39,11 @@ function* fetchRemixers({ const currentUserId = yield* select(accountSelectors.getUserId) const { data } = yield* call([sdk.full.users, sdk.full.users.getRemixers], { - id: id.toString(), + id: Id.parse(id), limit: MAX_REMIXERS, offset, userId: OptionalId.parse(currentUserId), - trackId: trackId?.toString() + trackId: OptionalId.parse(trackId?.toString()) }) const users = transformAndCleanList(data, userMetadataFromSDK) diff --git a/packages/web/src/components/notification/Notification/components/UserProfilePictureList.tsx b/packages/web/src/components/notification/Notification/components/UserProfilePictureList.tsx index aa868567438..22d40fd3162 100644 --- a/packages/web/src/components/notification/Notification/components/UserProfilePictureList.tsx +++ b/packages/web/src/components/notification/Notification/components/UserProfilePictureList.tsx @@ -3,7 +3,6 @@ import { useEffect } from 'react' import { useGetCurrentUserId } from '@audius/common/api' import { User } from '@audius/common/models' import { formatCount } from '@audius/common/utils' -import { Button } from 'antd' import cn from 'classnames' import { useDispatch } from 'react-redux' @@ -146,8 +145,6 @@ export const UserProfilePictureList = ({ ) : null} - {/* debug */} - ) } diff --git a/packages/web/src/components/user-list-modal/components/UserListModal.tsx b/packages/web/src/components/user-list-modal/components/UserListModal.tsx index 0cb879e70fa..e4007630ab4 100644 --- a/packages/web/src/components/user-list-modal/components/UserListModal.tsx +++ b/packages/web/src/components/user-list-modal/components/UserListModal.tsx @@ -43,6 +43,7 @@ import { IconRemix } from '@audius/harmony' import { ChatBlastAudience } from '@audius/sdk' +import { useRouteMatch } from 'react-router-dom' import { useSelector } from 'common/hooks/useSelector' import UserList from 'components/user-list/UserList' @@ -114,6 +115,15 @@ const UserListModal = ({ FeatureFlags.ONE_TO_MANY_DMS ) + const match = useRouteMatch<{ audience_type: string }>( + '/messages/:audience_type' + ) + const isChatBlastPath = + match?.params.audience_type && + Object.values(ChatBlastAudience).includes( + match.params.audience_type as ChatBlastAudience + ) + switch (userListType) { case UserListType.FAVORITE: tag = FAVORITES_TAG @@ -267,6 +277,7 @@ const UserListModal = ({ /> {isOneToManyDmsEnabled && + !isChatBlastPath && (userListType === UserListType.FOLLOWER || userListType === UserListType.SUPPORTER) && userId === currentUserId ? ( diff --git a/packages/web/src/pages/chat-page/components/ChatBlastAudienceDisplay.tsx b/packages/web/src/pages/chat-page/components/ChatBlastAudienceDisplay.tsx index a85013b907c..dc24ca5c2c1 100644 --- a/packages/web/src/pages/chat-page/components/ChatBlastAudienceDisplay.tsx +++ b/packages/web/src/pages/chat-page/components/ChatBlastAudienceDisplay.tsx @@ -86,8 +86,7 @@ export const ChatBlastAudienceDisplay = ( From 598135b0d92a8437a3b19120bec863c048cb93c7 Mon Sep 17 00:00:00 2001 From: amendelsohn Date: Tue, 8 Oct 2024 09:37:49 -0700 Subject: [PATCH 4/4] import fixes --- .../web/src/common/store/user-list/purchasers/sagas.ts | 9 +++------ .../web/src/common/store/user-list/remixers/sagas.ts | 3 +-- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/packages/web/src/common/store/user-list/purchasers/sagas.ts b/packages/web/src/common/store/user-list/purchasers/sagas.ts index 632504b5018..ec21d2014be 100644 --- a/packages/web/src/common/store/user-list/purchasers/sagas.ts +++ b/packages/web/src/common/store/user-list/purchasers/sagas.ts @@ -3,10 +3,6 @@ import { transformAndCleanList } from '@audius/common/adapters' import { Id, ID, OptionalId } from '@audius/common/models' -import { - getContentId, - getContentType -} from '@audius/common/src/store/user-list/purchasers/selectors' import { accountSelectors, UserListSagaFactory, @@ -23,7 +19,8 @@ import { watchPurchasersError } from './errorSagas' export const MAX_PURCHASERS = 50 const { getPurchasersError } = purchasersUserListActions -const { getId, getUserList } = purchasersUserListSelectors +const { getId, getUserList, getContentId, getContentType } = + purchasersUserListSelectors type FetchPurchasersArgs = { id: ID @@ -50,7 +47,7 @@ function* fetchPurchasers({ offset, userId: OptionalId.parse(currentUserId), contentType, - contentId: OptionalId.parse(contentId?.toString()) + contentId: contentId?.toString() }) const users = transformAndCleanList(data, userMetadataFromSDK) diff --git a/packages/web/src/common/store/user-list/remixers/sagas.ts b/packages/web/src/common/store/user-list/remixers/sagas.ts index da3b056f2c9..73fe90d0a69 100644 --- a/packages/web/src/common/store/user-list/remixers/sagas.ts +++ b/packages/web/src/common/store/user-list/remixers/sagas.ts @@ -3,7 +3,6 @@ import { transformAndCleanList } from '@audius/common/adapters' import { Id, ID, OptionalId } from '@audius/common/models' -import { getTrackId } from '@audius/common/src/store/user-list/remixers/selectors' import { accountSelectors, UserListSagaFactory, @@ -19,7 +18,7 @@ import { watchRemixersError } from './errorSagas' export const MAX_REMIXERS = 50 const { getRemixersError } = remixersUserListActions -const { getId, getUserList } = remixersUserListSelectors +const { getId, getUserList, getTrackId } = remixersUserListSelectors type FetchRemixersArgs = { id: ID