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')" /> + + + + + @@ -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 @@