Browse Source

Improve top channels sorting (#4214)

* Improve top channels sorting

* fix conditions
Bartosz Dryl 1 year ago
parent
commit
31ab2cd9b4

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

@@ -10,7 +10,7 @@ VITE_ENV_SELECTION_ENABLED=true
 
 VITE_AVATAR_SERVICE_URL=https://atlas-services.joystream.org/avatars
 VITE_GEOLOCATION_SERVICE_URL=https://geolocation.joystream.org
-#VITE_HCAPTCHA_SITE_KEY=41cae189-7676-4f6b-aa56-635be26d3ceb
+VITE_HCAPTCHA_SITE_KEY=41cae189-7676-4f6b-aa56-635be26d3ceb
 VITE_GOOGLE_CONSOLE_CLIENT_ID=
 VITE_YOUTUBE_SYNC_API_URL=
 VITE_YOUTUBE_COLLABORATOR_MEMBER_ID=

+ 102 - 70
packages/atlas/src/api/queries/__generated__/channels.generated.tsx

@@ -623,67 +623,61 @@ export type GetChannelPaymentEventsQuery = {
   }>
 }
 
-export type GetTopSellingChannelsQueryVariables = Types.Exact<{
+export type GetTopSellingChannelsFromThreePeriodsQueryVariables = Types.Exact<{
   limit: Types.Scalars['Int']
-  periodDays: Types.Scalars['Int']
   where?: Types.InputMaybe<Types.ExtendedChannelWhereInput>
 }>
 
-export type GetTopSellingChannelsQuery = {
+export type GetTopSellingChannelsFromThreePeriodsQuery = {
   __typename?: 'Query'
-  topSellingChannels: Array<{
+  topAllTimeSellingChannels: Array<{
     __typename?: 'TopSellingChannelsResult'
     amount: string
     nftSold: number
     channel: {
       __typename?: 'Channel'
-      videoViewsNum: number
-      description?: string | null
-      isPublic?: boolean | null
-      cumulativeRewardClaimed?: string | null
-      isCensored: boolean
-      language?: string | null
       id: string
       title?: string | null
+      description?: string | null
       createdAt: Date
       followsNum: number
       rewardAccount: string
       channelStateBloatBond: string
-      ownerMember?: {
-        __typename?: 'Membership'
+      avatarPhoto?: {
+        __typename?: 'StorageDataObject'
         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
+        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
-      coverPhoto?: {
+    }
+  }>
+  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>
@@ -702,6 +696,21 @@ export type GetTopSellingChannelsQuery = {
           | { __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
@@ -1532,58 +1541,81 @@ export type GetChannelPaymentEventsQueryResult = Apollo.QueryResult<
   GetChannelPaymentEventsQuery,
   GetChannelPaymentEventsQueryVariables
 >
-export const GetTopSellingChannelsDocument = gql`
-  query GetTopSellingChannels($limit: Int!, $periodDays: Int!, $where: ExtendedChannelWhereInput) {
-    topSellingChannels(where: $where, limit: $limit, periodDays: $periodDays) {
+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 {
-        ...FullChannelFields
+        ...BasicChannelFields
+      }
+    }
+    topMonthSellingChannels: topSellingChannels(where: $where, limit: $limit, periodDays: 30) {
+      amount
+      nftSold
+      channel {
+        ...BasicChannelFields
       }
     }
   }
-  ${FullChannelFieldsFragmentDoc}
+  ${BasicChannelFieldsFragmentDoc}
 `
 
 /**
- * __useGetTopSellingChannelsQuery__
+ * __useGetTopSellingChannelsFromThreePeriodsQuery__
  *
- * To run a query within a React component, call `useGetTopSellingChannelsQuery` and pass it any options that fit your needs.
- * When your component renders, `useGetTopSellingChannelsQuery` returns an object from Apollo Client that contains loading, error, and data properties
+ * 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 } = useGetTopSellingChannelsQuery({
+ * const { data, loading, error } = useGetTopSellingChannelsFromThreePeriodsQuery({
  *   variables: {
  *      limit: // value for 'limit'
- *      periodDays: // value for 'periodDays'
  *      where: // value for 'where'
  *   },
  * });
  */
-export function useGetTopSellingChannelsQuery(
-  baseOptions: Apollo.QueryHookOptions<GetTopSellingChannelsQuery, GetTopSellingChannelsQueryVariables>
+export function useGetTopSellingChannelsFromThreePeriodsQuery(
+  baseOptions: Apollo.QueryHookOptions<
+    GetTopSellingChannelsFromThreePeriodsQuery,
+    GetTopSellingChannelsFromThreePeriodsQueryVariables
+  >
 ) {
   const options = { ...defaultOptions, ...baseOptions }
-  return Apollo.useQuery<GetTopSellingChannelsQuery, GetTopSellingChannelsQueryVariables>(
-    GetTopSellingChannelsDocument,
-    options
-  )
+  return Apollo.useQuery<
+    GetTopSellingChannelsFromThreePeriodsQuery,
+    GetTopSellingChannelsFromThreePeriodsQueryVariables
+  >(GetTopSellingChannelsFromThreePeriodsDocument, options)
 }
-export function useGetTopSellingChannelsLazyQuery(
-  baseOptions?: Apollo.LazyQueryHookOptions<GetTopSellingChannelsQuery, GetTopSellingChannelsQueryVariables>
+export function useGetTopSellingChannelsFromThreePeriodsLazyQuery(
+  baseOptions?: Apollo.LazyQueryHookOptions<
+    GetTopSellingChannelsFromThreePeriodsQuery,
+    GetTopSellingChannelsFromThreePeriodsQueryVariables
+  >
 ) {
   const options = { ...defaultOptions, ...baseOptions }
-  return Apollo.useLazyQuery<GetTopSellingChannelsQuery, GetTopSellingChannelsQueryVariables>(
-    GetTopSellingChannelsDocument,
-    options
-  )
+  return Apollo.useLazyQuery<
+    GetTopSellingChannelsFromThreePeriodsQuery,
+    GetTopSellingChannelsFromThreePeriodsQueryVariables
+  >(GetTopSellingChannelsFromThreePeriodsDocument, options)
 }
-export type GetTopSellingChannelsQueryHookResult = ReturnType<typeof useGetTopSellingChannelsQuery>
-export type GetTopSellingChannelsLazyQueryHookResult = ReturnType<typeof useGetTopSellingChannelsLazyQuery>
-export type GetTopSellingChannelsQueryResult = Apollo.QueryResult<
-  GetTopSellingChannelsQuery,
-  GetTopSellingChannelsQueryVariables
+export type GetTopSellingChannelsFromThreePeriodsQueryHookResult = ReturnType<
+  typeof useGetTopSellingChannelsFromThreePeriodsQuery
+>
+export type GetTopSellingChannelsFromThreePeriodsLazyQueryHookResult = ReturnType<
+  typeof useGetTopSellingChannelsFromThreePeriodsLazyQuery
+>
+export type GetTopSellingChannelsFromThreePeriodsQueryResult = Apollo.QueryResult<
+  GetTopSellingChannelsFromThreePeriodsQuery,
+  GetTopSellingChannelsFromThreePeriodsQueryVariables
 >

+ 19 - 3
packages/atlas/src/api/queries/channels.graphql

@@ -296,12 +296,28 @@ query GetChannelPaymentEvents($channelId: String) {
   }
 }
 
-query GetTopSellingChannels($limit: Int!, $periodDays: Int!, $where: ExtendedChannelWhereInput) {
-  topSellingChannels(where: $where, limit: $limit, periodDays: $periodDays) {
+query GetTopSellingChannelsFromThreePeriods($limit: Int!, $where: ExtendedChannelWhereInput) {
+  topAllTimeSellingChannels: topSellingChannels(where: $where, limit: $limit, periodDays: 0) {
     amount
     nftSold
     channel {
-      ...FullChannelFields
+      ...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
     }
   }
 }

+ 93 - 86
packages/atlas/src/components/TopSellingChannelsTable/TopSellingChannelsTable.tsx

@@ -3,9 +3,10 @@ import { useMemo, useRef, useState } from 'react'
 import useDraggableScroll from 'use-draggable-scroll'
 
 import {
-  GetTopSellingChannelsQuery,
-  useGetTopSellingChannelsQuery,
+  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'
@@ -61,19 +62,16 @@ const tableEmptyState = {
   icon: <SvgEmptyStateIllustration />,
 }
 
+type TopSellingChannelsQueryPropKey = Exclude<keyof GetTopSellingChannelsFromThreePeriodsQuery, '__typename'>
+
+const MIN_TOP_SELLING_ITEMS = 4
+
 export const TopSellingChannelsTable = () => {
-  const [sort, setSort] = useState(7)
-  const [emptyPeriods, setEmptyPeriods] = useState<number[]>([])
-  const { data, loading } = useGetTopSellingChannelsQuery({
+  const [sort, setSort] = useState<TopSellingChannelsQueryPropKey>('topAllTimeSellingChannels')
+
+  const { data, loading } = useGetTopSellingChannelsFromThreePeriodsQuery({
     variables: {
       limit: 10,
-      periodDays: sort,
-    },
-    onCompleted: (data) => {
-      if (sort !== 0 && !data.topSellingChannels.length) {
-        setEmptyPeriods((prev) => [...prev, sort])
-        setSort((prev) => (prev === 7 ? 30 : 0))
-      }
     },
   })
 
@@ -81,72 +79,77 @@ export const TopSellingChannelsTable = () => {
   const { onMouseDown } = useDraggableScroll(ref, { direction: 'horizontal' })
 
   const lgMatch = useMediaMatch('lg')
-  const mappedData: TableProps['data'] = useMemo(
-    () =>
-      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?.topSellingChannels.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?.topSellingChannels, loading]
-  )
+  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(
-    () =>
-      [
-        {
-          label: 'Last week',
-          value: 7,
-        },
-        {
-          label: 'Last month',
-          value: 30,
-        },
-        {
-          label: 'All time',
-          value: 0,
-        },
-      ].map((option) => ({
-        ...option,
-        disabled: emptyPeriods.includes(option.value),
-        tooltipText: emptyPeriods.includes(option.value) ? 'No channels available for this period' : undefined,
-      })),
-    [emptyPeriods]
+    () => [
+      ...((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?.topSellingChannels.length && sort === 0 && !loading) {
+  if (!data?.topAllTimeSellingChannels.length && sort === 'topAllTimeSellingChannels' && !loading) {
     return null
   }
 
@@ -157,15 +160,19 @@ export const TopSellingChannelsTable = () => {
           type: 'title',
           title: 'Top selling channels',
         },
-        sort: {
-          type: 'toggle-button',
-          toggleButtonOptionTypeProps: {
-            type: 'options',
-            value: sort,
-            onChange: setSort,
-            options: sortingOptions,
-          },
-        },
+        ...(sortingOptions.length > 1
+          ? {
+              sort: {
+                type: 'toggle-button',
+                toggleButtonOptionTypeProps: {
+                  type: 'options',
+                  value: sort,
+                  onChange: setSort,
+                  options: sortingOptions,
+                },
+              },
+            }
+          : {}),
       }}
       contentProps={{
         type: 'grid',
@@ -184,16 +191,16 @@ export const TopSellingChannelsTable = () => {
   )
 }
 
-const Channel = ({ channel }: { channel: GetTopSellingChannelsQuery['topSellingChannels'][number]['channel'] }) => {
+const Channel = ({ channel }: { channel: BasicChannelFieldsFragment }) => {
   // todo to be implemented
   const creatorToken = false
   // todo to be implemented
   const verified = false
   return (
-    <StyledLink to={absoluteRoutes.viewer.member(channel.ownerMember?.handle)} title={channel.ownerMember?.handle}>
+    <StyledLink to={absoluteRoutes.viewer.channel(channel.id)} title={channel.title || ''}>
       <StyledListItem
         nodeStart={<Avatar assetUrl={channel.avatarPhoto?.resolvedUrl ?? undefined} />}
-        label={channel.ownerMember?.handle}
+        label={channel.title}
         isInteractive={false}
         nodeEnd={
           <SenderItemIconsWrapper>