Skip to content

Commit

Permalink
Improve organization page info (#67)
Browse files Browse the repository at this point in the history
* Add tabs to org detail

* Implement account transfers

This is not really ended, it needs vocdoni/vocdoni-sdk#400 to be merged

* Implement transfers table

* Split org components into multiple files

* Bump extended-sdk

* Fix transfers pagination

* Fix default translation

* Implement error page

* Implement organization fees

* Use filter.map

* Delete commented

* Implement text and tag
  • Loading branch information
selankon authored Jul 24, 2024
1 parent 31ceb0e commit 9652ed6
Show file tree
Hide file tree
Showing 12 changed files with 888 additions and 436 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@
"@emotion/styled": "^11.10.6",
"@tanstack/react-query": "^5.40.0",
"@vocdoni/chakra-components": "~0.8.1",
"@vocdoni/extended-sdk": "^0.0.4",
"@vocdoni/sdk": "~0.8.1",
"@vocdoni/extended-sdk": "^0.1.0",
"@vocdoni/sdk": "https://github.com/vocdoni/vocdoni-sdk.git#main",
"date-fns": "^2.29.3",
"ethers": "^5.7.2",
"framer-motion": "^10.9.2",
Expand Down
15 changes: 15 additions & 0 deletions src/components/Layout/TextAndTag.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Box, HStack, Tag } from '@chakra-ui/react'
import { TagProps } from '@chakra-ui/tag/dist/tag'

const TextAndTag = ({ text, tagLabel, ...rest }: { text: string; tagLabel: string } & TagProps) => {
return (
<HStack spacing={2}>
<Box>{text}</Box>
<Tag borderRadius='full' colorScheme='green' {...rest}>
{tagLabel}
</Tag>
</HStack>
)
}

export default TextAndTag
157 changes: 60 additions & 97 deletions src/components/Organizations/Detail.tsx
Original file line number Diff line number Diff line change
@@ -1,123 +1,86 @@
import { Box, Flex, Icon, Text, VStack } from '@chakra-ui/react'
import { OrganizationDescription, OrganizationHeader, OrganizationName } from '@vocdoni/chakra-components'
import { Tab, TabList, TabPanel, TabPanels, VStack } from '@chakra-ui/react'
import { OrganizationHeader, OrganizationName } from '@vocdoni/chakra-components'
import { useOrganization } from '@vocdoni/react-providers'
import { AccountData, ensure0x, PublishedElection } from '@vocdoni/sdk'
import { Trans } from 'react-i18next'
import { FaUserAlt } from 'react-icons/fa'
import { useParams } from 'react-router-dom'
import { Trans, useTranslation } from 'react-i18next'
import { ReducedTextAndCopy } from '~components/Layout/CopyButton'
import { HeroHeaderLayout } from '~components/Layout/HeroHeaderLayout'
import { LoadingCards } from '~components/Layout/Loading'
import ShowRawButton from '~components/Layout/ShowRawButton'
import { RoutedPagination } from '~components/Pagination/Pagination'
import { RoutedPaginationProvider } from '~components/Pagination/PaginationProvider'
import { ElectionCard } from '~components/Process/Card'
import { AppBaseURL, FallbackHeaderImg, PaginationItemsPerPage, RoutePath } from '~constants'
import { useOrganizationElections } from '~queries/organizations'
import { retryUnlessNotFound } from '~utils/queries'
import { QueryParamsTabs } from '~components/Layout/QueryParamsTabs'
import { RawContentBox } from '~components/Layout/ShowRawButton'
import { FallbackHeaderImg } from '~constants'
import { useAccountTransfersCount } from '~queries/organizations'
import AccountTransfers from '~components/Organizations/Details/Transfers'
import OrganizationElections from './Details/Elections'
import OrgDetails from './Details/OrgDetails'
import AccountFees from '~components/Organizations/Details/Fees'
import TextAndTag from '~components/Layout/TextAndTag'

