diff --git a/src/app/Scenes/MyCollection/Components/MyCollectionBottomSheetModals/MyCollectionBottomSheetModalArtistPreview.tsx b/src/app/Scenes/MyCollection/Components/MyCollectionBottomSheetModals/MyCollectionBottomSheetModalArtistPreview.tsx index 8ee00fa749b..5cc71b57360 100644 --- a/src/app/Scenes/MyCollection/Components/MyCollectionBottomSheetModals/MyCollectionBottomSheetModalArtistPreview.tsx +++ b/src/app/Scenes/MyCollection/Components/MyCollectionBottomSheetModals/MyCollectionBottomSheetModalArtistPreview.tsx @@ -1,88 +1,140 @@ -import { - Checkbox, - Flex, - InfoCircleIcon, - Join, - Message, - Spacer, - Text, - Touchable, -} from "@artsy/palette-mobile" +import { Checkbox, Flex, InfoCircleIcon, Join, Message, Spacer, Text } from "@artsy/palette-mobile" +import { useActionSheet } from "@expo/react-native-action-sheet" import { BottomSheetView } from "@gorhom/bottom-sheet" import { MyCollectionBottomSheetModalArtistPreviewQuery } from "__generated__/MyCollectionBottomSheetModalArtistPreviewQuery.graphql" import { MyCollectionBottomSheetModalArtistPreview_artist$data } from "__generated__/MyCollectionBottomSheetModalArtistPreview_artist.graphql" import { MyCollectionBottomSheetModalArtistPreview_me$data } from "__generated__/MyCollectionBottomSheetModalArtistPreview_me.graphql" import { ArtistListItemContainer, ArtistListItemPlaceholder } from "app/Components/ArtistListItem" +import { useToast } from "app/Components/Toast/toastHook" import { ArtistKindPills } from "app/Scenes/MyCollection/Components/MyCollectionBottomSheetModals/MyCollectionBottomSheetModalArtistPreview/ArtistKindPills" +import { MyCollectionTabsStore } from "app/Scenes/MyCollection/State/MyCollectionTabsStore" +import { deleteUserInterest } from "app/Scenes/MyCollection/mutations/deleteUserInterest" +import { updateUserInterest } from "app/Scenes/MyCollection/mutations/updateUserInterest" import { getRelayEnvironment } from "app/system/relay/defaultEnvironment" import { renderWithPlaceholder } from "app/utils/renderWithPlaceholder" +import { useState } from "react" import { QueryRenderer, createFragmentContainer } from "react-relay" +import useDebounce from "react-use/lib/useDebounce" import { graphql } from "relay-runtime" interface MyCollectionBottomSheetModalArtistPreviewProps { artist: MyCollectionBottomSheetModalArtistPreview_artist$data me: MyCollectionBottomSheetModalArtistPreview_me$data + interestId: string } export const MyCollectionBottomSheetModalArtistPreview: React.FC< MyCollectionBottomSheetModalArtistPreviewProps -> = ({ artist, me }) => { - const artworksCountWithinMyCollection = me?.myCollectionConnection?.totalCount ?? 0 - const canBeRemoved = artworksCountWithinMyCollection === 0 +> = ({ artist, me, interestId }) => { + const artworksCountWithMyCollection = me?.myCollectionConnection?.totalCount ?? 0 + const canBeRemoved = artworksCountWithMyCollection === 0 + const { showActionSheetWithOptions } = useActionSheet() + + const [isPrivate, setIsPrivate] = useState(me.userInterest?.private ?? false) + + const setViewKind = MyCollectionTabsStore.useStoreActions((actions) => actions.setViewKind) + + const toast = useToast() + + useDebounce( + () => { + if (me.userInterest?.private === isPrivate) { + return + } + updateUserInterest({ + id: interestId, + private: isPrivate, + }) + }, + 300, + [isPrivate] + ) + + const deleteArtist = () => { + deleteUserInterest({ + id: interestId, + }) + .then(() => { + toast.show("Artist removed from My Collection", "bottom", { + backgroundColor: "green100", + }) + // Hide modal after removing artist + setViewKind({ viewKind: null }) + }) + .catch((error) => { + console.error(error) + toast.show("Something went wrong", "bottom", { + backgroundColor: "red100", + }) + }) + } return ( }> }> - + - {}} /> + + { + setIsPrivate(!isPrivate) + }} + > + <> + Share this artist with galleries during inquiries. + + Galleries are more likely to respond if they can see the artists you collect. + + + + - + {canBeRemoved ? ( + { + showActionSheetWithOptions( + { + title: "Delete artist?", + options: ["Delete", "Cancel"], + destructiveButtonIndex: 0, + cancelButtonIndex: 1, + useModal: true, + }, + (buttonIndex) => { + if (buttonIndex === 0) { + deleteArtist() + } + } + ) + }} + color="red100" + variant="xs" + underline + > + Remove Artist + + ) : ( + <> + + } + /> + + )} ) } -const RemoveTheArtist: React.FC<{ canBeRemoved: boolean }> = ({ canBeRemoved }) => ( - }> - {canBeRemoved ? ( - {}} color="red100" variant="xs" underline> - Remove Artist - - ) : ( - } - /> - )} - -) - -const ShareArtistCheckbox: React.FC<{ onCheckboxPress: () => void }> = ({ onCheckboxPress }) => { - return ( - - - - <> - Share this artist with galleries during inquiries. - - Galleries are more likely to respond if they can see the artists you collect. - - - - - - ) -} export const MyCollectionBottomSheetModalArtistPreviewFragmentContainer = createFragmentContainer( MyCollectionBottomSheetModalArtistPreview, { @@ -97,6 +149,10 @@ export const MyCollectionBottomSheetModalArtistPreviewFragmentContainer = create myCollectionConnection(artistIDs: [$artistID]) { totalCount } + userInterest(id: $interestId) { + id + private + } } `, } @@ -104,12 +160,16 @@ export const MyCollectionBottomSheetModalArtistPreviewFragmentContainer = create export const MyCollectionBottomSheetModalArtistPreviewQueryRenderer: React.FC<{ artistID: string -}> = ({ artistID }) => { + interestId: string +}> = ({ artistID, interestId }) => { return ( environment={getRelayEnvironment()} query={graphql` - query MyCollectionBottomSheetModalArtistPreviewQuery($artistID: String!) { + query MyCollectionBottomSheetModalArtistPreviewQuery( + $artistID: String! + $interestId: String! + ) { artist(id: $artistID) { ...MyCollectionBottomSheetModalArtistPreview_artist } @@ -121,11 +181,15 @@ export const MyCollectionBottomSheetModalArtistPreviewQueryRenderer: React.FC<{ cacheConfig={{ force: true }} variables={{ artistID, + interestId, }} render={renderWithPlaceholder({ Container: MyCollectionBottomSheetModalArtistPreviewFragmentContainer, renderPlaceholder: LoadingSkeleton, renderFallback: () => null, + initialProps: { + interestId, + }, })} /> ) diff --git a/src/app/Scenes/MyCollection/Components/MyCollectionBottomSheetModals/MyCollectionBottomSheetModals.tsx b/src/app/Scenes/MyCollection/Components/MyCollectionBottomSheetModals/MyCollectionBottomSheetModals.tsx index ce08c903e1d..8f350595313 100644 --- a/src/app/Scenes/MyCollection/Components/MyCollectionBottomSheetModals/MyCollectionBottomSheetModals.tsx +++ b/src/app/Scenes/MyCollection/Components/MyCollectionBottomSheetModals/MyCollectionBottomSheetModals.tsx @@ -12,7 +12,8 @@ export const MyCollectionBottomSheetModals: React.FC<{}> = () => { const setViewKind = MyCollectionTabsStore.useStoreActions((actions) => actions.setViewKind) const view = MyCollectionTabsStore.useStoreState((state) => state.viewKind) - const id = MyCollectionTabsStore.useStoreState((state) => state.id) + const artistId = MyCollectionTabsStore.useStoreState((state) => state.artistId) + const interestId = MyCollectionTabsStore.useStoreState((state) => state.interestId) const snapPoints = useMemo(() => [view === "Artist" ? 410 : 370], []) @@ -45,9 +46,12 @@ export const MyCollectionBottomSheetModals: React.FC<{}> = () => { handleIndicatorStyle={{ backgroundColor: "black", width: 40, height: 4, borderRadius: 2 }} > {view === "Add" && } - {view === "Artist" && !!id && ( - - )} + {view === "Artist" && !!artistId && !!interestId ? ( + + ) : null} ) diff --git a/src/app/Scenes/MyCollection/Components/MyCollectionCollectedArtistGridItem.tsx b/src/app/Scenes/MyCollection/Components/MyCollectionCollectedArtistGridItem.tsx deleted file mode 100644 index 1572eab6d7f..00000000000 --- a/src/app/Scenes/MyCollection/Components/MyCollectionCollectedArtistGridItem.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import { Avatar, Flex, Text, Touchable } from "@artsy/palette-mobile" -import { MyCollectionCollectedArtistGridItem_artist$key } from "__generated__/MyCollectionCollectedArtistGridItem_artist.graphql" -import { formatTombstoneText } from "app/Components/ArtistListItem" -import { MyCollectionTabsStore } from "app/Scenes/MyCollection/State/MyCollectionTabsStore" -import { isPad } from "app/utils/hardware" -import { pluralize } from "app/utils/pluralize" -import { useFragment } from "react-relay" -import { graphql } from "relay-runtime" - -interface MyCollectionCollectedArtistGridItemProps { - artist: MyCollectionCollectedArtistGridItem_artist$key - artworksCount: number | null -} - -export const MyCollectionCollectedArtistGridItem: React.FC< - MyCollectionCollectedArtistGridItemProps -> = ({ artist, artworksCount }) => { - const setViewKind = MyCollectionTabsStore.useStoreActions((state) => state.setViewKind) - - const artistData = useFragment( - artistFragment, - artist - ) - const { nationality, birthday, deathday } = artistData - - const isAPad = isPad() - - const getMeta = () => { - const tombstoneText = formatTombstoneText(nationality, birthday, deathday) - - if (!!tombstoneText || Number.isInteger(artworksCount)) { - return ( - - {!!tombstoneText && ( - - {tombstoneText} - - )} - {Number.isInteger(artworksCount) && ( - - {artworksCount} {pluralize("artwork", artworksCount || 0)} uploaded - - )} - - ) - } - - return undefined - } - const meta = getMeta() - - return ( - - { - setViewKind({ - viewKind: "Artist", - id: artistData.internalID, - }) - }} - > - - - - {artistData.name ?? ""} - - {meta} - - - - ) -} - -const artistFragment = graphql` - fragment MyCollectionCollectedArtistGridItem_artist on Artist { - name - id - internalID - slug - name - initials - href - is_followed: isFollowed - nationality - birthday - deathday - image { - url - } - } -` diff --git a/src/app/Scenes/MyCollection/Components/MyCollectionCollectedArtistItem.tsx b/src/app/Scenes/MyCollection/Components/MyCollectionCollectedArtistItem.tsx index 815af92bc11..c59c7a5a4ae 100644 --- a/src/app/Scenes/MyCollection/Components/MyCollectionCollectedArtistItem.tsx +++ b/src/app/Scenes/MyCollection/Components/MyCollectionCollectedArtistItem.tsx @@ -10,9 +10,14 @@ interface ArtistItem { // TODO: Implement compact version of artists grid compact?: boolean isPrivate?: boolean + interestId: string } -export const MyCollectionCollectedArtistItem: React.FC = ({ artist, isPrivate }) => { +export const MyCollectionCollectedArtistItem: React.FC = ({ + artist, + isPrivate, + interestId, +}) => { const setViewKind = MyCollectionTabsStore.useStoreActions((state) => state.setViewKind) const artistData = useFragment(artistFragment, artist) const space = useSpace() @@ -20,7 +25,8 @@ export const MyCollectionCollectedArtistItem: React.FC = ({ artist, const showArtistPreview = () => { setViewKind({ viewKind: "Artist", - id: artistData.internalID, + artistId: artistData.internalID, + interestId: interestId, }) } diff --git a/src/app/Scenes/MyCollection/Components/MyCollectionCollectedArtistsRail.tsx b/src/app/Scenes/MyCollection/Components/MyCollectionCollectedArtistsRail.tsx index 92c4176a852..c3aa1c98256 100644 --- a/src/app/Scenes/MyCollection/Components/MyCollectionCollectedArtistsRail.tsx +++ b/src/app/Scenes/MyCollection/Components/MyCollectionCollectedArtistsRail.tsx @@ -2,7 +2,6 @@ import { Avatar, Flex, Spacer, Spinner, Text, Touchable, useSpace } from "@artsy import { MyCollectionCollectedArtistsRail_artist$key } from "__generated__/MyCollectionCollectedArtistsRail_artist.graphql" import { MyCollectionCollectedArtistsRail_me$key } from "__generated__/MyCollectionCollectedArtistsRail_me.graphql" import { MyCollectionTabsStore } from "app/Scenes/MyCollection/State/MyCollectionTabsStore" -import { extractNodes } from "app/utils/extractNodes" import { Animated } from "react-native" import { useFragment, usePaginationFragment } from "react-relay" import { graphql } from "relay-runtime" @@ -31,18 +30,33 @@ export const MyCollectionCollectedArtistsRail: React.FC userInterest?.node) if (!collectedArtists) return <> + const filteredUserInterests = userInterests.filter((userInterest) => { + if (userInterest?.internalID && userInterest.node && userInterest.node.internalID) { + return true + } + return + }) + return ( } - keyExtractor={({ internalID }) => internalID!} + data={filteredUserInterests} + renderItem={({ item }) => { + if (item?.node && item.internalID && item.node.internalID) { + return ( + + ) + } + return null + }} + keyExtractor={(item, index) => item?.internalID || index.toString()} onEndReachedThreshold={1} ItemSeparatorComponent={() => } contentContainerStyle={{ @@ -70,9 +84,10 @@ export const MyCollectionCollectedArtistsRail: React.FC = ({ - artist, -}) => { +export const Artist: React.FC<{ + artist: MyCollectionCollectedArtistsRail_artist$key + interestId: string +}> = ({ artist, interestId }) => { const data = useFragment(artistFragment, artist) const setViewKind = MyCollectionTabsStore.useStoreActions((state) => state.setViewKind) @@ -82,7 +97,8 @@ export const Artist: React.FC<{ artist: MyCollectionCollectedArtistsRail_artist$ onPress={() => { setViewKind({ viewKind: "Artist", - id: data.internalID, + artistId: data.internalID, + interestId: interestId, }) }} accessibilityHint={`View more details ${data.name}`} @@ -112,6 +128,7 @@ const collectedArtistsPaginationFragment = graphql` interestType: ARTIST ) @connection(key: "MyCollectionCollectedArtistsRail_userInterestsConnection") { edges { + internalID node { ... on Artist { internalID diff --git a/src/app/Scenes/MyCollection/Components/MyCollectionCollectedArtistsView.tsx b/src/app/Scenes/MyCollection/Components/MyCollectionCollectedArtistsView.tsx index edcf212162a..c160ee1a5bf 100644 --- a/src/app/Scenes/MyCollection/Components/MyCollectionCollectedArtistsView.tsx +++ b/src/app/Scenes/MyCollection/Components/MyCollectionCollectedArtistsView.tsx @@ -53,15 +53,16 @@ export const MyCollectionCollectedArtistsView: React.FC "list" + item?.node?.internalID} + keyExtractor={(item) => "list" + item?.internalID} renderItem={({ item }) => { - if (item && item.node && item.node.internalID && item.private) { + if (item?.node) { return ( ) } @@ -102,7 +103,6 @@ const collectedArtistsPaginationFragment = graphql` internalID name ...MyCollectionCollectedArtistItem_artist - ...MyCollectionCollectedArtistGridItem_artist } } } diff --git a/src/app/Scenes/MyCollection/State/MyCollectionTabsStore.tsx b/src/app/Scenes/MyCollection/State/MyCollectionTabsStore.tsx index 41efab9e46d..e2ed0dafc37 100644 --- a/src/app/Scenes/MyCollection/State/MyCollectionTabsStore.tsx +++ b/src/app/Scenes/MyCollection/State/MyCollectionTabsStore.tsx @@ -9,12 +9,14 @@ type ViewPayload = } | { viewKind: "Artist" - id: string + artistId: string + interestId: string } export interface MyCollectionTabsStoreModel { selectedTab: CollectedTab viewKind: MyCollectionBottomSheetModalKind - id: string | null + artistId: string | null + interestId: string | null setSelectedTab: Action setViewKind: Action } @@ -22,7 +24,8 @@ export interface MyCollectionTabsStoreModel { export const myCollectionTabsStoreModel: MyCollectionTabsStoreModel = { selectedTab: null, viewKind: null, - id: null, + artistId: null, + interestId: null, setSelectedTab: action((state, payload) => { state.selectedTab = payload }), @@ -31,7 +34,8 @@ export const myCollectionTabsStoreModel: MyCollectionTabsStoreModel = { switch (payload.viewKind) { case null: state.viewKind = null - state.id = null + state.interestId = null + state.artistId = null break case "Add": @@ -40,7 +44,8 @@ export const myCollectionTabsStoreModel: MyCollectionTabsStoreModel = { default: state.viewKind = payload.viewKind - state.id = payload.id + state.artistId = payload.artistId + state.interestId = payload.interestId break } }), diff --git a/src/app/Scenes/MyCollection/mutations/updateUserInterest.ts b/src/app/Scenes/MyCollection/mutations/updateUserInterest.ts new file mode 100644 index 00000000000..7b4dbaebf30 --- /dev/null +++ b/src/app/Scenes/MyCollection/mutations/updateUserInterest.ts @@ -0,0 +1,48 @@ +import { UpdateUserInterestMutationInput } from "__generated__/updateUserInterestMutation.graphql" +import { getRelayEnvironment } from "app/system/relay/defaultEnvironment" +import { commitMutation, graphql } from "react-relay" + +export const updateUserInterest = (input: UpdateUserInterestMutationInput) => { + return new Promise((resolve, reject) => { + commitMutation(getRelayEnvironment(), { + mutation: graphql` + mutation updateUserInterestMutation($input: UpdateUserInterestMutationInput!) { + updateUserInterest(input: $input) { + userInterestOrError { + ... on UpdateUserInterestSuccess { + userInterest { + id + private + } + } + } + } + } + `, + variables: { + input, + }, + // @ts-expect-error + optimisticResponse: { + updateUserInterest: { + userInterestOrError: { + __typename: "UpdateUserInterestSuccess", + userInterest: { + id: input.id, + private: input.private, + }, + }, + }, + }, + + onCompleted: (response, errors) => { + if (errors?.length) { + reject(errors) + } else { + resolve(response) + } + }, + onError: reject, + }) + }) +}