From 1d7cedfbb6392221f294e52d5e88f4a5bd5b3b12 Mon Sep 17 00:00:00 2001 From: ikprk Date: Mon, 20 May 2024 18:36:23 +0200 Subject: [PATCH 1/5] Add number of items to tabs of channel view --- .../views/viewer/ChannelView/ChannelView.tsx | 37 ++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/packages/atlas/src/views/viewer/ChannelView/ChannelView.tsx b/packages/atlas/src/views/viewer/ChannelView/ChannelView.tsx index 655f2a783a..73b10e6035 100644 --- a/packages/atlas/src/views/viewer/ChannelView/ChannelView.tsx +++ b/packages/atlas/src/views/viewer/ChannelView/ChannelView.tsx @@ -5,7 +5,9 @@ import { useNavigate } from 'react-router' import { useParams, useSearchParams } from 'react-router-dom' import { useChannelNftCollectors, useFullChannel } from '@/api/hooks/channel' +import { useVideoCount } from '@/api/hooks/video' import { OwnedNftOrderByInput, VideoOrderByInput } from '@/api/queries/__generated__/baseTypes.generated' +import { useGetNftsCountQuery } from '@/api/queries/__generated__/nfts.generated' import { SvgActionCheck, SvgActionFilters, SvgActionFlag, SvgActionMore, SvgActionPlus } from '@/assets/icons' import { ChannelTitle } from '@/components/ChannelTitle' import { EmptyFallback } from '@/components/EmptyFallback' @@ -62,6 +64,7 @@ import { ChannelAbout, ChannelNfts, ChannelVideos } from './ChannelViewTabs' import { TABS } from './utils' export const INITIAL_TILES_PER_ROW = 4 +const USER_TIMESTAMP = new Date() export const ChannelView: FC = () => { const [searchParams, setSearchParams] = useSearchParams() @@ -88,6 +91,35 @@ export const ChannelView: FC = () => { const filteredTabs = TABS.filter((tab) => tab === 'Token' ? !!tab && (isChannelOwner || !!channel?.creatorToken?.token.id) : !!tab ) + const { videoCount } = useVideoCount({ + where: { + channel: { + id_eq: id, + }, + isPublic_eq: true, + createdAt_lt: USER_TIMESTAMP, + isCensored_eq: false, + thumbnailPhoto: { + isAccepted_eq: true, + }, + media: { + isAccepted_eq: true, + }, + }, + }) + const { data: nftCountData } = useGetNftsCountQuery({ + variables: { + where: { + createdAt_lte: USER_TIMESTAMP, + video: { + channel: { + id_eq: id, + }, + isPublic_eq: !isChannelOwner || undefined, + }, + }, + }, + }) // At mount set the tab from the search params // This hook has to come before useRedirectMigratedContent so it doesn't messes it's navigate call @@ -186,7 +218,10 @@ export const ChannelView: FC = () => { const handleOnResizeGrid = (sizes: number[]) => setTilesPerRow(sizes.length) - const mappedTabs = filteredTabs.map((tab) => ({ name: tab, badgeNumber: 0 })) + const mappedTabs = filteredTabs.map((tab) => ({ + name: tab, + pillText: tab === 'Videos' ? videoCount : tab === 'NFTs' ? nftCountData?.ownedNftsConnection.totalCount : undefined, + })) const getChannelContent = (tab: (typeof TABS)[number]) => { switch (tab) { From cc1e49eb62d001ea4e06c028471a668efd99db01 Mon Sep 17 00:00:00 2001 From: ikprk Date: Mon, 20 May 2024 18:57:21 +0200 Subject: [PATCH 2/5] Extra info for market when revenue share is active --- .../components/_crt/SellTokenModal/SellTokenModal.tsx | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/atlas/src/components/_crt/SellTokenModal/SellTokenModal.tsx b/packages/atlas/src/components/_crt/SellTokenModal/SellTokenModal.tsx index 2327e4cde6..58d2a6bc54 100644 --- a/packages/atlas/src/components/_crt/SellTokenModal/SellTokenModal.tsx +++ b/packages/atlas/src/components/_crt/SellTokenModal/SellTokenModal.tsx @@ -3,8 +3,10 @@ import { useCallback, useMemo, useState } from 'react' import { useForm } from 'react-hook-form' import { useGetFullCreatorTokenQuery } from '@/api/queries/__generated__/creatorTokens.generated' +import { SvgAlertsWarning24 } from '@/assets/icons' import { NumberFormat, formatNumberShort } from '@/components/NumberFormat' import { Text } from '@/components/Text' +import { Tooltip } from '@/components/Tooltip' import { AmmModalFormTemplate } from '@/components/_crt/AmmModalTemplates' import { AmmModalSummaryTemplate } from '@/components/_crt/AmmModalTemplates/AmmModalSummaryTemplate' import { DialogModal } from '@/components/_overlays/DialogModal' @@ -263,6 +265,13 @@ export const SellTokenModal = ({ tokenId, onClose: _onClose, show }: SellTokenMo title={`Sell $${title}`} show={show} onExitClick={onClose} + additionalActionsNode={ + hasActiveRevenueShare ? ( + + + + ) : null + } secondaryButton={{ text: isFormStep ? 'Cancel' : 'Back', onClick: isFormStep ? onClose : () => setStep('form'), @@ -270,6 +279,8 @@ export const SellTokenModal = ({ tokenId, onClose: _onClose, show }: SellTokenMo primaryButton={{ text: isFormStep ? 'Continue' : `Sell $${title}`, onClick: isFormStep ? onFormSubmit : onTransactionSubmit, + disabled: hasActiveRevenueShare, + variant: hasActiveRevenueShare ? 'warning' : undefined, }} > {step === 'form' ? ( From 9080cd1cacef023f64d35d802c2782e30292630d Mon Sep 17 00:00:00 2001 From: ikprk Date: Mon, 20 May 2024 18:57:47 +0200 Subject: [PATCH 3/5] Restrict revenue share duration to 2 weeks --- .../_crt/StartRevenueShareModal/StartRevenueShareModal.tsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/atlas/src/components/_crt/StartRevenueShareModal/StartRevenueShareModal.tsx b/packages/atlas/src/components/_crt/StartRevenueShareModal/StartRevenueShareModal.tsx index 3c84e94929..ec4d41ebf2 100644 --- a/packages/atlas/src/components/_crt/StartRevenueShareModal/StartRevenueShareModal.tsx +++ b/packages/atlas/src/components/_crt/StartRevenueShareModal/StartRevenueShareModal.tsx @@ -385,6 +385,12 @@ export const StartRevenueShare = ({ token, onClose, show }: StartRevenueSharePro )}` } + const twoWeeksDate = addDaysToDate(14, new Date()) + + if (valueTimestamp.getTime() > twoWeeksDate.getTime()) { + return 'Maximal revenue share duration is 2 weeks.' + } + return true }, }} From eaf2ef10d90e4ecf48d2cf7deda1c90b359c5e5a Mon Sep 17 00:00:00 2001 From: ikprk Date: Mon, 20 May 2024 18:58:08 +0200 Subject: [PATCH 4/5] Add QS pagination for crt market --- packages/atlas/src/hooks/usePagination.ts | 27 +++++++++++++++++++ .../atlas/src/hooks/useTokensPagniation.ts | 14 ++++------ 2 files changed, 32 insertions(+), 9 deletions(-) create mode 100644 packages/atlas/src/hooks/usePagination.ts diff --git a/packages/atlas/src/hooks/usePagination.ts b/packages/atlas/src/hooks/usePagination.ts new file mode 100644 index 0000000000..c86c985a74 --- /dev/null +++ b/packages/atlas/src/hooks/usePagination.ts @@ -0,0 +1,27 @@ +import { useCallback, useEffect, useState } from 'react' +import { useSearchParams } from 'react-router-dom' + +export type UsePaginationOptions = { + initialPerPage?: number + initialCurrentPage?: number + currentTab?: number +} + +export const useQueryPagination = (opts?: UsePaginationOptions) => { + const [getSearchParams, setSearchParams] = useSearchParams() + const _perPage = +(getSearchParams.get('perPage') ?? opts?.initialPerPage ?? 10) + const _currentPage = +(getSearchParams.get('currentPage') ?? opts?.initialCurrentPage ?? 0) + const [perPage, setPerPage] = useState(_perPage) + const [currentPage, setCurrentPage] = useState(_currentPage) + + useEffect(() => { + setSearchParams({ currentPage: String(currentPage), perPage: String(perPage) }, { replace: true }) + }, [currentPage, perPage, setSearchParams]) + + const resetPagination = useCallback(() => { + setCurrentPage(opts?.initialCurrentPage ?? 0) + setPerPage(opts?.initialCurrentPage ?? 10) + }, [opts?.initialCurrentPage, setCurrentPage, setPerPage]) + + return { currentPage, setCurrentPage, perPage, setPerPage, resetPagination } +} diff --git a/packages/atlas/src/hooks/useTokensPagniation.ts b/packages/atlas/src/hooks/useTokensPagniation.ts index 8f03a7d88a..ef72a9a6ec 100644 --- a/packages/atlas/src/hooks/useTokensPagniation.ts +++ b/packages/atlas/src/hooks/useTokensPagniation.ts @@ -1,12 +1,11 @@ -import { useState } from 'react' - import { CreatorTokenOrderByInput, CreatorTokenWhereInput } from '@/api/queries/__generated__/baseTypes.generated' import { useGetBasicCreatorTokensQuery, useGetCreatorTokensCountQuery, } from '@/api/queries/__generated__/creatorTokens.generated' import { SentryLogger } from '@/utils/logs' -import { usePagination } from '@/views/viewer/ChannelView/ChannelView.hooks' + +import { useQueryPagination } from './usePagination' export const useTokensPagination = ({ where, @@ -17,16 +16,15 @@ export const useTokensPagination = ({ orderBy?: CreatorTokenOrderByInput initialPageSize?: number }) => { - const pagination = usePagination(0) - const [perPage, setPerPage] = useState(initialPageSize) + const pagination = useQueryPagination({ initialPerPage: initialPageSize }) const { data, loading } = useGetBasicCreatorTokensQuery({ notifyOnNetworkStatusChange: true, variables: { where, orderBy, - offset: pagination.currentPage * perPage, - limit: perPage, + offset: pagination.currentPage * pagination.perPage, + limit: pagination.perPage, }, onError: (error) => { SentryLogger.error('Failed to fetch tokens query', 'useTokensPagination', error) @@ -43,7 +41,5 @@ export const useTokensPagination = ({ tokens: data?.creatorTokens, totalCount: countData?.creatorTokensConnection.totalCount ?? 0, isLoading: loading || loadingCount, - setPerPage, - perPage, } } From 6bbeba5b8beadbc3fd9038f349543a37590928d6 Mon Sep 17 00:00:00 2001 From: ikprk Date: Mon, 20 May 2024 19:22:43 +0200 Subject: [PATCH 5/5] Better signalize revenue share money block on channel view --- .../_crt/BuyFromMarketButton/BuyFromMarketButton.tsx | 10 ++++++++-- .../_crt/BuyMarketTokenModal/BuyMarketTokenModal.tsx | 11 +++++++++++ .../_crt/CrtStatusWidget/CrtStatusWidget.tsx | 8 ++++++-- .../_crt/SellOnMarketButton/SellOnMarketButton.tsx | 10 ++++++++-- .../components/_crt/SellTokenModal/SellTokenModal.tsx | 7 +++++-- 5 files changed, 38 insertions(+), 8 deletions(-) diff --git a/packages/atlas/src/components/_crt/BuyFromMarketButton/BuyFromMarketButton.tsx b/packages/atlas/src/components/_crt/BuyFromMarketButton/BuyFromMarketButton.tsx index e0d56c6a94..61587e88bc 100644 --- a/packages/atlas/src/components/_crt/BuyFromMarketButton/BuyFromMarketButton.tsx +++ b/packages/atlas/src/components/_crt/BuyFromMarketButton/BuyFromMarketButton.tsx @@ -6,15 +6,21 @@ import { BuyMarketTokenModal } from '@/components/_crt/BuyMarketTokenModal' type BuyFromMarketButtonProps = { tokenId: string + hasActiveRevenueShare?: boolean } -export const BuyFromMarketButton = ({ tokenId }: BuyFromMarketButtonProps) => { +export const BuyFromMarketButton = ({ tokenId, hasActiveRevenueShare }: BuyFromMarketButtonProps) => { const [showModal, setShowModal] = useState(false) return ( <> setShowModal(false)} /> - diff --git a/packages/atlas/src/components/_crt/BuyMarketTokenModal/BuyMarketTokenModal.tsx b/packages/atlas/src/components/_crt/BuyMarketTokenModal/BuyMarketTokenModal.tsx index c8daaf9e54..bfafcad780 100644 --- a/packages/atlas/src/components/_crt/BuyMarketTokenModal/BuyMarketTokenModal.tsx +++ b/packages/atlas/src/components/_crt/BuyMarketTokenModal/BuyMarketTokenModal.tsx @@ -7,8 +7,10 @@ import { useGetCreatorTokenHoldersQuery, useGetFullCreatorTokenQuery, } from '@/api/queries/__generated__/creatorTokens.generated' +import { SvgAlertsWarning24 } from '@/assets/icons' import { NumberFormat } from '@/components/NumberFormat' import { Text } from '@/components/Text' +import { Tooltip } from '@/components/Tooltip' import { AmmModalFormTemplate } from '@/components/_crt/AmmModalTemplates' import { AmmModalSummaryTemplate } from '@/components/_crt/AmmModalTemplates/AmmModalSummaryTemplate' import { BuyMarketTokenSuccess } from '@/components/_crt/BuyMarketTokenModal/steps/BuyMarketTokenSuccess' @@ -353,6 +355,8 @@ export const BuyMarketTokenModal = ({ tokenId, onClose: _onClose, show }: BuySal if (activeStep === BUY_MARKET_TOKEN_STEPS.form) { setPrimaryButtonProps({ text: 'Continue', + disabled: hasActiveRevenueShare, + variant: hasActiveRevenueShare ? 'warning' : undefined, onClick: () => { if (hasActiveRevenueShare) { displaySnackbar({ @@ -405,6 +409,13 @@ export const BuyMarketTokenModal = ({ tokenId, onClose: _onClose, show }: BuySal secondaryButton={secondaryButton} confetti={activeStep === BUY_MARKET_TOKEN_STEPS.success && smMatch} noContentPadding={activeStep === BUY_MARKET_TOKEN_STEPS.conditions} + additionalActionsNode={ + hasActiveRevenueShare ? ( + + + + ) : null + } > {activeStep === BUY_MARKET_TOKEN_STEPS.form && ( { } const MarketDetails = ({ token }: { token: FullCreatorTokenFragment }) => { + const currentBlockRef = useRef(useJoystreamStore((store) => store.currentBlock)) + const activeRevenueShare = token.revenueShares.find((rS) => !rS.finalized) + const hasActiveRevenueShare = (activeRevenueShare?.endsAt ?? 0) > currentBlockRef.current const calculateSlippageAmount = useCallback( (amount: number) => { const currentAmm = token?.ammCurves.find((amm) => !amm.finalized) @@ -132,8 +136,8 @@ const MarketDetails = ({ token }: { token: FullCreatorTokenFragment }) => { tooltipText="Price of each incremental unit purchased or sold depends on overall quantity of tokens transacted, the actual average price per unit for the entire purchase or sale will differ from the price displayed for the first unit transacted." /> - - + + diff --git a/packages/atlas/src/components/_crt/SellOnMarketButton/SellOnMarketButton.tsx b/packages/atlas/src/components/_crt/SellOnMarketButton/SellOnMarketButton.tsx index d4444c67cf..3e9ee174d6 100644 --- a/packages/atlas/src/components/_crt/SellOnMarketButton/SellOnMarketButton.tsx +++ b/packages/atlas/src/components/_crt/SellOnMarketButton/SellOnMarketButton.tsx @@ -6,15 +6,21 @@ import { SellTokenModal } from '@/components/_crt/SellTokenModal' type SellOnMarketButtonProps = { tokenId: string + hasActiveRevenueShare?: boolean } -export const SellOnMarketButton = ({ tokenId }: SellOnMarketButtonProps) => { +export const SellOnMarketButton = ({ tokenId, hasActiveRevenueShare }: SellOnMarketButtonProps) => { const [showModal, setShowModal] = useState(false) return ( <> setShowModal(false)} /> - diff --git a/packages/atlas/src/components/_crt/SellTokenModal/SellTokenModal.tsx b/packages/atlas/src/components/_crt/SellTokenModal/SellTokenModal.tsx index 58d2a6bc54..2a2c4e850e 100644 --- a/packages/atlas/src/components/_crt/SellTokenModal/SellTokenModal.tsx +++ b/packages/atlas/src/components/_crt/SellTokenModal/SellTokenModal.tsx @@ -1,5 +1,5 @@ import BN from 'bn.js' -import { useCallback, useMemo, useState } from 'react' +import { useCallback, useMemo, useRef, useState } from 'react' import { useForm } from 'react-hook-form' import { useGetFullCreatorTokenQuery } from '@/api/queries/__generated__/creatorTokens.generated' @@ -15,6 +15,7 @@ import { useGetTokenBalance } from '@/hooks/useGetTokenBalance' import { useSegmentAnalytics } from '@/hooks/useSegmentAnalytics' import { hapiBnToTokenNumber, tokenNumberToHapiBn } from '@/joystream-lib/utils' import { useFee, useJoystream } from '@/providers/joystream' +import { useJoystreamStore } from '@/providers/joystream/joystream.store' import { useNetworkUtils } from '@/providers/networkUtils/networkUtils.hooks' import { useSnackbar } from '@/providers/snackbars' import { useTransaction } from '@/providers/transactions/transactions.hooks' @@ -40,6 +41,7 @@ export const SellTokenModal = ({ tokenId, onClose: _onClose, show }: SellTokenMo const { trackAMMTokensSold } = useSegmentAnalytics() const { displaySnackbar } = useSnackbar() const { fullFee } = useFee('sellTokenOnMarketTx', ['1', '1', '2', '10000000']) + const currentBlockRef = useRef(useJoystreamStore((store) => store.currentBlock)) const { data, loading } = useGetFullCreatorTokenQuery({ variables: { id: tokenId, @@ -48,7 +50,8 @@ export const SellTokenModal = ({ tokenId, onClose: _onClose, show }: SellTokenMo SentryLogger.error('Failed to fetch token data', 'SellTokenModal', { error }) }, }) - const hasActiveRevenueShare = data?.creatorTokenById?.revenueShares.some((rS) => !rS.finalized) + const activeRevenueShare = data?.creatorTokenById?.revenueShares.find((rS) => !rS.finalized) + const hasActiveRevenueShare = (activeRevenueShare?.endsAt ?? 0) > currentBlockRef.current const currentAmm = data?.creatorTokenById?.currentAmmSale const ammBalance = currentAmm ? +currentAmm.mintedByAmm - +currentAmm.burnedByAmm : 0