Skip to content

Commit

Permalink
introduce get featured flow
Browse files Browse the repository at this point in the history
  • Loading branch information
drillprop committed Apr 28, 2023
1 parent bd03b22 commit efae64e
Show file tree
Hide file tree
Showing 5 changed files with 290 additions and 3 deletions.
57 changes: 57 additions & 0 deletions packages/atlas/src/api/queries/__generated__/nfts.generated.tsx

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions packages/atlas/src/api/queries/nfts.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,11 @@ query GetFeaturedNftsVideos($limit: Int) {
}
}
}

mutation RequestNftFeatured($nftId: String!, $rationale: String!) {
requestNftFeatured(nftId: $nftId, rationale: $rationale) {
rationale
nftId
createdAt
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
import { useApolloClient } from '@apollo/client'
import debouncePromise from 'awesome-debounce-promise'
import { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useForm } from 'react-hook-form'

import {
GetNftDocument,
GetNftQuery,
GetNftQueryVariables,
useRequestNftFeaturedMutation,
} from '@/api/queries/__generated__/nfts.generated'
import { Text } from '@/components/Text'
import { Input } from '@/components/_inputs/Input'
import { DialogModal } from '@/components/_overlays/DialogModal'
import { VideoTileViewer } from '@/components/_video/VideoTileViewer'
import { useSnackbar } from '@/providers/snackbars'
import { SentryLogger } from '@/utils/logs'

import { PreviewWrapper, StyledFormField } from './FeaturedNftsSection.styles'

type FeatureNftModalProps = {
isOpen: boolean
onClose: () => void
}

export const FeatureNftModal: FC<FeatureNftModalProps> = ({ isOpen, onClose }) => {
const {
register,
handleSubmit,
trigger,
reset,
formState: { errors },
} = useForm<{ url: string }>()
const [isInputValidating, setIsInputValidating] = useState(false)

const handleClose = () => {
onClose()
setVideoId('')
reset({ url: '' })
}
const [videoId, setVideoId] = useState('')
const [requestNftFeaturedMutation, { loading }] = useRequestNftFeaturedMutation({
onError: (error) => {
displaySnackbar({
title: 'Something went wrong',
description: 'There was a problem with sending your request. Please try again later.',
iconType: 'error',
})
SentryLogger.error('Error during sending requestNftFeaturedMutation', 'FeatureNftModal', { videoId, error })
},
onCompleted: () => {
displaySnackbar({
title: 'Video submitted succesfully',
description: 'Thanks! We will review your video shortly. Keep an eye on the featured section.',
iconType: 'success',
})
handleClose()
},
})

const { displaySnackbar } = useSnackbar()

const inputRef = useRef<HTMLInputElement | null>(null)

const submit = () => {
handleSubmit(async (data) => {
const videoId = /[^/]*$/.exec(data.url)?.[0]
if (!videoId) {
return
}

await requestNftFeaturedMutation({
variables: {
nftId: videoId,
rationale: '',
},
})
})()
}

const client = useApolloClient()

useEffect(() => {
if (isOpen) {
inputRef.current?.focus()
}
}, [isOpen])

const validateNft = useCallback(
async (id?: string) => {
if (!id) {
return 'Enter a valid link to your video NFT.'
}
const {
data: { ownedNftById },
} = await client.query<GetNftQuery, GetNftQueryVariables>({
query: GetNftDocument,
variables: {
id: id,
},
})
if (!ownedNftById) {
return 'This video is not an NFT.'
}
setVideoId(id)
if (
ownedNftById.transactionalStatus?.__typename === 'TransactionalStatusIdle' ||
ownedNftById.transactionalStatus?.__typename === 'TransactionalStatusInitiatedOfferToMember'
) {
return "This video's NFT is not put on sale."
}

if (
ownedNftById.transactionalStatus?.__typename === 'TransactionalStatusAuction' &&
ownedNftById.transactionalStatus.auction.isCompleted
) {
return "This video's NFT is not put on sale."
}
return true
},
[client]
)

const { ref, ...inputRefRest } = useMemo(() => {
return register('url', {
onChange: debouncePromise(
async () => {
await trigger('url')
setIsInputValidating(false)
},
500,
{
key() {
setIsInputValidating(true)
return null
},
}
),
required: { value: true, message: 'Enter link to your video NFT.' },
validate: {
validUrl: (val) => {
return val.startsWith(window.location.origin + '/video/') ? true : 'Enter a valid link to your video NFT.'
},
nftIsValid: async (val) => {
// get the last string after slash
const videoId = /[^/]*$/.exec(val)?.[0]
const validation = await validateNft(videoId)

return validation
},
},
})
}, [register, trigger, validateNft])

const isInputRefActiveElement = document.activeElement === inputRef.current
return (
<DialogModal
title="Apply for featured section"
show={isOpen}
noContentPadding
primaryButton={{
text: loading ? 'Please wait...' : 'Submit video',
disabled: loading,
onClick: submit,
}}
secondaryButton={{
text: 'Cancel',
disabled: loading,
onClick: handleClose,
}}
onExitClick={handleClose}
>
<StyledFormField
disableErrorAnimation={isInputRefActiveElement}
error={errors.url?.message}
label="Link to your video NFT"
description="Make sure your video NFT is listed on sale (or an upcoming sale) at the time of submission."
>
<Input
{...inputRefRest}
ref={(e) => {
ref(e)
inputRef.current = e
}}
processing={isInputValidating}
autoComplete="off"
placeholder="Paste your link here"
error={!!errors.url}
/>
</StyledFormField>
<PreviewWrapper>
{videoId ? (
<VideoTileViewer direction="horizontal" id={videoId} detailsVariant="withChannelName" />
) : (
<Text variant="t200" as="p" color="colorTextMuted">
Preview of your video will appear here
</Text>
)}
</PreviewWrapper>
</DialogModal>
)
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,26 @@
import styled from '@emotion/styled'

import { sizes } from '@/styles'
import { FormField } from '@/components/_inputs/FormField'
import { cVar, sizes } from '@/styles'

export const FeaturedNftsWrapper = styled.div`
display: flex;
flex-direction: column;
gap: ${sizes(8)};
`

export const StyledFormField = styled(FormField)`
padding: ${sizes(6)} ${sizes(5)} ${sizes(5)};
`

export const PreviewWrapper = styled.div`
display: flex;
padding: ${sizes(5)};
align-items: center;
justify-content: center;
height: 164px;
background: ${cVar('colorBackgroundMutedAlpha')};
box-shadow: ${cVar('effectDividersBottom')}, ${cVar('effectDividersTop')};
width: 100%;
margin-bottom: ${sizes(5)};
`
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { FC } from 'react'
import { FC, useState } from 'react'

import { useNfts } from '@/api/hooks/nfts'
import { OwnedNftOrderByInput } from '@/api/queries/__generated__/baseTypes.generated'
Expand All @@ -13,6 +13,7 @@ import { useUser } from '@/providers/user/user.hooks'
import { breakpoints } from '@/styles'
import { createPlaceholderData } from '@/utils/data'

import { FeatureNftModal } from './FeatureNftModal'
import { FeaturedNftsWrapper } from './FeaturedNftsSection.styles'

const responsive: CarouselProps['breakpoints'] = {
Expand Down Expand Up @@ -48,6 +49,7 @@ const responsive: CarouselProps['breakpoints'] = {

export const FeaturedNftsSection: FC = () => {
const { activeChannel } = useUser()
const [isFeatureNftModalOpen, setIsFeatureNfrModalOpen] = useState(false)

const { nfts, loading } = useNfts({
variables: {
Expand Down Expand Up @@ -75,6 +77,7 @@ export const FeaturedNftsSection: FC = () => {

return (
<FeaturedNftsWrapper>
<FeatureNftModal isOpen={isFeatureNftModalOpen} onClose={() => setIsFeatureNfrModalOpen(false)} />
{items.length >= 4 && (
<Section
headerProps={{
Expand Down Expand Up @@ -106,7 +109,7 @@ export const FeaturedNftsSection: FC = () => {
actionButton={{
text: 'Submit your video NFT to be featured',
onClick: () => {
// todo
setIsFeatureNfrModalOpen(true)
},
}}
/>
Expand Down

0 comments on commit efae64e

Please sign in to comment.