Bläddra i källkod

Release v3.3.0

attemka 1 år sedan
förälder
incheckning
f1780fc493
100 ändrade filer med 2562 tillägg och 2888 borttagningar
  1. 18 0
      CHANGELOG.md
  2. 5 2
      packages/atlas/.storybook/main.ts
  3. 9 9
      packages/atlas/package.json
  4. 2 1
      packages/atlas/src/.env
  5. 5 1
      packages/atlas/src/MainLayout.tsx
  6. 11 11
      packages/atlas/src/api/client/cache.ts
  7. 6 0
      packages/atlas/src/api/client/index.ts
  8. 0 21
      packages/atlas/src/api/hooks/channel.ts
  9. 19 4
      packages/atlas/src/api/hooks/nfts.ts
  10. 18 16
      packages/atlas/src/api/hooks/notifications.ts
  11. 7 24
      packages/atlas/src/api/hooks/video.ts
  12. 63 0
      packages/atlas/src/api/queries/__generated__/baseTypes.generated.ts
  13. 189 88
      packages/atlas/src/api/queries/__generated__/channels.generated.tsx
  14. 95 18
      packages/atlas/src/api/queries/__generated__/nfts.generated.tsx
  15. 517 507
      packages/atlas/src/api/queries/__generated__/notifications.generated.tsx
  16. 0 768
      packages/atlas/src/api/queries/__generated__/videos.generated.tsx
  17. 26 9
      packages/atlas/src/api/queries/channels.graphql
  18. 14 2
      packages/atlas/src/api/queries/nfts.graphql
  19. 270 251
      packages/atlas/src/api/queries/notifications.graphql
  20. 0 22
      packages/atlas/src/api/queries/videos.graphql
  21. 0 0
      packages/atlas/src/api/schemas/orion.json
  22. 16 0
      packages/atlas/src/assets/icons/ActionCreatorToken.tsx
  23. 16 0
      packages/atlas/src/assets/icons/ActionVerified.tsx
  24. 26 6
      packages/atlas/src/assets/icons/JoyTokenSilver16.tsx
  25. 26 6
      packages/atlas/src/assets/icons/JoyTokenSilver24.tsx
  26. 2 0
      packages/atlas/src/assets/icons/index.ts
  27. 3 0
      packages/atlas/src/assets/icons/svgs/action-creator-token.svg
  28. 3 0
      packages/atlas/src/assets/icons/svgs/action-verified.svg
  29. 12 0
      packages/atlas/src/assets/icons/svgs/joy-token-silver-16.svg
  30. 12 0
      packages/atlas/src/assets/icons/svgs/joy-token-silver-24.svg
  31. 49 43
      packages/atlas/src/components/AllNftSection/AllNftSection.tsx
  32. 30 85
      packages/atlas/src/components/Avatar/Avatar.styles.ts
  33. 7 14
      packages/atlas/src/components/Avatar/Avatar.tsx
  34. 4 4
      packages/atlas/src/components/Avatar/AvatarGroup.stories.tsx
  35. 7 6
      packages/atlas/src/components/Avatar/AvatarGroup.styles.ts
  36. 12 4
      packages/atlas/src/components/Avatar/AvatarGroup.tsx
  37. 8 1
      packages/atlas/src/components/Carousel/Carousel.styles.ts
  38. 15 2
      packages/atlas/src/components/Carousel/Carousel.tsx
  39. 0 12
      packages/atlas/src/components/InfiniteGrids/InfiniteGrid.styles.ts
  40. 0 229
      packages/atlas/src/components/InfiniteGrids/InfiniteVideoGrid.tsx
  41. 0 1
      packages/atlas/src/components/InfiniteGrids/index.ts
  42. 0 186
      packages/atlas/src/components/InfiniteGrids/useInfiniteGrid.ts
  43. 3 2
      packages/atlas/src/components/LimitedWidthContainer/LimitedWidthContainer.tsx
  44. 1 1
      packages/atlas/src/components/MembershipInfo/MembershipInfo.tsx
  45. 23 0
      packages/atlas/src/components/MinimizedPlayer/MinimizedPlayer.styles.ts
  46. 51 0
      packages/atlas/src/components/MinimizedPlayer/MinimizedPlayer.tsx
  47. 1 0
      packages/atlas/src/components/MinimizedPlayer/index.ts
  48. 32 32
      packages/atlas/src/components/NftCarousel/MarketplaceCarousel.tsx
  49. 27 73
      packages/atlas/src/components/NftCarousel/components/MarketplaceCarouselCard/MarketplaceCarouselCard.styles.ts
  50. 2 2
      packages/atlas/src/components/NftCarousel/components/MarketplaceCarouselCard/MarketplaceCarouselCard.tsx
  51. 120 74
      packages/atlas/src/components/NftCarousel/components/MarketplaceCarouselCard/NftCarouselDetails.tsx
  52. 2 2
      packages/atlas/src/components/NftCarousel/components/NftCarouselItem/NftCarouselItem.styles.ts
  53. 2 2
      packages/atlas/src/components/NftCarousel/components/NftCarouselItem/NftCarouselItem.tsx
  54. 1 1
      packages/atlas/src/components/OutputPill/OutputPill.tsx
  55. 1 1
      packages/atlas/src/components/OwnerPill/OwnerPill.tsx
  56. 1 1
      packages/atlas/src/components/Searchbar/SearchBox/SearchBox.styles.ts
  57. 8 8
      packages/atlas/src/components/Searchbar/SearchBox/SearchBox.tsx
  58. 2 5
      packages/atlas/src/components/Section/Section.stories.tsx
  59. 4 3
      packages/atlas/src/components/Section/Section.styles.ts
  60. 43 16
      packages/atlas/src/components/Section/Section.tsx
  61. 0 1
      packages/atlas/src/components/Section/SectionContent/SectionContent.stories.tsx
  62. 25 3
      packages/atlas/src/components/Section/SectionContent/SectionContent.styles.ts
  63. 3 4
      packages/atlas/src/components/Section/SectionContent/SectionContent.tsx
  64. 10 6
      packages/atlas/src/components/Section/SectionFooter/SectionFooter.tsx
  65. 13 4
      packages/atlas/src/components/Section/SectionHeader/SectionHeader.stories.tsx
  66. 28 29
      packages/atlas/src/components/Section/SectionHeader/SectionHeader.tsx
  67. 1 1
      packages/atlas/src/components/Section/SectionHeader/SectionTitle/SectionTitle.tsx
  68. 6 2
      packages/atlas/src/components/Table/Table.stories.tsx
  69. 13 1
      packages/atlas/src/components/Table/Table.styles.ts
  70. 69 38
      packages/atlas/src/components/Table/Table.tsx
  71. 1 1
      packages/atlas/src/components/TablePaymentsHistory/TablePaymentsHistory.tsx
  72. 30 0
      packages/atlas/src/components/TopSellingChannelsTable/TopSellingChannelsTable.stories.tsx
  73. 100 0
      packages/atlas/src/components/TopSellingChannelsTable/TopSellingChannelsTable.styles.ts
  74. 222 0
      packages/atlas/src/components/TopSellingChannelsTable/TopSellingChannelsTable.tsx
  75. 1 0
      packages/atlas/src/components/TopSellingChannelsTable/index.ts
  76. 1 1
      packages/atlas/src/components/_auth/SignInModal/SignInSteps/SignInModalMembershipStep.tsx
  77. 3 3
      packages/atlas/src/components/_buttons/Button/Button.styles.ts
  78. 1 29
      packages/atlas/src/components/_buttons/CallToActionButton/CallToActionButton.tsx
  79. 2 1
      packages/atlas/src/components/_channel/ChannelCard/ChannelCard.styles.ts
  80. 1 1
      packages/atlas/src/components/_channel/ChannelCard/ChannelCard.tsx
  81. 1 1
      packages/atlas/src/components/_channel/ChannelLink/ChannelLink.styles.ts
  82. 1 1
      packages/atlas/src/components/_channel/ChannelLink/ChannelLink.tsx
  83. 7 1
      packages/atlas/src/components/_channel/ChannelWithVideos/ChannelWithVideos.tsx
  84. 44 17
      packages/atlas/src/components/_channel/ChannelsSection/ChannelsSection.tsx
  85. 1 1
      packages/atlas/src/components/_channel/CollectorsBox/CollectorsBox.tsx
  86. 2 17
      packages/atlas/src/components/_channel/ExpandableChannelsList/ExpandableChannelsList.tsx
  87. 4 4
      packages/atlas/src/components/_comments/CommentRow/CommentRow.tsx
  88. 1 1
      packages/atlas/src/components/_comments/CommentSnapshot/CommentSnaphsot.tsx
  89. 0 83
      packages/atlas/src/components/_content/NewNftSales/NewNftSales.tsx
  90. 0 1
      packages/atlas/src/components/_content/NewNftSales/index.ts
  91. 0 24
      packages/atlas/src/components/_content/OfficialJoystreamUpdate/OfficialJoystreamUpdate.tsx
  92. 0 1
      packages/atlas/src/components/_content/OfficialJoystreamUpdate/index.ts
  93. 69 8
      packages/atlas/src/components/_content/TopTenVideos/TopTenVideos.tsx
  94. 1 1
      packages/atlas/src/components/_inputs/Input/Input.stories.tsx
  95. 6 6
      packages/atlas/src/components/_inputs/InputAutocomplete/InputAutocomplete.tsx
  96. 1 1
      packages/atlas/src/components/_inputs/MemberComboBox/MemberComboBox.tsx
  97. 14 1
      packages/atlas/src/components/_inputs/ToggleButtonGroup/ToggleButtonGroup.stories.tsx
  98. 6 0
      packages/atlas/src/components/_inputs/ToggleButtonGroup/ToggleButtonGroup.styles.ts
  99. 24 16
      packages/atlas/src/components/_inputs/ToggleButtonGroup/ToggleButtonGroup.tsx
  100. 4 2
      packages/atlas/src/components/_navigation/NotificationsButton/NotificationsButton.tsx

+ 18 - 0
CHANGELOG.md

@@ -5,6 +5,24 @@ All notable changes to this project will be documented in this file.
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
+## [3.3.0] - 2023-05-23
+
+### Added
+
+ - New NFT marketplace page
+ - New homepage with videos feed
+
+### Changed
+
+ - Improved notifications fetch query 
+
+### Fixed
+
+ - Fixed incorrect comments display
+ - Fixed tiles status in studio
+ - Fixed bug with modal windows blocking the scroll
+ - Minor bugfixes
+
 ## [3.2.0] - 2023-04-21
 
 ### Changed

+ 5 - 2
packages/atlas/.storybook/main.ts

