Skip to content

Commit

Permalink
😮‍💨 Start revenue share modal (Joystream#4869)
Browse files Browse the repository at this point in the history
* Add start revenue share modal

* CR fixes

* CR fixes v2

* Lint
  • Loading branch information
WRadoslaw committed Apr 22, 2024
1 parent 9a05180 commit e462249
Show file tree
Hide file tree
Showing 3 changed files with 253 additions and 5 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
import styled from '@emotion/styled'
import { useCallback, useMemo } from 'react'
import { Controller, useForm } from 'react-hook-form'

import { FlexBox } from '@/components/FlexBox/FlexBox'
import { JoyTokenIcon } from '@/components/JoyTokenIcon'
import { NumberFormat } from '@/components/NumberFormat'
import { Text } from '@/components/Text'
import { TextButton } from '@/components/_buttons/Button'
import { AuctionDatePicker, AuctionDatePickerProps } from '@/components/_inputs/AuctionDatePicker'
import { FormField } from '@/components/_inputs/FormField'
import { TokenInput } from '@/components/_inputs/TokenInput'
import { DetailsContent } from '@/components/_nft/NftTile'
import { DialogModal } from '@/components/_overlays/DialogModal'
import { useMediaMatch } from '@/hooks/useMediaMatch'
import { pluralizeNoun } from '@/utils/misc'

export type StartRevenueShareProps = {
tokenId: string
onClose: () => void
show: boolean
}

const getTokenDetails = (_: string) => ({
title: 'JBC',
pricePerUnit: 1000,
tokensOnSale: 67773,
userBalance: 100000,
patronageRate: 0.8,
})

const datePickerItemsFactory = (days: number[]) =>
days.map((value) => ({
name: pluralizeNoun(value, 'day'),
value: {
type: 'duration' as const,
durationDays: value,
},
}))

const endDateItems = datePickerItemsFactory([7, 14, 30])

export const StartRevenueShare = ({ tokenId, onClose, show }: StartRevenueShareProps) => {
const smMatch = useMediaMatch('sm')
const { patronageRate, userBalance } = getTokenDetails(tokenId)

const form = useForm<{
tokens: number | null
startDate: AuctionDatePickerProps['value'] | null
endDate: AuctionDatePickerProps['value'] | null
}>()
const { trigger, control, watch } = form
const [startDate, endDate, tokens] = watch(['startDate', 'endDate', 'tokens'])

const details = useMemo(
() => [
{
title: 'You will receive',
content: (
<FlexBox alignItems="baseline" width="fit-content">
<NumberFormat value={(tokens || 0) * patronageRate} as="p" variant="t100" color="colorText" withToken />
<Text variant="t100" as="p" color="colorText">
({Math.round(patronageRate * 100)}%)
</Text>
</FlexBox>
),
},
{
title: 'Your holders will receive',
content: (
<FlexBox alignItems="baseline" width="fit-content">
<NumberFormat
value={(tokens || 0) * (1 - patronageRate)}
as="p"
variant="t100"
color="colorText"
withToken
/>
<Text variant="t100" as="p" color="colorText">
( {Math.round((1 - patronageRate) * 100)}%)
</Text>
</FlexBox>
),
},
],
[patronageRate, tokens]
)

const selectDurationToDate = useCallback((value: AuctionDatePickerProps['value'], base?: Date) => {
if (value?.type === 'date') {
return value.date
}

if (value?.type === 'duration') {
const now = base ? new Date(base.getTime()) : new Date()
now.setDate(now.getDate() + value.durationDays)
return now
}
return undefined
}, [])

return (
<DialogModal
title="Start revenue share"
show={show}
onExitClick={onClose}
primaryButton={{
text: 'Start revenue share',
}}
>
<FlexBox flow="column" gap={8}>
<FlexBox gap={6} equalChildren>
<DetailsContent
avoidIconStyling
tileSize={smMatch ? 'big' : 'bigSmall'}
caption="YOUR CHANNEL BALANCE"
content={userBalance}
icon={<JoyTokenIcon size={smMatch ? 24 : 16} variant="silver" />}
withDenomination
/>
</FlexBox>
<Controller
name="tokens"
control={control}
render={({ field: { onChange, value } }) => (
<FormField
label="Amount to share"
description="Those tokens will be withdrawn from your channel balance and divided between you and your token holders."
>
<TokenInput
value={value}
onChange={onChange}
placeholder="0"
nodeEnd={
<FlexBox gap={2} alignItems="baseline">
<Text variant="t300" as="p" color="colorTextMuted">
$0.00
</Text>
<TextButton>Max</TextButton>
</FlexBox>
}
/>
</FormField>
)}
/>

<FlexBox flow="column" gap={2}>
{details.map((row) => (
<FlexBox key={row.title} alignItems="center" justifyContent="space-between">
<FlexBox width="fit-content" alignItems="center">
<Text variant="t100" as="p" color="colorText">
{row.title}
</Text>
</FlexBox>
{row.content}
</FlexBox>
))}
</FlexBox>

<FlexBox equalChildren alignItems="center" gap={6}>
<Controller
name="startDate"
control={control}
render={({ field: { onChange, value }, fieldState: { error } }) => (
<FormField
error={error?.message}
// TODO shake animation on date picker is very glitchy, for now just disable it
disableErrorAnimation
label="Starts"
>
<OuterBox>
<InnerBox>
<AuctionDatePicker
error={!!error}
minDate={new Date()}
maxDate={selectDurationToDate(endDate, selectDurationToDate(startDate))}
items={[
{
value: null,
name: 'Now',
},
]}
onChange={(value) => {
onChange(value)
trigger('startDate')
}}
value={value}
/>
</InnerBox>
</OuterBox>
</FormField>
)}
/>
<Controller
name="endDate"
control={control}
render={({ field: { onChange, value }, fieldState: { error } }) => (
<FormField
error={error?.message}
// TODO shake animation on date picker is very glitchy, for now just disable it
disableErrorAnimation
label="Ends"
>
<OuterBox>
<InnerBox>
<AuctionDatePicker
error={!!error}
minDate={selectDurationToDate(startDate)}
items={endDateItems}
onChange={(value) => {
onChange(value)
trigger('endDate')
}}
value={value}
/>
</InnerBox>
</OuterBox>
</FormField>
)}
/>
</FlexBox>
</FlexBox>
</DialogModal>
)
}

const OuterBox = styled.div`
position: relative;
height: 50px;
`

const InnerBox = styled.div`
position: absolute;
inset: 0;
`
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ export const AuctionDatePicker: FC<AuctionDatePickerProps> = ({
offset={[0, 8]}
ref={popOverRef}
triggerMode="manual"
appendTo={document.body}
triggerTarget={selectRef.current}
trigger={null}
onShow={() => {
Expand Down
22 changes: 17 additions & 5 deletions packages/atlas/src/views/studio/CrtDashboard/CrtDashboard.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { useCallback, useState } from 'react'

import { SvgActionEdit, SvgActionLinkUrl, SvgActionSell } from '@/assets/icons'
import { SvgActionEdit, SvgActionLinkUrl, SvgActionRevenueShare, SvgActionSell } from '@/assets/icons'
import { LimitedWidthContainer } from '@/components/LimitedWidthContainer'
import { Tabs } from '@/components/Tabs'
import { Text } from '@/components/Text'
import { Button } from '@/components/_buttons/Button'
import { StartRevenueShare } from '@/components/_crt/StartRevenueShareModal/StartRevenueShareModal'
import { HeaderContainer, MainContainer, TabsContainer } from '@/views/studio/CrtDashboard/CrtDashboard.styles'
import { CrtDashboardMainTab } from '@/views/studio/CrtDashboard/tabs/CrtDashboardMainTab'
import { CrtHoldersTab } from '@/views/studio/CrtDashboard/tabs/CrtHoldersTab'
Expand All @@ -14,6 +15,7 @@ const TABS = ['Dashboard', 'Holders', 'Revenue share', 'Settings'] as const

export const CrtDashboard = () => {
const [currentTab, setCurrentTab] = useState<number>(0)
const [openRevenueShareModal, setOpenRevenueShareModal] = useState(false)
const handleChangeTab = useCallback((idx: number) => {
setCurrentTab(idx)
}, [])
Expand All @@ -22,6 +24,7 @@ export const CrtDashboard = () => {

return (
<LimitedWidthContainer>
<StartRevenueShare show={openRevenueShareModal} tokenId="1" onClose={() => setOpenRevenueShareModal(false)} />
<MainContainer>
<HeaderContainer>
<Text variant="h700" as="h1">
Expand All @@ -34,10 +37,19 @@ export const CrtDashboard = () => {

<TabsContainer>
<Tabs initialIndex={0} selected={currentTab} tabs={mappedTabs} onSelectTab={handleChangeTab} />
<Button variant="secondary" icon={<SvgActionEdit />}>
Edit token page
</Button>
<Button icon={<SvgActionSell />}>Start sale or market</Button>
{currentTab === 0 && (
<>
<Button variant="secondary" icon={<SvgActionEdit />}>
Edit token page
</Button>
<Button icon={<SvgActionSell />}>Start sale or market</Button>
</>
)}
{currentTab === 2 && (
<Button onClick={() => setOpenRevenueShareModal(true)} icon={<SvgActionRevenueShare />}>
Start revenue share
</Button>
)}
</TabsContainer>
{currentTab === 0 && <CrtDashboardMainTab />}
{currentTab === 1 && <CrtHoldersTab />}
Expand Down

0 comments on commit e462249

Please sign in to comment.