1
0
Artem 1 rok pred
rodič
commit
8c440965ca

+ 24 - 4
packages/atlas/src/api/hooks/notifications.ts

@@ -1,10 +1,13 @@
 import { QueryHookOptions } from '@apollo/client'
 
 import {
+  GetNftActivitiesCountQuery,
+  GetNftActivitiesCountQueryVariables,
   GetNftActivitiesQuery,
   GetNftActivitiesQueryVariables,
   GetNotificationsConnectionQuery,
   GetNotificationsConnectionQueryVariables,
+  useGetNftActivitiesCountQuery,
   useGetNftActivitiesQuery,
   useGetNotificationsConnectionQuery,
 } from '@/api/queries/__generated__/notifications.generated'
@@ -31,6 +34,27 @@ export const useRawNotifications = (
   }
 }
 
+export const useActivitiesCount = (
+  memberId?: string,
+  opts?: QueryHookOptions<GetNftActivitiesCountQuery, GetNftActivitiesCountQueryVariables>
+) => {
+  const { data, ...rest } = useGetNftActivitiesCountQuery({
+    ...opts,
+    variables: {
+      memberId: memberId || '',
+    },
+    skip: !memberId,
+  })
+
+  return {
+    nftsBiddedTotalCount: data?.nftsBidded.totalCount,
+    nftsBoughtTotalCount: data?.nftsBought.totalCount,
+    nftsSoldTotalCount: data?.nftsSold.totalCount,
+    nftsIssuedTotalCount: data?.nftsIssued.totalCount,
+    ...rest,
+  }
+}
+
 export const useRawActivities = (
   memberId?: string,
   sort: NftActivityOrderByInput = NftActivityOrderByInput.EventTimestampDesc,
@@ -47,10 +71,6 @@ export const useRawActivities = (
   })
 
   return {
-    nftsBiddedTotalCount: data?.nftsBidded.totalCount,
-    nftsBoughtTotalCount: data?.nftsBought.totalCount,
-    nftsSoldTotalCount: data?.nftsSold.totalCount,
-    nftsIssuedTotalCount: data?.nftsIssued.totalCount,
     totalCount: data?.nftActivitiesConnection.totalCount,
     pageInfo: data?.nftActivitiesConnection.pageInfo,
     activities: data?.nftActivitiesConnection.edges,

+ 367 - 220
packages/atlas/src/api/queries/__generated__/notifications.generated.tsx

@@ -109,9 +109,32 @@ export type GetNotificationsConnectionQuery = {
                       } | null
                     }
                   } | null
-                  auction: {
-                    __typename?: 'Auction'
-                    nft: { __typename?: 'OwnedNft'; video: { __typename?: 'Video'; id: string; title?: string | null } }
+                  nft: {
+                    __typename?: 'OwnedNft'
+                    video: {
+                      __typename?: 'Video'
+                      id: string
+                      title?: string | null
+                      thumbnailPhoto?: {
+                        __typename?: 'StorageDataObject'
+                        id: string
+                        resolvedUrls: Array<string>
+                        resolvedUrl?: string | null
+                        createdAt: Date
+                        size: string
+                        isAccepted: boolean
+                        ipfsHash: string
+                        storageBag: { __typename?: 'StorageBag'; id: string }
+                        type?:
+                          | { __typename: 'DataObjectTypeChannelAvatar' }
+                          | { __typename: 'DataObjectTypeChannelCoverPhoto' }
+                          | { __typename: 'DataObjectTypeChannelPayoutsPayload' }
+                          | { __typename: 'DataObjectTypeVideoMedia' }
+                          | { __typename: 'DataObjectTypeVideoSubtitle' }
+                          | { __typename: 'DataObjectTypeVideoThumbnail' }
+                          | null
+                      } | null
+                    }
                   }
                 }
                 nftOwner:
@@ -233,7 +256,33 @@ export type GetNotificationsConnectionQuery = {
                         | null
                     } | null
                   }
-                  nft: { __typename?: 'OwnedNft'; video: { __typename?: 'Video'; id: string; title?: string | null } }
+                  nft: {
+                    __typename?: 'OwnedNft'
+                    video: {
+                      __typename?: 'Video'
+                      id: string
+                      title?: string | null
+                      thumbnailPhoto?: {
+                        __typename?: 'StorageDataObject'
+                        id: string
+                        resolvedUrls: Array<string>
+                        resolvedUrl?: string | null
+                        createdAt: Date
+                        size: string
+                        isAccepted: boolean
+                        ipfsHash: string
+                        storageBag: { __typename?: 'StorageBag'; id: string }
+                        type?:
+                          | { __typename: 'DataObjectTypeChannelAvatar' }
+                          | { __typename: 'DataObjectTypeChannelCoverPhoto' }
+                          | { __typename: 'DataObjectTypeChannelPayoutsPayload' }
+                          | { __typename: 'DataObjectTypeVideoMedia' }
+                          | { __typename: 'DataObjectTypeVideoSubtitle' }
+                          | { __typename: 'DataObjectTypeVideoThumbnail' }
+                          | null
+                      } | null
+                    }
+                  }
                 }
                 previousNftOwner:
                   | {
@@ -403,9 +452,32 @@ export type GetNotificationsConnectionQuery = {
                         | null
                     } | null
                   }
-                  auction: {
-                    __typename?: 'Auction'
-                    nft: { __typename?: 'OwnedNft'; video: { __typename?: 'Video'; id: string; title?: string | null } }
+                  nft: {
+                    __typename?: 'OwnedNft'
+                    video: {
+                      __typename?: 'Video'
+                      id: string
+                      title?: string | null
+                      thumbnailPhoto?: {
+                        __typename?: 'StorageDataObject'
+                        id: string
+                        resolvedUrls: Array<string>
+                        resolvedUrl?: string | null
+                        createdAt: Date
+                        size: string
+                        isAccepted: boolean
+                        ipfsHash: string
+                        storageBag: { __typename?: 'StorageBag'; id: string }
+                        type?:
+                          | { __typename: 'DataObjectTypeChannelAvatar' }
+                          | { __typename: 'DataObjectTypeChannelCoverPhoto' }
+                          | { __typename: 'DataObjectTypeChannelPayoutsPayload' }
+                          | { __typename: 'DataObjectTypeVideoMedia' }
+                          | { __typename: 'DataObjectTypeVideoSubtitle' }
+                          | { __typename: 'DataObjectTypeVideoThumbnail' }
+                          | null
+                      } | null
+                    }
                   }
                 }
                 previousNftOwner:
@@ -527,7 +599,33 @@ export type GetNotificationsConnectionQuery = {
                       | null
                   } | null
                 }
-                nft: { __typename?: 'OwnedNft'; video: { __typename?: 'Video'; id: string; title?: string | null } }
+                nft: {
+                  __typename?: 'OwnedNft'
+                  video: {
+                    __typename?: 'Video'
+                    id: string
+                    title?: string | null
+                    thumbnailPhoto?: {
+                      __typename?: 'StorageDataObject'
+                      id: string
+                      resolvedUrls: Array<string>
+                      resolvedUrl?: string | null
+                      createdAt: Date
+                      size: string
+                      isAccepted: boolean
+                      ipfsHash: string
+                      storageBag: { __typename?: 'StorageBag'; id: string }
+                      type?:
+                        | { __typename: 'DataObjectTypeChannelAvatar' }
+                        | { __typename: 'DataObjectTypeChannelCoverPhoto' }
+                        | { __typename: 'DataObjectTypeChannelPayoutsPayload' }
+                        | { __typename: 'DataObjectTypeVideoMedia' }
+                        | { __typename: 'DataObjectTypeVideoSubtitle' }
+                        | { __typename: 'DataObjectTypeVideoThumbnail' }
+                        | null
+                    } | null
+                  }
+                }
               }
             | { __typename?: 'NftIssuedEventData' }
             | { __typename?: 'NftSellOrderMadeEventData' }
@@ -570,9 +668,32 @@ export type GetNotificationsConnectionQuery = {
                         | null
                     } | null
                   }
-                  auction: {
-                    __typename?: 'Auction'
-                    nft: { __typename?: 'OwnedNft'; video: { __typename?: 'Video'; id: string; title?: string | null } }
+                  nft: {
+                    __typename?: 'OwnedNft'
+                    video: {
+                      __typename?: 'Video'
+                      id: string
+                      title?: string | null
+                      thumbnailPhoto?: {
+                        __typename?: 'StorageDataObject'
+                        id: string
+                        resolvedUrls: Array<string>
+                        resolvedUrl?: string | null
+                        createdAt: Date
+                        size: string
+                        isAccepted: boolean
+                        ipfsHash: string
+                        storageBag: { __typename?: 'StorageBag'; id: string }
+                        type?:
+                          | { __typename: 'DataObjectTypeChannelAvatar' }
+                          | { __typename: 'DataObjectTypeChannelCoverPhoto' }
+                          | { __typename: 'DataObjectTypeChannelPayoutsPayload' }
+                          | { __typename: 'DataObjectTypeVideoMedia' }
+                          | { __typename: 'DataObjectTypeVideoSubtitle' }
+                          | { __typename: 'DataObjectTypeVideoThumbnail' }
+                          | null
+                      } | null
+                    }
                   }
                 }
                 previousNftOwner:
