diff --git a/packages/atlas/src/components/NumberFormat/NumberFormat.tsx b/packages/atlas/src/components/NumberFormat/NumberFormat.tsx index 475102d177..83fcab6c86 100644 --- a/packages/atlas/src/components/NumberFormat/NumberFormat.tsx +++ b/packages/atlas/src/components/NumberFormat/NumberFormat.tsx @@ -106,7 +106,7 @@ export const NumberFormat = forwardRef( ref={mergeRefs([ref, textRef])} > {displayedValue ? {displayedValue} : {formattedValue}} - {withToken ? ` ${customTicker}` ?? ` ${atlasConfig.joystream.tokenTicker}` : null} + {withToken ? (customTicker ? ` ${customTicker}` : ` ${atlasConfig.joystream.tokenTicker}`) : null} {withDenomination === 'after' && ( ( + + + + + + ), + ], +} as Meta + +const Template: StoryFn = (args) => + +export const Default = Template.bind({}) diff --git a/packages/atlas/src/components/_crt/BuySaleTokenModal/BuySaleTokenModal.tsx b/packages/atlas/src/components/_crt/BuySaleTokenModal/BuySaleTokenModal.tsx new file mode 100644 index 0000000000..4c32293f8b --- /dev/null +++ b/packages/atlas/src/components/_crt/BuySaleTokenModal/BuySaleTokenModal.tsx @@ -0,0 +1,82 @@ +import { useMemo, useState } from 'react' + +import { BuySaleTokenForm } from '@/components/_crt/BuySaleTokenModal/steps/BuySaleTokenForm' +import { BuySaleTokenSuccess } from '@/components/_crt/BuySaleTokenModal/steps/BuySaleTokenSuccess' +import { BuySaleTokenTerms, getTokenDetails } from '@/components/_crt/BuySaleTokenModal/steps/BuySaleTokenTerms' +import { DialogProps } from '@/components/_overlays/Dialog' +import { DialogModal } from '@/components/_overlays/DialogModal' +import { useMediaMatch } from '@/hooks/useMediaMatch' + +export type BuySaleTokenModalProps = { + tokenId: string + onClose: () => void +} + +enum BUY_SALE_TOKEN_STEPS { + form, + terms, + success, +} + +export const BuySaleTokenModal = ({ tokenId, onClose }: BuySaleTokenModalProps) => { + const { title } = getTokenDetails(tokenId) + const userTokenAmount = 1000 + + const [activeStep, setActiveStep] = useState(BUY_SALE_TOKEN_STEPS.form) + const [primaryButtonProps, setPrimaryButtonProps] = useState() + const smMatch = useMediaMatch('sm') + + const secondaryButton = useMemo(() => { + switch (activeStep) { + case BUY_SALE_TOKEN_STEPS.terms: + return { + text: 'Back', + onClick: () => { + setActiveStep(BUY_SALE_TOKEN_STEPS.form) + }, + } + case BUY_SALE_TOKEN_STEPS.form: + return { + text: 'Cancel', + } + default: + return undefined + } + }, [activeStep]) + + const commonProps = { + setPrimaryButtonProps, + } + + return ( + + {activeStep === BUY_SALE_TOKEN_STEPS.form && ( + setActiveStep(BUY_SALE_TOKEN_STEPS.terms)} + tokenId={tokenId} + /> + )} + {activeStep === BUY_SALE_TOKEN_STEPS.terms && ( + setActiveStep(BUY_SALE_TOKEN_STEPS.success)} + tokenId={tokenId} + tokenAmount={userTokenAmount} + /> + )} + {activeStep === BUY_SALE_TOKEN_STEPS.success && ( + + )} + + ) +} diff --git a/packages/atlas/src/components/_crt/BuySaleTokenModal/index.ts b/packages/atlas/src/components/_crt/BuySaleTokenModal/index.ts new file mode 100644 index 0000000000..b840a522aa --- /dev/null +++ b/packages/atlas/src/components/_crt/BuySaleTokenModal/index.ts @@ -0,0 +1 @@ +export * from './BuySaleTokenModal' diff --git a/packages/atlas/src/components/_crt/BuySaleTokenModal/steps/BuySaleTokenForm.tsx b/packages/atlas/src/components/_crt/BuySaleTokenModal/steps/BuySaleTokenForm.tsx new file mode 100644 index 0000000000..3cea7669d7 --- /dev/null +++ b/packages/atlas/src/components/_crt/BuySaleTokenModal/steps/BuySaleTokenForm.tsx @@ -0,0 +1,143 @@ +import { useMemo, useState } from 'react' + +import { FlexBox } from '@/components/FlexBox/FlexBox' +import { Information } from '@/components/Information' +import { JoyTokenIcon } from '@/components/JoyTokenIcon' +import { NumberFormat } from '@/components/NumberFormat' +import { Text } from '@/components/Text' +import { TextButton } from '@/components/_buttons/Button' +import { CommonProps } from '@/components/_crt/BuySaleTokenModal/steps/types' +import { FormField } from '@/components/_inputs/FormField' +import { TokenInput } from '@/components/_inputs/TokenInput' +import { DetailsContent } from '@/components/_nft/NftTile' +import { atlasConfig } from '@/config' +import { useMediaMatch } from '@/hooks/useMediaMatch' +import { useMountEffect } from '@/hooks/useMountEffect' + +const getTokenDetails = (_: string) => ({ + title: 'JBC', + pricePerUnit: 1000, + tokensOnSale: 67773, + userBalance: 100000, +}) + +type BuySaleTokenFormProps = { + tokenId: string + onSubmit: () => void +} & CommonProps + +const currentJoyRate = 0.15 + +export const BuySaleTokenForm = ({ tokenId, setPrimaryButtonProps, onSubmit }: BuySaleTokenFormProps) => { + const { pricePerUnit, tokensOnSale, userBalance, title } = getTokenDetails(tokenId) + + const [tokens, setTokens] = useState(null) + const tokenInUsd = (tokens || 0) * pricePerUnit * currentJoyRate + const smMatch = useMediaMatch('sm') + + const details = useMemo( + () => [ + { + title: 'Tokens on sale', + content: ( + + ), + tooltipText: 'Lorem ipsum', + }, + { + title: 'You will get', + content: ( + 1_000_000 ? 'short' : 'full'} + as="p" + variant="t200" + withDenomination="before" + withToken + customTicker={`$${title}`} + /> + ), + tooltipText: 'Lorem ipsum', + }, + { + title: 'Fee', + content: , + tooltipText: 'Lorem ipsum', + }, + { + title: 'You will spend', + content: , + tooltipText: 'Lorem ipsum', + }, + ], + [title, tokens, tokensOnSale] + ) + + useMountEffect(() => { + setPrimaryButtonProps({ + text: 'Continue', + onClick: () => onSubmit(), + }) + }) + + return ( + <> + + + } + withDenomination + /> + } + withDenomination + /> + + + + + ${tokenInUsd} + + setTokens(Math.floor(userBalance / pricePerUnit))}>Max + + } + /> + + + + {details.map((row, i) => ( + + + + {row.title} + + + + {row.content} + + ))} + + + + ) +} diff --git a/packages/atlas/src/components/_crt/BuySaleTokenModal/steps/BuySaleTokenSuccess.tsx b/packages/atlas/src/components/_crt/BuySaleTokenModal/steps/BuySaleTokenSuccess.tsx new file mode 100644 index 0000000000..b4d7478215 --- /dev/null +++ b/packages/atlas/src/components/_crt/BuySaleTokenModal/steps/BuySaleTokenSuccess.tsx @@ -0,0 +1,59 @@ +import { FC } from 'react' + +import confettiAnimation from '@/assets/animations/confetti.json' +import { AppKV } from '@/components/AppKV' +import { LottiePlayer } from '@/components/LottiePlayer' +import { Text } from '@/components/Text' +import { + ContentWrapper, + IllustrationWrapper, + LottieContainer, +} from '@/components/_auth/SignUpModal/SignUpSteps/SignUpSuccessStep/SignUpSuccessStep.styles' +import { useMediaMatch } from '@/hooks/useMediaMatch' +import { useMountEffect } from '@/hooks/useMountEffect' + +import { CommonProps } from './types' + +type SignUpSuccessStepProps = { + tokenName?: string + coinImageUrl?: string + onClose: () => void +} & CommonProps + +export const BuySaleTokenSuccess: FC = ({ tokenName, setPrimaryButtonProps, onClose }) => { + const smMatch = useMediaMatch('sm') + + useMountEffect(() => { + setPrimaryButtonProps({ + text: 'Continue', + onClick: () => onClose(), + }) + }) + return ( + <> + + + {!smMatch && ( + + + + )} + + + + Congratulations on your purchase + + + You are now an {tokenName} holder - you can buy, sell, transfer your token. You can also stake when creator + opens revenue share to claim your part of it. + + + + ) +} diff --git a/packages/atlas/src/components/_crt/BuySaleTokenModal/steps/BuySaleTokenTerms.tsx b/packages/atlas/src/components/_crt/BuySaleTokenModal/steps/BuySaleTokenTerms.tsx new file mode 100644 index 0000000000..0d427040c5 --- /dev/null +++ b/packages/atlas/src/components/_crt/BuySaleTokenModal/steps/BuySaleTokenTerms.tsx @@ -0,0 +1,224 @@ +import styled from '@emotion/styled' +import { useEffect, useMemo, useState } from 'react' + +import { FlexBox } from '@/components/FlexBox/FlexBox' +import { Information } from '@/components/Information' +import { NumberFormat } from '@/components/NumberFormat' +import { Text } from '@/components/Text' +import { LineChart } from '@/components/_charts/LineChart' +import { CommonProps } from '@/components/_crt/BuySaleTokenModal/steps/types' +import { generateChartData } from '@/components/_crt/CreateTokenDrawer/steps/TokenIssuanceStep/TokenIssuanceStep.utils' +import { TooltipBox } from '@/components/_crt/CreateTokenDrawer/steps/styles' +import { Checkbox } from '@/components/_inputs/Checkbox' +import { cVar, media, sizes } from '@/styles' +import { formatNumber } from '@/utils/number' +import { formatDate } from '@/utils/time' + +export const getTokenDetails = (_: string) => ({ + title: 'JBC', + pricePerUnit: 1000, + tokensOnSale: 67773, + userBalance: 100000, + cliffTime: 1, + vestingTime: 6, + firstPayout: 25, +}) + +type BuySaleTokenTermsProps = { + tokenId: string + tokenAmount: number + onSubmit: () => void +} & CommonProps + +export const BuySaleTokenTerms = ({ + tokenId, + setPrimaryButtonProps, + onSubmit, + tokenAmount, +}: BuySaleTokenTermsProps) => { + const { title, cliffTime, vestingTime, firstPayout } = getTokenDetails(tokenId) + const [isChecked, setIsChecked] = useState(false) + const [checkboxError, setCheckboxError] = useState('') + + const details = useMemo(() => { + const currentDate = new Date() + return [ + { + title: 'Your cliff', + content: ( + + {cliffTime} month{cliffTime > 1 ? 's' : ''} + + ), + date: new Date(), + tooltipText: 'Lorem ipsum', + }, + { + title: 'First payout', + content: ( + + ), + date: new Date(new Date(currentDate.setMonth(currentDate.getMonth() + cliffTime))), + + tooltipText: 'Lorem ipsum', + }, + { + title: 'Your vesting', + content: ( + + {vestingTime} month{vestingTime > 1 ? 's' : ''} + + ), + date: new Date(new Date(currentDate.setMonth(currentDate.getMonth() + cliffTime + vestingTime))), + + tooltipText: 'Lorem ipsum', + }, + ] + }, [cliffTime, firstPayout, title, tokenAmount, vestingTime]) + + useEffect(() => { + setPrimaryButtonProps({ + text: 'Buy tokens', + onClick: () => { + if (isChecked) { + onSubmit() + } else { + setCheckboxError('Terms & Conditions have to be accepted to continue') + } + }, + }) + }, [isChecked, onSubmit, setPrimaryButtonProps]) + + const chartData = useMemo(() => { + return generateChartData(cliffTime, vestingTime, firstPayout) + }, [cliffTime, firstPayout, vestingTime]) + + return ( + + + + Holders terms + + + + { + const currentDate = new Date() + const timeInMonths = point.data.x === 'Now' ? 0 : +(point.data.x as string).split('M')[0] + return ( + + + {formatNumber(((tokenAmount ?? 0) * (point.data.y as number)) / 100)} ${title} + + + {point.data.x !== 'Now' + ? formatDate(new Date(currentDate.setMonth(currentDate.getMonth() + timeInMonths))) + : 'Now'} + + + ) + }} + yScale={{ + type: 'linear', + min: 0, + max: 'auto', + stacked: false, + reverse: false, + }} + axisLeft={{ + tickSize: 5, + tickPadding: 5, + tickValues: [0, 25, 50, 75, 100], + format: (tick) => `${tick}%`, + }} + gridYValues={[0, 25, 50, 75, 100]} + data={[ + { + id: 1, + color: cVar('colorTextPrimary'), + data: chartData, + }, + ]} + enableCrosshair={false} + /> + + + + {details.map((row) => ( + + + + {row.title} + + + + + {row.content} + + {formatDate(row.date)} + + + + ))} + + + + + { + setIsChecked(val) + setCheckboxError('') + }} + caption={checkboxError} + error={!!checkboxError} + value={isChecked} + label="I have saved my wallet seed phrase safely" + /> + + + ) +} + +const ChartBox = styled.div` + width: calc(100% + 125px); + height: 300px; + margin-left: -25px; + + ${media.sm} { + width: calc(100% + 125px); + } +` + +export const CheckboxWrapper = styled.div<{ isAccepted: boolean }>` + position: fixed; + bottom: 80px; + left: 0; + right: 1px; + width: 100%; + display: flex; + align-items: center; + background-color: ${({ isAccepted }) => (isAccepted ? cVar('colorBackground') : cVar('colorBackgroundElevated'))}; + padding: ${sizes(4)} var(--local-size-dialog-padding); + + ${media.sm} { + bottom: 87px; + } +` + +const Container = styled.div` + display: flex; + max-height: calc(100% - 55px); + overflow-x: hidden; + overflow-y: auto; + padding: var(--local-size-dialog-padding); + padding-bottom: 0; + margin-bottom: 55px; +` diff --git a/packages/atlas/src/components/_crt/BuySaleTokenModal/steps/types.ts b/packages/atlas/src/components/_crt/BuySaleTokenModal/steps/types.ts new file mode 100644 index 0000000000..e14cfe3688 --- /dev/null +++ b/packages/atlas/src/components/_crt/BuySaleTokenModal/steps/types.ts @@ -0,0 +1,5 @@ +import { DialogProps } from '@/components/_overlays/Dialog' + +export type CommonProps = { + setPrimaryButtonProps: (props: DialogProps['primaryButton']) => void +}