components/migrate/steps/Review.vue
<template>
<div>
<div v-if="collectionOwner">
<div class="my-4 font-bold">
Migrate Items
</div>
<div class="max-h-40 overflow-auto">
<div
v-for="nft in collectionAnother?.nfts"
:key="nft.id"
class="flex items-center gap-4 mb-2"
:class="{ hidden: nft.currentOwner !== accountId }"
>
<NuxtImg
:src="sanitizeIpfsUrl(nft.meta?.image)"
:alt="nft.name"
width="32"
height="32"
class="border"
/>
<p>{{ nft.name }}</p>
</div>
</div>
<hr>
</div>
<div v-else>
<div class="mt-5">
<p class="font-bold">
{{ $t('migrate.collection') }}
</p>
<div class="flex mt-4">
<img
class="border mr-4"
:src="collectionMetadata?.image"
:alt="collection?.name"
width="48"
height="48"
>
<div>
<p>{{ collection?.name }}</p>
<p class="text-k-grey text-xs">
{{ $t('migrate.collectionName') }}
</p>
</div>
</div>
</div>
<hr>
<div
v-if="collection?.nftsOwned && collection?.nfts"
class="flex justify-between mb-5"
>
<p>{{ $t('migrate.ready.status') }}</p>
<p>{{ collection.nftsOwned.length }}/{{ collection.nfts.length }}</p>
</div>
<div class="border border-k-shade p-2 flex text-xs text-k-grey">
<NeoIcon
icon="circle-info"
class="mr-2"
/>
<p>{{ $t('migrate.reviewNotes') }}</p>
</div>
</div>
<div>
<p class="font-bold mt-5">
{{ $t('migrate.route') }}:
</p>
<NeoButton
rounded
variant="pill"
class="mt-2 hover:bg-transparent cursor-default"
>
<div class="flex items-center">
<img
width="20"
:src="source?.icon"
:alt="source?.text"
class="mr-2"
>
{{ source?.text }}
</div>
<NeoIcon
icon="chevron-right"
class="mx-4"
/>
<div class="flex items-center">
<img
width="20"
:src="destination?.icon"
:alt="destination?.text"
class="mr-2"
>
{{ destination?.text }}
</div>
</NeoButton>
</div>
<hr>
<div>
<div class="font-bold mt-5">
{{ $t('migrate.costs') }}
</div>
<div class="text-xs">
<p
class="my-4 text-k-grey cursor-pointer"
@click="toggleFee = !toggleFee"
>
{{ $t('migrate.feeBreakdown') }}
<NeoIcon :icon="toggleFee ? 'chevron-up' : 'chevron-down'" />
</p>
<div v-show="toggleFee">
<!-- paid on source chain -->
<p
v-if="source?.value"
class="mb-2 capitalize"
>
<strong>Paid On {{ prefixToNetwork[source.value] }}</strong>
</p>
<div class="flex justify-between mb-5">
<p>Burn {{ itemCount }} Items</p>
<p v-if="sourceNetworkFee >= 0">
{{ sourceNetworkFee }} {{ sourceSymbol }}
</p>
<div v-else>
<NeoSkeleton
width="100"
size="small"
/>
</div>
</div>
<!-- paid on destination chain -->
<p
v-if="destination?.value"
class="mb-2 capitalize"
>
<strong>Paid On {{ prefixToNetwork[destination.value] }}</strong>
</p>
<div class="flex justify-between mt-1">
<p>Migrate {{ itemCount }} Items</p>
<p v-if="destinationSymbol && destinationNetworkFee >= 0">
{{ destinationNetworkFee }} {{ destinationSymbol }}
</p>
<div v-else>
<NeoSkeleton
width="100"
size="small"
/>
</div>
</div>
<!-- collection existential deposit -->
<div
v-if="!collectionOwner"
class="text-k-grey flex mt-1 items-center justify-between"
>
<div>
{{ $t('mint.collection.modal.existentialDeposit') }}
<NeoTooltip
position="top"
class="cursor-pointer"
multiline-width="14rem"
multiline
:label="
$t('mint.collection.modal.depositTooltip', [
totalCollectionDeposit,
destinationSymbol,
])
"
>
<NeoIcon icon="circle-question" />
</NeoTooltip>
</div>
<p v-if="destinationSymbol">
{{ totalCollectionDeposit }} {{ destinationSymbol }}
</p>
<div v-else>
<NeoSkeleton
width="100"
size="small"
/>
</div>
</div>
<!-- nft existential deposit -->
<div class="text-k-grey flex mt-1 items-center justify-between">
<div>
{{ $t('mint.nft.modal.existentialDeposit') }}
<NeoTooltip
position="top"
class="cursor-pointer"
multiline-width="14rem"
multiline
:label="
$t('mint.nft.modal.depositTooltip', [
destinationItemDeposit,
destinationSymbol,
])
"
>
<NeoIcon icon="circle-question" />
</NeoTooltip>
</div>
<p v-if="destinationSymbol && destinationItemDeposit >= 0">
{{ destinationItemDeposit }} {{ destinationSymbol }}
</p>
<div v-else>
<NeoSkeleton
width="100"
size="small"
/>
</div>
</div>
<!-- kodadot fee -->
<div class="flex mt-1 text-k-grey items-center justify-between">
<div>
{{ $t('mint.nft.modal.kodadotFee') }}
<NeoTooltip
class="cursor-pointer"
position="top"
multiline-width="14rem"
:label="$t('mint.nft.modal.kodadotTooltip')"
multiline
>
<NeoIcon icon="circle-question" />
</NeoTooltip>
</div>
<div v-if="destinationSymbol">
{{ kodadotFee }} {{ destinationSymbol }}
</div>
<div v-else>
<NeoSkeleton
width="100"
size="small"
/>
</div>
</div>
</div>
</div>
</div>
<hr>
<p class="mb-1">
{{ $t('mint.estimated') }}
</p>
<div class="mb-1 flex justify-between">
<div
v-if="source?.value"
class="text-k-grey capitalize"
>
On {{ prefixToNetwork[source.value] }}
</div>
<div
v-if="sourceSymbol && sourceNetworkFee >= 0"
class="flex items-center"
>
<div class="text-k-grey text-xs mr-2">
${{ sourceTotalUsd }}
</div>
<div>{{ sourceNetworkFee }} {{ sourceSymbol }}</div>
</div>
<div v-else>
<NeoSkeleton
width="100"
size="small"
/>
</div>
</div>
<div class="pb-7 flex justify-between">
<div
v-if="destination?.value"
class="text-k-grey capitalize"
>
On {{ prefixToNetwork[destination.value] }}
</div>
<div
v-if="destinationSymbol && parseFloat(totalDestination.toString())"
class="flex items-center"
>
<div class="text-k-grey text-xs mr-2">
${{ totalDestinationUsd }}
</div>
<div>{{ totalDestination }} {{ destinationSymbol }}</div>
</div>
<div v-else>
<NeoSkeleton
width="100"
size="small"
/>
</div>
</div>
<NeoField>
<NeoCheckbox v-model="agree">
{{ $t('migrate.agreement') }}
</NeoCheckbox>
</NeoField>
<NeoButton
:label="checkBalances.label"
variant="primary"
:disabled="checkBalances.disabled"
class="mt-4 h-14 capitalize"
expanded
@click="toSign()"
/>
</div>
</template>
<script setup lang="ts">
import {
NeoButton,
NeoCheckbox,
NeoField,
NeoIcon,
NeoSkeleton,
NeoTooltip,
} from '@kodadot1/brick'
import { type Prefix } from '@kodadot1/static'
import { prefixToNetwork } from '@/composables/useMultipleBalance'
import { useCollectionReady, useMigrateDeposit } from '@/composables/useMigrate'
import { availablePrefixWithIcon } from '@/utils/chain'
import { hasOperationsDisabled } from '@/utils/prefix'
const { urlPrefix } = usePrefix()
const { $i18n } = useNuxtApp()
const { accountId } = useAuth()
const route = useRoute()
const source = availablePrefixWithIcon().find(
item => item.value === route.query.source,
)
const destination = availablePrefixWithIcon().find(
item => item.value === route.query.destination,
)
const itemCount = parseInt(route.query.itemCount?.toString() || '0')
const fromAccountId = route.query.accountId?.toString()
const collectionOwner = route.query.collectionOwner?.toString()
const collectionId = route.query.collectionId
// a collection where the owner is myself
const { collections } = await useCollectionReady()
const collection = computed(() =>
collections.value.find(item => item.id === collectionId),
)
// a collection where the owner is someone else
const { collections: collectionsAnother } = await useCollectionReady(
urlPrefix.value,
collectionOwner,
)
const collectionAnother = computed(() =>
collectionsAnother.value.find(item => item.id === collectionId),
)
// source balance and deposit
const sourceChain = computed(() => (source?.value || 'ksm') as Prefix)
const {
balance: sourceBalance,
chainNetworkFee: sourceNetworkFee,
chainSymbol: sourceSymbol,
totalChainUsd: sourceTotalUsd,
} = useMigrateDeposit(sourceChain, itemCount, fromAccountId)
// destination balance and deposit
const destinationChain = computed(() => (destination?.value || 'ksm') as Prefix)
const {
balance: destinationBalance,
chainItemDeposit: destinationItemDeposit,
chainNetworkFee: destinationNetworkFee,
chainSymbol: destinationSymbol,
kodadotFee,
totalChain: totalDestination,
totalChainUsd: totalDestinationUsd,
totalCollectionDeposit,
} = useMigrateDeposit(destinationChain, itemCount, accountId.value)
const agree = ref(false)
const toggleFee = ref(true)
const checkBalances = computed(() => {
const insufficient = $i18n.t('tooltip.notEnoughBalance')
const sourceNetwork = prefixToNetwork[sourceChain.value]
const sourceBalances = parseFloat(sourceBalance.value)
const destinationNetwork = prefixToNetwork[destinationChain.value]
const destinationBalances = parseFloat(destinationBalance.value)
const total = parseFloat(totalDestination.value.toString())
if (hasOperationsDisabled(sourceChain.value)) {
return {
label: $i18n.t('toast.unsupportedOperation'),
disabled: true,
}
}
if (!agree.value) {
return {
label: $i18n.t('migrate.reviewCtaUncheck'),
disabled: true,
}
}
if (sourceBalances < sourceNetworkFee.value) {
return {
label: `${insufficient} On ${sourceNetwork}`,
disabled: true,
}
}
if (destinationBalances < total) {
return {
label: `${insufficient} On ${destinationNetwork}`,
disabled: true,
}
}
if (sourceBalances > sourceNetworkFee.value && destinationBalances > total) {
return {
label: $i18n.t('migrate.reviewCtaCheck'),
disabled: false,
}
}
return {
label: `${insufficient} On ${sourceNetwork} & ${destinationNetwork}`,
disabled: true,
}
})
const toSign = () => {
navigateTo({
path: '/migrate/sign',
query: {
...route.query,
},
})
}
const collectionMetadata = computedAsync(async () => {
if (collection.value) {
return await getNftMetadata(collection.value as MinimalNFT, urlPrefix.value)
}
})
</script>