Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Display the votes on the network proposal details page #1214

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .changelog/1214.feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Display the votes on the network proposal details page
12 changes: 9 additions & 3 deletions src/app/components/Account/AccountLink.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { FC } from 'react'
import { FC, ReactNode } from 'react'
import { Link as RouterLink } from 'react-router-dom'
import { useScreenSize } from '../../hooks/useScreensize'
import Link from '@mui/material/Link'
Expand All @@ -10,11 +10,17 @@ import { SearchScope } from '../../../types/searchScope'
export const AccountLink: FC<{
scope: SearchScope
address: string
title?: ReactNode
alwaysTrim?: boolean
}> = ({ scope, address, alwaysTrim }) => {
}> = ({ scope, address, title, alwaysTrim }) => {
const { isTablet } = useScreenSize()
const to = RouteUtils.getAccountRoute(scope, address)
return (

return title ? (
<Link component={RouterLink} to={to}>
{title}
</Link>
) : (
<Typography variant="mono" component="span">
{alwaysTrim || isTablet ? (
<TrimLinkLabel label={address} to={to} />
Expand Down
77 changes: 77 additions & 0 deletions src/app/components/Proposals/ProposalVoteIndicator.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { FC } from 'react'
import { TFunction } from 'i18next'
import { useTranslation } from 'react-i18next'
import Box from '@mui/material/Box'
import CheckCircleIcon from '@mui/icons-material/CheckCircle'
import CancelIcon from '@mui/icons-material/Cancel'
import RemoveCircleIcon from '@mui/icons-material/RemoveCircle'
import { styled } from '@mui/material/styles'
import { COLORS } from '../../../styles/theme/colors'
import { ProposalVoteValue } from '../../../types/vote'

const StyledBox = styled(Box)(({ theme }) => ({
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'space-between',
gap: 3,
flex: 1,
borderRadius: 10,
padding: theme.spacing(2, 2, 2, '10px'),
fontSize: '12px',
minWidth: '85px',
}))

const StyledIcon = styled(Box)({
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
fontSize: '18px',
})

const getStatuses = (t: TFunction) => ({
[ProposalVoteValue.abstain]: {
backgroundColor: COLORS.lightSilver,
icon: RemoveCircleIcon,
iconColor: COLORS.grayMedium,
label: t('networkProposal.vote.abstain'),
textColor: COLORS.grayExtraDark,
},
[ProposalVoteValue.yes]: {
backgroundColor: COLORS.honeydew,
icon: CheckCircleIcon,
iconColor: COLORS.eucalyptus,
label: t('networkProposal.vote.yes'),
textColor: COLORS.grayExtraDark,
},
[ProposalVoteValue.no]: {
backgroundColor: COLORS.linen,
icon: CancelIcon,
iconColor: COLORS.errorIndicatorBackground,
label: t('networkProposal.vote.no'),
textColor: COLORS.grayExtraDark,
},
})

type ProposalVoteIndicatorProps = {
vote: ProposalVoteValue
}

export const ProposalVoteIndicator: FC<ProposalVoteIndicatorProps> = ({ vote }) => {
const { t } = useTranslation()

if (!ProposalVoteValue[vote]) {
return null
}

const statusConfig = getStatuses(t)[vote]
const IconComponent = statusConfig.icon

return (
<StyledBox sx={{ backgroundColor: statusConfig.backgroundColor, color: statusConfig.textColor }}>
{statusConfig.label}
<StyledIcon sx={{ color: statusConfig.iconColor }}>
<IconComponent color="inherit" fontSize="inherit" />
</StyledIcon>
</StyledBox>
)
}
64 changes: 64 additions & 0 deletions src/app/components/Proposals/VoteTypePills.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { FC } from 'react'
import { useTranslation } from 'react-i18next'
import Box from '@mui/material/Box'
import Chip from '@mui/material/Chip'
import Typography from '@mui/material/Typography'
import { COLORS } from '../../../styles/theme/colors'
import { ProposalVoteValue, VoteType } from '../../../types/vote'

type VoteTypePillsProps = {
handleChange: (voteType: VoteType) => void
value?: VoteType
}

export const VoteTypePills: FC<VoteTypePillsProps> = ({ handleChange, value }) => {
const { t } = useTranslation()
const options: { label: string; value: VoteType }[] = [
{
label: t('networkProposal.vote.all'),
value: 'any',
},
{
label: t('networkProposal.vote.yes'),
value: ProposalVoteValue.yes,
},
{
label: t('networkProposal.vote.abstain'),
value: ProposalVoteValue.abstain,
},
{
label: t('networkProposal.vote.no'),
value: ProposalVoteValue.no,
},
]

return (
<>
{options.map(option => {
const selected = option.value === value
return (
<Chip
key={option.value}
onClick={() => handleChange(option.value)}
clickable
color="secondary"
label={
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<Typography component="span" sx={{ fontSize: 16 }}>
{option.label}
</Typography>
</Box>
}
sx={{
mr: 3,
borderColor: COLORS.brandMedium,
backgroundColor: selected ? COLORS.brandMedium : COLORS.brandMedium15,
color: selected ? COLORS.white : COLORS.grayExtraDark,
}}
variant={selected ? 'outlined-selected' : 'outlined'}
/>
)
})}
</>
)
}
70 changes: 70 additions & 0 deletions src/app/components/Proposals/VoterSearchBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { FC } from 'react'
import TextField from '@mui/material/TextField'
import SearchIcon from '@mui/icons-material/Search'
import HighlightOffIcon from '@mui/icons-material/HighlightOff'
import InputAdornment from '@mui/material/InputAdornment'
import { COLORS } from '../../../styles/theme/colors'
import { SearchVariant } from '../Search'
import { useTranslation } from 'react-i18next'
import IconButton from '@mui/material/IconButton'

export interface SearchBarProps {
variant: SearchVariant
value: string
onChange: (value: string) => void
}

export const VoterSearchBar: FC<SearchBarProps> = ({ variant, value, onChange }) => {
const { t } = useTranslation()
const startAdornment = variant === 'button' && (
<InputAdornment
position="start"
disablePointerEvents // Pass clicks through, so it focuses the input
>
<SearchIcon sx={{ color: COLORS.grayDark }} />
</InputAdornment>
)

const onClearValue = () => onChange('')

const endAdornment = (
<InputAdornment position="end">
<>
{value && (
<IconButton color="inherit" onClick={onClearValue}>
<HighlightOffIcon />
</IconButton>
)}
</>
</InputAdornment>
)

return (
<>
<TextField
value={value}
onChange={e => onChange(e.target.value)}
InputProps={{
inputProps: {
sx: {
p: 0,
marginRight: 2,
},
},
startAdornment,
endAdornment,
}}
placeholder={t('networkProposal.searchForVoters')}
// FormHelperTextProps={{
// component: 'div', // replace p with div tag
// sx: {
// marginTop: 0,
// marginBottom: 0,
// marginLeft: variant === 'button' ? '48px' : '17px',
// marginRight: variant === 'button' ? '48px' : '17px',
// },
// }}
/>
</>
)
}
47 changes: 42 additions & 5 deletions src/app/components/Table/PaginationEngine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,52 @@ export interface SimplePaginationEngine {
linkToPage: (pageNumber: number) => To
}

/**
* The data returned by a comprehensive pagination engine to the data consumer component
*/
export interface PaginatedResults<Item> {
/**
* Control interface that can be plugged to a Table's `pagination` prop
*/
tablePaginationProps: TablePaginationProps

/**
* The data provided to the data consumer in the current window
*/
data: Item[] | undefined

/**
* Is the data set still loading from the server?
*/
isLoading: boolean
}

/**
* A Comprehensive PaginationEngine sits between the server and the consumer of the data and does transformations
*
* Specifically, the interface for loading the data and the one for the data consumers are decoupled.
*/
export interface ComprehensivePaginationEngine<Item, QueryResult extends List> {
selectedPage: number
offsetForQuery: number
limitForQuery: number
paramsForQuery: { offset: number; limit: number }
getResults: (queryResult: QueryResult | undefined, key?: keyof QueryResult) => PaginatedResults<Item>
/**
* The currently selected page from the data consumer's POV
*/
selectedPageForClient: number

/**
* Parameters for data to be loaded from the server
*/
paramsForServer: { offset: number; limit: number }

/**
* Get the current data/state info for the data consumer component.
*
* @param isLoading Is the data still being loaded from the server?
* @param queryResult the data coming in the server, requested according to this engine's specs, including metadata
* @param key The field where the actual records can be found within queryResults
*/
getResults: (
isLoading: boolean,
queryResult: QueryResult | undefined,
key?: keyof QueryResult,
) => PaginatedResults<Item>
}
Loading
Loading