@chakra-ui/react#CloseButton JavaScript Examples
The following examples show how to use
@chakra-ui/react#CloseButton.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: sidebar.js From idena-web with MIT License | 6 votes |
function Sidebar({isOpen, onClose}) {
return (
<Flex
backgroundColor="gray.500"
color="white"
height="100vh"
overflowY="auto"
width={['100%', 200]}
px={4}
py={[4, 2]}
zIndex={[8, 2]}
position={['fixed', 'relative']}
direction="column"
display={[isOpen ? 'flex' : 'none', 'flex']}
>
<Flex justifyContent="space-between" alignItems="center">
<ApiStatus position="relative" />
<CloseButton
onClick={onClose}
boxSize={4}
visibility={['visible', 'hidden']}
/>
</Flex>
<Flex direction="column" px={[6, 0]}>
<Logo />
<Nav onClose={onClose} />
<ActionPanel onClose={onClose} />
</Flex>
</Flex>
)
}
Example #2
Source File: components.js From idena-web with MIT License | 5 votes |
export function PageCloseButton({href, ...props}) {
return (
<NextLink href={href}>
<CloseButton {...props} />
</NextLink>
)
}
Example #3
Source File: components.js From idena-web with MIT License | 5 votes |
export function FlipPageTitle({onClose, ...props}) {
return (
<Flex align="center" alignSelf="stretch" justify="space-between" my={6}>
<PageTitle mb={0} {...props} />
<CloseButton onClick={onClose} />
</Flex>
)
}
Example #4
Source File: NotificationsBadge.js From web-client with Apache License 2.0 | 5 votes |
NotificationsBadge = () => {
const [notifications, fetchNotifications] = useFetch('/notifications?status=unread');
const onMessageHandler = () => {
fetchNotifications();
}
useWebsocketMessage(onMessageHandler);
const markAsRead = notification => {
secureApiFetch(`/notifications/${notification.id}`, {
method: 'PUT',
body: JSON.stringify({ status: 'read' })
}).then(() => {
fetchNotifications();
})
}
return <Popover placement="bottom-end" closeOnBlur={true}>
<PopoverTrigger>
<Button pr={null !== notifications && notifications.length > 0 ? 1 : 2} variant="ghost" aria-label="Notifications" >
<BellIcon fontSize="xl" color="gray.500" />
{null !== notifications && notifications.length > 0 && (
<Tag colorScheme='red' >{notifications.length}</Tag>
)}
</Button>
</PopoverTrigger>
<PopoverContent>
<PopoverArrow />
<PopoverHeader px="3" pb="3" color="gray.500">
<Link to="/notifications">Notifications</Link>
</PopoverHeader>
<PopoverBody>
{null !== notifications && notifications.length > 0 ? (
<Stack>
{notifications.map(notification =>
<Alert key={notification.id} status='info' variant="top-accent">
<Box flex='1'>
<AlertTitle>{notification.time} <strong><Link to="/vulnerabilities">{notification.title}</Link></strong></AlertTitle>
<AlertDescription display='block'>{notification.content}</AlertDescription>
</Box>
<CloseButton position='absolute' right='8px' top='8px' onClick={() => markAsRead(notification)} />
</Alert>
)}
</Stack>
) : <span>Nothing to see here.</span>}
</PopoverBody>
</PopoverContent>
</Popover>
}
Example #5
Source File: view.js From idena-web with MIT License | 4 votes |
export default function ViewFlipPage() {
const {t, i18n} = useTranslation()
const router = useRouter()
const {id} = router.query
const {
isOpen: isOpenDeleteForm,
onOpen: openDeleteForm,
onClose: onCloseDeleteForm,
} = useDisclosure()
const toast = useToast()
const [{flips: knownFlips}] = useIdentity()
const [current, send] = useMachine(createViewFlipMachine(), {
context: {
locale: 'en',
},
services: {
// eslint-disable-next-line no-shadow
loadFlip: async ({id}) => db.table('ownFlips').get(id),
},
actions: {
onDeleted: () => router.push('/flips/list'),
onDeleteFailed: ({error}) =>
toast({
// eslint-disable-next-line react/display-name
render: () => <Toast title={error} status="error" />,
}),
},
logger: msg => console.log(redact(msg)),
})
useEffect(() => {
if (id) {
send('LOAD', {id})
}
}, [id, send])
const {
hash,
keywords,
images,
originalOrder,
order,
showTranslation,
type,
} = current.context
if (!id) return null
return (
<Layout showHamburger={false}>
<Page px={0} py={0}>
<Flex
direction="column"
flex={1}
alignSelf="stretch"
px={20}
overflowY="auto"
>
<Flex
align="center"
alignSelf="stretch"
justify="space-between"
my={6}
mb={0}
>
<PageTitle mb={0} pb={0}>
{t('View flip')}
</PageTitle>
<CloseButton onClick={() => router.push('/flips/list')} />
</Flex>
{current.matches('loaded') && (
<FlipMaster>
<FlipStepBody minH="180px" my="auto">
<Stack isInline spacing={10}>
<FlipKeywordPanel w={rem(320)}>
{keywords.words.length ? (
<FlipKeywordTranslationSwitch
keywords={keywords}
showTranslation={showTranslation}
locale={i18n.language}
isInline={false}
onSwitchLocale={() => send('SWITCH_LOCALE')}
/>
) : (
<FlipKeyword>
<FlipKeywordName>
{t('Missing keywords')}
</FlipKeywordName>
</FlipKeyword>
)}
</FlipKeywordPanel>
<Stack isInline spacing={10} justify="center">
<FlipImageList>
{originalOrder.map((num, idx) => (
<FlipImageListItem
key={num}
src={images[num]}
isFirst={idx === 0}
isLast={idx === images.length - 1}
width={130}
/>
))}
</FlipImageList>
<FlipImageList>
{order.map((num, idx) => (
<FlipImageListItem
key={num}
src={images[num]}
isFirst={idx === 0}
isLast={idx === images.length - 1}
width={130}
/>
))}
</FlipImageList>
</Stack>
</Stack>
</FlipStepBody>
</FlipMaster>
)}
</Flex>
{type !== FlipType.Archived && (
<FlipMasterFooter>
<FlipCardMenu>
<FlipCardMenuItem
onClick={() => {
if ((knownFlips || []).includes(hash)) openDeleteForm()
else send('ARCHIVE')
}}
>
<DeleteIcon size={5} mr={2} color="red.500" />
{t('Delete flip')}
</FlipCardMenuItem>
</FlipCardMenu>
</FlipMasterFooter>
)}
{current.matches('loaded') && (
<DeleteFlipDrawer
hash={hash}
cover={images[originalOrder[0]]}
isOpen={isOpenDeleteForm}
onClose={onCloseDeleteForm}
onDelete={() => {
send('DELETE')
onCloseDeleteForm()
}}
/>
)}
</Page>
</Layout>
)
}
Example #6
Source File: get-invitation.js From idena-web with MIT License | 4 votes |
export default function GetInvitation() {
const router = useRouter()
const {t} = useTranslation()
const [nickname, setNickname] = useState('')
const failToast = useFailToast()
const successToast = useSuccessToast()
const [code, setCode] = useState()
const [isWaiting, setIsWaiting] = useState(false)
const size = useBreakpointValue(['lg', 'md'])
const invitationCodeRef = useRef()
const {scrollTo: scrollToCode} = useScroll(invitationCodeRef)
const getCode = async () => {
setIsWaiting(true)
const name = nickname.startsWith('@') ? nickname.substring(1) : nickname
try {
const {invitation} = await getInvitationCode(name, cookie.get('refId'))
setCode(invitation)
successToast(t('Your invitation code has been generated successfully!'))
scrollToCode()
} catch (e) {
failToast(e.message)
} finally {
setIsWaiting(false)
}
}
return (
<Layout showHamburger={false}>
<Page>
<Flex
align="center"
alignSelf="stretch"
justify={['center', 'space-between']}
mb={[8, 2]}
w={['100%', null]}
>
<PageTitle
fontSize={['base', 'xl']}
fontWeight={['bold', 500]}
mb={0}
>
{t('How to get an invitation')}
</PageTitle>
<CloseButton
color={['blue.500', 'inherit']}
size="lg"
position={['absolute', 'inherit']}
right={2}
onClick={() => router.push('/home')}
/>
</Flex>
<Flex
direction="column"
maxW="480px"
w={['100%', null]}
fontSize={['mdx', 'md']}
>
<Text>
{t(
'To minimize the probability of a Sybil attack, the pace of network growth is restricted: Idena network participation is invitation-based. New invitations can be sent out only by validated users. The number of invitations is limited and increases as the network grows.',
{nsSeparator: '|'}
)}
</Text>
<Text mt={2}>
{t(
'Please choose the platform where you have the most active account:',
{nsSeparator: '|'}
)}
</Text>
<Tabs variant="unstyled" mt={8}>
<TabList bg={['gray.50', 'white']} p={[1, 0]} borderRadius="md">
<GetInvitationTab
iconSelected={<TelegramInvertedIcon />}
icon={<TelegramIcon />}
title="Telegram"
/>
<GetInvitationTab
iconSelected={<DiscordInvertedIcon />}
icon={<DiscordIcon />}
title="Discord"
/>
<GetInvitationTab
iconSelected={<TwitterIcon />}
icon={<TwitterIcon />}
title="Twitter"
/>
<GetInvitationTab
iconSelected={<RedditInvertedIcon />}
icon={<RedditIcon />}
title="Reddit"
/>
</TabList>
<TabPanels>
<GetInvitationTabPanel>
<GetInvitationTabTitle>Telegram</GetInvitationTabTitle>
<Text>
<Trans t={t} i18nKey="joinIdenaTelegram">
Join the official{' '}
<Link
href="https://t.me/IdenaNetworkPublic"
target="_blank"
color="blue.500"
>
Idena Telegram group
</Link>{' '}
and request an invitation code from the community.
</Trans>
</Text>
</GetInvitationTabPanel>
<GetInvitationTabPanel>
<GetInvitationTabTitle>Discord</GetInvitationTabTitle>
<Text>
<Trans t={t} i18nKey="joinIdenaDiscord">
Join{' '}
<Link
href="https://discord.gg/8BusRj7"
target="_blank"
color="blue.500"
>
Idena Community Discord
</Link>{' '}
and request an invitation code from the community in
#invite-requests channel.
</Trans>
</Text>
</GetInvitationTabPanel>
<GetInvitationTabPanel>
<GetInvitationTabTitle>Twitter</GetInvitationTabTitle>
<OrderedList spacing={2}>
<ListItem>
{t('Follow')}{' '}
<Link
href="https://twitter.com/IdenaNetwork"
target="_blank"
color="blue.500"
>
@IdenaNetwork
</Link>{' '}
</ListItem>
<ListItem>
<Stack spacing={4}>
<Text>
<Trans t={t} i18nKey="joinIdenaTwitterSendTweet">
<Link
target="_blank"
color="blue.500"
rel="noreferrer"
href="https://twitter.com/intent/tweet?text=I%20want%20to%20join%20%40IdenaNetwork%20to%20become%20a%20validator%20of%20the%20first%20Proof-of-Person%20blockchain%20%23IdenaInvite%0A%0Ahttps://www.idena.io"
>
Send a tweet
</Link>{' '}
with a hashtag #IdenaInvite from your account. To get
an invite, your account should be older than 1 year or
older than two months and have at least 50 followers.
The tweet should say:
</Trans>
</Text>
<Flex mt={4} p={[7, 10]} borderRadius="md" bg="gray.50">
<Flex direction={['column', 'row']}>
<Stack>
<Text color="gray.500">
I want to join @IdenaNetwork to become a validator
of the first Proof-of-Person blockchain
#IdenaInvite
</Text>
<Text>
<Link
href="https://www.idena.io"
target="_blank"
color="blue.500"
>
https://www.idena.io
</Link>
</Text>
</Stack>
<GetInvitationCopyButton
ml={[0, 10]}
value={
'I want to join @IdenaNetwork to become a validator of the first Proof-of-Person blockchain #IdenaInvite\n' +
'\n' +
'https://www.idena.io'
}
/>
</Flex>
</Flex>
</Stack>
</ListItem>
<ListItem>
<Stack spacing={4} pt={2}>
<Text>
<Trans t={t} i18nKey="joinIdenaTwitterGetCode">
Enter your twitter name and click{' '}
<i>Get an invitation code</i> button. The code will be
shown automatically.
</Trans>
</Text>
<Stack>
<Text color="gray.500" fontWeight="500">
{t('Your nickname')}
</Text>
<GetInvitationTwitterInput
value={nickname}
onChange={value => setNickname(value)}
/>
</Stack>
{code ? (
<Flex
boxShadow="0 3px 12px 0 rgba(83, 86, 92, 0.1), 0 2px 3px 0 rgba(83, 86, 92, 0.2)"
px={10}
py={8}
borderRadius="lg"
position="relative"
>
<Flex
direction={['column', 'row']}
justifyContent="space-between"
w="100%"
>
<Stack spacing={0}>
<Text color="muted">{t('Invitation code')}</Text>
<Text
color="gray.500"
fontWeight={500}
wordBreak="break-all"
>
{code}
</Text>
</Stack>
<GetInvitationCopyButton
value={code}
ml={[0, 10]}
/>
</Flex>
</Flex>
) : (
<Flex>
<PrimaryButton
ml="auto"
onClick={getCode}
isLoading={isWaiting}
loadingText=""
w={['100%', 'auto']}
size={size}
>
{t('Get an invitation code')}
</PrimaryButton>
</Flex>
)}
<Box ref={invitationCodeRef}></Box>
</Stack>
</ListItem>
</OrderedList>
</GetInvitationTabPanel>
<GetInvitationTabPanel>
<GetInvitationTabTitle>Reddit</GetInvitationTabTitle>
<Text color="gray.500">
<Trans t={t} i18nKey="joinIdenaReddit">
Join{' '}
<Link
href="https://www.reddit.com/r/Idena/"
target="_blank"
color="blue.500"
>
Idena subreddit
</Link>{' '}
and request an invitation code from the community.
</Trans>
</Text>
</GetInvitationTabPanel>
</TabPanels>
</Tabs>
</Flex>
</Page>
</Layout>
)
}
Example #7
Source File: rent.js From idena-web with MIT License | 4 votes |
export default function Rent() {
const router = useRouter()
const {t} = useTranslation()
const {coinbase} = useAuthState()
const buySharedNodeDisclosure = useDisclosure()
const [state, setState] = useState(0)
const [checkedProviders, setCheckedProviders] = useState([])
const {
data: indexerData,
isFetched: indexerIsFetched,
isLoading: indexerIsLoading,
} = useQuery(['last-block'], () => getLastBlock(), {
retry: false,
refetchOnWindowFocus: false,
})
const {data: identity, isLoading: identityIsLoading} = useQuery(
['fetch-identity', coinbase],
() => fetchIdentity(coinbase, true),
{
enabled: !!coinbase,
refetchOnWindowFocus: false,
}
)
const {data: providers, isLoading: providersIsLoading} = useQuery(
['providers'],
getProviders,
{
initialData: [],
enabled: !!indexerIsFetched,
refetchOnWindowFocus: false,
}
)
const indexerLastBlock = indexerData?.height || 0
useEffect(() => {
async function updateStatus() {
const shuffled = shuffle(providers.filter(x => Boolean(x.slots)))
shuffled.forEach(provider => {
checkProviderSyncing(provider.data.url)
.then(response =>
setCheckedProviders(prev => {
const blocksLeft = indexerLastBlock - response?.currentBlock
return mergeProviders(prev, {
...provider,
duration: response.duration,
blocksLeft,
status:
blocksLeft > SYNCING_DIFF
? ProviderStatus.OutOfSync
: ProviderStatus.Success,
})
})
)
.catch(() =>
setCheckedProviders(prev =>
mergeProviders(prev, {
...provider,
duration: MAX_DURATION,
status: ProviderStatus.Error,
})
)
)
})
}
if (providers.length) updateStatus()
}, [indexerLastBlock, providers])
const isLoading = indexerIsLoading || identityIsLoading || providersIsLoading
const isDesktop = useIsDesktop()
const {
isOpen: isOpenRentDetailDrawer,
onOpen: onOpenRentDetailDrawer,
onClose: onCloseRentDetailDrawer,
} = useDisclosure()
const selectedProvider = checkedProviders.length && checkedProviders[state]
return (
<Layout canRedirect={false}>
<Page mb={14} pt={[4, 6]}>
<Box w="full">
<Flex align="center" justify="space-between">
<AngleArrowBackIcon
stroke="#578FFF"
display={['block', 'none']}
position="absolute"
left={4}
top={4}
h="28px"
w="28px"
onClick={() => {
router.back()
}}
/>
<PageTitleNew>{t('Rent a shared node')}</PageTitleNew>
<CloseButton
display={['none', 'flex']}
onClick={() => router.back()}
/>
</Flex>
<Box>
<Table>
<Thead display={['none', 'table-header-group']}>
<Tr>
<RoundedTh isLeft width={rem(40)}></RoundedTh>
<RoundedTh>{t('Node URL')}</RoundedTh>
<RoundedTh>{t('Owner')}</RoundedTh>
<RoundedTh>{t('Location')}</RoundedTh>
<RoundedTh>{t('Latency, sec')}</RoundedTh>
<RoundedTh textAlign="right">
{t('Slots available')}
</RoundedTh>
<RoundedTh isRight textAlign="right">
{t('Price per validation')}
</RoundedTh>
</Tr>
</Thead>
<Tbody>
{isLoading
? new Array(10).fill(0).map((_, idx) => (
<Tr key={idx}>
<Td colSpan={7} px={0}>
<Skeleton h={[32, 8]} />
</Td>
</Tr>
))
: checkedProviders.map((p, idx) => (
<Tr key={idx}>
<Td display={['none', 'table-cell']}>
<Radio
isChecked={state === idx}
onClick={() => setState(idx)}
borderColor="#d2d4d9"
/>
</Td>
<Td
borderBottom={['solid 0px', 'solid 1px #e8eaed']}
px={[0, 3]}
py={[1, 2]}
>
<Flex
direction="column"
border={['solid 1px', 'initial']}
borderColor={['gray.100', 'inherit']}
borderRadius={['8px', 0]}
p={[4, 0]}
onClick={() => {
setState(idx)
if (!isDesktop) onOpenRentDetailDrawer()
}}
>
<Flex justifyContent="flex-start">
<Flex
direction="column"
maxW={['100%', 'initial']}
>
<Text
fontSize={['mdx', 'md']}
fontWeight={[500, 400]}
isTruncated
>
{p.data.url}
</Text>
<ProviderStatusLabel
status={p.status}
blocksLeft={p.blocksLeft}
></ProviderStatusLabel>
</Flex>
<Flex display="none">
<Text
fontSize="mdx"
fontWeight={500}
color="gray.064"
>
FILL_RATING
</Text>
<SoftStarIcon mt="3px" ml="3px" h={4} w={4} />
</Flex>
</Flex>
<Flex
display={['flex', 'none']}
justifyContent="flex-start"
>
<Flex
direction="column"
fontSize="base"
color="gray.064"
mt={4}
w="50%"
>
<Text>{t('Latency, sec')}</Text>
<Flex>
<Text color="gray.500" mr={1}>
{p.status === ProviderStatus.Error
? '—'
: (p.duration / 1000).toFixed(3)}
</Text>
<Text display="none">/ FILL_SLOTS</Text>
</Flex>
</Flex>
<Flex
direction="column"
fontSize="base"
color="gray.064"
mt={4}
w="50%"
>
<Text>Price</Text>
<Flex>
<Text color="gray.500">
{GetProviderPrice(
p.data,
identity?.state,
identity?.age
)}{' '}
iDNA
</Text>
</Flex>
</Flex>
</Flex>
</Flex>
</Td>
<Td display={['none', 'table-cell']}>
<Link
target="_blank"
rel="noreferrer"
color="brandBlue.100"
href={`https://t.me/${p.data.ownerName}`}
>
{p.data.ownerName}
</Link>
</Td>
<Td display={['none', 'table-cell']}>
{p.data.location}
</Td>
<Td display={['none', 'table-cell']}>
{p.status === ProviderStatus.Error
? '—'
: (p.duration / 1000).toFixed(3)}
</Td>
<Td display={['none', 'table-cell']} textAlign="right">
{p.slots}
</Td>
<Td display={['none', 'table-cell']} textAlign="right">
{GetProviderPrice(
p.data,
identity?.state,
identity?.age
)}{' '}
iDNA
</Td>
</Tr>
))}
</Tbody>
</Table>
</Box>
</Box>
<Stack
display={['none', 'flex']}
isInline
spacing={2}
justify="flex-end"
bg="white"
borderTop="1px"
borderTopColor="gray.100"
px={4}
py={3}
h={14}
position="fixed"
bottom={0}
left={0}
right={0}
>
<SecondaryButton onClick={router.back}>Cancel</SecondaryButton>
<PrimaryButton onClick={buySharedNodeDisclosure.onOpen}>
{t('Continue')}
</PrimaryButton>
</Stack>
</Page>
{selectedProvider && identity && (
<ProviderInfoDrawer
p={selectedProvider}
identity={identity}
isOpen={isOpenRentDetailDrawer}
onClose={onCloseRentDetailDrawer}
onSubmit={buySharedNodeDisclosure.onOpen}
/>
)}
<BuySharedNodeForm
{...buySharedNodeDisclosure}
providerId={selectedProvider && selectedProvider.id}
url={selectedProvider && selectedProvider.data.url}
from={coinbase}
amount={
selectedProvider &&
GetProviderPrice(
selectedProvider.data,
identity?.state,
identity?.age
)
}
to={selectedProvider && selectedProvider.data.address}
/>
</Layout>
)
}
Example #8
Source File: new.js From idena-web with MIT License | 4 votes |
function NewVotingPage() {
const {t, i18n} = useTranslation()
const router = useRouter()
const toast = useToast()
const {isOpen: isOpenAdvanced, onToggle: onToggleAdvanced} = useDisclosure()
const epochData = useEpoch()
const {coinbase, privateKey} = useAuthState()
const {
data: {balance},
} = useBalance()
const [current, send, service] = useMachine(newVotingMachine, {
actions: {
onDone: () => {
router.push(viewVotingHref(current.context.contractHash))
},
onError: (context, {data: {message}}) => {
toast({
// eslint-disable-next-line react/display-name
render: () => (
<Toast title={humanError(message, context)} status="error" />
),
})
},
onInvalidForm: () => {
toast({
// eslint-disable-next-line react/display-name
render: () => (
<Toast title={t('Please correct form fields')} status="error" />
),
})
},
},
})
React.useEffect(() => {
if (epochData && coinbase) send('START', {epoch: epochData.epoch, coinbase})
}, [coinbase, epochData, privateKey, send])
const {
options,
startDate,
votingDuration,
publicVotingDuration,
shouldStartImmediately,
isFreeVoting,
committeeSize,
quorum = 1,
winnerThreshold = '66',
feePerGas,
oracleReward,
isWholeNetwork,
oracleRewardsEstimates,
ownerFee = 0,
minOracleReward,
votingMinPayment,
dirtyBag,
} = current.context
const isInvalid = (field, cond = current.context[field]) =>
dirtyBag[field] && !cond
const isInvalidOptions = isInvalid('options', hasValuableOptions(options))
const hasLinksInOptions = isInvalid('options', hasLinklessOptions(options))
const handleChange = ({target: {id, value}}) => send('CHANGE', {id, value})
const dna = toLocaleDna(i18n)
return (
<Layout showHamburger={false}>
<Page px={0} py={0}>
<Box px={20} py={6} w="full" overflowY="auto">
<Flex justify="space-between" align="center">
<PageTitle mb={0}>{t('New voting')}</PageTitle>
<CloseButton
ml="auto"
onClick={() => router.push('/oracles/list')}
/>
</Flex>
<SuccessAlert my={8}>
{t(
'After publishing or launching, you will not be able to edit the voting parameters.'
)}
</SuccessAlert>
{current.matches('preload.late') && <NewVotingFormSkeleton />}
{!current.matches('preload') && (
<Stack spacing={3}>
<VotingInlineFormControl
htmlFor="title"
label={t('Title')}
isInvalid={isInvalid('title')}
>
<Input id="title" onChange={handleChange} />
{isInvalid('title') && (
<FormErrorMessage fontSize="md" mt={1}>
{t('You must provide title')}
</FormErrorMessage>
)}
</VotingInlineFormControl>
<VotingInlineFormControl
htmlFor="desc"
label={t('Description')}
isInvalid={isInvalid('desc')}
>
<Textarea id="desc" w="md" h={32} onChange={handleChange} />
{isInvalid('desc') && (
<FormErrorMessage fontSize="md" mt={1}>
{t('You must provide description')}
</FormErrorMessage>
)}
</VotingInlineFormControl>
<VotingInlineFormControl
label={t('Voting options')}
isInvalid={isInvalidOptions || hasLinksInOptions}
>
<Box
borderWidth={
isInvalidOptions || hasLinksInOptions ? '2px' : 1
}
borderColor={
isInvalidOptions || hasLinksInOptions
? 'red.500'
: 'gray.100'
}
borderRadius="md"
p={1}
w="md"
>
{options.map(({id, value}, idx) => (
<VotingOptionInput
key={id}
value={value}
placeholder={`${t('Option')} ${idx + 1}...`}
isLast={idx === options.length - 1}
isDisabled={[0, 1].includes(idx)}
onChange={({target}) => {
send('SET_OPTIONS', {id, value: target.value})
}}
onAddOption={() => {
send('ADD_OPTION')
}}
onRemoveOption={() => {
send('REMOVE_OPTION', {id})
}}
_invalid={null}
/>
))}
</Box>
{isInvalidOptions && (
<FormErrorMessage fontSize="md" mt={1}>
{t('You must provide at least 2 options')}
</FormErrorMessage>
)}
{hasLinksInOptions && (
<FormErrorMessage fontSize="md" mt={1}>
{t(
'Links are not allowed in voting options. Please use Description for links.'
)}
</FormErrorMessage>
)}
</VotingInlineFormControl>
<VotingInlineFormControl
htmlFor="startDate"
label={t('Start date')}
isDisabled={shouldStartImmediately}
isInvalid={isInvalid(
'startDate',
startDate || shouldStartImmediately
)}
mt={4}
>
<Stack spacing={3} flex={1}>
<Input
id="startDate"
type="datetime-local"
onChange={handleChange}
/>
{isInvalid(
'startDate',
startDate || shouldStartImmediately
) && (
<FormErrorMessage fontSize="md" mt={-2}>
{t('You must either choose start date or start now')}
</FormErrorMessage>
)}
<Checkbox
id="shouldStartImmediately"
isChecked={shouldStartImmediately}
onChange={({target: {id, checked}}) => {
send('CHANGE', {id, value: checked})
}}
>
{t('Start now')}
</Checkbox>
</Stack>
</VotingInlineFormControl>
<VotingDurationInput
id="votingDuration"
label={t('Voting duration')}
value={votingDuration}
tooltip={t('Secret voting period')}
presets={[
durationPreset({hours: 12}),
durationPreset({days: 1}),
durationPreset({days: 2}),
durationPreset({days: 5}),
durationPreset({weeks: 1}),
]}
service={service}
mt={2}
/>
<NewVotingFormSubtitle>
{t('Oracles requirements')}
</NewVotingFormSubtitle>
<VotingInlineFormControl
htmlFor="committeeSize"
label={t('Committee size, oracles')}
isInvalid={committeeSize < 1}
tooltip={t(
'The number of randomly selected oracles allowed to vote'
)}
mt={2}
>
<Stack spacing={3} flex={1}>
<NumberInput
id="committeeSize"
value={committeeSize}
min={1}
step={1}
preventInvalidInput
isDisabled={isWholeNetwork}
onChange={({target: {id, value}}) => {
send('CHANGE_COMMITTEE', {id, value})
}}
/>
<Checkbox
id="isWholeNetwork"
onChange={({target: {checked}}) => {
send('SET_WHOLE_NETWORK', {checked})
}}
>
{t('Whole network')}
</Checkbox>
</Stack>
</VotingInlineFormControl>
<VotingInlineFormControl
htmlFor="quorum"
label={t('Quorum')}
tooltip={t(
'The share of Oracle committee sufficient to determine the voting outcome'
)}
mt={2}
>
<Stack spacing={0} flex={1}>
<PercentInput
id="quorum"
value={quorum}
onChange={handleChange}
/>
<NewOracleFormHelperText textAlign="right">
{t('{{count}} votes are required', {
count: quorumVotesCount({quorum, committeeSize}),
})}
</NewOracleFormHelperText>
</Stack>
</VotingInlineFormControl>
<VotingInlineFormControl
htmlFor="votingMinPayment"
label={t('Voting deposit')}
tooltip={t(
'Refunded when voting in majority and lost when voting in minority'
)}
isDisabled={isFreeVoting}
mt={2}
>
<Stack spacing={3} flex={1}>
<DnaInput
id="votingMinPayment"
value={votingMinPayment}
isDisabled={isFreeVoting}
onChange={handleChange}
/>
<Checkbox
id="isFreeVoting"
isChecked={isFreeVoting}
onChange={({target: {id, checked}}) => {
send('CHANGE', {id, value: checked})
}}
>
{t('No voting deposit for oracles')}
</Checkbox>
</Stack>
</VotingInlineFormControl>
<NewVotingFormSubtitle>
{t('Cost of voting')}
</NewVotingFormSubtitle>
<PresetFormControl
label={t('Total funds')}
tooltip={t(
'Total funds locked during the voting and paid to oracles and owner afterwards'
)}
>
<PresetFormControlOptionList
value={String(oracleReward)}
onChange={value => {
send('CHANGE', {
id: 'oracleReward',
value,
})
}}
>
{oracleRewardsEstimates.map(({label, value}) => (
<PresetFormControlOption key={value} value={String(value)}>
{label}
</PresetFormControlOption>
))}
</PresetFormControlOptionList>
<PresetFormControlInputBox>
<DnaInput
id="oracleReward"
value={oracleReward * committeeSize || 0}
min={minOracleReward * committeeSize || 0}
onChange={({target: {id, value}}) => {
send('CHANGE', {
id,
value: (value || 0) / Math.max(1, committeeSize),
})
}}
/>
<NewOracleFormHelperText textAlign="right">
{t('Min reward per oracle: {{amount}}', {
amount: dna(
rewardPerOracle({fundPerOracle: oracleReward, ownerFee})
),
nsSeparator: '!',
})}
</NewOracleFormHelperText>
</PresetFormControlInputBox>
</PresetFormControl>
<VotingInlineFormControl
htmlFor="ownerFee"
label={t('Owner fee')}
tooltip={t('% of the Total funds you receive')}
>
<PercentInput
id="ownerFee"
value={ownerFee}
onChange={handleChange}
/>
<NewOracleFormHelperText textAlign="right">
{t('Paid to owner: {{amount}}', {
amount: dna(
(oracleReward * committeeSize * Math.min(100, ownerFee)) /
100 || 0
),
nsSeparator: '!',
})}
</NewOracleFormHelperText>
</VotingInlineFormControl>
<NewVotingFormSubtitle
cursor="pointer"
onClick={onToggleAdvanced}
>
{t('Advanced settings')}
<ChevronDownIcon
boxSize={5}
color="muted"
ml={1}
transform={isOpenAdvanced ? 'rotate(180deg)' : ''}
transition="all 0.2s ease-in-out"
/>
</NewVotingFormSubtitle>
<Collapse in={isOpenAdvanced} mt={2}>
<Stack spacing={3}>
<VotingDurationInput
id="publicVotingDuration"
value={publicVotingDuration}
label={t('Counting duration')}
tooltip={t(
'Period when secret votes are getting published and results are counted'
)}
presets={[
durationPreset({hours: 12}),
durationPreset({days: 1}),
durationPreset({days: 2}),
durationPreset({days: 5}),
durationPreset({weeks: 1}),
]}
service={service}
/>
<PresetFormControl
label={t('Majority threshold')}
tooltip={t(
'The minimum share of the votes which an option requires to achieve before it becomes the voting outcome'
)}
>
<PresetFormControlOptionList
value={winnerThreshold}
onChange={value => {
send('CHANGE', {
id: 'winnerThreshold',
value,
})
}}
>
<PresetFormControlOption value="51">
{t('Simple majority')}
</PresetFormControlOption>
<PresetFormControlOption value="66">
{t('Super majority')}
</PresetFormControlOption>
<PresetFormControlOption value="100">
{t('N/A (polls)')}
</PresetFormControlOption>
</PresetFormControlOptionList>
<PresetFormControlInputBox>
<PercentInput
id="winnerThreshold"
value={winnerThreshold}
onChange={handleChange}
/>
</PresetFormControlInputBox>
</PresetFormControl>
</Stack>
</Collapse>
</Stack>
)}
</Box>
<Stack
isInline
mt="auto"
alignSelf="stretch"
justify="flex-end"
borderTop="1px"
borderTopColor="gray.100"
py={3}
px={4}
>
<PrimaryButton
isLoading={current.matches('publishing')}
loadingText={t('Publishing')}
onClick={() => send('PUBLISH')}
>
{t('Publish')}
</PrimaryButton>
</Stack>
<ReviewVotingDrawer
isOpen={current.matches('publishing')}
onClose={() => send('CANCEL')}
from={coinbase}
available={balance}
balance={votingMinBalance(oracleReward, committeeSize)}
minStake={votingMinStake(feePerGas)}
votingDuration={votingDuration}
publicVotingDuration={publicVotingDuration}
ownerFee={ownerFee}
isLoading={eitherState(
current,
'publishing.deploy',
`publishing.${VotingStatus.Starting}`
)}
// eslint-disable-next-line no-shadow
onConfirm={({balance, stake}) =>
send('CONFIRM', {privateKey, balance, stake})
}
/>
<NewOraclePresetDialog
isOpen={eitherState(current, 'choosingPreset')}
onChoosePreset={preset => send('CHOOSE_PRESET', {preset})}
onCancel={() => send('CANCEL')}
/>
</Page>
</Layout>
)
}
Example #9
Source File: view.js From idena-web with MIT License | 4 votes |
export default function ViewVotingPage() {
const {t, i18n} = useTranslation()
const [, {addVote}] = useDeferredVotes()
const toast = useToast()
const {
query: {id},
push: redirect,
} = useRouter()
const {epoch} = useEpoch() ?? {epoch: -1}
const {coinbase, privateKey} = useAuthState()
const {
data: {balance: identityBalance},
} = useBalance()
const [current, send, service] = useMachine(viewVotingMachine, {
actions: {
onError: (context, {data: {message}}) => {
toast({
status: 'error',
// eslint-disable-next-line react/display-name
render: () => (
<Toast title={humanError(message, context)} status="error" />
),
})
},
addVote: (_, {data: {vote}}) => addVote(vote),
},
})
React.useEffect(() => {
send('RELOAD', {id, epoch, address: coinbase})
}, [coinbase, epoch, id, send])
const toDna = toLocaleDna(i18n.language)
const {
title,
desc,
contractHash,
status,
balance = 0,
contractBalance = Number(balance),
votingMinPayment = 0,
publicVotingDuration = 0,
quorum = 20,
committeeSize,
options = [],
votes = [],
voteProofsCount,
finishDate,
finishCountingDate,
selectedOption,
winnerThreshold = 50,
balanceUpdates,
ownerFee,
totalReward,
estimatedOracleReward,
estimatedMaxOracleReward = estimatedOracleReward,
isOracle,
minOracleReward,
estimatedTotalReward,
pendingVote,
adCid,
issuer,
} = current.context
const [
{canProlong, canFinish, canTerminate, isFetching: actionsIsFetching},
refetchActions,
] = useOracleActions(id)
const isLoaded = !current.matches('loading')
const sameString = a => b => areSameCaseInsensitive(a, b)
const eitherIdleState = (...states) =>
eitherState(current, ...states.map(s => `idle.${s}`.toLowerCase())) ||
states.some(sameString(status))
const isClosed = eitherIdleState(
VotingStatus.Archived,
VotingStatus.Terminated
)
const didDetermineWinner = hasWinner({
votes,
votesCount: voteProofsCount,
winnerThreshold,
quorum,
committeeSize,
finishCountingDate,
})
const isMaxWinnerThreshold = winnerThreshold === 100
const accountableVoteCount = sumAccountableVotes(votes)
const {data: ad} = useIpfsAd(adCid)
const adPreviewDisclosure = useDisclosure()
const isValidAdVoting = React.useMemo(
() => validateAdVoting({ad, voting: current.context}) === false,
[ad, current.context]
)
const isMaliciousAdVoting = ad && isValidAdVoting
return (
<>
<Layout showHamburger={false}>
<Page pt={8}>
<Stack spacing={10}>
<VotingSkeleton isLoaded={isLoaded} h={6}>
<Stack isInline spacing={2} align="center">
<VotingStatusBadge status={status} fontSize="md">
{t(mapVotingStatus(status))}
</VotingStatusBadge>
<Box
as={VotingBadge}
bg="gray.100"
color="muted"
fontSize="md"
cursor="pointer"
pl="1/2"
transition="color 0.2s ease"
_hover={{
color: 'brandGray.500',
}}
onClick={() => {
openExternalUrl(
`https://scan.idena.io/contract/${contractHash}`
)
}}
>
<Stack isInline spacing={1} align="center">
<Avatar size={5} address={contractHash} />
<Text>{contractHash}</Text>
</Stack>
</Box>
<CloseButton
sx={{
'&': {
marginLeft: 'auto!important',
},
}}
onClick={() => redirect('/oracles/list')}
/>
</Stack>
</VotingSkeleton>
<Stack isInline spacing={10} w="full">
<Box minWidth="lg" maxW="lg">
<Stack spacing={6}>
<VotingSkeleton isLoaded={isLoaded}>
<Stack
spacing={8}
borderRadius="md"
bg="gray.50"
py={8}
px={10}
>
<Stack spacing={4}>
<Heading
overflow="hidden"
fontSize={21}
fontWeight={500}
display="-webkit-box"
sx={{
'&': {
WebkitBoxOrient: 'vertical',
WebkitLineClamp: '2',
},
}}
>
{isMaliciousAdVoting
? t('Please reject malicious ad')
: title}
</Heading>
{ad ? (
<>
{isMaliciousAdVoting ? (
<MaliciousAdOverlay>
<OracleAdDescription ad={ad} />
</MaliciousAdOverlay>
) : (
<OracleAdDescription ad={ad} />
)}
</>
) : (
<Text
isTruncated
lineHeight="tall"
whiteSpace="pre-wrap"
>
<Linkify
onClick={url => {
send('FOLLOW_LINK', {url})
}}
>
{desc}
</Linkify>
</Text>
)}
</Stack>
<Flex>
{adCid && (
<IconButton
icon={<ViewIcon boxSize={4} />}
_hover={{background: 'transparent'}}
onClick={adPreviewDisclosure.onOpen}
>
{t('Preview')}
</IconButton>
)}
<GoogleTranslateButton
phrases={[
title,
desc &&
encodeURIComponent(desc?.replace(/%/g, '%25')),
options.map(({value}) => value).join('\n'),
]}
locale={i18n.language}
alignSelf="start"
/>
</Flex>
<Divider orientation="horizontal" />
{isLoaded && <VotingPhase service={service} />}
</Stack>
</VotingSkeleton>
{eitherIdleState(
VotingStatus.Pending,
VotingStatus.Starting,
VotingStatus.Open,
VotingStatus.Voting,
VotingStatus.Voted,
VotingStatus.Prolonging
) && (
<VotingSkeleton isLoaded={isLoaded}>
{isMaliciousAdVoting ? (
<>
{eitherIdleState(VotingStatus.Voted) ? (
<Box>
<Text color="muted" fontSize="sm" mb={3}>
{t('Choose an option to vote')}
</Text>
<Stack spacing={3}>
{/* eslint-disable-next-line no-shadow */}
{options.map(({id, value}) => {
const isMine = id === selectedOption
return (
<Stack
isInline
spacing={2}
align="center"
bg={isMine ? 'blue.012' : 'gray.50'}
borderRadius="md"
minH={8}
px={3}
py={2}
zIndex={1}
>
<Flex
align="center"
justify="center"
bg={
isMine
? 'brandBlue.500'
: 'transparent'
}
borderRadius="full"
borderWidth={isMine ? 0 : '4px'}
borderColor="gray.100"
color="white"
w={4}
h={4}
>
{isMine && <OkIcon boxSize={3} />}
</Flex>
<Text
isTruncated
maxW="sm"
title={value.length > 50 ? value : ''}
>
{value}
</Text>
</Stack>
)
})}
</Stack>
</Box>
) : null}
</>
) : (
<Box>
<Text color="muted" fontSize="sm" mb={3}>
{t('Choose an option to vote')}
</Text>
{eitherIdleState(VotingStatus.Voted) ? (
<Stack spacing={3}>
{/* eslint-disable-next-line no-shadow */}
{options.map(({id, value}) => {
const isMine = id === selectedOption
return (
<Stack
isInline
spacing={2}
align="center"
bg={isMine ? 'blue.012' : 'gray.50'}
borderRadius="md"
minH={8}
px={3}
py={2}
zIndex={1}
>
<Flex
align="center"
justify="center"
bg={
isMine ? 'brandBlue.500' : 'transparent'
}
borderRadius="full"
borderWidth={isMine ? 0 : '4px'}
borderColor="gray.100"
color="white"
w={4}
h={4}
>
{isMine && <OkIcon boxSize={3} />}
</Flex>
<Text
isTruncated
maxW="sm"
title={value.length > 50 ? value : ''}
>
{value}
</Text>
</Stack>
)
})}
</Stack>
) : (
<RadioGroup
value={String(selectedOption)}
onChange={value => {
send('SELECT_OPTION', {
option: Number(value),
})
}}
>
<Stack spacing={2}>
{/* eslint-disable-next-line no-shadow */}
{options.map(({id, value}) => (
<VotingOption
key={id}
value={String(id)}
isDisabled={eitherIdleState(
VotingStatus.Pending,
VotingStatus.Starting,
VotingStatus.Voted
)}
annotation={
isMaxWinnerThreshold
? null
: t('{{count}} min. votes required', {
count: toPercent(
winnerThreshold / 100
),
})
}
>
{value}
</VotingOption>
))}
</Stack>
</RadioGroup>
)}
</Box>
)}
</VotingSkeleton>
)}
{eitherIdleState(
VotingStatus.Counting,
VotingStatus.Finishing,
VotingStatus.Archived,
VotingStatus.Terminating,
VotingStatus.Terminated
) && (
<VotingSkeleton isLoaded={isLoaded}>
<Stack spacing={3}>
<Text color="muted" fontSize="sm">
{t('Voting results')}
</Text>
<VotingResult votingService={service} spacing={3} />
</Stack>
</VotingSkeleton>
)}
<VotingSkeleton isLoaded={!actionsIsFetching}>
<Flex justify="space-between" align="center">
<Stack isInline spacing={2}>
{eitherIdleState(VotingStatus.Pending) && (
<PrimaryButton
loadingText={t('Launching')}
onClick={() => {
send('REVIEW_START_VOTING', {
from: coinbase,
})
}}
>
{t('Launch')}
</PrimaryButton>
)}
{eitherIdleState(VotingStatus.Open) &&
(isOracle ? (
<PrimaryButton
onClick={() => {
if (isMaliciousAdVoting) {
send('FORCE_REJECT')
}
send('REVIEW')
}}
>
{isMaliciousAdVoting ? t('Reject') : t('Vote')}
</PrimaryButton>
) : (
<Box>
<Tooltip
label={t(
'This vote is not available to you. Only validated identities randomly selected to the committee can vote.'
)}
placement="top"
zIndex="tooltip"
>
<PrimaryButton isDisabled>
{t('Vote')}
</PrimaryButton>
</Tooltip>
</Box>
))}
{eitherIdleState(VotingStatus.Counting) && canFinish && (
<PrimaryButton
isLoading={current.matches(
`mining.${VotingStatus.Finishing}`
)}
loadingText={t('Finishing')}
onClick={() => send('FINISH', {from: coinbase})}
>
{didDetermineWinner
? t('Finish voting')
: t('Claim refunds')}
</PrimaryButton>
)}
{eitherIdleState(
VotingStatus.Open,
VotingStatus.Voting,
VotingStatus.Voted,
VotingStatus.Counting
) &&
canProlong && (
<PrimaryButton
onClick={() => send('REVIEW_PROLONG_VOTING')}
>
{t('Prolong voting')}
</PrimaryButton>
)}
{(eitherIdleState(
VotingStatus.Voted,
VotingStatus.Voting
) ||
(eitherIdleState(VotingStatus.Counting) &&
!canProlong &&
!canFinish)) && (
<PrimaryButton as={Box} isDisabled>
{t('Vote')}
</PrimaryButton>
)}
{!eitherIdleState(
VotingStatus.Terminated,
VotingStatus.Terminating
) &&
canTerminate && (
<PrimaryButton
colorScheme="red"
variant="solid"
_active={{}}
onClick={() => send('TERMINATE')}
>
{t('Terminate')}
</PrimaryButton>
)}
</Stack>
<Stack isInline spacing={3} align="center">
{eitherIdleState(
VotingStatus.Archived,
VotingStatus.Terminated
) &&
!didDetermineWinner && (
<Text color="red.500">
{t('No winner selected')}
</Text>
)}
<VDivider />
<Stack isInline spacing={2} align="center">
{didDetermineWinner ? (
<UserTickIcon color="muted" boxSize={4} />
) : (
<UserIcon color="muted" boxSize={4} />
)}
<Text as="span">
{/* eslint-disable-next-line no-nested-ternary */}
{eitherIdleState(VotingStatus.Counting) ? (
<>
{t('{{count}} published votes', {
count: accountableVoteCount,
})}{' '}
{t('out of {{count}}', {
count: voteProofsCount,
})}
</>
) : eitherIdleState(
VotingStatus.Pending,
VotingStatus.Open,
VotingStatus.Voting,
VotingStatus.Voted
) ? (
t('{{count}} votes', {
count: voteProofsCount,
})
) : (
t('{{count}} published votes', {
count: accountableVoteCount,
})
)}
</Text>
</Stack>
</Stack>
</Flex>
</VotingSkeleton>
<VotingSkeleton isLoaded={isLoaded}>
<Stack spacing={5}>
<Box>
<Text fontWeight={500}>{t('Recent transactions')}</Text>
</Box>
<Table style={{tableLayout: 'fixed', fontWeight: 500}}>
<Thead>
<Tr>
<RoundedTh isLeft>{t('Transaction')}</RoundedTh>
<RoundedTh>{t('Date and time')}</RoundedTh>
<RoundedTh isRight textAlign="right">
{t('Amount')}
</RoundedTh>
</Tr>
</Thead>
<Tbody>
{balanceUpdates.map(
({
hash,
type,
timestamp,
from,
amount,
fee,
tips,
balanceChange = 0,
contractCallMethod,
}) => {
const isSender = areSameCaseInsensitive(
from,
coinbase
)
const txCost =
(isSender ? -amount : 0) + balanceChange
const totalTxCost =
txCost - ((isSender ? fee : 0) + tips)
const isCredit = totalTxCost > 0
const color =
// eslint-disable-next-line no-nested-ternary
totalTxCost === 0
? 'brandGray.500'
: isCredit
? 'blue.500'
: 'red.500'
return (
<Tr key={hash}>
<OraclesTxsValueTd>
<Stack isInline>
<Flex
align="center"
justify="center"
bg={isCredit ? 'blue.012' : 'red.012'}
color={color}
borderRadius="lg"
minH={8}
minW={8}
>
{isSender ? (
<ArrowUpIcon boxSize={5} />
) : (
<ArrowDownIcon boxSize={5} />
)}
</Flex>
<Box isTruncated>
{contractCallMethod ? (
<Text>
{
ContractCallMethod[
contractCallMethod
]
}
</Text>
) : (
<Text>
{ContractTransactionType[type]}
</Text>
)}
<SmallText isTruncated title={from}>
{hash}
</SmallText>
</Box>
</Stack>
</OraclesTxsValueTd>
<OraclesTxsValueTd>
<Text>
{new Date(timestamp).toLocaleString()}
</Text>
</OraclesTxsValueTd>
<OraclesTxsValueTd textAlign="right">
<Text
color={color}
overflowWrap="break-word"
>
{toLocaleDna(i18n.language, {
signDisplay: 'exceptZero',
})(txCost)}
</Text>
{isSender && (
<SmallText>
{t('Fee')} {toDna(fee + tips)}
</SmallText>
)}
</OraclesTxsValueTd>
</Tr>
)
}
)}
{balanceUpdates.length === 0 && (
<Tr>
<OraclesTxsValueTd colSpan={3}>
<FillCenter py={12}>
<Stack spacing={4} align="center">
<CoinsLgIcon
boxSize={20}
color="gray.100"
/>
<Text color="muted">
{t('No transactions')}
</Text>
</Stack>
</FillCenter>
</OraclesTxsValueTd>
</Tr>
)}
</Tbody>
</Table>
</Stack>
</VotingSkeleton>
</Stack>
</Box>
<VotingSkeleton isLoaded={isLoaded} h={isLoaded ? 'auto' : 'lg'}>
<Box mt={3}>
<Box mt={-2} mb={4}>
<IconButton
icon={<RefreshIcon boxSize={5} />}
px={1}
pr={3}
_focus={null}
onClick={() => {
send('REFRESH')
refetchActions()
}}
>
{t('Refresh')}
</IconButton>
</Box>
{!isClosed && (
<Stat mb={8}>
<StatLabel as="div" color="muted" fontSize="md">
<Stack isInline spacing={2} align="center">
<StarIcon boxSize={4} color="white" />
<Text fontWeight={500}>{t('Prize pool')}</Text>
</Stack>
</StatLabel>
<StatNumber fontSize="base" fontWeight={500}>
{toDna(estimatedTotalReward)}
</StatNumber>
<Box mt={1}>
<IconButton
icon={<AddFundIcon boxSize={5} />}
onClick={() => {
send('ADD_FUND')
}}
>
{t('Add funds')}
</IconButton>
</Box>
</Stat>
)}
<Stack spacing={6}>
{!isClosed && (
<Stat>
<StatLabel color="muted" fontSize="md">
<Tooltip
label={
// eslint-disable-next-line no-nested-ternary
Number(votingMinPayment) > 0
? isMaxWinnerThreshold
? t('Deposit will be refunded')
: t(
'Deposit will be refunded if your vote matches the majority'
)
: t('Free voting')
}
placement="top"
>
<Text
as="span"
borderBottom="dotted 1px"
borderBottomColor="muted"
cursor="help"
>
{t('Voting deposit')}
</Text>
</Tooltip>
</StatLabel>
<StatNumber fontSize="base" fontWeight={500}>
{toDna(votingMinPayment)}
</StatNumber>
</Stat>
)}
{!isClosed && (
<Stat>
<StatLabel color="muted" fontSize="md">
<Tooltip
label={t('Including your Voting deposit')}
placement="top"
>
<Text
as="span"
borderBottom="dotted 1px"
borderBottomColor="muted"
cursor="help"
>
{t('Min reward')}
</Text>
</Tooltip>
</StatLabel>
<StatNumber fontSize="base" fontWeight={500}>
{toDna(estimatedOracleReward)}
</StatNumber>
</Stat>
)}
{!isClosed && (
<Stat>
<StatLabel color="muted" fontSize="md">
{isMaxWinnerThreshold ? (
<Text as="span">{t('Your max reward')}</Text>
) : (
<Tooltip
label={t(
`Including a share of minority voters' deposit`
)}
placement="top"
>
<Text
as="span"
borderBottom="dotted 1px"
borderBottomColor="muted"
cursor="help"
>
{t('Max reward')}
</Text>
</Tooltip>
)}
</StatLabel>
<StatNumber fontSize="base" fontWeight={500}>
{toDna(estimatedMaxOracleReward)}
</StatNumber>
</Stat>
)}
<AsideStat
label={t('Committee size')}
value={t('{{committeeSize}} oracles', {committeeSize})}
/>
<AsideStat
label={t('Quorum required')}
value={t('{{count}} votes', {
count: quorumVotesCount({quorum, committeeSize}),
})}
/>
<AsideStat
label={t('Majority threshold')}
value={
isMaxWinnerThreshold
? t('N/A')
: toPercent(winnerThreshold / 100)
}
/>
{isClosed && totalReward && (
<AsideStat
label={t('Prize paid')}
value={toDna(totalReward)}
/>
)}
</Stack>
</Box>
</VotingSkeleton>
</Stack>
</Stack>
</Page>
</Layout>
<VoteDrawer
isOpen={
eitherState(current, 'review', `mining.${VotingStatus.Voting}`) &&
!eitherState(
current,
`mining.${VotingStatus.Voting}.reviewPendingVote`
)
}
onClose={() => {
send('CANCEL')
}}
// eslint-disable-next-line no-shadow
option={options.find(({id}) => id === selectedOption)?.value}
from={coinbase}
to={contractHash}
deposit={votingMinPayment}
publicVotingDuration={publicVotingDuration}
finishDate={finishDate}
finishCountingDate={finishCountingDate}
isLoading={current.matches(`mining.${VotingStatus.Voting}`)}
onVote={() => {
send('VOTE', {privateKey})
}}
/>
<AddFundDrawer
isOpen={eitherState(
current,
'funding',
`mining.${VotingStatus.Funding}`
)}
onClose={() => {
send('CANCEL')
}}
from={coinbase}
to={contractHash}
available={identityBalance}
ownerFee={ownerFee}
isLoading={current.matches(`mining.${VotingStatus.Funding}`)}
onAddFund={({amount}) => {
send('ADD_FUND', {amount, privateKey})
}}
/>
<LaunchDrawer
isOpen={eitherState(
current,
`idle.${VotingStatus.Pending}.review`,
`mining.${VotingStatus.Starting}`
)}
onClose={() => {
send('CANCEL')
}}
balance={contractBalance}
requiredBalance={votingMinBalance(minOracleReward, committeeSize)}
ownerFee={ownerFee}
from={coinbase}
available={identityBalance}
isLoading={current.matches(`mining.${VotingStatus.Starting}`)}
onLaunch={({amount}) => {
send('START_VOTING', {amount, privateKey})
}}
/>
<FinishDrawer
isOpen={eitherState(
current,
`idle.${VotingStatus.Counting}.finish`,
`mining.${VotingStatus.Finishing}`
)}
onClose={() => {
send('CANCEL')
}}
from={coinbase}
available={identityBalance}
isLoading={current.matches(`mining.${VotingStatus.Finishing}`)}
onFinish={() => {
send('FINISH', {privateKey})
}}
hasWinner={didDetermineWinner}
/>
<ProlongDrawer
isOpen={eitherState(
current,
'prolong',
`mining.${VotingStatus.Prolonging}`
)}
onClose={() => {
send('CANCEL')
}}
from={coinbase}
available={identityBalance}
isLoading={current.matches(`mining.${VotingStatus.Prolonging}`)}
onProlong={() => {
send('PROLONG_VOTING', {privateKey})
}}
/>
<TerminateDrawer
isOpen={eitherState(
current,
`idle.terminating`,
`mining.${VotingStatus.Terminating}`
)}
onClose={() => {
send('CANCEL')
}}
contractAddress={contractHash}
isLoading={current.matches(`mining.${VotingStatus.Terminating}`)}
onTerminate={() => {
send('TERMINATE', {privateKey})
}}
/>
{pendingVote && (
<ReviewNewPendingVoteDialog
isOpen={eitherState(
current,
`mining.${VotingStatus.Voting}.reviewPendingVote`
)}
onClose={() => {
send('GOT_IT')
}}
vote={pendingVote}
startCounting={finishDate}
finishCounting={finishCountingDate}
/>
)}
{adCid && (
<AdPreview
ad={{...ad, author: issuer}}
isMalicious={isMaliciousAdVoting}
{...adPreviewDisclosure}
/>
)}
<Dialog
isOpen={eitherIdleState('redirecting')}
onClose={() => send('CANCEL')}
>
<DialogHeader>{t('Leaving Idena')}</DialogHeader>
<DialogBody>
<Text>{t(`You're about to leave Idena.`)}</Text>
<Text>{t(`Are you sure?`)}</Text>
</DialogBody>
<DialogFooter>
<SecondaryButton onClick={() => send('CANCEL')}>
{t('Cancel')}
</SecondaryButton>
<PrimaryButton onClick={() => send('CONTINUE')}>
{t('Continue')}
</PrimaryButton>
</DialogFooter>
</Dialog>
</>
)
}
Example #10
Source File: [id].js From idena-web with MIT License | 4 votes |
export default function Details() {
const {t} = useTranslation()
const router = useRouter()
const isDesktop = useIsDesktop()
const [flipView, setFlipView] = useState({
isOpen: false,
})
const {id} = router.query
const {data, isFetching} = useQuery(
['get-certificate-full', id],
() => getCertificate(id, 1),
{
enabled: !!id,
retry: false,
refetchOnWindowFocus: false,
initialData: {
shortFlips: [],
longFlips: [],
},
}
)
const openFlipView = (
hash,
answer,
isCorrect,
withWords,
isCorrectReport,
shouldBeReported
) => {
setFlipView({
isOpen: true,
hash,
answer,
isCorrect,
withWords,
isCorrectReport,
shouldBeReported,
})
}
return (
<Layout>
<Page py={0}>
<Flex direction="column" flex={1} alignSelf="stretch" pb={10}>
<Flex
align="center"
alignSelf="stretch"
justify="space-between"
mt={[4, 8]}
>
<AngleArrowBackIcon
stroke="#578FFF"
display={['block', 'none']}
position="absolute"
left={4}
top={4}
h="28px"
w="28px"
onClick={() => {
router.push('/try')
}}
/>
<PageTitleNew>{t('Training validation report')}</PageTitleNew>
<CloseButton
display={['none', 'flex']}
alignSelf="flex-start"
onClick={() => {
router.push('/try')
}}
/>
</Flex>
<Flex width={['100%', '720px']} direction="column">
{data.actionType === CertificateActionType.Passed && (
<AlertBox>
<Flex align="center">
<RightIcon boxSize={[5, 4]} color="green.500" mr={[3, 2]} />
<Text fontWeight={500}>{t('Passed successfully')}</Text>
</Flex>
</AlertBox>
)}
{data.actionType === CertificateActionType.Failed && (
<AlertBox borderColor="red.050" bg="red.010">
<Flex align="center">
<WarningIcon boxSize={4} color="red.500" mr={2} />
<Text fontWeight={500}>{t('Failed. Please try again')}</Text>
</Flex>
</AlertBox>
)}
<Flex
direction={['column', 'row']}
justifyContent="space-between"
fontSize="md"
align="center"
mt={8}
>
<Flex direction={['column', 'row']} w={['100%', 'auto']}>
<DetailsPoints
title={t('Short score')}
isLoading={isFetching}
value={`${data.shortScore || 0}/6`}
isFailed={data.shortScore < 4}
/>
<DetailsPoints
title={t('Long score')}
isLoading={isFetching}
value={`${data.longScore || 0}/18`}
isFailed={data.longScore < 14}
/>
<DetailsPoints
title={t('Reporting score')}
isLoading={isFetching}
value={`${data.reportScore || 0}/6`}
isFailed={data.reportScore < 4}
mb={[8, 0]}
/>
</Flex>
{data.actionType === CertificateActionType.Passed && (
<Flex w={['100%', 'auto']}>
{isDesktop ? (
<TextLink
href="/certificate/[id]"
as={`/certificate/${id}`}
fontWeight={500}
mr={4}
target="_blank"
>
<CertificateIcon boxSize={5} mr={1} />
{t('Show certificate')}
</TextLink>
) : (
<WideLink
href={`/certificate/${id}`}
target="_blank"
label={t('Show certificate')}
>
<Box
boxSize={8}
backgroundColor="brandBlue.10"
borderRadius="10px"
>
<CertificateIcon boxSize={5} mr={1} mt="6px" ml="6px" />
</Box>
</WideLink>
)}
</Flex>
)}
</Flex>
<Heading
fontSize={['md', 'lg']}
fontWeight="500"
color={['muted', 'brandGray.500']}
mt={[10, 8]}
>
{t('Short session')}
</Heading>
<Flex mt={[0, 5]}>
<Table>
<Thead display={['none', 'table-header-group']}>
<Tr>
<RoundedFlipsTh>
{t('Flips')}
<FlipsThCorner borderLeftRadius="md" />
</RoundedFlipsTh>
<RoundedFlipsTh w={32}>
{t('Answers')}
<FlipsThCorner borderRightRadius="md" />
</RoundedFlipsTh>
</Tr>
</Thead>
<Tbody>
{isFetching
? new Array(6).fill(0).map((_, idx) => (
<Tr key={idx}>
<Td colSpan={2} px={0} py={2}>
<Skeleton h={7} />
</Td>
</Tr>
))
: data.shortFlips.map(({hash, answer, correct}) => (
<Tr key={hash}>
<FlipsValueTd>
<ShortFlipWithIcon
hash={hash}
onClick={() =>
openFlipView(hash, answer, correct)
}
/>
</FlipsValueTd>
<FlipsValueTd w={['60px', 'auto']}>
<Flex alignItems="center">
{correct ? (
<RightIcon color="green.500" boxSize={5} />
) : (
<WrongIcon color="red.500" boxSize={5} />
)}
<Flex fontSize={['base', 'md']} ml={[1, 2]}>
{GetAnswerTitle(t, answer)}
</Flex>
</Flex>
</FlipsValueTd>
</Tr>
))}
</Tbody>
</Table>
</Flex>
<Heading
fontSize={['md', 'lg']}
fontWeight="500"
color={['muted', 'brandGray.500']}
mt={[10, 8]}
>
{t('Long session')}
</Heading>
<Flex mt={[0, 5]}>
<Table style={{tableLayout: 'fixed'}}>
<Thead display={['none', 'table-header-group']}>
<Tr>
<RoundedFlipsTh w="35%">
{t('Flips')}
<FlipsThCorner borderLeftRadius="md" />
</RoundedFlipsTh>
<FlipsTh w={32}>{t('Answers')}</FlipsTh>
<FlipsTh>{t('Qualification')}</FlipsTh>
<RoundedFlipsTh>
{t('Reporting reason')}
<FlipsThCorner borderRightRadius="md" />
</RoundedFlipsTh>
</Tr>
</Thead>
<Tbody>
{isFetching
? new Array(6).fill(0).map((_, idx) => (
<Tr key={idx}>
<Td colSpan={4} px={0} py={2}>
<Skeleton h={7} />
</Td>
</Tr>
))
: data.longFlips.map(
({
hash,
answer,
correct,
correctReport,
wrongWords,
reason,
}) => (
<>
<Tr position={['relative', 'initial']} key={hash}>
<FlipsValueTd
borderBottom={[0, '1px solid #e8eaed']}
>
<LongFlipWithIcon
hash={hash}
onClick={() =>
openFlipView(
hash,
answer,
correct,
true,
correctReport,
reason !== 0
)
}
/>
</FlipsValueTd>
<FlipsValueTd
borderBottom={[0, '1px solid #e8eaed']}
w={['75px', 'auto']}
>
<Flex direction={['column', 'row']}>
<Flex alignItems="center">
{correct ? (
<RightIcon
color="green.500"
boxSize={5}
/>
) : (
<WrongIcon color="red.500" boxSize={5} />
)}
<Text
textOverflow="ellipsis"
overflow="hidden"
whiteSpace="nowrap"
fontSize={['base', 'md']}
ml={[1, 2]}
>
{GetAnswerTitle(t, answer)}
</Text>
</Flex>
<Text
display={['block', 'none']}
color="muted"
fontSize="md"
fontWeight={500}
>
{t('Answer')}
</Text>
</Flex>
</FlipsValueTd>
<FlipsValueTd
borderBottom={[0, '1px solid #e8eaed']}
w={['90px', 'auto']}
>
<Flex direction={['column', 'row']}>
<Flex alignItems="center">
{correctReport ? (
<RightIcon
color="green.500"
boxSize={5}
/>
) : (
<WrongIcon color="red.500" boxSize={5} />
)}
<Text
textOverflow="ellipsis"
overflow="hidden"
whiteSpace="nowrap"
fontSize={['base', 'md']}
ml={[1, 2]}
>
{wrongWords
? t('Reported')
: t('Not reported')}
</Text>
</Flex>
<Text
display={['block', 'none']}
color="muted"
fontSize="md"
fontWeight={500}
>
{t('Qualification')}
</Text>
</Flex>
</FlipsValueTd>
<FlipsValueTd display={['none', 'table-cell']}>
{GetReasonDesc(t, reason)}
</FlipsValueTd>
</Tr>
<FlipsHiddenDescRow>
<Flex
direction="column"
w="100%"
onClick={() =>
openFlipView(
hash,
answer,
correct,
true,
correctReport,
reason !== 0
)
}
>
<Text fontSize="base" fontWeight={500}>
{GetReasonDesc(t, reason)}
</Text>
<Text
color="muted"
fontSize="md"
fontWeight={500}
>
{t('Reason')}
</Text>
</Flex>
</FlipsHiddenDescRow>
</>
)
)}
</Tbody>
</Table>
</Flex>
</Flex>
</Flex>
<FlipView {...flipView} onClose={() => setFlipView({isOpen: false})} />
</Page>
</Layout>
)
}
Example #11
Source File: index.js From idena-web with MIT License | 4 votes |
export default function Try() {
const {t} = useTranslation()
const router = useRouter()
const {coinbase} = useAuthState()
return (
<Layout>
<Page pt={[4, 0]}>
<MobileApiStatus display={['initial', 'none']} left={4} />
<Flex
direction="column"
flex={1}
alignSelf="stretch"
pb={[6, 10]}
pt={[2, 0]}
>
<Flex
align="center"
alignSelf="stretch"
justify="space-between"
my={8}
>
<Stack isInline spacing={6} align="center" width="480px">
<Avatar address={coinbase} />
<Stack spacing={1}>
<Heading
as="h2"
fontSize="lg"
fontWeight={500}
lineHeight="short"
>
{t('Training validation')}
</Heading>
<Heading
as="h3"
fontSize="mdx"
fontWeight="normal"
color="muted"
lineHeight="shorter"
>
{t(
'Learn about the validation process and get a certificate. You can provide it as a proof to a validated person to get an invitation code.'
)}
</Heading>
</Stack>
</Stack>
<CloseButton
display={['none', 'initial']}
alignSelf="flex-start"
onClick={() => {
router.push('/home')
}}
/>
</Flex>
<Stack width={['100%', '480px']} spacing={5}>
<CertificateCard
title={t('Easy')}
description={t('Pass the training validation immediately.')}
trustLevel={t('Low')}
scheduleText={t('Immediately')}
type={CertificateType.Easy}
certificateColor="red.500"
/>
<CertificateCard
title={t('Medium')}
description={t(
'Schedule your next training validation and join exactly on time.'
)}
trustLevel={t('Middle')}
scheduleText={t('In 1 hour')}
type={CertificateType.Medium}
certificateColor="gray.500"
/>
<CertificateCard
title={t('Hard')}
description={t(
'Train your time management skills. Validation requires participants to join exactly on time.'
)}
trustLevel={t('High')}
scheduleText={`${dayjs(GetNextUTCValidationDate()).format(
'D MMM HH:mm'
)}`}
type={CertificateType.Hard}
certificateColor="orange.500"
/>
</Stack>
</Flex>
</Page>
</Layout>
)
}
Example #12
Source File: validation-report.js From idena-web with MIT License | 4 votes |
export default function ValidationReport() {
const {t, i18n} = useTranslation()
const {colors} = useTheme()
const epoch = useEpoch()
const [identity] = useIdentity()
const isMobile = useMobile()
const {address, state, isValidated} = identity
const {
prevState,
lastValidationScore,
totalScore,
earnings,
earningsScore,
validationReward,
missedValidationReward,
invitationReward,
missedInvitationReward,
flipReward,
missedFlipReward,
flipReportReward,
missedFlipReportReward,
totalMissedReward,
validationResult,
stakingReward,
missedStakingReward,
candidateReward,
missedCandidateReward,
isLoading,
} = useValidationReportSummary()
const {
short: {score: shortScore, ...shortResults},
long: {score: longScore, ...longResults},
} = lastValidationScore
const toDna = toLocaleDna(i18n.language, {maximumFractionDigits: 3})
const maybeDna = amount =>
!amount || Number.isNaN(amount)
? '–'
: amount.toLocaleString(i18n.language, {maximumFractionDigits: 3})
const epochNumber = epoch?.epoch
return (
<Layout>
<Page as={Stack} spacing={6}>
<Flex
justify="space-between"
align="center"
w="full"
alignItems="center"
>
<AngleArrowBackIcon
stroke="#578FFF"
position="absolute"
left={4}
top={4}
h="28px"
w="28px"
onClick={() => router.back()}
display={['initial', 'none']}
/>
<PageTitleNew mb={0} mt={[-2, 0]}>
{t('Epoch #{{epochNumber}} validation report', {epochNumber})}
</PageTitleNew>
<CloseButton
display={['none', 'initial']}
onClick={() => router.push('/home')}
/>
</Flex>
<Stack spacing={6} w="full">
<Box>
<Skeleton isLoaded={!isLoading} alignSelf="start">
{isValidated ? (
<SuccessAlert>
{validationResult === ValidationResult.Success &&
t('Successfully validated')}
{validationResult === ValidationResult.Penalty &&
t('Validated')}
</SuccessAlert>
) : (
<ErrorAlert>{t('Validation failed')}</ErrorAlert>
)}
</Skeleton>
</Box>
<Box py={2} display={['none', 'block']}>
<UserCard identity={{address, state}} />
</Box>
<Stack isInline={!isMobile} spacing={[4, 10]}>
<ValidationReportBlockOverview>
<Stack spacing={[6, 10]}>
<Box>
<ValidationReportGauge>
<ValidationReportGaugeBox>
{isLoading ? (
<ValidationReportGaugeBar color={colors.gray['100']} />
) : isValidated ? (
<ValidationReportGaugeBar
value={totalScore * 100}
color={
totalScore <= 0.75
? colors.red['500']
: totalScore <= 0.9
? colors.orange['500']
: colors.green['500']
}
bg="white"
/>
) : (
<ValidationReportGaugeBar
value={shortScore * 100 || 2}
color={colors.red['500']}
bg="white"
/>
)}
<ValidationReportGaugeIcon
icon={<TimerIcon />}
display={['none', 'initial']}
/>
</ValidationReportGaugeBox>
<ValidationReportGaugeStat>
<Skeleton isLoaded={!isLoading} w="auto">
{isValidated ? (
<ValidationReportGaugeStatValue>
{toPercent(totalScore)}
</ValidationReportGaugeStatValue>
) : (
<ValidationReportGaugeStatValue color="red.500">
{t('Failed')}
</ValidationReportGaugeStatValue>
)}
</Skeleton>
<Skeleton isLoaded={!isLoading} w="auto">
<ValidationReportGaugeStatLabel>
{isValidated && t('Total score')}
{validationResult ===
ValidationResult.LateSubmission &&
t('Late submission')}
{validationResult ===
ValidationResult.MissedValidation &&
t('Missed validation')}
{validationResult === ValidationResult.WrongAnswers &&
t('Wrong answers')}
</ValidationReportGaugeStatLabel>
</Skeleton>
</ValidationReportGaugeStat>
</ValidationReportGauge>
</Box>
<Stack spacing={[2, 4]} isInline={!isMobile}>
<Flex justify="space-between">
<Skeleton isLoaded={!isLoading}>
<ValidationReportStat
label={t('Short session')}
value={
[
ValidationResult.MissedValidation,
ValidationResult.LateSubmission,
].includes(validationResult)
? '—'
: t('{{score}} ({{point}} out of {{flipsCount}})', {
score: toPercent(shortScore),
point: shortResults.point,
flipsCount: shortResults.flipsCount,
})
}
/>
</Skeleton>
</Flex>
<Divider
orientation="horizontal"
display={['initial', 'none']}
/>
<Flex justify="space-between">
<Skeleton isLoaded={!isLoading}>
<ValidationReportStat
label={t('Long session')}
value={
validationResult === ValidationResult.MissedValidation
? '—'
: t('{{score}} ({{point}} out of {{flipsCount}})', {
score: toPercent(longScore),
point: longResults.point,
flipsCount: longResults.flipsCount,
})
}
/>
</Skeleton>
</Flex>
</Stack>
</Stack>
</ValidationReportBlockOverview>
<ValidationReportBlockOverview>
<Stack spacing={[6, 10]}>
<Box>
<ValidationReportGauge>
<ValidationReportGaugeBox>
{isLoading ? (
<ValidationReportGaugeBar color={colors.gray['100']} />
) : isValidated ? (
<ValidationReportGaugeBar
value={earningsScore * 100 || 2}
color={
// eslint-disable-next-line no-nested-ternary
earningsScore <= 0.5
? colors.red['500']
: earningsScore <= 0.75
? colors.orange['500']
: colors.green['500']
}
bg="white"
/>
) : (
<ValidationReportGaugeBar
value={2}
color={colors.red['500']}
bg="white"
/>
)}
<ValidationReportGaugeIcon
icon={<SendOutIcon />}
display={['none', 'initial']}
/>
</ValidationReportGaugeBox>
<ValidationReportGaugeStat>
<Skeleton isLoaded={!isLoading} w="auto">
{validationResult === ValidationResult.Success ? (
<ValidationReportGaugeStatValue>
{toDna(earnings)}
</ValidationReportGaugeStatValue>
) : (
<ValidationReportGaugeStatValue color="red.500">
{toDna(totalMissedReward)}
</ValidationReportGaugeStatValue>
)}
</Skeleton>
<ValidationReportGaugeStatLabel>
{t('Earnings')}
</ValidationReportGaugeStatLabel>
</ValidationReportGaugeStat>
</ValidationReportGauge>
</Box>
<Flex justify="space-between" flexWrap="wrap">
<Flex mr={4} mb={[0, 4]}>
<Skeleton isLoaded={!isLoading}>
<ValidationReportStat
label={t('Missed invitation earnings')}
value={toDna(missedInvitationReward)}
/>
</Skeleton>
</Flex>
<Divider
orientation="horizontal"
display={['initial', 'none']}
my={2}
/>
<Flex mr={4} mb={[0, 4]}>
<Skeleton isLoaded={!isLoading}>
<ValidationReportStat
label={t('Missed reporting earnings')}
value={toDna(missedFlipReportReward)}
/>
</Skeleton>
</Flex>
<Divider
orientation="horizontal"
display={['initial', 'none']}
my={2}
/>
<Flex mr={4} mb={[0, 4]}>
<Skeleton isLoaded={!isLoading}>
<ValidationReportStat
label={t('Missed flip earnings')}
value={toDna(missedFlipReward)}
/>
</Skeleton>
</Flex>
</Flex>
</Stack>
</ValidationReportBlockOverview>
</Stack>
<Stack spacing={[0, 5]}>
<Box mb={2}>
<Heading color="brandGray.500" fontSize="lg" fontWeight={500}>
{t('Earnings summary')}
</Heading>
<ExternalLink
href={`https://scan.idena.io/identity/${address}/epoch/${
epoch?.epoch
}/${isValidated ? 'rewards' : 'validation'}`}
>
{t('See the full report in blockchain explorer')}
</ExternalLink>
</Box>
<Table fontWeight={500}>
<Thead display={['none', 'table-header-group']}>
<Tr>
<ValidationReportTh>
{t('Category')}
<ValidationReportThCorner borderLeftRadius="md" />
</ValidationReportTh>
<ValidationReportTh>
{t('Earned, iDNA')}
<ValidationReportThCorner />
</ValidationReportTh>
<ValidationReportTh>
{t('Missed, iDNA')}
<ValidationReportThCorner />
</ValidationReportTh>
<ValidationReportTh style={{width: '260px'}}>
{t('How to get maximum reward')}
<ValidationReportThCorner borderRightRadius="md" />
</ValidationReportTh>
</Tr>
</Thead>
<Tbody>
{stakingReward === 0 && candidateReward === 0 ? (
<>
<Tr>
<ValidationReportColumn>
<ValidationReportCategoryLabel
isFirst
label={t('Validation')}
description={
isMobile
? t('Category')
: t('Rewards for the successfull validation')
}
info={t('Rewards for the successfull validation')}
/>
</ValidationReportColumn>
<ValidationReportColumn>
<ValidationReportCategoryLabel
label={maybeDna(validationReward)}
description={isMobile ? t('Earned') : ''}
/>
</ValidationReportColumn>
<ValidationReportColumn>
<ValidationReportCategoryLabel
label={
<Text
color={
missedValidationReward > 0 ? 'red.500' : ''
}
>
{maybeDna(missedValidationReward)}
</Text>
}
description={isMobile ? t('Missed') : ''}
/>
</ValidationReportColumn>
<ValidationReportColumn display={['none', 'table-cell']}>
<TableValidationDesc
t={t}
validationResult={validationResult}
missedValidationReward={missedValidationReward}
/>
</ValidationReportColumn>
</Tr>
<TableHiddenDescRow>
<TableValidationDesc
t={t}
validationResult={validationResult}
missedValidationReward={missedValidationReward}
/>
</TableHiddenDescRow>
</>
) : (
<>
<Tr>
<ValidationReportColumn>
<ValidationReportCategoryLabel
isFirst
label={t('Staking')}
description={t('Quadratic staking rewards')}
info={t('Quadratic staking rewards')}
/>
</ValidationReportColumn>
<ValidationReportColumn>
<ValidationReportCategoryLabel
label={maybeDna(stakingReward)}
description={isMobile ? t('Earned') : ''}
/>
</ValidationReportColumn>
<ValidationReportColumn>
<ValidationReportCategoryLabel
label={
<Text
color={missedStakingReward > 0 ? 'red.500' : ''}
>
{maybeDna(missedStakingReward)}
</Text>
}
description={isMobile ? t('Missed') : ''}
/>
</ValidationReportColumn>
<ValidationReportColumn display={['none', 'table-cell']}>
<TextLink href="/home?replenishStake">
{t('Add stake')}
<ChevronRightIcon />
</TextLink>
</ValidationReportColumn>
</Tr>
<TableHiddenDescRow>
<TextLink href="/home">
{t('Add stake')}
<ChevronRightIcon />
</TextLink>
</TableHiddenDescRow>
{state === IdentityStatus.Newbie &&
prevState === IdentityStatus.Candidate && (
<>
<Tr>
<ValidationReportColumn>
<ValidationReportCategoryLabel
isFirst
label={t('Validation')}
description={
isMobile
? t('Category')
: t(
'Rewards for the 1st successful validation'
)
}
info={t(
'Rewards for the 1st successful validation'
)}
/>
</ValidationReportColumn>
<ValidationReportColumn>
<ValidationReportCategoryLabel
label={maybeDna(candidateReward)}
description={isMobile ? t('Earned') : ''}
/>
</ValidationReportColumn>
<ValidationReportColumn>
<ValidationReportCategoryLabel
label={
<Text
color={
missedCandidateReward > 0 ? 'red.500' : ''
}
>
{maybeDna(missedCandidateReward)}
</Text>
}
description={isMobile ? t('Missed') : ''}
/>
</ValidationReportColumn>
<ValidationReportColumn
display={['none', 'table-cell']}
>
<TableValidationDesc
t={t}
validationResult={validationResult}
missedValidationReward={missedCandidateReward}
/>
</ValidationReportColumn>
</Tr>
<TableHiddenDescRow>
<TableValidationDesc
t={t}
validationResult={validationResult}
missedValidationReward={missedCandidateReward}
/>
</TableHiddenDescRow>
</>
)}
</>
)}
<Tr>
<ValidationReportColumn>
<ValidationReportCategoryLabel
isFirst
label={t('Flips')}
description={
isMobile
? t('Category')
: t('Rewards for submitted and qualified flips')
}
info={t('Rewards for submitted and qualified flips')}
/>
</ValidationReportColumn>
<ValidationReportColumn>
<ValidationReportCategoryLabel
label={maybeDna(flipReward)}
description={isMobile ? t('Earned') : ''}
/>
</ValidationReportColumn>
<ValidationReportColumn>
<ValidationReportCategoryLabel
label={
<Text color={missedFlipReward > 0 ? 'red.500' : ''}>
{maybeDna(missedFlipReward)}
</Text>
}
description={isMobile ? t('Missed') : ''}
/>
</ValidationReportColumn>
<ValidationReportColumn display={['none', 'table-cell']}>
<TableFlipsDesc
t={t}
validationResult={validationResult}
missedFlipReward={missedFlipReward}
flipReward={flipReward}
/>
</ValidationReportColumn>
</Tr>
<TableHiddenDescRow>
<TableFlipsDesc
t={t}
validationResult={validationResult}
missedFlipReward={missedFlipReward}
flipReward={flipReward}
/>
</TableHiddenDescRow>
<Tr>
<ValidationReportColumn>
<ValidationReportCategoryLabel
isFirst
label={t('Invitations')}
description={
isMobile
? t('Category')
: t('Rewards for invitee validation')
}
info={t('Rewards for invitee validation')}
/>
</ValidationReportColumn>
<ValidationReportColumn>
<ValidationReportCategoryLabel
label={maybeDna(invitationReward)}
description={isMobile ? t('Earned') : ''}
/>
</ValidationReportColumn>
<ValidationReportColumn>
<ValidationReportCategoryLabel
label={
<Text
color={missedInvitationReward > 0 ? 'red.500' : ''}
>
{maybeDna(missedInvitationReward)}
</Text>
}
description={isMobile ? t('Missed') : ''}
/>
</ValidationReportColumn>
<ValidationReportColumn display={['none', 'table-cell']}>
<TableInvitationsDesc
t={t}
validationResult={validationResult}
missedInvitationReward={missedInvitationReward}
invitationReward={invitationReward}
/>
</ValidationReportColumn>
</Tr>
<TableHiddenDescRow>
<TableInvitationsDesc
t={t}
validationResult={validationResult}
missedInvitationReward={missedInvitationReward}
invitationReward={invitationReward}
/>
</TableHiddenDescRow>
<Tr>
<ValidationReportColumn>
<ValidationReportCategoryLabel
isFirst
label={t('Flip reports')}
description={
isMobile
? t('Category')
: t('Rewards for reporting bad flips')
}
info={t('Rewards for reporting bad flips')}
/>
</ValidationReportColumn>
<ValidationReportColumn>
<ValidationReportCategoryLabel
label={maybeDna(flipReportReward)}
description={isMobile ? t('Earned') : ''}
/>
</ValidationReportColumn>
<ValidationReportColumn>
<ValidationReportCategoryLabel
label={
<Text
color={missedFlipReportReward > 0 ? 'red.500' : ''}
>
{maybeDna(missedFlipReportReward)}
</Text>
}
description={isMobile ? t('Missed') : ''}
/>
</ValidationReportColumn>
<ValidationReportColumn display={['none', 'table-cell']}>
<TableFlipReportsDesc
t={t}
validationResult={validationResult}
missedFlipReportReward={missedFlipReportReward}
flipReportReward={flipReportReward}
/>
</ValidationReportColumn>
</Tr>
<TableHiddenDescRow>
<TableFlipReportsDesc
t={t}
validationResult={validationResult}
missedFlipReportReward={missedFlipReportReward}
flipReportReward={flipReportReward}
/>
</TableHiddenDescRow>
</Tbody>
</Table>
</Stack>
</Stack>
</Page>
</Layout>
)
}
Example #13
Source File: components.js From idena-web with MIT License | 4 votes |
export function MyIdenaBotAlert({onConnect, onSkip}) {
const {t} = useTranslation()
const [{state}] = useIdentity()
const myIdenaBotDisclosure = useDisclosure()
const [doNotShowAgain, setDoNotShowAgain] = React.useState()
const connectButtonRef = React.useRef()
// eslint-disable-next-line no-shadow
const eitherState = (...states) => states.some(s => s === state)
const size = useBreakpointValue(['sm', 'md'])
const isDesktop = useIsDesktop()
return (
<>
<Alert
variant="solid"
justifyContent="center"
flexShrink={0}
boxShadow="0 3px 12px 0 rgb(255 163 102 /0.1), 0 2px 3px 0 rgb(255 163 102 /0.2)"
color="white"
cursor="pointer"
fontWeight={500}
rounded="md"
mt={2}
mx={2}
w="auto"
onClick={myIdenaBotDisclosure.onOpen}
>
<Flex flexGrow={1} justifyContent="center" position="relative">
<Box mr={[5, 0]}>
<TelegramIcon boxSize={6} mr={1} display={['none', 'initial']} />
{t(`Subscribe to @MyIdenaBot to get personalized notifications based on
your status`)}
</Box>
{isDesktop ? (
<FlatButton
p={2}
position="absolute"
right={0}
top={0}
height="100%"
color="white"
onClick={e => {
e.stopPropagation()
onSkip()
}}
_hover={{color: 'white'}}
>
{t('Close')}
</FlatButton>
) : (
<CloseButton
position="absolute"
right={-3}
top={-2}
onClick={e => {
e.stopPropagation()
onSkip()
}}
/>
)}
</Flex>
</Alert>
<Dialog
title="Subscribe to @MyIdenaBot"
size={size}
initialFocusRef={connectButtonRef}
{...myIdenaBotDisclosure}
>
<DialogBody>
<Stack>
<Text>
{t(
`MyIdenaBot reminds you about important actions based on your
identity status:`,
{nsSeparator: '!!'}
)}
</Text>
{eitherState(IdentityStatus.Undefined) && (
<IdenaBotFeatureList
features={[
'next validation reminder',
'notification when you get an invite',
'reminder to activate your invite',
'your validation results when validation consensus is reached',
]}
/>
)}
{eitherState(IdentityStatus.Invite, IdentityStatus.Candidate) && (
<IdenaBotFeatureList
features={[
'next validation reminder',
'your validation results when validation consensus is reached',
]}
/>
)}
{eitherState(IdentityStatus.Newbie) && (
<IdenaBotFeatureList
features={[
'next validation reminder',
'reminder to create flips if you haven’t done it yet and the validation is coming',
'your validation results when validation consensus is reached',
]}
/>
)}
{eitherState(IdentityStatus.Verified, IdentityStatus.Human) && (
<IdenaBotFeatureList
features={[
'next validation reminder',
'reminder to create flips',
'your validation results when validation consensus is reached',
'reminder to share your remaining invites',
'reminder to submit extra flips to get more rewards',
'status update of all your invitees to check if they are ready for the validation (activated invites, submitted flips)',
]}
/>
)}
{eitherState(IdentityStatus.Zombie, IdentityStatus.Suspended) && (
<IdenaBotFeatureList
features={[
'next validation reminder',
'your validation results when validation consensus is reached',
'reminder to share your remaining invites',
'reminder to submit extra flips to get more rewards',
'status update of all your invitees to check if they are ready for the validation (activated invites, submitted flips)',
]}
/>
)}
</Stack>
</DialogBody>
<DialogFooter align="center">
<Checkbox
borderColor="gray.100"
isChecked={doNotShowAgain}
onChange={e => {
setDoNotShowAgain(e.target.checked)
}}
>
{t('Do not show again')}
</Checkbox>
<SecondaryButton
onClick={() => {
myIdenaBotDisclosure.onClose()
if (doNotShowAgain) onConnect()
}}
>
{t('Not now')}
</SecondaryButton>
<PrimaryButton
ref={connectButtonRef}
onClick={() => {
openExternalUrl('https://t.me/MyIdenaBot')
onConnect()
}}
>
{t('Connect')}
</PrimaryButton>
</DialogFooter>
</Dialog>
</>
)
}
Example #14
Source File: components.js From idena-web with MIT License | 4 votes |
export function ValidationReportSummary({onClose, ...props}) {
const {t, i18n} = useTranslation()
const {colors} = useTheme()
const router = useRouter()
const [{isValidated, status}] = useIdentity()
const {
lastValidationScore,
totalScore,
earnings,
earningsScore,
totalMissedReward,
validationResult,
isLoading,
isFailed,
} = useValidationReportSummary()
const maybeNew =
!status ||
[
IdentityStatus.Undefined,
IdentityStatus.Invite,
IdentityStatus.Candidate,
].includes(status)
if (isFailed || (isLoading && maybeNew)) return null
const {
short: {score: shortScore},
} = lastValidationScore
const dna = toLocaleDna(i18n.language, {maximumFractionDigits: 3})
const tweet = () =>
openExternalUrl(`
https://twitter.com/intent/tweet?text=${encodeURIComponent(
`I've earned ${earnings.toLocaleString(i18n.language, {
maximumFractionDigits: 3,
})} $IDNA`
)}&url=https://idena.io/join-idena&hashtags=Idena,ubi,blockchain,mining
`)
return (
<Box w="100%" pb={[2, 0]} {...props}>
<Flex
borderTop="4px solid"
borderTopColor={
// eslint-disable-next-line no-nested-ternary
isLoading
? 'transparent'
: // eslint-disable-next-line no-nested-ternary
isValidated
? validationResult === ValidationResult.Penalty
? 'orange.500'
: 'green.500'
: 'red.500'
}
borderRadius="md"
boxShadow="0 3px 12px 0 rgba(83, 86, 92, 0.1), 0 2px 3px 0 rgba(83, 86, 92, 0.2)"
px={[7, 8]}
pt={6}
pb={[2, 6]}
position="relative"
w="100%"
>
<CloseButton
w={6}
h={6}
pos="absolute"
top={3}
right={3}
onClick={onClose}
/>
<Stack spacing={6} w="full">
<Skeleton isLoaded={!isLoading} alignSelf="start" w="auto">
<Text fontSize="lg" fontWeight={500}>
{(() => {
switch (validationResult) {
case ValidationResult.Success:
return t('Successfully validated')
case ValidationResult.Penalty:
return t('Validated')
default:
return t('Validation failed')
}
})()}
</Text>
</Skeleton>
<Stack spacing={[6, 10]}>
<Flex justify="space-between" direction={['column', 'row']}>
<ValidationReportGauge>
<ValidationReportGaugeBox>
{isLoading ? (
<ValidationReportGaugeBar color={colors.gray['100']} />
) : isValidated ? (
<ValidationReportGaugeBar
value={totalScore * 100}
color={
// eslint-disable-next-line no-nested-ternary
totalScore <= 0.75
? colors.red[500]
: totalScore <= 0.9
? colors.orange[500]
: colors.green[500]
}
/>
) : (
<ValidationReportGaugeBar
value={shortScore * 100 || 2}
color={colors.red[500]}
/>
)}
<ValidationReportGaugeIcon
display={['none', 'initial']}
icon={<TimerIcon />}
/>
</ValidationReportGaugeBox>
<ValidationReportGaugeStat>
<Skeleton isLoaded={!isLoading} w="auto">
{isValidated ? (
<ValidationReportGaugeStatValue>
{toPercent(totalScore)}
</ValidationReportGaugeStatValue>
) : (
<ValidationReportGaugeStatValue color="red.500">
{t('Failed')}
</ValidationReportGaugeStatValue>
)}
</Skeleton>
<ValidationReportGaugeStatLabel>
{[
ValidationResult.Success,
ValidationResult.Penalty,
].includes(validationResult) && t('Score')}
{validationResult === ValidationResult.LateSubmission &&
t('Late submission')}
{validationResult === ValidationResult.MissedValidation &&
t('Missed validation')}
{validationResult === ValidationResult.WrongAnswers &&
t('Wrong answers')}
</ValidationReportGaugeStatLabel>
</ValidationReportGaugeStat>
</ValidationReportGauge>
<ValidationReportGauge mt={[6, 0]}>
<ValidationReportGaugeBox>
{isLoading ? (
<ValidationReportGaugeBar color={colors.gray['100']} />
) : isValidated ? (
<ValidationReportGaugeBar
value={earningsScore * 100 || 2}
color={
// eslint-disable-next-line no-nested-ternary
earningsScore <= 0.5
? colors.red[500]
: earningsScore <= 0.75
? colors.orange[500]
: colors.green[500]
}
/>
) : (
<ValidationReportGaugeBar
value={2}
color={colors.red[500]}
/>
)}
<ValidationReportGaugeIcon
display={['none', 'initial']}
icon={<SendOutIcon />}
/>
</ValidationReportGaugeBox>
<ValidationReportGaugeStat>
<Skeleton isLoaded={!isLoading} w="auto">
{validationResult === ValidationResult.Success ? (
<ValidationReportGaugeStatValue>
{dna(earnings)}
</ValidationReportGaugeStatValue>
) : (
<ValidationReportGaugeStatValue color="red.500">
{dna(totalMissedReward)}
</ValidationReportGaugeStatValue>
)}
</Skeleton>
<ValidationReportGaugeStatLabel>
{t('Earnings')}
</ValidationReportGaugeStatLabel>
</ValidationReportGaugeStat>
</ValidationReportGauge>
</Flex>
<Flex display={['flex', 'none']} justify="space-around">
<Button
onClick={tweet}
variant="primaryFlat"
size="lg"
fontWeight={500}
isDisabled={!isValidated}
>
{t('Share')}
</Button>
<Divider
display={['block', 'none']}
h={10}
orientation="vertical"
color="gray.100"
/>
<Button
onClick={() => router.push('/validation-report')}
variant="primaryFlat"
size="lg"
fontWeight={500}
>
{t('Details')}
</Button>
</Flex>
<Flex
display={['none', 'flex']}
justify="space-between"
alignItems="center"
>
<Box>
<TextLink
href="/validation-report"
fontWeight={500}
display="inline-block"
>
<Stack isInline spacing={0} align="center">
<Text as="span">{t('More details')}</Text>
<ChevronDownIcon boxSize={4} transform="rotate(-90deg)" />
</Stack>
</TextLink>
</Box>
<Stack isInline color="muted">
<IconButton
icon={<TwitterIcon boxSize={5} />}
size="xs"
variant="ghost"
color="blue.500"
fontSize={20}
_hover={{bg: 'blue.50'}}
onClick={tweet}
/>
</Stack>
</Flex>
</Stack>
</Stack>
</Flex>
</Box>
)
}