diff --git a/src/assets/images/nft/books/collector-message-item-background-lg.svg b/src/assets/images/nft/books/collector-message-item-background-lg.svg
new file mode 100644
index 000000000..bf670be71
--- /dev/null
+++ b/src/assets/images/nft/books/collector-message-item-background-lg.svg
@@ -0,0 +1,15 @@
+
\ No newline at end of file
diff --git a/src/assets/images/nft/books/collector-message-item-background.svg b/src/assets/images/nft/books/collector-message-item-background.svg
new file mode 100644
index 000000000..86586ec7f
--- /dev/null
+++ b/src/assets/images/nft/books/collector-message-item-background.svg
@@ -0,0 +1,15 @@
+
\ No newline at end of file
diff --git a/src/components/CollapsibleCard.vue b/src/components/CollapsibleCard.vue
index d6cb36768..b377fcb05 100644
--- a/src/components/CollapsibleCard.vue
+++ b/src/components/CollapsibleCard.vue
@@ -83,10 +83,14 @@ export default {
type: Boolean,
default: true,
},
+ defaultOpen: {
+ type: Boolean,
+ default: true,
+ },
},
data() {
return {
- isOpen: true,
+ isOpen: this.defaultOpen,
};
},
computed: {
diff --git a/src/components/NFTMessage/Identity.vue b/src/components/NFTMessage/Identity.vue
index b3a16ed22..7ffa19214 100644
--- a/src/components/NFTMessage/Identity.vue
+++ b/src/components/NFTMessage/Identity.vue
@@ -36,9 +36,12 @@
>
{{ userLabel }}
-
+
diff --git a/src/components/NFTPage/ChainDataSection/index.vue b/src/components/NFTPage/ChainDataSection/index.vue
index c61b80bb4..47c3585ca 100644
--- a/src/components/NFTPage/ChainDataSection/index.vue
+++ b/src/components/NFTPage/ChainDataSection/index.vue
@@ -2,6 +2,7 @@
diff --git a/src/components/NFTPage/CollectorListDialog.vue b/src/components/NFTPage/CollectorListDialog.vue
new file mode 100644
index 000000000..29d99319e
--- /dev/null
+++ b/src/components/NFTPage/CollectorListDialog.vue
@@ -0,0 +1,91 @@
+
+
+
+
diff --git a/src/components/NFTPage/CollectorMessageList/Identity.vue b/src/components/NFTPage/CollectorMessageList/Identity.vue
new file mode 100644
index 000000000..cbed64e8e
--- /dev/null
+++ b/src/components/NFTPage/CollectorMessageList/Identity.vue
@@ -0,0 +1,104 @@
+
+
+
+
+
+
+
+
diff --git a/src/components/NFTPage/CollectorMessageList/Item.vue b/src/components/NFTPage/CollectorMessageList/Item.vue
new file mode 100644
index 000000000..204ccf33c
--- /dev/null
+++ b/src/components/NFTPage/CollectorMessageList/Item.vue
@@ -0,0 +1,110 @@
+
+
+
+
+
+
+
diff --git a/src/components/NFTPage/CollectorMessageList/index.vue b/src/components/NFTPage/CollectorMessageList/index.vue
new file mode 100644
index 000000000..5fce5af42
--- /dev/null
+++ b/src/components/NFTPage/CollectorMessageList/index.vue
@@ -0,0 +1,77 @@
+
+
+
+
+
+
+
+
diff --git a/src/components/ScrollingList.vue b/src/components/ScrollingList.vue
new file mode 100644
index 000000000..a766fcdca
--- /dev/null
+++ b/src/components/ScrollingList.vue
@@ -0,0 +1,121 @@
+
+
+
+
+
+
diff --git a/src/locales/en.json b/src/locales/en.json
index 8ac634eb8..748082504 100644
--- a/src/locales/en.json
+++ b/src/locales/en.json
@@ -622,6 +622,8 @@
"nft_collect_modal_subtitle_select_collect_method": "Select a collect method",
"nft_collect_modal_title_collect": "Collect NFT",
"nft_collect_modal_title_collecting": "Collecting NFT",
+ "nft_collector_message_label": "Collector's message",
+ "nft_collector_message_view_all": "View all",
"nft_collection_content_label": "Content in this collection",
"nft_collection_hint": "This book is part of a collection",
"nft_collection_label": "Collection",
diff --git a/src/locales/zh-Hant.json b/src/locales/zh-Hant.json
index e51787675..2e9a69a1e 100644
--- a/src/locales/zh-Hant.json
+++ b/src/locales/zh-Hant.json
@@ -622,6 +622,8 @@
"nft_collect_modal_subtitle_select_collect_method": "請選擇收藏方法",
"nft_collect_modal_title_collect": "收藏作品",
"nft_collect_modal_title_collecting": "正在收藏作品",
+ "nft_collector_message_label": "收藏者的留言",
+ "nft_collector_message_view_all": "顯示全部",
"nft_collection_content_label": "套裝內容",
"nft_collection_hint": "此本書收錄在套裝中",
"nft_collection_label": "組合套裝",
diff --git a/src/pages/nft/class/_classId/index.vue b/src/pages/nft/class/_classId/index.vue
index e8bc0888a..0f9cd747e 100644
--- a/src/pages/nft/class/_classId/index.vue
+++ b/src/pages/nft/class/_classId/index.vue
@@ -1,5 +1,5 @@
-
+
{{
$t('nft_details_page_label_loading')
}}
@@ -173,6 +173,65 @@
+
+
+
+
+
-
-
-
-
+
+
@@ -479,6 +527,10 @@ export default {
customPrice: 0,
isOpeningCheckoutPage: false,
+
+ isHistoryInfoLoading: false,
+ hasHistoryInfoLoaded: false,
+ isOpeningCollectorListDialog: false,
};
},
async fetch({ route, store, redirect, error, localeLocation }) {
@@ -936,6 +988,7 @@ export default {
shouldShowFollowButton() {
return Boolean(this.iscnOwner !== this.getAddress);
},
+ // for WNFT
populatedCollectorsWithMemo() {
if (!this.populatedDisplayEvents) {
return this.populatedCollectors;
@@ -967,6 +1020,7 @@ export default {
}
return collectorsWithMemo;
},
+ // for collector dialog
populatedBuyerWithMessage() {
if (!this.populatedDisplayEvents) {
return this.populatedCollectors;
@@ -1001,6 +1055,62 @@ export default {
}
return collectorsWithBuyerMessages;
},
+ // for collector message list
+ filterCollectorsWithReplies() {
+ if (!this.populatedDisplayEvents || !this.populatedCollectors) {
+ return [];
+ }
+ const collectorsWithReplies = this.populatedCollectors
+ .map(collector => {
+ const purchaseEvent = this.populatedDisplayEvents.find(
+ event =>
+ event.event === 'purchase' && event.toWallet === collector.id
+ );
+
+ const transferEvent = this.populatedDisplayEvents.find(
+ event =>
+ event.event === 'transfer' && event.toWallet === collector.id
+ );
+
+ let buyerMessage = null;
+ let authorReply = null;
+
+ if (purchaseEvent) {
+ buyerMessage = purchaseEvent.buyerMessage || null;
+ } else if (transferEvent) {
+ buyerMessage = transferEvent.buyerMessage || null;
+ authorReply = transferEvent.memo || null;
+ }
+
+ if (!buyerMessage) {
+ return null;
+ }
+
+ return {
+ ...collector,
+ buyerMessage,
+ authorReply,
+ };
+ })
+ .filter(Boolean);
+
+ if (collectorsWithReplies && collectorsWithReplies.length) {
+ collectorsWithReplies.sort((a, b) => {
+ if (a.buyerMessage && !b.buyerMessage) {
+ return -1;
+ }
+ if (!a.buyerMessage && b.buyerMessage) {
+ return 1;
+ }
+ return 0;
+ });
+ }
+
+ return collectorsWithReplies;
+ },
+ shouldShowCollectorMessage() {
+ return this.filterCollectorsWithReplies?.length > 3;
+ },
isShowBanner() {
return (
this.classId ===
@@ -1026,6 +1136,16 @@ export default {
: 'nft_edition_select_compare_button_text'
);
},
+ overlayClasses() {
+ return [
+ 'h-full',
+ 'w-[60px]',
+ 'from-light-gray',
+ 'to-transparent',
+ 'hidden',
+ 'laptop:block',
+ ];
+ },
},
async mounted() {
try {
@@ -1100,6 +1220,7 @@ export default {
]),
parseNFTMetadataURL,
async fetchTrimmedCollectorsInfo() {
+ this.isHistoryInfoLoading = true;
const trimmedCollectors = this.sortedOwnerListId.slice(
0,
this.trimmedCount
@@ -1108,6 +1229,8 @@ export default {
this.lazyGetUserInfoByAddresses(trimmedCollectors),
this.updateNFTHistory({ getAllUserInfo: false }),
]);
+ this.isHistoryInfoLoading = false;
+ this.hasHistoryInfoLoaded = true;
},
getPurchaseEventParams(edition) {
const customPriceInDecimal = this.customPrice
@@ -1140,6 +1263,17 @@ export default {
);
await this.lazyGetUserInfoByAddresses(this.sortedOwnerListId);
},
+ async handleClickMoreCollectorMessage() {
+ this.isOpeningCollectorListDialog = true;
+ logTrackerEvent(
+ this,
+ 'NFT',
+ 'class_details_show_more_collector_clicked',
+ this.classId,
+ 1
+ );
+ await this.lazyGetUserInfoByAddresses(this.sortedOwnerListId);
+ },
async handleClickMoreHistory() {
logTrackerEvent(
this,
diff --git a/src/util/ui.js b/src/util/ui.js
index 5e5ffee3a..eb07f4e21 100644
--- a/src/util/ui.js
+++ b/src/util/ui.js
@@ -9,16 +9,15 @@ const largeNumFormatter = new Intl.NumberFormat('en-US', {
maximumFractionDigits: 2,
});
-export function ellipsis(value) {
- if (value) {
- const len = value.length;
- const dots = '...';
- if (!value) return '';
- if (value.length > 20) {
- return value.substring(0, 8) + dots + value.substring(len - 6, len);
- }
- return value;
+export function ellipsis(value, maxLength = 20, endLength = 6) {
+ if (!value) return '';
+ const len = value.length;
+ const dots = '...';
+
+ if (len > maxLength) {
+ return value.substring(0, 8) + dots + value.substring(len - endLength, len);
}
+
return value;
}