const OrganizationDetail = () => {
const { organization } = useOrganization()
const { data } = useAccountTransfersCount({
address: organization?.address || '',
})
const { t } = useTranslation()

// Should be already loaded
if (!organization) return null

const id = organization.address
const transfersCount = data?.count

return (
<>
<HeroHeaderLayout header={<OrganizationHeader fallbackSrc={FallbackHeaderImg} />}>
<VStack>
<OrganizationName fontSize='4xl' wordBreak='break-word' />
<OrganizationName fontSize='4xl' wordBreak='break-word' textAlign={'center'} />
<ReducedTextAndCopy color={'textAccent1'} toCopy={id} fontWeight={'normal'} h={0} fontSize={'md'}>
{id}
</ReducedTextAndCopy>
<Flex
as={'a'}
target='blank'
href={`${AppBaseURL}/organization/${ensure0x(id)}`}
pt={4}
align={'end'}
gap={3}
color={'blueText'}
>
<Box>
<Icon as={FaUserAlt} boxSize={5} />
</Box>
<Box>
<Text fontSize='xl' verticalAlign='bottom'>
<Trans i18nKey={'organization.view_profile'}>(View profile)</Trans>
</Text>
</Box>
</Flex>
</VStack>
</HeroHeaderLayout>

<Flex align='start' gap={2} direction={'column'}>
{organization.account.description.default && (
<>
<Text fontSize='xl' color={'blueText'}>
<Trans i18nKey={'organization.description'}>Description</Trans>
</Text>
<OrganizationDescription />
</>
)}
</Flex>
<Text fontSize='xl' color={'blueText'}>
<Trans i18nKey={'organization.elections_list'}>Elections List:</Trans>
</Text>
<OrganizationElections org={organization} />
<ShowRawButton obj={organization} mt={4} />
<QueryParamsTabs isLazy>
<TabList display='flex' flexWrap='wrap'>
<Tab>
<Trans i18nKey={'process.tab_details'}>Details</Trans>
</Tab>
<Tab>
<TextAndTag
text={t('organization.elections_count', { defaultValue: 'Elections' })}
tagLabel={organization.electionIndex.toString()}
/>
</Tab>
<Tab>
<TextAndTag
text={t('organization.transfers_count', { defaultValue: 'Transfers' })}
tagLabel={transfersCount?.toString() ?? '0'}
/>
</Tab>
<Tab>
<Trans i18nKey={'organization.fees'}>Fees</Trans>
</Tab>
<Tab>
<Trans i18nKey={'raw'}>Raw</Trans>
</Tab>
</TabList>
<TabPanels>
<TabPanel>
<OrgDetails org={organization} />
</TabPanel>
<TabPanel>
<OrganizationElections org={organization} />
</TabPanel>
<TabPanel>
<AccountTransfers org={organization} txCount={transfersCount} />
</TabPanel>
<TabPanel>
<AccountFees org={organization} />
</TabPanel>
<TabPanel>
<RawContentBox obj={organization} />
</TabPanel>
</TabPanels>
</QueryParamsTabs>
</>
)
}

const OrganizationElections = ({ org }: { org: AccountData }) => {
if (org.electionIndex === 0) {
return (
<Text>
<Trans i18nKey={'organization.no_elections'}>No elections yet!</Trans>
</Text>
)
}

return (
<RoutedPaginationProvider
totalPages={Math.ceil(org.electionIndex / PaginationItemsPerPage)}
path={RoutePath.Organization}
>
<Flex direction={'column'} gap={4}>
<OrganizationElectionsList org={org} />
<RoutedPagination />
</Flex>
</RoutedPaginationProvider>
)
}

const OrganizationElectionsList = ({ org }: { org: AccountData }) => {
const { page } = useParams()

const { data: elections, isLoading } = useOrganizationElections({
address: org.address,
page: Number(page) - 1 || 0,
options: {
enabled: !!org.address,
retry: retryUnlessNotFound,
},
})

if (isLoading) {
return <LoadingCards />
}

return (
<Flex direction={'column'} gap={4}>
{elections?.map((election) => {
if (election instanceof PublishedElection) return <ElectionCard key={election.id} election={election} />
return null
})}
</Flex>
)
}

export default OrganizationDetail
68 changes: 68 additions & 0 deletions src/components/Organizations/Details/Elections.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { Flex, Text } from '@chakra-ui/react'
import { AccountData, PublishedElection } from '@vocdoni/sdk'
import { Trans } from 'react-i18next'
import { useParams } from 'react-router-dom'
import { LoadingCards } from '~components/Layout/Loading'
import { RoutedPagination } from '~components/Pagination/Pagination'
import { RoutedPaginationProvider } from '~components/Pagination/PaginationProvider'
import { ElectionCard } from '~components/Process/Card'
import { PaginationItemsPerPage, RoutePath } from '~constants'
import { useOrganizationElections } from '~queries/organizations'
import { retryUnlessNotFound } from '~utils/queries'

