Skip to content

Commit

Permalink
Merge pull request #359 from liteflow-labs/feature/drops
Browse files Browse the repository at this point in the history
Feature / Drops
  • Loading branch information
ismailToyran authored Oct 2, 2023
2 parents 3ef367d + 54426c7 commit fb28df1
Show file tree
Hide file tree
Showing 35 changed files with 2,366 additions and 69 deletions.
8 changes: 4 additions & 4 deletions components/Collection/CollectionHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ const CollectionHeader: FC<Props> = ({ collection, reportEmail }) => {
h={32}
rounded="2xl"
overflow="hidden"
border="2px solid"
borderWidth="2px"
borderColor="white"
bg="gray.200"
>
Expand Down Expand Up @@ -111,8 +111,8 @@ const CollectionHeader: FC<Props> = ({ collection, reportEmail }) => {
href={`/users/${collection.deployer.address}`}
color="brand.black"
>
<Text as="span" color="">
{collection?.deployer.name ||
<Text as="span">
{collection.deployer.name ||
formatAddress(collection.deployer.address, 10)}
</Text>
{collection.deployer.verified && (
Expand Down Expand Up @@ -199,7 +199,7 @@ const CollectionHeader: FC<Props> = ({ collection, reportEmail }) => {
</Menu>
</Flex>
</Flex>
{collection?.description && (
{collection.description && (
<Box mt={4}>
<Truncate size="lg" color="gray.500" length={200}>
{collection.description}
Expand Down
30 changes: 10 additions & 20 deletions components/Countdown/Countdown.tsx
Original file line number Diff line number Diff line change
@@ -1,36 +1,26 @@
import useTranslation from 'next-translate/useTranslation'
import { FC, HTMLAttributes, useEffect, useMemo, useState } from 'react'
import { FC } from 'react'
import useCountdown from '../../hooks/useCountdown'

const refreshRate = 1000
type Props = {
date: Date
hideSeconds?: boolean
}

const Countdown: FC<
HTMLAttributes<any> & { date: Date; hideSeconds?: boolean }
> = ({ date, hideSeconds = false, ...props }) => {
const Countdown: FC<Props> = ({ date, hideSeconds = false }) => {
const { t } = useTranslation('components')
const [diff, setDiff] = useState(+new Date(date) - +new Date())
const d = useMemo(() => Math.floor(diff / (1000 * 60 * 60 * 24)), [diff])
const h = useMemo(() => Math.floor((diff / (1000 * 60 * 60)) % 24), [diff])
const m = useMemo(() => Math.floor((diff / 1000 / 60) % 60), [diff])
const s = useMemo(() => Math.floor((diff / 1000) % 60), [diff])

useEffect(() => {
const timer = setInterval(() => {
const newDiff = +new Date(date) - +new Date()
setDiff(newDiff <= 0 ? 0 : newDiff)
}, refreshRate)
return () => clearInterval(timer)
}, [date])
const { diff, d, h, m, s } = useCountdown(date)

if (diff <= 0) return null
return (
<div {...props}>
<span>
{d > 0 && <span>{t('countdown.day', { value: d })}</span>}{' '}
<span>{t('countdown.hour', { value: `0${h}`.slice(-2) })}</span>{' '}
<span>{t('countdown.min', { value: `0${m}`.slice(-2) })}</span>{' '}
{!hideSeconds && (
<span>{t('countdown.sec', { value: `0${s}`.slice(-2) })}</span>
)}
</div>
</span>
)
}

Expand Down
56 changes: 56 additions & 0 deletions components/Countdown/DropCountdown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { Box, Flex, Text } from '@chakra-ui/react'
import useTranslation from 'next-translate/useTranslation'
import { FC, useEffect } from 'react'
import useCountdown from '../../hooks/useCountdown'

type Props = {
date: Date
isHidden?: boolean
onCountdownEnd?: () => void
}

const DropCountdown: FC<Props> = ({ date, isHidden, onCountdownEnd }) => {
const { t } = useTranslation('components')
const { diff, d, h, m, s } = useCountdown(date)

useEffect(() => {
if (diff <= 0) {
onCountdownEnd?.()
}
}, [diff, onCountdownEnd])

const boxStyle = {
w: 10,
py: 0.5,
background: 'rgba(107, 114, 128, 0.5)',
color: 'white',
rounded: 'lg',
textAlign: 'center' as any,
}

if (diff <= 0) return null
return (
<Flex display={isHidden ? 'none' : 'flex'} gap={2}>
{d > 0 && (
<Box {...boxStyle}>
<Text variant="subtitle2">{d}</Text>
<Text variant="caption">{t('countdown.day-long')}</Text>
</Box>
)}
<Box {...boxStyle}>
<Text variant="subtitle2">{`0${h}`.slice(-2)}</Text>
<Text variant="caption">{t('countdown.hour-long')}</Text>
</Box>
<Box {...boxStyle}>
<Text variant="subtitle2">{`0${m}`.slice(-2)}</Text>
<Text variant="caption">{t('countdown.min-long')}</Text>
</Box>
<Box {...boxStyle}>
<Text variant="subtitle2">{`0${s}`.slice(-2)}</Text>
<Text variant="caption">{t('countdown.sec-long')}</Text>
</Box>
</Flex>
)
}

export default DropCountdown
197 changes: 197 additions & 0 deletions components/Drop/DropCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
import {
Badge,
Box,
Divider,
Flex,
Heading,
Icon,
IconButton,
Stack,
Text,
} from '@chakra-ui/react'
import { HiArrowNarrowRight } from '@react-icons/all-files/hi/HiArrowNarrowRight'
import { HiBadgeCheck } from '@react-icons/all-files/hi/HiBadgeCheck'
import { convertDropActive } from 'convert'
import Trans from 'next-translate/Trans'
import useTranslation from 'next-translate/useTranslation'
import numbro from 'numbro'
import { useMemo } from 'react'
import useTimeStatus, { Status } from '../../hooks/useTimeStatus'
import { formatAddress } from '../../utils'
import DropCountdown from '../Countdown/DropCountdown'
import Image from '../Image/Image'
import Link from '../Link/Link'
import Price from '../Price/Price'
import TokenMedia from '../Token/Media'

type Props = {
drop: ReturnType<typeof convertDropActive>
onCountdownEnd?: () => void
}

export default function DropCard({ drop, onCountdownEnd }: Props) {
const { t } = useTranslation('components')
const status = useTimeStatus(drop)

const statusText = useMemo(() => {
if (status === Status.UPCOMING) return t('drop.timeline.upcoming')
if (status === Status.INPROGRESS) return t('drop.timeline.in-progress')
return t('drop.timeline.ended')
}, [t, status])

return (
<Box
as={Link}
href={`/collection/${drop.collection.chainId}/${drop.collection.address}/drop`}
borderWidth="1px"
borderRadius="2xl"
w="full"
overflow="hidden"
position="relative"
p={4}
>
<Box
position="absolute"
inset={0}
_after={{
content: '""',
position: 'absolute',
inset: 0,
bg: 'rgba(0,0,0,0.7)',
}}
bg="gray.100"
>
{drop.collection.cover && (
<TokenMedia
imageUrl={drop.collection.cover}
defaultText={drop.collection.name}
animationUrl={undefined}
unlockedContent={null}
sizes="(min-width: 62em) 600px, 100vw"
fill
/>
)}
</Box>

<Flex position="relative" w="full">
{status === Status.INPROGRESS && (
// Hidden countdown to trigger refetch when countdown ends
<DropCountdown
date={drop.endDate}
isHidden
onCountdownEnd={onCountdownEnd}
/>
)}
{status === Status.UPCOMING && (
<DropCountdown
date={drop.startDate}
onCountdownEnd={onCountdownEnd}
/>
)}
<Text as="span" variant="caption" verticalAlign="middle" ml="auto">
<Badge
background="rgba(107, 114, 128, 0.5)"
color="white"
px={2}
py={1}
borderRadius="2xl"
>
{statusText}
</Badge>
</Text>
</Flex>

<Box
overflow="hidden"
rounded="2xl"
borderWidth="2px"
position="relative"
w={status === Status.ENDED ? 14 : '72px'}
h={status === Status.ENDED ? 14 : '72px'}
mt={status === Status.ENDED ? -3 : 14}
mb={4}
bg="gray.200"
>
{drop.collection.image && (
<Image
src={drop.collection.image}
alt={drop.collection.name}
fill
sizes="72px"
objectFit="cover"
/>
)}
</Box>

<Flex position="relative" justifyContent="space-between" gap={4} w="full">
<Flex flexDir="column" gap={1}>
<Heading variant="heading2" color="white" isTruncated>
{drop.collection.name}
</Heading>

<Flex alignItems="center" gap={1.5}>
<Text variant="button2" color="white">
{t('drop.by', {
address:
drop.collection.deployer.name ||
formatAddress(drop.collection.deployer.address, 10),
})}
</Text>
{drop.collection.deployer?.verified && (
<Icon as={HiBadgeCheck} color="brand.500" h={4} w={4} />
)}
</Flex>

<Flex alignItems="center" gap={2}>
{drop.supply ? (
<Text variant="caption" color="white">
<Trans
ns="components"
i18nKey="drop.supply.available"
values={{
count: drop.supply.toNumber(),
}}
components={[
<>
{numbro(drop.supply).format({
thousandSeparated: true,
})}
</>,
]}
/>
</Text>
) : (
<Text variant="caption" color="white">
{t('drop.supply.infinite')}
</Text>
)}
<Divider orientation="vertical" h={4} />
<Stack alignItems="center" direction="row" spacing={1}>
<Flex flexShrink={0}>
<Image
src={drop.currency.image}
alt={drop.currency.symbol}
width={16}
height={16}
w={4}
h={4}
/>
</Flex>
<Text variant="caption" color="white" isTruncated>
<Price amount={drop.unitPrice} currency={drop.currency} />
</Text>
</Stack>
</Flex>
</Flex>
<IconButton
variant="outline"
aria-label="Drop detail"
icon={<Icon as={HiArrowNarrowRight} boxSize={4} />}
placeSelf="flex-end"
color="white"
_hover={{ bg: 'whiteAlpha.200' }}
/>
</Flex>
</Box>
)
}
27 changes: 27 additions & 0 deletions components/Drop/DropDetailSkeleton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Flex, Progress, SimpleGrid, Skeleton } from '@chakra-ui/react'
import { FC } from 'react'

type Props = {
items?: number
}

const DropDetailSkeleton: FC<Props> = ({ items = 4 }) => {
return (
<>
<Flex flexDirection="column" py={8} gap={2} width="full">
<Flex justifyContent="space-between" py={2} w="full">
<Skeleton height="1em" width="100px" />
<Skeleton height="1em" width="100px" />
</Flex>
<Progress colorScheme="brand" rounded="full" size="xs" value={0} />
</Flex>
<SimpleGrid columns={{ base: 1, md: 2 }} spacing={4} width="full">
{Array.from({ length: items }).map((_, i) => (
<Skeleton key={i} height={28} borderRadius="2xl" />
))}
</SimpleGrid>
</>
)
}

export default DropDetailSkeleton
34 changes: 34 additions & 0 deletions components/Drop/DropMintSchedule.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { StyleProps, VStack } from '@chakra-ui/react'
import { FC } from 'react'
import DropListItem from './ListItem'

type Props = StyleProps & {
drops: {
id: string
name: string
startDate: Date
endDate: Date
unitPrice: string
isAllowed: boolean
currency: {
id: string
decimals: number
symbol: string
image: string
}
supply: string | null
maxQuantityPerWallet: string | null
}[]
}

const DropMintSchedule: FC<Props> = ({ drops, ...props }) => {
return (
<VStack align="stretch" spacing={3} {...props}>
{drops.map((drop, index) => (
<DropListItem key={drop.id} drop={drop} isOpen={index === 0} />
))}
</VStack>
)
}

export default DropMintSchedule
Loading

0 comments on commit fb28df1

Please sign in to comment.