diff --git a/assets/styles/abstracts/_theme.scss b/assets/styles/abstracts/_theme.scss
index 8eb45c0f2d..f5b74637e9 100644
--- a/assets/styles/abstracts/_theme.scss
+++ b/assets/styles/abstracts/_theme.scss
@@ -24,6 +24,7 @@ $themes: (
'k-grey-fix': #999999,
'k-pink': #ffb6ef,
'k-yellow': #feffb6,
+ 'warning-yellow': #fffbcc,
'k-blueaccent': #b6cbff,
'k-aqua-blue': #cafdf4,
'k-greenaccent': #c2ffac,
@@ -72,6 +73,7 @@ $themes: (
'k-grey-fix': #999999,
'k-pink': #7a2a68,
'k-yellow': #363234,
+ 'warning-yellow': #3f3500,
'k-blueaccent': #2e50a2,
'k-aqua-blue': #106153,
'k-greenaccent': #056a02,
diff --git a/components/base/types.ts b/components/base/types.ts
index a09ebe2b54..7704802ec3 100644
--- a/components/base/types.ts
+++ b/components/base/types.ts
@@ -30,6 +30,7 @@ export interface CarouselNFT extends ItemResources {
}
name: string
price: string
+ latestSalePrice?: string
timestamp: string
unixTime: number
metadata?: string
diff --git a/components/carousel/module/CarouselInfo.vue b/components/carousel/module/CarouselInfo.vue
index d02593e5b0..fd00779bfa 100644
--- a/components/carousel/module/CarouselInfo.vue
+++ b/components/carousel/module/CarouselInfo.vue
@@ -35,12 +35,16 @@
? 'is-justify-content-space-between'
: 'is-justify-content-end',
]">
-
+
+
+ - {{ $t('spotlight.sold') }}
+
{{ chainName }}
@@ -71,8 +75,11 @@ const chainName = computed(() => {
return getChainNameByPrefix(props.item.chain || urlPrefix.value)
})
+const price = computed(() => props.item.latestSalePrice ?? props.item.price)
+const showSold = computed(() => Number(props.item.latestSalePrice) > 0)
+
const showPrice = computed((): boolean => {
- return Number(props.item.price) > 0 && !isCollection
+ return Number(price.value) > 0 && !isCollection
})
const unitSymbol = computed(() => prefixToToken[props.item.chain || 'ksm'])
diff --git a/components/carousel/utils/useCarouselEvents.ts b/components/carousel/utils/useCarouselEvents.ts
index dad14d471a..36953f1db2 100644
--- a/components/carousel/utils/useCarouselEvents.ts
+++ b/components/carousel/utils/useCarouselEvents.ts
@@ -38,8 +38,32 @@ const fetchLatestEvents = async (chain, type, where = {}, limit = 5) => {
})
}
-const useChainEvents = async (chain, type, eventQueryLimit, collectionIds) => {
- const nfts = ref<{ nft: NFTWithMetadata; timestamp: string }[]>([])
+const createEventQuery = (
+ type,
+ excludeNftId,
+ collectionIds,
+ excludeCollectionId,
+) => ({
+ nft: {
+ ...(type === 'newestList' && { price_gt: 0 }),
+ id_not_in: [...new Set(excludeNftId.value)],
+ collection: {
+ ...(collectionIds && { id_in: collectionIds }),
+ id_not_in: [...new Set(excludeCollectionId.value)],
+ },
+ },
+})
+
+const useChainEvents = async (
+ chain,
+ type,
+ eventQueryLimit,
+ collectionIds,
+ withLastestSale = true,
+) => {
+ const nfts = ref<
+ { nft: NFTWithMetadata; timestamp: string; latestSalePrice?: string }[]
+ >([])
const uniqueNftId = ref([])
const totalCollection = reactive({})
const excludeCollectionId = ref([])
@@ -54,6 +78,9 @@ const useChainEvents = async (chain, type, eventQueryLimit, collectionIds) => {
const pushNft = (nft) => {
if (!uniqueNftId.value.includes(nft.nft.id) && nfts.value.length < limit) {
uniqueNftId.value.push(nft.nft.id)
+ if (type === 'latestSales' && withLastestSale) {
+ nft.latestSalePrice = nft.meta
+ }
nfts.value.push(nft)
}
}
@@ -75,22 +102,14 @@ const useChainEvents = async (chain, type, eventQueryLimit, collectionIds) => {
totalCollection[nft.nft.collection.id] = 1
pushNft(nft)
}
-
- const { data } = await fetchLatestEvents(
- chain,
+ const query = createEventQuery(
type,
- {
- nft: {
- ...(type === 'newestList' && { price_gt: 0 }),
- id_not_in: [...new Set(excludeNftId.value)],
- collection: {
- ...(collectionIds && { id_in: collectionIds }),
- id_not_in: [...new Set(excludeCollectionId.value)],
- },
- },
- },
- eventQueryLimit,
+ excludeNftId,
+ collectionIds,
+ excludeCollectionId,
)
+
+ const { data } = await fetchLatestEvents(chain, type, query, eventQueryLimit)
data.value?.events?.forEach((nft) => limitCollection(nft))
return {
@@ -107,6 +126,7 @@ export const flattenNFT = (data, chain) => {
return {
...nft.nft,
timestamp: nft.timestamp,
+ latestSalePrice: nft.latestSalePrice,
}
})
@@ -158,6 +178,7 @@ export const useCarouselGenerativeNftEvents = async (
'latestSales',
10,
ahkCollectionIds,
+ false,
)
const { data: listDataAhk } = await useChainEvents(
'ahk',
@@ -170,6 +191,7 @@ export const useCarouselGenerativeNftEvents = async (
'latestSales',
10,
ahpCollectionIds,
+ false,
)
const { data: listDataAhp } = await useChainEvents(
'ahp',
diff --git a/components/items/ItemsGrid/ItemsGridImageTokenEntity.vue b/components/items/ItemsGrid/ItemsGridImageTokenEntity.vue
index 57c27e33c5..845ad28dd3 100644
--- a/components/items/ItemsGrid/ItemsGridImageTokenEntity.vue
+++ b/components/items/ItemsGrid/ItemsGridImageTokenEntity.vue
@@ -115,6 +115,7 @@ const {
isOwner,
isThereAnythingToList,
} = useNftActions(props.entity)
+const cheapestNFT = ref()
const { showCardIcon, cardIcon } = await useNftCardIcon(
computed(() => props.entity),
@@ -136,7 +137,9 @@ const mediaPlayerCover = computed(() => nftMetadata.value?.image)
const showActionSection = computed(() => {
return (
- !isLogIn.value && shoppingCartStore.getItemToBuy?.id === props.entity.id
+ !isLogIn.value &&
+ shoppingCartStore.getItemToBuy?.id !== undefined &&
+ shoppingCartStore.getItemToBuy?.id === cheapestNFT.value?.id
)
})
@@ -199,7 +202,7 @@ const onClickShoppingCart = async () => {
const onClickListingCart = async () => {
const nftsToProcess = await getTokensNfts([props.entity])
- const floorPrice = nftsToProcess[0].collection.floorPrice[0].price
+ const floorPrice = nftsToProcess[0].collection.floorPrice[0]?.price || '0'
for (const nft of nftsToProcess) {
if (listingCartStore.isItemInCart(nft.id)) {
@@ -209,6 +212,10 @@ const onClickListingCart = async () => {
}
}
}
+
+onMounted(async () => {
+ cheapestNFT.value = await getNFTForBuying()
+})
diff --git a/components/massmint/modals/index.ts b/components/massmint/modals/index.ts
new file mode 100644
index 0000000000..808fd001d7
--- /dev/null
+++ b/components/massmint/modals/index.ts
@@ -0,0 +1,5 @@
+export { default as MintingModal } from './MintingModal.vue'
+export { default as MissingInfoModal } from './MissingInfoModal.vue'
+export { default as MobileDisclaimerModal } from './MobileDisclaimerModal.vue'
+export { default as DeleteModal } from './DeleteModal.vue'
+export { default as ReviewModal } from './ReviewModal.vue'
diff --git a/composables/useListInfiniteScroll.ts b/composables/useListInfiniteScroll.ts
index fa11929f8a..0885103548 100644
--- a/composables/useListInfiniteScroll.ts
+++ b/composables/useListInfiniteScroll.ts
@@ -69,6 +69,13 @@ export default function ({
)
const updateCurrentPage = () => {
+ // allow page update only when current path is same as route path
+ // i.e. scope it to only the page in which useListInfiniteScroll is used
+ const allowUpdate =
+ process.client && window.location.pathname === route.path
+ if (!allowUpdate) {
+ return
+ }
const page =
Math.floor(document.documentElement.scrollTop / pageHeight.value) +
startPage.value
diff --git a/layouts/explore-layout.vue b/layouts/explore-layout.vue
index ed882c8e2f..4d897e5e98 100644
--- a/layouts/explore-layout.vue
+++ b/layouts/explore-layout.vue
@@ -27,7 +27,7 @@
-
+
@@ -70,8 +70,6 @@ const isCollection = computed(
() => route.name?.toString().includes('prefix-collection-id'),
)
-const keepalive = computed(() => (isCollection.value ? false : undefined))
-
const getExploreTitle = computed(() => {
if (
Object.keys(chainNameSeoMap).includes(urlPrefix.value) &&
diff --git a/libs/ui/src/components/MediaItem/MediaItem.vue b/libs/ui/src/components/MediaItem/MediaItem.vue
index fa15360064..23f7b40097 100644
--- a/libs/ui/src/components/MediaItem/MediaItem.vue
+++ b/libs/ui/src/components/MediaItem/MediaItem.vue
@@ -96,7 +96,7 @@ const props = withDefaults(
original: false,
isLewd: false,
isDetail: false,
- placeholder: '',
+ placeholder: './Koda.svg',
disableOperation: undefined,
audioPlayerCover: '',
imageComponent: 'img',
diff --git a/libs/ui/src/components/MediaItem/type/ImageMedia.vue b/libs/ui/src/components/MediaItem/type/ImageMedia.vue
index aedc16239c..ac1ece3040 100644
--- a/libs/ui/src/components/MediaItem/type/ImageMedia.vue
+++ b/libs/ui/src/components/MediaItem/type/ImageMedia.vue
@@ -5,14 +5,32 @@
'is-square image': !original,
'is-detail': isDetail,
}">
+
+ @error.once="() => onError('error-1')" />
+
+
+ onError('error-2')" />
+
+
@@ -36,16 +54,18 @@ const props = withDefaults(
}>(),
{
sizes: '450px md:350px lg:270px',
+ imageComponent: 'img',
+ src: '',
+ alt: '',
},
)
-const onError = (e: Event) => {
- const target = e.target as HTMLImageElement
- if (target) {
- consola.log('[KODADOT::IMAGE] unable to load', props.src, e)
- target.removeAttribute('srcset')
- target.src = props.placeholder
- }
+type Status = 'ok' | 'error-1' | 'error-2'
+const status = ref('ok')
+
+const onError = async (phase: Status) => {
+ consola.log('[KODADOT::IMAGE] unable to load:', `${phase}:`, props.src)
+ status.value = phase
}
diff --git a/libs/ui/src/components/NeoAvatar/NeoAvatar.vue b/libs/ui/src/components/NeoAvatar/NeoAvatar.vue
index 5edc5b4d7c..54d26433a2 100644
--- a/libs/ui/src/components/NeoAvatar/NeoAvatar.vue
+++ b/libs/ui/src/components/NeoAvatar/NeoAvatar.vue
@@ -1,6 +1,7 @@
-
+ :original="true" />
import { computed, defineProps } from 'vue'
import type { ImageComponent } from '../TheImage/TheImage.vue'
-import TheImage from '../TheImage/TheImage.vue'
+import ImageMedia from '../MediaItem/type/ImageMedia.vue'
const props = defineProps<{
imageComponent?: ImageComponent
@@ -35,14 +37,6 @@ const customStyle = computed(() => ({
width: `${props.size}px`,
height: `${props.size}px`,
}))
-
-const onError = (e: Event) => {
- const target = e.target as HTMLImageElement
- if (target) {
- target.removeAttribute('srcset')
- target.src = props.placeholder
- }
-}