interface OrgComponentProps {
org: AccountData
}

const OrganizationElections = ({ org }: OrgComponentProps) => {
if (org.electionIndex === 0) {
return (
<Text>
<Trans i18nKey={'organization.no_elections'}>No elections yet!</Trans>
</Text>
)
}

return (
<RoutedPaginationProvider
totalPages={Math.ceil(org.electionIndex / PaginationItemsPerPage)}
path={RoutePath.Organization}
>
<Flex direction={'column'} gap={4}>
<OrganizationElectionsList org={org} />
<RoutedPagination />
</Flex>
</RoutedPaginationProvider>
)
}

const OrganizationElectionsList = ({ org }: OrgComponentProps) => {
const { page } = useParams()

const { data: elections, isLoading } = useOrganizationElections({
address: org.address,
page: Number(page) - 1 || 0,
options: {
enabled: !!org.address,
retry: retryUnlessNotFound,
},
})

if (isLoading) {
return <LoadingCards />
}

return (
<Flex direction={'column'} gap={4}>
{elections
?.filter((election) => {
return election instanceof PublishedElection
})
.map((election) => {
return <ElectionCard key={election.id} election={election as PublishedElection} />
})}
</Flex>
)
}

export default OrganizationElections
102 changes: 102 additions & 0 deletions src/components/Organizations/Details/Fees.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { Box, Flex, Link, Table, TableContainer, Tbody, Td, Text, Th, Thead, Tr } from '@chakra-ui/react'
import { Trans } from 'react-i18next'
import { LoadingCards } from '~components/Layout/Loading'
import { useDateFns } from '~i18n/use-date-fns'
import { AccountData, TransactionType } from '@vocdoni/sdk'
import { PaginationProvider, usePagination } from '~components/Pagination/PaginationProvider'
import { Pagination } from '~components/Pagination/Pagination'
import { useAccountFees } from '~queries/organizations'
import { retryUnlessNotFound } from '~utils/queries'
import LoadingError from '~components/Layout/LoadingError'
import { TransactionTypeBadge } from '~components/Transactions/TransactionCard'
import { generatePath, Link as RouterLink } from 'react-router-dom'
import { RoutePath } from '~constants'

const AccountFees = (org: { org: AccountData }) => {
return (
<PaginationProvider>
<AccountFeesTable {...org} />
</PaginationProvider>
)
}

const AccountFeesTable = ({ org }: { org: AccountData }) => {
const { page } = usePagination()
const { formatDistance } = useDateFns()

const { data, isLoading, isError, error } = useAccountFees({
address: org.address,
page: Number(page) - 1 || 0,
options: {
retry: retryUnlessNotFound,
},
})

if (isLoading) {
return <LoadingCards />
}

if (isError || !data) {
return <LoadingError error={error} />
}

if (!data.fees.length) {
return (
<Text>
<Trans i18nKey={'organization.fees.no_fees'}>No fees yet!</Trans>
</Text>
)
}

return (
<>
<Box overflow='auto' w='auto'>
<TableContainer>
<Table>
<Thead>
<Tr>
<Th>
<Trans i18nKey={'organization.fees.tx_type'}>Tx Type</Trans>
</Th>
<Th>
<Trans i18nKey={'organization.transfers.block'}>Block</Trans>
</Th>
<Th>
<Trans i18nKey={'organization.fees.cost'}>Cost</Trans>
</Th>
</Tr>
</Thead>
<Tbody>
{data.fees.map((fee, i) => (
<Tr key={i}>
<Td>
<Flex direction={'column'} align={'start'} gap={3}>
<TransactionTypeBadge transactionType={fee.txType as TransactionType} />
<Text fontWeight={100} color={'lighterText'} fontSize={'sm'}>
{formatDistance(new Date(fee.timestamp), new Date())}
</Text>
</Flex>
</Td>
<Td>
<Link
as={RouterLink}
to={generatePath(RoutePath.Block, { height: fee.height.toString(), page: null })}
>
{fee.height}
</Link>
</Td>
<Td>{fee.cost}</Td>
</Tr>
))}
</Tbody>
</Table>
</TableContainer>
</Box>
<Box pt={4}>
<Pagination />
</Box>
</>
)
}

export default AccountFees
Loading

2 comments on commit 9652ed6

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.