Skip to content

Commit

Permalink
🕳️ Holders widget (Joystream#4780)
Browse files Browse the repository at this point in the history
* Initial work on holders widget

* Story

* Introduce holders modal

* Fix asset loading on error

* Linter

* CR fixes v1

* CR fixes v2

* CR fixes v3
  • Loading branch information
WRadoslaw committed Apr 22, 2024
1 parent d3573e5 commit eaacf98
Show file tree
Hide file tree
Showing 5 changed files with 272 additions and 1 deletion.
10 changes: 9 additions & 1 deletion packages/atlas/src/components/Text/Text.styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export type TextBaseProps = {
clampAfterLine?: number
margin?: MarginProps
align?: AlignProps
truncate?: boolean
}

type MarginProps =
Expand Down Expand Up @@ -47,13 +48,20 @@ const clampStyles = ({ clampAfterLine }: TextBaseProps) => css`
overflow: hidden;
`

const baseStyles = ({ color = 'colorTextStrong', clampAfterLine, margin, align }: TextBaseProps) => css`
const truncateStyles = css`
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
`

const baseStyles = ({ color = 'colorTextStrong', clampAfterLine, margin, align, truncate }: TextBaseProps) => css`
color: ${color === 'inherit' ? color : cVar(color)};
white-space: pre-wrap;
${clampAfterLine && clampStyles({ clampAfterLine })}
${marginStyles({ margin })}
${alignStyles({ align })}
${truncate && truncateStyles}
`

export const styledVariants = {
Expand Down
120 changes: 120 additions & 0 deletions packages/atlas/src/components/_crt/CrtHoldersTable/CrtHoldersTable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import styled from '@emotion/styled'
import { useMemo } from 'react'

import { useMemberships } from '@/api/hooks/membership'
import { Avatar } from '@/components/Avatar'
import { FlexBox } from '@/components/FlexBox'
import { NumberFormat } from '@/components/NumberFormat'
import { Table, TableProps } from '@/components/Table'
import { Text } from '@/components/Text'
import { SkeletonLoader } from '@/components/_loaders/SkeletonLoader'
import { getMemberAvatar } from '@/providers/assets/assets.helpers'

const COLUMNS: TableProps['columns'] = [
{ Header: 'Member', accessor: 'member', width: 2 },
{ Header: 'Total', accessor: 'total', width: 1 },
{ Header: 'Vested', accessor: 'vested', width: 1 },
]

type CrtHolder = {
memberId: string
total: number
vested: number
}

export type CrtHoldersTableProps = {
data: CrtHolder[]
isLoading: boolean
className?: string
ownerId?: string
}

export const CrtHoldersTable = ({ isLoading, data, className, ownerId }: CrtHoldersTableProps) => {
const mappedData = useMemo(
() =>
data.map((row) => ({
member: <MemberRow memberId={row.memberId} isOwner={row.memberId === ownerId} />,
total: (
<RightAlignedCell>
<FlexBox alignItems="center" gap={1}>
<NumberFormat format="short" value={row.vested} as="p" variant="t200-strong" />
<Text variant="t200" as="p" color="colorText">
(20%)
</Text>
</FlexBox>
</RightAlignedCell>
),
vested: (
<RightAlignedCell>
<NumberFormat format="short" value={row.vested} as="p" variant="t200-strong" />
</RightAlignedCell>
),
})),
[data, ownerId]
)

if (isLoading) {
return null
}

return <StyledTable columns={COLUMNS} data={mappedData} className={className} />
}

const MemberRow = ({ memberId, isOwner }: { memberId: string; isOwner: boolean }) => {
const { loading, memberships } = useMemberships({
where: {
id_eq: memberId,
},
})

if (loading) {
return (
<FlexBox alignItems="center" gap={2}>
<SkeletonLoader rounded width={32} height={32} />
<SkeletonLoader height={16} width="40%" />
</FlexBox>
)
}

const member = memberships?.[0]
const { urls, isLoadingAsset } = getMemberAvatar(member)

return (
<FlexBox alignItems="center" gap={2}>
<Avatar loading={isLoadingAsset} assetUrls={urls} />
<HandleContainer flow="column" gap={0}>
<Text variant="t200-strong" as="p" color="colorText" truncate>
{member?.handle ?? 'Unknown'}
</Text>
{isOwner && (
<Text variant="t100" as="p" color="colorTextMuted">
Creator
</Text>
)}
</HandleContainer>
</FlexBox>
)
}

const HandleContainer = styled(FlexBox)`
overflow: hidden;
> * {
width: 100%;
}
`

export const StyledTable = styled(Table)`
th:nth-child(n + 2) {
justify-content: end;
align-items: end;
> div {
align-items: end;
}
}
`

export const RightAlignedCell = styled.div`
margin-left: auto;
`
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { ApolloProvider } from '@apollo/client'
import { Meta, StoryFn } from '@storybook/react'
import { QueryClient, QueryClientProvider } from 'react-query'
import { MemoryRouter } from 'react-router-dom'

import { createApolloClient } from '@/api'
import { HoldersWidget, HoldersWidgetProps } from '@/components/_crt/HoldersWidget/HoldersWidget'
import { OperatorsContextProvider } from '@/providers/assets/assets.provider'
import { JoystreamProvider } from '@/providers/joystream/joystream.provider'
import { OverlayManagerProvider } from '@/providers/overlayManager'

const queryClient = new QueryClient({
defaultOptions: {
queries: {
refetchOnWindowFocus: false,
},
},
})

export default {
title: 'crt/HoldersWidget',
component: HoldersWidget,
decorators: [
(Story) => {
const apolloClient = createApolloClient()
return (
<MemoryRouter>
<QueryClientProvider client={queryClient}>
<ApolloProvider client={apolloClient}>
<OverlayManagerProvider>
<OperatorsContextProvider>
<JoystreamProvider>
<Story />
</JoystreamProvider>
</OperatorsContextProvider>
</OverlayManagerProvider>
</ApolloProvider>
</QueryClientProvider>
</MemoryRouter>
)
},
],
} as Meta<HoldersWidgetProps>

const Template: StoryFn<HoldersWidgetProps> = (args) => <HoldersWidget {...args} />

export const Default = Template.bind({})
95 changes: 95 additions & 0 deletions packages/atlas/src/components/_crt/HoldersWidget/HoldersWidget.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import styled from '@emotion/styled'
import { useState } from 'react'

import { SvgActionChevronR } from '@/assets/icons'
import { FlexBox } from '@/components/FlexBox'
import { Text } from '@/components/Text'
import { TextButton } from '@/components/_buttons/Button'
import { CrtHoldersTable, CrtHoldersTableProps } from '@/components/_crt/CrtHoldersTable/CrtHoldersTable'
import { DialogModal } from '@/components/_overlays/DialogModal'
import { cVar, sizes } from '@/styles'

export type HoldersWidgetProps = {
tokenId: string
}

const getTokenHolders = (_: string) => {
return {
holders: [
{
memberId: '1',
vested: 10000,
total: 11000,
},
{
memberId: '2',
vested: 1000,
total: 1000,
},
{
memberId: '3',
vested: 100,
total: 110,
},
],
ownerId: '1',
}
}

export const HoldersWidget = ({ tokenId }: HoldersWidgetProps) => {
const [showModal, setShowModal] = useState(false)
const { holders, ownerId } = getTokenHolders(tokenId)
return (
<Box>
<CrtHoldersTableModal
data={holders}
ownerId={ownerId}
isLoading={false}
show={showModal}
onExitClick={() => setShowModal(false)}
/>
<FlexBox alignItems="center" justifyContent="space-between">
<Text variant="h500" as="span">
Holders{' '}
<Text variant="h500" as="span" color="colorText">
({holders.length})
</Text>
</Text>
<TextButton icon={<SvgActionChevronR />} iconPlacement="right" onClick={() => setShowModal(true)}>
Show all holders
</TextButton>
</FlexBox>
<CrtHoldersTable data={holders} ownerId={ownerId} isLoading={false} />
</Box>
)
}

const Box = styled.div`
background-color: ${cVar('colorBackgroundMuted')};
> *:nth-child(1) {
padding: ${sizes(6)};
}
`

const StyledHoldersTable = styled(CrtHoldersTable)`
margin-top: ${sizes(6)};
background-color: transparent;
.table-header {
background-color: #343d44;
}
`

type CrtHoldersTableModalProps = {
show: boolean
onExitClick: () => void
} & CrtHoldersTableProps

const CrtHoldersTableModal = ({ data, onExitClick, show }: CrtHoldersTableModalProps) => {
return (
<DialogModal onExitClick={onExitClick} show={show} noContentPadding title="Holders">
<StyledHoldersTable data={data} isLoading={false} />
</DialogModal>
)
}
1 change: 1 addition & 0 deletions packages/atlas/src/components/_crt/HoldersWidget/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './HoldersWidget'

0 comments on commit eaacf98

Please sign in to comment.