@@ -1,4 +1,4 @@
-import { StorybookConfig } from '@storybook/builder-vite'
+import { StorybookConfig } from '@storybook/react-vite'
 
 const config: StorybookConfig = {
   stories: ['../src/**/*.stories.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],
@@ -7,12 +7,15 @@ const config: StorybookConfig = {
   viteFinal: (config) => {
     return {
       ...config,
-      // override .env directory
+      // override .env directorya jesl
       envDir: 'src',
       // get rid of checker plugin since it references .eslintignore at relative path, and we don't need it in storybook anyway
       // eslint-disable-next-line @typescript-eslint/no-explicit-any
       plugins: config.plugins.filter((p) => (p as any).name !== 'vite-plugin-checker'),
     }
   },
+  docs: {
+    autodocs: true,
+  },
 }
 export default config

+ 9 - 9
packages/atlas/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@joystream/atlas",
   "description": "UI for consuming Joystream - a user governed video platform",
-  "version": "3.2.0",
+  "version": "3.3.0",
   "license": "GPL-3.0",
   "scripts": {
     "start": "vite",
@@ -108,13 +108,13 @@
     "@joystream/prettier-config": "^1.0.0",
     "@modyfi/vite-plugin-yaml": "^1.0.3",
     "@rollup/plugin-babel": "^6.0.3",
-    "@storybook/addon-actions": "7.0.0-beta.6",
-    "@storybook/addon-docs": "7.0.0-beta.6",
-    "@storybook/addon-essentials": "7.0.0-beta.6",
-    "@storybook/addon-links": "7.0.0-beta.6",
-    "@storybook/addons": "7.0.0-beta.6",
-    "@storybook/react-vite": "7.0.0-beta.6",
-    "@storybook/theming": "7.0.0-beta.6",
+    "@storybook/addon-actions": "7.0.7",
+    "@storybook/addon-docs": "7.0.7",
+    "@storybook/addon-essentials": "7.0.7",
+    "@storybook/addon-links": "7.0.7",
+    "@storybook/addons": "7.0.7",
+    "@storybook/react-vite": "7.0.7",
+    "@storybook/theming": "7.0.7",
     "@svgr/cli": "^6.5.1",
     "@testing-library/dom": "^8.19.0",
     "@testing-library/jest-dom": "^5.16.5",
@@ -140,7 +140,7 @@
     "react-hooks-testing-library": "^0.6.0",
     "rimraf": "^3.0.2",
     "rollup-plugin-visualizer": "^5.8.3",
-    "storybook": "7.0.0-beta.3",
+    "storybook": "7.0.7",
     "style-dictionary": "^3.7.1",
     "vite": "^4.0.1",
     "vite-plugin-checker": "^0.5.2",

+ 2 - 1
packages/atlas/src/.env

@@ -6,6 +6,7 @@ VITE_ENV=development
 # App configuration
 VITE_APP_ID=4414-2
 VITE_APP_NAME=Atlas
+VITE_ENV_SELECTION_ENABLED=true
 
 VITE_AVATAR_SERVICE_URL=https://atlas-services.joystream.org/avatars
 VITE_GEOLOCATION_SERVICE_URL=https://geolocation.joystream.org
@@ -26,7 +27,7 @@ VITE_PRODUCTION_NODE_URL=wss://rpc.joystream.org:9944
 VITE_PRODUCTION_FAUCET_URL=https://faucet.joystream.org/member-faucet/register
 
 # Development env URLs - this is the default configuration if VITE_ENV != production
-VITE_DEVELOPMENT_ORION_URL=https://atlas-dev.joystream.org/orion-v2/graphql 
+VITE_DEVELOPMENT_ORION_URL=https://atlas-dev.joystream.org/orion-v2/graphql
 VITE_DEVELOPMENT_QUERY_NODE_SUBSCRIPTION_URL=wss://atlas-dev.joystream.org/orion-v2/graphql
 VITE_DEVELOPMENT_NODE_URL=wss://atlas-dev.joystream.org/ws-rpc
 VITE_DEVELOPMENT_FAUCET_URL=https://atlas-dev.joystream.org/member-faucet/register

+ 5 - 1
packages/atlas/src/MainLayout.tsx

@@ -12,6 +12,7 @@ import { isBrowserOutdated } from '@/utils/browser'
 import { AppLogo } from './components/AppLogo'
 import { TopbarBase } from './components/_navigation/TopbarBase'
 import { useConfirmationModal } from './providers/confirmationModal'
+import { useOverlayManager } from './providers/overlayManager'
 import { LegalLayout } from './views/legal/LegalLayout'
 import { ViewerLayout } from './views/viewer/ViewerLayout'
 
@@ -52,6 +53,8 @@ export const MainLayout: FC = () => {
     onExitClick: () => closeDialog(),
   })
 
+  const { clearOverlays } = useOverlayManager()
+
   useEffect(() => {
     if (isBrowserOutdated) {
       openDialog()
@@ -62,6 +65,7 @@ export const MainLayout: FC = () => {
     if (location.pathname === cachedLocation.pathname) {
       return
     }
+    clearOverlays()
 
     setCachedLocation(location)
 
@@ -76,7 +80,7 @@ export const MainLayout: FC = () => {
     setTimeout(() => {
       window.scrollTo(0, navigationType !== 'POP' ? 0 : scrollPosition.current)
     }, parseInt(transitions.timings.routing) + ROUTING_ANIMATION_OFFSET)
-  }, [location, cachedLocation, locationState, navigationType])
+  }, [location, cachedLocation, locationState, navigationType, clearOverlays])
 
   return (
     <>

+ 11 - 11
packages/atlas/src/api/client/cache.ts

@@ -10,7 +10,6 @@ import {
   QueryCommentsConnectionArgs,
   QueryOwnedNftsConnectionArgs,
   QueryVideosConnectionArgs,
-  VideoOrderByInput,
   VideosConnection,
 } from '../queries/__generated__/baseTypes.generated'
 import { FullChannelFieldsFragment, FullVideoFieldsFragment } from '../queries/__generated__/fragments.generated'
@@ -88,9 +87,14 @@ const getChannelKeyArgs = (args: Partial<QueryChannelsConnectionArgs> | null) =>
   return `${language}:${idIn}:${sorting}:${titleContains}`
 }
 
-const getCommentKeyArgs = (args: Partial<QueryCommentsConnectionArgs> | null) => {
+const getCommentKeyArgs = (
+  args: Partial<QueryCommentsConnectionArgs> | null,
+  ctx: {
+    variables?: Record<string, unknown>
+  }
+) => {
   const parentCommentId = args?.where?.parentComment?.id_eq
-  const videoId = args?.where?.video?.id_eq
+  const videoId = args?.where?.video?.id_eq ?? ctx.variables?.videoId
   const orderBy = args?.orderBy || []
   return `${parentCommentId}:${videoId}:${orderBy}`
 }
@@ -139,17 +143,10 @@ const queryCacheFields: CachePolicyFields<keyof Query> = {
           return nodeFieldValue === isPublic
         }) ?? []
 
-      const sortingArray = args?.orderBy != null ? (Array.isArray(args.orderBy) ? args.orderBy : [args.orderBy]) : []
-      const sortingASC = sortingArray[0] === VideoOrderByInput.CreatedAtAsc
-      const preSortedDESC = (filteredEdges || []).slice().sort((a, b) => {
-        return (readField('createdAt', b.node) as Date).getTime() - (readField('createdAt', a.node) as Date).getTime()
-      })
-      const sortedEdges = sortingASC ? preSortedDESC.reverse() : preSortedDESC
-
       return (
         existing && {
           ...existing,
-          edges: sortedEdges,
+          edges: filteredEdges,
         }
       )
     },
@@ -248,6 +245,9 @@ const channelCacheFields: CachePolicyFields<keyof FullChannelFieldsFragment> = {
 }
 
 const cache = new InMemoryCache({
+  possibleTypes: {
+    NftOwner: ['NftOwnerChannel', 'NftOwnerMember'],
+  },
   typePolicies: {
     Query: {
       fields: queryCacheFields,

+ 6 - 0
packages/atlas/src/api/client/index.ts

@@ -109,6 +109,12 @@ const createApolloClient = () => {
               logDistributorPerformance(resolvedUrl, eventEntry)
               return resolvedUrl
             } catch (err) {
+              if (err instanceof MediaError) {
+                SentryLogger.error('Error during asset download test, media is not supported', 'AssetsManager', err, {
+                  asset: { parent, resolvedUrl, mediaError: err },
+                })
+                return null
+              }
               bannedDistributorUrls[distributorUrl] = (bannedDistributorUrls[distributorUrl] || 0) + 1
               if (err instanceof TimeoutError) {
                 AssetLogger.logDistributorResponseTimeout(eventEntry)

+ 0 - 21
packages/atlas/src/api/hooks/channel.ts

@@ -12,8 +12,6 @@ import {
   GetExtendedBasicChannelsQueryVariables,
   GetExtendedFullChannelsQuery,
   GetExtendedFullChannelsQueryVariables,
-  GetPopularChannelsQuery,
-  GetPopularChannelsQueryVariables,
   GetTop10ChannelsQuery,
   GetTop10ChannelsQueryVariables,
   UnfollowChannelMutation,
@@ -22,7 +20,6 @@ import {
   useGetDiscoverChannelsQuery,
   useGetExtendedBasicChannelsQuery,
   useGetExtendedFullChannelsQuery,
-  useGetPopularChannelsQuery,
   useGetTop10ChannelsQuery,
   useUnfollowChannelMutation,
 } from '@/api/queries/__generated__/channels.generated'
@@ -190,24 +187,6 @@ export const useDiscoverChannels = (
   }
 }
 
-export const usePopularChannels = (
-  variables?: GetPopularChannelsQueryVariables,
-  opts?: QueryHookOptions<GetPopularChannelsQuery, GetPopularChannelsQueryVariables>
-) => {
-  const { data, ...rest } = useGetPopularChannelsQuery({
-    ...opts,
-    variables,
-  })
-
-  const shuffledChannels = useShuffleResults<GetPopularChannelsQuery['extendedChannels'][number]>(
-    data?.extendedChannels
-  )
-  return {
-    extendedChannels: shuffledChannels,
-    ...rest,
-  }
-}
-
 export const useChannelNftCollectors = (
   variables?: GetChannelNftCollectorsQueryVariables,
   opts?: QueryHookOptions<GetChannelNftCollectorsQuery, GetChannelNftCollectorsQueryVariables>

+ 19 - 4
packages/atlas/src/api/hooks/nfts.ts

@@ -14,7 +14,7 @@ import {
   GetNftsConnectionQueryVariables,
   GetNftsQuery,
   GetNftsQueryVariables,
-  useGetFeaturedNftsQuery,
+  useGetFeaturedNftsVideosQuery,
   useGetNftQuery,
   useGetNftsConnectionQuery,
   useGetNftsQuery,
@@ -25,7 +25,7 @@ import {
   useGetNftHistoryQuery,
 } from '@/api/queries/__generated__/notifications.generated'
 
-import { OwnedNftWhereInput } from '../queries/__generated__/baseTypes.generated'
+import { OwnedNftOrderByInput, OwnedNftWhereInput } from '../queries/__generated__/baseTypes.generated'
 
 type CommonNftProperties = {
   title: string | null | undefined
@@ -189,8 +189,23 @@ export const useNfts = (baseOptions?: QueryHookOptions<GetNftsQuery, GetNftsQuer
   }
 }
 
-export const useFeaturedNfts = () => {
-  const { data, ...rest } = useGetFeaturedNftsQuery()
+export const useFeaturedNftsVideos = () => {
+  const { data, ...rest } = useGetFeaturedNftsVideosQuery({
+    variables: {
+      limit: 10,
+      orderBy: [OwnedNftOrderByInput.VideoViewsNumDesc],
+      where: {
+        isFeatured_eq: true,
+        transactionalStatus: {
+          isTypeOf_in: ['TransactionalStatusAuction', 'TransactionalStatusBuyNow'],
+          auction: {
+            isCompleted_eq: false,
+            isCanceled_eq: false,
+          },
+        },
+      },
+    },
+  })
 
   return {
     nfts: data?.ownedNfts,

+ 18 - 16
packages/atlas/src/api/hooks/notifications.ts

@@ -1,47 +1,48 @@
 import { QueryHookOptions } from '@apollo/client'
 
 import {
-  GetNotificationsQuery,
-  GetNotificationsQueryVariables,
+  GetNftActivitiesQuery,
+  GetNftActivitiesQueryVariables,
+  GetNotificationsConnectionQuery,
+  GetNotificationsConnectionQueryVariables,
   useGetNftActivitiesQuery,
-  useGetNotificationsQuery,
+  useGetNotificationsConnectionQuery,
 } from '@/api/queries/__generated__/notifications.generated'
 
 import { NftActivityOrderByInput } from '../queries/__generated__/baseTypes.generated'
 
 export const useRawNotifications = (
   memberId: string | null,
-  opts?: QueryHookOptions<GetNotificationsQuery, GetNotificationsQueryVariables>
+  opts?: QueryHookOptions<GetNotificationsConnectionQuery, GetNotificationsConnectionQueryVariables>
 ) => {
-  const { data, ...rest } = useGetNotificationsQuery({
+  const { data, ...rest } = useGetNotificationsConnectionQuery({
     variables: {
-      limit: 1000,
+      first: 10,
       memberId: memberId || '',
     },
-    // TODO Fix me. We use `no-cache` because for unknown reasons cache removes data about owner
-    fetchPolicy: 'no-cache',
     skip: !memberId,
     ...opts,
   })
-
   return {
-    notifications: data?.notifications || [],
+    notifications: data?.notificationsConnection.edges || [],
+    totalCount: data?.notificationsConnection.totalCount,
+    pageInfo: data?.notificationsConnection.pageInfo,
     ...rest,
   }
 }
 
 export const useRawActivities = (
   memberId?: string,
-  sort: NftActivityOrderByInput = NftActivityOrderByInput.EventTimestampDesc
+  sort: NftActivityOrderByInput = NftActivityOrderByInput.EventTimestampDesc,
+  opts?: QueryHookOptions<GetNftActivitiesQuery, GetNftActivitiesQueryVariables>
 ) => {
   const { data, ...rest } = useGetNftActivitiesQuery({
+    ...opts,
     variables: {
-      limit: 100,
+      first: 10,
       orderBy: sort,
       memberId: memberId || '',
     },
-    // TODO Fix me. We use `no-cache` because for unknown reasons cache removes data about owner
-    fetchPolicy: 'no-cache',
     skip: !memberId,
   })
 
@@ -50,8 +51,9 @@ export const useRawActivities = (
     nftsBoughtTotalCount: data?.nftsBought.totalCount,
     nftsSoldTotalCount: data?.nftsSold.totalCount,
     nftsIssuedTotalCount: data?.nftsIssued.totalCount,
-    nftsBidded: data?.nftsBidded,
-    activities: data?.nftActivities,
+    totalCount: data?.nftActivitiesConnection.totalCount,
+    pageInfo: data?.nftActivitiesConnection.pageInfo,
+    activities: data?.nftActivitiesConnection.edges,
     ...rest,
   }
 }

+ 7 - 24
packages/atlas/src/api/hooks/video.ts

@@ -7,17 +7,14 @@ import {
   GetBasicVideosQueryVariables,
   GetFullVideosQuery,
   GetFullVideosQueryVariables,
-  GetTop10VideosThisMonthQuery,
-  GetTop10VideosThisMonthQueryVariables,
-  GetTop10VideosThisWeekQuery,
-  GetTop10VideosThisWeekQueryVariables,
+  GetMostViewedVideosConnectionQuery,
+  GetMostViewedVideosConnectionQueryVariables,
   GetVideosCountQuery,
   GetVideosCountQueryVariables,
   useAddVideoViewMutation,
   useGetBasicVideosQuery,
   useGetFullVideosQuery,
-  useGetTop10VideosThisMonthQuery,
-  useGetTop10VideosThisWeekQuery,
+  useGetMostViewedVideosConnectionQuery,
   useGetVideosCountQuery,
 } from '@/api/queries/__generated__/videos.generated'
 import { publicVideoFilter } from '@/config/contentFilter'
@@ -113,25 +110,11 @@ export const useBasicVideo = (
   }
 }
 
-export const useTop10VideosThisWeek = (
-  variables?: GetTop10VideosThisWeekQueryVariables,
-  opts?: QueryHookOptions<GetTop10VideosThisWeekQuery, GetTop10VideosThisWeekQueryVariables>
+export const useMostViewedVideosConnection = (
+  variables?: GetMostViewedVideosConnectionQueryVariables,
+  opts?: QueryHookOptions<GetMostViewedVideosConnectionQuery, GetMostViewedVideosConnectionQueryVariables>
 ) => {
-  const { data, ...rest } = useGetTop10VideosThisWeekQuery({
-    ...opts,
-    variables,
-  })
-  return {
-    videos: data?.mostViewedVideosConnection.edges.map((video) => video.node),
-    ...rest,
-  }
-}
-
-export const useTop10VideosThisMonth = (
-  variables?: GetTop10VideosThisMonthQueryVariables,
-  opts?: QueryHookOptions<GetTop10VideosThisMonthQuery, GetTop10VideosThisMonthQueryVariables>
-) => {
-  const { data, ...rest } = useGetTop10VideosThisMonthQuery({
+  const { data, ...rest } = useGetMostViewedVideosConnectionQuery({
     ...opts,
     variables,
   })

+ 63 - 0
packages/atlas/src/api/queries/__generated__/baseTypes.generated.ts

@@ -1761,6 +1761,8 @@ export enum CommentOrderByInput {
   VideoReactionsCountDesc = 'video_reactionsCount_DESC',
   VideoTitleAsc = 'video_title_ASC',
   VideoTitleDesc = 'video_title_DESC',
+  VideoVideoRelevanceAsc = 'video_videoRelevance_ASC',
+  VideoVideoRelevanceDesc = 'video_videoRelevance_DESC',
   VideoVideoStateBloatBondAsc = 'video_videoStateBloatBond_ASC',
   VideoVideoStateBloatBondDesc = 'video_videoStateBloatBond_DESC',
   VideoViewsNumAsc = 'video_viewsNum_ASC',
@@ -1856,6 +1858,8 @@ export enum CommentReactionOrderByInput {
   VideoReactionsCountDesc = 'video_reactionsCount_DESC',
   VideoTitleAsc = 'video_title_ASC',
   VideoTitleDesc = 'video_title_DESC',
+  VideoVideoRelevanceAsc = 'video_videoRelevance_ASC',
+  VideoVideoRelevanceDesc = 'video_videoRelevance_DESC',
   VideoVideoStateBloatBondAsc = 'video_videoStateBloatBond_ASC',
   VideoVideoStateBloatBondDesc = 'video_videoStateBloatBond_DESC',
   VideoViewsNumAsc = 'video_viewsNum_ASC',
@@ -3776,6 +3780,7 @@ export type Mutation = {
   setSupportedCategories: SetSupportedCategoriesResult
   setVideoHero: SetVideoHeroResult
   setVideoViewPerIpTimeLimit: VideoViewPerIpTimeLimit
+  setVideoWeights: VideoWeights
   signAppActionCommitment: GeneratedSignature
   unfollowChannel: ChannelUnfollowResult
 }
@@ -3843,6 +3848,13 @@ export type MutationSetVideoViewPerIpTimeLimitArgs = {
   limitInSeconds: Scalars['Int']
 }
 
+export type MutationSetVideoWeightsArgs = {
+  commentsWeight: Scalars['Float']
+  newnessWeight: Scalars['Float']
+  reactionsWeight: Scalars['Float']
+  viewsWeight: Scalars['Float']
+}
+
 export type MutationSignAppActionCommitmentArgs = {
   actionType: AppActionActionType
   assets: Scalars['String']
@@ -4454,6 +4466,8 @@ export enum OwnedNftOrderByInput {
   VideoReactionsCountDesc = 'video_reactionsCount_DESC',
   VideoTitleAsc = 'video_title_ASC',
   VideoTitleDesc = 'video_title_DESC',
+  VideoVideoRelevanceAsc = 'video_videoRelevance_ASC',
+  VideoVideoRelevanceDesc = 'video_videoRelevance_DESC',
   VideoVideoStateBloatBondAsc = 'video_videoStateBloatBond_ASC',
   VideoVideoStateBloatBondDesc = 'video_videoStateBloatBond_DESC',
   VideoViewsNumAsc = 'video_viewsNum_ASC',
@@ -4682,6 +4696,7 @@ export type Query = {
   distributionBucketOperatorsConnection: DistributionBucketOperatorsConnection
   distributionBuckets: Array<DistributionBucket>
   distributionBucketsConnection: DistributionBucketsConnection
+  endingAuctionsNfts: Array<OwnedNft>
   eventById?: Maybe<Event>
   /** @deprecated Use eventById */
   eventByUniqueInput?: Maybe<Event>
@@ -4764,6 +4779,7 @@ export type Query = {
   storageDataObjectByUniqueInput?: Maybe<StorageDataObject>
   storageDataObjects: Array<StorageDataObject>
   storageDataObjectsConnection: StorageDataObjectsConnection
+  topSellingChannels: Array<TopSellingChannelsResult>
   videoById?: Maybe<Video>
   /** @deprecated Use videoById */
   videoByUniqueInput?: Maybe<Video>
@@ -5192,6 +5208,12 @@ export type QueryDistributionBucketsConnectionArgs = {
   where?: InputMaybe<DistributionBucketWhereInput>
 }
 
+export type QueryEndingAuctionsNftsArgs = {
+  limit?: InputMaybe<Scalars['Int']>
+  offset?: InputMaybe<Scalars['Int']>
+  where?: InputMaybe<OwnedNftWhereInput>
+}
+
 export type QueryEventByIdArgs = {
   id: Scalars['String']
 }
@@ -5544,6 +5566,12 @@ export type QueryStorageDataObjectsConnectionArgs = {
   where?: InputMaybe<StorageDataObjectWhereInput>
 }
 
+export type QueryTopSellingChannelsArgs = {
+  limit: Scalars['Int']
+  periodDays: Scalars['Int']
+  where?: InputMaybe<ExtendedChannelWhereInput>
+}
+
 export type QueryVideoByIdArgs = {
   id: Scalars['String']
 }
@@ -7220,6 +7248,13 @@ export type SubscriptionVideosArgs = {
   where?: InputMaybe<VideoWhereInput>
 }
 
+export type TopSellingChannelsResult = {
+  __typename?: 'TopSellingChannelsResult'
+  amount: Scalars['String']
+  channel: Channel
+  nftSold: Scalars['Int']
+}
+
 /** NFT transactional state */
 export type TransactionalStatus =
   | TransactionalStatusAuction
@@ -7358,6 +7393,8 @@ export type Video = {
   thumbnailPhoto?: Maybe<StorageDataObject>
   /** The title of the video */
   title?: Maybe<Scalars['String']>
+  /** Video relevance score based on the views, reactions, comments and update date */
+  videoRelevance: Scalars['Float']
   /** Value of video state bloat bond fee paid by channel owner */
   videoStateBloatBond: Scalars['BigInt']
   /** Number of video views (to speed up orderBy queries by avoiding COUNT aggregation) */
@@ -7610,6 +7647,8 @@ export enum VideoFeaturedInCategoryOrderByInput {
   VideoReactionsCountDesc = 'video_reactionsCount_DESC',
   VideoTitleAsc = 'video_title_ASC',
   VideoTitleDesc = 'video_title_DESC',
+  VideoVideoRelevanceAsc = 'video_videoRelevance_ASC',
+  VideoVideoRelevanceDesc = 'video_videoRelevance_DESC',
   VideoVideoStateBloatBondAsc = 'video_videoStateBloatBond_ASC',
   VideoVideoStateBloatBondDesc = 'video_videoStateBloatBond_DESC',
   VideoViewsNumAsc = 'video_viewsNum_ASC',
@@ -7728,6 +7767,8 @@ export enum VideoHeroOrderByInput {
   VideoReactionsCountDesc = 'video_reactionsCount_DESC',
   VideoTitleAsc = 'video_title_ASC',
   VideoTitleDesc = 'video_title_DESC',
+  VideoVideoRelevanceAsc = 'video_videoRelevance_ASC',
+  VideoVideoRelevanceDesc = 'video_videoRelevance_DESC',
   VideoVideoStateBloatBondAsc = 'video_videoStateBloatBond_ASC',
   VideoVideoStateBloatBondDesc = 'video_videoStateBloatBond_DESC',
   VideoViewsNumAsc = 'video_viewsNum_ASC',
@@ -8017,6 +8058,8 @@ export enum VideoMediaMetadataOrderByInput {
   VideoReactionsCountDesc = 'video_reactionsCount_DESC',
   VideoTitleAsc = 'video_title_ASC',
   VideoTitleDesc = 'video_title_DESC',
+  VideoVideoRelevanceAsc = 'video_videoRelevance_ASC',
+  VideoVideoRelevanceDesc = 'video_videoRelevance_DESC',
   VideoVideoStateBloatBondAsc = 'video_videoStateBloatBond_ASC',
   VideoVideoStateBloatBondDesc = 'video_videoStateBloatBond_DESC',
   VideoViewsNumAsc = 'video_viewsNum_ASC',
@@ -8262,6 +8305,8 @@ export enum VideoOrderByInput {
   ThumbnailPhotoUnsetAtDesc = 'thumbnailPhoto_unsetAt_DESC',
   TitleAsc = 'title_ASC',
   TitleDesc = 'title_DESC',
+  VideoRelevanceAsc = 'videoRelevance_ASC',
+  VideoRelevanceDesc = 'videoRelevance_DESC',
   VideoStateBloatBondAsc = 'videoStateBloatBond_ASC',
   VideoStateBloatBondDesc = 'videoStateBloatBond_DESC',
   ViewsNumAsc = 'viewsNum_ASC',
@@ -8346,6 +8391,8 @@ export enum VideoReactionOrderByInput {
   VideoReactionsCountDesc = 'video_reactionsCount_DESC',
   VideoTitleAsc = 'video_title_ASC',
   VideoTitleDesc = 'video_title_DESC',
+  VideoVideoRelevanceAsc = 'video_videoRelevance_ASC',
+  VideoVideoRelevanceDesc = 'video_videoRelevance_DESC',
   VideoVideoStateBloatBondAsc = 'video_videoStateBloatBond_ASC',
   VideoVideoStateBloatBondDesc = 'video_videoStateBloatBond_DESC',
   VideoViewsNumAsc = 'video_viewsNum_ASC',
@@ -8498,6 +8545,8 @@ export enum VideoSubtitleOrderByInput {
   VideoReactionsCountDesc = 'video_reactionsCount_DESC',
   VideoTitleAsc = 'video_title_ASC',
   VideoTitleDesc = 'video_title_DESC',
+  VideoVideoRelevanceAsc = 'video_videoRelevance_ASC',
+  VideoVideoRelevanceDesc = 'video_videoRelevance_DESC',
   VideoVideoStateBloatBondAsc = 'video_videoStateBloatBond_ASC',
   VideoVideoStateBloatBondDesc = 'video_videoStateBloatBond_DESC',
   VideoViewsNumAsc = 'video_viewsNum_ASC',
@@ -8696,6 +8745,11 @@ export type VideoViewPerIpTimeLimit = {
   limitInSeconds: Scalars['Int']
 }
 
+export type VideoWeights = {
+  __typename?: 'VideoWeights'
+  isApplied: Scalars['Boolean']
+}
+
 export type VideoWhereInput = {
   AND?: InputMaybe<Array<VideoWhereInput>>
   OR?: InputMaybe<Array<VideoWhereInput>>
@@ -8870,6 +8924,15 @@ export type VideoWhereInput = {
   title_not_in?: InputMaybe<Array<Scalars['String']>>
   title_not_startsWith?: InputMaybe<Scalars['String']>
   title_startsWith?: InputMaybe<Scalars['String']>
+  videoRelevance_eq?: InputMaybe<Scalars['Float']>
+  videoRelevance_gt?: InputMaybe<Scalars['Float']>
+  videoRelevance_gte?: InputMaybe<Scalars['Float']>
+  videoRelevance_in?: InputMaybe<Array<Scalars['Float']>>
+  videoRelevance_isNull?: InputMaybe<Scalars['Boolean']>
+  videoRelevance_lt?: InputMaybe<Scalars['Float']>
+  videoRelevance_lte?: InputMaybe<Scalars['Float']>
+  videoRelevance_not_eq?: InputMaybe<Scalars['Float']>
+  videoRelevance_not_in?: InputMaybe<Array<Scalars['Float']>>
   videoStateBloatBond_eq?: InputMaybe<Scalars['BigInt']>
   videoStateBloatBond_gt?: InputMaybe<Scalars['BigInt']>
   videoStateBloatBond_gte?: InputMaybe<Scalars['BigInt']>

+ 189 - 88
packages/atlas/src/api/queries/__generated__/channels.generated.tsx

@@ -399,46 +399,6 @@ export type GetDiscoverChannelsQuery = {
   }>
 }
 
-export type GetPopularChannelsQueryVariables = Types.Exact<{
-  where?: Types.InputMaybe<Types.ExtendedChannelWhereInput>
-}>
-
-export type GetPopularChannelsQuery = {
-  __typename?: 'Query'
-  extendedChannels: Array<{
-    __typename?: 'ExtendedChannel'
-    channel: {
-      __typename?: 'Channel'
-      id: string
-      title?: string | null
-      description?: string | null
-      createdAt: Date
-      followsNum: number
-      rewardAccount: string
-      channelStateBloatBond: string
-      avatarPhoto?: {
-        __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
-    }
-  }>
-}
-
 export type GetChannelNftCollectorsQueryVariables = Types.Exact<{
   channelId: Types.Scalars['String']
   orderBy?: Types.InputMaybe<Types.ChannelNftCollectorsOrderByInput>
@@ -663,6 +623,117 @@ export type GetChannelPaymentEventsQuery = {
   }>
 }
 
+export type GetTopSellingChannelsFromThreePeriodsQueryVariables = Types.Exact<{
+  limit: Types.Scalars['Int']
+  where?: Types.InputMaybe<Types.ExtendedChannelWhereInput>
+}>
+
+export type GetTopSellingChannelsFromThreePeriodsQuery = {
+  __typename?: 'Query'
+  topAllTimeSellingChannels: Array<{
+    __typename?: 'TopSellingChannelsResult'
+    amount: string
+    nftSold: number
+    channel: {
+      __typename?: 'Channel'
+      id: string
+      title?: string | null
+      description?: string | null
+      createdAt: Date
+      followsNum: number
+      rewardAccount: string
+      channelStateBloatBond: string
+      avatarPhoto?: {
+        __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
+    }
+  }>
+  topWeekSellingChannels: Array<{
+    __typename?: 'TopSellingChannelsResult'
+    amount: string
+    nftSold: number
+    channel: {
+      __typename?: 'Channel'
+      id: string
+      title?: string | null
+      description?: string | null
+      createdAt: Date
+      followsNum: number
+      rewardAccount: string
+      channelStateBloatBond: string
+      avatarPhoto?: {
+        __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
+    }
+  }>
+  topMonthSellingChannels: Array<{
+    __typename?: 'TopSellingChannelsResult'
+    amount: string
+    nftSold: number
+    channel: {
+      __typename?: 'Channel'
+      id: string
+      title?: string | null
+      description?: string | null
+      createdAt: Date
+      followsNum: number
+      rewardAccount: string
+      channelStateBloatBond: string
+      avatarPhoto?: {
+        __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
+    }
+  }>
+}
+
 export const GetFullChannelDocument = gql`
   query GetFullChannel($id: String!) {
     channelById(id: $id) {
@@ -1056,54 +1127,6 @@ export type GetDiscoverChannelsQueryResult = Apollo.QueryResult<
   GetDiscoverChannelsQuery,
   GetDiscoverChannelsQueryVariables
 >
-export const GetPopularChannelsDocument = gql`
-  query GetPopularChannels($where: ExtendedChannelWhereInput) {
-    extendedChannels(where: $where, orderBy: videoViewsNum_DESC, limit: 15) {
-      channel {
-        ...BasicChannelFields
-      }
-    }
-  }
-  ${BasicChannelFieldsFragmentDoc}
-`
-
-/**
- * __useGetPopularChannelsQuery__
- *
- * To run a query within a React component, call `useGetPopularChannelsQuery` and pass it any options that fit your needs.
- * When your component renders, `useGetPopularChannelsQuery` 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 } = useGetPopularChannelsQuery({
- *   variables: {
- *      where: // value for 'where'
- *   },
- * });
- */
-export function useGetPopularChannelsQuery(
-  baseOptions?: Apollo.QueryHookOptions<GetPopularChannelsQuery, GetPopularChannelsQueryVariables>
-) {
-  const options = { ...defaultOptions, ...baseOptions }
-  return Apollo.useQuery<GetPopularChannelsQuery, GetPopularChannelsQueryVariables>(GetPopularChannelsDocument, options)
-}
-export function useGetPopularChannelsLazyQuery(
-  baseOptions?: Apollo.LazyQueryHookOptions<GetPopularChannelsQuery, GetPopularChannelsQueryVariables>
-) {
-  const options = { ...defaultOptions, ...baseOptions }
-  return Apollo.useLazyQuery<GetPopularChannelsQuery, GetPopularChannelsQueryVariables>(
-    GetPopularChannelsDocument,
-    options
-  )
-}
-export type GetPopularChannelsQueryHookResult = ReturnType<typeof useGetPopularChannelsQuery>
-export type GetPopularChannelsLazyQueryHookResult = ReturnType<typeof useGetPopularChannelsLazyQuery>
-export type GetPopularChannelsQueryResult = Apollo.QueryResult<
-  GetPopularChannelsQuery,
-  GetPopularChannelsQueryVariables
->
 export const GetChannelNftCollectorsDocument = gql`
   query GetChannelNftCollectors($channelId: String!, $orderBy: ChannelNftCollectorsOrderByInput = amount_DESC) {
     channelNftCollectors(channelId: $channelId, orderBy: $orderBy) {
@@ -1518,3 +1541,81 @@ export type GetChannelPaymentEventsQueryResult = Apollo.QueryResult<
   GetChannelPaymentEventsQuery,
   GetChannelPaymentEventsQueryVariables
 >
+export const GetTopSellingChannelsFromThreePeriodsDocument = gql`
+  query GetTopSellingChannelsFromThreePeriods($limit: Int!, $where: ExtendedChannelWhereInput) {
+    topAllTimeSellingChannels: topSellingChannels(where: $where, limit: $limit, periodDays: 0) {
+      amount
+      nftSold
+      channel {
+        ...BasicChannelFields
+      }
+    }
+    topWeekSellingChannels: topSellingChannels(where: $where, limit: $limit, periodDays: 7) {
+      amount
+      nftSold
+      channel {
+        ...BasicChannelFields
+      }
+    }
+    topMonthSellingChannels: topSellingChannels(where: $where, limit: $limit, periodDays: 30) {
+      amount
+      nftSold
+      channel {
+        ...BasicChannelFields
+      }
+    }
+  }
+  ${BasicChannelFieldsFragmentDoc}
+`
+
+/**
+ * __useGetTopSellingChannelsFromThreePeriodsQuery__
+ *
+ * To run a query within a React component, call `useGetTopSellingChannelsFromThreePeriodsQuery` and pass it any options that fit your needs.
+ * When your component renders, `useGetTopSellingChannelsFromThreePeriodsQuery` 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 } = useGetTopSellingChannelsFromThreePeriodsQuery({
+ *   variables: {
+ *      limit: // value for 'limit'
+ *      where: // value for 'where'
+ *   },
+ * });
+ */
+export function useGetTopSellingChannelsFromThreePeriodsQuery(
+  baseOptions: Apollo.QueryHookOptions<
+    GetTopSellingChannelsFromThreePeriodsQuery,
+    GetTopSellingChannelsFromThreePeriodsQueryVariables
+  >
+) {
+  const options = { ...defaultOptions, ...baseOptions }
+  return Apollo.useQuery<
+    GetTopSellingChannelsFromThreePeriodsQuery,
+    GetTopSellingChannelsFromThreePeriodsQueryVariables
+  >(GetTopSellingChannelsFromThreePeriodsDocument, options)
+}
+export function useGetTopSellingChannelsFromThreePeriodsLazyQuery(
+  baseOptions?: Apollo.LazyQueryHookOptions<
+    GetTopSellingChannelsFromThreePeriodsQuery,
+    GetTopSellingChannelsFromThreePeriodsQueryVariables
+  >
+) {
+  const options = { ...defaultOptions, ...baseOptions }
+  return Apollo.useLazyQuery<
+    GetTopSellingChannelsFromThreePeriodsQuery,
+    GetTopSellingChannelsFromThreePeriodsQueryVariables
+  >(GetTopSellingChannelsFromThreePeriodsDocument, options)
+}
+export type GetTopSellingChannelsFromThreePeriodsQueryHookResult = ReturnType<
+  typeof useGetTopSellingChannelsFromThreePeriodsQuery
+>
+export type GetTopSellingChannelsFromThreePeriodsLazyQueryHookResult = ReturnType<
+  typeof useGetTopSellingChannelsFromThreePeriodsLazyQuery
+>
+export type GetTopSellingChannelsFromThreePeriodsQueryResult = Apollo.QueryResult<
+  GetTopSellingChannelsFromThreePeriodsQuery,
+  GetTopSellingChannelsFromThreePeriodsQueryVariables
+>

+ 95 - 18
packages/atlas/src/api/queries/__generated__/nfts.generated.tsx

@@ -1776,9 +1776,13 @@ export type GetNftsConnectionQuery = {
   }
 }
 
-export type GetFeaturedNftsQueryVariables = Types.Exact<{ [key: string]: never }>
+export type GetFeaturedNftsVideosQueryVariables = Types.Exact<{
+  limit?: Types.InputMaybe<Types.Scalars['Int']>
+  where?: Types.InputMaybe<Types.OwnedNftWhereInput>
+  orderBy?: Types.InputMaybe<Array<Types.OwnedNftOrderByInput> | Types.OwnedNftOrderByInput>
+}>
 
-export type GetFeaturedNftsQuery = {
+export type GetFeaturedNftsVideosQuery = {
   __typename?: 'Query'
   ownedNfts: Array<{
     __typename?: 'OwnedNft'
@@ -2377,6 +2381,16 @@ export type GetFeaturedNftsQuery = {
   }>
 }
 
+export type RequestNftFeaturedMutationVariables = Types.Exact<{
+  nftId: Types.Scalars['String']
+  rationale: Types.Scalars['String']
+}>
+
+export type RequestNftFeaturedMutation = {
+  __typename?: 'Mutation'
+  requestNftFeatured: { __typename?: 'NftFeaturedRequstInfo'; rationale: string; nftId: string; createdAt: Date }
+}
+
 export const GetNftDocument = gql`
   query GetNft($id: String!) {
     ownedNftById(id: $id) {
@@ -2518,9 +2532,13 @@ export function useGetNftsConnectionLazyQuery(
 export type GetNftsConnectionQueryHookResult = ReturnType<typeof useGetNftsConnectionQuery>
 export type GetNftsConnectionLazyQueryHookResult = ReturnType<typeof useGetNftsConnectionLazyQuery>
 export type GetNftsConnectionQueryResult = Apollo.QueryResult<GetNftsConnectionQuery, GetNftsConnectionQueryVariables>
-export const GetFeaturedNftsDocument = gql`
-  query GetFeaturedNfts {
-    ownedNfts(limit: 5, orderBy: [createdAt_DESC]) {
+export const GetFeaturedNftsVideosDocument = gql`
+  query GetFeaturedNftsVideos(
+    $limit: Int
+    $where: OwnedNftWhereInput
+    $orderBy: [OwnedNftOrderByInput!] = [createdAt_DESC]
+  ) {
+    ownedNfts(limit: $limit, orderBy: $orderBy, where: $where) {
       ...FullNftFields
       video {
         ...BasicVideoFields
@@ -2536,32 +2554,91 @@ export const GetFeaturedNftsDocument = gql`
 `
 
 /**
- * __useGetFeaturedNftsQuery__
+ * __useGetFeaturedNftsVideosQuery__
  *
- * To run a query within a React component, call `useGetFeaturedNftsQuery` and pass it any options that fit your needs.
- * When your component renders, `useGetFeaturedNftsQuery` returns an object from Apollo Client that contains loading, error, and data properties
+ * To run a query within a React component, call `useGetFeaturedNftsVideosQuery` and pass it any options that fit your needs.
+ * When your component renders, `useGetFeaturedNftsVideosQuery` 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 } = useGetFeaturedNftsQuery({
+ * const { data, loading, error } = useGetFeaturedNftsVideosQuery({
  *   variables: {
+ *      limit: // value for 'limit'
+ *      where: // value for 'where'
+ *      orderBy: // value for 'orderBy'
  *   },
  * });
  */
-export function useGetFeaturedNftsQuery(
-  baseOptions?: Apollo.QueryHookOptions<GetFeaturedNftsQuery, GetFeaturedNftsQueryVariables>
+export function useGetFeaturedNftsVideosQuery(
+  baseOptions?: Apollo.QueryHookOptions<GetFeaturedNftsVideosQuery, GetFeaturedNftsVideosQueryVariables>
 ) {
   const options = { ...defaultOptions, ...baseOptions }
-  return Apollo.useQuery<GetFeaturedNftsQuery, GetFeaturedNftsQueryVariables>(GetFeaturedNftsDocument, options)
+  return Apollo.useQuery<GetFeaturedNftsVideosQuery, GetFeaturedNftsVideosQueryVariables>(
+    GetFeaturedNftsVideosDocument,
+    options
+  )
 }
-export function useGetFeaturedNftsLazyQuery(
-  baseOptions?: Apollo.LazyQueryHookOptions<GetFeaturedNftsQuery, GetFeaturedNftsQueryVariables>
+export function useGetFeaturedNftsVideosLazyQuery(
+  baseOptions?: Apollo.LazyQueryHookOptions<GetFeaturedNftsVideosQuery, GetFeaturedNftsVideosQueryVariables>
 ) {
   const options = { ...defaultOptions, ...baseOptions }
-  return Apollo.useLazyQuery<GetFeaturedNftsQuery, GetFeaturedNftsQueryVariables>(GetFeaturedNftsDocument, options)
+  return Apollo.useLazyQuery<GetFeaturedNftsVideosQuery, GetFeaturedNftsVideosQueryVariables>(
+    GetFeaturedNftsVideosDocument,
+    options
+  )
+}
+export type GetFeaturedNftsVideosQueryHookResult = ReturnType<typeof useGetFeaturedNftsVideosQuery>
+export type GetFeaturedNftsVideosLazyQueryHookResult = ReturnType<typeof useGetFeaturedNftsVideosLazyQuery>
+export type GetFeaturedNftsVideosQueryResult = Apollo.QueryResult<
+  GetFeaturedNftsVideosQuery,
+  GetFeaturedNftsVideosQueryVariables
+>
+export const RequestNftFeaturedDocument = gql`
+  mutation RequestNftFeatured($nftId: String!, $rationale: String!) {
+    requestNftFeatured(nftId: $nftId, rationale: $rationale) {
+      rationale
+      nftId
+      createdAt
+    }
+  }
+`
+export type RequestNftFeaturedMutationFn = Apollo.MutationFunction<
+  RequestNftFeaturedMutation,
+  RequestNftFeaturedMutationVariables
+>
+
+/**
+ * __useRequestNftFeaturedMutation__
+ *
+ * To run a mutation, you first call `useRequestNftFeaturedMutation` within a React component and pass it any options that fit your needs.
+ * When your component renders, `useRequestNftFeaturedMutation` returns a tuple that includes:
+ * - A mutate function that you can call at any time to execute the mutation
+ * - An object with fields that represent the current status of the mutation's execution
+ *
+ * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
+ *
+ * @example
+ * const [requestNftFeaturedMutation, { data, loading, error }] = useRequestNftFeaturedMutation({
+ *   variables: {
+ *      nftId: // value for 'nftId'
+ *      rationale: // value for 'rationale'
+ *   },
+ * });
+ */
+export function useRequestNftFeaturedMutation(
+  baseOptions?: Apollo.MutationHookOptions<RequestNftFeaturedMutation, RequestNftFeaturedMutationVariables>
+) {
+  const options = { ...defaultOptions, ...baseOptions }
+  return Apollo.useMutation<RequestNftFeaturedMutation, RequestNftFeaturedMutationVariables>(
+    RequestNftFeaturedDocument,
+    options
+  )
 }
-export type GetFeaturedNftsQueryHookResult = ReturnType<typeof useGetFeaturedNftsQuery>
-export type GetFeaturedNftsLazyQueryHookResult = ReturnType<typeof useGetFeaturedNftsLazyQuery>
-export type GetFeaturedNftsQueryResult = Apollo.QueryResult<GetFeaturedNftsQuery, GetFeaturedNftsQueryVariables>
+export type RequestNftFeaturedMutationHookResult = ReturnType<typeof useRequestNftFeaturedMutation>
+export type RequestNftFeaturedMutationResult = Apollo.MutationResult<RequestNftFeaturedMutation>
+export type RequestNftFeaturedMutationOptions = Apollo.BaseMutationOptions<
+  RequestNftFeaturedMutation,
+  RequestNftFeaturedMutationVariables
+>

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 517 - 507
packages/atlas/src/api/queries/__generated__/notifications.generated.tsx


+ 0 - 768
packages/atlas/src/api/queries/__generated__/videos.generated.tsx

@@ -2398,668 +2398,6 @@ export type GetMostViewedVideosConnectionQuery = {
   }
 }
 
-export type GetTop10VideosThisWeekQueryVariables = Types.Exact<{
-  where?: Types.InputMaybe<Types.VideoWhereInput>
-}>
-
-export type GetTop10VideosThisWeekQuery = {
-  __typename?: 'Query'
-  mostViewedVideosConnection: {
-    __typename?: 'VideosConnection'
-    edges: Array<{
-      __typename?: 'VideoEdge'
-      node: {
-        __typename?: 'Video'
-        id: string
-        title?: string | null
-        viewsNum: number
-        createdAt: Date
-        duration?: number | null
-        reactionsCount: number
-        commentsCount: number
-        channel: {
-          __typename?: 'Channel'
-          id: string
-          title?: string | null
-          description?: string | null
-          createdAt: Date
-          followsNum: number
-          rewardAccount: string
-          channelStateBloatBond: string
-          avatarPhoto?: {
-            __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
-        }
-        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
-        nft?: {
-          __typename?: 'OwnedNft'
-          id: string
-          createdAt: Date
-          creatorRoyalty?: number | null
-          lastSaleDate?: Date | null
-          lastSalePrice?: string | null
-          owner:
-            | {
-                __typename: 'NftOwnerChannel'
-                channel: {
-                  __typename?: 'Channel'
-                  id: string
-                  title?: string | null
-                  description?: string | null
-                  createdAt: Date
-                  followsNum: number
-                  rewardAccount: string
-                  channelStateBloatBond: string
-                  ownerMember?: {
-                    __typename?: 'Membership'
-                    id: string
-                    handle: string
-                    metadata?: {
-                      __typename?: 'MemberMetadata'
-                      about?: string | null
-                      avatar?:
-                        | {
-                            __typename?: 'AvatarObject'
-                            avatarObject: {
-                              __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
-                            }
-                          }
-                        | { __typename?: 'AvatarUri'; avatarUri: string }
-                        | null
-                    } | null
-                  } | null
-                  avatarPhoto?: {
-                    __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: 'NftOwnerMember'
-                member: {
-                  __typename?: 'Membership'
-                  id: string
-                  handle: string
-                  metadata?: {
-                    __typename?: 'MemberMetadata'
-                    about?: string | null
-                    avatar?:
-                      | {
-                          __typename?: 'AvatarObject'
-                          avatarObject: {
-                            __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
-                          }
-                        }
-                      | { __typename?: 'AvatarUri'; avatarUri: string }
-                      | null
-                  } | null
-                }
-              }
-          transactionalStatus?:
-            | {
-                __typename: 'TransactionalStatusAuction'
-                auction: {
-                  __typename?: 'Auction'
-                  id: string
-                  isCompleted: boolean
-                  buyNowPrice?: string | null
-                  startingPrice: string
-                  startsAtBlock: number
-                  endedAtBlock?: number | null
-                  auctionType:
-                    | {
-                        __typename: 'AuctionTypeEnglish'
-                        duration: number
-                        extensionPeriod: number
-                        minimalBidStep: string
-                        plannedEndAtBlock: number
-                      }
-                    | { __typename: 'AuctionTypeOpen'; bidLockDuration: number }
-                  topBid?: {
-                    __typename?: 'Bid'
-                    amount: string
-                    createdAt: Date
-                    isCanceled: boolean
-                    createdInBlock: number
-                    id: string
-                    bidder: {
-                      __typename?: 'Membership'
-                      id: string
-                      handle: string
-                      metadata?: {
-                        __typename?: 'MemberMetadata'
-                        about?: string | null
-                        avatar?:
-                          | {
-                              __typename?: 'AvatarObject'
-                              avatarObject: {
-                                __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
-                              }
-                            }
-                          | { __typename?: 'AvatarUri'; avatarUri: string }
-                          | null
-                      } | null
-                    }
-                  } | null
-                  bids: Array<{
-                    __typename?: 'Bid'
-                    amount: string
-                    createdAt: Date
-                    isCanceled: boolean
-                    createdInBlock: number
-                    id: string
-                    bidder: {
-                      __typename?: 'Membership'
-                      id: string
-                      handle: string
-                      metadata?: {
-                        __typename?: 'MemberMetadata'
-                        about?: string | null
-                        avatar?:
-                          | {
-                              __typename?: 'AvatarObject'
-                              avatarObject: {
-                                __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
-                              }
-                            }
-                          | { __typename?: 'AvatarUri'; avatarUri: string }
-                          | null
-                      } | null
-                    }
-                  }>
-                  whitelistedMembers: Array<{
-                    __typename?: 'AuctionWhitelistedMember'
-                    member: {
-                      __typename?: 'Membership'
-                      id: string
-                      handle: string
-                      metadata?: {
-                        __typename?: 'MemberMetadata'
-                        about?: string | null
-                        avatar?:
-                          | {
-                              __typename?: 'AvatarObject'
-                              avatarObject: {
-                                __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
-                              }
-                            }
-                          | { __typename?: 'AvatarUri'; avatarUri: string }
-                          | null
-                      } | null
-                    }
-                  }>
-                }
-              }
-            | { __typename: 'TransactionalStatusBuyNow'; price: string }
-            | { __typename: 'TransactionalStatusIdle' }
-            | { __typename: 'TransactionalStatusInitiatedOfferToMember' }
-            | null
-        } | null
-      }
-    }>
-  }
-}
-
-export type GetTop10VideosThisMonthQueryVariables = Types.Exact<{
-  where?: Types.InputMaybe<Types.VideoWhereInput>
-}>
-
-export type GetTop10VideosThisMonthQuery = {
-  __typename?: 'Query'
-  mostViewedVideosConnection: {
-    __typename?: 'VideosConnection'
-    edges: Array<{
-      __typename?: 'VideoEdge'
-      node: {
-        __typename?: 'Video'
-        id: string
-        title?: string | null
-        viewsNum: number
-        createdAt: Date
-        duration?: number | null
-        reactionsCount: number
-        commentsCount: number
-        channel: {
-          __typename?: 'Channel'
-          id: string
-          title?: string | null
-          description?: string | null
-          createdAt: Date
-          followsNum: number
-          rewardAccount: string
-          channelStateBloatBond: string
-          avatarPhoto?: {
-            __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
-        }
-        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
-        nft?: {
-          __typename?: 'OwnedNft'
-          id: string
-          createdAt: Date
-          creatorRoyalty?: number | null
-          lastSaleDate?: Date | null
-          lastSalePrice?: string | null
-          owner:
-            | {
-                __typename: 'NftOwnerChannel'
-                channel: {
-                  __typename?: 'Channel'
-                  id: string
-                  title?: string | null
-                  description?: string | null
-                  createdAt: Date
-                  followsNum: number
-                  rewardAccount: string
-                  channelStateBloatBond: string
-                  ownerMember?: {
-                    __typename?: 'Membership'
-                    id: string
-                    handle: string
-                    metadata?: {
-                      __typename?: 'MemberMetadata'
-                      about?: string | null
-                      avatar?:
-                        | {
-                            __typename?: 'AvatarObject'
-                            avatarObject: {
-                              __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
-                            }
-                          }
-                        | { __typename?: 'AvatarUri'; avatarUri: string }
-                        | null
-                    } | null
-                  } | null
-                  avatarPhoto?: {
-                    __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: 'NftOwnerMember'
-                member: {
-                  __typename?: 'Membership'
-                  id: string
-                  handle: string
-                  metadata?: {
-                    __typename?: 'MemberMetadata'
-                    about?: string | null
-                    avatar?:
-                      | {
-                          __typename?: 'AvatarObject'
-                          avatarObject: {
-                            __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
-                          }
-                        }
-                      | { __typename?: 'AvatarUri'; avatarUri: string }
-                      | null
-                  } | null
-                }
-              }
-          transactionalStatus?:
-            | {
-                __typename: 'TransactionalStatusAuction'
-                auction: {
-                  __typename?: 'Auction'
-                  id: string
-                  isCompleted: boolean
-                  buyNowPrice?: string | null
-                  startingPrice: string
-                  startsAtBlock: number
-                  endedAtBlock?: number | null
-                  auctionType:
-                    | {
-                        __typename: 'AuctionTypeEnglish'
-                        duration: number
-                        extensionPeriod: number
-                        minimalBidStep: string
-                        plannedEndAtBlock: number
-                      }
-                    | { __typename: 'AuctionTypeOpen'; bidLockDuration: number }
-                  topBid?: {
-                    __typename?: 'Bid'
-                    amount: string
-                    createdAt: Date
-                    isCanceled: boolean
-                    createdInBlock: number
-                    id: string
-                    bidder: {
-                      __typename?: 'Membership'
-                      id: string
-                      handle: string
-                      metadata?: {
-                        __typename?: 'MemberMetadata'
-                        about?: string | null
-                        avatar?:
-                          | {
-                              __typename?: 'AvatarObject'
-                              avatarObject: {
-                                __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
-                              }
-                            }
-                          | { __typename?: 'AvatarUri'; avatarUri: string }
-                          | null
-                      } | null
-                    }
-                  } | null
-                  bids: Array<{
-                    __typename?: 'Bid'
-                    amount: string
-                    createdAt: Date
-                    isCanceled: boolean
-                    createdInBlock: number
-                    id: string
-                    bidder: {
-                      __typename?: 'Membership'
-                      id: string
-                      handle: string
-                      metadata?: {
-                        __typename?: 'MemberMetadata'
-                        about?: string | null
-                        avatar?:
-                          | {
-                              __typename?: 'AvatarObject'
-                              avatarObject: {
-                                __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
-                              }
-                            }
-                          | { __typename?: 'AvatarUri'; avatarUri: string }
-                          | null
-                      } | null
-                    }
-                  }>
-                  whitelistedMembers: Array<{
-                    __typename?: 'AuctionWhitelistedMember'
-                    member: {
-                      __typename?: 'Membership'
-                      id: string
-                      handle: string
-                      metadata?: {
-                        __typename?: 'MemberMetadata'
-                        about?: string | null
-                        avatar?:
-                          | {
-                              __typename?: 'AvatarObject'
-                              avatarObject: {
-                                __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
-                              }
-                            }
-                          | { __typename?: 'AvatarUri'; avatarUri: string }
-                          | null
-                      } | null
-                    }
-                  }>
-                }
-              }
-            | { __typename: 'TransactionalStatusBuyNow'; price: string }
-            | { __typename: 'TransactionalStatusIdle' }
-            | { __typename: 'TransactionalStatusInitiatedOfferToMember' }
-            | null
-        } | null
-      }
-    }>
-  }
-}
-
 export type GetVideosCountQueryVariables = Types.Exact<{
   where?: Types.InputMaybe<Types.VideoWhereInput>
 }>
@@ -3441,112 +2779,6 @@ export type GetMostViewedVideosConnectionQueryResult = Apollo.QueryResult<
   GetMostViewedVideosConnectionQuery,
   GetMostViewedVideosConnectionQueryVariables
 >
-export const GetTop10VideosThisWeekDocument = gql`
-  query GetTop10VideosThisWeek($where: VideoWhereInput) {
-    mostViewedVideosConnection(limit: 10, where: $where, periodDays: 7, orderBy: viewsNum_DESC) {
-      edges {
-        node {
-          ...BasicVideoFields
-        }
-      }
-    }
-  }
-  ${BasicVideoFieldsFragmentDoc}
-`
-
-/**
- * __useGetTop10VideosThisWeekQuery__
- *
- * To run a query within a React component, call `useGetTop10VideosThisWeekQuery` and pass it any options that fit your needs.
- * When your component renders, `useGetTop10VideosThisWeekQuery` 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 } = useGetTop10VideosThisWeekQuery({
- *   variables: {
- *      where: // value for 'where'
- *   },
- * });
- */
-export function useGetTop10VideosThisWeekQuery(
-  baseOptions?: Apollo.QueryHookOptions<GetTop10VideosThisWeekQuery, GetTop10VideosThisWeekQueryVariables>
-) {
-  const options = { ...defaultOptions, ...baseOptions }
-  return Apollo.useQuery<GetTop10VideosThisWeekQuery, GetTop10VideosThisWeekQueryVariables>(
-    GetTop10VideosThisWeekDocument,
-    options
-  )
-}
-export function useGetTop10VideosThisWeekLazyQuery(
-  baseOptions?: Apollo.LazyQueryHookOptions<GetTop10VideosThisWeekQuery, GetTop10VideosThisWeekQueryVariables>
-) {
-  const options = { ...defaultOptions, ...baseOptions }
-  return Apollo.useLazyQuery<GetTop10VideosThisWeekQuery, GetTop10VideosThisWeekQueryVariables>(
-    GetTop10VideosThisWeekDocument,
-    options
-  )
-}
-export type GetTop10VideosThisWeekQueryHookResult = ReturnType<typeof useGetTop10VideosThisWeekQuery>
-export type GetTop10VideosThisWeekLazyQueryHookResult = ReturnType<typeof useGetTop10VideosThisWeekLazyQuery>
-export type GetTop10VideosThisWeekQueryResult = Apollo.QueryResult<
-  GetTop10VideosThisWeekQuery,
-  GetTop10VideosThisWeekQueryVariables
->
-export const GetTop10VideosThisMonthDocument = gql`
-  query GetTop10VideosThisMonth($where: VideoWhereInput) {
-    mostViewedVideosConnection(limit: 10, where: $where, periodDays: 30, orderBy: viewsNum_DESC) {
-      edges {
-        node {
-          ...BasicVideoFields
-        }
-      }
-    }
-  }
-  ${BasicVideoFieldsFragmentDoc}
-`
-
-/**
- * __useGetTop10VideosThisMonthQuery__
- *
- * To run a query within a React component, call `useGetTop10VideosThisMonthQuery` and pass it any options that fit your needs.
- * When your component renders, `useGetTop10VideosThisMonthQuery` 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 } = useGetTop10VideosThisMonthQuery({
- *   variables: {
- *      where: // value for 'where'
- *   },
- * });
- */
-export function useGetTop10VideosThisMonthQuery(
-  baseOptions?: Apollo.QueryHookOptions<GetTop10VideosThisMonthQuery, GetTop10VideosThisMonthQueryVariables>
-) {
-  const options = { ...defaultOptions, ...baseOptions }
-  return Apollo.useQuery<GetTop10VideosThisMonthQuery, GetTop10VideosThisMonthQueryVariables>(
-    GetTop10VideosThisMonthDocument,
-    options
-  )
-}
-export function useGetTop10VideosThisMonthLazyQuery(
-  baseOptions?: Apollo.LazyQueryHookOptions<GetTop10VideosThisMonthQuery, GetTop10VideosThisMonthQueryVariables>
-) {
-  const options = { ...defaultOptions, ...baseOptions }
-  return Apollo.useLazyQuery<GetTop10VideosThisMonthQuery, GetTop10VideosThisMonthQueryVariables>(
-    GetTop10VideosThisMonthDocument,
-    options
-  )
-}
-export type GetTop10VideosThisMonthQueryHookResult = ReturnType<typeof useGetTop10VideosThisMonthQuery>
-export type GetTop10VideosThisMonthLazyQueryHookResult = ReturnType<typeof useGetTop10VideosThisMonthLazyQuery>
-export type GetTop10VideosThisMonthQueryResult = Apollo.QueryResult<
-  GetTop10VideosThisMonthQuery,
-  GetTop10VideosThisMonthQueryVariables
->
 export const GetVideosCountDocument = gql`
   query GetVideosCount($where: VideoWhereInput) {
     videosConnection(where: $where, orderBy: [createdAt_ASC]) {

+ 26 - 9
packages/atlas/src/api/queries/channels.graphql

@@ -90,15 +90,6 @@ query GetDiscoverChannels($where: ExtendedChannelWhereInput) {
   }
 }
 
-query GetPopularChannels($where: ExtendedChannelWhereInput) {
-  # CHANGE: Replacement for overly-specific `popularChannels` query
-  extendedChannels(where: $where, orderBy: videoViewsNum_DESC, limit: 15) {
-    channel {
-      ...BasicChannelFields
-    }
-  }
-}
-
 # CHANGE: Replaced with a custom `channelNftCollectors` query taking `channelId` argument (instead of `ChannelNftCollectorsWhereInput`)
 query GetChannelNftCollectors($channelId: String!, $orderBy: ChannelNftCollectorsOrderByInput = amount_DESC) {
   channelNftCollectors(channelId: $channelId, orderBy: $orderBy) {
@@ -304,3 +295,29 @@ query GetChannelPaymentEvents($channelId: String) {
     }
   }
 }
+
+query GetTopSellingChannelsFromThreePeriods($limit: Int!, $where: ExtendedChannelWhereInput) {
+  topAllTimeSellingChannels: topSellingChannels(where: $where, limit: $limit, periodDays: 0) {
+    amount
+    nftSold
+    channel {
+      ...BasicChannelFields
+    }
+  }
+  # week
+  topWeekSellingChannels: topSellingChannels(where: $where, limit: $limit, periodDays: 7) {
+    amount
+    nftSold
+    channel {
+      ...BasicChannelFields
+    }
+  }
+  # month
+  topMonthSellingChannels: topSellingChannels(where: $where, limit: $limit, periodDays: 30) {
+    amount
+    nftSold
+    channel {
+      ...BasicChannelFields
+    }
+  }
+}

+ 14 - 2
packages/atlas/src/api/queries/nfts.graphql

@@ -40,8 +40,12 @@ query GetNftsConnection(
   }
 }
 
-query GetFeaturedNfts {
-  ownedNfts(limit: 5, orderBy: [createdAt_DESC]) {
+query GetFeaturedNftsVideos(
+  $limit: Int
+  $where: OwnedNftWhereInput
+  $orderBy: [OwnedNftOrderByInput!] = [createdAt_DESC]
+) {
+  ownedNfts(limit: $limit, orderBy: $orderBy, where: $where) {
     ...FullNftFields
     video {
       ...BasicVideoFields
@@ -51,3 +55,11 @@ query GetFeaturedNfts {
     }
   }
 }
+
+mutation RequestNftFeatured($nftId: String!, $rationale: String!) {
+  requestNftFeatured(nftId: $nftId, rationale: $rationale) {
+    rationale
+    nftId
+    createdAt
+  }
+}

+ 270 - 251
packages/atlas/src/api/queries/notifications.graphql

@@ -6,30 +6,56 @@
 
 # Note that in this case `orderBy` and `limit` now applies to all events together, not on per-type basis
 
-query GetNotifications($memberId: String!, $limit: Int!) {
-  notifications(
-    limit: $limit
+query GetNotificationsConnection($memberId: String!, $first: Int!, $after: String) {
+  notificationsConnection(
+    first: $first
+    after: $after
     orderBy: event_timestamp_DESC
-    where: { member: { id_eq: $memberId } } # CHANGE: Simplified filtering
+    where: { member: { id_eq: $memberId } }
   ) {
-    # CHANGE: The actual `event` is now nested as a property of `Notification`
-    event {
-      id
-      timestamp # CHANGE: `timestamp` now used instead of `createdAt` (which is no longer available)
-      inBlock
-      data {
-        ... on AuctionBidMadeEventData {
-          bid {
-            amount
-            bidder {
-              ...BasicMembershipFields
+    pageInfo {
+      hasNextPage
+      endCursor
+    }
+    totalCount
+    edges {
+      cursor
+      node {
+        event {
+          id
+          timestamp
+          inBlock
+          data {
+            ... on AuctionBidMadeEventData {
+              bid {
+                amount
+                bidder {
+                  ...BasicMembershipFields
+                }
+                previousTopBid {
+                  bidder {
+                    ...BasicMembershipFields
+                  }
+                }
+                auction {
+                  nft {
+                    video {
+                      id
+                      title
+                    }
+                  }
+                }
+              }
+              nftOwner {
+                ...BasicNftOwnerFields
+              }
             }
-            previousTopBid {
-              bidder {
+
+            ... on NftBoughtEventData {
+              buyer {
                 ...BasicMembershipFields
               }
-            }
-            auction {
+              price
               nft {
                 video {
                   id
@@ -37,112 +63,91 @@ query GetNotifications($memberId: String!, $limit: Int!) {
                 }
               }
             }
-          }
-          nftOwner {
-            ...BasicNftOwnerFields
-          }
-        }
 
-        ... on NftBoughtEventData {
-          buyer {
-            ...BasicMembershipFields
-          }
-          price
-          nft {
-            video {
-              id
-              title
+            ... on BidMadeCompletingAuctionEventData {
+              winningBid {
+                bidder {
+                  ...BasicMembershipFields
+                }
+                amount
+                nft {
+                  video {
+                    id
+                    title
+                  }
+                }
+              }
+              previousNftOwner {
+                ...BasicNftOwnerFields
+              }
             }
-          }
-        }
 
-        ... on BidMadeCompletingAuctionEventData {
-          winningBid {
-            bidder {
-              ...BasicMembershipFields
-            }
-            amount
-            nft {
-              video {
-                id
-                title
+            ... on OpenAuctionBidAcceptedEventData {
+              winningBid {
+                amount
+                bidder {
+                  ...BasicMembershipFields
+                }
+                auction {
+                  nft {
+                    video {
+                      id
+                      title
+                    }
+                  }
+                }
+              }
+              previousNftOwner {
+                ...BasicNftOwnerFields
               }
             }
-          }
-          previousNftOwner {
-            ...BasicNftOwnerFields
-          }
-        }
 
-        ... on OpenAuctionBidAcceptedEventData {
-          winningBid {
-            amount
-            bidder {
-              ...BasicMembershipFields
-            }
-            auction {
-              nft {
-                video {
-                  id
-                  title
+            ... on EnglishAuctionSettledEventData {
+              winningBid {
+                bidder {
+                  ...BasicMembershipFields
                 }
+                auction {
+                  nft {
+                    video {
+                      id
+                      title
+                    }
+                  }
+                }
+              }
+              previousNftOwner {
+                ...BasicNftOwnerFields
               }
             }
-          }
-          previousNftOwner {
-            ...BasicNftOwnerFields
-          }
-        }
 
-        ... on EnglishAuctionSettledEventData {
-          winningBid {
-            bidder {
-              ...BasicMembershipFields
-            }
-            auction {
-              nft {
+            ... on CommentCreatedEventData {
+              comment {
+                id
                 video {
                   id
                   title
                 }
+                parentComment {
+                  id
+                }
+                author {
+                  ...BasicMembershipFields
+                }
               }
             }
           }
-          previousNftOwner {
-            ...BasicNftOwnerFields
-          }
-        }
-
-        ... on CommentCreatedEventData {
-          comment {
-            id
-            video {
-              id
-              title
-            }
-            parentComment {
-              id
-            }
-            author {
-              ...BasicMembershipFields
-            }
-          }
         }
       }
     }
   }
 }
 
-# CHANGE: ID is now `String`
 query GetNftHistory($nftId: String!) {
-  nftHistoryEntries(
-    orderBy: event_timestamp_DESC # CHANGE: `event_timestamp` now used instead of `createdAt` (which is no longer available)
-    where: { nft: { id_eq: $nftId } } # CHANGE: Simplified filtering
-  ) {
-    # CHANGE: The actual `event` is now nested as a property of `NftHistoryEntry`
+  nftHistoryEntries(orderBy: event_timestamp_DESC, where: { nft: { id_eq: $nftId } }) {
     event {
       id
-      timestamp # CHANGE: `timestamp` now used instead of `createdAt` (which is no longer available)
+      timestamp
       data {
         ... on NftIssuedEventData {
           nftOwner {
@@ -249,8 +254,12 @@ query GetNftHistory($nftId: String!) {
   }
 }
 
-query GetNftActivities($memberId: String!, $limit: Int!, $orderBy: [NftActivityOrderByInput!] = event_timestamp_DESC) {
-  # COUNT queries
+query GetNftActivities(
+  $memberId: String!
+  $first: Int!
+  $after: String
+  $orderBy: [NftActivityOrderByInput!] = event_timestamp_DESC
+) {
   nftsBought: nftActivitiesConnection(
     where: {
       event: {
@@ -329,200 +338,210 @@ query GetNftActivities($memberId: String!, $limit: Int!, $orderBy: [NftActivityO
     totalCount
   }
 
-  # DATA query
-  nftActivities(
-    limit: $limit
+  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`
-    event {
-      id
-      timestamp # CHANGE: `timestamp` now used instead of `createdAt` (which is no longer available)
-      inBlock
-      data {
-        ... on AuctionBidMadeEventData {
-          nftOwner {
-            ...BasicNftOwnerFields
-          }
-          bid {
-            amount
-            bidder {
-              ...BasicMembershipFields
+    totalCount
+    pageInfo {
+      endCursor
+      hasNextPage
+    }
+    edges {
+      cursor
+      node {
+        event {
+          id
+          timestamp # CHANGE: `timestamp` now used instead of `createdAt` (which is no longer available)
+          inBlock
+          data {
+            ... on AuctionBidMadeEventData {
+              nftOwner {
+                ...BasicNftOwnerFields
+              }
+              bid {
+                amount
+                bidder {
+                  ...BasicMembershipFields
+                }
+                previousTopBid {
+                  bidder {
+                    ...BasicMembershipFields
+                  }
+                }
+                auction {
+                  nft {
+                    video {
+                      ...BasicVideoActivityFields
+                    }
+                  }
+                }
+              }
             }
-            previousTopBid {
-              bidder {
-                ...BasicMembershipFields
+            ... on EnglishAuctionSettledEventData {
+              previousNftOwner {
+                ...BasicNftOwnerFields
+              }
+              winningBid {
+                bidder {
+                  ...BasicMembershipFields
+                }
+                amount
+                auction {
+                  nft {
+                    video {
+                      ...BasicVideoActivityFields
+                    }
+                  }
+                }
               }
             }
-            auction {
+            ... on NftBoughtEventData {
+              buyer {
+                ...BasicMembershipFields
+              }
+              previousNftOwner {
+                ...BasicNftOwnerFields
+              }
               nft {
                 video {
                   ...BasicVideoActivityFields
                 }
               }
+              price
             }
-          }
-        }
-        ... on EnglishAuctionSettledEventData {
-          previousNftOwner {
-            ...BasicNftOwnerFields
-          }
-          winningBid {
-            bidder {
-              ...BasicMembershipFields
+            ... on BidMadeCompletingAuctionEventData {
+              previousNftOwner {
+                ...BasicNftOwnerFields
+              }
+              winningBid {
+                bidder {
+                  ...BasicMembershipFields
+                }
+                auction {
+                  nft {
+                    video {
+                      ...BasicVideoActivityFields
+                    }
+                  }
+                }
+                amount
+              }
             }
-            amount
-            auction {
-              nft {
-                video {
-                  ...BasicVideoActivityFields
+            ... on OpenAuctionBidAcceptedEventData {
+              winningBid {
+                amount
+                bidder {
+                  ...BasicMembershipFields
+                }
+                auction {
+                  nft {
+                    video {
+                      ...BasicVideoActivityFields
+                    }
+                  }
                 }
               }
+              previousNftOwner {
+                ...BasicNftOwnerFields
+              }
             }
-          }
-        }
-        ... on NftBoughtEventData {
-          buyer {
-            ...BasicMembershipFields
-          }
-          previousNftOwner {
-            ...BasicNftOwnerFields
-          }
-          nft {
-            video {
-              ...BasicVideoActivityFields
+            ... on EnglishAuctionStartedEventData {
+              auction {
+                nft {
+                  video {
+                    ...BasicVideoActivityFields
+                  }
+                }
+              }
+              nftOwner {
+                ...BasicNftOwnerFields
+              }
             }
-          }
-          price
-        }
-        ... on BidMadeCompletingAuctionEventData {
-          previousNftOwner {
-            ...BasicNftOwnerFields
-          }
-          winningBid {
-            bidder {
-              ...BasicMembershipFields
+            ... on OpenAuctionStartedEventData {
+              auction {
+                nft {
+                  video {
+                    ...BasicVideoActivityFields
+                  }
+                }
+              }
+              nftOwner {
+                ...BasicNftOwnerFields
+              }
             }
-            auction {
+            ... on NftSellOrderMadeEventData {
+              price
               nft {
                 video {
                   ...BasicVideoActivityFields
                 }
               }
+              nftOwner {
+                ...BasicNftOwnerFields
+              }
             }
-            amount
-          }
-        }
-        ... on OpenAuctionBidAcceptedEventData {
-          winningBid {
-            amount
-            bidder {
-              ...BasicMembershipFields
+            ... on AuctionBidCanceledEventData {
+              member {
+                ...BasicMembershipFields
+              }
+              bid {
+                auction {
+                  nft {
+                    video {
+                      ...BasicVideoActivityFields
+                    }
+                  }
+                }
+              }
             }
-            auction {
+            ... on BuyNowCanceledEventData {
               nft {
                 video {
                   ...BasicVideoActivityFields
                 }
               }
-            }
-          }
-          previousNftOwner {
-            ...BasicNftOwnerFields
-          }
-        }
-        ... on EnglishAuctionStartedEventData {
-          auction {
-            nft {
-              video {
-                ...BasicVideoActivityFields
+              nftOwner {
+                ...BasicNftOwnerFields
               }
             }
-          }
-          nftOwner {
-            ...BasicNftOwnerFields
-          }
-        }
-        ... on OpenAuctionStartedEventData {
-          auction {
-            nft {
-              video {
-                ...BasicVideoActivityFields
+            ... on AuctionCanceledEventData {
+              auction {
+                nft {
+                  video {
+                    ...BasicVideoActivityFields
+                  }
+                }
+              }
+              nftOwner {
+                ...BasicNftOwnerFields
               }
             }
-          }
-          nftOwner {
-            ...BasicNftOwnerFields
-          }
-        }
-        ... on NftSellOrderMadeEventData {
-          price
-          nft {
-            video {
-              ...BasicVideoActivityFields
-            }
-          }
-          nftOwner {
-            ...BasicNftOwnerFields
-          }
-        }
-        ... on AuctionBidCanceledEventData {
-          member {
-            ...BasicMembershipFields
-          }
-          bid {
-            auction {
+            ... on BuyNowPriceUpdatedEventData {
+              newPrice
               nft {
                 video {
                   ...BasicVideoActivityFields
                 }
               }
-            }
-          }
-        }
-        ... on BuyNowCanceledEventData {
-          nft {
-            video {
-              ...BasicVideoActivityFields
-            }
-          }
-          nftOwner {
-            ...BasicNftOwnerFields
-          }
-        }
-        ... on AuctionCanceledEventData {
-          auction {
-            nft {
-              video {
-                ...BasicVideoActivityFields
+              nftOwner {
+                ...BasicNftOwnerFields
               }
             }
-          }
-          nftOwner {
-            ...BasicNftOwnerFields
-          }
-        }
-        ... on BuyNowPriceUpdatedEventData {
-          newPrice
-          nft {
-            video {
-              ...BasicVideoActivityFields
-            }
-          }
-          nftOwner {
-            ...BasicNftOwnerFields
-          }
-        }
-        ... on NftIssuedEventData {
-          nft {
-            video {
-              ...BasicVideoActivityFields
+            ... on NftIssuedEventData {
+              nft {
+                video {
+                  ...BasicVideoActivityFields
+                }
+              }
+              nftOwner {
+                ...BasicNftOwnerFields
+              }
             }
           }
-          nftOwner {
-            ...BasicNftOwnerFields
-          }
         }
       }
     }

+ 0 - 22
packages/atlas/src/api/queries/videos.graphql

@@ -107,28 +107,6 @@ query GetMostViewedVideosConnection(
   }
 }
 
-query GetTop10VideosThisWeek($where: VideoWhereInput) {
-  # CHANGE: Replaced overly-specific `top10VideosThisWeek` with more generic `mostViewedVideosConnection` query
-  mostViewedVideosConnection(limit: 10, where: $where, periodDays: 7, orderBy: viewsNum_DESC) {
-    edges {
-      node {
-        ...BasicVideoFields
-      }
-    }
-  }
-}
-
-query GetTop10VideosThisMonth($where: VideoWhereInput) {
-  # CHANGE: Replaced overly-specific `top10VideosThisMonth` with more generic `mostViewedVideosConnection` query
-  mostViewedVideosConnection(limit: 10, where: $where, periodDays: 30, orderBy: viewsNum_DESC) {
-    edges {
-      node {
-        ...BasicVideoFields
-      }
-    }
-  }
-}
-
 query GetVideosCount($where: VideoWhereInput) {
   videosConnection(where: $where, orderBy: [createdAt_ASC]) {
     totalCount

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 0 - 0
packages/atlas/src/api/schemas/orion.json


+ 16 - 0
packages/atlas/src/assets/icons/ActionCreatorToken.tsx

@@ -0,0 +1,16 @@
+// THIS FILE WAS AUTOGENERATED BY SVGR. DO NOT MODIFY IT MANUALLY;
+import { Ref, SVGProps, forwardRef, memo } from 'react'
+
+const SvgActionCreatorToken = forwardRef((props: SVGProps<SVGSVGElement>, ref: Ref<SVGSVGElement>) => (
+  <svg width={16} height={16} viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" ref={ref} {...props}>
+    <path
+      fillRule="evenodd"
+      clipRule="evenodd"
+      d="M8 14.5a6.5 6.5 0 1 0 0-13 6.5 6.5 0 0 0 0 13Zm1.968-7.667h1.782a3.147 3.147 0 0 0-.39-1.202c-.201-.35-.462-.645-.782-.886a3.486 3.486 0 0 0-1.103-.555A4.551 4.551 0 0 0 8.132 4c-.732 0-1.391.157-1.978.471a3.45 3.45 0 0 0-1.391 1.362C4.42 6.428 4.25 7.15 4.25 8c0 .847.168 1.568.505 2.164.339.595.8 1.05 1.383 1.364.586.315 1.25.472 1.994.472.535 0 1.014-.075 1.437-.224a3.52 3.52 0 0 0 1.098-.616c.31-.261.555-.557.737-.886.185-.332.3-.677.346-1.034l-1.782-.008a1.55 1.55 0 0 1-.61.97 1.77 1.77 0 0 1-.542.254c-.2.059-.42.088-.655.088-.42 0-.791-.097-1.111-.29A1.931 1.931 0 0 1 6.3 9.4c-.176-.377-.265-.844-.265-1.399 0-.54.089-.999.265-1.376.176-.38.425-.67.745-.867.32-.2.696-.3 1.127-.3.239 0 .46.031.663.094.206.064.388.155.546.274.157.12.286.264.386.434.1.167.167.358.2.574Z"
+      fill="#F4F6F8"
+    />
+  </svg>
+))
+SvgActionCreatorToken.displayName = 'SvgActionCreatorToken'
+const Memo = memo(SvgActionCreatorToken)
+export { Memo as SvgActionCreatorToken }

+ 16 - 0
packages/atlas/src/assets/icons/ActionVerified.tsx

@@ -0,0 +1,16 @@
+// THIS FILE WAS AUTOGENERATED BY SVGR. DO NOT MODIFY IT MANUALLY;
+import { Ref, SVGProps, forwardRef, memo } from 'react'
+
+const SvgActionVerified = forwardRef((props: SVGProps<SVGSVGElement>, ref: Ref<SVGSVGElement>) => (
+  <svg width={16} height={16} viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" ref={ref} {...props}>
+    <path
+      fillRule="evenodd"
+      clipRule="evenodd"
+      d="M8.698 1.28a1 1 0 0 0-1.462 0l-1.1 1.179a1 1 0 0 1-.765.316L3.76 2.721a1 1 0 0 0-1.034 1.033l.055 1.612a1 1 0 0 1-.317.764l-1.178 1.1a1 1 0 0 0 0 1.462l1.178 1.101a1 1 0 0 1 .317.764l-.055 1.612a1 1 0 0 0 1.034 1.033l1.611-.054a1 1 0 0 1 .764.316l1.101 1.178a1 1 0 0 0 1.462 0l1.1-1.178a1 1 0 0 1 .765-.316l1.611.054a1 1 0 0 0 1.033-1.033l-.054-1.612a1 1 0 0 1 .317-.764l1.178-1.1a1 1 0 0 0 0-1.462L13.47 6.13a1 1 0 0 1-.317-.764l.054-1.612a1 1 0 0 0-1.033-1.033l-1.611.054a1 1 0 0 1-.765-.316l-1.1-1.178Zm3.032 5.115L10.6 5.264 6.9 8.963 5.335 7.396l-1.13 1.13L6.9 11.225l4.83-4.83Z"
+      fill="#F4F6F8"
+    />
+  </svg>
+))
+SvgActionVerified.displayName = 'SvgActionVerified'
+const Memo = memo(SvgActionVerified)
+export { Memo as SvgActionVerified }

+ 26 - 6
packages/atlas/src/assets/icons/JoyTokenSilver16.tsx

@@ -3,17 +3,37 @@ import { Ref, SVGProps, forwardRef, memo } from 'react'
 
 const SvgJoyTokenSilver16 = forwardRef((props: SVGProps<SVGSVGElement>, ref: Ref<SVGSVGElement>) => (
   <svg width={16} height={16} viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" ref={ref} {...props}>
-    <path
-      fillRule="evenodd"
-      clipRule="evenodd"
-      d="M15 8A7 7 0 1 1 1 8a7 7 0 0 1 14 0ZM3 8a5 5 0 0 0 10 0h-2a3 3 0 0 1-6 0H3Z"
-      fill="url(#paint0_linear_911_333)"
-    />
+    <g filter="url(#filter0_i_911_333)">
+      <path
+        fillRule="evenodd"
+        clipRule="evenodd"
+        d="M15 8A7 7 0 1 1 1 8a7 7 0 0 1 14 0ZM3 8a5 5 0 0 0 10 0h-2a3 3 0 0 1-6 0H3Z"
+        fill="url(#paint0_linear_911_333)"
+      />
+    </g>
     <defs>
       <linearGradient id="paint0_linear_911_333" x1={8} y1={1} x2={8} y2={15} gradientUnits="userSpaceOnUse">
         <stop stopColor="#7B8A95" />
         <stop offset={1} stopColor="#424E57" />
       </linearGradient>
+      <filter
+        id="filter0_i_911_333"
+        x={1}
+        y={1}
+        width={14}
+        height={14.5}
+        filterUnits="userSpaceOnUse"
+        colorInterpolationFilters="sRGB"
+      >
+        <feFlood floodOpacity={0} result="BackgroundImageFix" />
+        <feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
+        <feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha" />
+        <feOffset dy={0.5} />
+        <feGaussianBlur stdDeviation={0.25} />
+        <feComposite in2="hardAlpha" operator="arithmetic" k2={-1} k3={1} />
+        <feColorMatrix values="0 0 0 0 0.70875 0 0 0 0 0.756 0 0 0 0 0.7875 0 0 0 1 0" />
+        <feBlend in2="shape" result="effect1_innerShadow_911_333" />
+      </filter>
     </defs>
   </svg>
 ))

+ 26 - 6
packages/atlas/src/assets/icons/JoyTokenSilver24.tsx

@@ -3,17 +3,37 @@ import { Ref, SVGProps, forwardRef, memo } from 'react'
 
 const SvgJoyTokenSilver24 = forwardRef((props: SVGProps<SVGSVGElement>, ref: Ref<SVGSVGElement>) => (
   <svg width={24} height={24} viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" ref={ref} {...props}>
-    <path
-      fillRule="evenodd"
-      clipRule="evenodd"
-      d="M12 22c5.523 0 10-4.477 10-10S17.523 2 12 2 2 6.477 2 12s4.477 10 10 10Zm-.004-6a4 4 0 0 1-4-4h-3a7 7 0 1 0 14 0h-3a4 4 0 0 1-4 4Z"
-      fill="url(#paint0_linear_911_331)"
-    />
+    <g filter="url(#filter0_i_911_331)">
+      <path
+        fillRule="evenodd"
+        clipRule="evenodd"
+        d="M12 22c5.523 0 10-4.477 10-10S17.523 2 12 2 2 6.477 2 12s4.477 10 10 10Zm-.004-6a4 4 0 0 1-4-4h-3a7 7 0 1 0 14 0h-3a4 4 0 0 1-4 4Z"
+        fill="url(#paint0_linear_911_331)"
+      />
+    </g>
     <defs>
       <linearGradient id="paint0_linear_911_331" x1={12} y1={2} x2={12} y2={22} gradientUnits="userSpaceOnUse">
         <stop stopColor="#7B8A95" />
         <stop offset={1} stopColor="#424E57" />
       </linearGradient>
+      <filter
+        id="filter0_i_911_331"
+        x={2}
+        y={2}
+        width={20}
+        height={20.5}
+        filterUnits="userSpaceOnUse"
+        colorInterpolationFilters="sRGB"
+      >
+        <feFlood floodOpacity={0} result="BackgroundImageFix" />
+        <feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
+        <feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha" />
+        <feOffset dy={0.5} />
+        <feGaussianBlur stdDeviation={0.25} />
+        <feComposite in2="hardAlpha" operator="arithmetic" k2={-1} k3={1} />
+        <feColorMatrix values="0 0 0 0 0.70875 0 0 0 0 0.756 0 0 0 0 0.7875 0 0 0 1 0" />
+        <feBlend in2="shape" result="effect1_innerShadow_911_331" />
+      </filter>
     </defs>
   </svg>
 ))

+ 2 - 0
packages/atlas/src/assets/icons/index.ts

@@ -27,6 +27,7 @@ export * from './ActionClose'
 export * from './ActionClosedCaptions'
 export * from './ActionCopy'
 export * from './ActionCouncil'
+export * from './ActionCreatorToken'
 export * from './ActionCrown'
 export * from './ActionDislikeOutline'
 export * from './ActionDislikeSolid'
@@ -99,6 +100,7 @@ export * from './ActionUnlocked'
 export * from './ActionUnorderedList'
 export * from './ActionUnread'
 export * from './ActionUpload'
+export * from './ActionVerified'
 export * from './ActionVideoFile'
 export * from './ActionWarning'
 export * from './ActionZoomIn'

+ 3 - 0
packages/atlas/src/assets/icons/svgs/action-creator-token.svg

@@ -0,0 +1,3 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M8 14.5C11.5899 14.5 14.5 11.5899 14.5 8C14.5 4.41015 11.5899 1.5 8 1.5C4.41015 1.5 1.5 4.41015 1.5 8C1.5 11.5899 4.41015 14.5 8 14.5ZM9.96758 6.8327H11.75C11.693 6.3815 11.5628 5.98099 11.3593 5.63118C11.1586 5.28137 10.8981 4.98606 10.578 4.74525C10.2579 4.5019 9.89026 4.31686 9.47518 4.19011C9.06281 4.06337 8.61517 4 8.13226 4C7.39976 4 6.7405 4.15716 6.1545 4.47148C5.5685 4.78327 5.10458 5.23701 4.76275 5.8327C4.42092 6.42839 4.25 7.15082 4.25 8C4.25 8.84664 4.4182 9.56781 4.75461 10.1635C5.09373 10.7592 5.55494 11.2142 6.13823 11.5285C6.72423 11.8428 7.3889 12 8.13226 12C8.66671 12 9.14555 11.9252 9.56877 11.7757C9.99471 11.6236 10.361 11.4183 10.6675 11.1597C10.9768 10.8986 11.2223 10.6033 11.4041 10.2738C11.5886 9.9417 11.7039 9.59696 11.75 9.23954L9.96758 9.23194C9.92689 9.4398 9.85364 9.62484 9.74783 9.78707C9.64474 9.9493 9.51451 10.0875 9.35716 10.2015C9.19981 10.3131 9.0194 10.398 8.81593 10.4563C8.61517 10.5146 8.39677 10.5437 8.16074 10.5437C7.74023 10.5437 7.36991 10.4474 7.04978 10.2548C6.72965 10.0621 6.48006 9.77693 6.301 9.39924C6.12466 9.02155 6.03649 8.55513 6.03649 8C6.03649 7.46008 6.12466 7.00127 6.301 6.62357C6.47735 6.24335 6.72558 5.95437 7.04571 5.75665C7.36584 5.5564 7.74159 5.45627 8.17295 5.45627C8.41169 5.45627 8.6328 5.48796 8.83627 5.55133C9.04246 5.6147 9.22423 5.70596 9.38158 5.8251C9.53893 5.94423 9.6678 6.08872 9.76818 6.25856C9.86856 6.42586 9.93502 6.61724 9.96758 6.8327Z" fill="#F4F6F8"/>
+</svg>

+ 3 - 0
packages/atlas/src/assets/icons/svgs/action-verified.svg

@@ -0,0 +1,3 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M8.69751 1.28083C8.30225 0.857927 7.63159 0.857927 7.23634 1.28083L6.1354 2.45879C5.93826 2.66972 5.65958 2.78515 5.37103 2.7754L3.75961 2.72094C3.18108 2.70139 2.70685 3.17562 2.7264 3.75415L2.78086 5.36557C2.79061 5.65412 2.67518 5.9328 2.46425 6.12993L1.2863 7.23088C0.863389 7.62613 0.86339 8.29679 1.2863 8.69205L2.46425 9.79299C2.67518 9.99013 2.79061 10.2688 2.78086 10.5574L2.7264 12.1688C2.70685 12.7473 3.18108 13.2215 3.75961 13.202L5.37103 13.1475C5.65958 13.1378 5.93826 13.2532 6.1354 13.4641L7.23634 14.6421C7.63159 15.065 8.30226 15.065 8.69751 14.6421L9.79845 13.4641C9.99559 13.2532 10.2743 13.1378 10.5628 13.1475L12.1742 13.202C12.7528 13.2215 13.227 12.7473 13.2074 12.1688L13.153 10.5574C13.1432 10.2688 13.2587 9.99013 13.4696 9.79299L14.6476 8.69205C15.0705 8.29679 15.0705 7.62613 14.6476 7.23088L13.4696 6.12993C13.2587 5.9328 13.1432 5.65412 13.153 5.36557L13.2074 3.75415C13.227 3.17562 12.7528 2.70139 12.1742 2.72094L10.5628 2.7754C10.2743 2.78515 9.99559 2.66972 9.79845 2.45879L8.69751 1.28083ZM11.7304 6.39471L10.5997 5.264L6.90082 8.96282L5.33409 7.39609L4.20338 8.5268L6.90082 11.2242L11.7304 6.39471Z" fill="#F4F6F8"/>
+</svg>

+ 12 - 0
packages/atlas/src/assets/icons/svgs/joy-token-silver-16.svg

@@ -1,6 +1,18 @@
 <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g filter="url(#filter0_i_911_333)">
 <path fill-rule="evenodd" clip-rule="evenodd" d="M15 8C15 11.866 11.866 15 8 15C4.13401 15 1 11.866 1 8C1 4.13401 4.13401 1 8 1C11.866 1 15 4.13401 15 8ZM3 8C3 10.7614 5.23858 13 8 13C10.7614 13 13 10.7614 13 8H11C11 9.65685 9.65685 11 8 11C6.34315 11 5 9.65685 5 8H3Z" fill="url(#paint0_linear_911_333)"/>
+</g>
 <defs>
+<filter id="filter0_i_911_333" x="1" y="1" width="14" height="14.5" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+<feFlood flood-opacity="0" result="BackgroundImageFix"/>
+<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
+<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
+<feOffset dy="0.5"/>
+<feGaussianBlur stdDeviation="0.25"/>
+<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
+<feColorMatrix type="matrix" values="0 0 0 0 0.70875 0 0 0 0 0.756 0 0 0 0 0.7875 0 0 0 1 0"/>
+<feBlend mode="normal" in2="shape" result="effect1_innerShadow_911_333"/>
+</filter>
 <linearGradient id="paint0_linear_911_333" x1="8" y1="1" x2="8" y2="15" gradientUnits="userSpaceOnUse">
 <stop stop-color="#7B8A95"/>
 <stop offset="1" stop-color="#424E57"/>

+ 12 - 0
packages/atlas/src/assets/icons/svgs/joy-token-silver-24.svg

@@ -1,6 +1,18 @@
 <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g filter="url(#filter0_i_911_331)">
 <path fill-rule="evenodd" clip-rule="evenodd" d="M12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22ZM11.9961 16C9.78695 16 7.99609 14.2091 7.99609 12H4.99609C4.99609 15.866 8.1301 19 11.9961 19C15.8621 19 18.9961 15.866 18.9961 12H15.9961C15.9961 14.2091 14.2052 16 11.9961 16Z" fill="url(#paint0_linear_911_331)"/>
+</g>
 <defs>
+<filter id="filter0_i_911_331" x="2" y="2" width="20" height="20.5" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+<feFlood flood-opacity="0" result="BackgroundImageFix"/>
+<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
+<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
+<feOffset dy="0.5"/>
+<feGaussianBlur stdDeviation="0.25"/>
+<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
+<feColorMatrix type="matrix" values="0 0 0 0 0.70875 0 0 0 0 0.756 0 0 0 0 0.7875 0 0 0 1 0"/>
+<feBlend mode="normal" in2="shape" result="effect1_innerShadow_911_331"/>
+</filter>
 <linearGradient id="paint0_linear_911_331" x1="12" y1="2" x2="12" y2="22" gradientUnits="userSpaceOnUse">
 <stop stop-color="#7B8A95"/>
 <stop offset="1" stop-color="#424E57"/>

+ 49 - 43
packages/atlas/src/components/AllNftSection/AllNftSection.tsx

@@ -1,18 +1,21 @@
 import styled from '@emotion/styled'
 import { useMemo, useState } from 'react'
 
-import { useNftsConnection } from '@/api/hooks/nfts'
 import { OwnedNftOrderByInput, OwnedNftWhereInput } from '@/api/queries/__generated__/baseTypes.generated'
 import { SvgActionSell, SvgActionSettings, SvgActionShoppingCart } from '@/assets/icons'
 import { EmptyFallback } from '@/components/EmptyFallback'
 import { FilterButtonOption, SectionFilter } from '@/components/FilterButton'
-import { NumberFormat } from '@/components/NumberFormat'
 import { Section } from '@/components/Section/Section'
 import { Button } from '@/components/_buttons/Button'
 import { NftTileViewer } from '@/components/_nft/NftTileViewer'
+import { publicVideoFilter } from '@/config/contentFilter'
+import { useInfiniteNftsGrid } from '@/hooks/useInfiniteNftsGrid'
 import { useMediaMatch } from '@/hooks/useMediaMatch'
 import { tokenNumberToHapiBn } from '@/joystream-lib/utils'
-import { createPlaceholderData } from '@/utils/data'
+import { DEFAULT_NFTS_GRID } from '@/styles'
+import { InfiniteLoadingOffsets } from '@/utils/loading.contants'
+
+import { NumberFormat } from '../NumberFormat'
 
 const NFT_STATUSES: FilterButtonOption[] = [
   {
@@ -64,16 +67,22 @@ const FILTERS: SectionFilter[] = [
   { name: 'other', type: 'checkbox', options: OTHER, label: 'Other', icon: <SvgActionSettings /> },
 ]
 
-const LIMIT = 12
-const LG_LIMIT = 30
+const sortingOptions = [
+  {
+    label: 'Newest',
+    value: OwnedNftOrderByInput.CreatedAtDesc,
+  },
+  {
+    label: 'Oldest',
+    value: OwnedNftOrderByInput.CreatedAtAsc,
+  },
+]
 
 export const AllNftSection = () => {
   const [filters, setFilters] = useState<SectionFilter[]>(FILTERS)
   const [hasAppliedFilters, setHasAppliedFilters] = useState(false)
   const [order, setOrder] = useState<OwnedNftOrderByInput>(OwnedNftOrderByInput.CreatedAtDesc)
   const smMatch = useMediaMatch('sm')
-  const lgMatch = useMediaMatch('lg')
-  const limit = lgMatch ? LG_LIMIT : LIMIT
   const mappedFilters = useMemo((): OwnedNftWhereInput => {
     const mappedStatus =
       filters
@@ -109,6 +118,7 @@ export const AllNftSection = () => {
       lastSalePrice_gte: minPrice ? tokenNumberToHapiBn(minPrice).toString() : undefined,
       lastSalePrice_lte: maxPrice ? tokenNumberToHapiBn(maxPrice).toString() : undefined,
       video: {
+        ...publicVideoFilter,
         ...(isMatureExcluded ? { isExcluded_eq: false } : {}),
         ...(isPromotionalExcluded ? { hasMarketing_eq: false } : {}),
       },
@@ -123,15 +133,12 @@ export const AllNftSection = () => {
     }
   }, [filters])
 
-  const { nfts, loading, totalCount, fetchMore, pageInfo } = useNftsConnection({
+  const { columns, fetchMore, pageInfo, tiles, totalCount } = useInfiniteNftsGrid({
     where: mappedFilters,
     orderBy: order,
-    first: limit,
   })
-  const [isLoading, setIsLoading] = useState(false)
 
-  const placeholderItems = loading || isLoading ? createPlaceholderData(limit) : []
-  const nftsWithPlaceholders = [...(nfts || []), ...placeholderItems]
+  const children = tiles?.map((nft, idx) => <NftTileViewer key={idx} nftId={nft.id} />)
   return (
     <Section
       headerProps={{
@@ -149,47 +156,46 @@ export const AllNftSection = () => {
           type: 'toggle-button',
           toggleButtonOptionTypeProps: {
             type: 'options',
-            options: ['Newest', 'Oldest'],
-            value: order === OwnedNftOrderByInput.CreatedAtDesc ? 'Newest' : 'Oldest',
-            onChange: (order) =>
-              setOrder(order === 'Oldest' ? OwnedNftOrderByInput.CreatedAtAsc : OwnedNftOrderByInput.CreatedAtDesc),
+            options: sortingOptions,
+            value: order,
+            onChange: setOrder,
           },
         },
       }}
       contentProps={{
         type: 'grid',
-        minChildrenWidth: 250,
-        children:
-          !(isLoading || loading) && !nfts?.length
-            ? [
-                <FallbackContainer key="fallback">
-                  <EmptyFallback
-                    title="No NFTs found"
-                    subtitle="Please, try changing your filtering criteria."
-                    button={
-                      hasAppliedFilters && (
-                        <Button variant="secondary" onClick={() => setFilters(FILTERS)}>
-                          Clear all filters
-                        </Button>
-                      )
-                    }
-                  />
-                </FallbackContainer>,
-              ]
-            : nftsWithPlaceholders.map((nft, idx) => <NftTileViewer key={idx} nftId={nft.id} />),
+        grid: DEFAULT_NFTS_GRID,
+        children: children.length
+          ? children
+          : [
+              <FallbackContainer key="fallback">
+                <EmptyFallback
+                  title="No NFTs found"
+                  subtitle="Please, try changing your filtering criteria."
+                  button={
+                    hasAppliedFilters && (
+                      <Button variant="secondary" onClick={() => setFilters(FILTERS)}>
+                        Clear all filters
+                      </Button>
+                    )
+                  }
+                />
+              </FallbackContainer>,
+            ],
       }}
       footerProps={{
         type: 'infinite',
+        loadingTriggerOffset: InfiniteLoadingOffsets.NftTile,
         reachedEnd: !pageInfo?.hasNextPage ?? true,
         fetchMore: async () => {
-          setIsLoading(true)
-          await fetchMore({
-            variables: {
-              after: pageInfo?.endCursor,
-            },
-          }).finally(() => {
-            setIsLoading(false)
-          })
+          if (pageInfo?.hasNextPage) {
+            await fetchMore({
+              variables: {
+                first: columns * 4,
+                after: pageInfo?.endCursor,
+              },
+            })
+          }
         },
       }}
     />

+ 30 - 85
packages/atlas/src/components/Avatar/Avatar.styles.ts

@@ -1,22 +1,13 @@
 import isPropValid from '@emotion/is-prop-valid'
-import { SerializedStyles, css } from '@emotion/react'
+import { css } from '@emotion/react'
 import styled from '@emotion/styled'
 
 import { SvgActionAddImage, SvgActionEdit, SvgIllustrativeFileFailed } from '@/assets/icons'
 import { SvgAvatarSilhouette } from '@/assets/illustrations'
 import { SkeletonLoader } from '@/components/_loaders/SkeletonLoader'
-import { cVar, media, square, zIndex } from '@/styles'
-
-export type AvatarSize =
-  | 'preview'
-  | 'cover'
-  | 'default'
-  | 'fill'
-  | 'bid'
-  | 'small'
-  | 'channel'
-  | 'channel-card'
-  | 'extra-small'
+import { cVar, square, zIndex } from '@/styles'
+
+export type AvatarSize = 136 | 104 | 88 | 40 | 32 | 24
 
 type ContainerProps = {
   size: AvatarSize
@@ -25,6 +16,7 @@ type ContainerProps = {
   disableInteractiveStyles?: boolean
   // allow passing 'type' prop to Container because it can be rendered as 'button' depending on context
   type?: 'button'
+  disableHoverDimm?: boolean
 }
 
 export const IconAndOverlayWrapper = styled.div`
@@ -66,76 +58,28 @@ export const Overlay = styled.div<{ isEdit?: boolean }>`
     `};
 `
 
-const previewAvatarCss = css`
-  ${square('136px')};
-`
-
-const coverAvatarCss = css`
-  ${square('64px')};
-
-  ${media.md} {
-    ${square('88px')};
-  }
-`
-
-const channelAvatarCss = css`
-  ${square('88px')};
-  ${media.md} {
-    ${square('136px')};
-  }
-`
-const channelCardAvatarCss = css`
-  ${square('88px')};
-  ${media.md} {
-    ${square('104px')};
-  }
-`
-
-const bidAvatarCss = css`
-  ${square('24px')};
-`
-
-const smallAvatarCss = css`
-  ${square('40px')};
-`
-
-const defaultAvatarCss = css`
-  ${square('32px')};
-`
-
-const fillAvatarCss = css`
-  width: 100%;
-  height: 100%;
-`
-
-const extraSmallAvatarCss = css`
-  ${square('16px')};
-`
-
-const getAvatarSizeCss = ({ size }: ContainerProps): SerializedStyles => {
-  switch (size) {
-    case 'preview':
-      return previewAvatarCss
-    case 'cover':
-      return coverAvatarCss
-    case 'channel':
-      return channelAvatarCss
-    case 'channel-card':
-      return channelCardAvatarCss
-    case 'fill':
-      return fillAvatarCss
-    case 'bid':
-      return bidAvatarCss
-    case 'small':
-      return smallAvatarCss
-    case 'extra-small':
-      return extraSmallAvatarCss
-    default:
-      return defaultAvatarCss
-  }
-}
-
-export const sharedAvatarHoverStyles = css`
+// const coverAvatarCss = css`
+//   ${square('64px')};
+
+//   ${media.md} {
+//     ${square('88px')};
+//   }
+// `
+
+// const channelAvatarCss = css`
+//   ${square('88px')};
+//   ${media.md} {
+//     ${square('136px')};
+//   }
+// `
+// const channelCardAvatarCss = css`
+//   ${square('88px')};
+//   ${media.md} {
+//     ${square('104px')};
+//   }
+// `
+
+export const sharedAvatarHoverStyles = (props: { disableHoverDimm?: boolean }) => css`
   ::after {
     border: 1px solid ${cVar('colorBackgroundAlpha')};
   }
@@ -143,7 +87,8 @@ export const sharedAvatarHoverStyles = css`
     opacity: 1;
   }
   ${Overlay} {
-    opacity: 0.5;
+    ${props.disableHoverDimm ? '' : 'opacity: 0.5;'}
+
     background-color: ${cVar('colorBackgroundOverlay')};
   }
 `
@@ -170,7 +115,7 @@ const getInteractiveStyles = ({ isLoading, isClickable }: Omit<ContainerProps, '
 }
 
 export const Container = styled('div', { shouldForwardProp: isPropValid })<ContainerProps>`
-  ${getAvatarSizeCss};
+  ${({ size }) => square(`${size}px`)};
   ${getInteractiveStyles};
 
   border-radius: 100%;

+ 7 - 14
packages/atlas/src/components/Avatar/Avatar.tsx

@@ -30,27 +30,18 @@ export type AvatarProps = PropsWithChildren<{
   hasAvatarUploadFailed?: boolean
   loading?: boolean
   className?: string
-  /**
-   * @description preview - 136px x 136px
-   * @description cover - default: 64px x 64px, md: 88px x 88px
-   * @description default - 32px x 32px
-   * @description fill - 100% x 100%
-   * @description bid - 24px x 24px
-   * @description small - 40px x 40px
-   * @description channel - default: 88px x 88px, md: 136px x 136px
-   * @description channel-card - default: 88px x 88px, md: 104px x 104px
-   */
   size?: AvatarSize
   newChannel?: boolean
   editable?: boolean
   clickable?: boolean
+  disableHoverDimm?: boolean
 }>
 
 export const Avatar: FC<AvatarProps> = ({
   assetUrl,
   hasAvatarUploadFailed,
   loading = false,
-  size = 'default',
+  size = 32,
   children,
   className,
   editable,
@@ -59,8 +50,9 @@ export const Avatar: FC<AvatarProps> = ({
   onError,
   onClick,
   onImageValidation,
+  disableHoverDimm,
 }) => {
-  const isEditable = !loading && editable && size !== 'default' && size !== 'bid'
+  const isEditable = !loading && editable && size !== 32 && size !== 24
 
   const checkIfImageIsValid = useCallback(async () => {
     if (!assetUrl) {
@@ -83,7 +75,7 @@ export const Avatar: FC<AvatarProps> = ({
   }, [assetUrl, checkIfImageIsValid])
 
   const getEditableIconSize = useCallback(() => {
-    const smallIconSizes = ['bid', 'default', 'small']
+    const smallIconSizes = [24, 32, 40]
     if (smallIconSizes.includes(size)) {
       return
     } else {
@@ -99,6 +91,7 @@ export const Avatar: FC<AvatarProps> = ({
       size={size}
       className={className}
       isLoading={loading}
+      disableHoverDimm={disableHoverDimm}
       isClickable={clickable || (clickable == null && !!onClick)} // default to true if onClick is provided
     >
       {(clickable || !!onClick) && (
@@ -120,7 +113,7 @@ export const Avatar: FC<AvatarProps> = ({
         ) : hasAvatarUploadFailed ? (
           <NewChannelAvatar>
             <StyledSvgIllustrativeFileFailed />
-            {size === 'preview' && (
+            {size === 136 && (
               <Text variant="t100" as="span" margin={{ top: 2 }}>
                 Failed upload
               </Text>

+ 4 - 4
packages/atlas/src/components/Avatar/AvatarGroup.stories.tsx

@@ -28,9 +28,9 @@ export const Default = Template.bind({})
 
 Default.args = {
   avatars: [
-    { url: 'https://thispersondoesnotexist.com/image', tooltipText: 'Jane' },
-    { url: 'https://thispersondoesnotexist.com/image', tooltipText: 'John' },
-    { url: 'https://thispersondoesnotexist.com/image', tooltipText: 'William' },
-    { url: 'https://thispersondoesnotexist.com/image', tooltipText: 'One line description' },
+    { url: 'https://i.pravatar.cc/300', tooltipText: 'Jane' },
+    { url: 'https://i.pravatar.cc/300', tooltipText: 'John' },
+    { url: 'https://i.pravatar.cc/300', tooltipText: 'William' },
+    { url: 'https://i.pravatar.cc/300', tooltipText: 'One line description' },
   ],
 }

+ 7 - 6
packages/atlas/src/components/Avatar/AvatarGroup.styles.ts

@@ -9,22 +9,22 @@ import { Avatar } from '.'
 
 export type AvatarGroupSize = 'small' | 'medium' | 'large'
 
-const getSizeOfGridColumn = ({ size }: AvatarGroupContainerProps) => {
+const getSizeOfGridColumn = ({ size, spreadAvatars }: AvatarGroupContainerProps) => {
   // grid-auto-columns = size of the avatar - offset
   switch (size) {
     case 'small':
       return css`
-        grid-auto-columns: 20px;
+        grid-auto-columns: ${spreadAvatars ? '36px' : '20px'};
         padding-right: 4px;
       `
     case 'medium':
       return css`
-        grid-auto-columns: 24px;
+        grid-auto-columns: ${spreadAvatars ? '40px' : '24px'};
         padding-right: 8px;
       `
     case 'large':
       return css`
-        grid-auto-columns: 32px;
+        grid-auto-columns: ${spreadAvatars ? '46px' : '32px'};
         padding-right: 8px;
       `
   }
@@ -37,6 +37,7 @@ export const StyledAvatar = styled(Avatar)`
 type AvatarGroupContainerProps = {
   size?: AvatarGroupSize
   shouldHighlightEveryAvatar?: boolean
+  spreadAvatars?: boolean
 }
 
 export const AvatarGroupContainer = styled.div<AvatarGroupContainerProps>`
@@ -48,7 +49,7 @@ export const AvatarGroupContainer = styled.div<AvatarGroupContainerProps>`
     shouldHighlightEveryAvatar &&
     css`
       :hover ${StyledAvatar} {
-        ${sharedAvatarHoverStyles};
+        ${sharedAvatarHoverStyles({ disableHoverDimm: true })};
       }
       :active ${StyledAvatar} {
         ${sharedAvatarActiveStyles};
@@ -95,7 +96,7 @@ type AvatarWrapperProps = {
 export const AvatarWrapper = styled.div<AvatarWrapperProps>`
   position: relative;
   border-radius: 50%;
-  width: calc(100% + ${({ size }) => (size === 'small' ? 4 : 8)}px);
+  width: fit-content;
   grid-row: 1;
 
   ${({ clickable }) =>

+ 12 - 4
packages/atlas/src/components/Avatar/AvatarGroup.tsx

@@ -38,17 +38,18 @@ export type AvatarGroupProps = {
   loading?: boolean
   className?: string
   shouldHighlightEveryAvatar?: boolean
+  spreadAvatars?: boolean
 }
 
 const getSizeofAvatar = (size: AvatarGroupSize) => {
   // converts size of avatar group to avatar size
   switch (size) {
     case 'large':
-      return 'small'
+      return 40
     case 'medium':
-      return 'default'
+      return 32
     case 'small':
-      return 'bid'
+      return 24
   }
 }
 
@@ -61,12 +62,18 @@ export const AvatarGroup: FC<AvatarGroupProps> = ({
   reverse,
   shouldHighlightEveryAvatar,
   className,
+  spreadAvatars,
 }) => {
   const [hoveredAvatarIdx, setHoveredAvatarIdx] = useState<number | null>(null)
   const ref = useRef<HTMLDivElement | null>(null)
 
   return (
-    <AvatarGroupContainer size={size} className={className} shouldHighlightEveryAvatar={shouldHighlightEveryAvatar}>
+    <AvatarGroupContainer
+      size={size}
+      className={className}
+      shouldHighlightEveryAvatar={shouldHighlightEveryAvatar}
+      spreadAvatars={spreadAvatars}
+    >
       {avatars.map((avatarProps, idx) => (
         <Fragment key={idx}>
           <AvatarWrapper
@@ -113,6 +120,7 @@ const SingleAvatar: FC<SingleAvatarProps> = ({ avatar, loading: loadingProp, siz
       loading={loading}
       assetUrl={url}
       size={size}
+      disableHoverDimm
       onClick={(e) => {
         e.stopPropagation()
         e.preventDefault()

+ 8 - 1
packages/atlas/src/components/Carousel/Carousel.styles.ts

@@ -1,4 +1,5 @@
 import styled from '@emotion/styled'
+import { SwiperOptions } from 'swiper'
 import { Swiper } from 'swiper/react'
 
 import { Button } from '@/components/_buttons/Button'
@@ -6,7 +7,13 @@ import { cVar, media, sizes, transitions, zIndex } from '@/styles'
 
 export const CAROUSEL_ARROW_HEIGHT = 48
 
-export const StyledSwiper = styled(Swiper)`
+type StyledSwiperProps = {
+  minSlideWidth?: number
+} & SwiperOptions
+
+const isPropValid = (prop: string) => prop !== 'minSlideWidth'
+
+export const StyledSwiper = styled(Swiper, { shouldForwardProp: isPropValid })<StyledSwiperProps>`
   width: 100%;
 
   .swiper-pagination {

+ 15 - 2
packages/atlas/src/components/Carousel/Carousel.tsx

@@ -10,6 +10,7 @@ export type SwiperInstance = SwiperType
 export type CarouselProps = {
   className?: string
   dotsVisible?: boolean
+  minSlideWidth?: number
   children: ReactNode[]
 } & SwiperProps
 
@@ -22,9 +23,21 @@ const dotsProps = {
   },
 }
 
-export const Carousel = ({ children, dotsVisible, ...swiperOptions }: CarouselProps) => {
+export const Carousel = ({
+  children,
+  dotsVisible,
+  spaceBetween = 12,
+  minSlideWidth,
+  ...swiperOptions
+}: CarouselProps) => {
   return (
-    <StyledSwiper navigation spaceBetween={12} {...(dotsVisible ? dotsProps : {})} {...swiperOptions}>
+    <StyledSwiper
+      navigation
+      minSlideWidth={minSlideWidth}
+      {...(dotsVisible ? dotsProps : {})}
+      {...swiperOptions}
+      spaceBetween={spaceBetween}
+    >
       {children?.map((child, idx) => (
         <SwiperSlide key={idx}>{child}</SwiperSlide>
       ))}

+ 0 - 12
packages/atlas/src/components/InfiniteGrids/InfiniteGrid.styles.ts

@@ -1,12 +0,0 @@
-import styled from '@emotion/styled'
-
-import { Button } from '@/components/_buttons/Button'
-import { sizes } from '@/styles'
-
-export const LoadMoreButtonWrapper = styled.div`
-  margin-top: ${sizes(12)};
-`
-
-export const AdditionalLink = styled(Button)`
-  margin-left: auto;
-`

+ 0 - 229
packages/atlas/src/components/InfiniteGrids/InfiniteVideoGrid.tsx

@@ -1,229 +0,0 @@
-import { QueryHookOptions } from '@apollo/client'
-import { DocumentNode } from 'graphql'
-import { ReactNode, forwardRef, useCallback, useState } from 'react'
-
-import { VideoOrderByInput, VideoWhereInput } from '@/api/queries/__generated__/baseTypes.generated'
-import {
-  GetBasicVideosConnectionDocument,
-  GetBasicVideosConnectionQuery,
-  GetBasicVideosConnectionQueryVariables,
-  GetMostViewedVideosConnectionQuery,
-  GetMostViewedVideosConnectionQueryVariables,
-} from '@/api/queries/__generated__/videos.generated'
-import { SvgActionChevronR } from '@/assets/icons'
-import { Grid } from '@/components/Grid'
-import { GridHeadingContainer, TitleContainer } from '@/components/GridHeading'
-import { Text } from '@/components/Text'
-import { LoadMoreButton } from '@/components/_buttons/LoadMoreButton'
-import { SkeletonLoader } from '@/components/_loaders/SkeletonLoader'
-import { publicChannelFilter, publicVideoFilter } from '@/config/contentFilter'
-import { useVideoGridRows } from '@/hooks/useVideoGridRows'
-import { SentryLogger } from '@/utils/logs'
-
-import { AdditionalLink, LoadMoreButtonWrapper } from './InfiniteGrid.styles'
-import { useInfiniteGrid } from './useInfiniteGrid'
-
-import { VideoTileViewer } from '../_video/VideoTileViewer'
-
-type InfiniteVideoGridProps = {
-  query?: DocumentNode
-  queryOpts?: QueryHookOptions
-  // `periodDays` argument to be passed to the most viewed connection query - it will let you set the time period of most views
-  periodDays?: number
-  // `limit` argument to be passed to the most viewed connection query - it will let you cap the number of videos in the connection
-  limit?: number
-  title?: string
-  titleLoader?: boolean
-  videoWhereInput?: VideoWhereInput
-  skipCount?: number
-  ready?: boolean
-  showChannel?: boolean
-  className?: string
-  // exclude a specific video from the result
-  excludeId?: string
-  onDemand?: boolean
-  onDemandInfinite?: boolean
-  orderBy?: VideoOrderByInput[]
-  emptyFallback?: ReactNode
-  additionalLink?: {
-    name: string
-    url: string
-  }
-}
-
-const INITIAL_VIDEOS_PER_ROW = 1
-
-type VideoQuery = GetBasicVideosConnectionQuery | GetMostViewedVideosConnectionQuery
-
-export const InfiniteVideoGrid = forwardRef<HTMLElement, InfiniteVideoGridProps>(
-  (
-    {
-      query = GetBasicVideosConnectionDocument,
-      queryOpts,
-      periodDays,
-      limit,
-      title,
-      videoWhereInput,
-      orderBy,
-      skipCount = 0,
-      ready = true,
-      className,
-      excludeId,
-      onDemand = false,
-      onDemandInfinite = false,
-      additionalLink,
-      titleLoader,
-      emptyFallback,
-    },
-    ref
-  ) => {
-    const [activatedInfinteGrid, setActivatedInfinteGrid] = useState(false)
-    const [videosPerRow, setVideosPerRow] = useState(INITIAL_VIDEOS_PER_ROW)
-    const rowsToLoad = useVideoGridRows()
-    const [_targetRowsCount, setTargetRowsCount] = useState(rowsToLoad)
-    const [initialGridResizeDone, setInitialGridResizeDone] = useState(false)
-    const targetRowsCount = Math.max(_targetRowsCount, rowsToLoad)
-
-    const queryVariables: GetBasicVideosConnectionQueryVariables & GetMostViewedVideosConnectionQueryVariables = {
-      periodDays,
-      limit,
-      orderBy,
-      where: {
-        ...videoWhereInput,
-        ...publicVideoFilter,
-        channel: { ...publicChannelFilter, ...videoWhereInput?.channel },
-      },
-    }
-
-    const fetchMore = useCallback(() => {
-      setTargetRowsCount(targetRowsCount + rowsToLoad)
-    }, [targetRowsCount, rowsToLoad])
-
-    const { placeholdersCount, displayedItems, error, totalCount, loading } = useInfiniteGrid<
-      VideoQuery,
-      GetBasicVideosConnectionQuery['videosConnection'],
-      GetBasicVideosConnectionQueryVariables
-    >({
-      query: query || GetBasicVideosConnectionDocument,
-      queryOpts,
-      isReady: ready && initialGridResizeDone,
-      skipCount,
-      queryVariables,
-      targetRowsCount,
-      onDemand,
-      onDemandInfinite,
-      activatedInfinteGrid,
-      onScrollToBottom: !onDemand ? fetchMore : undefined,
-      itemsPerRow: videosPerRow,
-      onError: (error) => SentryLogger.error('Failed to fetch videos', 'InfiniteVideoGrid', error),
-      dataAccessor: createRawDataAccessor(excludeId),
-    })
-
-    const placeholderItems = Array.from({ length: placeholdersCount }, () => ({ id: undefined }))
-    const gridContent = (
-      <>
-        {[...displayedItems, ...placeholderItems]?.map((video, idx) => (
-          <VideoTileViewer id={video.id} key={idx} />
-        ))}
-      </>
-    )
-
-    if (error) {
-      return null
-    }
-
-    const hasNoItems = ready && initialGridResizeDone && !loading && totalCount === 0
-    if (hasNoItems && !emptyFallback) {
-      return null
-    }
-
-    const shouldShowLoadMoreButton =
-      (onDemand || (onDemandInfinite && !activatedInfinteGrid)) && !loading && displayedItems.length < totalCount
-
-    return (
-      <section ref={ref} className={className}>
-        {hasNoItems && !!emptyFallback ? (
-          emptyFallback
-        ) : (
-          <>
-            {title && (
-              <GridHeadingContainer>
-                <TitleContainer>
-                  {(!ready || !displayedItems.length) && titleLoader ? (
-                    <SkeletonLoader height={30} width={250} />
-                  ) : (
-                    <Text as="h2" variant="h500">
-                      {title}
-                    </Text>
-                  )}
-                  {additionalLink && (
-                    <AdditionalLink
-                      to={additionalLink.url}
-                      size="medium"
-                      variant="secondary"
-                      iconPlacement="right"
-                      icon={<SvgActionChevronR />}
-                    >
-                      {additionalLink.name}
-                    </AdditionalLink>
-                  )}
-                </TitleContainer>
-              </GridHeadingContainer>
-            )}
-            <Grid
-              onResize={(sizes) => {
-                setVideosPerRow(sizes.length)
-                if (!initialGridResizeDone) {
-                  setInitialGridResizeDone(true)
-                }
-              }}
-            >
-              {gridContent}
-            </Grid>
-            {shouldShowLoadMoreButton && (
-              <LoadMoreButtonWrapper>
-                <LoadMoreButton
-                  label={onDemandInfinite ? 'Keep loading videos' : undefined}
-                  onClick={() => {
-                    fetchMore()
-                    if (onDemandInfinite) {
-                      setActivatedInfinteGrid(true)
-                    }
-                  }}
-                />
-              </LoadMoreButtonWrapper>
-            )}
-          </>
-        )}
-      </section>
-    )
-  }
-)
-InfiniteVideoGrid.displayName = 'InfiniteVideoGrid'
-
-const createRawDataAccessor = (excludeId?: string) => (rawData?: VideoQuery) => {
-  if (!rawData) {
-    return
-  }
-
-  const queryResult =
-    'videosConnection' in rawData
-      ? rawData.videosConnection
-      : 'mostViewedVideosConnection' in rawData
-      ? rawData.mostViewedVideosConnection
-      : null
-  if (!queryResult) {
-    SentryLogger.error('Unknown property in query data', 'InfiniteVideoGrid', null, { query: { rawData } })
-    throw new Error("Couldn't access data for video grid")
-  }
-
-  if (!excludeId) {
-    return queryResult
-  }
-
-  return {
-    ...queryResult,
-    totalCount: queryResult.totalCount - 1,
-    edges: queryResult.edges.filter((edge) => edge.node.id !== excludeId),
-  }
-}

+ 0 - 1
packages/atlas/src/components/InfiniteGrids/index.ts

@@ -1 +0,0 @@
-export * from './InfiniteVideoGrid'

+ 0 - 186
packages/atlas/src/components/InfiniteGrids/useInfiniteGrid.ts

@@ -1,186 +0,0 @@
-import { ApolloError, NetworkStatus, QueryHookOptions, useQuery } from '@apollo/client'
-import { TypedDocumentNode } from '@graphql-typed-document-node/core'
-import { DocumentNode } from 'graphql'
-import { debounce, isEqual } from 'lodash-es'
-import { useEffect, useRef } from 'react'
-
-import { ChannelEdge, VideoEdge } from '@/api/queries/__generated__/baseTypes.generated'
-
-export type PaginatedData<T> = {
-  edges: {
-    cursor: string
-    node: T
-  }[]
-  pageInfo: {
-    hasNextPage: boolean
-    endCursor?: string | null
-  }
-  totalCount: number
-}
-export type PaginatedDataArgs = {
-  first?: number | null
-  after?: string | null
-}
-
-const PREFETCHED_ITEMS_COUNT = 12
-
-// TODO these types below could be used to get rid of requirement to pass TPaginatedData explicitly
-// however this currently is not possible because of constraints of Typescript and our GraphQL codegen
-// tldr is that our codegen generates interfaces instead of types and a more specific interface cannot be assigned to a generic one
-
-// type RawData<TData> = { [p: string]: PaginatedData<TData> }
-// type PaginatedDataFromRawData<TRawData extends RawData<unknown>> = TRawData[keyof TRawData]
-// type ItemTypeFromRawData<TRawData extends RawData<unknown>> = PaginatedDataFromRawData<TRawData>['edges'][0]['node']
-
-type UseInfiniteGridParams<TRawData, TPaginatedData extends PaginatedData<unknown>, TArgs> = {
-  query: DocumentNode | TypedDocumentNode<TRawData, TArgs>
-  // eslint-disable-next-line @typescript-eslint/no-explicit-any
-  queryOpts?: QueryHookOptions<any, TArgs>
-  dataAccessor: (rawData?: TRawData) => TPaginatedData | undefined
-  isReady: boolean
-  targetRowsCount: number
-  itemsPerRow: number
-  skipCount: number
-  onError?: (error: unknown) => void
-  queryVariables: TArgs
-  onDemand?: boolean
-  onDemandInfinite?: boolean
-  activatedInfinteGrid?: boolean
-  onScrollToBottom?: () => void
-  additionalSortFn?: (edge?: ChannelEdge[] | VideoEdge[]) => (ChannelEdge | VideoEdge)[]
-}
-
-type UseInfiniteGridReturn<TPaginatedData extends PaginatedData<unknown>> = {
-  displayedItems: TPaginatedData['edges'][0]['node'][]
-  placeholdersCount: number
-  error?: ApolloError
-  allItemsLoaded: boolean
-  loading: boolean
-  totalCount: number
-}
-
-export const useInfiniteGrid = <
-  TRawData,
-  TPaginatedData extends PaginatedData<unknown>,
-  TArgs extends PaginatedDataArgs
->({
-  query,
-  queryOpts,
-  dataAccessor,
-  isReady,
-  targetRowsCount,
-  itemsPerRow,
-  skipCount,
-  onScrollToBottom,
-  onError,
-  queryVariables,
-  onDemand,
-  onDemandInfinite,
-  activatedInfinteGrid,
-}: UseInfiniteGridParams<TRawData, TPaginatedData, TArgs>): UseInfiniteGridReturn<TPaginatedData> => {
-  const targetDisplayedItemsCount = targetRowsCount * itemsPerRow
-  const targetLoadedItemsCount = targetDisplayedItemsCount + skipCount
-
-  const queryVariablesRef = useRef(queryVariables)
-
-  const {
-    loading,
-    data: rawData,
-    error,
-    fetchMore,
-    refetch,
-    networkStatus,
-  } = useQuery<TRawData, TArgs>(query, {
-    notifyOnNetworkStatusChange: true,
-    skip: !isReady,
-    variables: {
-      ...queryVariables,
-      first: targetDisplayedItemsCount + PREFETCHED_ITEMS_COUNT,
-    },
-    onError,
-    ...queryOpts,
-  })
-
-  const data = dataAccessor(rawData)
-
-  const loadedItemsCount = data?.edges.length ?? 0
-  const allItemsLoaded = data ? !data.pageInfo.hasNextPage : false
-  const endCursor = data?.pageInfo.endCursor
-
-  // handle fetching more items
-  useEffect(() => {
-    if (loading || error || !isReady || !fetchMore || allItemsLoaded) {
-      return
-    }
-
-    const missingItemsCount = targetLoadedItemsCount - loadedItemsCount
-
-    if (missingItemsCount <= 0) {
-      return
-    }
-
-    fetchMore({
-      variables: { ...queryVariables, first: missingItemsCount + PREFETCHED_ITEMS_COUNT, after: endCursor },
-      // remove delay if it's set. Setting delay could cause issues with fetching more items.
-      context: { ...(queryOpts?.context ? queryOpts.context : {}), delay: 0 },
-    })
-  }, [
-    loading,
-    error,
-    fetchMore,
-    allItemsLoaded,
-    queryVariables,
-    targetLoadedItemsCount,
-    loadedItemsCount,
-    endCursor,
-    isReady,
-    queryOpts?.context,
-  ])
-
-  useEffect(() => {
-    if (!isEqual(queryVariablesRef.current, queryVariables)) {
-      queryVariablesRef.current = queryVariables
-      refetch()
-    }
-  }, [queryVariables, refetch])
-
-  // handle scroll to bottom
-  useEffect(() => {
-    if (onDemand || (onDemandInfinite && !activatedInfinteGrid)) {
-      return
-    }
-    if (error) return
-
-    const scrollHandler = debounce(() => {
-      const scrolledToBottom =
-        window.innerHeight + document.documentElement.scrollTop >= document.documentElement.offsetHeight
-      if (onScrollToBottom && scrolledToBottom && isReady && !loading && !allItemsLoaded) {
-        onScrollToBottom()
-      }
-    }, 100)
-
-    window.addEventListener('scroll', scrollHandler)
-    return () => window.removeEventListener('scroll', scrollHandler)
-  }, [error, isReady, loading, allItemsLoaded, onScrollToBottom, onDemand, onDemandInfinite, activatedInfinteGrid])
-
-  const edges = data?.edges
-
-  const isRefetching = networkStatus === NetworkStatus.refetch
-
-  const displayedEdges = edges?.slice(skipCount, targetLoadedItemsCount) ?? []
-  const displayedItems = isRefetching ? [] : displayedEdges.map((edge) => edge.node)
-
-  const displayedItemsCount = data
-    ? Math.min(targetDisplayedItemsCount, data.totalCount - skipCount)
-    : targetDisplayedItemsCount
-  const placeholdersCount = isRefetching ? targetDisplayedItemsCount : displayedItemsCount - displayedItems.length
-
-  return {
-    displayedItems,
-    placeholdersCount,
-    allItemsLoaded,
-    error,
-    loading,
-    totalCount: data?.totalCount || 0,
-  }
-}

+ 3 - 2
packages/atlas/src/components/LimitedWidthContainer/LimitedWidthContainer.tsx

@@ -2,13 +2,14 @@ import styled from '@emotion/styled'
 
 import { sizes } from '@/styles'
 
-type LimitedWidthContainerProps = { big?: boolean }
+type LimitedWidthContainerProps = { big?: boolean; noBottomPadding?: boolean; fullWidth?: boolean }
 
 export const LimitedWidthContainer = styled.div<LimitedWidthContainerProps>`
   --max-inner-width: calc(${({ big }) => (big ? '2284' : '1440')}px - calc(2 * var(--size-global-horizontal-padding)));
 
   max-width: var(--max-inner-width);
+  width: ${({ fullWidth }) => (fullWidth ? '100%' : 'unset')};
   position: relative;
   margin: 0 auto;
-  padding-bottom: ${sizes(16)};
+  padding-bottom: ${({ noBottomPadding }) => (noBottomPadding ? 0 : sizes(16))};
 `

+ 1 - 1
packages/atlas/src/components/MembershipInfo/MembershipInfo.tsx

@@ -52,7 +52,7 @@ export const MembershipInfo: FC<MembershipInfoProps> = ({
         <MembershipHeader className={className}>
           <MembershipInfoContainer>
             <Avatar
-              size={smMatch ? 'preview' : 'channel-card'}
+              size={smMatch ? 136 : 88}
               editable={editable}
               onImageValidation={onImageValidation}
               onClick={onAvatarEditClick}

+ 23 - 0
packages/atlas/src/components/MinimizedPlayer/MinimizedPlayer.styles.ts

@@ -0,0 +1,23 @@
+import { css, keyframes } from '@emotion/react'
+import styled from '@emotion/styled'
+
+import { zIndex } from '@/styles'
+
+const FadeIn = keyframes`
+  0% { opacity: 0}
+  100% { opacity: 1}
+`
+
+export const Wrapper = styled.div<{ isInView: boolean }>`
+  ${(props) =>
+    !props.isInView &&
+    css`
+      width: 320px;
+      position: fixed;
+      z-index: ${zIndex.modals};
+      right: 40px;
+      bottom: 0;
+      animation: ${FadeIn} 0.1s linear;
+      height: 180px;
+    `}
+`

+ 51 - 0
packages/atlas/src/components/MinimizedPlayer/MinimizedPlayer.tsx

@@ -0,0 +1,51 @@
+import { forwardRef, useEffect, useState } from 'react'
+
+import { VideoPlayer, VideoPlayerProps } from '@/components/_video/VideoPlayer'
+import { useMediaMatch } from '@/hooks/useMediaMatch'
+import { usePersonalDataStore } from '@/providers/personalData'
+
+import { Wrapper } from './MinimizedPlayer.styles'
+
+type MiniVideoProps = {
+  isInView: boolean
+  title?: string | null
+  author?: string | null
+} & Omit<VideoPlayerProps, 'isMinimized'>
+
+export const MinimizedPlayer = forwardRef<HTMLVideoElement, MiniVideoProps>(
+  ({ isInView, author, title, ...videoPlayerProps }, ref) => {
+    const [forceExit, setForceExit] = useState(false)
+    const [isPaused, setIsPaused] = useState(false)
+    const isAllowed = usePersonalDataStore((state) => state.allowMinimizedPleyer)
+    const mdMatch = useMediaMatch('md')
+
+    useEffect(() => {
+      if (isInView) {
+        setForceExit(false)
+      }
+    }, [isInView])
+
+    const inView = isAllowed && mdMatch && !isPaused ? isInView || forceExit : true
+
+    return (
+      <Wrapper isInView={inView}>
+        <VideoPlayer
+          ref={ref}
+          isMinimized={!inView}
+          onPause={() => {
+            setIsPaused(true)
+            videoPlayerProps.onPause?.()
+          }}
+          onPlay={() => {
+            setIsPaused(false)
+            videoPlayerProps.onPlay?.()
+          }}
+          onMinimizedExit={() => setForceExit(true)}
+          {...videoPlayerProps}
+        />
+      </Wrapper>
+    )
+  }
+)
+
+MinimizedPlayer.displayName = 'MinimizedPlayer'

+ 1 - 0
packages/atlas/src/components/MinimizedPlayer/index.ts

@@ -0,0 +1 @@
+export * from './MinimizedPlayer'

+ 32 - 32
packages/atlas/src/components/NftCarousel/MarketplaceCarousel.tsx

@@ -1,17 +1,18 @@
 import styled from '@emotion/styled'
 import { useMemo, useState } from 'react'
 
-import { GetFeaturedNftsQuery } from '@/api/queries/__generated__/nfts.generated'
+import { GetFeaturedNftsVideosQuery } from '@/api/queries/__generated__/nfts.generated'
 import { Carousel, CarouselProps, SwiperInstance } from '@/components/Carousel'
 import { SkeletonLoader } from '@/components/_loaders/SkeletonLoader'
+import { useMediaMatch } from '@/hooks/useMediaMatch'
 import { breakpoints, media } from '@/styles'
 
 import { MarketplaceCarouselCard } from './components/MarketplaceCarouselCard'
-import { CarouselNavItem } from './components/NftCarouselItem/CarouselNavItem'
+import { NftCarouselItem } from './components/NftCarouselItem/NftCarouselItem'
 
 type NftCarouselType = {
   type: 'nft'
-  nfts?: GetFeaturedNftsQuery['ownedNfts']
+  nfts?: GetFeaturedNftsVideosQuery['ownedNfts']
 }
 
 type MarketplaceCarouselTypes = NftCarouselType
@@ -32,6 +33,7 @@ const responsive: CarouselProps['breakpoints'] = {
 
 export const MarketplaceCarousel = ({ carouselProps, isLoading, ...rest }: MarketplaceCarouselProps) => {
   const [glider, setGlider] = useState<SwiperInstance | null>(null)
+  const mdMatch = useMediaMatch('md')
 
   const content = useMemo(() => {
     if (isLoading) {
@@ -45,52 +47,50 @@ export const MarketplaceCarousel = ({ carouselProps, isLoading, ...rest }: Marke
 
     if (rest.type === 'nft' && rest.nfts && glider) {
       return rest.nfts.map((nft, idx) => (
-        <CarouselNavItem key={idx} onClick={(dir) => (dir === '>' ? glider?.slideNext() : glider?.slidePrev())}>
+        <NftCarouselItem key={idx} onClick={(dir) => (dir === '>' ? glider?.slideNext() : glider?.slidePrev())}>
           {(isActive) => (
             <MarketplaceCarouselCard slideNext={() => glider?.slideNext()} active={isActive} type="nft" nft={nft} />
           )}
-        </CarouselNavItem>
+        </NftCarouselItem>
       ))
     }
 
     return [null]
   }, [rest.type, rest.nfts, glider, isLoading])
 
+  if (!isLoading && (!rest.nfts || rest.nfts.length < 4)) {
+    return null
+  }
+
   return (
-    <Carousel
-      spaceBetween={12}
-      loop
-      centeredSlides
-      slidesPerView={1.3}
-      breakpoints={responsive}
-      onSwiper={(swiper) => setGlider(swiper)}
-    >
-      {content}
-    </Carousel>
+    <FullWidthWrapper>
+      <Carousel
+        spaceBetween={mdMatch ? 24 : 16}
+        loop
+        roundLengths
+        centeredSlides
+        slidesPerView={1.3}
+        breakpoints={responsive}
+        onSwiper={(swiper) => setGlider(swiper)}
+      >
+        {content}
+      </Carousel>
+    </FullWidthWrapper>
   )
 }
 
 const StyledSkeleton = styled(SkeletonLoader)`
-  height: 325px;
   width: 100%;
+  aspect-ratio: 1/1;
 
   ${media.sm} {
-    min-height: 340px;
-  }
-
-  ${media.md} {
-    min-height: 410px;
-  }
-
-  ${media.lg} {
-    min-height: 610px;
-  }
-
-  ${media.xl} {
-    min-height: 660px;
+    aspect-ratio: 16/9;
   }
+`
 
-  ${media.xxl} {
-    min-height: 830px;
-  }
+export const FullWidthWrapper = styled.div`
+  width: calc(100% + var(--size-global-horizontal-padding) * 2);
+  margin-left: calc(var(--size-global-horizontal-padding) * -1);
+  overflow: hidden;
+  position: relative;
 `

+ 27 - 73
packages/atlas/src/components/NftCarousel/components/MarketplaceCarouselCard/MarketplaceCarouselCard.styles.ts

@@ -1,22 +1,22 @@
-import { css } from '@emotion/react'
 import styled from '@emotion/styled'
+import { Link } from 'react-router-dom'
 
 import { cVar, media, sizes } from '@/styles'
 
-export const InformationContainer = styled.div`
-  width: calc(100% + 1px);
+export const InformationContainer = styled.div<{ isPaused: boolean }>`
+  width: 100%;
   display: flex;
   align-items: end;
   transition: all ${cVar('animationTransitionMedium')};
   margin-top: ${sizes(4)};
-  margin-bottom: ${sizes(8)};
   z-index: 12;
 
   ${media.sm} {
+    width: fit-content;
     position: absolute;
-    margin-top: 0;
-    opacity: 0.25;
-    padding: 0 100px 0 ${sizes(8)};
+    margin: 0;
+    opacity: ${({ isPaused }) => (isPaused ? 1 : 0.25)};
+    inset: auto auto 40px 32px;
   }
 `
 
@@ -27,40 +27,6 @@ export const Container = styled.div<{ isActive: boolean }>`
   width: 100%;
   justify-content: end;
 
-  ::after {
-    ${media.sm} {
-      content: '';
-      position: absolute;
-      inset: 50% 0 0 0;
-      margin-top: 0;
-
-      ${(props) =>
-        props.isActive &&
-        css`
-          background: linear-gradient(
-            180deg,
-            rgb(7 8 8 / 0) 0%,
-            rgb(7 8 8 / 0.0071) 11.79%,
-            rgb(7 8 8 / 0.0276) 21.38%,
-            rgb(7 8 8 / 0.0598) 29.12%,
-            rgb(7 8 8 / 0.1026) 35.34%,
-            rgb(7 8 8 / 0.1543) 40.37%,
-            rgb(7 8 8 / 0.2135) 44.56%,
-            rgb(7 8 8 / 0.2789) 48.24%,
-            rgb(7 8 8 / 0.349) 51.76%,
-            rgb(7 8 8 / 0.4222) 55.44%,
-            rgb(7 8 8 / 0.4974) 59.63%,
-            rgb(7 8 8 / 0.5729) 64.66%,
-            rgb(7 8 8 / 0.6474) 70.88%,
-            rgb(7 8 8 / 0.7193) 78.62%,
-            rgb(7 8 8 / 0.7873) 88.21%,
-            rgb(7 8 8 / 0.85) 100%
-          );
-          border-bottom: 32px solid ${cVar('colorCoreNeutral700Darken')};
-        `}
-    }
-  }
-
   :hover {
     ${InformationContainer} {
       opacity: 1;
@@ -70,26 +36,11 @@ export const Container = styled.div<{ isActive: boolean }>`
 
 export const VideoContainer = styled.div`
   position: relative;
-  height: 325px;
+  width: 100%;
+  aspect-ratio: 1/1;
 
   ${media.sm} {
-    min-height: 340px;
-  }
-
-  ${media.md} {
-    min-height: 410px;
-  }
-
-  ${media.lg} {
-    min-height: 610px;
-  }
-
-  ${media.xl} {
-    min-height: 660px;
-  }
-
-  ${media.xxl} {
-    min-height: 830px;
+    aspect-ratio: 16/9;
   }
 `
 
@@ -97,25 +48,28 @@ export const DetailsContainer = styled.div`
   display: flex;
   flex-direction: column;
   gap: ${sizes(4)};
+  width: 100%;
 `
 
 export const StatsContainer = styled.div`
-  display: flex;
-  flex-wrap: wrap;
-  row-gap: ${sizes(4)};
-  column-gap: ${sizes(6)};
+  display: grid;
+  width: 100%;
+  grid-gap: ${sizes(4)};
+  grid-template-columns: 1fr 1fr;
 
-  * > {
-    flex: 1;
+  ${media.sm} {
+    display: flex;
+    flex-wrap: wrap;
+    row-gap: ${sizes(4)};
+    column-gap: ${sizes(8)};
+
+    * > {
+      flex: 1;
+    }
   }
 `
 
-export const ShadeBox = styled.div`
-  width: calc(100% + 1px);
-  height: 32px;
-  opacity: 0.25;
-
-  :hover {
-    opacity: 1;
-  }
+export const StyledLink = styled(Link)`
+  text-decoration: none;
+  color: inherit;
 `

+ 2 - 2
packages/atlas/src/components/NftCarousel/components/MarketplaceCarouselCard/MarketplaceCarouselCard.tsx

@@ -1,4 +1,4 @@
-import { GetFeaturedNftsQuery } from '@/api/queries/__generated__/nfts.generated'
+import { GetFeaturedNftsVideosQuery } from '@/api/queries/__generated__/nfts.generated'
 import { NftCarouselDetails } from '@/components/NftCarousel/components/MarketplaceCarouselCard/NftCarouselDetails'
 
 type CrtCard = {
@@ -7,7 +7,7 @@ type CrtCard = {
 
 type NftCard = {
   type: 'nft'
-  nft: GetFeaturedNftsQuery['ownedNfts'][number]
+  nft: GetFeaturedNftsVideosQuery['ownedNfts'][number]
 }
 
 type CardTypes = NftCard | CrtCard

+ 120 - 74
packages/atlas/src/components/NftCarousel/components/MarketplaceCarouselCard/NftCarouselDetails.tsx

@@ -1,10 +1,10 @@
 import BN from 'bn.js'
-import { useLayoutEffect, useState } from 'react'
+import { useLayoutEffect, useMemo, useState } from 'react'
 import { useNavigate } from 'react-router'
 import { CSSTransition } from 'react-transition-group'
 
 import { getNftStatus } from '@/api/hooks/nfts'
-import { GetFeaturedNftsQuery } from '@/api/queries/__generated__/nfts.generated'
+import { GetFeaturedNftsVideosQuery } from '@/api/queries/__generated__/nfts.generated'
 import { AvatarGroup } from '@/components/Avatar/AvatarGroup'
 import { JoyTokenIcon } from '@/components/JoyTokenIcon'
 import {
@@ -12,6 +12,7 @@ import {
   DetailsContainer,
   InformationContainer,
   StatsContainer,
+  StyledLink,
   VideoContainer,
 } from '@/components/NftCarousel/components/MarketplaceCarouselCard/MarketplaceCarouselCard.styles'
 import { Text } from '@/components/Text'
@@ -20,6 +21,7 @@ import { DetailsContent } from '@/components/_nft/NftTile'
 import { BackgroundVideoPlayer } from '@/components/_video/BackgroundVideoPlayer'
 import { absoluteRoutes } from '@/config/routes'
 import { useBlockTimeEstimation } from '@/hooks/useBlockTimeEstimation'
+import { useMediaMatch } from '@/hooks/useMediaMatch'
 import { hapiBnToTokenNumber } from '@/joystream-lib/utils'
 import { getMemberAvatar } from '@/providers/assets/assets.helpers'
 import { transitions } from '@/styles'
@@ -29,66 +31,93 @@ export const NftCarouselDetails = ({
   active,
   slideNext,
 }: {
-  nft: GetFeaturedNftsQuery['ownedNfts'][number]
+  nft: GetFeaturedNftsVideosQuery['ownedNfts'][number]
   active: boolean
   slideNext: () => void
 }) => {
+  const smMatch = useMediaMatch('sm')
   const navigate = useNavigate()
+  const [timeLeft, setTimeLeft] = useState<string | null>(null)
+  const [isPaused, setIsPaused] = useState(!active)
+  const { convertBlockToMsTimestamp } = useBlockTimeEstimation()
+  const nftStatus = getNftStatus(nft, nft?.video)
+
   const creatorAvatarUrl =
     nft.owner.__typename === 'NftOwnerChannel'
       ? nft.owner.channel.avatarPhoto?.resolvedUrl
       : getMemberAvatar(nft.owner.member).url
-
-  const [timeLeft, setTimeLeft] = useState<string | null>(null)
   const thumbnailUrl = nft.video.thumbnailPhoto?.resolvedUrl
-  const { convertBlockToMsTimestamp } = useBlockTimeEstimation()
-
   const mediaUrl = nft.video.media?.resolvedUrl
-
-  const isLoading = !thumbnailUrl || !mediaUrl
-
-  const nftStatus = getNftStatus(nft, nft?.video)
   const plannedEndDateBlockTimestamp =
     nftStatus?.status === 'auction' &&
     nftStatus.auctionPlannedEndBlock &&
     convertBlockToMsTimestamp(nftStatus.auctionPlannedEndBlock)
-
+  const isLoading = !thumbnailUrl || !mediaUrl
   const name = nft.owner.__typename === 'NftOwnerChannel' ? nft.video.channel.title : nft.owner.member.handle
+  const owner = useMemo(
+    () =>
+      nft?.owner.__typename === 'NftOwnerChannel'
+        ? {
+            name,
+            assetUrl: creatorAvatarUrl,
+            onClick: () => navigate(absoluteRoutes.viewer.channel(nft.video.channel.id)),
+          }
+        : nft?.owner.__typename === 'NftOwnerMember'
+        ? {
+            name,
+            assetUrl: creatorAvatarUrl,
+            onClick: () => name && navigate(absoluteRoutes.viewer.member(name)),
+          }
+        : undefined,
+    [creatorAvatarUrl, name, navigate, nft]
+  )
 
-  const owner =
-    nft?.owner.__typename === 'NftOwnerChannel'
-      ? {
-          name,
-          assetUrl: creatorAvatarUrl,
-          onClick: () => navigate(absoluteRoutes.viewer.channel(nft.video.channel.id)),
-        }
-      : nft?.owner.__typename === 'NftOwnerMember'
-      ? {
-          name,
-          assetUrl: creatorAvatarUrl,
-          onClick: () => name && navigate(absoluteRoutes.viewer.member(name)),
-        }
-      : undefined
+  const nftDetails = useMemo(
+    () => ({
+      buyNow:
+        nftStatus?.status === 'auction' || nftStatus?.status === 'buy-now'
+          ? nftStatus.buyNowPrice
+            ? hapiBnToTokenNumber(nftStatus.buyNowPrice)
+            : undefined
+          : undefined,
+      creator: {
+        name: nft?.video.channel.title || undefined,
+        assetUrl: creatorAvatarUrl,
+        onClick: () => navigate(absoluteRoutes.viewer.channel(nft?.video.channel.id)),
+      },
+      title: nft.video.title,
+      type: 'nft',
+      topBid:
+        nftStatus?.status === 'auction' && nftStatus.topBid?.amount
+          ? hapiBnToTokenNumber(new BN(nftStatus.topBid?.amount))
+          : undefined,
+      minBid:
+        nftStatus?.status === 'auction' && nftStatus.startingPrice
+          ? hapiBnToTokenNumber(nftStatus.startingPrice)
+          : undefined,
+    }),
+    [creatorAvatarUrl, navigate, nft, nftStatus]
+  )
 
-  const nftDetails = {
-    buyNow:
-      nftStatus?.status === 'auction' || nftStatus?.status === 'buy-now'
-        ? nftStatus.buyNowPrice
-          ? hapiBnToTokenNumber(nftStatus.buyNowPrice)
-          : undefined
-        : undefined,
-    creator: {
-      name: nft?.video.channel.title || undefined,
-      assetUrl: creatorAvatarUrl,
-      onClick: () => navigate(absoluteRoutes.viewer.channel(nft?.video.channel.id)),
-    },
-    title: nft.video.title,
-    type: 'nft',
-    topBid:
-      nftStatus?.status === 'auction' && nftStatus.topBid?.amount
-        ? hapiBnToTokenNumber(new BN(nftStatus.topBid?.amount))
-        : undefined,
-  }
+  const avatars = useMemo(
+    () => [
+      {
+        url: nftDetails.creator?.assetUrl,
+        tooltipText: `Creator: ${nftDetails.creator?.name}`,
+        onClick: nftDetails.creator?.onClick,
+      },
+      ...(owner
+        ? [
+            {
+              url: owner?.assetUrl,
+              tooltipText: `Owner: ${owner?.name}`,
+              onClick: owner?.onClick,
+            },
+          ]
+        : []),
+    ],
+    [nftDetails.creator?.assetUrl, nftDetails.creator?.name, nftDetails.creator?.onClick, owner]
+  )
 
   useLayoutEffect(() => {
     if (plannedEndDateBlockTimestamp) {
@@ -106,7 +135,9 @@ export const NftCarouselDetails = ({
         const minutes = Math.floor((timeDiffInSeconds / 60) % 60)
         const seconds = Math.floor(timeDiffInSeconds % 60)
         setTimeLeft(
-          `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`
+          `${hours ? `${String(hours).padStart(2, '0')}:` : ''}${
+            minutes ? `${String(minutes).padStart(2, '0')}:` : ''
+          }${String(seconds).padStart(2, '0')}`
         )
       }, 1000)
 
@@ -128,10 +159,13 @@ export const NftCarouselDetails = ({
     <Container isActive={active}>
       <VideoContainer>
         <BackgroundVideoPlayer
+          videoId={nft.video.id}
+          withFade={active}
           autoPlay={active}
           playing={active}
           muted={true}
-          onPause={(e) => (e.currentTarget.currentTime = 0)}
+          onPause={() => setIsPaused(true)}
+          onPlay={() => setIsPaused(false)}
           preload="auto"
           src={mediaUrl ?? undefined}
           poster={thumbnailUrl ?? undefined}
@@ -142,49 +176,61 @@ export const NftCarouselDetails = ({
       </VideoContainer>
       {active && (
         <CSSTransition in={active} timeout={100} classNames={transitions.names.fade} unmountOnExit>
-          <InformationContainer>
+          <InformationContainer isPaused={isPaused}>
             <DetailsContainer>
-              <AvatarGroup
-                avatarStrokeColor="transparent"
-                avatars={[
-                  {
-                    url: nftDetails.creator?.assetUrl,
-                    tooltipText: `Creator: ${nftDetails.creator?.name}`,
-                    onClick: nftDetails.creator?.onClick,
-                  },
-                  ...(owner
-                    ? [
-                        {
-                          url: owner?.assetUrl,
-                          tooltipText: `Owner: ${owner?.name}`,
-                          onClick: owner?.onClick,
-                        },
-                      ]
-                    : []),
-                ]}
-              />
-              <Text variant="h500" as="p">
-                {nftDetails.title}
+              <AvatarGroup spreadAvatars avatarStrokeColor="transparent" avatars={avatars} />
+              <Text variant={smMatch ? 'h500' : 'h400'} as={smMatch ? 'h5' : 'h4'}>
+                <StyledLink to={absoluteRoutes.viewer.video(nft.video.id)}>{nftDetails.title}</StyledLink>
               </Text>
               <StatsContainer>
                 {nftDetails.buyNow && (
                   <DetailsContent
-                    tileSize="big"
+                    avoidIconStyling
+                    tileSize={smMatch ? 'big' : 'bigSmall'}
                     caption="BUY NOW"
                     content={nftDetails.buyNow}
-                    icon={<JoyTokenIcon size={16} variant="regular" />}
+                    icon={<JoyTokenIcon size={smMatch ? 24 : 16} variant="silver" />}
                   />
                 )}
                 {nftDetails.topBid && (
                   <DetailsContent
-                    tileSize="big"
+                    avoidIconStyling
+                    tileSize={smMatch ? 'big' : 'bigSmall'}
                     caption="TOP BID"
                     content={nftDetails.topBid}
-                    icon={<JoyTokenIcon size={16} variant="regular" />}
+                    icon={<JoyTokenIcon size={smMatch ? 24 : 16} variant="silver" />}
+                  />
+                )}
+                {nftDetails.minBid && (
+                  <DetailsContent
+                    avoidIconStyling
+                    tileSize={smMatch ? 'big' : 'bigSmall'}
+                    caption="MIN BID"
+                    content={nftDetails.minBid}
+                    icon={<JoyTokenIcon size={smMatch ? 24 : 16} variant="silver" />}
                   />
                 )}
 
-                {timeLeft && <DetailsContent tileSize="big" caption="AUCTION ENDS IN" content={timeLeft} />}
+                {timeLeft && (
+                  <DetailsContent
+                    tileSize={smMatch ? 'big' : 'bigSmall'}
+                    caption="AUCTION ENDS IN"
+                    content={timeLeft.split(':').map((tick, i) => {
+                      return (
+                        <>
+                          {i !== 0 ? (
+                            <Text as="span" color="colorText" variant={smMatch ? 'h500' : 'h400'}>
+                              :
+                            </Text>
+                          ) : null}
+                          <Text as="span" variant={smMatch ? 'h500' : 'h400'}>
+                            {tick}
+                          </Text>
+                        </>
+                      )
+                    })}
+                  />
+                )}
               </StatsContainer>
             </DetailsContainer>
           </InformationContainer>

+ 2 - 2
packages/atlas/src/components/NftCarousel/components/NftCarouselItem/NftCarouselItem.styles.ts

@@ -52,14 +52,14 @@ export const NavigationContainer = styled.div`
     position: absolute;
     inset: 0;
     z-index: -1;
-    opacity: 0.5;
+    opacity: 0.7;
     background: ${cVar('colorBackgroundMuted')};
     transition: opacity ${cVar('animationTransitionMedium')};
   }
 
   :hover {
     ::after {
-      opacity: 0.25;
+      opacity: 0.55;
     }
   }
 `

+ 2 - 2
packages/atlas/src/components/NftCarousel/components/NftCarouselItem/CarouselNavItem.tsx → packages/atlas/src/components/NftCarousel/components/NftCarouselItem/NftCarouselItem.tsx

@@ -5,11 +5,11 @@ import { SvgActionChevronL, SvgActionChevronR } from '@/assets/icons'
 
 import { ItemWrapper, LeftChevronContainer, NavigationContainer, RightChevronContainer } from './NftCarouselItem.styles'
 
-type CarouselNavItemProps = {
+type NftCarouselItemProps = {
   onClick?: (dir: '<' | '>') => void
   children?: ((isActive: boolean) => ReactNode) | ReactNode
 }
-export const CarouselNavItem = ({ onClick, children }: CarouselNavItemProps) => {
+export const NftCarouselItem = ({ onClick, children }: NftCarouselItemProps) => {
   const { isActive } = useSwiperSlide()
   return (
     <ItemWrapper>

+ 1 - 1
packages/atlas/src/components/OutputPill/OutputPill.tsx

@@ -35,7 +35,7 @@ export const OutputPill: FC<OutputPillProps> = ({
 
   return (
     <OutputPillWrapper className={className} withoutButton={!onDeleteClick || readonly}>
-      {withAvatar && <StyledAvatar size="bid" assetUrl={avatarUri} loading={isLoadingAvatar} />}
+      {withAvatar && <StyledAvatar size={24} assetUrl={avatarUri} loading={isLoadingAvatar} />}
       <Text variant="t200" as="p">
         {handle}
       </Text>

+ 1 - 1
packages/atlas/src/components/OwnerPill/OwnerPill.tsx

@@ -17,7 +17,7 @@ export type OwnerPillProps = {
 export const OwnerPill: FC<OwnerPillProps> = ({ handle, avatar, onClick, title }) => {
   return (
     <OwnerPillWrapper onClick={onClick} title={title}>
-      <Avatar size="bid" {...avatar} />
+      <Avatar size={24} {...avatar} />
       <DesaturedText as="span" variant="t100">
         Owner:
       </DesaturedText>

+ 1 - 1
packages/atlas/src/components/Searchbar/SearchBox/SearchBox.styles.ts

@@ -87,7 +87,7 @@ export const Container = styled.div<ContainerProps>`
   }
 `
 
-export const Section = styled.section`
+export const SearchSection = styled.section`
   padding: ${sizes(2)} 0;
 
   :not(:last-child) {

+ 8 - 8
packages/atlas/src/components/Searchbar/SearchBox/SearchBox.tsx

@@ -13,7 +13,7 @@ import {
   Caption,
   Container,
   PlaceholderWrapper,
-  Section,
+  SearchSection,
   ShortcutsGroup,
   ShortcutsWrapper,
   SkeletonAvatar,
@@ -147,7 +147,7 @@ export const SearchBox: FC<SearchBoxProps> = memo(
         data-scroll-lock-scrollable
       >
         {!!filteredRecentSearches.length && (
-          <Section>
+          <SearchSection>
             <Caption as="span" color="colorText" variant="t100">
               Recent searches
             </Caption>
@@ -163,11 +163,11 @@ export const SearchBox: FC<SearchBoxProps> = memo(
                 selectedItem={selectedItem}
               />
             ))}
-          </Section>
+          </SearchSection>
         )}
-        {loading && !!searchQuery && <Section>{placeholders}</Section>}
+        {loading && !!searchQuery && <SearchSection>{placeholders}</SearchSection>}
         {!!slicedVideos.length && !loading && (
-          <Section>
+          <SearchSection>
             <Caption as="span" color="colorText" variant="t100">
               Videos
             </Caption>
@@ -182,10 +182,10 @@ export const SearchBox: FC<SearchBoxProps> = memo(
                 selectedItem={selectedItem}
               />
             ))}
-          </Section>
+          </SearchSection>
         )}
         {!!slicedChannels.length && !loading && (
-          <Section>
+          <SearchSection>
             <Caption as="span" color="colorText" variant="t100">
               Channels
             </Caption>
@@ -199,7 +199,7 @@ export const SearchBox: FC<SearchBoxProps> = memo(
                 selectedItem={selectedItem}
               />
             ))}
-          </Section>
+          </SearchSection>
         )}
         <ShortcutsWrapper>
           <ShortcutsGroup>

+ 2 - 5
packages/atlas/src/components/Section/Section.stories.tsx

@@ -148,7 +148,7 @@ export default {
   ],
 } as Meta
 
-const DefaultTemplate: StoryFn<SectionProps> = () => {
+const DefaultTemplate: StoryFn<SectionProps<unknown>> = () => {
   const [filters, setFilters] = useState<SectionFilter[]>(INITIAL_STATE)
   const [placeholdersCount, setPlaceholdersCount] = useState(8)
   const [secondPlaceholdersCount, setSecondPlaceholdersCount] = useState(8)
@@ -171,7 +171,6 @@ const DefaultTemplate: StoryFn<SectionProps> = () => {
         }}
         contentProps={{
           type: 'grid',
-          minChildrenWidth: 200,
           children: placeholderItems.map((_, idx) => (
             <VideoTile key={idx} loadingDetails={true} loadingAvatar={true} loadingThumbnail={true} />
           )),
@@ -204,7 +203,6 @@ const DefaultTemplate: StoryFn<SectionProps> = () => {
         }}
         contentProps={{
           type: 'grid',
-          minChildrenWidth: 200,
           children: secondPlaceholderItems.map((_, idx) => (
             <VideoTile key={idx} loadingDetails={true} loadingAvatar={true} loadingThumbnail={true} />
           )),
@@ -229,7 +227,6 @@ const DefaultTemplate: StoryFn<SectionProps> = () => {
         }}
         contentProps={{
           type: 'grid',
-          minChildrenWidth: 200,
           children: secondPlaceholderItems.map((_, idx) => (
             <VideoTile key={idx} loadingDetails={true} loadingAvatar={true} loadingThumbnail={true} />
           )),
@@ -246,7 +243,7 @@ const DefaultTemplate: StoryFn<SectionProps> = () => {
 
 export const Default = DefaultTemplate.bind({})
 
-const CarouselTemplate: StoryFn<SectionProps> = () => {
+const CarouselTemplate: StoryFn<SectionProps<unknown>> = () => {
   const placeholderItems = createPlaceholderData(10)
   return (
     <div style={{ display: 'grid', gap: 64, paddingBottom: 200 }}>

+ 4 - 3
packages/atlas/src/components/Section/Section.styles.ts

@@ -2,10 +2,11 @@ import styled from '@emotion/styled'
 
 import { media, sizes } from '@/styles'
 
-export const SectionWrapper = styled.section`
+export const SectionWrapper = styled.section<{ withoutGap?: boolean }>`
   display: grid;
-  gap: ${sizes(4)};
+  position: relative;
+  gap: ${({ withoutGap }) => (withoutGap ? 0 : sizes(4))};
   ${media.sm} {
-    gap: ${sizes(6)};
+    gap: ${({ withoutGap }) => (withoutGap ? 0 : sizes(6))};
   }
 `

+ 43 - 16
packages/atlas/src/components/Section/Section.tsx

@@ -1,4 +1,5 @@
-import { FC, PropsWithChildren, useRef } from 'react'
+import { useEffect, useRef, useState } from 'react'
+import Swiper from 'swiper'
 
 import { SectionWrapper } from './Section.styles'
 import { SectionContent, SectionContentProps } from './SectionContent'
@@ -7,15 +8,19 @@ import { SectionHeader, SectionHeaderProps } from './SectionHeader'
 
 import { SwiperInstance } from '../Carousel'
 
-export type SectionProps = {
-  headerProps: Omit<SectionHeaderProps, 'isCarousel'>
+export type SectionProps<T> = {
+  headerProps?: Omit<SectionHeaderProps<T>, 'isCarousel'>
   contentProps: SectionContentProps
   footerProps?: SectionFooterProps
   className?: string
+  withoutGap?: boolean
 }
 
-export const Section: FC<PropsWithChildren<SectionProps>> = ({ headerProps, contentProps, footerProps, className }) => {
+export function Section<T>({ headerProps, contentProps, footerProps, className, withoutGap }: SectionProps<T>) {
   const isCarousel = contentProps.type === 'carousel'
+  const [isCarouselEnd, setIsCarouselEnd] = useState(false)
+  const [isCarouselBeginning, setIsCarouselBeginning] = useState(true)
+
   const gliderRef = useRef<SwiperInstance>()
 
   const handleSlideLeft = () => {
@@ -24,20 +29,42 @@ export const Section: FC<PropsWithChildren<SectionProps>> = ({ headerProps, cont
   const handleSlideRight = () => {
     gliderRef.current?.slideNext()
   }
+
+  useEffect(() => {
+    const handler = (slider: Swiper) => {
+      setIsCarouselBeginning(slider.isBeginning)
+      setIsCarouselEnd(slider.isEnd)
+    }
+    gliderRef.current?.on('slideChange', handler)
+    gliderRef.current?.on('resize', handler)
+    gliderRef.current?.on('update', handler)
+
+    return () => {
+      gliderRef.current?.off('slideChange', handler)
+      gliderRef.current?.off('resize', handler)
+      gliderRef.current?.off('update', handler)
+    }
+  }, [])
+
   return (
-    <SectionWrapper className={className}>
-      {isCarousel ? (
-        <SectionHeader
-          {...headerProps}
-          isCarousel
-          onMoveCarouselLeft={handleSlideLeft}
-          onMoveCarouselRight={handleSlideRight}
-        />
-      ) : (
-        <SectionHeader {...headerProps} />
-      )}
+    <SectionWrapper withoutGap={withoutGap} className={className}>
+      {headerProps &&
+        (isCarousel ? (
+          <SectionHeader
+            {...headerProps}
+            isBeginning={isCarouselBeginning}
+            isEnd={isCarouselEnd}
+            isCarousel
+            onMoveCarouselLeft={handleSlideLeft}
+            onMoveCarouselRight={handleSlideRight}
+          />
+        ) : (
+          <SectionHeader {...headerProps} />
+        ))}
       {isCarousel ? (
-        <SectionContent {...contentProps} onSwiper={(swiper) => (gliderRef.current = swiper)} />
+        contentProps.children.length > 0 && (
+          <SectionContent {...contentProps} onSwiper={(swiper) => (gliderRef.current = swiper)} />
+        )
       ) : (
         <SectionContent {...contentProps} />
       )}

+ 0 - 1
packages/atlas/src/components/Section/SectionContent/SectionContent.stories.tsx

@@ -23,7 +23,6 @@ const Template: StoryFn<SectionContentProps> = (args: SectionContentProps) => {
 export const Grid = Template.bind({})
 Grid.args = {
   type: 'grid',
-  minChildrenWidth: 200,
 }
 
 export const Carousel = Template.bind({})

+ 25 - 3
packages/atlas/src/components/Section/SectionContent/SectionContent.styles.ts

@@ -1,13 +1,35 @@
+import { css } from '@emotion/react'
 import styled from '@emotion/styled'
 
-import { media, sizes } from '@/styles'
+import { BreakpointKey, Grid, media, sizes } from '@/styles'
 
-export const GridWrapper = styled.div<{ minWidth: number }>`
+export type GridWrapperProps = {
+  grid?: Grid
+}
+
+const createGridBreakpoints = ({ grid = { xxs: { columns: 'auto', minItemWidth: 300 } } }: GridWrapperProps) => {
+  const gridKeys = Object.keys(grid) as Array<BreakpointKey>
+
+  const styles = gridKeys.map((key) => {
+    const gridValue = grid[key]
+    const repeatCounts = gridValue?.columns === 'auto' ? 'auto-fill' : gridValue?.columns
+    const tracks = gridValue?.columns === 'auto' ? `minmax(${gridValue.minItemWidth}, 1fr)` : 'minmax(0, 1fr)'
+    return css`
+      ${media[key]} {
+        grid-template-columns: repeat(${repeatCounts}, ${tracks});
+      }
+    `
+  })
+  return styles
+}
+
+export const GridWrapper = styled.div<GridWrapperProps>`
   display: grid;
-  grid-template-columns: repeat(auto-fill, minmax(${(props) => `${props.minWidth}px, 1fr`}));
   gap: ${sizes(4)};
 
   ${media.md} {
     gap: ${sizes(6)};
   }
+
+  ${createGridBreakpoints}
 `

+ 3 - 4
packages/atlas/src/components/Section/SectionContent/SectionContent.tsx

@@ -1,12 +1,11 @@
 import { FC, ReactElement } from 'react'
 
 import { Carousel, CarouselProps } from '@/components/Carousel'
-import { GridWrapper } from '@/components/Section/SectionContent/SectionContent.styles'
+import { GridWrapper, GridWrapperProps } from '@/components/Section/SectionContent/SectionContent.styles'
 
 type SectionGridTypeProps = {
   type: 'grid'
-  minChildrenWidth: number
-}
+} & GridWrapperProps
 
 type SectionCarouselTypeProps = {
   type: 'carousel'
@@ -22,7 +21,7 @@ export type SectionContentProps = {
 export const SectionContent: FC<SectionContentProps> = (props) => {
   if (props.type === 'grid') {
     return (
-      <GridWrapper className={props.className} minWidth={props.minChildrenWidth}>
+      <GridWrapper grid={props.grid} className={props.className}>
         {props.children}
       </GridWrapper>
     )

+ 10 - 6
packages/atlas/src/components/Section/SectionFooter/SectionFooter.tsx

@@ -17,6 +17,7 @@ type SectionFooterLoadProps = {
   label: string
   fetchMore: () => Promise<void>
   reachedEnd: boolean
+  loadingTriggerOffset?: number
 }
 
 type SectionFooterPaginationProps = {
@@ -27,6 +28,7 @@ type SectionFooterInfiniteLoadingProps = {
   type: 'infinite'
   fetchMore: () => Promise<void>
   reachedEnd: boolean
+  loadingTriggerOffset?: number
 }
 
 export type SectionFooterProps =
@@ -35,9 +37,11 @@ export type SectionFooterProps =
   | SectionFooterPaginationProps
   | SectionFooterInfiniteLoadingProps
 
-const InfiniteLoaderMargin = styled.div`
+const InfiniteLoaderMargin = styled.div<{ loadingTriggerOffset?: number }>`
   width: 100%;
   height: 1px;
+  position: absolute;
+  bottom: ${({ loadingTriggerOffset }) => (loadingTriggerOffset ? `${loadingTriggerOffset}px` : 0)};
 `
 export const SectionFooter = (props: SectionFooterProps) => {
   const [isSwitchedToInfinite, setIsSwitchedToInfinite] = useState(false)
@@ -63,7 +67,7 @@ export const SectionFooter = (props: SectionFooterProps) => {
     }
   }, [isLoading, props])
 
-  if (props.type === 'load') {
+  if (props.type === 'load' && !props.reachedEnd) {
     return !isSwitchedToInfinite ? (
       <Button
         variant="tertiary"
@@ -76,12 +80,12 @@ export const SectionFooter = (props: SectionFooterProps) => {
         {props.label}
       </Button>
     ) : (
-      <InfiniteLoaderMargin ref={ref} />
+      <InfiniteLoaderMargin ref={ref} loadingTriggerOffset={props.loadingTriggerOffset} />
     )
   }
 
-  if (props.type === 'infinite') {
-    return <InfiniteLoaderMargin ref={ref} />
+  if (props.type === 'infinite' && !props.reachedEnd && !isLoading) {
+    return <InfiniteLoaderMargin ref={ref} loadingTriggerOffset={props.loadingTriggerOffset} />
   }
 
   if (props.type === 'link') {
@@ -104,5 +108,5 @@ export const SectionFooter = (props: SectionFooterProps) => {
     return <Pagination {...paginationProps} />
   }
 
-  return <div />
+  return null
 }

+ 13 - 4
packages/atlas/src/components/Section/SectionHeader/SectionHeader.stories.tsx

@@ -134,9 +134,9 @@ export default {
       </OverlayManagerProvider>
     ),
   ],
-} as Meta<SectionHeaderProps>
+} as Meta<SectionHeaderProps<unknown>>
 
-const DefaultTemplate: StoryFn<SectionHeaderProps> = (args: SectionHeaderProps) => {
+const DefaultTemplate: StoryFn<SectionHeaderProps<unknown>> = (args: SectionHeaderProps<unknown>) => {
   return <SectionHeader {...args} />
 }
 
@@ -194,7 +194,7 @@ const WithTabsTemplate = () => {
 
 export const WithTabs = WithTabsTemplate.bind({})
 
-const WithTitleTemplate: StoryFn<SectionHeaderProps> = () => {
+const WithTitleTemplate: StoryFn<SectionHeaderProps<unknown>> = () => {
   const [filters, setFilters] = useState<SectionFilter[]>(INITIAL_STATE)
 
   return (
@@ -255,7 +255,16 @@ const WithTitleTemplate: StoryFn<SectionHeaderProps> = () => {
           type: 'toggle-button',
           toggleButtonOptionTypeProps: {
             type: 'options',
-            options: ['Newest', 'Oldest'],
+            options: [
+              {
+                label: 'Newest',
+                value: 'Newest',
+              },
+              {
+                label: 'Oldest',
+                value: 'Oldest',
+              },
+            ],
             onChange: () => null,
           },
         }}

+ 28 - 29
packages/atlas/src/components/Section/SectionHeader/SectionHeader.tsx

@@ -26,10 +26,10 @@ import { Tabs, TabsProps } from '../../Tabs'
 import { Select, SelectProps } from '../../_inputs/Select'
 import { ToggleButtonGroup, ToggleButtonOptionTypeProps } from '../../_inputs/ToggleButtonGroup'
 
-type Sort =
+type Sort<T> =
   | {
       type: 'toggle-button'
-      toggleButtonOptionTypeProps: ToggleButtonOptionTypeProps
+      toggleButtonOptionTypeProps: ToggleButtonOptionTypeProps<T>
     }
   | {
       type: 'select'
@@ -67,21 +67,23 @@ type Carousel =
       isCarousel?: true
       onMoveCarouselRight?: () => void
       onMoveCarouselLeft?: () => void
+      isBeginning?: boolean
+      isEnd?: boolean
     }
   | {
       isCarousel?: false
     }
 
-export type SectionHeaderProps = {
+export type SectionHeaderProps<T> = {
   start: SectionHeaderStart
   search?: SearchProps
-  sort?: Sort
+  sort?: Sort<T>
   filters?: SectionFilter[]
   onApplyFilters?: (appliedFilters: SectionFilter[]) => void
   button?: Omit<ButtonProps, 'size' | 'variant'>
 } & Carousel
 
-export const SectionHeader: FC<SectionHeaderProps> = (props) => {
+export function SectionHeader<T>(props: SectionHeaderProps<T>) {
   const { start, sort, search, filters, onApplyFilters, button, isCarousel } = props
   const [isSearchInputOpen, setIsSearchInputOpen] = useState(false)
   const smMatch = useMediaMatch('sm')
@@ -105,18 +107,8 @@ export const SectionHeader: FC<SectionHeaderProps> = (props) => {
               {filters && filtersInFirstRow && <SectionFilters filters={filters} onApplyFilters={onApplyFilters} />}
               {isCarousel && (
                 <>
-                  <StyledArrowButton
-                    size="medium"
-                    icon={<SvgActionChevronL />}
-                    variant="tertiary"
-                    onClick={props.onMoveCarouselLeft}
-                  />
-                  <StyledArrowButton
-                    size="medium"
-                    icon={<SvgActionChevronR />}
-                    variant="tertiary"
-                    onClick={props.onMoveCarouselRight}
-                  />
+                  <ArrowButton disabled={props.isBeginning} direction="left" onClick={props.onMoveCarouselLeft} />
+                  <ArrowButton disabled={props.isEnd} direction="right" onClick={props.onMoveCarouselRight} />
                 </>
               )}
               {button && <StyledButton {...button} size="medium" variant="secondary" />}
@@ -148,21 +140,28 @@ export const SectionHeader: FC<SectionHeaderProps> = (props) => {
       {sort?.type === 'select' && <StyledSelect {...sort.selectProps} size="medium" />}
       {isCarousel && (
         <>
-          <StyledArrowButton
-            size="medium"
-            icon={<SvgActionChevronL />}
-            variant="tertiary"
-            onClick={props.onMoveCarouselLeft}
-          />
-          <StyledArrowButton
-            size="medium"
-            icon={<SvgActionChevronR />}
-            variant="tertiary"
-            onClick={props.onMoveCarouselRight}
-          />
+          <ArrowButton disabled={props.isBeginning} direction="left" onClick={props.onMoveCarouselLeft} />
+          <ArrowButton disabled={props.isEnd} direction="right" onClick={props.onMoveCarouselRight} />
         </>
       )}
       {button && <StyledButton {...button} size="medium" variant="secondary" />}
     </SectionHeaderWrapper>
   )
 }
+
+type ArrowButtonProps = {
+  disabled?: boolean
+  onClick?: () => void
+  direction: 'left' | 'right'
+}
+const ArrowButton: FC<ArrowButtonProps> = ({ disabled, onClick, direction }) => {
+  return (
+    <StyledArrowButton
+      disabled={disabled}
+      size="medium"
+      icon={direction === 'left' ? <SvgActionChevronL /> : <SvgActionChevronR />}
+      variant="tertiary"
+      onClick={onClick}
+    />
+  )
+}

+ 1 - 1
packages/atlas/src/components/Section/SectionHeader/SectionTitle/SectionTitle.tsx

@@ -31,7 +31,7 @@ export const SectionTitleComponent: FC<SectionTitleComponentProps> = ({ nodeStar
 
   const renderNodeStart = () => {
     if (nodeStart?.type === 'avatar') {
-      return <Avatar {...nodeStart.avatarProps} size={smMatch ? 'default' : 'bid'} />
+      return <Avatar {...nodeStart.avatarProps} size={smMatch ? 32 : 24} />
     }
     if (nodeStart?.type === 'icon') {
       return <IconWrapper {...nodeStart?.iconWrapperProps} size="medium" />

+ 6 - 2
packages/atlas/src/components/Table/Table.stories.tsx

@@ -12,8 +12,8 @@ const MOCKED_DATA: TableProps['data'] = [
   {
     column1: new Date().getDate(),
     column2: 'NFT Sale',
-    column3: 10000,
-    column4: 132334,
+    column3: 22222,
+    column4: 999999,
   },
 ]
 
@@ -52,3 +52,7 @@ export default {
 const Template: Story<TableProps> = (args) => <Table {...args} />
 
 export const Default = Template.bind({})
+export const Double = Template.bind({})
+Double.args = {
+  doubleColumn: true,
+}

+ 13 - 1
packages/atlas/src/components/Table/Table.styles.ts

@@ -22,6 +22,8 @@ export const Thead = styled.thead`
 
 const cellStyles = css`
   padding: ${sizes(3.5)} ${sizes(2)};
+  display: flex;
+  align-items: center;
 
   :first-of-type {
     padding-left: ${sizes(6)};
@@ -35,7 +37,7 @@ const cellStyles = css`
 export const Th = styled(Text)`
   ${cellStyles};
 
-  text-align: left;
+  white-space: nowrap;
 `
 
 export const Td = styled(Text)`
@@ -66,3 +68,13 @@ export const EmptyTableDescription = styled(Text)`
   text-align: center;
   margin-top: ${sizes(2)};
 `
+
+export const PageWrapper = styled.div`
+  display: flex;
+  gap: ${sizes(6)};
+`
+
+export const RightAlignedHeader = styled.div`
+  width: 100%;
+  text-align: right;
+`

+ 69 - 38
packages/atlas/src/components/Table/Table.tsx

@@ -1,5 +1,5 @@
-import { ReactElement } from 'react'
-import { Column, usePagination, useTable } from 'react-table'
+import { ReactElement, useMemo } from 'react'
+import { Column, useFlexLayout, usePagination, useTable } from 'react-table'
 
 import { Text } from '@/components/Text'
 import { useMediaMatch } from '@/hooks/useMediaMatch'
@@ -8,6 +8,7 @@ import {
   EmptyTableContainer,
   EmptyTableDescription,
   EmptyTableHeader,
+  PageWrapper,
   StyledPagination,
   TableBase,
   Td,
@@ -21,26 +22,46 @@ export type TableProps<T = object> = {
   data: T[]
   title?: string
   pageSize?: number
+  doubleColumn?: boolean
   emptyState?: {
     title: string
     description: string
     icon: ReactElement
   }
+  className?: string
 }
 
-export const Table = <T extends object>({ columns, data, title, pageSize = 20, emptyState }: TableProps<T>) => {
+export const Table = <T extends object>({
+  columns,
+  data,
+  title,
+  pageSize = 20,
+  emptyState,
+  doubleColumn,
+  className,
+}: TableProps<T>) => {
   const {
     getTableProps,
     getTableBodyProps,
     headerGroups,
-    page,
+    page: rawPage,
     prepareRow,
     gotoPage,
     state: { pageIndex },
-  } = useTable({ columns, data, initialState: { pageSize } }, usePagination)
+  } = useTable({ columns, data, initialState: { pageSize } }, usePagination, useFlexLayout)
+
+  const page = useMemo(() => {
+    if (doubleColumn) {
+      const sliceIndex = Math.ceil(rawPage.length / 2)
+      return [rawPage.slice(0, sliceIndex), rawPage.slice(sliceIndex)]
+    }
+
+    return [rawPage]
+  }, [doubleColumn, rawPage])
+
   const mdMatch = useMediaMatch('md')
   return (
-    <Wrapper>
+    <Wrapper className={className}>
       {title && (
         <Text
           as="h3"
@@ -51,39 +72,49 @@ export const Table = <T extends object>({ columns, data, title, pageSize = 20, e
         </Text>
       )}
       {data.length ? (
-        <TableBase {...getTableProps()}>
-          <Thead>
-            {headerGroups.map((headerGroup) => (
-              <tr {...headerGroup.getHeaderGroupProps()} key={headerGroup.getHeaderGroupProps().key}>
-                {headerGroup.headers.map((column) => (
-                  <Th
-                    variant="h100"
-                    as="th"
-                    color="colorText"
-                    {...column.getHeaderProps()}
-                    key={column.getHeaderProps().key}
-                  >
-                    {column.render('Header')}
-                  </Th>
+        <PageWrapper>
+          {page.map((subpage, idx) => (
+            <TableBase className="table-base" {...getTableProps()} key={`table-slice-${idx}`}>
+              <Thead className="table-header">
+                {headerGroups.map((headerGroup) => (
+                  <tr {...headerGroup.getHeaderGroupProps()} key={headerGroup.getHeaderGroupProps().key}>
+                    {headerGroup.headers.map((column) => (
+                      <Th
+                        variant="h100"
+                        as="th"
+                        color="colorText"
+                        {...column.getHeaderProps({ style: { width: column.width } })}
+                        key={column.getHeaderProps().key}
+                      >
+                        {column.render('Header')}
+                      </Th>
+                    ))}
+                  </tr>
                 ))}
-              </tr>
-            ))}
-          </Thead>
-          <tbody {...getTableBodyProps()}>
-            {page.map((row) => {
-              prepareRow(row)
-              return (
-                <tr {...row.getRowProps()} key={row.getRowProps().key}>
-                  {row.cells.map((cell) => (
-                    <Td variant="t100" as="td" {...cell.getCellProps()} key={cell.getCellProps().key}>
-                      {cell.render('Cell')}
-                    </Td>
-                  ))}
-                </tr>
-              )
-            })}
-          </tbody>
-        </TableBase>
+              </Thead>
+              <tbody {...getTableBodyProps()}>
+                {subpage.map((row) => {
+                  prepareRow(row)
+                  return (
+                    <tr className="table-row" {...row.getRowProps()} key={row.getRowProps().key}>
+                      {row.cells.map((cell) => (
+                        <Td
+                          variant="t100"
+                          as="td"
+                          {...cell.getCellProps()}
+                          key={cell.getCellProps().key}
+                          className="table-cell"
+                        >
+                          {cell.render('Cell')}
+                        </Td>
+                      ))}
+                    </tr>
+                  )
+                })}
+              </tbody>
+            </TableBase>
+          ))}
+        </PageWrapper>
       ) : emptyState ? (
         <EmptyTableContainer>
           {emptyState.icon}

+ 1 - 1
packages/atlas/src/components/TablePaymentsHistory/TablePaymentsHistory.tsx

@@ -135,7 +135,7 @@ const Sender = ({ sender }: { sender: PaymentHistory['sender'] }) => {
     return (
       <StyledLink to={absoluteRoutes.viewer.member(member.handle)}>
         <SenderItem
-          nodeStart={<Avatar assetUrl={avatarUrl} loading={avatarLoading} />}
+          nodeStart={<Avatar assetUrl={avatarUrl} size={32} loading={avatarLoading} />}
           label={member?.handle}
           isInteractive={false}
         />

+ 30 - 0
packages/atlas/src/components/TopSellingChannelsTable/TopSellingChannelsTable.stories.tsx

@@ -0,0 +1,30 @@
+import { ApolloProvider } from '@apollo/client'
+import { Meta } from '@storybook/react'
+import { MemoryRouter } from 'react-router-dom'
+
+import { createApolloClient } from '@/api'
+import { TopSellingChannelsTable } from '@/components/TopSellingChannelsTable/TopSellingChannelsTable'
+import { OverlayManagerProvider } from '@/providers/overlayManager'
+
+export default {
+  title: 'other/TopSellingChannelsTable',
+  component: TopSellingChannelsTable,
+  decorators: [
+    (Story) => {
+      const apolloClient = createApolloClient()
+      return (
+        <ApolloProvider client={apolloClient}>
+          <OverlayManagerProvider>
+            <MemoryRouter>
+              <Story />
+            </MemoryRouter>
+          </OverlayManagerProvider>
+        </ApolloProvider>
+      )
+    },
+  ],
+} as Meta
+
+const Template = () => <TopSellingChannelsTable />
+
+export const Default = Template.bind({})

+ 100 - 0
packages/atlas/src/components/TopSellingChannelsTable/TopSellingChannelsTable.styles.ts

@@ -0,0 +1,100 @@
+import styled from '@emotion/styled'
+import { Link } from 'react-router-dom'
+
+import { ListItem } from '@/components/ListItem'
+import { Table } from '@/components/Table'
+import { Text } from '@/components/Text'
+import { cVar, sizes } from '@/styles'
+
+import { LabelContainer, LabelText } from '../ListItem/ListItem.styles'
+
+export const ScrollWrapper = styled.div`
+  scrollbar-width: none;
+  position: relative;
+  overflow: auto;
+
+  ::-webkit-scrollbar {
+    display: none;
+  }
+`
+
+export const StyledTable = styled(Table)`
+  background: transparent;
+  min-width: 528px;
+
+  .table-base {
+    height: fit-content;
+  }
+
+  .table-row {
+    background-color: transparent;
+
+    td {
+      :first-of-type {
+        max-width: 40px;
+      }
+    }
+  }
+
+  .table-header {
+    box-shadow: 0 1px 0 0 ${cVar('colorBorderMutedAlpha')};
+    background-color: transparent;
+
+    th {
+      :first-of-type {
+        max-width: 40px;
+      }
+    }
+  }
+`
+
+export const StyledListItem = styled(ListItem)`
+  padding-left: 0;
+  width: fit-content;
+  align-items: center;
+
+  ${LabelContainer} {
+    overflow: hidden;
+  }
+
+  ${LabelText} {
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    color: ${cVar('colorTextStrong')};
+  }
+`
+
+export const SenderItemIconsWrapper = styled.div`
+  display: flex;
+  align-items: center;
+  gap: ${sizes(1)};
+
+  svg {
+    path {
+      fill: ${cVar('colorTextMuted')};
+    }
+  }
+`
+
+export const StyledLink = styled(Link)`
+  text-decoration: none;
+`
+
+export const JoyAmountWrapper = styled.div`
+  display: inline-flex;
+  align-items: center;
+  justify-content: center;
+  margin-left: auto;
+`
+
+export const SkeletonChannelContainer = styled.div`
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  width: 100%;
+`
+
+export const NftSoldText = styled(Text)`
+  margin-left: auto;
+`

+ 222 - 0
packages/atlas/src/components/TopSellingChannelsTable/TopSellingChannelsTable.tsx

@@ -0,0 +1,222 @@
+import BN from 'bn.js'
+import { useMemo, useRef, useState } from 'react'
+import useDraggableScroll from 'use-draggable-scroll'
+
+import {
+  GetTopSellingChannelsFromThreePeriodsQuery,
+  useGetTopSellingChannelsFromThreePeriodsQuery,
+} from '@/api/queries/__generated__/channels.generated'
+import { BasicChannelFieldsFragment } from '@/api/queries/__generated__/fragments.generated'
+import { SvgActionCreatorToken, SvgActionVerified } from '@/assets/icons'
+import { SvgEmptyStateIllustration } from '@/assets/illustrations'
+import { JoyTokenIcon } from '@/components/JoyTokenIcon'
+import { NumberFormat } from '@/components/NumberFormat'
+import { Section } from '@/components/Section/Section'
+import { TableProps } from '@/components/Table'
+import { RightAlignedHeader } from '@/components/Table/Table.styles'
+import { Text } from '@/components/Text'
+import { SkeletonLoader } from '@/components/_loaders/SkeletonLoader'
+import { absoluteRoutes } from '@/config/routes'
+import { useMediaMatch } from '@/hooks/useMediaMatch'
+
+import {
+  JoyAmountWrapper,
+  NftSoldText,
+  ScrollWrapper,
+  SenderItemIconsWrapper,
+  SkeletonChannelContainer,
+  StyledLink,
+  StyledListItem,
+  StyledTable,
+} from './TopSellingChannelsTable.styles'
+
+import { Avatar } from '../Avatar'
+
+const COLUMNS: TableProps['columns'] = [
+  {
+    Header: '',
+    accessor: 'index',
+    width: 1,
+  },
+  {
+    Header: 'CHANNEL',
+    accessor: 'channel',
+    width: 9,
+  },
+  {
+    Header: () => <RightAlignedHeader>NFTS SOLD</RightAlignedHeader>,
+    accessor: 'nftsSold',
+    width: 4,
+  },
+  {
+    Header: () => <RightAlignedHeader>SALES VOLUME</RightAlignedHeader>,
+    accessor: 'salesVolume',
+    width: 4,
+  },
+]
+
+const tableEmptyState = {
+  title: 'No top selling channels found',
+  description:
+    'There are no top selling channels to display for the selected period. You can try selecting a different period to see if there were any sales during that time.',
+  icon: <SvgEmptyStateIllustration />,
+}
+
+type TopSellingChannelsQueryPropKey = Exclude<keyof GetTopSellingChannelsFromThreePeriodsQuery, '__typename'>
+
+const MIN_TOP_SELLING_ITEMS = 4
+
+export const TopSellingChannelsTable = () => {
+  const [sort, setSort] = useState<TopSellingChannelsQueryPropKey>('topAllTimeSellingChannels')
+
+  const { data, loading } = useGetTopSellingChannelsFromThreePeriodsQuery({
+    variables: {
+      limit: 10,
+    },
+  })
+
+  const ref = useRef<HTMLDivElement>(null)
+  const { onMouseDown } = useDraggableScroll(ref, { direction: 'horizontal' })
+
+  const lgMatch = useMediaMatch('lg')
+  const mappedData: TableProps['data'] = useMemo(() => {
+    return loading
+      ? Array.from({ length: 10 }, () => ({
+          index: null,
+          channel: (
+            <SkeletonChannelContainer>
+              <SkeletonLoader width={32} height={32} rounded />
+              <SkeletonLoader width="30%" height={20} />
+            </SkeletonChannelContainer>
+          ),
+          nftsSold: <SkeletonLoader width="50%" height={16} />,
+          salesVolume: <SkeletonLoader width="100%" height={16} />,
+        }))
+      : data?.[sort].map((data, index) => ({
+          index: (
+            <Text variant="t100" as="p" color="colorTextMuted">
+              {index + 1}
+            </Text>
+          ),
+          salesVolume: (
+            <JoyAmountWrapper>
+              <JoyTokenIcon variant="gray" />
+              <NumberFormat
+                variant="t200-strong"
+                as="p"
+                value={new BN(data.amount)}
+                margin={{ left: 1 }}
+                format="short"
+              />
+            </JoyAmountWrapper>
+          ),
+          nftsSold: (
+            <NftSoldText variant="t100" as="p">
+              {data.nftSold}
+            </NftSoldText>
+          ),
+          channel: <Channel channel={data.channel} />,
+        })) ?? []
+  }, [data, loading, sort])
+
+  const sortingOptions = useMemo(
+    () => [
+      ...((data?.topWeekSellingChannels.length || 0) >= MIN_TOP_SELLING_ITEMS
+        ? [
+            {
+              label: 'Last week',
+              value: 'topWeekSellingChannels' as const,
+            },
+          ]
+        : []),
+      ...((data?.topMonthSellingChannels.length || 0) >= MIN_TOP_SELLING_ITEMS
+        ? [
+            {
+              label: 'Last month',
+              value: 'topMonthSellingChannels' as const,
+            },
+          ]
+        : []),
+      ...((data?.topAllTimeSellingChannels.length || 0) >= MIN_TOP_SELLING_ITEMS
+        ? [
+            {
+              label: 'All time',
+              value: 'topAllTimeSellingChannels' as const,
+            },
+          ]
+        : []),
+    ],
+    [data]
+  )
+
+  if (!data?.topAllTimeSellingChannels.length && sort === 'topAllTimeSellingChannels' && !loading) {
+    return null
+  }
+
+  return (
+    <Section
+      headerProps={{
+        start: {
+          type: 'title',
+          title: 'Top selling channels',
+        },
+        ...(sortingOptions.length > 1
+          ? {
+              sort: {
+                type: 'toggle-button',
+                toggleButtonOptionTypeProps: {
+                  type: 'options',
+                  value: sort,
+                  onChange: setSort,
+                  options: sortingOptions,
+                },
+              },
+            }
+          : {}),
+      }}
+      contentProps={{
+        type: 'grid',
+        grid: {
+          sm: {
+            columns: 1,
+          },
+        },
+        children: [
+          <ScrollWrapper key="single" ref={ref} onMouseDown={onMouseDown}>
+            <StyledTable emptyState={tableEmptyState} columns={COLUMNS} data={mappedData} doubleColumn={lgMatch} />
+          </ScrollWrapper>,
+        ],
+      }}
+    />
+  )
+}
+
+const Channel = ({ channel }: { channel: BasicChannelFieldsFragment }) => {
+  // todo to be implemented
+  const creatorToken = false
+  // todo to be implemented
+  const verified = false
+  return (
+    <StyledLink to={absoluteRoutes.viewer.channel(channel.id)} title={channel.title || ''}>
+      <StyledListItem
+        nodeStart={<Avatar assetUrl={channel.avatarPhoto?.resolvedUrl ?? undefined} />}
+        label={channel.title}
+        isInteractive={false}
+        nodeEnd={
+          <SenderItemIconsWrapper>
+            {creatorToken && (
+              <span title="Creator token">
+                <SvgActionCreatorToken />
+              </span>
+            )}
+            {verified && (
+              <span title="Verified">
+                <SvgActionVerified />
+              </span>
+            )}
+          </SenderItemIconsWrapper>
+        }
+      />
+    </StyledLink>
+  )
+}

+ 1 - 0
packages/atlas/src/components/TopSellingChannelsTable/index.ts

@@ -0,0 +1 @@
+export * from './TopSellingChannelsTable'

+ 1 - 1
packages/atlas/src/components/_auth/SignInModal/SignInSteps/SignInModalMembershipStep.tsx

@@ -140,7 +140,7 @@ export const SignInModalMembershipStep: FC<SignInModalMembershipStepProps> = ({
             render={({ field: { value: imageInputFile, onChange } }) => (
               <>
                 <StyledAvatar
-                  size="cover"
+                  size={88}
                   onClick={() =>
                     avatarDialogRef.current?.open(
                       imageInputFile?.originalBlob ? imageInputFile.originalBlob : imageInputFile?.blob,

+ 3 - 3
packages/atlas/src/components/_buttons/Button/Button.styles.ts

@@ -67,7 +67,7 @@ const buttonVariantStyles = ({ variant, iconOnly }: ButtonBaseStyleProps): Seria
 
         &:disabled,
         &[aria-disabled='true'] {
-          background-color: ${cVar('colorBackground')};
+          background-color: ${cVar('colorBackgroundAlpha')};
           box-shadow: none;
         }
       `
@@ -126,7 +126,7 @@ const buttonVariantStyles = ({ variant, iconOnly }: ButtonBaseStyleProps): Seria
 
         &:disabled,
         &[aria-disabled='true'] {
-          background-color: ${cVar('colorBackgroundElevated')};
+          background-color: ${cVar('colorBackgroundAlpha')};
         }
       `
     case 'destructive-secondary':
@@ -180,7 +180,7 @@ const buttonVariantStyles = ({ variant, iconOnly }: ButtonBaseStyleProps): Seria
 
         &:disabled,
         &[aria-disabled='true'] {
-          background-color: ${cVar('colorBackgroundElevated')};
+          background-color: ${cVar('colorBackgroundAlpha')};
           box-shadow: none;
         }
       `

+ 1 - 29
packages/atlas/src/components/_buttons/CallToActionButton/CallToActionButton.tsx

@@ -1,15 +1,7 @@
 import { To } from 'history'
 import { FC, MouseEvent, ReactNode } from 'react'
 
-import {
-  SvgActionChevronR,
-  SvgActionNewTab,
-  SvgSidebarExplore,
-  SvgSidebarHome,
-  SvgSidebarPopular,
-} from '@/assets/icons'
-import { atlasConfig } from '@/config'
-import { absoluteRoutes } from '@/config/routes'
+import { SvgActionChevronR, SvgActionNewTab } from '@/assets/icons'
 import { useMediaMatch } from '@/hooks/useMediaMatch'
 import { getLinkPropsFromTo } from '@/utils/button'
 
@@ -49,23 +41,3 @@ export const CallToActionButton: FC<CallToActionButtonProps> = ({
     </StyledContainer>
   )
 }
-export const CTA_MAP: Record<string, CallToActionButtonProps> = {
-  home: {
-    label: 'Home',
-    to: absoluteRoutes.viewer.index(),
-    colorVariant: 'yellow',
-    icon: <SvgSidebarHome />,
-  },
-  popular: {
-    label: `Popular on ${atlasConfig.general.appName}`,
-    to: absoluteRoutes.viewer.popular(),
-    colorVariant: 'red',
-    icon: <SvgSidebarPopular />,
-  },
-  discover: {
-    label: 'Discover videos',
-    to: absoluteRoutes.viewer.discover(),
-    colorVariant: 'yellow',
-    icon: <SvgSidebarExplore />,
-  },
-}

+ 2 - 1
packages/atlas/src/components/_channel/ChannelCard/ChannelCard.styles.ts

@@ -5,7 +5,7 @@ import { Link } from 'react-router-dom'
 import { Avatar } from '@/components/Avatar'
 import { Text } from '@/components/Text'
 import { Button } from '@/components/_buttons/Button'
-import { breakpoints, cVar, sizes } from '@/styles'
+import { breakpoints, cVar, sizes, square } from '@/styles'
 
 export const ChannelCardArticle = styled.article<{ activeDisabled?: boolean }>`
   position: relative;
@@ -46,6 +46,7 @@ export const ChannelCardAnchor = styled(Link)`
 
 export const StyledAvatar = styled(Avatar)`
   margin-bottom: ${sizes(4)};
+  ${square('104px')}
 `
 
 export const InfoWrapper = styled.div`

+ 1 - 1
packages/atlas/src/components/_channel/ChannelCard/ChannelCard.tsx

@@ -46,7 +46,7 @@ export const ChannelCard: FC<ChannelCardProps> = ({
   return (
     <ChannelCardArticle className={className} activeDisabled={activeDisabled}>
       <ChannelCardAnchor onClick={onClick} to={channel?.id ? absoluteRoutes.viewer.channel(channel.id) : ''}>
-        <StyledAvatar size="channel-card" loading={loading} assetUrl={channel?.avatarPhoto?.resolvedUrl} />
+        <StyledAvatar loading={loading} assetUrl={channel?.avatarPhoto?.resolvedUrl} />
         <SwitchTransition>
           <CSSTransition
             key={loading ? 'placeholder' : 'content'}

+ 1 - 1
packages/atlas/src/components/_channel/ChannelLink/ChannelLink.styles.ts

@@ -18,7 +18,7 @@ type AvatarProps = {
 }
 
 export const StyledAvatar = styled(Avatar)<AvatarProps>`
-  margin-right: ${({ withHandle, size }) => (withHandle ? (size === 'small' ? sizes(4) : sizes(3)) : 0)};
+  margin-right: ${({ withHandle, size }) => (withHandle ? (size === 40 ? sizes(4) : sizes(3)) : 0)};
 `
 
 export const TitleWrapper = styled.div<{ followButton?: boolean }>`

+ 1 - 1
packages/atlas/src/components/_channel/ChannelLink/ChannelLink.tsx

@@ -36,7 +36,7 @@ export const ChannelLink: FC<ChannelLinkProps> = ({
   hideAvatar,
   noLink,
   overrideChannel,
-  avatarSize = 'default',
+  avatarSize = 32,
   onNotFound,
   className,
   textVariant,

+ 7 - 1
packages/atlas/src/components/_channel/ChannelWithVideos/ChannelWithVideos.tsx

@@ -9,6 +9,7 @@ import { SkeletonLoader } from '@/components/_loaders/SkeletonLoader'
 import { VideoTileViewer } from '@/components/_video/VideoTileViewer'
 import { absoluteRoutes } from '@/config/routes'
 import { useHandleFollowChannel } from '@/hooks/useHandleFollowChannel'
+import { useMediaMatch } from '@/hooks/useMediaMatch'
 import { useVideoGridRows } from '@/hooks/useVideoGridRows'
 import { createPlaceholderData } from '@/utils/data'
 import { SentryLogger } from '@/utils/logs'
@@ -32,6 +33,7 @@ export const ChannelWithVideos: FC<ChannelWithVideosProps> = memo(({ channelId }
     skip: !channelId,
     onError: (error) => SentryLogger.error('Failed to fetch channel', 'ChannelWithVideos', error),
   })
+  const mdMatch = useMediaMatch('md')
 
   const {
     videos,
@@ -72,7 +74,11 @@ export const ChannelWithVideos: FC<ChannelWithVideosProps> = memo(({ channelId }
   return (
     <>
       <ChannelCardAnchor to={channelId ? absoluteRoutes.viewer.channel(channelId) : ''}>
-        <StyledAvatar size="channel" loading={isLoading} assetUrl={extendedChannel?.channel.avatarPhoto?.resolvedUrl} />
+        <StyledAvatar
+          size={mdMatch ? 136 : 88}
+          loading={isLoading}
+          assetUrl={extendedChannel?.channel.avatarPhoto?.resolvedUrl}
+        />
         <InfoWrapper>
           {isLoading ? (
             <SkeletonLoader width="120px" height="20px" bottomSpace="4px" />

+ 44 - 17
packages/atlas/src/components/_channel/ChannelsSection/ChannelsSection.tsx

@@ -5,25 +5,39 @@ import { ChannelOrderByInput } from '@/api/queries/__generated__/baseTypes.gener
 import { Section } from '@/components/Section/Section'
 import { ChannelCard } from '@/components/_channel/ChannelCard'
 
+const sortingOptions = [
+  {
+    label: 'Newest',
+    value: ChannelOrderByInput.CreatedAtDesc,
+  },
+  {
+    label: 'Oldest',
+    value: ChannelOrderByInput.CreatedAtAsc,
+  },
+  {
+    label: 'Most followed',
+    value: ChannelOrderByInput.FollowsNumDesc,
+  },
+]
+
 export const ChannelsSection = () => {
-  const [sortBy, setSortBy] = useState<string>('Most followed')
+  const [sortBy, setSortBy] = useState(ChannelOrderByInput.FollowsNumDesc)
   const {
     edges: channels,
     pageInfo,
     loading,
     fetchMore,
-  } = useBasicChannelsConnection({
-    orderBy:
-      sortBy === 'Newest'
-        ? ChannelOrderByInput.CreatedAtDesc
-        : sortBy === 'Oldest'
-        ? ChannelOrderByInput.CreatedAtAsc
-        : ChannelOrderByInput.FollowsNumDesc,
-    first: 10,
-  })
-  const [isLoading, setIsLoading] = useState(false)
+  } = useBasicChannelsConnection(
+    {
+      orderBy: sortBy,
+      first: 10,
+    },
+    {
+      notifyOnNetworkStatusChange: true,
+    }
+  )
 
-  if (!channels || (!channels?.length && !(loading || isLoading))) {
+  if (!channels || (!channels?.length && !loading)) {
     return null
   }
 
@@ -38,7 +52,7 @@ export const ChannelsSection = () => {
           type: 'toggle-button',
           toggleButtonOptionTypeProps: {
             type: 'options',
-            options: ['Newest', 'Oldest', 'Most followed'],
+            options: sortingOptions,
             value: sortBy,
             onChange: setSortBy,
           },
@@ -46,7 +60,23 @@ export const ChannelsSection = () => {
       }}
       contentProps={{
         type: 'grid',
-        minChildrenWidth: 200,
+        grid: {
+          sm: {
+            columns: 2,
+          },
+          md: {
+            columns: 3,
+          },
+          lg: {
+            columns: 4,
+          },
+          xl: {
+            columns: 5,
+          },
+          xxl: {
+            columns: 6,
+          },
+        },
         children: channels.map(({ node }) => <ChannelCard key={node.id} channel={node} />),
       }}
       footerProps={{
@@ -54,13 +84,10 @@ export const ChannelsSection = () => {
         label: 'Load more channels',
         reachedEnd: !pageInfo?.hasNextPage ?? true,
         fetchMore: async () => {
-          setIsLoading(true)
           await fetchMore({
             variables: {
               after: pageInfo?.endCursor,
             },
-          }).finally(() => {
-            setIsLoading(false)
           })
         },
       }}

+ 1 - 1
packages/atlas/src/components/_channel/CollectorsBox/CollectorsBox.tsx

@@ -67,7 +67,7 @@ export const CollectorsBox: FC<CollectorsBoxProps> = ({ collectors, maxShowedCol
         {mappedCollectors.map((collector, idx) => (
           <StyledLink key={idx} to={collector.memberUrl || ''}>
             <ListItem
-              nodeStart={<Avatar size="small" assetUrl={collector.url} />}
+              nodeStart={<Avatar size={40} assetUrl={collector.url} />}
               nodeEnd={
                 <Text as="span" variant="t100" color="colorText">
                   Owns {collector.nftsAmount}

+ 2 - 17
packages/atlas/src/components/_channel/ExpandableChannelsList/ExpandableChannelsList.tsx

@@ -1,7 +1,7 @@
 import { QueryHookOptions } from '@apollo/client'
 import { FC, Fragment, useState } from 'react'
 
-import { useBasicChannels, useDiscoverChannels, usePopularChannels } from '@/api/hooks/channel'
+import { useBasicChannels, useDiscoverChannels } from '@/api/hooks/channel'
 import { ChannelOrderByInput } from '@/api/queries/__generated__/baseTypes.generated'
 import { SvgActionChevronR } from '@/assets/icons'
 import { EmptyFallback } from '@/components/EmptyFallback'
@@ -22,7 +22,7 @@ import {
   Separator,
 } from './ExpandableChannelsList.styles'
 
-type ChannelsQueryType = 'discover' | 'popular' | 'regular'
+type ChannelsQueryType = 'discover' | 'regular'
 
 type ExpandableChannelsListProps = {
   queryType: ChannelsQueryType
@@ -153,19 +153,6 @@ const useChannelsListData = (queryType: ChannelsQueryType, selectedLanguage: str
     { ...commonOpts, skip: queryType !== 'discover' }
   )
 
-  const popular = usePopularChannels(
-    {
-      where: {
-        activeVideosCount_gt: activeVideosCountGt,
-        channel: {
-          ...publicChannelFilter,
-          videoViewsNum_gt: 0,
-        },
-      },
-    },
-    { ...commonOpts, skip: queryType !== 'popular' }
-  )
-
   // regular channels query needs explicit limit and sorting as it's not defined by Orion
   const regular = useBasicChannels(
     {
@@ -184,8 +171,6 @@ const useChannelsListData = (queryType: ChannelsQueryType, selectedLanguage: str
 
   if (queryType === 'discover') {
     return discover
-  } else if (queryType === 'popular') {
-    return popular
   } else {
     return regular
   }

+ 4 - 4
packages/atlas/src/components/_comments/CommentRow/CommentRow.tsx

@@ -37,14 +37,14 @@ export const CommentRow: FC<CommentRowProps> = ({
   const getAvatarSize = useCallback((): AvatarSize => {
     if (smMatch) {
       if (indented) {
-        return 'default'
+        return 32
       }
-      return 'small'
+      return 40
     } else {
       if (indented) {
-        return 'bid'
+        return 24
       }
-      return 'default'
+      return 32
     }
   }, [indented, smMatch])
 

+ 1 - 1
packages/atlas/src/components/_comments/CommentSnapshot/CommentSnaphsot.tsx

@@ -44,7 +44,7 @@ export const CommentSnapshot: FC<CommentSnapshotProps> = ({
     <ContentWrapper>
       <AvatarWrapper>
         <Link to={memberUrl}>
-          <Avatar assetUrl={memberAvatarUrl} size="small" loading={isMemberAvatarLoading} clickable />
+          <Avatar assetUrl={memberAvatarUrl} size={40} loading={isMemberAvatarLoading} clickable />
         </Link>
         {!last && <Line />}
       </AvatarWrapper>

+ 0 - 83
packages/atlas/src/components/_content/NewNftSales/NewNftSales.tsx

@@ -1,83 +0,0 @@
-import styled from '@emotion/styled'
-import { FC, useState } from 'react'
-
-import { useNftsConnection } from '@/api/hooks/nfts'
-import { OwnedNftOrderByInput } from '@/api/queries/__generated__/baseTypes.generated'
-import { SvgActionChevronR } from '@/assets/icons'
-import { EmptyFallback } from '@/components/EmptyFallback'
-import { Grid } from '@/components/Grid'
-import { Text } from '@/components/Text'
-import { Button } from '@/components/_buttons/Button'
-import { NftTileViewer } from '@/components/_nft/NftTileViewer'
-import { absoluteRoutes } from '@/config/routes'
-import { useVideoGridRows } from '@/hooks/useVideoGridRows'
-import { cVar, sizes } from '@/styles'
-import { createPlaceholderData } from '@/utils/data'
-
-export const NewNftSales: FC = () => {
-  const gridRows = useVideoGridRows('compact')
-  const [tilesPerRow, setTilesPerRow] = useState(4)
-  // fetch only NFTs currently on sale
-  const { nfts, loading } = useNftsConnection({
-    where: {
-      transactionalStatus: {
-        isTypeOf_in: ['TransactionalStatusAuction', 'TransactionalStatusBuyNow'],
-      },
-      video: {
-        isPublic_eq: true,
-        channel: {
-          isPublic_eq: true,
-        },
-        media: {
-          isAccepted_eq: true,
-        },
-        thumbnailPhoto: {
-          isAccepted_eq: true,
-        },
-      },
-    },
-    orderBy: OwnedNftOrderByInput.CreatedAtDesc,
-    first: 8,
-  })
-  const handleResizeGrid = (sizes: number[]) => setTilesPerRow(sizes.length)
-
-  const placeholderItems = createPlaceholderData(loading ? tilesPerRow - (nfts ? nfts.length : 0) : 0)
-
-  const nftsWithPlaceholders = [...(nfts || []), ...placeholderItems].slice(0, gridRows * tilesPerRow)
-
-  return (
-    <section>
-      <NftHeader>
-        <Text as="h2" variant="h500">
-          New NFTs on sale
-        </Text>
-        <Button
-          icon={<SvgActionChevronR />}
-          iconPlacement="right"
-          variant="secondary"
-          to={absoluteRoutes.viewer.nfts()}
-        >
-          Browse NFTs
-        </Button>
-      </NftHeader>
-      {nftsWithPlaceholders.length ? (
-        <Grid maxColumns={null} onResize={handleResizeGrid}>
-          {nftsWithPlaceholders.map((nft, idx) => (
-            <NftTileViewer key={`${idx}-${nft.id}`} nftId={nft.id} />
-          ))}
-        </Grid>
-      ) : (
-        <EmptyFallback title="No NFTs on sale" />
-      )}
-    </section>
-  )
-}
-
-const NftHeader = styled.div`
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  padding-bottom: ${sizes(4)};
-  border-bottom: 1px solid ${cVar('colorCoreNeutral700')};
-  margin-bottom: ${sizes(12)};
-`

+ 0 - 1
packages/atlas/src/components/_content/NewNftSales/index.ts

@@ -1 +0,0 @@
-export * from './NewNftSales'

+ 0 - 24
packages/atlas/src/components/_content/OfficialJoystreamUpdate/OfficialJoystreamUpdate.tsx

@@ -1,24 +0,0 @@
-import { useChannelPreviewVideos } from '@/api/hooks/video'
-import { VideoGallery } from '@/components/_video/VideoGallery'
-import { atlasConfig } from '@/config'
-import { SentryLogger } from '@/utils/logs'
-
-const channelId = atlasConfig.content.officialJoystreamChannelId
-
-export const OfficialJoystreamUpdate = () => {
-  const { videos, loading, error } = useChannelPreviewVideos(channelId, {
-    onError: (error) => SentryLogger.error('Failed to fetch videos', 'OfficialJoystreamUpdate', error),
-    context: { delay: 1500 },
-    skip: !channelId,
-  })
-
-  if (error || !channelId) {
-    return null
-  }
-
-  return (
-    <section>
-      <VideoGallery title="Official Joystream updates" videos={videos || []} loading={loading} />
-    </section>
-  )
-}

+ 0 - 1
packages/atlas/src/components/_content/OfficialJoystreamUpdate/index.ts

@@ -1 +0,0 @@
-export * from './OfficialJoystreamUpdate'

+ 69 - 8
packages/atlas/src/components/_content/TopTenVideos/TopTenVideos.tsx

@@ -1,20 +1,56 @@
 import { FC } from 'react'
 
-import { useTop10VideosThisMonth, useTop10VideosThisWeek } from '@/api/hooks/video'
-import { VideoGallery } from '@/components/_video/VideoGallery'
+import { useMostViewedVideosConnection } from '@/api/hooks/video'
+import { VideoOrderByInput } from '@/api/queries/__generated__/baseTypes.generated'
+import { CarouselProps } from '@/components/Carousel'
+import { RankingNumberTile } from '@/components/RankingNumberTile'
+import { Section } from '@/components/Section/Section'
+import { VideoTileViewer } from '@/components/_video/VideoTileViewer'
 import { publicChannelFilter, publicVideoFilter } from '@/config/contentFilter'
 import { useMediaMatch } from '@/hooks/useMediaMatch'
+import { breakpoints } from '@/styles'
+import { createPlaceholderData } from '@/utils/data'
 import { SentryLogger } from '@/utils/logs'
 
 type TopTenVideosProps = {
   period: 'week' | 'month'
 }
 
+const PLACEHOLDERS_COUNT = 10
+
+const responsive: CarouselProps['breakpoints'] = {
+  [parseInt(breakpoints.xs)]: {
+    slidesPerView: 1.2,
+    slidesPerGroup: 1,
+  },
+  [parseInt(breakpoints.sm)]: {
+    slidesPerView: 2,
+    slidesPerGroup: 2,
+  },
+  [parseInt(breakpoints.md)]: {
+    slidesPerView: 2,
+    slidesPerGroup: 2,
+  },
+  [parseInt(breakpoints.lg)]: {
+    slidesPerView: 3,
+    slidesPerGroup: 3,
+  },
+  [parseInt(breakpoints.xl)]: {
+    slidesPerView: 4,
+    slidesPerGroup: 4,
+  },
+  [parseInt(breakpoints.xxl)]: {
+    slidesPerView: 6,
+    slidesPerGroup: 6,
+  },
+}
+
 export const TopTenVideos: FC<TopTenVideosProps> = ({ period }) => {
-  const smMatch = useMediaMatch('sm')
-  const queryFn = period === 'week' ? useTop10VideosThisWeek : useTop10VideosThisMonth
-  const { videos, loading } = queryFn(
+  const { videos, loading } = useMostViewedVideosConnection(
     {
+      orderBy: VideoOrderByInput.ViewsNumDesc,
+      limit: 10,
+      periodDays: period === 'week' ? 7 : 30,
       where: {
         ...publicVideoFilter,
         channel: publicChannelFilter,
@@ -23,9 +59,34 @@ export const TopTenVideos: FC<TopTenVideosProps> = ({ period }) => {
     { onError: (error) => SentryLogger.error('Failed to fetch most viewed videos', 'TopTenVideos', error) }
   )
 
+  const placeholderItems = createPlaceholderData(loading || !videos?.length ? PLACEHOLDERS_COUNT : 0, {
+    id: undefined,
+    progress: undefined,
+  })
+  const smMatch = useMediaMatch('sm')
+
   return (
-    <section>
-      <VideoGallery title={`Top 10 this ${period}`} videos={videos} loading={loading} hasRanking={smMatch} />
-    </section>
+    <Section
+      headerProps={{
+        start: {
+          type: 'title',
+          title: `Top 10 this ${period}`,
+        },
+      }}
+      contentProps={{
+        type: 'carousel',
+        spaceBetween: smMatch ? 24 : 16,
+        breakpoints: responsive,
+        children: [...(videos ? videos : []), ...placeholderItems]?.map((video, idx) =>
+          smMatch ? (
+            <RankingNumberTile number={idx + 1} key={`${idx}-${video.id}`}>
+              <VideoTileViewer id={video.id} />
+            </RankingNumberTile>
+          ) : (
+            <VideoTileViewer id={video.id} key={`${idx}-${video.id}`} />
+          )
+        ),
+      }}
+    />
   )
 }

+ 1 - 1
packages/atlas/src/components/_inputs/Input/Input.stories.tsx

@@ -60,7 +60,7 @@ const TemplateWithPreffixAndSuffix: StoryFn<InputProps> = (args) => {
       <Input {...args} nodeStart={<Pill label="label" />} nodeEnd="$" />
       <Input
         {...args}
-        nodeStart={<Avatar size="bid" assetUrl="https://placedog.net/360/203" />}
+        nodeStart={<Avatar size={24} assetUrl="https://placedog.net/360/203" />}
         nodeEnd={<Pill label="500$" />}
       />
       <Input

+ 6 - 6
packages/atlas/src/components/_inputs/InputAutocomplete/InputAutocomplete.tsx

@@ -8,12 +8,8 @@ import { SvgActionCancel } from '@/assets/icons'
 import { ComboBox, ComboBoxProps } from '@/components/_inputs/ComboBox'
 import { SentryLogger } from '@/utils/logs'
 
-const notFoundNode = {
-  label: `We couldn't find any result. Please check if spelling is correct.`,
-  nodeStart: <SvgActionCancel />,
-}
-
 type InputAutocompleteProps<Q extends object, V extends OperationVariables, R = object> = {
+  notFoundLabel?: string
   value: string
   onChange: (value: string) => void
   clearSelection: () => void
@@ -27,6 +23,7 @@ type InputAutocompleteProps<Q extends object, V extends OperationVariables, R =
 } & Pick<ComboBoxProps, 'onBlur' | 'disabled' | 'error' | 'className'>
 
 export const InputAutocomplete = <Q extends object, V extends OperationVariables, R = object>({
+  notFoundLabel = "We couldn't find any result. Please check if spelling is correct.",
   onItemSelect,
   queryVariablesFactory,
   documentQuery,
@@ -86,7 +83,10 @@ export const InputAutocomplete = <Q extends object, V extends OperationVariables
       {...comboBoxProps}
       items={result ? renderItem(result) : []}
       placeholder={placeholder}
-      notFoundNode={notFoundNode}
+      notFoundNode={{
+        nodeStart: <SvgActionCancel />,
+        label: notFoundLabel,
+      }}
       processing={isLoading}
       nodeEnd={nodeEnd}
       value={value}

+ 1 - 1
packages/atlas/src/components/_inputs/MemberComboBox/MemberComboBox.tsx

@@ -138,7 +138,7 @@ type AvatarWithResolvedAssetProps = {
 
 const AvatarWithResolvedAsset: FC<AvatarWithResolvedAssetProps> = ({ member }) => {
   const { url, isLoadingAsset } = getMemberAvatar(member)
-  return <Avatar assetUrl={url} loading={isLoadingAsset} />
+  return <Avatar size={32} assetUrl={url} loading={isLoadingAsset} />
 }
 
 type StyledOutputPillWithResolvedAssetProps = {

+ 14 - 1
packages/atlas/src/components/_inputs/ToggleButtonGroup/ToggleButtonGroup.stories.tsx

@@ -10,7 +10,20 @@ export default {
   component: ToggleButtonGroup,
   args: {
     type: 'options',
-    options: ['small', 'large', 'medium', 'medium3', 'medium2'],
+    options: [
+      {
+        value: 'large',
+        label: 'Large',
+      },
+      {
+        label: 'Small',
+        value: 'small',
+      },
+      {
+        label: 'Medium',
+        value: 'medium',
+      },
+    ],
   },
 } as Meta<ToggleButtonGroupProps<SbOptions>>
 

+ 6 - 0
packages/atlas/src/components/_inputs/ToggleButtonGroup/ToggleButtonGroup.styles.ts

@@ -1,6 +1,7 @@
 import styled from '@emotion/styled'
 
 import { Text } from '@/components/Text'
+import { Tooltip } from '@/components/Tooltip'
 import { Button } from '@/components/_buttons/Button'
 import { cVar, sizes, zIndex } from '@/styles'
 import { MaskProps, getMaskImage } from '@/utils/styles'
@@ -49,6 +50,11 @@ export const ToggleButton = styled(Button)`
   }
 `
 
+export const StyledTooltip = styled(Tooltip)`
+  width: 100%;
+  display: block;
+`
+
 export const Label = styled(Text)`
   padding: ${sizes(2)};
   align-self: center;

+ 24 - 16
packages/atlas/src/components/_inputs/ToggleButtonGroup/ToggleButtonGroup.tsx

@@ -12,6 +12,7 @@ import {
   ContentWrapper,
   Label,
   OptionWrapper,
+  StyledTooltip,
   ToggleButton,
 } from './ToggleButtonGroup.styles'
 
@@ -21,9 +22,16 @@ type SharedToggleButtonProps = {
   className?: string
 }
 
-export type ToggleButtonOptionTypeProps<T extends string = string> = {
+type ToggleButtonOption<T = string> = {
+  label: string
+  value: T
+  disabled?: boolean
+  tooltipText?: string
+}
+
+export type ToggleButtonOptionTypeProps<T = string> = {
   type: 'options'
-  options: T[]
+  options: ToggleButtonOption<T>[]
   value?: T
   onChange: (value: T) => void
 } & SharedToggleButtonProps
@@ -34,11 +42,9 @@ export type ToggleButtonFilterTypeProps = {
   filters: FilterButtonProps[]
 } & SharedToggleButtonProps
 
-export type ToggleButtonGroupProps<T extends string = string> =
-  | ToggleButtonFilterTypeProps
-  | ToggleButtonOptionTypeProps<T>
+export type ToggleButtonGroupProps<T = string> = ToggleButtonFilterTypeProps | ToggleButtonOptionTypeProps<T>
 
-export const ToggleButtonGroup = <T extends string = string>(props: ToggleButtonGroupProps<T>) => {
+export function ToggleButtonGroup<T = string>(props: ToggleButtonGroupProps<T>) {
   const { type, label, width = 'auto', className } = props
   const optionWrapperRef = useRef<HTMLDivElement>(null)
 
@@ -62,16 +68,18 @@ export const ToggleButtonGroup = <T extends string = string>(props: ToggleButton
         )}
         <OptionWrapper onMouseDown={handleMouseDown} ref={optionWrapperRef} visibleShadows={visibleShadows}>
           {type === 'options' &&
-            props.options.map((option) => (
-              <ToggleButton
-                key={option}
-                fullWidth
-                variant={option !== props.value ? 'tertiary' : 'secondary'}
-                onClick={() => props.onChange(option)}
-                size="small"
-              >
-                {option}
-              </ToggleButton>
+            props.options.map((option, i) => (
+              <StyledTooltip key={i} text={option.tooltipText}>
+                <ToggleButton
+                  fullWidth
+                  variant={option.value !== props.value ? 'tertiary' : 'secondary'}
+                  onClick={() => props.onChange(option.value)}
+                  size="small"
+                  disabled={option.disabled}
+                >
+                  {option.label}
+                </ToggleButton>
+              </StyledTooltip>
             ))}
           {type === 'filter' &&
             props.filters.map((filterButtonProps, idx) => <FilterButton key={idx} {...filterButtonProps} />)}

+ 4 - 2
packages/atlas/src/components/_navigation/NotificationsButton/NotificationsButton.tsx

@@ -3,7 +3,9 @@ import { useNotifications } from '@/providers/notifications/notifications.hooks'
 
 import { StyledButton } from './NotificationsButton.styles'
 
-export const NotificationsButton = () => {
+export const NotificationsButton = (props: { onClick?: () => void }) => {
   const { unseenNotificationsCounts } = useNotifications()
-  return <StyledButton variant="secondary" icon={<SvgActionNotifications />} badge={unseenNotificationsCounts} />
+  return (
+    <StyledButton {...props} variant="secondary" icon={<SvgActionNotifications />} badge={unseenNotificationsCounts} />
+  )
 }

Vissa filer visades inte eftersom för många filer har ändrats