@@ -1728,6 +1849,18 @@ export type GetNftHistoryQuery = {
   }>
 }
 
+export type GetNftActivitiesCountQueryVariables = Types.Exact<{
+  memberId: Types.Scalars['String']
+}>
+
+export type GetNftActivitiesCountQuery = {
+  __typename?: 'Query'
+  nftsBought: { __typename?: 'NftActivitiesConnection'; totalCount: number }
+  nftsSold: { __typename?: 'NftActivitiesConnection'; totalCount: number }
+  nftsIssued: { __typename?: 'NftActivitiesConnection'; totalCount: number }
+  nftsBidded: { __typename?: 'NftActivitiesConnection'; totalCount: number }
+}
+
 export type GetNftActivitiesQueryVariables = Types.Exact<{
   memberId: Types.Scalars['String']
   first: Types.Scalars['Int']
@@ -1737,10 +1870,6 @@ export type GetNftActivitiesQueryVariables = Types.Exact<{
 
 export type GetNftActivitiesQuery = {
   __typename?: 'Query'
-  nftsBought: { __typename?: 'NftActivitiesConnection'; totalCount: number }
-  nftsSold: { __typename?: 'NftActivitiesConnection'; totalCount: number }
-  nftsIssued: { __typename?: 'NftActivitiesConnection'; totalCount: number }
-  nftsBidded: { __typename?: 'NftActivitiesConnection'; totalCount: number }
   nftActivitiesConnection: {
     __typename?: 'NftActivitiesConnection'
     totalCount: number
@@ -1794,34 +1923,31 @@ export type GetNftActivitiesQuery = {
                 }
                 bid: {
                   __typename?: 'Bid'
-                  auction: {
-                    __typename?: 'Auction'
-                    nft: {
-                      __typename?: 'OwnedNft'
-                      video: {
-                        __typename?: 'Video'
+                  nft: {
+                    __typename?: 'OwnedNft'
+                    video: {
+                      __typename?: 'Video'
+                      id: string
+                      title?: string | null
+                      thumbnailPhoto?: {
+                        __typename?: 'StorageDataObject'
                         id: string
-                        title?: string | null
-                        thumbnailPhoto?: {
-                          __typename?: 'StorageDataObject'
-                          id: string
-                          resolvedUrls: Array<string>
-                          resolvedUrl?: string | null
-                          createdAt: Date
-                          size: string
-                          isAccepted: boolean
-                          ipfsHash: string
-                          storageBag: { __typename?: 'StorageBag'; id: string }
-                          type?:
-                            | { __typename: 'DataObjectTypeChannelAvatar' }
-                            | { __typename: 'DataObjectTypeChannelCoverPhoto' }
-                            | { __typename: 'DataObjectTypeChannelPayoutsPayload' }
-                            | { __typename: 'DataObjectTypeVideoMedia' }
-                            | { __typename: 'DataObjectTypeVideoSubtitle' }
-                            | { __typename: 'DataObjectTypeVideoThumbnail' }
-                            | null
-                        } | null
-                      }
+                        resolvedUrls: Array<string>
+                        resolvedUrl?: string | null
+                        createdAt: Date
+                        size: string
+                        isAccepted: boolean
+                        ipfsHash: string
+                        storageBag: { __typename?: 'StorageBag'; id: string }
+                        type?:
+                          | { __typename: 'DataObjectTypeChannelAvatar' }
+                          | { __typename: 'DataObjectTypeChannelCoverPhoto' }
+                          | { __typename: 'DataObjectTypeChannelPayoutsPayload' }
+                          | { __typename: 'DataObjectTypeVideoMedia' }
+                          | { __typename: 'DataObjectTypeVideoSubtitle' }
+                          | { __typename: 'DataObjectTypeVideoThumbnail' }
+                          | null
+                      } | null
                     }
                   }
                 }
@@ -1980,34 +2106,31 @@ export type GetNftActivitiesQuery = {
                       } | null
                     }
                   } | null
-                  auction: {
-                    __typename?: 'Auction'
-                    nft: {
-                      __typename?: 'OwnedNft'
-                      video: {
-                        __typename?: 'Video'
+                  nft: {
+                    __typename?: 'OwnedNft'
+                    video: {
+                      __typename?: 'Video'
+                      id: string
+                      title?: string | null
+                      thumbnailPhoto?: {
+                        __typename?: 'StorageDataObject'
                         id: string
-                        title?: string | null
-                        thumbnailPhoto?: {
-                          __typename?: 'StorageDataObject'
-                          id: string
-                          resolvedUrls: Array<string>
-                          resolvedUrl?: string | null
-                          createdAt: Date
-                          size: string
-                          isAccepted: boolean
-                          ipfsHash: string
-                          storageBag: { __typename?: 'StorageBag'; id: string }
-                          type?:
-                            | { __typename: 'DataObjectTypeChannelAvatar' }
-                            | { __typename: 'DataObjectTypeChannelCoverPhoto' }
-                            | { __typename: 'DataObjectTypeChannelPayoutsPayload' }
-                            | { __typename: 'DataObjectTypeVideoMedia' }
-                            | { __typename: 'DataObjectTypeVideoSubtitle' }
-                            | { __typename: 'DataObjectTypeVideoThumbnail' }
-                            | null
-                        } | null
-                      }
+                        resolvedUrls: Array<string>
+                        resolvedUrl?: string | null
+                        createdAt: Date
+                        size: string
+                        isAccepted: boolean
+                        ipfsHash: string
+                        storageBag: { __typename?: 'StorageBag'; id: string }
+                        type?:
+                          | { __typename: 'DataObjectTypeChannelAvatar' }
+                          | { __typename: 'DataObjectTypeChannelCoverPhoto' }
+                          | { __typename: 'DataObjectTypeChannelPayoutsPayload' }
+                          | { __typename: 'DataObjectTypeVideoMedia' }
+                          | { __typename: 'DataObjectTypeVideoSubtitle' }
+                          | { __typename: 'DataObjectTypeVideoThumbnail' }
+                          | null
+                      } | null
                     }
                   }
                 }
@@ -2240,34 +2363,31 @@ export type GetNftActivitiesQuery = {
                         | null
                     } | null
                   }
-                  auction: {
-                    __typename?: 'Auction'
-                    nft: {
-                      __typename?: 'OwnedNft'
-                      video: {
-                        __typename?: 'Video'
+                  nft: {
+                    __typename?: 'OwnedNft'
+                    video: {
+                      __typename?: 'Video'
+                      id: string
+                      title?: string | null
+                      thumbnailPhoto?: {
+                        __typename?: 'StorageDataObject'
                         id: string
-                        title?: string | null
-                        thumbnailPhoto?: {
-                          __typename?: 'StorageDataObject'
-                          id: string
-                          resolvedUrls: Array<string>
-                          resolvedUrl?: string | null
-                          createdAt: Date
-                          size: string
-                          isAccepted: boolean
-                          ipfsHash: string
-                          storageBag: { __typename?: 'StorageBag'; id: string }
-                          type?:
-                            | { __typename: 'DataObjectTypeChannelAvatar' }
-                            | { __typename: 'DataObjectTypeChannelCoverPhoto' }
-                            | { __typename: 'DataObjectTypeChannelPayoutsPayload' }
-                            | { __typename: 'DataObjectTypeVideoMedia' }
-                            | { __typename: 'DataObjectTypeVideoSubtitle' }
-                            | { __typename: 'DataObjectTypeVideoThumbnail' }
-                            | null
-                        } | null
-                      }
+                        resolvedUrls: Array<string>
+                        resolvedUrl?: string | null
+                        createdAt: Date
+                        size: string
+                        isAccepted: boolean
+                        ipfsHash: string
+                        storageBag: { __typename?: 'StorageBag'; id: string }
+                        type?:
+                          | { __typename: 'DataObjectTypeChannelAvatar' }
+                          | { __typename: 'DataObjectTypeChannelCoverPhoto' }
+                          | { __typename: 'DataObjectTypeChannelPayoutsPayload' }
+                          | { __typename: 'DataObjectTypeVideoMedia' }
+                          | { __typename: 'DataObjectTypeVideoSubtitle' }
+                          | { __typename: 'DataObjectTypeVideoThumbnail' }
+                          | null
+                      } | null
                     }
                   }
                 }
@@ -2613,34 +2733,31 @@ export type GetNftActivitiesQuery = {
                         | null
                     } | null
                   }
-                  auction: {
-                    __typename?: 'Auction'
-                    nft: {
-                      __typename?: 'OwnedNft'
-                      video: {
-                        __typename?: 'Video'
+                  nft: {
+                    __typename?: 'OwnedNft'
+                    video: {
+                      __typename?: 'Video'
+                      id: string
+                      title?: string | null
+                      thumbnailPhoto?: {
+                        __typename?: 'StorageDataObject'
                         id: string
-                        title?: string | null
-                        thumbnailPhoto?: {
-                          __typename?: 'StorageDataObject'
-                          id: string
-                          resolvedUrls: Array<string>
-                          resolvedUrl?: string | null
-                          createdAt: Date
-                          size: string
-                          isAccepted: boolean
-                          ipfsHash: string
-                          storageBag: { __typename?: 'StorageBag'; id: string }
-                          type?:
-                            | { __typename: 'DataObjectTypeChannelAvatar' }
-                            | { __typename: 'DataObjectTypeChannelCoverPhoto' }
-                            | { __typename: 'DataObjectTypeChannelPayoutsPayload' }
-                            | { __typename: 'DataObjectTypeVideoMedia' }
-                            | { __typename: 'DataObjectTypeVideoSubtitle' }
-                            | { __typename: 'DataObjectTypeVideoThumbnail' }
-                            | null
-                        } | null
-                      }
+                        resolvedUrls: Array<string>
+                        resolvedUrl?: string | null
+                        createdAt: Date
+                        size: string
+                        isAccepted: boolean
+                        ipfsHash: string
+                        storageBag: { __typename?: 'StorageBag'; id: string }
+                        type?:
+                          | { __typename: 'DataObjectTypeChannelAvatar' }
+                          | { __typename: 'DataObjectTypeChannelCoverPhoto' }
+                          | { __typename: 'DataObjectTypeChannelPayoutsPayload' }
+                          | { __typename: 'DataObjectTypeVideoMedia' }
+                          | { __typename: 'DataObjectTypeVideoSubtitle' }
+                          | { __typename: 'DataObjectTypeVideoThumbnail' }
+                          | null
+                      } | null
                     }
                   }
                 }
@@ -3157,34 +3274,31 @@ export type GetNftActivitiesQuery = {
                         | null
                     } | null
                   }
-                  auction: {
-                    __typename?: 'Auction'
-                    nft: {
-                      __typename?: 'OwnedNft'
-                      video: {
-                        __typename?: 'Video'
+                  nft: {
+                    __typename?: 'OwnedNft'
+                    video: {
+                      __typename?: 'Video'
+                      id: string
+                      title?: string | null
+                      thumbnailPhoto?: {
+                        __typename?: 'StorageDataObject'
                         id: string
-                        title?: string | null
-                        thumbnailPhoto?: {
-                          __typename?: 'StorageDataObject'
-                          id: string
-                          resolvedUrls: Array<string>
-                          resolvedUrl?: string | null
-                          createdAt: Date
-                          size: string
-                          isAccepted: boolean
-                          ipfsHash: string
-                          storageBag: { __typename?: 'StorageBag'; id: string }
-                          type?:
-                            | { __typename: 'DataObjectTypeChannelAvatar' }
-                            | { __typename: 'DataObjectTypeChannelCoverPhoto' }
-                            | { __typename: 'DataObjectTypeChannelPayoutsPayload' }
-                            | { __typename: 'DataObjectTypeVideoMedia' }
-                            | { __typename: 'DataObjectTypeVideoSubtitle' }
-                            | { __typename: 'DataObjectTypeVideoThumbnail' }
-                            | null
-                        } | null
-                      }
+                        resolvedUrls: Array<string>
+                        resolvedUrl?: string | null
+                        createdAt: Date
+                        size: string
+                        isAccepted: boolean
+                        ipfsHash: string
+                        storageBag: { __typename?: 'StorageBag'; id: string }
+                        type?:
+                          | { __typename: 'DataObjectTypeChannelAvatar' }
+                          | { __typename: 'DataObjectTypeChannelCoverPhoto' }
+                          | { __typename: 'DataObjectTypeChannelPayoutsPayload' }
+                          | { __typename: 'DataObjectTypeVideoMedia' }
+                          | { __typename: 'DataObjectTypeVideoSubtitle' }
+                          | { __typename: 'DataObjectTypeVideoThumbnail' }
+                          | null
+                      } | null
                     }
                   }
                 }
@@ -3416,12 +3530,9 @@ export const GetNotificationsConnectionDocument = gql`
                       ...BasicMembershipFields
                     }
                   }
-                  auction {
-                    nft {
-                      video {
-                        id
-                        title
-                      }
+                  nft {
+                    video {
+                      ...BasicVideoActivityFields
                     }
                   }
                 }
@@ -3436,8 +3547,7 @@ export const GetNotificationsConnectionDocument = gql`
                 price
                 nft {
                   video {
-                    id
-                    title
+                    ...BasicVideoActivityFields
                   }
                 }
               }
@@ -3449,8 +3559,7 @@ export const GetNotificationsConnectionDocument = gql`
                   amount
                   nft {
                     video {
-                      id
-                      title
+                      ...BasicVideoActivityFields
                     }
                   }
                 }
@@ -3464,12 +3573,9 @@ export const GetNotificationsConnectionDocument = gql`
                   bidder {
                     ...BasicMembershipFields
                   }
-                  auction {
-                    nft {
-                      video {
-                        id
-                        title
-                      }
+                  nft {
+                    video {
+                      ...BasicVideoActivityFields
                     }
                   }
                 }
@@ -3482,12 +3588,9 @@ export const GetNotificationsConnectionDocument = gql`
                   bidder {
                     ...BasicMembershipFields
                   }
-                  auction {
-                    nft {
-                      video {
-                        id
-                        title
-                      }
+                  nft {
+                    video {
+                      ...BasicVideoActivityFields
                     }
                   }
                 }
@@ -3517,6 +3620,7 @@ export const GetNotificationsConnectionDocument = gql`
     }
   }
   ${BasicMembershipFieldsFragmentDoc}
+  ${BasicVideoActivityFieldsFragmentDoc}
   ${BasicNftOwnerFieldsFragmentDoc}
 `
 
@@ -3696,18 +3800,14 @@ export function useGetNftHistoryLazyQuery(
 export type GetNftHistoryQueryHookResult = ReturnType<typeof useGetNftHistoryQuery>
 export type GetNftHistoryLazyQueryHookResult = ReturnType<typeof useGetNftHistoryLazyQuery>
 export type GetNftHistoryQueryResult = Apollo.QueryResult<GetNftHistoryQuery, GetNftHistoryQueryVariables>
-export const GetNftActivitiesDocument = gql`
-  query GetNftActivities(
-    $memberId: String!
-    $first: Int!
-    $after: String
-    $orderBy: [NftActivityOrderByInput!] = event_timestamp_DESC
-  ) {
+export const GetNftActivitiesCountDocument = gql`
+  query GetNftActivitiesCount($memberId: String!) {
     nftsBought: nftActivitiesConnection(
       where: {
-        event: {
-          OR: [
-            {
+        OR: [
+          {
+            member: { id_eq: $memberId }
+            event: {
               data: {
                 isTypeOf_in: [
                   "EnglishAuctionSettledEventData"
@@ -3717,19 +3817,23 @@ export const GetNftActivitiesDocument = gql`
                 winningBid: { bidder: { id_eq: $memberId } }
               }
             }
-            { data: { isTypeOf_eq: "NftBoughtEventData", buyer: { id_eq: $memberId } } }
-          ]
-        }
+          }
+          {
+            member: { id_eq: $memberId }
+            event: { data: { isTypeOf_eq: "NftBoughtEventData", buyer: { id_eq: $memberId } } }
+          }
+        ]
       }
