diff --git a/data/schema.graphql b/data/schema.graphql
index 447c049a106..0bef2ed3e73 100644
--- a/data/schema.graphql
+++ b/data/schema.graphql
@@ -3051,6 +3051,9 @@ type Artwork implements Node & Searchable & Sellable {
# Whether a user can make an offer on the work through inquiry
isOfferableFromInquiry: Boolean
isOnHold: String
+
+ # Whether a partner can send an offer for this work
+ isPartnerOfferable: Boolean!
isPartnerPromoted: Boolean
# Whether a work is available for pickup
@@ -9947,6 +9950,19 @@ type Conversation implements Node {
states: [CommerceOrderStateEnum!]
): CommerceOrderConnectionWithTotalCount
+ # Partner offers for this conversation's artwork, scoped to the user who initiated the conversation (from_id).
+ partnerOffersConnection(
+ after: String
+ before: String
+ first: Int
+ last: Int
+
+ # Filter by offer type(s). Gravity defaults to bulk offers when omitted.
+ offerType: [PartnerOfferTypeEnum]
+ page: Int
+ size: Int
+ ): PartnerOfferConnection
+
# A connection of orders for artworks in this conversation from the partner's perspective.
partnerOrdersConnection(
after: String
@@ -17895,6 +17911,9 @@ type Me implements Node {
before: String
first: Int
last: Int
+
+ # Filter by offer type(s). Gravity defaults to all a users partner offers when omitted.
+ offerType: [PartnerOfferTypeEnum]
page: Int
size: Int
sort: PartnerOfferToCollectorSorts
diff --git a/src/Apps/Conversations/ConversationsContext.tsx b/src/Apps/Conversations/ConversationsContext.tsx
index 80be6933c12..15bf7c1ab35 100644
--- a/src/Apps/Conversations/ConversationsContext.tsx
+++ b/src/Apps/Conversations/ConversationsContext.tsx
@@ -112,7 +112,7 @@ export const useConversationsContext = () => {
const VIEWER_FRAGMENT = graphql`
fragment ConversationsContext_viewer on Viewer {
me {
- partnerOffersConnection(first: 100) {
+ partnerOffersConnection(first: 100, offerType: [PERSONALIZED]) {
edges {
node {
artworkId
diff --git a/src/Apps/Conversations/components/ConversationCTA/ConversationCTA.tsx b/src/Apps/Conversations/components/ConversationCTA/ConversationCTA.tsx
index 7ce1658654d..b911a7400ce 100644
--- a/src/Apps/Conversations/components/ConversationCTA/ConversationCTA.tsx
+++ b/src/Apps/Conversations/components/ConversationCTA/ConversationCTA.tsx
@@ -1,6 +1,7 @@
import VerifiedIcon from "@artsy/icons/VerifiedIcon"
import { Box, Flex, type FlexProps, Spacer, Text } from "@artsy/palette"
import { themeGet } from "@styled-system/theme-get"
+import { useFlag } from "@unleash/proxy-client-react"
import { useConversationsContext } from "Apps/Conversations/ConversationsContext"
import { ConversationConfirmModal } from "Apps/Conversations/components/ConversationCTA/ConversationConfirmModal"
import { ConversationMakeOfferButton } from "Apps/Conversations/components/ConversationCTA/ConversationMakeOfferButton"
@@ -31,6 +32,8 @@ export const ConversationCTA: React.FC<
partnerOffer?.endAt ?? new Date(0).toISOString(),
)
+ const isPartnerOfferConvoEnabled = useFlag("topaz_partner-offer-convo")
+
const activeOrder = extractNodes(data.activeOrderCTA)[0]
const activePartnerOffer =
!activeOrder && partnerOffer && !partnerOfferTimer.hasEnded
@@ -50,7 +53,12 @@ export const ConversationCTA: React.FC<
const canPurchase = artwork.isAcquireable || !!activePartnerOffer
const canMakeOffer = artwork.isOfferable || artwork.isOfferableFromInquiry
const showTransactionButtons =
- !activeOrder && artwork.published && (canPurchase || canMakeOffer)
+ !activeOrder &&
+ // When the new convo offer UI is on, an active offer is shown via the CTA
+ // bar instead, so the transaction buttons are suppressed.
+ !(isPartnerOfferConvoEnabled && activePartnerOffer) &&
+ artwork.published &&
+ (canPurchase || canMakeOffer)
return (
<>
diff --git a/src/Apps/Conversations/components/ConversationCTA/__tests__/ConversationCTA.jest.tsx b/src/Apps/Conversations/components/ConversationCTA/__tests__/ConversationCTA.jest.tsx
index b43299d26a8..b4cf87f3c3f 100644
--- a/src/Apps/Conversations/components/ConversationCTA/__tests__/ConversationCTA.jest.tsx
+++ b/src/Apps/Conversations/components/ConversationCTA/__tests__/ConversationCTA.jest.tsx
@@ -1,4 +1,5 @@
import { screen } from "@testing-library/react"
+import { useFlag } from "@unleash/proxy-client-react"
import { ConversationsProvider } from "Apps/Conversations/ConversationsContext"
import { ConversationCTA } from "Apps/Conversations/components/ConversationCTA/ConversationCTA"
import { setupTestWrapperTL } from "DevTools/setupTestWrapperTL"
@@ -10,6 +11,8 @@ jest.unmock("react-relay")
jest.mock("react-tracking")
jest.mock("System/Hooks/useSystemContext")
+const mockUseFlag = useFlag as jest.Mock
+
describe("ConversationCTA", () => {
const { renderWithRelay } = setupTestWrapperTL({
Component: (props: any) => (
@@ -190,34 +193,61 @@ describe("ConversationCTA", () => {
expect(screen.queryByText("Purchase")).not.toBeInTheDocument()
expect(screen.queryByText("Make an Offer")).not.toBeInTheDocument()
})
- it("renders purchase button on orders with an active partner offer", () => {
+ const offerViewer = () => {
const futureTime = new Date()
futureTime.setHours(futureTime.getHours() + 1)
- renderWithRelay({
- Conversation: mockConversationWithArtwork({
- internalID: "artwork-id",
- isAcquireable: false,
- }),
- Viewer: () => ({
- me: {
- partnerOffersConnection: {
- edges: [
- {
- node: {
- internalID: "partner-offer-id",
- artworkId: "artwork-id",
- endAt: futureTime.toISOString(),
- },
+ return () => ({
+ me: {
+ partnerOffersConnection: {
+ edges: [
+ {
+ node: {
+ internalID: "partner-offer-id",
+ artworkId: "artwork-id",
+ endAt: futureTime.toISOString(),
},
- ],
- },
+ },
+ ],
},
- }),
+ },
})
+ }
- expect(screen.queryByText("Purchase")).toBeInTheDocument()
- expect(screen.queryByText("Make an Offer")).not.toBeInTheDocument()
+ describe("with the partner-offer-convo flag on", () => {
+ beforeEach(() => {
+ mockUseFlag.mockImplementation(() => true)
+ })
+
+ it("hides the transaction buttons when there is an active partner offer", () => {
+ renderWithRelay({
+ Conversation: mockConversationWithArtwork({
+ internalID: "artwork-id",
+ isAcquireable: true,
+ }),
+ Viewer: offerViewer(),
+ })
+
+ expect(screen.queryByText("Purchase")).not.toBeInTheDocument()
+ expect(screen.queryByText("Make an Offer")).not.toBeInTheDocument()
+ })
+ })
+
+ describe("with the partner-offer-convo flag off", () => {
+ beforeEach(() => {
+ mockUseFlag.mockImplementation(() => false)
+ })
+
+ it("ignores the partner offer and shows the normal transaction buttons", () => {
+ renderWithRelay({
+ Conversation: mockConversationWithArtwork({
+ internalID: "artwork-id",
+ isAcquireable: true,
+ }),
+ Viewer: offerViewer(),
+ })
+ expect(screen.queryByText("Purchase")).toBeInTheDocument()
+ })
})
it("renders Make Offer button on MO-only artworks", () => {
diff --git a/src/Apps/Conversations/components/ConversationReply.tsx b/src/Apps/Conversations/components/ConversationReply.tsx
index fd36a147314..80a4a9e9274 100644
--- a/src/Apps/Conversations/components/ConversationReply.tsx
+++ b/src/Apps/Conversations/components/ConversationReply.tsx
@@ -1,6 +1,7 @@
import { sentConversationMessage } from "@artsy/cohesion"
import { Button, Flex, TextArea, useToasts } from "@artsy/palette"
import { ConversationCTA } from "Apps/Conversations/components/ConversationCTA/ConversationCTA"
+import { ConversationPartnerOfferCTA } from "Apps/Conversations/components/Message/ConversationPartnerOfferCTA"
import { useSendConversationMessage } from "Apps/Conversations/mutations/useSendConversationMessage"
import { useRouter } from "System/Hooks/useRouter"
import { useSystemContext } from "System/Hooks/useSystemContext"
@@ -53,6 +54,7 @@ export const ConversationReply: FC<
item {
... on Artwork {
id
+ ...ConversationPartnerOfferCTA_artwork
}
}
}
@@ -172,6 +174,8 @@ export const ConversationReply: FC<
backgroundColor="mono5"
flexDirection="column"
>
+