Skip to content

Commit d58538f

Browse files
authored
Merge pull request #11410 from kodadot/issue-11331-0
feat: Create Collection Atomic Swap
2 parents 67dc794 + 424df7d commit d58538f

32 files changed

+443
-84
lines changed

components/collection/CollectionTrades.vue

+18-1
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,31 @@
1212
:key="key"
1313
:query="tradeQuery"
1414
:type="tradeType"
15-
/>
15+
>
16+
<template
17+
v-if="collectionId"
18+
#action
19+
>
20+
<CreateCollectionSwapButton
21+
v-if="isTradeSwap(tradeType)"
22+
:collection-id="collectionId"
23+
/>
24+
<CreateCollectionOfferButton
25+
v-else-if="isTradeOffer(tradeType)"
26+
:collection-id="collectionId"
27+
/>
28+
</template>
29+
</TradeActivityTable>
1630
</div>
1731
</div>
1832
</template>
1933

2034
<script setup lang="ts">
2135
import { type TradeTableQuery } from '@/components/trade/TradeActivityTable.vue'
2236
import type { TradeType } from '@/components/trade/types'
37+
import { isTradeSwap } from '@/composables/useTradeType'
38+
import CreateCollectionSwapButton from '@/components/swap/CreateCollectionSwapButton.vue'
39+
import CreateCollectionOfferButton from '@/components/trade/makeOffer/CreateCollectionOfferButton.vue'
2340
2441
defineProps<{
2542
tradeType: TradeType

components/collection/drop/ItemsGrid.vue

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
<ItemsGrid
2727
:search="itemsGridSearch"
2828
:grid-size="'medium'"
29-
collection-popover-hide
29+
hide-collection-popover
3030
hide-listing
3131
show-timestamp
3232
:reset-search-query-params="['sort']"

components/gallery/GalleryItemAction/GalleryItemActionType/GalleryItemTrade.vue

+2-19
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@ import { usePreferencesStore } from '@/stores/preferences'
4141
import { useMakingOfferStore } from '@/stores/makeOffer'
4242
import GalleryItemPriceSection from '@/components/gallery/GalleryItemAction/GalleryItemActionSection.vue'
4343
import type { NFTOffer } from '@/composables/useNft'
44-
import { doAfterCheckCurrentChainVM } from '@/components/common/ConnectWallet/openReconnectWalletModal'
4544
4645
const props = defineProps<{
4746
nft: NFT
@@ -51,10 +50,9 @@ const props = defineProps<{
5150
const preferencesStore = usePreferencesStore()
5251
const makeOfferStore = useMakingOfferStore()
5352
const swapStore = useAtomicSwapStore()
54-
const { doAfterLogin } = useDoAfterlogin()
55-
const { isCurrentAccount, isLogIn } = useAuth()
53+
const { isCurrentAccount } = useAuth()
5654
const isOwner = computed(() => isCurrentAccount(props.nft?.currentOwner))
57-
55+
const { onTradeActionClick } = useTradeActionClick(isOwner)
5856
const highestOfferPrice = computed(() => props.highestOffer?.price || '')
5957
6058
const openOfferModal = () => {
@@ -65,21 +63,6 @@ const openOfferModal = () => {
6563
preferencesStore.setMakeOfferModalOpen(true)
6664
}
6765
68-
const onTradeActionClick = (cb: () => void) => {
69-
const fn = () => {
70-
if (!isOwner.value) {
71-
cb()
72-
}
73-
}
74-
75-
if (isLogIn.value) {
76-
doAfterCheckCurrentChainVM(fn)
77-
}
78-
else {
79-
doAfterLogin({ onLoginSuccess: fn })
80-
}
81-
}
82-
8366
const onMakeOfferClick = () => {
8467
onTradeActionClick(openOfferModal)
8568
}

components/items/ItemsGrid/ItemsGrid.vue

+3-3
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
:hide-media-info="hideMediaInfo"
2727
:hide-action="hideNFTHoverAction"
2828
:show-timestamp="showTimestamp"
29-
:collection-popover-hide="collectionPopoverHide"
29+
:hide-collection-popover="hideCollectionPopover"
3030
:hide-listing="hideListing"
3131
:lazy-loading="
3232
shouldLazyLoad({
@@ -148,7 +148,7 @@ const props = defineProps<{
148148
loadingOtherNetwork?: boolean
149149
showTimestamp?: boolean
150150
hideHoverAction?: boolean
151-
collectionPopoverHide?: boolean
151+
hideCollectionPopover?: boolean
152152
hideListing?: boolean
153153
linkTarget?: string
154154
fetchOnchainData?: boolean
@@ -284,7 +284,7 @@ const getSkeletonVariant = (slotProps) => {
284284
if (slotProps.isMobileVariant || slotProps.grid === 'small') {
285285
return 'minimal'
286286
}
287-
if (props.collectionPopoverHide) {
287+
if (props.hideCollectionPopover) {
288288
return 'slim'
289289
}
290290
return 'primary'

components/items/ItemsGrid/ItemsGridImage.vue

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
:variant="variant"
99
:hide-media-info="hideMediaInfo"
1010
:show-timestamp="showTimestamp"
11-
:collection-popover-hide="collectionPopoverHide"
11+
:hide-collection-popover="hideCollectionPopover"
1212
:lazy-loading="lazyLoading"
1313
:class="{ 'in-cart-border': shoppingCartStore.isItemInCart(nft.id) || isSelectActionItemInCart }"
1414
:show-action-on-hover="!showActionSection"
@@ -104,7 +104,7 @@ const props = defineProps<{
104104
hideVideoControls?: boolean
105105
hideListing?: boolean
106106
showTimestamp?: boolean
107-
collectionPopoverHide?: boolean
107+
hideCollectionPopover?: boolean
108108
lazyLoading?: boolean
109109
skeletonVariant: string
110110
linkTarget?: string

components/shared/nftCard/NftCard.vue

+2-2
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@
5959
:prefix="prefix"
6060
:show-price="showPrice"
6161
:show-timestamp="showTimestamp"
62-
:collection-popover-hide="collectionPopoverHide"
62+
:hide-collection-popover="hideCollectionPopover"
6363
:collection-popover-show-delay="collectionPopoverShowDelay"
6464
/>
6565
</component>
@@ -125,7 +125,7 @@ const props = withDefaults(
125125
prefix: string
126126
showPrice?: boolean
127127
showTimestamp?: boolean
128-
collectionPopoverHide?: boolean
128+
hideCollectionPopover?: boolean
129129
collectionPopoverShowDelay?: number
130130
variant?: NftCardVariant
131131
placeholder?: string

components/shared/nftCard/NftMediaInfo.vue

+3-3
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
:class="[
55
`nft-media-info__${variant}`,
66
{
7-
'nft-media-info__slim': collectionPopoverHide,
7+
'nft-media-info__slim': hideCollectionPopover,
88
},
99
]"
1010
>
@@ -17,7 +17,7 @@
1717
<CollectionDetailsPopover
1818
v-if="
1919
!isMinimal
20-
&& !collectionPopoverHide
20+
&& !hideCollectionPopover
2121
&& (nft.collection.name || nft.collection.id)
2222
"
2323
:show-delay="collectionPopoverShowDelay"
@@ -74,7 +74,7 @@ const props = withDefaults(
7474
prefix: string
7575
showPrice?: boolean
7676
showTimestamp?: boolean
77-
collectionPopoverHide?: boolean
77+
hideCollectionPopover?: boolean
7878
collectionPopoverShowDelay?: number
7979
variant?: NftCardVariant
8080
}>(),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<template>
2+
<NeoButton
3+
variant="secondary"
4+
class=""
5+
@click="onCreateCollectionSwapClick"
6+
>
7+
{{ $t('swap.createCollectionSwap') }}
8+
</NeoButton>
9+
</template>
10+
11+
<script lang="ts" setup>
12+
import { NeoButton } from '@kodadot1/brick'
13+
import { useCollectionMinimal } from '@/components/collection/utils/useCollectionDetails'
14+
15+
const props = defineProps<{
16+
collectionId: string
17+
}>()
18+
19+
const { $i18n } = useNuxtApp()
20+
21+
const collectionId = computed(() => props.collectionId)
22+
const swapStore = useAtomicSwapStore()
23+
24+
const { collection } = useCollectionMinimal({
25+
collectionId: collectionId,
26+
})
27+
const { onTradeActionClick } = useTradeActionClick()
28+
29+
const onCreateCollectionSwapClick = () => {
30+
onTradeActionClick(() => {
31+
const swap = swapStore.createSwap(collectionId.value!, {
32+
isCollectionSwap: true,
33+
desired: [
34+
{
35+
id: null,
36+
collectionId: collectionId.value!,
37+
name: $i18n.t('swap.anyNftFromCollection', [collection.value?.name]),
38+
sn: null,
39+
meta: {
40+
image: collection.value?.meta?.image,
41+
},
42+
},
43+
],
44+
45+
})
46+
navigateToSwap(swap)
47+
})
48+
}
49+
</script>

components/swap/Preview.vue

+24-20
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
</span>
2525

2626
<a
27-
v-if="count"
27+
v-if="count && !isCollectionSwapDesired"
2828
@click="clearAll"
2929
>
3030
{{ $t('shoppingCart.clearAll') }}
@@ -46,6 +46,7 @@
4646
:name="nft.name"
4747
:image="sanitizeIpfsUrl(nft.meta.image)"
4848
image-class="border border-k-shade"
49+
:removable="!(isCollectionSwap && nft.id === null)"
4950
@remove="swapStore.removeStepItem(nft.id)"
5051
/>
5152

@@ -107,7 +108,7 @@
107108
size="large"
108109
label="Next"
109110
variant="primary"
110-
:disabled
111+
:disabled="disabled"
111112
no-shadow
112113
expanded
113114
@click="onNext"
@@ -133,23 +134,6 @@ type StepDetails = {
133134
surchargeDirection: SwapSurchargeDirection
134135
}
135136
136-
const stepDetailsMap: Partial<Record<SwapStep, StepDetails>> = {
137-
[SwapStep.DESIRED]: {
138-
title: 'swap.yourSwapList',
139-
surchargeTitle: 'swap.requestToken',
140-
nextRouteName: getSwapStepRouteName(SwapStep.OFFERED),
141-
backRouteName: getSwapStepRouteName(SwapStep.COUNTERPARTY),
142-
surchargeDirection: 'Receive',
143-
},
144-
[SwapStep.OFFERED]: {
145-
title: 'swap.yourOffer',
146-
surchargeTitle: 'swap.addToken',
147-
nextRouteName: getSwapStepRouteName(SwapStep.REVIEW),
148-
backRouteName: getSwapStepRouteName(SwapStep.DESIRED),
149-
surchargeDirection: 'Send',
150-
},
151-
}
152-
153137
const props = defineProps<{
154138
step: SwapStep
155139
}>()
@@ -161,20 +145,40 @@ const { accountId } = useAuth()
161145
const { decimals } = useChain()
162146
const { urlPrefix } = usePrefix()
163147
const { getChainIcon } = useIcon()
148+
const isCollectionSwap = computed(() => swap.value.isCollectionSwap)
149+
150+
const stepDetailsMap: ComputedRef<Partial<Record<SwapStep, StepDetails>>> = computed(() => ({
151+
[SwapStep.DESIRED]: {
152+
title: 'swap.yourSwapList',
153+
surchargeTitle: 'swap.requestToken',
154+
nextRouteName: getSwapStepRouteName(SwapStep.OFFERED, isCollectionSwap.value),
155+
backRouteName: getSwapStepRouteName(SwapStep.COUNTERPARTY, isCollectionSwap.value),
156+
surchargeDirection: 'Receive',
157+
},
158+
[SwapStep.OFFERED]: {
159+
title: 'swap.yourOffer',
160+
surchargeTitle: 'swap.addToken',
161+
nextRouteName: getSwapStepRouteName(SwapStep.REVIEW, isCollectionSwap.value),
162+
backRouteName: getSwapStepRouteName(SwapStep.DESIRED, isCollectionSwap.value),
163+
surchargeDirection: 'Send',
164+
},
165+
}))
164166
165167
const target = ref()
166168
const amount = ref()
167169
const itemsContainer = ref()
168170
169171
const isTargetVisible = useElementVisibility(target)
170172
const stepItems = computed(() => swapStore.getStepItems(props.step))
171-
const stepDetails = computed(() => stepDetailsMap[props.step] as StepDetails)
173+
const stepDetails = computed(() => stepDetailsMap.value[props.step] as StepDetails)
172174
const title = computed(() => $i18n.t(stepDetails.value.title))
173175
const surchargeTitle = computed(() => $i18n.t(stepDetails.value.surchargeTitle))
174176
const surchargeDisabled = computed(() => Boolean(swap.value.surcharge))
175177
const stepHasSurcharge = computed(() => swap.value.surcharge?.direction === stepDetails.value.surchargeDirection)
176178
const count = computed(() => stepItems.value.length + (stepHasSurcharge.value ? 1 : 0))
177179
const isOverOneToOneSwap = computed(() => swap.value.offered.length > swap.value.desired.length && props.step === SwapStep.OFFERED)
180+
const isCollectionSwapDesired = computed(() => isCollectionSwap.value && props.step === SwapStep.DESIRED)
181+
178182
const disabled = computed(() => {
179183
if ((!accountId.value && props.step === SwapStep.OFFERED) || isOverOneToOneSwap.value) {
180184
return true

components/swap/PreviewItem.vue

+12-5
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
</div>
2323

2424
<NeoButton
25+
v-if="removable"
2526
size="small"
2627
variant="icon"
2728
icon="xmark"
@@ -35,9 +36,15 @@ import { NeoButton } from '@kodadot1/brick'
3536
3637
const emit = defineEmits(['remove'])
3738
38-
defineProps<{
39-
name?: string
40-
image: string
41-
imageClass?: string
42-
}>()
39+
withDefaults(
40+
defineProps<{
41+
name?: string
42+
image: string
43+
imageClass?: string
44+
removable?: boolean
45+
}>(),
46+
{
47+
removable: true,
48+
},
49+
)
4350
</script>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<template>
2+
<div
3+
class="rounded-full min-w-[236px] h-[62px] md:w-auto border border-k-shade inline-flex justify-start px-2.5"
4+
data-testid="drop-created-by-container"
5+
>
6+
<div class="flex items-center">
7+
<div
8+
class="rounded-full overflow-hidden bg-background-color border"
9+
:style="{
10+
width: `${imageSize}px`,
11+
height: `${imageSize}px`,
12+
padding: `${Math.round(imageSize / 16)}px`,
13+
}"
14+
>
15+
<BaseMediaItem
16+
:src="sanitizeIpfsUrl(collection.meta?.image)"
17+
:image-component="NuxtImg"
18+
:sizes="`${imageSize}px`"
19+
title="User Avatar"
20+
class="object-cover overflow-hidden rounded-full h-full w-full !shadow-none"
21+
inner-class="object-cover"
22+
/>
23+
</div>
24+
<div class="ml-3.5">
25+
<NuxtLink :to="`/${urlPrefix}/collection/${collectionId}`">
26+
{{ collection.name }}
27+
</NuxtLink>
28+
</div>
29+
</div>
30+
</div>
31+
</template>
32+
33+
<script lang="ts" setup>
34+
import { useCollectionMinimal } from '@/components/collection/utils/useCollectionDetails'
35+
import { sanitizeIpfsUrl } from '@/utils/ipfs'
36+
37+
const NuxtImg = resolveComponent('NuxtImg')
38+
39+
const props = withDefaults(
40+
defineProps<{
41+
collectionId: string
42+
imageSize?: number
43+
}>(),
44+
{
45+
imageSize: 48,
46+
},
47+
)
48+
49+
const { urlPrefix } = usePrefix()
50+
const { collection } = useCollectionMinimal({
51+
collectionId: computed(() => props.collectionId),
52+
})
53+
</script>

0 commit comments

Comments
 (0)