-      orderBy: $orderBy
+      orderBy: event_timestamp_DESC
     ) {
       totalCount
     }
     nftsSold: nftActivitiesConnection(
       where: {
-        event: {
-          OR: [
-            {
+        OR: [
+          {
+            member: { id_eq: $memberId }
+            event: {
               data: {
                 isTypeOf_in: [
                   "EnglishAuctionSettledEventData"
@@ -3740,7 +3844,10 @@ export const GetNftActivitiesDocument = gql`
                 previousNftOwner: { member: { id_eq: $memberId } }
               }
             }
-            {
+          }
+          {
+            member: { id_eq: $memberId }
+            event: {
               data: {
                 isTypeOf_in: [
                   "EnglishAuctionSettledEventData"
@@ -3751,10 +3858,10 @@ export const GetNftActivitiesDocument = gql`
                 previousNftOwner: { channel: { ownerMember: { id_eq: $memberId } } }
               }
             }
-          ]
-        }
+          }
+        ]
       }
-      orderBy: $orderBy
+      orderBy: event_timestamp_DESC
     ) {
       totalCount
     }
@@ -3769,16 +3876,66 @@ export const GetNftActivitiesDocument = gql`
           ]
         }
       }
-      orderBy: $orderBy
+      orderBy: event_timestamp_DESC
     ) {
       totalCount
     }
     nftsBidded: nftActivitiesConnection(
       where: { event: { data: { isTypeOf_eq: "AuctionBidMadeEventData", bid: { bidder: { id_eq: $memberId } } } } }
-      orderBy: $orderBy
+      orderBy: event_timestamp_DESC
     ) {
       totalCount
     }
+  }
+`
+
+/**
+ * __useGetNftActivitiesCountQuery__
+ *
+ * To run a query within a React component, call `useGetNftActivitiesCountQuery` and pass it any options that fit your needs.
+ * When your component renders, `useGetNftActivitiesCountQuery` returns an object from Apollo Client that contains loading, error, and data properties
+ * you can use to render your UI.
+ *
+ * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
+ *
+ * @example
+ * const { data, loading, error } = useGetNftActivitiesCountQuery({
+ *   variables: {
+ *      memberId: // value for 'memberId'
+ *   },
+ * });
+ */
+export function useGetNftActivitiesCountQuery(
+  baseOptions: Apollo.QueryHookOptions<GetNftActivitiesCountQuery, GetNftActivitiesCountQueryVariables>
+) {
+  const options = { ...defaultOptions, ...baseOptions }
+  return Apollo.useQuery<GetNftActivitiesCountQuery, GetNftActivitiesCountQueryVariables>(
+    GetNftActivitiesCountDocument,
+    options
+  )
+}
+export function useGetNftActivitiesCountLazyQuery(
+  baseOptions?: Apollo.LazyQueryHookOptions<GetNftActivitiesCountQuery, GetNftActivitiesCountQueryVariables>
+) {
+  const options = { ...defaultOptions, ...baseOptions }
+  return Apollo.useLazyQuery<GetNftActivitiesCountQuery, GetNftActivitiesCountQueryVariables>(
+    GetNftActivitiesCountDocument,
+    options
+  )
+}
+export type GetNftActivitiesCountQueryHookResult = ReturnType<typeof useGetNftActivitiesCountQuery>
+export type GetNftActivitiesCountLazyQueryHookResult = ReturnType<typeof useGetNftActivitiesCountLazyQuery>
+export type GetNftActivitiesCountQueryResult = Apollo.QueryResult<
+  GetNftActivitiesCountQuery,
+  GetNftActivitiesCountQueryVariables
+>
+export const GetNftActivitiesDocument = gql`
+  query GetNftActivities(
+    $memberId: String!
+    $first: Int!
+    $after: String
+    $orderBy: [NftActivityOrderByInput!] = event_timestamp_DESC
+  ) {
     nftActivitiesConnection(first: $first, after: $after, orderBy: $orderBy, where: { member: { id_eq: $memberId } }) {
       totalCount
       pageInfo {
@@ -3807,11 +3964,9 @@ export const GetNftActivitiesDocument = gql`
                       ...BasicMembershipFields
                     }
                   }
