diff --git a/components/Navbar.vue b/components/Navbar.vue index cb061d113f..2703adee5a 100644 --- a/components/Navbar.vue +++ b/components/Navbar.vue @@ -77,6 +77,10 @@ id="NavChainSelect" class="navbar-chain custom-navbar-item" data-cy="chain-select" /> + + + + + + diff --git a/components/common/NotificationBox/NotificationItem.vue b/components/common/NotificationBox/NotificationItem.vue new file mode 100644 index 0000000000..ea48f7918a --- /dev/null +++ b/components/common/NotificationBox/NotificationItem.vue @@ -0,0 +1,115 @@ + + + + + diff --git a/components/common/NotificationBox/types.ts b/components/common/NotificationBox/types.ts new file mode 100644 index 0000000000..50362a088e --- /dev/null +++ b/components/common/NotificationBox/types.ts @@ -0,0 +1,14 @@ +import type { CarouselNFT } from '@/components/base/types' +import type { Interaction } from '@/components/rmrk/service/scheme' +export type FilterOption = { + id: string + name: string +} + +export declare interface Event extends Interaction { + nft: CarouselNFT & { + meta: { + name: string + } + } +} diff --git a/components/common/NotificationBox/useNotification.ts b/components/common/NotificationBox/useNotification.ts new file mode 100644 index 0000000000..f56698121c --- /dev/null +++ b/components/common/NotificationBox/useNotification.ts @@ -0,0 +1,85 @@ +import { Event, FilterOption } from './types' +import { Interaction as _Interaction } from '@kodadot1/minimark' +import { sortedEventByDate } from '@/utils/sorting' + +export const Interaction = { + SALE: _Interaction.BUY, + OFFER: 'CREATE', + ACCEPTED_OFFER: 'ACCEPT', +} + +export const getInteractionName = (key: string) => { + const { $i18n } = useNuxtApp() + const nameMap = { + [Interaction.SALE]: $i18n.t('filters.sale'), + [Interaction.OFFER]: $i18n.t('filters.offer'), + [Interaction.ACCEPTED_OFFER]: $i18n.t('filters.acceptedOffer'), + } + + return nameMap[key] +} + +export const getInteractionColor = (key: string) => { + const colorMap = { + [Interaction.SALE]: 'k-pink', + [Interaction.OFFER]: 'k-greenaccent', + [Interaction.ACCEPTED_OFFER]: 'k-blueaccent', + } + + return colorMap[key] +} + +export const useNotification = (account: string) => { + const { apiInstance } = useApi() + const collections = ref([]) + const events = ref([]) + + async function currentBlock() { + const api = await apiInstance.value + const block = await api.rpc.chain.getHeader() + return block.number.toNumber() + } + + const { data: collectionData } = useGraphql({ + queryName: 'collectionByAccount', + variables: { + account, + }, + }) + + const { data: eventData } = useGraphql({ + queryName: 'notificationsByAccount', + variables: { + account, + }, + }) + + watch(collectionData, (result) => { + collections.value = result.collectionEntities ?? [] + }) + + watch(eventData, async (result) => { + const currentBlockNumber = await currentBlock() + const offerEvents = result.offerEvents + .map((event) => ({ + ...event, + nft: { + ...event.offer.nft, + }, + })) + .filter( + (event) => + event.interaction === Interaction.OFFER && + Number(event.offer.expiration) < Number(currentBlockNumber) + ) + events.value = sortedEventByDate( + [...result.events, ...offerEvents], + 'ASC' + ) as unknown as Event[] + }) + + return { + collections, + events, + } +} diff --git a/components/common/NotificationBox/useNotificationBox.ts b/components/common/NotificationBox/useNotificationBox.ts new file mode 100644 index 0000000000..2cd3f05155 --- /dev/null +++ b/components/common/NotificationBox/useNotificationBox.ts @@ -0,0 +1,7 @@ +import NotificationBoxModal from './NotificationBoxModal.vue' + +export const NotificationBoxModalConfig = { + component: NotificationBoxModal, + canCancel: ['outside'], + customClass: 'notification-box-modal', +} diff --git a/components/navbar/NotificationBoxButton.vue b/components/navbar/NotificationBoxButton.vue new file mode 100644 index 0000000000..e731b61a6f --- /dev/null +++ b/components/navbar/NotificationBoxButton.vue @@ -0,0 +1,35 @@ + + + diff --git a/locales/en.json b/locales/en.json index 533d5967d8..0eb1f4cefc 100644 --- a/locales/en.json +++ b/locales/en.json @@ -1288,17 +1288,29 @@ "uploadDesc": "Upload Description File (Optional)" }, "filters": { + "buy": "Buy", "sale": "Sale", "offer": "Offer", + "acceptedOffer": "Accepted offer", "listing": "Listing", "mint": "Mint", "transfer": "Transfer" }, - "redirect":{ - "title":"Leaving Kodadot", - "leavingTips":"You are going outside KodaDot to external website", - "safetyTips":"Be sure that it's safe!", - "continue":"Continue", - "stay":"Stay" + "redirect": { + "title": "Leaving Kodadot", + "leavingTips": "You are going outside KodaDot to external website", + "safetyTips": "Be sure that it's safe!", + "continue": "Continue", + "stay": "Stay" + }, + "notification": { + "notifications": "Notifications", + "filters": "Filters", + "add": "Add", + "done": "Done", + "byCollection": "By collection", + "byEvent": "By event", + "emptyTipLine1": "Don't wait for notifications,", + "emptyTipLine2": "make your own buzz with your art." } } diff --git a/queries/subsquid/general/collectionByAccount.graphql b/queries/subsquid/general/collectionByAccount.graphql new file mode 100644 index 0000000000..88a32bcadd --- /dev/null +++ b/queries/subsquid/general/collectionByAccount.graphql @@ -0,0 +1,9 @@ +query collectionByAccount($account: String!) { + collectionEntities( + where: { currentOwner_eq: $account } + orderBy: blockNumber_DESC + ) { + id + name + } +} diff --git a/queries/subsquid/general/notificationsByAccount.graphql b/queries/subsquid/general/notificationsByAccount.graphql new file mode 100644 index 0000000000..f5c1a94649 --- /dev/null +++ b/queries/subsquid/general/notificationsByAccount.graphql @@ -0,0 +1,44 @@ +#import "../../fragments/subsquidNft.graphql" + +query notificationsByAccount($account: String!, $limit: Int = 30) { + # get sale event + events( + limit: $limit + where: { currentOwner_eq: $account, interaction_eq: BUY } + orderBy: timestamp_DESC + ) { + id + interaction + timestamp + currentOwner + meta + nft { + ...subsquidNft + } + } + # get offer and offer_accept events + offerEvents( + limit: $limit + where: { + currentOwner_eq: $account + AND: { interaction_eq: CREATE, offer: { status_eq: ACTIVE } } + OR: { + caller_eq: $account + AND: { interaction_eq: ACCEPT, offer: { status_eq: ACCEPTED } } + } + } + orderBy: timestamp_DESC + ) { + id + interaction + timestamp + currentOwner + meta + offer { + expiration + nft { + ...subsquidNft + } + } + } +} diff --git a/styles/abstracts/_variables.scss b/styles/abstracts/_variables.scss index 2e3e3015fe..07e6191444 100644 --- a/styles/abstracts/_variables.scss +++ b/styles/abstracts/_variables.scss @@ -180,3 +180,5 @@ $body-family: $family-primary; $fluid-container-padding: 2.5rem; $fluid-container-padding-mobile: 1.25rem; + +$tooltip-content-zindex: 999; \ No newline at end of file diff --git a/styles/global.scss b/styles/global.scss index a962919cc3..d7f6484c88 100644 --- a/styles/global.scss +++ b/styles/global.scss @@ -304,6 +304,11 @@ hr { border-top: 1px solid theme('border-color'); } } +.border-left { + @include ktheme() { + border-left: 1px solid theme('border-color'); + } +} .border { @include ktheme() { border: 1px solid theme('border-color'); diff --git a/utils/format/time.ts b/utils/format/time.ts index 7ffe0b83cb..69f69da386 100644 --- a/utils/format/time.ts +++ b/utils/format/time.ts @@ -35,6 +35,6 @@ export const endDate = (seconds: number): string => { return parseDate(addSeconds(new Date(), seconds)) } -export const formatToNow = (date: Date): string => { - return formatDistanceToNowStrict(new Date(date), { addSuffix: true }) +export const formatToNow = (date: Date, addSuffix = true): string => { + return formatDistanceToNowStrict(new Date(date), { addSuffix }) }