-                  auction {
-                    nft {
-                      video {
-                        ...BasicVideoActivityFields
-                      }
+                  nft {
+                    video {
+                      ...BasicVideoActivityFields
                     }
                   }
                 }
@@ -3825,11 +3980,9 @@ export const GetNftActivitiesDocument = gql`
                     ...BasicMembershipFields
                   }
                   amount
-                  auction {
-                    nft {
-                      video {
-                        ...BasicVideoActivityFields
-                      }
+                  nft {
+                    video {
+                      ...BasicVideoActivityFields
                     }
                   }
                 }
@@ -3856,11 +4009,9 @@ export const GetNftActivitiesDocument = gql`
                   bidder {
                     ...BasicMembershipFields
                   }
-                  auction {
-                    nft {
-                      video {
-                        ...BasicVideoActivityFields
-                      }
+                  nft {
+                    video {
+                      ...BasicVideoActivityFields
                     }
                   }
                   amount
@@ -3872,11 +4023,9 @@ export const GetNftActivitiesDocument = gql`
                   bidder {
                     ...BasicMembershipFields
                   }
-                  auction {
-                    nft {
-                      video {
-                        ...BasicVideoActivityFields
-                      }
+                  nft {
+                    video {
+                      ...BasicVideoActivityFields
                     }
                   }
                 }
@@ -3924,11 +4073,9 @@ export const GetNftActivitiesDocument = gql`
                   ...BasicMembershipFields
                 }
                 bid {
-                  auction {
-                    nft {
-                      video {
-                        ...BasicVideoActivityFields
-                      }
+                  nft {
+                    video {
+                      ...BasicVideoActivityFields
                     }
                   }
                 }

+ 61 - 85
packages/atlas/src/api/queries/notifications.graphql

@@ -1,11 +1,3 @@
-# CHANGE: Throught this file we're now using one `events` query to receive all related events
-# (instead of separate queries for each event type)
-
-# Some redundant fields (result of schema "flattening") were also removed from the events,
-# as they can now be accessed though deep filtering.
-
-# Note that in this case `orderBy` and `limit` now applies to all events together, not on per-type basis
-
 query GetNotificationsConnection($memberId: String!, $first: Int!, $after: String) {
   notificationsConnection(
     first: $first
@@ -37,12 +29,9 @@ query GetNotificationsConnection($memberId: String!, $first: Int!, $after: Strin
                     ...BasicMembershipFields
                   }
                 }
-                auction {
-                  nft {
-                    video {
-                      id
-                      title
-                    }
+                nft {
+                  video {
+                    ...BasicVideoActivityFields
                   }
                 }
               }
@@ -58,8 +47,7 @@ query GetNotificationsConnection($memberId: String!, $first: Int!, $after: Strin
               price
               nft {
                 video {
-                  id
-                  title
+                  ...BasicVideoActivityFields
                 }
               }
             }
@@ -72,8 +60,7 @@ query GetNotificationsConnection($memberId: String!, $first: Int!, $after: Strin
                 amount
                 nft {
                   video {
-                    id
-                    title
+                    ...BasicVideoActivityFields
                   }
                 }
               }
@@ -88,12 +75,9 @@ query GetNotificationsConnection($memberId: String!, $first: Int!, $after: Strin
                 bidder {
                   ...BasicMembershipFields
                 }
-                auction {
-                  nft {
-                    video {
-                      id
-                      title
-                    }
+                nft {
+                  video {
+                    ...BasicVideoActivityFields
                   }
                 }
               }
@@ -107,12 +91,9 @@ query GetNotificationsConnection($memberId: String!, $first: Int!, $after: Strin
                 bidder {
                   ...BasicMembershipFields
                 }
-                auction {
-                  nft {
-                    video {
-                      id
-                      title
-                    }
+                nft {
+                  video {
+                    ...BasicVideoActivityFields
                   }
                 }
               }
@@ -254,17 +235,13 @@ query GetNftHistory($nftId: String!) {
   }
 }
 
-query GetNftActivities(
-  $memberId: String!
-  $first: Int!
-  $after: String
-  $orderBy: [NftActivityOrderByInput!] = event_timestamp_DESC
-) {
+query GetNftActivitiesCount($memberId: String!) {
   nftsBought: nftActivitiesConnection(
     where: {
-      event: {
-        OR: [
-          {
+      OR: [
+        {
+          member: { id_eq: $memberId }
+          event: {
             data: {
               isTypeOf_in: [
                 "EnglishAuctionSettledEventData"
@@ -274,20 +251,24 @@ query GetNftActivities(
               winningBid: { bidder: { id_eq: $memberId } }
             }
           }
-          { data: { isTypeOf_eq: "NftBoughtEventData", buyer: { id_eq: $memberId } } }
-        ]
-      }
+        }
+        {
+          member: { id_eq: $memberId }
+          event: { data: { isTypeOf_eq: "NftBoughtEventData", buyer: { id_eq: $memberId } } }
+        }
+      ]
     }
-    orderBy: $orderBy
+    orderBy: event_timestamp_DESC
   ) {
     totalCount
   }
 
   nftsSold: nftActivitiesConnection(
     where: {
-      event: {
-        OR: [
-          {
+      OR: [
+        {
+          member: { id_eq: $memberId }
+          event: {
             data: {
               isTypeOf_in: [
                 "EnglishAuctionSettledEventData"
@@ -298,7 +279,10 @@ query GetNftActivities(
               previousNftOwner: { member: { id_eq: $memberId } }
             }
           }
-          {
+        }
+        {
+          member: { id_eq: $memberId }
+          event: {
             data: {
               isTypeOf_in: [
                 "EnglishAuctionSettledEventData"
@@ -309,10 +293,10 @@ query GetNftActivities(
               previousNftOwner: { channel: { ownerMember: { id_eq: $memberId } } }
             }
           }
-        ]
-      }
+        }
+      ]
     }
-    orderBy: $orderBy
+    orderBy: event_timestamp_DESC
   ) {
     totalCount
   }
@@ -326,25 +310,26 @@ query GetNftActivities(
         ]
       }
     }
-    orderBy: $orderBy
+    orderBy: event_timestamp_DESC
   ) {
     totalCount
   }
 
   nftsBidded: nftActivitiesConnection(
     where: { event: { data: { isTypeOf_eq: "AuctionBidMadeEventData", bid: { bidder: { id_eq: $memberId } } } } }
-    orderBy: $orderBy
+    orderBy: event_timestamp_DESC
   ) {
     totalCount
   }
+}
 
-  nftActivitiesConnection(
-    first: $first
-    after: $after
-    orderBy: $orderBy # CHANGE: `event_timestamp` now used instead of `createdAt` (which is no longer available)
-    where: { member: { id_eq: $memberId } } # CHANGE: Simplified filtering
-  ) {
-    # CHANGE: The actual `event` is now nested as a property of `NftActivity`
+query GetNftActivities(
+  $memberId: String!
+  $first: Int!
+  $after: String
+  $orderBy: [NftActivityOrderByInput!] = event_timestamp_DESC
+) {
+  nftActivitiesConnection(first: $first, after: $after, orderBy: $orderBy, where: { member: { id_eq: $memberId } }) {
     totalCount
     pageInfo {
       endCursor
@@ -355,7 +340,7 @@ query GetNftActivities(
       node {
         event {
           id
-          timestamp # CHANGE: `timestamp` now used instead of `createdAt` (which is no longer available)
+          timestamp
           inBlock
           data {
             ... on AuctionBidMadeEventData {
@@ -372,11 +357,9 @@ query GetNftActivities(
                     ...BasicMembershipFields
                   }
                 }
-                auction {
-                  nft {
-                    video {
-                      ...BasicVideoActivityFields
-                    }
+                nft {
+                  video {
+                    ...BasicVideoActivityFields
                   }
                 }
               }
@@ -390,11 +373,9 @@ query GetNftActivities(
                   ...BasicMembershipFields
                 }
                 amount
-                auction {
-                  nft {
-                    video {
-                      ...BasicVideoActivityFields
-                    }
+                nft {
+                  video {
+                    ...BasicVideoActivityFields
                   }
                 }
               }
@@ -421,11 +402,10 @@ query GetNftActivities(
                 bidder {
                   ...BasicMembershipFields
                 }
-                auction {
-                  nft {
-                    video {
-                      ...BasicVideoActivityFields
-                    }
+
+                nft {
+                  video {
+                    ...BasicVideoActivityFields
                   }
                 }
                 amount
@@ -437,11 +417,9 @@ query GetNftActivities(
                 bidder {
                   ...BasicMembershipFields
                 }
-                auction {
-                  nft {
-                    video {
-                      ...BasicVideoActivityFields
-                    }
+                nft {
+                  video {
+                    ...BasicVideoActivityFields
                   }
                 }
               }
@@ -489,11 +467,9 @@ query GetNftActivities(
                 ...BasicMembershipFields
               }
               bid {
-                auction {
-                  nft {
-                    video {
-                      ...BasicVideoActivityFields
-                    }
+                nft {
+                  video {
+                    ...BasicVideoActivityFields
                   }
                 }
               }

+ 3 - 0
packages/atlas/src/components/_nft/NftTile/NftTile.tsx

@@ -16,6 +16,7 @@ import { Member, NftTileDetails } from './NftTileDetails'
 
 export type NftTileProps = {
   status?: 'idle' | 'buy-now' | 'auction'
+  isInCarousel?: boolean
   thumbnail?: VideoThumbnailProps
   title?: string | null
   owner?: Member
@@ -38,6 +39,7 @@ export type NftTileProps = {
 
 export const NftTile: FC<NftTileProps> = ({
   status,
+  isInCarousel,
   thumbnail,
   loading,
   title,
@@ -99,6 +101,7 @@ export const NftTile: FC<NftTileProps> = ({
         }}
       />
       <NftTileDetails
+        isInCarousel={isInCarousel}
         videoHref={thumbnail?.videoHref as string}
         hovered={hovered}
         owner={owner}

+ 190 - 155
packages/atlas/src/components/_nft/NftTile/NftTileDetails.tsx

@@ -1,4 +1,4 @@
-import { FC, ReactElement, ReactNode, memo, useMemo, useState } from 'react'
+import { FC, ReactElement, ReactNode, memo, useEffect, useId, useMemo, useRef, useState } from 'react'
 import useResizeObserver from 'use-resize-observer'
 
 import { SvgActionMore, SvgActionNotForSale } from '@/assets/icons'
@@ -8,6 +8,8 @@ import { NumberFormat } from '@/components/NumberFormat'
 import { Text } from '@/components/Text'
 import { SkeletonLoader } from '@/components/_loaders/SkeletonLoader'
 import { ContextMenu } from '@/components/_overlays/ContextMenu'
+import { PopoverImperativeHandle } from '@/components/_overlays/Popover'
+import { useMiscStore } from '@/providers/misc/store'
 import { cVar } from '@/styles'
 
 import {
@@ -30,6 +32,7 @@ export type Member = {
 
 export type NftTileDetailsProps = {
   loading?: boolean
+  isInCarousel?: boolean
   owner?: Member
   creator?: Member
   role?: 'owner' | 'viewer'
@@ -48,169 +51,201 @@ type TileSize = 'small' | 'medium' | 'big' | 'bigSmall'
 
 const SMALL_SIZE_WIDTH = 288
 
-export const NftTileDetails: FC<NftTileDetailsProps> = ({
-  loading,
-  creator,
-  owner,
-  nftStatus,
-  startingPrice,
-  buyNowPrice,
-  topBid,
-  title,
-  hovered,
-  videoHref,
-  interactable = true,
-  contextMenuItems,
-}) => {
-  const [contentHovered, setContentHovered] = useState(false)
-  const toggleContentHover = () => setContentHovered((prevState) => !prevState)
-  const [tileSize, setTileSize] = useState<TileSize>()
-  const { ref: contentRef } = useResizeObserver<HTMLAnchorElement>({
-    box: 'border-box',
-    onResize: (size) => {
-      const { width } = size
-      if (width) {
-        if (tileSize !== 'small' && width < SMALL_SIZE_WIDTH) {
-          setTileSize('small')
-        }
-        if (tileSize !== 'medium' && width >= SMALL_SIZE_WIDTH) {
-          setTileSize('medium')
+export const NftTileDetails: FC<NftTileDetailsProps> = memo(
+  ({
+    loading,
+    isInCarousel,
+    creator,
+    owner,
+    nftStatus,
+    startingPrice,
+    buyNowPrice,
+    topBid,
+    title,
+    hovered,
+    videoHref,
+    interactable = true,
+    contextMenuItems,
+  }) => {
+    const [contentHovered, setContentHovered] = useState(false)
+    const setOpenedContextMenuId = useMiscStore((state) => state.actions.setOpenedContextMenuId)
+    const openedContexMenuId = useMiscStore((state) => state.openedContexMenuId)
+    const toggleContentHover = () => setContentHovered((prevState) => !prevState)
+    const [tileSize, setTileSize] = useState<TileSize>()
+    const { ref: contentRef } = useResizeObserver<HTMLAnchorElement>({
+      box: 'border-box',
+      onResize: (size) => {
+        const { width } = size
+        if (width) {
+          if (tileSize !== 'small' && width < SMALL_SIZE_WIDTH) {
+            setTileSize('small')
+          }
+          if (tileSize !== 'medium' && width >= SMALL_SIZE_WIDTH) {
+            setTileSize('medium')
+          }
         }
+      },
+    })
+    const id = useId()
+    const ref = useRef<HTMLButtonElement>(null)
+    const contextMenuInstanceRef = useRef<PopoverImperativeHandle>(null)
+
+    // This useEffect is called only inside carousel and it's a workaround fix for https://github.com/Joystream/atlas/issues/4239
+    // We need manually remove all popovers, because tippy is not working well with swiper carousel
+    useEffect(() => {
+      if (!openedContexMenuId || !isInCarousel) {
+        return
       }
-    },
-  })
+      if (openedContexMenuId !== id) {
+        contextMenuInstanceRef.current?.hide()
+      }
+    }, [id, isInCarousel, openedContexMenuId])
 
-  const getDetails = useMemo(() => {
-    if (loading) {
-      return (
-        <CaptionSkeletonWrapper>
-          <SkeletonLoader width="17%" height={tileSize === 'medium' ? 20 : 16} bottomSpace={4} />
-          <SkeletonLoader width="28%" height={tileSize === 'medium' ? 24 : 20} />
-        </CaptionSkeletonWrapper>
-      )
-    }
-    switch (nftStatus) {
-      case 'idle':
+    const getDetails = useMemo(() => {
+      if (loading) {
         return (
-          <DetailsContent
-            tileSize={tileSize}
-            caption="Status"
-            content="Not for sale"
-            icon={<SvgActionNotForSale />}
-            secondary
-          />
+          <CaptionSkeletonWrapper>
+            <SkeletonLoader width="17%" height={tileSize === 'medium' ? 20 : 16} bottomSpace={4} />
+            <SkeletonLoader width="28%" height={tileSize === 'medium' ? 24 : 20} />
+          </CaptionSkeletonWrapper>
         )
-      case 'buy-now':
-        return (
-          <DetailsContent
-            tileSize={tileSize}
-            caption="Buy now"
-            content={buyNowPrice ?? 0}
-            icon={<JoyTokenIcon size={16} variant="regular" />}
+      }
+      switch (nftStatus) {
+        case 'idle':
+          return (
+            <DetailsContent
+              tileSize={tileSize}
+              caption="Status"
+              content="Not for sale"
+              icon={<SvgActionNotForSale />}
+              secondary
+            />
+          )
+        case 'buy-now':
+          return (
+            <DetailsContent
+              tileSize={tileSize}
+              caption="Buy now"
+              content={buyNowPrice ?? 0}
+              icon={<JoyTokenIcon size={16} variant="regular" />}
+            />
+          )
+        case 'auction':
+          return (
+            <>
+              {topBid ? (
+                <DetailsContent
+                  tileSize={tileSize}
+                  caption="Top bid"
+                  content={topBid}
+                  icon={<JoyTokenIcon size={16} variant="regular" />}
+                />
+              ) : (
+                <DetailsContent
+                  tileSize={tileSize}
+                  caption="Min bid"
+                  content={startingPrice ?? 0}
+                  icon={<JoyTokenIcon size={16} variant="regular" />}
+                />
+              )}
+              {!!buyNowPrice && (
+                <DetailsContent
+                  tileSize={tileSize}
+                  caption="Buy now"
+                  content={buyNowPrice}
+                  icon={<JoyTokenIcon size={16} variant="regular" />}
+                />
+              )}
+            </>
+          )
+      }
+    }, [loading, nftStatus, tileSize, buyNowPrice, topBid, startingPrice])
+
+    const avatars = useMemo(
+      () => [
+        {
+          url: creator?.assetUrl,
+          tooltipText: `Creator: ${creator?.name}`,
+          onClick: creator?.onClick,
+          loading: creator?.loading,
+        },
+        ...(owner
+          ? [
+              {
+                url: owner?.assetUrl,
+                tooltipText: `Owner: ${owner?.name}`,
+                onClick: owner?.onClick,
+                loading: owner.loading,
+              },
+            ]
+          : []),
+      ],
+      [creator?.assetUrl, creator?.loading, creator?.name, creator?.onClick, owner]
+    )
+
+    return (
+      <Content
+        to={videoHref || ''}
+        ref={contentRef}
+        loading={loading}
+        onMouseEnter={toggleContentHover}
+        onMouseLeave={toggleContentHover}
+        tileSize={tileSize}
+        shouldHover={(contentHovered || hovered) && interactable}
+      >
+        <Header>
+          <StyledAvatarGroup
+            avatarStrokeColor={
+              (contentHovered || hovered) && interactable
+                ? cVar('colorBackground', true)
+                : cVar('colorBackgroundMuted', true)
+            }
+            loading={loading}
+            avatars={avatars}
           />
-        )
-      case 'auction':
-        return (
-          <>
-            {topBid ? (
-              <DetailsContent
-                tileSize={tileSize}
-                caption="Top bid"
-                content={topBid}
-                icon={<JoyTokenIcon size={16} variant="regular" />}
+          {contextMenuItems && (
+            <div>
+              <KebabMenuButtonIcon
+                ref={ref}
+                icon={<SvgActionMore />}
+                variant="tertiary"
+                size="small"
+                isActive={!loading}
+                onClick={(e) => {
+                  e.stopPropagation()
+                  e.preventDefault()
+                }}
               />
-            ) : (
-              <DetailsContent
-                tileSize={tileSize}
-                caption="Min bid"
-                content={startingPrice ?? 0}
-                icon={<JoyTokenIcon size={16} variant="regular" />}
+              <ContextMenu
+                ref={contextMenuInstanceRef}
+                appendTo={document.body}
+                placement="bottom-end"
+                flipEnabled={false}
+                disabled={loading}
+                onShow={() => {
+                  setOpenedContextMenuId(id)
+                }}
+                items={contextMenuItems}
+                trigger={null}
+                triggerTarget={ref.current}
               />
-            )}
-            {!!buyNowPrice && (
-              <DetailsContent
-                tileSize={tileSize}
-                caption="Buy now"
-                content={buyNowPrice}
-                icon={<JoyTokenIcon size={16} variant="regular" />}
-              />
-            )}
-          </>
-        )
-    }
-  }, [loading, nftStatus, tileSize, buyNowPrice, topBid, startingPrice])
-
-  const avatars = useMemo(
-    () => [
-      {
-        url: creator?.assetUrl,
-        tooltipText: `Creator: ${creator?.name}`,
-        onClick: creator?.onClick,
-        loading: creator?.loading,
-      },
-      ...(owner
-        ? [
-            {
-              url: owner?.assetUrl,
-              tooltipText: `Owner: ${owner?.name}`,
-              onClick: owner?.onClick,
-              loading: owner.loading,
-            },
-          ]
-        : []),
-    ],
-    [creator?.assetUrl, creator?.loading, creator?.name, creator?.onClick, owner]
-  )
-
-  return (
-    <Content
-      to={videoHref || ''}
-      ref={contentRef}
-      loading={loading}
-      onMouseEnter={toggleContentHover}
-      onMouseLeave={toggleContentHover}
-      tileSize={tileSize}
-      shouldHover={(contentHovered || hovered) && interactable}
-    >
-      <Header>
-        <StyledAvatarGroup
-          avatarStrokeColor={
-            (contentHovered || hovered) && interactable
-              ? cVar('colorBackground', true)
-              : cVar('colorBackgroundMuted', true)
-          }
-          loading={loading}
-          avatars={avatars}
-        />
-        {contextMenuItems && (
-          <div
-            onClick={(e) => {
-              e.stopPropagation()
-              e.preventDefault()
-            }}
-          >
-            <ContextMenu
-              placement="bottom-end"
-              disabled={loading}
-              items={contextMenuItems}
-              trigger={
-                <KebabMenuButtonIcon icon={<SvgActionMore />} variant="tertiary" size="small" isActive={!loading} />
-              }
-            />
-          </div>
+            </div>
+          )}
+        </Header>
+        {loading ? (
+          <SkeletonLoader width="55.6%" height={24} />
+        ) : (
+          <Title as="h3" variant={tileSize === 'medium' ? 'h400' : 'h300'}>
+            {title}
+          </Title>
         )}
-      </Header>
-      {loading ? (
-        <SkeletonLoader width="55.6%" height={24} />
-      ) : (
-        <Title as="h3" variant={tileSize === 'medium' ? 'h400' : 'h300'}>
-          {title}
-        </Title>
-      )}
-      <Details>{getDetails}</Details>
-    </Content>
-  )
-}
+        <Details>{getDetails}</Details>
+      </Content>
+    )
+  }
+)
+
+NftTileDetails.displayName = 'NftTileDetails'
 
 type DetailsContentProps = {
   caption: string

+ 3 - 1
packages/atlas/src/components/_nft/NftTileViewer/NftTileViewer.tsx

@@ -13,9 +13,10 @@ import { NftTile, NftTileProps } from '../NftTile'
 
 type NftTileViewerProps = {
   nftId?: string
+  isInCarousel?: boolean
 }
 
-export const NftTileViewer: FC<NftTileViewerProps> = ({ nftId }) => {
+export const NftTileViewer: FC<NftTileViewerProps> = ({ nftId, isInCarousel }) => {
   const { nftStatus, nft, loading } = useNft(nftId || '')
   const navigate = useNavigate()
   const thumbnailUrl = nft?.video.thumbnailPhoto?.resolvedUrl
@@ -100,6 +101,7 @@ export const NftTileViewer: FC<NftTileViewerProps> = ({ nftId }) => {
   return (
     <NftTile
       {...nftCommonProps}
+      isInCarousel={isInCarousel}
       timerLoading={timerLoading}
       buyNowPrice={
         nftStatus?.status === 'auction' || nftStatus?.status === 'buy-now' ? nftStatus.buyNowPrice : undefined

+ 23 - 25
packages/atlas/src/components/_overlays/ContextMenu/ContextMenu.tsx

@@ -1,5 +1,6 @@
 import styled from '@emotion/styled'
-import { FC, useRef } from 'react'
+import { forwardRef, useRef } from 'react'
+import { mergeRefs } from 'react-merge-refs'
 
 import { List } from '@/components/List'
 import { ListItemProps, ListItemSizes } from '@/components/ListItem'
@@ -12,31 +13,28 @@ export type ContextMenuProps = {
   size?: ListItemSizes
 } & Omit<PopoverProps, 'content' | 'instanceRef'>
 
-export const ContextMenu: FC<ContextMenuProps> = ({
-  children,
-  items,
-  scrollable = false,
-  size = 'medium',
-  ...rest
-}) => {
-  const contextMenuInstanceRef = useRef<PopoverImperativeHandle>(null)
-  return (
-    <Popover hideOnClick ref={contextMenuInstanceRef} {...rest}>
-      <StyledList
-        scrollable={scrollable}
-        size={size}
-        items={items.map((item) => ({
-          ...item,
-          onClick: (e) => {
-            item.onClick?.(e)
-            contextMenuInstanceRef.current?.hide()
-          },
-        }))}
-      />
-    </Popover>
-  )
-}
+export const ContextMenu = forwardRef<PopoverImperativeHandle, ContextMenuProps>(
+  ({ children, items, scrollable = false, size = 'medium', ...rest }, ref) => {
+    const contextMenuInstanceRef = useRef<PopoverImperativeHandle>(null)
+    return (
+      <Popover hideOnClick ref={mergeRefs([contextMenuInstanceRef, ref])} {...rest}>
+        <StyledList
+          scrollable={scrollable}
+          size={size}
+          items={items.map((item) => ({
+            ...item,
+            onClick: (e) => {
+              item.onClick?.(e)
+              contextMenuInstanceRef.current?.hide()
+            },
+          }))}
+        />
+      </Popover>
+    )
+  }
+)
 
+ContextMenu.displayName = 'ContextMenu'
 export const StyledList = styled(List)`
   width: 192px;
 `

+ 8 - 5
packages/atlas/src/components/_overlays/Popover/Popover.tsx

@@ -21,7 +21,7 @@ export type PopoverProps = PropsWithChildren<{
   className?: string
   appendTo?: Element | 'parent' | ((ref: Element) => Element) | undefined
   onHide?: () => void
-  onShow?: () => void
+  onShow?: (instance?: Instance) => void
   disabled?: boolean
   flipEnabled?: boolean
   animation?: boolean
@@ -103,7 +103,7 @@ const _Popover: ForwardRefRenderFunction<PopoverImperativeHandle | undefined, Po
       onTrigger={onTrigger}
       onShow={(instance) => {
         onTrigger(instance)
-        onShow?.()
+        onShow?.(instance)
       }}
       onHide={(instance) => {
         const box = instance.popper?.firstElementChild
@@ -140,13 +140,16 @@ const _Popover: ForwardRefRenderFunction<PopoverImperativeHandle | undefined, Po
       placement={placement}
       offset={offset}
     >
-      <TriggerContainer tabIndex={1}>{trigger}</TriggerContainer>
+      <TriggerContainer tabIndex={1} isTrigger={!!trigger}>
+        {trigger}
+      </TriggerContainer>
     </Tippy>
   )
 }
 
-const TriggerContainer = styled.div`
-  height: max-content;
+const TriggerContainer = styled.div<{ isTrigger: boolean }>`
+  /* if we use triggerElement, don't set height */
+  height: ${({ isTrigger }) => (isTrigger ? 'max-content' : 'unset')};
 `
 
 const ContentContainer = styled.div<{ animation?: boolean }>`

+ 5 - 1
packages/atlas/src/config/routes.ts

@@ -29,7 +29,8 @@ export const relativeRoutes = {
     channels: () => 'channels',
     video: (id = ':id', query?: { [QUERY_PARAMS.COMMENT_ID]?: string }) => withQueryParameters(`video/${id}`, query),
     editMembership: () => 'member/edit',
-    member: (handle = ':handle') => `member/${handle}`,
+    member: (handle = ':handle', query?: { [QUERY_PARAMS.TAB]?: MemberTabs }) =>
+      withQueryParameters(`member/${handle}`, query),
     notifications: () => 'notifications',
     marketplace: () => 'marketplace',
     ypp: (query?: { [QUERY_PARAMS.REFERRER_ID]?: string }) => withQueryParameters('ypp', query),
@@ -81,8 +82,11 @@ export const absoluteRoutes = Object.entries(BASE_PATHS).reduce((absoluteRoutesA
   return absoluteRoutesAcc
 }, {} as typeof relativeRoutes)
 
+export type MemberTabs = 'NFTs owned' | 'Activity' | 'About'
+
 export const QUERY_PARAMS = {
   SEARCH: 'query',
   COMMENT_ID: 'commentId',
   REFERRER_ID: 'referrerId',
+  TAB: 'tab',
 } as const

+ 22 - 0
packages/atlas/src/providers/misc/store.ts

@@ -0,0 +1,22 @@
+import { createStore } from '@/utils/store'
+
+export type MiscStoreState = {
+  openedContexMenuId?: string
+}
+
+type MiscStoreActions = {
+  setOpenedContextMenuId: (id: string) => void
+}
+
+export const useMiscStore = createStore<MiscStoreState, MiscStoreActions>({
+  state: {
+    openedContexMenuId: '',
+  },
+  actionsFactory: (set) => ({
+    setOpenedContextMenuId: (id) => {
+      set((state) => {
+        state.openedContexMenuId = id
+      })
+    },
+  }),
+})

+ 3 - 4
packages/atlas/src/providers/notifications/notifications.hooks.ts

@@ -18,7 +18,7 @@ export const useNotifications = (
   opts?: QueryHookOptions<GetNotificationsConnectionQuery, GetNotificationsConnectionQueryVariables>
 ) => {
   const { memberId } = useUser()
-  const { notifications: rawNotifications, ...rest } = useRawNotifications(memberId, opts)
+  const { notifications: rawNotifications, ...rest } = useRawNotifications('111', opts)
   const {
     readNotificationsIdsMap,
     lastSeenNotificationBlock,
@@ -49,12 +49,11 @@ const getVideoDataFromEvent = ({
 }: GetNotificationsConnectionQuery['notificationsConnection']['edges'][number]) => {
   switch (notification.event.data.__typename) {
     case 'AuctionBidMadeEventData':
-      return notification.event.data.bid.auction.nft.video
+      return notification.event.data.bid.nft.video
     case 'BidMadeCompletingAuctionEventData':
-      return notification.event.data.winningBid.nft.video
     case 'EnglishAuctionSettledEventData':
     case 'OpenAuctionBidAcceptedEventData':
-      return notification.event.data.winningBid.auction.nft.video
+      return notification.event.data.winningBid.nft.video
     case 'CommentCreatedEventData':
       return notification.event.data.comment.video
     case 'NftBoughtEventData':

+ 1 - 1
packages/atlas/src/views/viewer/MarketplaceView/FeaturedNftsSection/FeaturedNftsSection.tsx

@@ -147,7 +147,7 @@ export const FeaturedNftsSection: FC = () => {
             }}
             contentProps={{
               type: 'carousel',
-              children: items.map((nft, idx) => <NftTileViewer nftId={nft.id} key={idx} />),
+              children: items.map((nft, idx) => <NftTileViewer isInCarousel nftId={nft.id} key={idx} />),
               spaceBetween: mdMatch ? 24 : 16,
               breakpoints: responsive,
             }}

+ 4 - 4
packages/atlas/src/views/viewer/MemberView/ActivityItem.tsx

@@ -36,7 +36,7 @@ export const ActivityItem: FC<ActivityItemProps> = ({
   type,
   title,
   description,
-  thumbnailUri: thumnailUri,
+  thumbnailUri,
   thumbnailLoading,
   loading,
   onItemClick,
@@ -47,11 +47,11 @@ export const ActivityItem: FC<ActivityItemProps> = ({
 
   useEffect(() => {
     const validateImg = async () => {
-      const res = await imageUrlValidation(thumnailUri)
+      const res = await imageUrlValidation(thumbnailUri)
       setThumbnailLoaded(res)
     }
     validateImg()
-  }, [thumnailUri])
+  }, [thumbnailUri])
 
   const getTitleTextVariant = () => {
     if (lgMatch) {
@@ -66,7 +66,7 @@ export const ActivityItem: FC<ActivityItemProps> = ({
   const isImageLoading = loading || thumbnailLoading || !thumbnailLoaded
   return (
     <ActivityItemContainer loading={loading} onClick={onItemClick}>
-      {isImageLoading ? <ThumbnailSkeletonLoader /> : <Thumbnail src={thumnailUri} />}
+      {isImageLoading ? <ThumbnailSkeletonLoader /> : <Thumbnail src={thumbnailUri} />}
       <TitleAndDescriptionContainer>
         {loading ? (
           <TitleSkeletonLoader />

+ 8 - 12
packages/atlas/src/views/viewer/MemberView/MemberActivity.hooks.ts

@@ -2,7 +2,7 @@ import { QueryHookOptions } from '@apollo/client'
 import BN from 'bn.js'
 import { useMemo } from 'react'
 
-import { useRawActivities } from '@/api/hooks/notifications'
+import { useActivitiesCount, useRawActivities } from '@/api/hooks/notifications'
 import { NftActivityOrderByInput } from '@/api/queries/__generated__/baseTypes.generated'
 import {
   BasicMembershipFieldsFragment,
@@ -74,11 +74,11 @@ const getVideoDataFromEvent = (
   switch (nftActivity.event.data.__typename) {
     case 'AuctionBidMadeEventData':
     case 'AuctionBidCanceledEventData':
-      return nftActivity.event.data.bid.auction.nft.video
-    case 'EnglishAuctionSettledEventData':
+      return nftActivity.event.data.bid.nft.video
     case 'BidMadeCompletingAuctionEventData':
+    case 'EnglishAuctionSettledEventData':
     case 'OpenAuctionBidAcceptedEventData':
-      return nftActivity.event.data.winningBid.auction.nft.video
+      return nftActivity.event.data.winningBid.nft.video
     case 'NftBoughtEventData':
     case 'NftSellOrderMadeEventData':
     case 'BuyNowCanceledEventData':
@@ -245,14 +245,10 @@ export const useActivities = (
   sort?: NftActivityOrderByInput,
   opts?: QueryHookOptions<GetNftActivitiesQuery, GetNftActivitiesQueryVariables>
 ) => {
-  const {
-    activities: rawActivities,
-    nftsBiddedTotalCount,
-    nftsIssuedTotalCount,
-    nftsSoldTotalCount,
-    nftsBoughtTotalCount,
-    ...rest
-  } = useRawActivities(memberId, sort, opts)
+  const { activities: rawActivities, ...rest } = useRawActivities(memberId, sort, opts)
+
+  const { nftsBiddedTotalCount, nftsBoughtTotalCount, nftsIssuedTotalCount, nftsSoldTotalCount } =
+    useActivitiesCount(memberId)
   const parsedActivities = rawActivities && rawActivities.map((a) => parseActivities(a, memberId))
   const activities = parsedActivities ? parsedActivities.filter((a): a is ActivitiesRecord => !!a) : undefined
 

+ 68 - 9
packages/atlas/src/views/viewer/MemberView/MemberActivity.tsx

@@ -39,14 +39,26 @@ const getDescription = (activity: ActivitiesRecord) => {
     case 'Bid':
       return (
         <>
-          {fromHandle} placed a bid for{' '}
+          <StyledLink
+            to={absoluteRoutes.viewer.member(fromHandle, { tab: 'NFTs owned' })}
+            onClick={(e) => e.stopPropagation()}
+          >
+            {fromHandle}
+          </StyledLink>{' '}
+          placed a bid for{' '}
           <NumberFormat as="span" color="inherit" format="short" value={activity.bidAmount} withToken />
         </>
       )
     case 'Sale':
       return (
         <>
-          {fromHandle} sold NFT to{' '}
+          <StyledLink
+            to={absoluteRoutes.viewer.member(fromHandle, { tab: 'NFTs owned' })}
+            onClick={(e) => e.stopPropagation()}
+          >
+            {fromHandle}
+          </StyledLink>{' '}
+          sold NFT to{' '}
           <StyledLink to={absoluteRoutes.viewer.member(activity.to?.handle)} onClick={(e) => e.stopPropagation()}>
             {activity.to?.handle}
           </StyledLink>{' '}
@@ -60,13 +72,25 @@ const getDescription = (activity: ActivitiesRecord) => {
             {activity.to?.handle}{' '}
           </StyledLink>{' '}
           purchased NFT for <NumberFormat as="span" color="inherit" format="short" value={activity.price} withToken />{' '}
-          from {fromHandle}
+          from{' '}
+          <StyledLink
+            to={absoluteRoutes.viewer.member(fromHandle, { tab: 'NFTs owned' })}
+            onClick={(e) => e.stopPropagation()}
+          >
+            {fromHandle}
+          </StyledLink>{' '}
         </>
       )
     case 'Listing':
       return (
         <>
-          {fromHandle} listed NFT{' '}
+          <StyledLink
+            to={absoluteRoutes.viewer.member(fromHandle, { tab: 'NFTs owned' })}
+            onClick={(e) => e.stopPropagation()}
+          >
+            {fromHandle}
+          </StyledLink>{' '}
+          listed NFT{' '}
           {activity.typeName === 'NftSellOrderMadeEventData' && activity.price && (
             <>
               for <NumberFormat as="span" color="inherit" format="short" value={activity.price} withToken />
@@ -75,16 +99,51 @@ const getDescription = (activity: ActivitiesRecord) => {
         </>
       )
     case 'Removal':
-      return <>{fromHandle} removed NFT from sale</>
+      return (
+        <>
+          <StyledLink
+            to={absoluteRoutes.viewer.member(fromHandle, { tab: 'NFTs owned' })}
+            onClick={(e) => e.stopPropagation()}
+          >
+            {fromHandle}
+          </StyledLink>{' '}
+          removed NFT from sale
+        </>
+      )
     case 'Mint':
-      return <>{fromHandle} minted new NFT</>
+      return (
+        <>
+          <StyledLink
+            to={absoluteRoutes.viewer.member(fromHandle, { tab: 'NFTs owned' })}
+            onClick={(e) => e.stopPropagation()}
+          >
+            {fromHandle}
+          </StyledLink>{' '}
+          minted new NFT
+        </>
+      )
     case 'Withdrawal':
-      return <>{fromHandle} withdrew a bid</>
+      return (
+        <>
+          <StyledLink
+            to={absoluteRoutes.viewer.member(fromHandle, { tab: 'NFTs owned' })}
+            onClick={(e) => e.stopPropagation()}
+          >
+            {fromHandle}
+          </StyledLink>{' '}
+          withdrew a bid
+        </>
+      )
     case 'Price change':
       return (
         <>
-          {fromHandle} changed price to{' '}
-          <NumberFormat as="span" color="inherit" format="short" value={activity.price} withToken />
+          <StyledLink
+            to={absoluteRoutes.viewer.member(fromHandle, { tab: 'NFTs owned' })}
+            onClick={(e) => e.stopPropagation()}
+          >
+            {fromHandle}
+          </StyledLink>{' '}
+          changed price to <NumberFormat as="span" color="inherit" format="short" value={activity.price} withToken />
         </>
       )
   }

+ 7 - 6
packages/atlas/src/views/viewer/MemberView/MemberView.tsx

@@ -1,5 +1,5 @@
 import { FC, useEffect, useMemo, useRef, useState } from 'react'
-import { useParams } from 'react-router'
+import { useNavigate, useParams } from 'react-router'
 import { useSearchParams } from 'react-router-dom'
 
 import { useMemberships } from '@/api/hooks/membership'
@@ -12,7 +12,7 @@ import { ViewErrorFallback } from '@/components/ViewErrorFallback'
 import { ViewWrapper } from '@/components/ViewWrapper'
 import { Button } from '@/components/_buttons/Button'
 import { Select } from '@/components/_inputs/Select'
-import { absoluteRoutes } from '@/config/routes'
+import { MemberTabs, QUERY_PARAMS, absoluteRoutes } from '@/config/routes'
 import { NFT_SORT_ACTIVITY_OPTIONS, NFT_SORT_OPTIONS } from '@/config/sorting'
 import { useHeadTags } from '@/hooks/useHeadTags'
 import { getMemberAvatar } from '@/providers/assets/assets.helpers'
@@ -32,16 +32,17 @@ import {
   TabsWrapper,
 } from './MemberView.styles'
 
-const TABS = ['NFTs owned', 'Activity', 'About'] as const
+const TABS: MemberTabs[] = ['NFTs owned', 'Activity', 'About']
 
 export const MemberView: FC = () => {
   const [searchParams, setSearchParams] = useSearchParams()
-  const currentTabName = searchParams.get('tab') as typeof TABS[number] | null
+  const currentTabName = searchParams.get(QUERY_PARAMS.TAB) as MemberTabs | null
   const [sortBy, setSortBy] = useState<OwnedNftOrderByInput>(OwnedNftOrderByInput.CreatedAtDesc)
   const [sortByTimestamp, setSortByTimestamp] = useState<NftActivityOrderByInput>(
     NftActivityOrderByInput.EventTimestampDesc
   )
-  const [currentTab, setCurrentTab] = useState<typeof TABS[number] | null>(null)
+  const navigate = useNavigate()
+  const [currentTab, setCurrentTab] = useState<MemberTabs | null>(null)
   const [nftCount, setNftCount] = useState<number | undefined>()
   const { memberId, activeMembership } = useUser()
   const { handle } = useParams()
@@ -82,7 +83,7 @@ export const MemberView: FC = () => {
     }
   }
   const handleSetCurrentTab = async (tab: number) => {
-    setSearchParams({ 'tab': TABS[tab] }, { replace: true })
+    navigate(absoluteRoutes.viewer.member(handle, { tab: TABS[tab] }))
   }
 
   const mappedTabs = TABS.map((tab) => ({