diff --git a/.env.example b/.env.example index 5af29c64..35ce23b8 100644 --- a/.env.example +++ b/.env.example @@ -1,2 +1,2 @@ VOCDONI_ENVIRONMENT=prod -FEATURES='{"faucet":true,"vote":{"anonymous":true,"overwrite":true,"secret":true},"login":["web3","web2"],"census":["spreadsheet","token","web3"],"languages":["en","es","ca"]}' +FEATURES='{"faucet":true,"vote":{"anonymous":true,"overwrite":true,"secret":true,"customization":true},"types":{"single":true,"multi":false,"approval":false,"participatory":false,"borda":false},"login":["web3","web2"],"census":["spreadsheet","token","web3"],"unimplemented_census":[],"voting_type":["single"],"unimplemented_voting_type":[],"languages":["en","es","ca"]}' \ No newline at end of file diff --git a/package.json b/package.json index 715b04a2..8dfbf8ca 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "@chakra-ui/anatomy": "^2.1.2", "@chakra-ui/icons": "^2.0.19", "@chakra-ui/react": "^2.7.1", + "@emailjs/browser": "^4.1.0", "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", "@ethersproject/abstract-signer": "^5.7.0", @@ -93,6 +94,7 @@ "typescript": "^5.0.2", "vite": "^4.4.4", "vite-plugin-html": "^3.2.0", + "vite-plugin-svgr": "^4.2.0", "vite-tsconfig-paths": "^4.2.1" }, "packageManager": "yarn@1.22.19", diff --git a/public/default/assets/agm.avif b/public/default/assets/agm.avif new file mode 100644 index 00000000..4de14a9f Binary files /dev/null and b/public/default/assets/agm.avif differ diff --git a/public/default/assets/barca.png b/public/default/assets/barca.png new file mode 100644 index 00000000..016acdec Binary files /dev/null and b/public/default/assets/barca.png differ diff --git a/public/default/assets/bellpuig.svg.png b/public/default/assets/bellpuig.svg.png new file mode 100644 index 00000000..a0b2a128 Binary files /dev/null and b/public/default/assets/bellpuig.svg.png differ diff --git a/public/default/assets/berga.svg.png b/public/default/assets/berga.svg.png new file mode 100644 index 00000000..0a1bad7c Binary files /dev/null and b/public/default/assets/berga.svg.png differ diff --git a/public/default/assets/bisbal.svg b/public/default/assets/bisbal.svg new file mode 100644 index 00000000..00bda4fb --- /dev/null +++ b/public/default/assets/bisbal.svg @@ -0,0 +1,2277 @@ + + + + + + + + image/svg+xml + + Escut de Cabrera de Mar + 22-gener-2008 + + + Xavi Garcia + + + + + + + + + + + + + image/svg+xml + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/default/assets/bloock.png b/public/default/assets/bloock.png new file mode 100644 index 00000000..58088375 Binary files /dev/null and b/public/default/assets/bloock.png differ diff --git a/public/default/assets/budgeting.avif b/public/default/assets/budgeting.avif new file mode 100644 index 00000000..4b6fc109 Binary files /dev/null and b/public/default/assets/budgeting.avif differ diff --git a/public/default/assets/coec.png b/public/default/assets/coec.png new file mode 100644 index 00000000..b146a0fc Binary files /dev/null and b/public/default/assets/coec.png differ diff --git a/public/default/assets/decidim.jpg b/public/default/assets/decidim.jpg new file mode 100644 index 00000000..03379eed Binary files /dev/null and b/public/default/assets/decidim.jpg differ diff --git a/public/default/assets/decidim.png b/public/default/assets/decidim.png new file mode 100644 index 00000000..5d73b3c6 Binary files /dev/null and b/public/default/assets/decidim.png differ diff --git a/public/default/assets/devices_vocdoni.png b/public/default/assets/devices_vocdoni.png new file mode 100644 index 00000000..c70fa4d6 Binary files /dev/null and b/public/default/assets/devices_vocdoni.png differ diff --git a/public/default/assets/elections.avif b/public/default/assets/elections.avif new file mode 100644 index 00000000..8f11e80d Binary files /dev/null and b/public/default/assets/elections.avif differ diff --git a/public/default/assets/erc.svg b/public/default/assets/erc.svg new file mode 100644 index 00000000..9c9c2fba --- /dev/null +++ b/public/default/assets/erc.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/public/default/assets/logo-classic.svg b/public/default/assets/logo-classic.svg new file mode 100644 index 00000000..137b88c0 --- /dev/null +++ b/public/default/assets/logo-classic.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/public/default/assets/mbl.png b/public/default/assets/mbl.png new file mode 100644 index 00000000..0806fca0 Binary files /dev/null and b/public/default/assets/mbl.png differ diff --git a/public/default/assets/omnium.png b/public/default/assets/omnium.png new file mode 100644 index 00000000..50194001 Binary files /dev/null and b/public/default/assets/omnium.png differ diff --git a/public/default/assets/online-survey.avif b/public/default/assets/online-survey.avif new file mode 100644 index 00000000..c300014e Binary files /dev/null and b/public/default/assets/online-survey.avif differ diff --git a/public/default/assets/online-voting.avif b/public/default/assets/online-voting.avif new file mode 100644 index 00000000..cced4de3 Binary files /dev/null and b/public/default/assets/online-voting.avif differ diff --git a/public/default/assets/pc.png b/public/default/assets/pc.png new file mode 100644 index 00000000..6ba9d61f Binary files /dev/null and b/public/default/assets/pc.png differ diff --git a/public/default/assets/pirates.svg b/public/default/assets/pirates.svg new file mode 100644 index 00000000..d23ca921 --- /dev/null +++ b/public/default/assets/pirates.svg @@ -0,0 +1,101 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/public/default/assets/software-integration.avif b/public/default/assets/software-integration.avif new file mode 100644 index 00000000..ef274b63 Binary files /dev/null and b/public/default/assets/software-integration.avif differ diff --git a/public/default/assets/solutions.png b/public/default/assets/solutions.png new file mode 100644 index 00000000..4e4d5530 Binary files /dev/null and b/public/default/assets/solutions.png differ diff --git a/public/default/assets/vocdoni.jpeg b/public/default/assets/vocdoni.jpeg new file mode 100644 index 00000000..c40959cf Binary files /dev/null and b/public/default/assets/vocdoni.jpeg differ diff --git a/public/default/assets/vocdoni_usage.jpg b/public/default/assets/vocdoni_usage.jpg new file mode 100644 index 00000000..9640c0be Binary files /dev/null and b/public/default/assets/vocdoni_usage.jpg differ diff --git a/public/default/assets/vocdoniapp.png b/public/default/assets/vocdoniapp.png deleted file mode 100644 index 4f072aa2..00000000 Binary files a/public/default/assets/vocdoniapp.png and /dev/null differ diff --git a/public/onvote/assets/governance-daoplugins.png b/public/onvote/assets/governance-daoplugins.png new file mode 100644 index 00000000..55c8556f Binary files /dev/null and b/public/onvote/assets/governance-daoplugins.png differ diff --git a/public/onvote/assets/governance-farcaster.png b/public/onvote/assets/governance-farcaster.png new file mode 100644 index 00000000..4347c621 Binary files /dev/null and b/public/onvote/assets/governance-farcaster.png differ diff --git a/public/onvote/assets/governance-onvote.png b/public/onvote/assets/governance-onvote.png new file mode 100644 index 00000000..14b96c1d Binary files /dev/null and b/public/onvote/assets/governance-onvote.png differ diff --git a/public/onvote/assets/governance-others.png b/public/onvote/assets/governance-others.png new file mode 100644 index 00000000..76a37f5a Binary files /dev/null and b/public/onvote/assets/governance-others.png differ diff --git a/src/components/Editor/Editor.tsx b/src/components/Editor/Editor.tsx index feb76df7..b1413f74 100644 --- a/src/components/Editor/Editor.tsx +++ b/src/components/Editor/Editor.tsx @@ -1,7 +1,7 @@ import { CodeHighlightNode, CodeNode } from '@lexical/code' import { AutoLinkNode, LinkNode } from '@lexical/link' import { ListItemNode, ListNode } from '@lexical/list' -import { TRANSFORMERS } from '@lexical/markdown' +import { $convertFromMarkdownString, TRANSFORMERS } from '@lexical/markdown' import { OverflowNode } from '@lexical/overflow' import { CharacterLimitPlugin } from '@lexical/react/LexicalCharacterLimitPlugin' import LexicalClickableLinkPlugin from '@lexical/react/LexicalClickableLinkPlugin' @@ -37,6 +37,7 @@ type EditorProps = { maxLength?: number onChange: (value: string) => void placeholder?: string + defaultValue?: string } const Editor = (props: EditorProps) => { @@ -44,6 +45,7 @@ const Editor = (props: EditorProps) => { const [floatingAnchorElem, setFloatingAnchorElem] = useState(null) const settings = { + editorState: () => $convertFromMarkdownString(props.defaultValue ?? '', TRANSFORMERS), namespace: '', // The editor theme theme: exampleTheme, diff --git a/src/components/Home/Governance.tsx b/src/components/Home/Governance.tsx new file mode 100644 index 00000000..b03826d9 --- /dev/null +++ b/src/components/Home/Governance.tsx @@ -0,0 +1,118 @@ +import { Box, Button, Card, CardBody, CardFooter, CardHeader, Flex, Icon, Img, Text, Link } from '@chakra-ui/react' +import { Trans, useTranslation } from 'react-i18next' +import { useMemo } from 'react' +import onvote from '/assets/governance-onvote.png' +import farcaster from '/assets/governance-farcaster.png' +import daoplugins from '/assets/governance-daoplugins.png' +import others from '/assets/governance-others.png' +import { Link as ReactRouterLink } from 'react-router-dom' +import { TFunction } from 'i18next' +import { useAccount } from 'wagmi' +import { useConnectModal } from '@rainbow-me/rainbowkit' + +interface IGovernanceCardProps { + buttonText: string + buttonColor: string + buttonAction: string | (() => void) + title: string + description: string + image: string +} + +const Governance = () => { + const { t } = useTranslation() + const { isConnected } = useAccount() + const { openConnectModal } = useConnectModal() + + const cards: IGovernanceCardProps[] = [ + { + buttonText: !isConnected ? t('menu.login') : t('web3cards.onvote.btn'), + buttonColor: 'web3_cta.onvote', + buttonAction: !isConnected + ? () => { + if (openConnectModal) openConnectModal() + } + : 'processes/create', + title: t('web3cards.onvote.title'), + description: t('web3cards.onvote.description'), + image: onvote, + }, + { + buttonText: t('web3cards.farcaster.btn'), + buttonColor: 'web3_cta.farcaster', + buttonAction: 'https://farcaster.vote', + title: t('web3cards.farcaster.title'), + description: t('web3cards.farcaster.description'), + image: farcaster, + }, + { + buttonText: t('web3cards.plugins.btn'), + buttonColor: 'web3_cta.plugins', + buttonAction: 'https://app.aragon.org', + title: t('web3cards.plugins.title'), + description: t('web3cards.plugins.description'), + image: daoplugins, + }, + { + buttonText: t('web3cards.others.btn'), + buttonColor: 'web3_cta.others', + buttonAction: 'mailto:info@vocdoni.org', + title: t('web3cards.others.title'), + description: t('web3cards.others.description'), + image: others, + }, + ] + return ( + <> + + {t('web3cards.title')} + + + {cards.map((card, i) => { + return ( + + + + + + + + {card.title} + + , + br:
, + }} + /> +
+
+ + {typeof card.buttonAction === 'string' ? ( + + ) : ( + + )} + +
+ ) + })} +
+ + ) +} + +export default Governance diff --git a/src/components/Home/Roadmap.tsx b/src/components/Home/Roadmap.tsx deleted file mode 100644 index 0dc3cfde..00000000 --- a/src/components/Home/Roadmap.tsx +++ /dev/null @@ -1,155 +0,0 @@ -import { Box, Checkbox, Flex, Text } from '@chakra-ui/react' -import { Trans, useTranslation } from 'react-i18next' - -const Roadmap = () => { - const { t } = useTranslation() - return ( - - - ), - p2: ( - - ), - }} - /> - - - - - {t('roadmap.milestone1')} - - - - - - {t('roadmap.complex_startegies_title')} - - - {t('roadmap.complex_startegies_description')} - - - - - - - - {t('roadmap.social_census_title')} - - - {t('roadmap.social_census_desciption')} - - - - - - - - {t('roadmap.versatil_title')} - - - {t('roadmap.versatil_description')} - - - - - - - - {t('roadmap.private_title')} - - - {t('roadmap.private_description')} - - - - - - - - {t('roadmap.chainlink_title')} - - - {t('roadmap.chainlink_description')} - - - - - - - {t('roadmap.milestone2')} - - - - - - - {t('roadmap.registry_title')} - - - {t('roadmap.registry_description')} - - - - - - - - {t('roadmap.census_title')} - - - {t('roadmap.census_description')} - - - - - - - - {t('roadmap.voting_title')} - - - {t('roadmap.voting_description')} - - - - - - - - {t('roadmap.rollup_title')} - - {t('roadmap.rollup_description')} - - - - - - - {t('roadmap.execution_title')} - - {t('roadmap.execution_description')} - - - - - - ) -} - -export default Roadmap diff --git a/src/components/Home/Voting.tsx b/src/components/Home/Voting.tsx index 2bf9aa64..6b9c9964 100644 --- a/src/components/Home/Voting.tsx +++ b/src/components/Home/Voting.tsx @@ -3,40 +3,38 @@ import { useTranslation } from 'react-i18next' import { BsArrowUpRight, BsStars } from 'react-icons/bs' import { TbDiscountCheckFilled } from 'react-icons/tb' -const VotingTypesBanner = () => { +const VotingTypesBanner = ({ app = 'Vocdoni App' }: { app?: string }) => { const { t } = useTranslation() return ( - <> - - - - - {t('banner_voting_types.anonymous_title')} - - - {t('banner_voting_types.anonymous_description')} - - - - - - {t('banner_voting_types.token_title')} - - - {t('banner_voting_types.token_description')} - - - - - - {t('banner_voting_types.flexible_title')} - - - {t('banner_voting_types.flexible_description')} - - - - + + + + + {t('banner_voting_types.anonymous_title')} + + + {t('banner_voting_types.anonymous_description', { app })} + + + + + + {t('banner_voting_types.token_title')} + + + {t('banner_voting_types.token_description', { app })} + + + + + + {t('banner_voting_types.flexible_title')} + + + {t('banner_voting_types.flexible_description', { app })} + + + ) } export default VotingTypesBanner diff --git a/src/components/Layout/Logo.tsx b/src/components/Layout/Logo.tsx index 314e94d3..ecbb8901 100644 --- a/src/components/Layout/Logo.tsx +++ b/src/components/Layout/Logo.tsx @@ -1,12 +1,15 @@ import { Flex } from '@chakra-ui/react' import { NavLink } from 'react-router-dom' -import { Logo as LogoImage } from '~theme/icons' +import { Logo as LogoImage, LogoMbl } from '~theme/icons' const Logo = ({ ...props }) => ( - + + + + ) diff --git a/src/components/Process/Aside.tsx b/src/components/Process/Aside.tsx index 64778f0e..d0e46691 100644 --- a/src/components/Process/Aside.tsx +++ b/src/components/Process/Aside.tsx @@ -99,13 +99,7 @@ const ProcessAside = () => { )} {voted !== null && voted.length > 0 && ( - + {t('aside.verify_vote_on_explorer')} )} @@ -200,8 +194,10 @@ export const VoteButton = ({ setQuestionsTab, ...props }: { setQuestionsTab: () fontSize='lg' height='50px' onClick={setQuestionsTab} - _disabled={{ - opacity: '0.8', + sx={{ + '&::disabled': { + opacity: '0.8', + }, }} /> )} diff --git a/src/components/Process/CreatedBy.tsx b/src/components/Process/CreatedBy.tsx index 5b285d08..2e505f38 100644 --- a/src/components/Process/CreatedBy.tsx +++ b/src/components/Process/CreatedBy.tsx @@ -38,7 +38,7 @@ export const LongOrganizationName = (props: TextProps) => { const CopyAddressBtn = () => { const { organization } = useOrganization() - const { onCopy } = useClipboard(organization?.address as string) + const { onCopy } = useClipboard(enforceHexPrefix(organization?.address)) const toast = useToast() const { t } = useTranslation() diff --git a/src/components/Process/Header.tsx b/src/components/Process/Header.tsx index f0d548b3..6a7f0fdb 100644 --- a/src/components/Process/Header.tsx +++ b/src/components/Process/Header.tsx @@ -1,5 +1,5 @@ -import { WarningIcon } from '@chakra-ui/icons' -import { Box, Button, Flex, Icon, Image, Text } from '@chakra-ui/react' +import { InfoIcon, WarningIcon } from '@chakra-ui/icons' +import { Box, Button, Flex, Icon, Image, SliderThumb, Text, Tooltip } from '@chakra-ui/react' import { ElectionDescription, ElectionSchedule, @@ -8,7 +8,7 @@ import { OrganizationName, } from '@vocdoni/chakra-components' import { useClient, useElection, useOrganization } from '@vocdoni/react-providers' -import { ElectionStatus } from '@vocdoni/sdk' +import { CensusType, ElectionStatus } from '@vocdoni/sdk' import { useTranslation } from 'react-i18next' import { Link } from 'react-router-dom' import { useReadMoreMarkdown } from '~components/Layout/use-read-more' @@ -16,12 +16,17 @@ import { GoBack } from '~theme/icons' import { ActionsMenu } from './ActionsMenu' import { CreatedBy } from './CreatedBy' import { ProcessDate } from './Date' +import { ShareModalButton } from '~components/Share' +import { useEffect, useState } from 'react' + +type CensusInfo = { size: number; weight: bigint; type: CensusType } const ProcessHeader = () => { const { t } = useTranslation() const { election } = useElection() const { organization, loaded } = useOrganization() - const { account } = useClient() + const { account, client } = useClient() + const [censusInfo, setCensusInfo] = useState() const { ReadMoreMarkdownWrapper, ReadMoreMarkdownButton } = useReadMoreMarkdown( 'rgba(242, 242, 242, 0)', 'rgba(242, 242, 242, 1)', @@ -30,7 +35,22 @@ const ProcessHeader = () => { ) const strategy = useStrategy() + // Get the census info to show the total size if the maxCensusSize is less than the total size + useEffect(() => { + ;(async () => { + try { + if (!election?.census?.censusId || !client) return + const censusInfo: CensusInfo = await client.fetchCensusInfo(election.census.censusId) + setCensusInfo(censusInfo) + } catch (e) { + // If the census info is not available, just ignore it + setCensusInfo(undefined) + } + })() + }, [election, client]) + const showOrgInformation = !loaded || (loaded && organization?.account?.name) + const showTotalCensusSize = censusInfo?.size && election?.maxCensusSize && election.maxCensusSize < censusInfo.size return ( @@ -48,28 +68,36 @@ const ProcessHeader = () => { - - - - {t('process.state')} - - - - - - {t('process.schedule')} - - + + + + + + {t('process.state')} + + + + + + + + + + {t('process.schedule')} + + + + + + {!election?.description?.default.length && ( @@ -118,9 +146,35 @@ const ProcessHeader = () => { {t('process.is_anonymous.description')} )} - - {t('process.census')} - {t('process.people_in_census', { count: election?.maxCensusSize })} + + + {t('process.census')} {showTotalCensusSize && } + + {showTotalCensusSize ? ( + + + {t('process.total_census_size', { + censusSize: censusInfo?.size, + maxCensusSize: election?.maxCensusSize, + })} + + + ) : ( + {t('process.people_in_census', { count: election?.maxCensusSize })} + )} {election?.meta?.census && ( @@ -128,7 +182,6 @@ const ProcessHeader = () => { {strategy} )} - {showOrgInformation && ( @@ -184,6 +237,7 @@ const useStrategy = () => { token: t('process.census_strategies.token', { token: election?.meta?.token }), web3: t('process.census_strategies.web3'), csp: t('process.census_strategies.csp'), + gitcoin: t('process.census_strategies.gitcoin'), } if (!election || (election && !election?.meta?.census)) return '' diff --git a/src/components/Process/View.tsx b/src/components/Process/View.tsx index 63ffd7d4..7942a95c 100644 --- a/src/components/Process/View.tsx +++ b/src/components/Process/View.tsx @@ -3,7 +3,6 @@ import { Box, Button, Flex, - Icon, Link, ListItem, Modal, @@ -29,12 +28,12 @@ import { ElectionResultsTypeNames, ElectionStatus, PublishedElection } from '@vo import { useEffect, useRef, useState } from 'react' import { FieldValues } from 'react-hook-form' import { Trans, useTranslation } from 'react-i18next' -import { FaFacebook, FaReddit, FaTelegram, FaTwitter } from 'react-icons/fa' import ReactPlayer from 'react-player' import ProcessAside, { VoteButton } from './Aside' import Header from './Header' import confirmImg from '/assets/spreadsheet-confirm-modal.jpg' import successImg from '/assets/spreadsheet-success-modal.jpg' +import { FacebookShare, RedditShare, TelegramShare, TwitterShare } from '~components/Share' export const ProcessView = () => { const { t } = useTranslation() @@ -79,9 +78,9 @@ export const ProcessView = () => { {election?.streamUri && ( @@ -180,12 +179,6 @@ const SuccessVoteModal = () => { const verify = environment.verifyVote(env, voted) const url = encodeURIComponent(document.location.href) const caption = t('process.share_caption', { title: election?.title.default }) - const linked = encodeURIComponent(`${caption} — ${document.location.href}`) - - const twitter = `https://twitter.com/intent/tweet?text=${linked}` - const facebook = `https://www.facebook.com/sharer/sharer.php?u=${url}` - const telegram = `https://t.me/share/url?url=${url}&text=${caption}` - const reddit = `https://reddit.com/submit?url=${url}&title=${caption}` return ( @@ -206,48 +199,16 @@ const SuccessVoteModal = () => { /> - - - + - - - + - - - + - - - + diff --git a/src/components/Process/VoteButton.tsx b/src/components/Process/VoteButton.tsx deleted file mode 100644 index 2d9adcdb..00000000 --- a/src/components/Process/VoteButton.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { VoteButton as CVoteButton } from '@vocdoni/chakra-components' - -const VoteButton = ({ ...props }) => ( - -) - -export default VoteButton diff --git a/src/components/ProcessCreate/Census/Gitcoin/GitcoinForm.tsx b/src/components/ProcessCreate/Census/Gitcoin/GitcoinForm.tsx new file mode 100644 index 00000000..7ee9463b --- /dev/null +++ b/src/components/ProcessCreate/Census/Gitcoin/GitcoinForm.tsx @@ -0,0 +1,123 @@ +import { + FormLabel, + NumberDecrementStepper, + NumberIncrementStepper, + NumberInput, + NumberInputField, + NumberInputStepper, + Slider, + SliderTrack, + SliderFilledTrack, + SliderThumb, + Grid, + Flex, + Checkbox, + Text, +} from '@chakra-ui/react' +import { Controller, useFormContext } from 'react-hook-form' +import { useTranslation } from 'react-i18next' +import { FC } from 'react' +import { StampCard } from '~components/ProcessCreate/Census/Gitcoin/StampCard' +import { GitcoinStampToken } from '~components/ProcessCreate/Census/Gitcoin/index' +import { StampsUnionType } from '~components/ProcessCreate/Census/Gitcoin/StampsUnionType' +import { MaxCensusSizeSelector } from '~components/ProcessCreate/Census/Token' +import { Census3Token } from '@vocdoni/sdk' + +interface IGitcoinFormProps { + gitcoinTokens: GitcoinStampToken[] +} + +export const GitcoinForm: FC = ({ gitcoinTokens }) => { + const { t } = useTranslation() + const { control, watch } = useFormContext() + + const required = { + value: true, + message: t('form.error.field_is_required'), + } + + const ct: Census3Token = watch('gitcoinGPSToken') + const strategySize: number = watch('strategySize') + + return ( + + ( + + onChange(e.target.checked as boolean)}> + + {t('form.process_create.census.weighted_voting_title')} + + + + {t('form.process_create.census.weighted_voting_description')} + + + )} + > + ( + <> + {t('form.process_create.census.gitcoin_passport_score')} + + { + onChange(Number(e)) + }} + > + + + + + + + { + onChange(Number(e)) + }} + > + + + + + + + + )} + > + <> + + {t('form.process_create.census.gitcoin_stamps')} + + + + {gitcoinTokens.map((token, i) => ( + + ))} + + + + + ) +} diff --git a/src/components/ProcessCreate/Census/Gitcoin/StampCard.tsx b/src/components/ProcessCreate/Census/Gitcoin/StampCard.tsx new file mode 100644 index 00000000..b684db18 --- /dev/null +++ b/src/components/ProcessCreate/Census/Gitcoin/StampCard.tsx @@ -0,0 +1,72 @@ +import { Box, Checkbox, Text, Card, CardHeader } from '@chakra-ui/react' +import { StampIcon } from '~components/ProcessCreate/Census/Gitcoin/StampIcon' +import { Controller, useFormContext } from 'react-hook-form' +import { GitcoinStampToken } from '~components/ProcessCreate/Census/Gitcoin/index' +import { CensusGitcoinValues } from '~components/ProcessCreate/StepForm/CensusGitcoin' + +interface IStampCardProps { + token: GitcoinStampToken +} + +export const StampCard: React.FC = ({ token }) => { + const stampId = token.symbol + const { control, setValue } = useFormContext() + + const switchOnChange = (value: boolean) => { + setValue(`stamps.${stampId}`, { ...token, isChecked: value }) + } + + return ( + { + return ( + switchOnChange(e.target.checked as boolean)} + isChecked={field.value.isChecked} + > + + + + + ) + }} + /> + ) +} + +type StampInnerBoxProps = { name: string; iconURI: string | undefined } + +const StampInnerBox: React.FC = ({ name, iconURI }) => { + const stampTitle = name.replace('Gitcoin Passport Score', '') + return ( + <> + + {stampTitle} + + ) +} + +export const StampPreviewCard: React.FC = (props) => { + return ( + + + + ) +} diff --git a/src/components/ProcessCreate/Census/Gitcoin/StampIcon.tsx b/src/components/ProcessCreate/Census/Gitcoin/StampIcon.tsx new file mode 100644 index 00000000..993b7228 --- /dev/null +++ b/src/components/ProcessCreate/Census/Gitcoin/StampIcon.tsx @@ -0,0 +1,8 @@ +import { Image } from '@chakra-ui/react' +import fallback from '/assets/default-avatar.png' + +export const StampIcon = ({ iconURI, alt }: { iconURI: string | undefined; alt?: string }) => { + const size = 5 + + return {alt} +} diff --git a/src/components/ProcessCreate/Census/Gitcoin/StampsUnionType.tsx b/src/components/ProcessCreate/Census/Gitcoin/StampsUnionType.tsx new file mode 100644 index 00000000..39e687cf --- /dev/null +++ b/src/components/ProcessCreate/Census/Gitcoin/StampsUnionType.tsx @@ -0,0 +1,55 @@ +import { Controller, useFormContext } from 'react-hook-form' +import { Box, Flex, FormLabel, Switch, Text } from '@chakra-ui/react' +import { useTranslation } from 'react-i18next' +import { CensusGitcoinValues } from '~components/ProcessCreate/StepForm/CensusGitcoin' + +export type StampsUnionTypes = 'AND' | 'OR' + +export const StampsUnionType = () => { + const { control, setValue } = useFormContext() + const { t } = useTranslation() + const switchOnChange = (value: string) => { + const newValue = value === 'OR' ? 'AND' : 'OR' + setValue('stampsUnionType', newValue) + } + + return ( + { + const isChecked = field.value === 'AND' + let description = t('form.process_create.census.gitcoin_strategy_description_OR') + if (isChecked) { + description = t('form.process_create.census.gitcoin_strategy_description_AND') + } + return ( + + + switchOnChange(e.target.value)} + /> + + {field.value} + + + + {description} + + + ) + }} + /> + ) +} diff --git a/src/components/ProcessCreate/Census/Gitcoin/index.tsx b/src/components/ProcessCreate/Census/Gitcoin/index.tsx new file mode 100644 index 00000000..5a45ea40 --- /dev/null +++ b/src/components/ProcessCreate/Census/Gitcoin/index.tsx @@ -0,0 +1,84 @@ +import { Alert, AlertIcon, Spinner, Stack } from '@chakra-ui/react' +import { useEffect, useMemo, useState } from 'react' +import { errorToString, useClient } from '@vocdoni/react-providers' +import { EnvOptions, TokenSummary, VocdoniCensus3Client } from '@vocdoni/sdk' +import { GitcoinForm } from '~components/ProcessCreate/Census/Gitcoin/GitcoinForm' +import Wrapper from '~components/ProcessCreate/Steps/Wrapper' +import { useFormContext } from 'react-hook-form' +import { useProcessCreationSteps } from '~components/ProcessCreate/Steps/use-steps' + +export type GitcoinStampToken = Omit & { + externalID: string // For stamp tokens externalID is not nullable +} + +export const GitcoinStrategyBuilder = () => { + const { env } = useClient() + + const { setValue } = useFormContext() + const { form } = useProcessCreationSteps() + + const [error, setError] = useState() + const [loading, setLoading] = useState(false) + const [gitcoinTokens, setGitcoinTokens] = useState([]) + + const client = useMemo( + () => + new VocdoniCensus3Client({ + env: env as EnvOptions, + }), + [env] + ) + + // UseEffect to fetch tokens and filter by the tag gitcoin + useEffect(() => { + // fetch tokens and chains + ;(async () => { + setLoading(true) + setError(undefined) + try { + // Get Gitcoin tokens + const tks = await client.getSupportedTokens() + let ct: TokenSummary | undefined + const gitcoinTokens = tks.filter((tk) => { + // Store gitcoin passport score on other place + if (tk.type === 'gitcoinpassport' && !tk.externalID) { + ct = tk + setValue('gitcoinGPSToken', tk) + setValue('chain', tk.chainID) + return false + } + return tk.type === 'gitcoinpassport' && tk.externalID + }) + setGitcoinTokens(gitcoinTokens as GitcoinStampToken[]) + + // Get gitcoin passport token info + if (!ct) throw new Error('Error finding gitcoin passport token') + const { size, timeToCreateCensus, accuracy } = await client.getStrategyEstimation( + ct.defaultStrategy, + form.electionType.anonymous + ) + setValue('accuracy', accuracy) + setValue('strategySize', size) + setValue('timeToCreateCensus', timeToCreateCensus) + } catch (err) { + setError(errorToString(err)) + setValue('gitcoinGPSToken', undefined) + setValue('chain', undefined) + setValue('strategySize', undefined) + } + setLoading(false) + })() + }, [client]) + + return ( + + {loading ? : !error && } + {error && ( + + + {error} + + )} + + ) +} diff --git a/src/components/ProcessCreate/Census/Token.tsx b/src/components/ProcessCreate/Census/Token.tsx index cbe223a7..665d4102 100644 --- a/src/components/ProcessCreate/Census/Token.tsx +++ b/src/components/ProcessCreate/Census/Token.tsx @@ -2,6 +2,7 @@ import { Alert, AlertIcon, Badge, + Button, Card, CardHeader, Flex, @@ -20,6 +21,8 @@ import { Stack, Text, Tooltip, + Wrap, + WrapItem, } from '@chakra-ui/react' import { errorToString, useClient } from '@vocdoni/react-providers' import { Census3Token, EnvOptions, ICensus3SupportedChain, TokenSummary, VocdoniCensus3Client } from '@vocdoni/sdk' @@ -30,12 +33,14 @@ import { Trans, useTranslation } from 'react-i18next' import { customStylesSelect, customStylesTokensSelect } from '~theme/tokenSelectStyles' import { useProcessCreationSteps } from '../Steps/use-steps' import selectComponents, { CryptoAvatar } from './select-components' +import { DefaultCensusSize } from '~constants' export interface FilterOptionOption - - - - - - {isDragActive ? ( - - {t('uploader.drop_here')} + + ( + ) => setValue('weightedVote', event.target.checked)} + onBlur={onBlur} + ref={ref} + isChecked={value} + w='full' + p={3} + maxW={100} + variant={'radiobox'} + > + + + + Weighted vote + + + + {t('form.process_create.spreadsheet.requirements.list_three')} - ) : ( - , - p2: , - }} - /> - )} - - - {fileErr} - + + )} + /> + + + + + + {isDragActive ? ( + + {t('uploader.drop_here')} + + ) : ( + , + p2: , + }} + /> + )} + + + {fileErr} + + ) diff --git a/src/components/ProcessCreate/Census/select-components.tsx b/src/components/ProcessCreate/Census/select-components.tsx index 05ee9144..d4b08636 100644 --- a/src/components/ProcessCreate/Census/select-components.tsx +++ b/src/components/ProcessCreate/Census/select-components.tsx @@ -13,15 +13,17 @@ import { FaEthereum } from 'react-icons/fa' import { FaPeopleGroup } from 'react-icons/fa6' import ethIcon from '/assets/eth.jpg' import polygonIcon from '/assets/polygon-matic.jpg' +import { useEffect, useState } from 'react' +import { getNetwork } from '@ethersproject/providers' const SingleValueToken = (props: SingleValueProps>) => { const { - data: { name, iconURI, ID }, + data: { name, iconURI, ID, chainID }, children, } = props return ( - + {children} ) @@ -52,9 +54,8 @@ const GroupHeadingToken = (props: GroupHeadingProps>) const OptionToken = (props: OptionProps>) => { const { children, - data: { type: groupType, name, iconURI, ID }, + data: { type: groupType, name, iconURI, ID, chainID }, } = props - if (groupType === 'request') { return ( @@ -72,7 +73,7 @@ const OptionToken = (props: OptionProps>) => { return ( - + {children} @@ -152,19 +153,34 @@ export const CryptoAvatar = ({ name, icon, id, + chainId, size, }: { name?: string icon?: string id?: string + chainId?: number size?: string -}) => ( - -) +}) => { + const [netName, setNetName] = useState('ethereum') + + useEffect(() => { + ;(async () => { + if (!chainId || chainId === 1) return + const network = await getNetwork(chainId) + setNetName(network.name) + })() + }, [chainId]) + + return ( + + ) +} diff --git a/src/components/ProcessCreate/Confirm/Census.tsx b/src/components/ProcessCreate/Confirm/Census.tsx index 2e825578..2b61320a 100644 --- a/src/components/ProcessCreate/Confirm/Census.tsx +++ b/src/components/ProcessCreate/Confirm/Census.tsx @@ -9,7 +9,7 @@ const Census = () => { form: { censusType }, } = useProcessCreationSteps() - if (censusType === 'token') return + if (censusType === 'token' || censusType === 'gitcoin') return if (censusType === 'spreadsheet') return if (censusType === 'csp') return diff --git a/src/components/ProcessCreate/Confirm/CensusToken.tsx b/src/components/ProcessCreate/Confirm/CensusToken.tsx index 7cc09cdb..b8fa79b7 100644 --- a/src/components/ProcessCreate/Confirm/CensusToken.tsx +++ b/src/components/ProcessCreate/Confirm/CensusToken.tsx @@ -1,21 +1,30 @@ -import { Flex, Text, Tooltip } from '@chakra-ui/react' +import { Flex, FormLabel, Grid, Switch, Text, Tooltip } from '@chakra-ui/react' import { useTranslation } from 'react-i18next' import { AiTwotoneQuestionCircle } from 'react-icons/ai' import { TokenPreview } from '../Census/Token' import { useProcessCreationSteps } from '../Steps/use-steps' +import { StampPreviewCard } from '~components/ProcessCreate/Census/Gitcoin/StampCard' const PreviewCensusToken = () => { const { t } = useTranslation() const { - form: { maxCensusSize, censusToken, chain, strategySize, accuracy, electionType }, + form: { gitcoinGPSToken, censusType, maxCensusSize, censusToken, chain, strategySize, accuracy, electionType }, } = useProcessCreationSteps() const size = maxCensusSize || 0 const max = strategySize || 0 + let token = censusToken + let chainName = chain.name + if (censusType === 'gitcoin') { + token = gitcoinGPSToken + chainName = 'Gitcoin' + } + return ( <> - + + {censusType === 'gitcoin' && } {t('form.process_create.census.max_census_slider_label')} @@ -44,4 +53,45 @@ const PreviewCensusToken = () => { ) } +const GitcoinStampsPreview = () => { + const { t } = useTranslation() + const { + form: { stamps, stampsUnionType }, + } = useProcessCreationSteps() + + const selectedStamps = Object.values(stamps).filter((stamp) => stamp.isChecked) + + if (selectedStamps.length === 0) { + return + } + + let description = t('form.process_create.census.gitcoin_strategy_description_OR') + if (stampsUnionType === 'AND') { + description = t('form.process_create.census.gitcoin_strategy_description_AND') + } + + return ( + <> + + {t('form.process_create.census.gitcoin_stamps')} + {stampsUnionType} + + {description} + + + + {Object.values(selectedStamps).map((token, i) => ( + + ))} + + + ) +} + export default PreviewCensusToken diff --git a/src/components/ProcessCreate/Meta.tsx b/src/components/ProcessCreate/Meta.tsx index 6ce9f047..fff2dbd6 100644 --- a/src/components/ProcessCreate/Meta.tsx +++ b/src/components/ProcessCreate/Meta.tsx @@ -20,6 +20,7 @@ const CreateProcessMeta = () => { } const title = watch('title') + const description = watch('description') const maxLengthTitle = 500 const maxLengthDescription = 10000 @@ -61,6 +62,7 @@ const CreateProcessMeta = () => { onChange={(text: string) => setValue('description', text)} placeholder={t('form.process_create.meta.description_placeholder').toString()} maxLength={maxLengthDescription} + defaultValue={description} /> diff --git a/src/components/ProcessCreate/ModalPro.tsx b/src/components/ProcessCreate/ModalPro.tsx new file mode 100644 index 00000000..6e3adec8 --- /dev/null +++ b/src/components/ProcessCreate/ModalPro.tsx @@ -0,0 +1,177 @@ +import { CalendarIcon } from '@chakra-ui/icons' +import { + Box, + Button, + Flex, + FormControl, + FormErrorMessage, + FormLabel, + Input, + Modal, + ModalBody, + ModalCloseButton, + ModalContent, + ModalHeader, + ModalOverlay, + Text, +} from '@chakra-ui/react' +import emailjs from '@emailjs/browser' +import { useEffect, useState } from 'react' +import { useForm } from 'react-hook-form' +import { Trans, useTranslation } from 'react-i18next' +import { AiFillGooglePlusCircle } from 'react-icons/ai' +import { FaRegCalendarAlt } from 'react-icons/fa' + +const ModalPro = ({ isOpen, onClose, reason }: { isOpen: boolean; onClose: () => void; reason: string }) => { + const { t } = useTranslation() + const [success, setSuccess] = useState(false) + const [error, setError] = useState(false) + const { + register, + handleSubmit, + formState: { errors }, + setValue, + } = useForm({ + defaultValues: { + name: '', + email: '', + reason: reason, + }, + }) + + const required = { + value: true, + message: t('form.error.field_is_required'), + } + + const emailjsEnabled = + import.meta.env.EMAILJS_PUBLIC_ID && import.meta.env.EMAILJS_SERVICE_ID && import.meta.env.EMAILJS_TEMPLATE_ID + + useEffect(() => { + setValue('reason', reason) + }, [reason]) + + useEffect(() => { + setSuccess(false) + setError(false) + }, []) + + const sendEmail = (form: any) => { + emailjs + .sendForm(import.meta.env.EMAILJS_SERVICE_ID, import.meta.env.EMAILJS_TEMPLATE_ID, form, { + publicKey: import.meta.env.EMAILJS_PUBLIC_ID, + }) + .then( + () => { + setSuccess(true) + }, + (error: any) => { + setError(true) + } + ) + } + + return ( + + + + {t('process_create.modal_pro.title')} + + + + , + bold: , + }} + /> + + + + {emailjsEnabled && ( + <> + { + e.stopPropagation() + handleSubmit(sendEmail)(e) + }} + > + + {t('process_create.modal_pro.form_description')} + + + + {t('process_create.modal_pro.form_name_label')} + + {!!errors.name && {errors.name?.message?.toString()}} + + + {t('process_create.modal_pro.form_email_label')} + + {!!errors.email && {errors.email?.message?.toString()}} + + + + {success && ( + + {t('process_create.modal_pro.success')} + + )} + {error && ( + + {t('process_create.modal_pro.error')} + + )} + + + + + + )} + + + + {t('process_create.modal_pro.scheudle_description')} + + + + + + + + + + + + + + + + ) +} + +export default ModalPro diff --git a/src/components/ProcessCreate/Questions/Question.tsx b/src/components/ProcessCreate/Questions/Question.tsx index 6f40afd9..875cd512 100644 --- a/src/components/ProcessCreate/Questions/Question.tsx +++ b/src/components/ProcessCreate/Questions/Question.tsx @@ -28,6 +28,8 @@ const Question = ({ index, remove }: Props) => { name: `questions.${index}.options`, }) + const description = watch(`questions.${index}.description`) + return ( { setValue(`questions.${index}.description`, text)} placeholder={t('form.process_create.question.description_placeholder').toString()} + defaultValue={description} /> diff --git a/src/components/ProcessCreate/Questions/VotingTypes.tsx b/src/components/ProcessCreate/Questions/VotingTypes.tsx new file mode 100644 index 00000000..f313dc32 --- /dev/null +++ b/src/components/ProcessCreate/Questions/VotingTypes.tsx @@ -0,0 +1,104 @@ +import { Box, Checkbox, Grid, Icon, Text, useDisclosure } from '@chakra-ui/react' +import { useState } from 'react' +import { Trans, useTranslation } from 'react-i18next' +import { CgMoreO } from 'react-icons/cg' +import ModalPro from '../ModalPro' +import { UnimplementedVotingType, useUnimplementedVotingType } from './useUnimplementedVotingType' +import { useVotingType, VotingType } from './useVotingType' + +const VotingTypes = () => { + const { t } = useTranslation() + const { isOpen, onOpen, onClose } = useDisclosure() + + const [reason, setReason] = useState('') + + const { defined, details } = useVotingType() + const { defined: unDefined, details: undDetails } = useUnimplementedVotingType() + + const [showProCards, setShowProCards] = useState(false) + + return ( + <> + + + {t('process_create.question.voting_type.title')} + + {t('process_create.question.voting_type.description')} + + + + {defined.map((ct: VotingType, index: number) => ( + + + + + , + }} + /> + + + {details[ct].description} + + ))} + {!!unDefined.length && ( + + + + + + + + {t('process_create.question.others.description')} + setShowProCards((prev) => !prev)} /> + + )} + + {showProCards && ( + <> + + {t('process_create.question.voting_type.pro')} + + + {unDefined.map((ct: UnimplementedVotingType, index: number) => ( + + + + + , + }} + /> + + + Pro + {undDetails[ct].description} + { + setReason(undDetails[ct].title) + onOpen() + }} + /> + + ))} + + + )} + + + ) +} + +export default VotingTypes diff --git a/src/components/ProcessCreate/Questions/index.tsx b/src/components/ProcessCreate/Questions/index.tsx index 458b3c15..c28385cf 100644 --- a/src/components/ProcessCreate/Questions/index.tsx +++ b/src/components/ProcessCreate/Questions/index.tsx @@ -4,6 +4,7 @@ import { useEffect, useState } from 'react' import { FieldError, FieldErrors, useFieldArray, useFormContext } from 'react-hook-form' import { useTranslation } from 'react-i18next' import Question from './Question' +import VotingTypes from './VotingTypes' interface CustomFieldError extends FieldError { index: number @@ -11,6 +12,7 @@ interface CustomFieldError extends FieldError { const CreateProcessQuestions = () => { const { t } = useTranslation() + const { watch, formState: { errors }, @@ -55,6 +57,7 @@ const CreateProcessQuestions = () => { return ( + {t('form.process_create.question.title')} diff --git a/src/components/ProcessCreate/Questions/useUnimplementedVotingType.ts b/src/components/ProcessCreate/Questions/useUnimplementedVotingType.ts new file mode 100644 index 00000000..713a232d --- /dev/null +++ b/src/components/ProcessCreate/Questions/useUnimplementedVotingType.ts @@ -0,0 +1,54 @@ +import { useTranslation } from 'react-i18next' + +import { GoNumber } from 'react-icons/go' +import { HiCheckBadge } from 'react-icons/hi2' +import { ImListNumbered } from 'react-icons/im' +import { MdOutlineLibraryAddCheck } from 'react-icons/md' + +export const UnimplementedVotingTypeMulti = 'multi' +export const UnimplementedVotingTypeApproval = 'approval' +export const UnimplementedVotingTypeParticipatory = 'participatory' +export const UnimplementedVotingTypeBorda = 'borda' + +export type UnimplementedVotingType = + | typeof UnimplementedVotingTypeMulti + | typeof UnimplementedVotingTypeApproval + | typeof UnimplementedVotingTypeParticipatory + | typeof UnimplementedVotingTypeBorda + +export const UnimplementedVotingType = [ + UnimplementedVotingTypeMulti as UnimplementedVotingType, + UnimplementedVotingTypeApproval as UnimplementedVotingType, + UnimplementedVotingTypeParticipatory as UnimplementedVotingType, + UnimplementedVotingTypeBorda as UnimplementedVotingType, +] + +export const useUnimplementedVotingType = () => { + const { t } = useTranslation() + return { + list: UnimplementedVotingType, + defined: import.meta.env.features.unimplemented_voting_type as UnimplementedVotingType[], + details: { + [UnimplementedVotingTypeMulti]: { + title: t('process_create.question.multi_choice.title'), + description: t('process_create.question.multi_choice.description'), + icon: MdOutlineLibraryAddCheck, + }, + [UnimplementedVotingTypeApproval]: { + title: t('process_create.question.approval_voting.title'), + description: t('process_create.question.approval_voting.description'), + icon: HiCheckBadge, + }, + [UnimplementedVotingTypeParticipatory]: { + title: t('process_create.question.participation_budgeting.title'), + description: t('process_create.question.participation_budgeting.description'), + icon: GoNumber, + }, + [UnimplementedVotingTypeBorda]: { + title: t('process_create.question.borda_count.title'), + description: t('process_create.question.borda_count.description'), + icon: ImListNumbered, + }, + }, + } +} diff --git a/src/components/ProcessCreate/Questions/useVotingType.ts b/src/components/ProcessCreate/Questions/useVotingType.ts new file mode 100644 index 00000000..c8cdcca7 --- /dev/null +++ b/src/components/ProcessCreate/Questions/useVotingType.ts @@ -0,0 +1,23 @@ +import { useTranslation } from 'react-i18next' +import { GiChoice } from 'react-icons/gi' + +export const VotingTypeSingle = 'single' + +export type VotingType = typeof VotingTypeSingle + +export const VotingTypes = [VotingTypeSingle as VotingType] + +export const useVotingType = () => { + const { t } = useTranslation() + return { + list: VotingTypes, + defined: import.meta.env.features.voting_type as VotingType[], + details: { + [VotingTypeSingle]: { + title: t('process_create.question.single_choice.title'), + description: t('process_create.question.single_choice.description'), + icon: GiChoice, + }, + }, + } +} diff --git a/src/components/ProcessCreate/Settings/Advanced.tsx b/src/components/ProcessCreate/Settings/Advanced.tsx index 672f7a4f..bcd5678f 100644 --- a/src/components/ProcessCreate/Settings/Advanced.tsx +++ b/src/components/ProcessCreate/Settings/Advanced.tsx @@ -1,56 +1,76 @@ -import { Box, Checkbox, Flex, Icon, Text } from '@chakra-ui/react' +import { Box, Checkbox, Grid, Icon, Text, useDisclosure } from '@chakra-ui/react' import { useFormContext } from 'react-hook-form' import { useTranslation } from 'react-i18next' import { BiCheckDouble } from 'react-icons/bi' import { FaUserSecret } from 'react-icons/fa' import { HiKey } from 'react-icons/hi2' +import ModalPro from '../ModalPro' const SettingsAdvanced = () => { const { t } = useTranslation() const { register } = useFormContext() + const { isOpen, onOpen, onClose } = useDisclosure() + + const numberOfFeatures = Object.values(import.meta.env.features.vote).filter((value) => value === true).length const features = import.meta.env.features.vote.anonymous || import.meta.env.features.vote.overwrite || - import.meta.env.features.vote.secret + import.meta.env.features.vote.secret || + import.meta.env.features.vote.customization if (!features) return null return ( - - - {t('form.process_create.behavior.title')} + <> + + + + {t('form.process_create.behavior.title')} + + + + {import.meta.env.features.vote.anonymous && ( + + + + {t('form.process_create.behavior.anonymous.title')} + + {t('form.process_create.behavior.anonymous.description')} + + )} + {import.meta.env.features.vote.secret && ( + + + + {t('form.process_create.behavior.secret.title')} + + {t('form.process_create.behavior.secret.description')} + + )} + {import.meta.env.features.vote.overwrite && ( + + + + {t('form.process_create.behavior.overwrite.title')} + + {t('form.process_create.behavior.overwrite.description')} + + )} + {import.meta.env.features.vote.customization && ( + + + + {t('form.process_create.behavior.customization.title')} + + Pro + {t('form.process_create.behavior.customization.description')} + + + )} + - - {import.meta.env.features.vote.anonymous && ( - - - - {t('form.process_create.behavior.anonymous.title')} - - {t('form.process_create.behavior.anonymous.description')} - - )} - {import.meta.env.features.vote.secret && ( - - - - {t('form.process_create.behavior.secret.title')} - - {t('form.process_create.behavior.secret.description')} - - )} - {import.meta.env.features.vote.overwrite && ( - - - - {t('form.process_create.behavior.overwrite.title')} - - {t('form.process_create.behavior.overwrite.description')} - - )} - - + ) } export default SettingsAdvanced diff --git a/src/components/ProcessCreate/StepForm/CensusGitcoin.tsx b/src/components/ProcessCreate/StepForm/CensusGitcoin.tsx new file mode 100644 index 00000000..cad25247 --- /dev/null +++ b/src/components/ProcessCreate/StepForm/CensusGitcoin.tsx @@ -0,0 +1,55 @@ +import { Box, Text } from '@chakra-ui/react' +import { FormProvider, SubmitHandler, useForm } from 'react-hook-form' +import { useTranslation } from 'react-i18next' +import { useProcessCreationSteps } from '../Steps/use-steps' +import { CensusTokenValues } from '~components/ProcessCreate/StepForm/CensusToken' +import { GitcoinStampToken, GitcoinStrategyBuilder } from '../Census/Gitcoin' +import { StampsUnionTypes } from '~components/ProcessCreate/Census/Gitcoin/StampsUnionType' +import { Census3Token } from '@vocdoni/sdk' + +type FormGitcoinStamps = Record + +export type CensusGitcoinValues = { + gitcoinGPSToken: Census3Token + passportScore: number + stamps: FormGitcoinStamps + stampsUnionType: StampsUnionTypes + gpsWeighted: boolean +} & Omit + +export const StepFormCensusGitcoin = () => { + const { t } = useTranslation() + const { form, setForm, next } = useProcessCreationSteps() + const methods = useForm({ + defaultValues: { + gitcoinGPSToken: form.gitcoinGPSToken, + maxCensusSize: form.maxCensusSize, + strategySize: form.strategySize, + chain: form.chain, + passportScore: form.passportScore, + stamps: form.stamps, + stampsUnionType: form.stampsUnionType, + gpsWeighted: form.gpsWeighted, + }, + }) + + const onSubmit: SubmitHandler = (data) => { + setForm({ ...form, ...data }) + next() + } + return ( + + + {t('census.gitcoin_title')} + + + {t('census.gitcoin_description')} + + + + + + + + ) +} diff --git a/src/components/ProcessCreate/StepForm/CensusWeb3.tsx b/src/components/ProcessCreate/StepForm/CensusWeb3.tsx index b1ad8f23..d7b136c5 100644 --- a/src/components/ProcessCreate/StepForm/CensusWeb3.tsx +++ b/src/components/ProcessCreate/StepForm/CensusWeb3.tsx @@ -48,7 +48,7 @@ export const StepFormCensusWeb3 = () => { return ( <> - + {t('census.wallet_address_title')} diff --git a/src/components/ProcessCreate/StepForm/Censuses.tsx b/src/components/ProcessCreate/StepForm/Censuses.tsx index 1f36b79d..f41d4568 100644 --- a/src/components/ProcessCreate/StepForm/Censuses.tsx +++ b/src/components/ProcessCreate/StepForm/Censuses.tsx @@ -56,3 +56,17 @@ export const CspCensus = () => ( ) + +const Gitcoin = lazy(() => { + if (import.meta.env.features._census.gitcoin) { + return import('./CensusGitcoin').then((module) => ({ default: module.StepFormCensusGitcoin })) + } + + return Promise.resolve({ default: () => <> }) +}) + +export const GitcoinCensus = () => ( + + + +) diff --git a/src/components/ProcessCreate/Steps/Census.tsx b/src/components/ProcessCreate/Steps/Census.tsx index 2322f43c..a7511ab2 100644 --- a/src/components/ProcessCreate/Steps/Census.tsx +++ b/src/components/ProcessCreate/Steps/Census.tsx @@ -1,11 +1,14 @@ -import { Box, Flex, Icon, Tab, TabList, TabPanel, TabPanels, Tabs, Text } from '@chakra-ui/react' +import { Box, Flex, Icon, Tab, TabList, TabPanel, TabPanels, Tabs, Text, useDisclosure } from '@chakra-ui/react' +import { useState } from 'react' import { useTranslation } from 'react-i18next' +import { CgMoreO } from 'react-icons/cg' import { Check } from '~theme/icons' import { CensusType, useCensusTypes } from '../Census/TypeSelector' +import { UnimplementedCensusType, useUnimplementedCensusTypes } from '../Census/UnimplementedTypeSelector' +import ModalPro from '../ModalPro' import { StepsNavigation } from './Navigation' import { StepsFormValues, useProcessCreationSteps } from './use-steps' import Wrapper from './Wrapper' -// import checkIcon from '/assets/check-icon.svg' export interface CensusValues { censusType: CensusType | null @@ -13,13 +16,20 @@ export interface CensusValues { export const Census = () => { const { t } = useTranslation() + const { isOpen, onOpen, onClose } = useDisclosure() + const { form, setForm } = useProcessCreationSteps() const { defined, details } = useCensusTypes() + const { defined: definedUnim, details: detailsUnim } = useUnimplementedCensusTypes() const { censusType } = form + const [reason, setReason] = useState('') + const [showProCards, setShowProCards] = useState(false) + return ( - + + {t('census.title')} @@ -35,23 +45,82 @@ export const Census = () => { if (defined[index] !== 'token' && 'maxCensusSize' in nform) { delete nform?.maxCensusSize } + + if (definedUnim[index - defined.length]) setReason(detailsUnim[definedUnim[index - defined.length]].title) + setForm(nform) }} variant='card' isLazy > - - {defined.map((ct: CensusType, index: number) => ( - - + + + {defined.map((ct: CensusType, index: number) => ( + setShowProCards(false)}> + + + + + {details[ct].title} + + {details[ct].description} + + + ))} + {!!definedUnim.length && ( + div:nth-of-type(2)': { + display: 'none', + }, + }} + > + + + {t('process_create.census.others_title')} + + {t('process_create.census.others_description')} + + setShowProCards((prev) => !prev)} + position='absolute' + w='100%' + h='100%' + left={0} + top={0} + /> + + )} + + + {showProCards && ( + <> + + {t('census.pro')} + - - {details[ct].title} + {definedUnim.map((ct: UnimplementedCensusType, index: number) => ( + { + e.preventDefault() + e.stopPropagation() + onOpen() + }} + > + + + {detailsUnim[ct].title} + + Pro + {detailsUnim[ct].description} + + ))} - {details[ct].description} - - ))} + + )} diff --git a/src/components/ProcessCreate/Steps/Confirm.tsx b/src/components/ProcessCreate/Steps/Confirm.tsx index f8aad589..ffea3a85 100644 --- a/src/components/ProcessCreate/Steps/Confirm.tsx +++ b/src/components/ProcessCreate/Steps/Confirm.tsx @@ -29,9 +29,12 @@ import { IPublishedElectionParameters, IQuestion, PlainCensus, + PublishedCensus, PublishedElection, + StrategyToken, UnpublishedElection, VocdoniCensus3Client, + CensusType as VocdoniCensusType, WeightedCensus, } from '@vocdoni/sdk' import { useEffect, useMemo, useState } from 'react' @@ -39,8 +42,10 @@ import { FormProvider, useForm } from 'react-hook-form' import { Trans, useTranslation } from 'react-i18next' import { useNavigate } from 'react-router-dom' import { IElection, IElectionWithTokenResponse } from 'vocdoni-admin-sdk' +import { StampsUnionTypes } from '~components/ProcessCreate/Census/Gitcoin/StampsUnionType' +import { CensusType } from '~components/ProcessCreate/Census/TypeSelector' +import { CensusGitcoinValues } from '~components/ProcessCreate/StepForm/CensusGitcoin' import { useCspAdmin } from '../Census/Csp/use-csp' -import { CensusType } from '../Census/TypeSelector' import Preview from '../Confirm/Preview' import { CostPreview } from '../CostPreview' import { CreationProgress, Steps } from '../CreationProgress' @@ -331,17 +336,35 @@ const getCensus = async (env: EnvOptions, form: StepsFormValues, salt: string) = } switch (form.censusType) { + case 'gitcoin': case 'token': const c3client = new VocdoniCensus3Client({ env, }) const retryTime = 5000 - const attempts = form.timeToCreateCensus / retryTime - c3client.queueWait.retryTime = retryTime // clamp attempts between 20 and 100 - c3client.queueWait.attempts = Math.min(Math.max(Math.ceil(attempts), 20), 100) + c3client.queueWait.attempts = 100 + + if (form.censusType === 'gitcoin') { + // Calculate the strategy id + const strategyID = await getGitcoinStrategyId(form, c3client) + + // Once strategy is created, we got the stimation again to update the awaiting time + const { timeToCreateCensus } = await c3client.getStrategyEstimation(strategyID, form.electionType.anonymous) + c3client.queueWait.attempts = 100 + + // Create the census + const census = await c3client.createCensus(strategyID, form.electionType.anonymous) + return new PublishedCensus( + census.merkleRoot, + census.uri, + census.anonymous ? VocdoniCensusType.ANONYMOUS : VocdoniCensusType.WEIGHTED, + census.size, + BigInt(census.weight) + ) + } return c3client.createTokenCensus( form.censusToken.ID, @@ -415,6 +438,70 @@ const electionFromForm = (form: StepsFormValues) => { } } +/** + * For a list of stamp keys, create a predicate like key1 AND (key2 AND (key3 AND key4)) + * + * @param symbols list of token symbols + * @param operator The operator to add, AND or OR for example + * @param index actual iteration index. + */ +const buildPredicate = (symbols: string[], operator: StampsUnionTypes, index: number = 0): string => { + // Base case: when we reach the lasts key + if (index === symbols.length - 2) { + return symbols[index] + ` ${operator} ` + symbols[index + 1] + } + // Recursive case: build the string with nesting + return symbols[index] + ` ${operator} (` + buildPredicate(symbols, operator, index + 1) + ')' +} + +/** + * Get strategy id for gitcoin census type. + * @param form + * @param c3client + */ +const getGitcoinStrategyId = async (form: CensusGitcoinValues, c3client: VocdoniCensus3Client) => { + let strategyTokens: Record = {} + let predicate = '' + if (Object.keys(form.stamps).length > 0) { + Object.entries(form.stamps).forEach(([key, token]) => { + if (!token.isChecked) return + const newToken: StrategyToken = { + ID: token.ID, + chainID: token.chainID, + externalID: token.externalID, + } + strategyTokens[key] = newToken + }) + + const gpsPredicate = form.gpsWeighted ? 'AND:mul' : 'AND' + const stampKeys = Object.keys(strategyTokens) + if (stampKeys.length === 1) { + predicate = `${gpsPredicate} (${stampKeys[0]} OR ${stampKeys[0]})` + } else if (stampKeys.length) { + predicate = `${gpsPredicate} (${ + buildPredicate(stampKeys, form.stampsUnionType) + ')'.repeat(stampKeys.length - 1) + }` // Add closing parentheses at the end + } + } + + const scoreToken = form.gitcoinGPSToken + strategyTokens[scoreToken.symbol] = { + ID: scoreToken.ID, + chainID: scoreToken.chainID, + minBalance: form.passportScore.toString(), + } + if (predicate.length == 0) { + if (form.gpsWeighted) { + predicate = `${scoreToken.symbol}` + } else { + predicate = `${scoreToken.symbol} AND ${scoreToken.symbol}` + } + } else { + predicate = `${scoreToken.symbol} ${predicate}` + } + return await c3client.createStrategy('gitcoin_onvote_' + Date.now(), predicate, strategyTokens) +} + export type CensusMeta = { type: CensusType fields: string[] diff --git a/src/components/ProcessCreate/Steps/Form.tsx b/src/components/ProcessCreate/Steps/Form.tsx index 4f361a0a..c96b748e 100644 --- a/src/components/ProcessCreate/Steps/Form.tsx +++ b/src/components/ProcessCreate/Steps/Form.tsx @@ -24,6 +24,7 @@ export const StepsForm = ({ steps, children, activeStep, next, prev, setActiveSt weightedVote: false, questions: [{ options: [{}, {}] }], addresses: [], + gpsWeighted: false, }) // reinitialize form if we have a draft and `state` is set in the URL diff --git a/src/components/ProcessCreate/Steps/use-steps.tsx b/src/components/ProcessCreate/Steps/use-steps.tsx index fe7e2698..6561c913 100644 --- a/src/components/ProcessCreate/Steps/use-steps.tsx +++ b/src/components/ProcessCreate/Steps/use-steps.tsx @@ -9,6 +9,7 @@ import { Census, CensusValues } from './Census' import { Checks } from './Checks' import { Confirm } from './Confirm' import { CensusCspValues } from '../StepForm/CensusCsp' +import { CensusGitcoinValues } from '~components/ProcessCreate/StepForm/CensusGitcoin' export interface StepsFormValues extends InfoValues, @@ -17,6 +18,7 @@ export interface StepsFormValues CensusWeb3Values, CensusTokenValues, CensusCspValues, + CensusGitcoinValues, CensusSpreadsheetValues {} export interface StepsState { diff --git a/src/components/Share/Facebook.tsx b/src/components/Share/Facebook.tsx new file mode 100644 index 00000000..f25815e3 --- /dev/null +++ b/src/components/Share/Facebook.tsx @@ -0,0 +1,10 @@ +import ShareButton, { ShareButtonProps } from '~components/Share/ShareButton' +import { FaFacebook } from 'react-icons/fa' + +const FacebookShare = ({ url, ...rest }: ShareButtonProps) => { + const facebook = `https://www.facebook.com/sharer/sharer.php?u=${url}` + + return +} + +export default FacebookShare diff --git a/src/components/Share/Mail.tsx b/src/components/Share/Mail.tsx new file mode 100644 index 00000000..30963f95 --- /dev/null +++ b/src/components/Share/Mail.tsx @@ -0,0 +1,14 @@ +import ShareButton, { ShareButtonProps } from '~components/Share/ShareButton' +import { IoMdMail } from 'react-icons/io' +import objectToGetParams from '~components/Share/utils' +import { useTranslation } from 'react-i18next' + +const MailShare = ({ subject, url, caption, ...rest }: ShareButtonProps & { subject?: string }) => { + const { t } = useTranslation() + const _subject = subject ?? t('share.mail_subject') + const mail = 'mailto:' + objectToGetParams({ _subject, body: caption ? caption + '\n' + url : url }) + + return +} + +export default MailShare diff --git a/src/components/Share/Reddit.tsx b/src/components/Share/Reddit.tsx new file mode 100644 index 00000000..413e027f --- /dev/null +++ b/src/components/Share/Reddit.tsx @@ -0,0 +1,10 @@ +import ShareButton, { ShareButtonProps } from '~components/Share/ShareButton' +import { FaReddit } from 'react-icons/fa' + +const RedditShare = ({ url, caption, ...rest }: ShareButtonProps) => { + const reddit = `https://reddit.com/submit?url=${url}&title=${caption}` + + return +} + +export default RedditShare diff --git a/src/components/Share/ShareButton.tsx b/src/components/Share/ShareButton.tsx new file mode 100644 index 00000000..707affeb --- /dev/null +++ b/src/components/Share/ShareButton.tsx @@ -0,0 +1,35 @@ +import { Icon, Link } from '@chakra-ui/react' +import { IconType } from 'react-icons' +import { useTranslation } from 'react-i18next' + +export type ShareButtonProps = { url: string; caption: string } & ShareIconProps + +interface ShareIconProps { + variant?: string + w?: number + h?: number +} + +type GenericShareButtonProps = { + shareUrl: string + icon: IconType + network?: string +} & ShareIconProps + +const ShareButton = ({ shareUrl, network = 'default', variant, icon, w = 6, h = 6 }: GenericShareButtonProps) => { + const { t } = useTranslation() + + return ( + + + + ) +} + +export default ShareButton diff --git a/src/components/Share/ShareModal.tsx b/src/components/Share/ShareModal.tsx new file mode 100644 index 00000000..1634d625 --- /dev/null +++ b/src/components/Share/ShareModal.tsx @@ -0,0 +1,107 @@ +import { useTranslation } from 'react-i18next' +import { + Button, + Flex, + Icon, + Input, + InputGroup, + Modal, + ModalBody, + ModalCloseButton, + ModalContent, + ModalFooter, + ModalHeader, + ModalOverlay, + Text, + useClipboard, + useDisclosure, + useToast, +} from '@chakra-ui/react' +import { LuShare } from 'react-icons/lu' +import { + FacebookShare, + MailShare, + RedditShare, + TelegramShare, + TwitterShare, + WhatsappShare, +} from '~components/Share/index' + +const ShareModalButton = ({ caption = '', text }: { caption?: string; text?: string }) => { + const { t } = useTranslation() + const { isOpen, onOpen, onClose } = useDisclosure() + const rawUrl = document.location.href.split('#')[0] // Remove the PK after the hash + const url = encodeURIComponent(rawUrl) + + const toast = useToast() + const { onCopy } = useClipboard(rawUrl as string) + const iconWidth = 9 + + return ( + <> + + + + + {t('share.modal_title')} + + + + + + + + + + + + + + + + + + + + + + + ) +} + +export default ShareModalButton diff --git a/src/components/Share/Telegram.tsx b/src/components/Share/Telegram.tsx new file mode 100644 index 00000000..1aea89af --- /dev/null +++ b/src/components/Share/Telegram.tsx @@ -0,0 +1,10 @@ +import ShareButton, { ShareButtonProps } from '~components/Share/ShareButton' +import { FaTelegram } from 'react-icons/fa' + +const TelegramShare = ({ url, caption, ...rest }: ShareButtonProps) => { + const telegram = `https://t.me/share/url?url=${url}&text=${caption}` + + return +} + +export default TelegramShare diff --git a/src/components/Share/Twitter.tsx b/src/components/Share/Twitter.tsx new file mode 100644 index 00000000..6a8991a8 --- /dev/null +++ b/src/components/Share/Twitter.tsx @@ -0,0 +1,11 @@ +import ShareButton, { ShareButtonProps } from '~components/Share/ShareButton' +import { FaTwitter } from 'react-icons/fa' + +const TwitterShare = ({ url, caption, ...rest }: ShareButtonProps) => { + const linked = encodeURIComponent(`${caption} — ${document.location.href}`) + const twitter = `https://twitter.com/intent/tweet?text=${linked}` + + return +} + +export default TwitterShare diff --git a/src/components/Share/Whatsapp.tsx b/src/components/Share/Whatsapp.tsx new file mode 100644 index 00000000..4bcd1e2c --- /dev/null +++ b/src/components/Share/Whatsapp.tsx @@ -0,0 +1,27 @@ +import ShareButton, { ShareButtonProps } from '~components/Share/ShareButton' +import { IoLogoWhatsapp } from 'react-icons/io' +import objectToGetParams from '~components/Share/utils' + +// THX https://github.com/nygardk/react-share/blob/master/src/WhatsappShareButton.ts + +function isMobileOrTablet() { + return /(android|iphone|ipad|mobile)/i.test(navigator.userAgent) +} + +function whatsappLink(url: string, { title, separator }: { title?: string; separator?: string }) { + return ( + 'https://' + + (isMobileOrTablet() ? 'api' : 'web') + + '.whatsapp.com/send' + + objectToGetParams({ + text: title ? title + separator + url : url, + }) + ) +} + +const WhatsappShare = ({ url, caption, ...rest }: ShareButtonProps) => { + const whatsapp = whatsappLink(url, { title: caption, separator: ' - ' }) + return +} + +export default WhatsappShare diff --git a/src/components/Share/index.tsx b/src/components/Share/index.tsx new file mode 100644 index 00000000..687af9f8 --- /dev/null +++ b/src/components/Share/index.tsx @@ -0,0 +1,7 @@ +export { default as TelegramShare } from './Telegram' +export { default as RedditShare } from './Reddit' +export { default as TwitterShare } from './Twitter' +export { default as FacebookShare } from './Facebook' +export { default as ShareModalButton } from './ShareModal' +export { default as MailShare } from './Mail' +export { default as WhatsappShare } from './Whatsapp' diff --git a/src/components/Share/utils.ts b/src/components/Share/utils.ts new file mode 100644 index 00000000..24499875 --- /dev/null +++ b/src/components/Share/utils.ts @@ -0,0 +1,7 @@ +export default function objectToGetParams(object: { [key: string]: string | number | undefined | null }) { + const params = Object.entries(object) + .filter(([, value]) => value !== undefined && value !== null) + .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`) + + return params.length > 0 ? `?${params.join('&')}` : '' +} diff --git a/src/constants/index.ts b/src/constants/index.ts index a60435c0..d7fc38ac 100644 --- a/src/constants/index.ts +++ b/src/constants/index.ts @@ -14,6 +14,9 @@ export const ExplorerBaseURL = explorer export const VocdoniEnvironment = evocdoni export const CensusPreviewRowsLimit = 10 +const defaultCensusSize = import.meta.env.DEFAULT_CENSUS_SIZE +export const DefaultCensusSize = defaultCensusSize + /** * Given an object of react-hook-form errors, determines if the specified mapped field is invalid (returns an error) * diff --git a/src/elements/Layout.tsx b/src/elements/Layout.tsx index 4693ab73..c5a29a88 100644 --- a/src/elements/Layout.tsx +++ b/src/elements/Layout.tsx @@ -1,7 +1,7 @@ import { Box, Flex, HStack, Text } from '@chakra-ui/react' import { Outlet } from 'react-router-dom' -import Navbar from '~components/Navbar' import Footer from '~theme/components/Footer' +import Navbar from '~theme/components/Navbar' const Layout = () => { return ( diff --git a/src/i18n/components.tsx b/src/i18n/components.tsx index b5510e56..1829520a 100644 --- a/src/i18n/components.tsx +++ b/src/i18n/components.tsx @@ -4,7 +4,7 @@ export const translations = (t: TFunction) => ({ actions: { cancel_description: t('cc.actions.cancel_description').toString(), cancel: t('cc.actions.cancel').toString(), - continue_description: t('cc.actions.cancel_description').toString(), + continue_description: t('cc.actions.continue_description').toString(), continue: t('cc.actions.continue').toString(), end_description: t('cc.actions.end_description').toString(), end: t('cc.actions.end').toString(), diff --git a/src/i18n/locales/ca.json b/src/i18n/locales/ca.json index e9adb0fa..a95fc6aa 100644 --- a/src/i18n/locales/ca.json +++ b/src/i18n/locales/ca.json @@ -6,12 +6,12 @@ "overwrite_votes_left_one": "Pots corregir el teu vot una vegada.", "overwrite_votes_left_many": "", "overwrite_votes_left_other": "Pots corregir el teu vot {{ count }} vegades.", - "submitting": "", + "submitting": "Enviant", "verify_vote_on_explorer": "Verifica el vot a l'explorador", "votes_one": "1 votant", "votes_many": "{{ count }} votants", "votes_other": "{{ count }} votants", - "voting_anonymous_advice": "" + "voting_anonymous_advice": "Si us plau, espera mentre la màgia té lloc.Això pot trigar uns minuts; no tanquis la finestra." }, "banner": { "start_now": "Crea una votació!", @@ -32,7 +32,8 @@ "actions": { "cancel": "Cancel·la immediatament el procés de votació, sense permetre nous vots i sense comptabilitzar resultats (precaució: no es pot desfer)", "cancel_description": "cancel·lant el procés de votació \"{{ election.title.default }}\"...\nAtenció: aquesta acció no es pot desfer i cancel·larà els vots que s'hagin emès.", - "continue": "Continua el procés immediatament, si s'ha pausat", + "continue": "Reobre el procés immediatament, si s'ha pausat", + "continue_description": "Reobrint el procés de votació \"{{ election.title.default }}\"", "end": "Finalitza immediatament el procés, impedint que es presentin nous vots, i mostra els resultats de votació (precaució: no es pot desfer)", "end_description": "Finalitzant el procés de votació \"{{ election.title.default }}\"...\nAtenció: aquesta acció no es pot desfer i els votants no podran emetre nous vots.", "error_title": "S'ha produït algun error en executar la transacció", @@ -93,6 +94,9 @@ "census": { "chain_required": "Necessites seleccionar una xarxa", "description": "Selecciona com s'autenticaran i identificaran els votants.", + "gitcoin_description": "Amb aquest cens, només els votants amb Gitcoin Passport podran votar, proporcionant un mètode de resistència contra atacs sybil. Pots definir la puntuació general (GPS) i diferents stamps que el votant ha de tenir.", + "gitcoin_title": "Gitcoin Passport", + "pro": "", "request_custom_token": "Sol·licitar Tokens Personalitzats", "social_address_title": "Usuaris de Github", "spreadsheet_title": "Dades personalitzades", @@ -129,7 +133,7 @@ }, "csp_census": { "github": { - "error_fetching_users": "", + "error_fetching_users": "S'ha produït un error en recuperar els usuaris", "selected_users": "Usuaris seleccionats:" } }, @@ -146,15 +150,15 @@ "faucet": { "advanced_settings": "Avançat...", "connect": "Si us plau, connecteu la vostra cartera", - "copy_package": "", - "copy_package_done": "", + "copy_package": "Copiar Paquet de Faucet", + "copy_package_done": "Copiat!", "description": "Per desenvolupar amb Vocdoni, necessites tokens per a certes accions (crear una elecció, canviar l'estat d'una elecció, etc.). El nombre de diferents factors com la mida del cens, la durada de l'elecció, els paràmetres de l'elecció, etc.", "general_information": { "description": "Quan sol·licites tokens de desenvolupament, rebràs {{ amount }} tokens. Pots reclamar des del faucet una vegada cada {{ waitHours }} hores.", "description2": "Si necessites més tokens per desenvolupar sobre la plataforma de Vocdoni, pots posar-te en contacte amb nosaltres a tokens (at) vocdoni.io", "title": "Informació General" }, - "package_success": "", + "package_success": "Felicitats! Aquí tens el teu Paquet de Faucet:", "recipient_address": "Introduïu l'adreça del destinatari on voleu rebre els tokens.", "request_description": "Per aconseguir més tokens de Vocdoni, has d'iniciar la sessió amb un compte de xarxes socials (per evitar-ne l'abús). Només demanem accés de lectura.", "request_tokens": { @@ -168,16 +172,30 @@ "blog": "Blog", "company": "Empresa", "contact": "Contacte", + "demo": "Tipus de Votació", + "demo1": "Elecció Simple", + "demo2": "Elecció Múltiple", + "demo3": "Votació per Aprovació", + "demo4": "Votació per Classificació", + "demo5": "Pressupostos Participatius", + "demo6": "Votació Ponderada", "developer_portal": "Portal de desenvolupadors", "developers": "Desenvolupadors", "discord": "Discord", "follow_us": "Segueix-nos", + "footer_subtitle": "El Protocol de Votació Global", "homepage": "Inici", "repos": "Codi Obert", "resources": "Recursos", "terms_and_privacy": "Termes d'ús i Política de privacitat", "text": "El futurde la votació en Web3 comença aquí", "tutorials": "Tutorials", + "uses_cases": "Casos d'Ús", + "uses_cases1": "Assemblees Generals d'Accionistes (AGMs)", + "uses_cases2": "Eleccions", + "uses_cases3": "Pressupostos Participatius", + "uses_cases4": "Votació Digital", + "uses_cases5": "Enquesta Digital Tancada", "vocdoni_api": "API de Vocdoni", "vocdoni_app": "APP de Vocdoni" }, @@ -202,7 +220,7 @@ "avatar_error": "URL no vàlida", "field_is_required": "Aquest camp és obligatori", "min_address": "Si us plau, proporciona com a mínim una adreça", - "min_users_address": "", + "min_users_address": "Si us plau, proporciona com a mínim una adreça", "recipient_address_invalid": "L'adreça especificada no sembla vàlida" }, "process_create": { @@ -211,6 +229,10 @@ "description": "La identitat dels votants serà completament anonimitzada, incloent per a l'organitzador. Cada votant haurà d'establir una contrasenya per garantir la confidencialitat de la seva identitat.", "title": "Anònim" }, + "customization": { + "description": "Selecciona el teu tema desitjat, colors, textos i afegeix la teva marca a la pàgina de votació. També hi ha opcions més complexes disponibles.", + "title": "Personalització" + }, "overwrite": { "description": "Si està marcat, els votants tindran l'opció de corregir el seu vot un cop.", "title": "Sobreescriure" @@ -237,6 +259,12 @@ "add_button": "Afegir", "add_new_address": "Afageix una nova adreça", "delete_web3_address": "Eliminar adreça amb index {{ index }}", + "gitcoin_description": "Crear un cens de votants elegibles proporcionant una llista", + "gitcoin_passport_score": "Puntuació del Gitcoin Passport", + "gitcoin_stamps": "Stamps", + "gitcoin_strategy_description_AND": "Per votar, els usuaris han de posseir tots els stamps seleccionats i tenir una Puntuació del Gitcoin Passport igual o superior.", + "gitcoin_strategy_description_OR": "Per votar, els usuaris han de posseir almenys un stamp i tenir una Puntuació del Gitcoin Passport igual o superior.", + "gitcoin_title": "Gitcoin Passport", "holders": "{{ holders }} titulars", "max_census_resum": "Tens un grup de {{ uniTokenHolders }} possibles votants de {{ symbol }}. Per reduir el cost del procés de votació, pots optar per limitar el nombre total de participants en el procés de votació. Això no afectarà a votants específics ni reduirà el cens; simplement reduirà el nombre màxim de vots que es poden emetre.", "max_census_slider_arialabel": "Selector de participació màxima esperada", @@ -252,7 +280,10 @@ "token_base_title": "Basat en token", "tokens_placeholder": "Cerca o enganxa la direcció del token", "wallet_address_description": "Identifica els teus votants per les seves adreces de cartera.", - "wallet_address_title": "Adreces de cartera" + "wallet_address_title": "Adreces de cartera", + "weight": "(Pes)", + "weighted_voting_description": "Quan s'activa, el teu poder de vot s'alinea amb la teva puntuació GPS. Si es desactiva, el vot de cada usuari té el mateix pes, establert en 1 punt.", + "weighted_voting_title": "Pes del Vot Basat en GPS" }, "confirm": { "anonymous": "Anònim", @@ -334,9 +365,9 @@ "weight": "Pes" } }, - "spreadsheet_total_rows_one": "El cens només conté un votant", + "spreadsheet_total_rows_one": "El cens té un sol registre", "spreadsheet_total_rows_many": "", - "spreadsheet_total_rows_other": "Grandària del cens: {{count}} votants", + "spreadsheet_total_rows_other": "El cens té {{ count }} registres", "steps": { "census": "Cens", "checks": "Organització", @@ -363,6 +394,218 @@ "info": "Si en necessiteu més, poseu-vos en contacte amb info@onvote.app", "title": "Obtenir VOC Tokens" }, + "home": { + "benefits": { + "card_1": { + "description": "La nostra solució és fins a 10 vegades més econòmica, fent que la votació sigui asequible per a tothom i millorant la democràcia.", + "title": "10 vegades més econòmic" + }, + "card_2": { + "description": "Estalvieu temps i esforç amb els processos optimitzats de Vocdoni, disfrutant de menors costos operatius.", + "title": "Estalvis de costos" + }, + "card_3": { + "description": "Augmenta la participació i la implicació amb una plataforma fàcil d'usar accessible per a tothom, independentment de la competència tècnica.", + "title": "Participació incrementada" + }, + "card_4": { + "description": "Vota des de qualsevol lloc, des de qualsevol dispositiu. Tan simple com això.", + "title": "Participació sense esforç" + }, + "card_5": { + "description": "Tingues la tranquil·litat de saber que els teus vots estan protegits gràcies als nostres avançats mètodes criptogràfics, els quals garanteixen la màxima seguretat, preservant l'anonimat del votant i la integritat de cada vot.", + "title": "Seguretat incomparable" + }, + "card_6": { + "description": "Fomenta la confiança i la transparència mitjançant la tecnologia blockchain, oferint a les parts interessades una visibilitat sense precedents en el procés de votació.", + "title": "Auditable i de codi obert" + }, + "subtitle_1": "Quan tries Vocdoni, obtens beneficis dissenyats especialment per a les teves necessitats de votació. La nostra tecnologia avançada facilita la transició cap a la democràcia digital per a organitzacions, comunitats i governs. Aquí et mostrem com Vocdoni afegeix valor:", + "title": "Beneficis" + }, + "clients_title": "Més de 100 organitzacions confien en nosaltres", + "contactus": { + "btn": "Contacta'ns", + "card_1": { + "description": "Explica'ns les teves necessitats i elaborarem una solució personalitzada per als teus desafiaments.", + "title": "Solucions a mida per a les teves necessitats úniques" + }, + "card_2": { + "description": "Vocdoni ofereix solucions tant d'autoservei com personalitzades, claus en mà, adaptades per a tu.", + "title": "Estarem amb tu en tot moment" + }, + "header": "Contacta'ns", + "title": "Col·laborem per trobar la solució perfecta per a tu" + }, + "create_process": { + "btn": "Prova-ho gratis", + "helper_1": "Processos de votació preparats per a producció i responsius", + "helper_2": "Gratuït fins a 100 votants", + "subtitle": "La participació fàcil. Vocdoni APP ofereix una experiència de votació assequible, fàcil d'usar i complint amb el GDPR que qualsevol pot utilitzar des de qualsevol lloc amb el seu PC, portàtil, tauleta i mòbil.", + "title": "Solució de votació segura, ràpida i verificable" + }, + "demo": { + "btn": "Veure Totes les Demostracions", + "census_type": { + "card_1": "Correu electrònic (GMail)", + "card_3": "Cens Obert", + "radio": "Cens / Autenticació", + "title": "Per Tipus de Cens" + }, + "description": "Descobreix el poder de Vocdoni a través de demos interactives, on pots explorar diferents escenaris de votació i experimentar de primera mà la transició perfecta a la democràcia digital. Troba el tipus de votació que desitges, prova-ho i vota amb facilitat.", + "header": "Demostració", + "title": "Prova la nostra solució", + "use_cases": { + "card_1": "AGMs", + "card_2": "Eleccions", + "card_3": "Pressupostos Participatius", + "card_4": "Votació en línia", + "card_5": "Integració de programari", + "card_6": "Enquesta en línia recopilada", + "radio": "Per Cas d'Ús", + "title": "Per Cas d'Ús" + }, + "voting_type": { + "card_1": "Elecció Simple", + "card_2": "Elecció Múltiple", + "card_3": "Vot d'Aprovació", + "card_4": "Vot de Pressupost", + "card_5": "Votació Classificada", + "card_6": "Votació Ponderada", + "radio": "Per Tipus de Votació", + "title": "Per Tipus de Votació" + } + }, + "faqs": { + "faq_1": { + "description": "Vocdoni es destaca per la seva experiència d'usuari fluida, característiques de seguretat avançades i transparència sense precedents. La nostra plataforma utilitza tecnologia de tall com ara proves de coneixement zero i blockchain per garantir una votació segura i anònima alhora que proporciona un procés transparent i verificable.", + "title": "Què fa que Vocdoni sigui diferent d'altres plataformes de votació?" + }, + "faq_2": { + "description": "Sí, Vocdoni segueix estrictament les directrius del GDPR, garantint que les dades de l'usuari es tractin amb el màxim respecte i confidencialitat. La nostra plataforma prioritza la privacitat i la seguretat de les dades en cada etapa del procés de votació.", + "title": "Vocdoni compleix amb els reglaments de privacitat de dades?" + }, + "faq_3": { + "description": "Absolutament! Vocdoni ofereix opcions de personalització per alinear-se amb la marca de la vostra organització, assegurant una integració perfecta amb els vostres sistemes existents i mantenint una experiència de marca consistent per als votants. Poseu-vos en contacte amb nosaltres per saber-ne més!", + "title": "Es pot personalitzar Vocdoni per ajustar-se a la marca de la nostra organització?" + }, + "faq_4": { + "description": "Oferim el `Vocdoni SDK` i els `Components d'interfície d'usuari de Vocdoni`, dissenyats per a una integració fàcil amb el vostre programari o plataforma. Els hem creat per facilitar la feina dels desenvolupadors i poder començar a veure resultats des del primer dia. El nostre equip d'experts us guiarà en el procés d'integració per garantir una transició suau i minimitzar les interrupcions a les vostres operacions.", + "title": "Quan fàcil és integrar Vocdoni amb els nostres sistemes existents?" + }, + "faq_5": { + "description": "Vocdoni ofereix un suport complet per ajudar-vos en cada pas del vostre viatge de votació. Des de la configuració inicial i la personalització fins al manteniment continu i la resolució de problemes, el nostre equip de suport dedicat està aquí per ajudar-vos a maximitzar el valor de la nostra plataforma.", + "title": "Quin tipus de suport ofereix Vocdoni?" + }, + "faq_6": { + "description": "Sí, Vocdoni està preparat per gestionar eleccions de qualsevol magnitud, des de petites votacions comunitàries fins a eleccions públiques a gran escala. La nostra infraestructura escalable garanteix un rendiment fiable fins i tot durant els períodes de votació més alts, permetent-vos involucrar els votants de manera eficaç i eficient.", + "title": "Pot Vocdoni gestionar eleccions a gran escala?" + }, + "faq_7": { + "description": "La seguretat és la nostra prioritat a Vocdoni. La nostra plataforma utilitza tècniques criptogràfiques avançades i tecnologia blockchain per garantir el màxim nivell de seguretat, mantenint la integritat del vot i protegint l'anonimat del votant. Amb Vocdoni, podeu confiar que el vostre procés de votació és segur i a prova de manipulacions.", + "title": "Quan segur és la plataforma de Vocdoni?" + }, + "faq_8": { + "description": "Vocdoni utilitza tècniques criptogràfiques avançades i tecnologia blockchain per garantir la seguretat i la integritat del procés de votació. Cada vot es registra com una transacció de blockchain a prova de manipulacions, proporcionant transparència i verificabilitat a totes les parts interessades. A més, les mesures de seguretat rigoroses de Vocdoni protegeixen contra la manipulació o la manipulació, assegurant que cada vot es compti amb precisió.", + "title": "Com garanteix Vocdoni la integritat del procés de votació?" + }, + "subtitle": "A continuació, trobareu respostes a les preguntes que ens solen fer, guanyant claredat i confiança sobre la vostra experiència de votació digital.", + "title": "Preguntes Freqüents" + }, + "features": { + "card_1": { + "description": "Des de referèndums simples de sí/no fins a eleccions complexes de preferència classificada, Vocdoni admet una àmplia gamma de tipus de votació per adaptar-se a qualsevol procés de presa de decisions.", + "title": "Tipus de votació versàtils" + }, + "card_2": { + "description": "Personalitzeu cada aspecte del vostre procés de votació, des de la marca i els temes fins als dissenys de paperetes, assegurant una integració perfecta amb la identitat de la vostra organització.", + "title": "Capacitats de personalització" + }, + "card_3": { + "description": "Utilitzant interfícies intuitives i processos d'introducció guiats, Vocdoni garanteix que els organitzadors i els votants puguin navegar fàcilment per la plataforma des del primer dia.", + "title": "Onboarding Amigable amb l'usuari" + }, + "card_4": { + "description": "Utilitzant mètodes d'autenticació robustos com ara l'autenticació de dos factors, Vocdoni protegeix contra l'accés no autoritzat i garanteix la integritat de cada vot.", + "title": "Autenticació segura" + }, + "card_5": { + "description": "Creeu llistes de votants personalitzades (censos personalitzats, oAuth, CRM...), permetent una difusió dirigida i experiències de votació adaptades per a diferents segments de la vostra audiència.", + "title": "Censos personalitzats" + }, + "card_6": { + "description": "Personalitzeu els vostres processos de votació amb dates d'inici i finalització específiques, inici automàtic, resultats secrets fins al final, actualitzacions en temps real, votació anònima, votació ponderada i personalització de paperetes.", + "title": "Opcions de votació flexibles" + }, + "card_7": { + "description": "Arribeu a una audiència diversa amb suport multilingüe, permetent que els participants es relacionin en el seu idioma preferit per a experiències de votació inclusives i accessibles.", + "title": "Suport multilingüe" + }, + "card_8": { + "description": "Superviseu el progrés i els resultats de la votació en temps real, amb actualitzacions instantànies i notificacions que mantinguin informats els organitzadors i els participants durant tot el procés de votació.", + "title": "Actualitzacions en temps real" + }, + "card_9": { + "description": "Permeteu als votants emetre els seus vots a través de diversos canals, inclosos navegadors web, mòbils, tauletes, etc., garantint l'accessibilitat i la comoditat per a tots els participants.", + "title": "Suport multiplataforma" + }, + "header": "Solucions capacitant", + "subtitle_1": "Desbloquegeu el potencial de la plataforma rica en funcionalitats de Vocdoni, que ofereix un valor excepcional tant per als organitzadors com per als votants. Personalitzeu de manera transparent els vostres processos de votació, assegureu eleccions segures i transparents i doneu poder als participants amb opcions intuïtives i accessibles. Amb l'APP de Vocdoni, experimenteu el poder de l'engagement millorat, l'eficiència i la confiança en cada experiència de votació.", + "title": "Característiques" + }, + "process": { + "description_1": "L'APP de Vocdoni us permet crear el procés de votació que necessiteu en 4 senzills passos.", + "description_2": "Us permetem configurar amb un sol clic si voleu votació anònima, resultats secrets fins al final, el tipus de votació, carregar el cens i molt més.", + "header": "Procés", + "step_1": { + "description": "Obtingueu un control total amb l'opció d'autoservei de la nostra plataforma, que ofereix una flexibilitat sense igual. Necessiteu personalització o orientació? Us tenim cobert!", + "title": "Inicieu el vostre propi procés de votació" + }, + "step_2": { + "description": "Us guiaran en el procés de creació per seleccionar el tipus de votació, configurar la informació, seleccionar les opcions necessàries i registrar els participants (cens).", + "title": "Configureu i publiqueu" + }, + "step_3": { + "description": "Els votants s'autentiquen i envien els seus vots de manera fluida a través del nostre flux d'usuari amigable, dissenyat per a l'accessibilitat universal en tots els dispositius.", + "title": "Període de votació" + }, + "step_4": { + "description": "Tan fàcil com això. Un cop acabat, els resultats es calcularan, publicaran i estaran disponibles per a qualsevol de manera automàtica.", + "title": "Obtén els resultats" + }, + "title": "Un senzill procés de 4 passos" + }, + "solutions": { + "card_1": { + "description": "Optimitzeu la presa de decisions amb una votació segura i transparent. Involucreu els membres de manera efectiva, estalvieu en costos administratius i fomenta una cultura de participació i transparència.", + "title": "Per a Organitzacions" + }, + "card_2": { + "description": "Millora la implicació de la comunitat amb una plataforma fàcil d'utilitzar que fomenta la participació i dóna veu a cada membre.", + "title": "Per a Comunitats" + }, + "card_3": { + "description": "Augmenta la participació electoral, redueix els costos electorals i millora la confiança ciutadana amb un procés de votació verificable i segur.", + "title": "Per a Govern i Institucions Públiques" + }, + "header": "Abraça el canvi amb Vocdoni", + "img_card_1": "Organitzacions", + "img_card_2": "Comunitats", + "img_card_3": "Govern i Institucions Públiques", + "subtitle_1": "Oferim solucions per a una àmplia gamma de casos d'ús, demostrant la versatilitat de l'aplicació Vocdoni.", + "title": "La nostra solució s'adapta a les vostres necessitats" + }, + "support": { + "btn_contact": "Contacta amb nosaltres", + "btn_watch": "Programa una trucada", + "header": "Proveu-ho! Obtingueu suport gratuït", + "helper_1": "Trucada gratuïta amb els nostres experts", + "helper_2": "Redueix els costos i les hores de treball", + "helper_3": "Costos competitives", + "subtitle": "Podem ajudar-vos a crear la vostra experiència de votació perfecta", + "title": "Comenceu a donar veu a la vostra comunitat" + } + }, "home_features": { "anonymous_description": "Desbloqueja la veritable democràcia amb zkSNARKs! Els participants poden emetre els seus vots amb total anonimat, assegurant que cada veu sigui escoltada sense compromisos.", "anonymous_title": "Anònim", @@ -400,7 +643,7 @@ "documentation": "Documentació", "get_more": "Obtenir-ne més", "languages": "Idiomes", - "languages_list": "", + "languages_list": "Mostrar idiomes disponibles", "login": "Iniciar sessió", "logout": "Tancar sessió", "my_org": "La Meva Org", @@ -408,6 +651,9 @@ "organization": "Edita Organització", "privacy": "Privadesa", "terms": "Termes", + "vocdoni": { + "login": "Login" + }, "wallet": "Cartera" }, "meta": { @@ -440,6 +686,7 @@ "census": "Cens", "census_strategies": { "csp": "CSP", + "gitcoin": "Gitcoin", "spreadsheet": "CSV", "token": "Token ({{ token.type, uppercase }}): {{ token.symbol }}", "web3": "Adreces Web3" @@ -480,7 +727,7 @@ "canceled": "Procés cancel·lat", "ended": "Finalitzat", "paused": "Procés en pausa", - "paused_description": "", + "paused_description": "No és possible votar ara mateix.", "unknown": "Desconegut", "upcoming": "Previst" }, @@ -490,6 +737,8 @@ "text": "

El teu vot ha estat emès i emmagatzemat de forma segura a la cadena de blocs de Vocdoni. Pots comprovar-ho aquí.

La democràcia és important, la pots compartir amb la teva comunitat i amics:

", "title": "Vot emès correctament!" }, + "total_census_size": "{{maxCensusSize}} electors permesos de {{censusSize}} totals en el cens", + "total_census_size_tooltip": "El nombre màxim d'electors permesos està limitat a {{maxCensusSize}} d'un cens de {{censusSize}} ({{percent}}% del total). Només els primers {{maxCensusSize}} electors poden votar.", "voters": "Votants" }, "process_actions": { @@ -504,7 +753,19 @@ "legal_note": "Avís legal: " }, "census": { - "mandatory_max_census_size": "Ha d'haver-hi un votant com a mínim", + "crm_description": "Si ja tens el teu propi CRM, el podem integrar per permetre que la teva comunitat voti amb les mateixes credencials.", + "crm_title": "Integració CRM", + "database_description": "Integra la votació directament amb els usuaris a la teva base de dades. Una manera senzilla de votar per a la teva comunitat.", + "database_title": "Base de Dades", + "digital_certificate_description": "Donem suport a diferents certificats digitals, per obtenir la màxima seguretat i validació d'identitat.", + "digital_certificate_title": "Certificat Digital", + "email_description": "Els votants validaran el seu correu electrònic autenticant-se en el seu compte de Google Mail (OAuth).", + "email_title": "Per Correu Electrònic", + "mandatory_max_census_size": "Com a mínim un votant ha de ser seleccionat", + "others_description": "Necessites una solució específica? Podem ajudar amb una integració personalitzada que s'adapti a les teves necessitats.", + "others_title": "Altres", + "phone_description": "Els votants hauran de validar el seu telèfon mòbil, introduint el seu número i el codi de verificació que rebran.", + "phone_title": "Per Número de Telèfon", "total_tokens_one": "1 token", "total_tokens_many": "", "total_tokens_other": "{{ count }} tokens" @@ -516,8 +777,54 @@ }, "creation_steps_description": "Necessites signar les transaccions per crear el procés de votació a la cadena de blocs de Vocdoni. No tanquis aquesta finestra.", "creation_steps_title": "Progrés", + "modal_pro": { + "description": "

L'opció seleccionada no està disponible en el pla d'autoatenció (gratuït). Per poder utilitzar-la, has de comprar el pla de projecte personalitzat

", + "error": "Error en l'enviament del correu electrònic, intenta-ho més tard o contacta'ns a info@vocdoni.org", + "form_btn": "ENVIAR", + "form_description": "Pots deixar-nos les teves dades de contacte i ens posarem en contacte amb tu per parlar de les teves necessitats", + "form_email_label": "Correu electrònic:", + "form_email_placeholder": "El teu correu electrònic", + "form_name_label": "Nom:", + "form_name_placeholder": "El teu nom", + "scheudle_btn": "RESERVAR ARA", + "scheudle_description": " O pots reservar directament una trucada ràpida (15 minuts) amb els nostres experts per ajudar-te a crear el teu projecte de votació desitjat", + "success": "Correu electrònic enviat correctament", + "title": "Contacta'ns" + }, "preview": { "accuracy": "Precisió dels Saldos de Cens Anònims: " + }, + "question": { + "approval_voting": { + "description": "Els votants poden votar per tants candidats com aprovin, i el candidat amb més vots guanya.", + "title": "Votació per Aprovació" + }, + "borda_count": { + "description": "Els votants classifiquen els candidats, i es assignen punts en funció de la seva classificació. El candidat amb més punts guanya.", + "title": "

Elecció per Rang

(Comptatge Borda)

" + }, + "multi_choice": { + "description": "Els votants trien un nombre predeterminat d'opcions, i l'opció amb més votants guanya.", + "title": "

Votació Plurinominal

(Elecció Múltiple)

" + }, + "others": { + "description": "", + "title": "" + }, + "participation_budgeting": { + "description": "Els votants poden distribuir un nombre predeterminat entre totes les opcions/projectes (que representen el pressupost total disponible) com desitgin.", + "title": "Pressupost Participatiu" + }, + "single_choice": { + "description": "Els votants trien una opció/candidat, i l'opció/candidat amb més vots guanya.", + "title": "

Votació Plurinominal

(Elecció Individual)

" + }, + "voting_type": { + "description": "Selecciona el tipus de votació per a aquesta elecció. Si no trobes el que necessites, pots contactar-nos.", + "pro": "", + "pro_census": "", + "title": "Tipus de Votació" + } } }, "rainbow": { @@ -527,30 +834,13 @@ "recovery": "Recuperar compte" }, "retry": "Reintenta", - "roadmap": { - "census_description": "Crear un cens descentralitzat fora de la cadena i compatible amb zk a partir d'una instantània de blocs d'Ethereum, amb verificació a la cadena.", - "census_title": "zkCensus:", - "chainlink_description": "Habilitar l'execució automàtica amb suport optimista.", - "chainlink_title": "Integració de Chainlink:", - "complex_startegies_description": "Integrar qualsevol token de qualsevol cadena de blocs utilitzant una combinació de lògica i operacions algebraiques, directament. Per exemple,", - "complex_startegies_title": "Estratègies complexes:", - "execution_description": "Enviar la prova final a la cadena per a una execució vinculant.", - "execution_title": "Execució:", - "milestone1": "Fita 1: Governança flexible sense gasos", - "milestone2": "Fita 2: zkRollup per a votacions vinculants segures i anònimes", - "private_description": "Mostrar el contingut de la proposta de votació només als membres de la comunitat.", - "private_title": "Metadades de votació privada:", - "registry_description": "Un registre global a la cadena per a identitats zk (coneixement zero).", - "registry_title": "zkRegistry:", - "rollup_description": "Consolidar tots els vots en una única prova zk segura mitjançant la tecnologia de zkRollup personalitzada.", - "rollup_title": "zkRollup:", - "social_census_desciption": "Millora el teu cens amb identitats socials de plataformes com ara Github o Twitter. Inclou mecanismes de resistència a Sybil com Holonym o Proof-of-Humanity.", - "social_census_title": "Cens social:", - "title": "Pla d'acció 2024 Què ve després?", - "versatil_description": "Ja sigui votació simple, escalonada o quadràtica, Onvote admet diversos mètodes de votació per adaptar-se a les diverses necessitats de la comunitat.", - "versatil_title": "Mètodes de votació versàtils:", - "voting_description": "Facilitar la votació anònima, fora de la cadena (sense gasos) per a qualsevol votant eligible mitjançant la xarxa p2p de Vocdoni.", - "voting_title": "zkVoting:" + "share": { + "copy": "Copia", + "election_share_btn_text": "Comparteix", + "election_share_text": "Mira aquesta elecció!", + "icon_title": "compartir enllaç", + "mail_subject": "Plataforma de votació", + "modal_title": "Comparteix amb amics" }, "uploader": { "click_or_drag_and_drop": "Puja o arrossega i deixa anar aquí la llista de votants(Formats permesos: CSV, XLSX i ODS)", @@ -560,6 +850,29 @@ "read_less": "Llegeix menys", "read_more": "Llegeix més" }, + "web3cards": { + "farcaster": { + "btn": "Ves a Farcaster.vote", + "description": "Amb \"Farcaster.vote\" permetem una manera simple i social de votar. Els teus membres només han de votar a través de Farcaster en 2 simples clics. Prova a crear una votació amb Farcaster frame", + "title": "Farcaster" + }, + "onvote": { + "btn": "Crear", + "description": "Sistema Web3 de votació segur, sense taxes i transparent. Permet crear cens de tokens, cens multichain, votació anònima i cens sybil-resistant amb un tipus de votació flexible.", + "title": "Onvote" + }, + "others": { + "btn": "Contacta'ns", + "description": "Hem construït un conjunt de votacions que permet una integració fàcil amb la nostra Blockchain dedicada a votacions a través del nostre SDK &; Components UI.

Comença a integrar-ho ara!", + "title": "Altres" + }, + "plugins": { + "btn": "Prova la Aragon App", + "description": "Hem implementat una solució de votació sense taxes personalitzada per a la DAO d'Aragon, i podem fer el mateix per a tu!

Comença amb votació sense taxes!", + "title": "DAO Plugins" + }, + "title": "Les nostres solucions de governança Web3" + }, "welcome": { "description": "Per començar, has de registrar el teu compte i el teu SIK (clau d'identitat segura) firmant dues transaccions (és segur i gratuït).", "intro": "Amb aquesta dApp pots crear votacions anònimes, segures i verificables.", diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index f3e925f7..883e27c3 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -9,7 +9,7 @@ "verify_vote_on_explorer": "Verify your vote on the explorer", "votes_one": "1 voter", "votes_other": "{{ count }} voters", - "voting_anonymous_advice": "Please wait while the anonymous magic 🪄 happens.\nThis can take up to several minutes; don't close the window." + "voting_anonymous_advice": "Please wait while the anonymous magic happens.\nThis can take up to several minutes; don't close the window." }, "banner": { "start_now": "Create a vote now!", @@ -18,12 +18,12 @@ "title": "Secure, verifiable and flexible digital voting" }, "banner_voting_types": { - "anonymous_description": "Say goodbye to the high expenses of traditional voting methods. Vocdoni App brings you a more affordable way to conduct elections, cutting down on logistical and administrative costs while maintaining the highest standards of quality and security.", + "anonymous_description": "Say goodbye to the high expenses of traditional voting methods. {{app}} brings you a more affordable way to conduct elections, cutting down on logistical and administrative costs while maintaining the highest standards of quality and security.", "anonymous_title": "Cost-Effective Voting Solutions", "bottom_text": "Privacy should be normal", - "flexible_description": "Engagement is key in democratic processes, and our platform is designed to maximize it. With its user-friendly interface and accessibility, Vocdoni App encourages higher participation rates, ensuring a more representative and inclusive voting experience.", + "flexible_description": "Engagement is key in democratic processes, and our platform is designed to maximize it. With its user-friendly interface and accessibility, {{app}} encourages higher participation rates, ensuring a more representative and inclusive voting experience.", "flexible_title": "Boosted Participation Rates", - "token_description": "At the heart of Vocdoni App is our commitment to security. Leveraging cutting-edge cryptographic methods, we ensure that every vote cast is not only secure but fully verifiable. This transparency builds unparalleled trust among participants, crucial for the legitimacy of any election.", + "token_description": "At the heart of {{app}} is our commitment to security. Leveraging cutting-edge cryptographic methods, we ensure that every vote cast is not only secure but fully verifiable. This transparency builds unparalleled trust among participants, crucial for the legitimacy of any election.", "token_title": "Enhanced Security and Trust" }, "cc": { @@ -31,6 +31,7 @@ "cancel": "Cancel the process immediately, not allowing new votes and not counting any results (caution: it cannot be reverted)", "cancel_description": "Canceling \"{{ election.title.default }}\"...\nAttention: this action is irreversible and will cancel all the previous votes on it", "continue": "Resume the process immediately, in case it has been paused", + "continue_description": "Resuming \"{{ election.title.default }}\" voting process...", "end": "End the process immediately, preventing new votes to be submitted, and shows the voting results (caution: it cannot be reverted)", "end_description": "Ending \"{{ election.title.default }}\" voting process...\nAttention: this action is irreversible and voters will no longer be able to vote.", "error_title": "There was some error while executing the transaction", @@ -91,6 +92,9 @@ "census": { "chain_required": "You need to select a network", "description": "Choose how to authenticate and verify voters.", + "gitcoin_description": "With this census only voters with a Gitcoin Passport will be able to vote, providing a sybyl-attack resistance method. You can define the general score (GPS) and different stamps that the voter must have.", + "gitcoin_title": "Gitcoin Passport", + "pro": "Pro Census", "request_custom_token": "'Request Custom Tokens'", "social_address_title": "Github users", "spreadsheet_title": "Custom data", @@ -126,7 +130,7 @@ }, "csp_census": { "github": { - "error_fetching_users": "", + "error_fetching_users": "Error fetching users", "selected_users": "Selected users:" } }, @@ -165,16 +169,30 @@ "blog": "Blog", "company": "Company", "contact": "Contact", + "demo": "Voting Types", + "demo1": "Single Choice", + "demo2": "Multiple Choice", + "demo3": "Approval Voting", + "demo4": "Ranked Voting", + "demo5": "Participatory Budgeting", + "demo6": "Weighted Voting", "developer_portal": "Developers Portal", "developers": "Developers", "discord": "Discord", "follow_us": "Follow us", + "footer_subtitle": "The Global Voting Protocol", "homepage": "Home", "repos": "Open-source Code", "resources": "Resources", "terms_and_privacy": "Terms of use & Privacy Policy", "text": "The futureof Web3 voting starts here", "tutorials": "Tutorials", + "uses_cases": "Use Cases", + "uses_cases1": "AGMs", + "uses_cases2": "Elections", + "uses_cases3": "Participatory Budgeting", + "uses_cases4": "Online Voting", + "uses_cases5": "Gated Online Survey", "vocdoni_api": "Vocdoni API", "vocdoni_app": "Vocdoni APP" }, @@ -199,7 +217,7 @@ "avatar_error": "URL not valid", "field_is_required": "This field is required", "min_address": "Please, provide at least one address", - "min_users_address": "", + "min_users_address": "Please, provide at least one address", "recipient_address_invalid": "Specified address does not seem to be valid" }, "process_create": { @@ -208,6 +226,10 @@ "description": "This setting ensures that votes remain unlinked to voter identities by utilizing advanced zkSNARKs technology on the Vocdoni Protocol.", "title": "Anonymous" }, + "customization": { + "description": "Select your desired theme, colors, texts and add your branding in the voting page. More complex options are also available.", + "title": "Customization" + }, "overwrite": { "description": "This setting allow voters to revise their vote a single time if needed, providing flexibility and protection against vote coercion.", "title": "Vote overwrite" @@ -234,6 +256,12 @@ "add_button": "Add", "add_new_address": "add a new address", "delete_web3_address": "Delete address index {{ index }}", + "gitcoin_description": "Create a census of eligible voters providing a list", + "gitcoin_passport_score": "Gitcoin Passport Score", + "gitcoin_stamps": "Stamps", + "gitcoin_strategy_description_AND": "To vote, users must own all selected stamps and have an equal or higher Gitcoin Passport Score.", + "gitcoin_strategy_description_OR": "To vote, users must own at least one stamp and have an equal or higher Gitcoin Passport Score", + "gitcoin_title": "Gitcoin Passport", "holders": "{{ holders }} holders", "max_census_resum": "You have a pool of {{ uniTokenHolders }} {{ symbol }} potential voters. To reduce the cost of the voting process, you can opt to limit the total number of participants in the voting process. This will not affect specific voters nor will reduce the census; it will simply reduce the maximum number of votes that can be cast.", "max_census_slider_arialabel": "Maximum participation expected slider selector", @@ -249,7 +277,10 @@ "token_base_title": "Token based", "tokens_placeholder": "Search or paste token address", "wallet_address_description": "Create a census of eligible voters providing a list of wallet addresses.", - "wallet_address_title": "Wallet addresses" + "wallet_address_title": "Wallet addresses", + "weight": "(Weight)", + "weighted_voting_description": "When activated, your voting power aligns with your GPS score. If deactivated, each user's vote carries equal weight, set at 1 point.", + "weighted_voting_title": "GPS-Based Voting Weight" }, "confirm": { "anonymous": "Anonymous voting active", @@ -356,6 +387,218 @@ "info": "If you need more, contact to info@onvote.app", "title": "Get VOC Tokens" }, + "home": { + "benefits": { + "card_1": { + "description": "Our solution is 10x cheaper, making voting accessible to all and enhancing democracy.", + "title": "10x Cheaper" + }, + "card_2": { + "description": "Save time and effort with Vocdoni's streamlined processes and reduced operational costs.", + "title": "Cost Savings" + }, + "card_3": { + "description": "Increase engagement and participation with a user-friendly platform accessible to all, regardless of technical proficiency.", + "title": "Enhanced Participation" + }, + "card_4": { + "description": "Vote from anywhere, on any device. It's that simple.", + "title": "Effortless Participation" + }, + "card_5": { + "description": "Rest assured with advanced cryptographic methods ensuring the utmost security, maintaining voter anonymity and vote integrity.", + "title": "Unmatched Security" + }, + "card_6": { + "description": "Foster trust and transparency with blockchain-powered transparency, offering stakeholders unparalleled visibility into the voting process.", + "title": "Auditable & Open-source" + }, + "subtitle_1": "Clients who choose Vocdoni unlock a world of benefits tailored to meet their unique voting needs. With our cutting-edge technology, organizations, communities, and governments experience a seamless transition to digital democracy. Here's how Vocdoni adds value:", + "title": "Benefits" + }, + "clients_title": "Trusted by over +100 organizations", + "contactus": { + "btn": "Contact us", + "card_1": { + "description": "Explain us your needs and we will craft a personalized solution for your challenges.", + "title": "Tailored solutions for your unique needs" + }, + "card_2": { + "description": "Vocdoni offers both self-sevice and custom, key in hand solutions tailored for you, .", + "title": "We will be with you every step of the way" + }, + "header": "Contact Us", + "title": "Let's collaborate to find the perfect solution for you" + }, + "create_process": { + "btn": "Try it for free", + "helper_1": "Production ready, responsive voting processes", + "helper_2": "Free up to 100 voters", + "subtitle": "Participation made easy. Vocdoni APP delivers an affordable, easy-to-use, and GDPR-compliant voting experience that anyone can use from anywhere with their PC, laptop, tablet and mobile.", + "title": "Secure, Fast, and Verifiable Voting Solution" + }, + "demo": { + "btn": "View All Demos", + "census_type": { + "card_1": "E-Mail (GMail)", + "card_3": "Open Census", + "radio": "Census / Authentication", + "title": "By Census Type" + }, + "description": "Discover the power of Vocdoni through interactive demos, where you can explore different voting scenarios and firsthand experience the seamless transition to digital democracy. Find your desired voting type, test it, and vote with ease.", + "header": "Demo", + "title": "Test our solution", + "use_cases": { + "card_1": "AGMs", + "card_2": "Elections", + "card_3": "Participatory Budgeting", + "card_4": "Online Voting", + "card_5": "Software Integration", + "card_6": "Gated Online Survey", + "radio": "Use Cases", + "title": "By Use Case" + }, + "voting_type": { + "card_1": "Single Choice", + "card_2": "Multiple Choices", + "card_3": "Approval Voting", + "card_4": "Budget Voting", + "card_5": "Ranked Voting", + "card_6": "Weighted Voting", + "radio": "Voting Type", + "title": "By Voting Type" + } + }, + "faqs": { + "faq_1": { + "description": "Vocdoni stands out for its seamless user experience, advanced security features, and unparalleled transparency. Our platform utilizes cutting-edge technology such as zero-knowledge proofs and blockchain to ensure secure and anonymous voting while providing a transparent and verifiable process.", + "title": "What makes Vocdoni different from other voting platforms?" + }, + "faq_2": { + "description": "Yes, Vocdoni strictly adheres to GDPR guidelines, ensuring that user data is handled with the utmost care and confidentiality. Our platform prioritizes data privacy and security at every stage of the voting process.", + "title": "Is Vocdoni compliant with data privacy regulations?" + }, + "faq_3": { + "description": "Absolutely! Vocdoni offers customization options to align with your organization's branding, ensuring a seamless integration with your existing systems and maintaining a consistent brand experience for voters. Contact us to know more!", + "title": "Can Vocdoni be customized to fit our organization's branding?" + }, + "faq_4": { + "description": "We offer the `Vocdoni SDK` & `Vocdoni UI Components`, designed for an easy integration with your software or platform. We created them to facilitate the developers work and being able to start seeing results from the day one. Our team of experts will guide you through the integration process to ensure a smooth transition and minimal disruption to your operations.", + "title": "How easy is it to integrate Vocdoni with our existing systems?" + }, + "faq_5": { + "description": "Vocdoni offers comprehensive support to assist you at every step of your voting journey. From initial setup and customization to ongoing maintenance and troubleshooting, our dedicated support team is here to help you maximize the value of our platform.", + "title": "What type of support does Vocdoni provide?" + }, + "faq_6": { + "description": "Yes, Vocdoni is equipped to handle elections of any size, from small community votes to large-scale public elections. Our scalable infrastructure ensures reliable performance even during peak voting periods, allowing you to engage voters effectively and efficiently.", + "title": "Can Vocdoni handle large-scale elections?" + }, + "faq_7": { + "description": "Security is our top priority at Vocdoni. Our platform utilizes advanced cryptographic techniques and blockchain technology to ensure the highest level of security, maintaining vote integrity and protecting voter anonymity. With Vocdoni, you can trust that your voting process is secure and tamper-proof.", + "title": "How secure is the Vocdoni platform?" + }, + "faq_8": { + "description": "Vocdoni employs advanced cryptographic techniques and blockchain technology to ensure the security and integrity of the voting process. Each vote is recorded as a tamper-proof blockchain transaction, providing transparency and verifiability to all stakeholders. Additionally, Vocdoni's rigorous security measures safeguard against tampering or manipulation, ensuring that every vote is accurately recorded and counted.", + "title": "How does Vocdoni ensure the integrity of the voting process?" + }, + "subtitle": "Below you'll find answers to the questions we get asked, gaining clarity and confidence about your digital voting experience.", + "title": "Frequently Ask Questions" + }, + "features": { + "card_1": { + "description": "From simple yes/no referendums to complex ranked-choice elections, Vocdoni supports a wide range of voting types to suit any decision-making process.", + "title": "Versatile Voting types" + }, + "card_2": { + "description": "Customize every aspect of your voting process, from branding and themes to ballot designs, ensuring a seamless integration with your organization's identity.", + "title": "Customization Capabilities" + }, + "card_3": { + "description": "With intuitive interfaces and guided onboarding processes, Vocdoni ensures that organizers and voters alike can easily navigate the platform from day one.", + "title": "User-Friendly Onboarding" + }, + "card_4": { + "description": "Utilizing robust authentication methods such as two-factor authentication, Vocdoni safeguards against unauthorized access and ensures the integrity of each vote.", + "title": "Secure Authentication" + }, + "card_5": { + "description": "Create custom voter lists (custom census, oAuth, CRMs...), allowing for targeted outreach and tailored voting experiences for different segments of your audience.", + "title": "Custom Censuses" + }, + "card_6": { + "description": "Customize your voting processes with specific start and end dates, auto-start, secret results until the end, real-time updates, anonymous voting, weighted voting, and ballot customization.", + "title": "Flexible Voting options" + }, + "card_7": { + "description": "Reach a diverse audience with multi-language support, allowing participants to engage in their preferred language for inclusive and accessible voting experiences.", + "title": "Multi-language Support" + }, + "card_8": { + "description": "Monitor voting progress and results in real-time, with instant updates and notifications keeping organizers and participants informed throughout the voting process.", + "title": "Real-time Updates" + }, + "card_9": { + "description": "Enable voters to cast their ballots through various channels, including web browsers, mobiles, tablets, etc., ensuring accessibility and convenience for all participants.", + "title": "Multi-device Support" + }, + "header": "Empowering Solutions", + "subtitle_1": "Unlock the potential of Vocdoni's feature-rich platform, delivering exceptional value to organizers and voters alike. Seamlessly customize your voting processes, ensure secure and transparent elections, and empower participants with intuitive and accessible options. With Vocdoni APP, experience the power of enhanced engagement, efficiency, and trust in every voting experience.", + "title": "Features" + }, + "process": { + "description_1": "Vocdoni APP allows you to create your needed voting process in 4 easy steps.", + "description_2": "We allow you to configure with one click either if you want anonymous voting, secret results until the end, the voting type, upload the census and much more.", + "header": "Process", + "step_1": { + "description": "Gain total control with our platform's self-service option, offering unmatched flexibility. Need customization or guidance? We've got you covered!", + "title": "Start your own voting process" + }, + "step_2": { + "description": "You will be guided through the creation process to select your voting type, configure the information, select the needed options & register the participants (census).", + "title": "Configure & Publish" + }, + "step_3": { + "description": "Voters authenticate and submit their votes seamlessly through our user-friendly flow, designed for universal accessibility across all devices.", + "title": "Voting Period" + }, + "step_4": { + "description": "As easy as that. Once finished, the results will be automatically computed, published and available to anyone.", + "title": "Get the results" + }, + "title": "A simple 4 steps process" + }, + "solutions": { + "card_1": { + "description": "Streamline decision-making with secure, transparent voting. Engage members effectively, save on administrative costs, and foster a culture of participation and transparency.", + "title": "For Organizations" + }, + "card_2": { + "description": " Enhance community involvement with an easy-to-use platform that encourages participation and gives every member a voice.", + "title": "For Communities" + }, + "card_3": { + "description": "Increase voter turnout, reduce electoral costs, and enhance citizen trust with a verifiable, secure voting process.", + "title": "For Governments and Public Institutions" + }, + "header": "Embrace Change with Vocdoni", + "img_card_1": "Organizations", + "img_card_2": "Communities", + "img_card_3": "Governments and Public Institutions", + "subtitle_1": "We offered solutions to wide range of uses-cases, demonstrating Vocdoni App versatility.", + "title": "Our solution fits your needs" + }, + "support": { + "btn_contact": "Contact with us", + "btn_watch": "Schedule a call", + "header": "Let's Try! Get Free Support", + "helper_1": "Free call with our experts", + "helper_2": "Reduce costs & working hours", + "helper_3": "Competitive costs", + "subtitle": "We can help you to create your perfect voting experience", + "title": "Start giving voice to your community" + } + }, "home_features": { "anonymous_description": "Unlock true democracy with zkSNARKs! Participants can cast their votes with complete anonymity, ensuring every voice is heard without compromise.", "anonymous_title": "Anonymous", @@ -401,6 +644,9 @@ "organization": "Edit Organization", "privacy": "Privacy", "terms": "Terms", + "vocdoni": { + "login": "Open App" + }, "wallet": "Wallet" }, "meta": { @@ -433,6 +679,7 @@ "census": "Census", "census_strategies": { "csp": "CSP", + "gitcoin": "Gitcoin", "spreadsheet": "CSV/Spreadsheet", "token": "Token ({{ token.type, uppercase }}): {{ token.symbol }}", "web3": "Web3 addresses" @@ -482,6 +729,8 @@ "text": "

We've securely processed and stored your vote on our Blockchain. Click here to verify your vote.

", "title": "Vote submitted!" }, + "total_census_size": "{{maxCensusSize}} allowed voters of {{censusSize}} total in census", + "total_census_size_tooltip": "The maximum number of voters allowed is limited to {{maxCensusSize}} from a census of {{censusSize}} ({{percent}}% of the total). Only the first {{maxCensusSize}} voters can vote.", "voters": "Voters" }, "process_actions": { @@ -496,7 +745,19 @@ "legal_note": "Disclaimer: " }, "census": { + "crm_description": "If you already have your own CRM, we can integrate it so your community can vote using the same credentials.", + "crm_title": "CRM Integration", + "database_description": "Integrate voting directly with the users in your database. A seamless way for your community to vote.", + "database_title": "Database Integration", + "digital_certificate_description": "We support different types of digital certificates to ensure maximum security and identity validation.", + "digital_certificate_title": "Digital Certificates", + "email_description": "Voters will validate their email by authenticating through their Google Mail account (OAuth).", + "email_title": "Via Email", "mandatory_max_census_size": "At least one voter needs to be selected", + "others_description": "Do you need a specific solution? We can help with a custom integration tailored to your needs.", + "others_title": "Others", + "phone_description": "Voters will need to validate their mobile phone by entering their number and the verification code they receive.", + "phone_title": "Via Phone Number", "total_tokens_one": "1 token", "total_tokens_other": "{{ count }} tokens" }, @@ -507,8 +768,53 @@ }, "creation_steps_description": "Sign transaction to create the voting process. Generating the census can take a few minutes, please don't close this window.", "creation_steps_title": "Progress", + "modal_pro": { + "description": "

Option not available in the free plan. To use it, you need to purchase the custom project plan.

", + "error": "Error sending email, try it later or contact us at info@vocdoni.org", + "form_btn": "SEND", + "form_description": "You can give us your contact information and we will contact you to talk about your needs.", + "form_email_label": "Email:", + "form_email_placeholder": "Your email", + "form_name_label": "Name:", + "form_name_placeholder": "Your name", + "scheudle_btn": "BOOK NOW", + "scheudle_description": "Or you can directly schedule one quick call (30') with our experts to help you create your desired voting project.", + "success": "Email sent successfully", + "title": "Contact Us" + }, "preview": { "accuracy": "Anonymous Census Balance Accuracy: " + }, + "question": { + "approval_voting": { + "description": "Voters can vote for as many options as they approve of. The option with the most votes win.", + "title": "Approval Voting" + }, + "borda_count": { + "description": "Voters rank the options, and points are assigned base on their rank. The option with the most points wins.", + "title": "Ranked Choice" + }, + "multi_choice": { + "description": "Voters choose a pre-defined number of options. The option with more voters win.", + "title": "Multiple Choice" + }, + "others": { + "description": "Select other voting types (multiple-choice, approval voting, participatory budgeting, ranked choice...)", + "title": "Others" + }, + "participation_budgeting": { + "description": "Voters can distribute a predefined number among all options/projects (representing the total available budget) as they wish.", + "title": "Participatory Budgeting" + }, + "single_choice": { + "description": "Voters choose one option. The option with the most votes wins.", + "title": "Single Choice" + }, + "voting_type": { + "description": "Select the voting type for this election. If you don't find what you need can contact us.", + "pro": "Pro Voting Type", + "title": "Voting Types" + } } }, "rainbow": { @@ -518,30 +824,13 @@ "recovery": "Account recovery" }, "retry": "Retry", - "roadmap": { - "census_description": "Create a trust-less off-chain zk-friendly census from an Ethereum block snapshot, with on-chain verification.", - "census_title": "zkCensus:", - "chainlink_description": "Enable automatic execution with optimistic support.", - "chainlink_title": "Chainlink Integration:", - "complex_startegies_description": "Integrate any token from any blockchain using a blend of logic and algebraic operations, out of the box. I.e.", - "complex_startegies_title": "Complex Strategies:", - "execution_description": "Submit the final proof on-chain for binding execution.", - "execution_title": "Execution:", - "milestone1": "Milestone 1: Flexible Gasless Governance", - "milestone2": "Milestone 2: Anonymous voting with binding execution", - "private_description": "Only display voting proposal content to your community members.", - "private_title": "Private Election Metadata:", - "registry_description": "A global on-chain registry for zk (zero-knowledge) identities.", - "registry_title": "zkRegistry:", - "rollup_description": "Consolidate all votes into a single, secure zkProof using custom zkRollup technology", - "rollup_title": "zkRollup:", - "social_census_desciption": "Enhance your census with social identities from platforms like Github or Twitter. Include Sybil resistance mechanisms such as Holonym or Proof-of-Humanity.", - "social_census_title": "Social Census:", - "title": "Roadmap 2024 What's next?", - "versatil_description": "Whether it’s simple, ranged, or quadratic voting, Onvote supports multiple voting methods to adapt to diverse community needs.", - "versatil_title": "Versatile Voting Methods:", - "voting_description": "Facilitate anonymous, off-chain (gasless) voting for any eligible voter using the Vocdoni p2p network.", - "voting_title": "zkVoting:" + "share": { + "copy": "Copy", + "election_share_btn_text": "Share", + "election_share_text": "Look at this election!", + "icon_title": "share link", + "mail_subject": "Voting platform", + "modal_title": "Share with friends" }, "uploader": { "click_or_drag_and_drop": "Upload or drag and drop the voter list here(Allowed formats: CSV, XLSX, and ODS)", @@ -551,6 +840,29 @@ "read_less": "Read less", "read_more": "Read more" }, + "web3cards": { + "farcaster": { + "btn": "Go to Farcaster.vote", + "description": "With \"Farcaster.vote\" we allow a simple and social way to vote. Your members only have to vote trough Farcaster in 2 simple clicks. Try creating your farcaster vote frame.", + "title": "Farcaster" + }, + "onvote": { + "btn": "Create", + "description": "Secure, gasless and transparent Web3 voting. Allows to create token census, multichain census, anonymous voting, sybil-resistant census with a flexible voting type.", + "title": "Onvote" + }, + "others": { + "btn": "Contact Us", + "description": "We've build a voting suite allowing an easy integration with our dedicated voting Blockchain via our SDK & UI-Components.

Start integrating with us now!", + "title": "Others" + }, + "plugins": { + "btn": "Try Aragon App", + "description": "We implemented a custom DAO gasless voting solution for Aragon DAO, and we can do the same for you!

Check gasless DAO voting!", + "title": "DAO Plugins" + }, + "title": "Our Web3 governance solutions" + }, "welcome": { "description": "To access all its features, including anonymous voting, you must create an account on the Vocdoni Protocol and register your Secret Identity Key (SIK) by signing two transactions. It's secure and free.", "intro": "This is your first time using {{ sitename }}.", diff --git a/src/i18n/locales/es.json b/src/i18n/locales/es.json index d0245d7d..745251a6 100644 --- a/src/i18n/locales/es.json +++ b/src/i18n/locales/es.json @@ -6,12 +6,12 @@ "overwrite_votes_left_one": "Puedes corregir tu voto una vez.", "overwrite_votes_left_many": "", "overwrite_votes_left_other": "Puedes corregir tu voto {{ count }} veces.", - "submitting": "", + "submitting": "Enviando", "verify_vote_on_explorer": "Verifica el voto en el explorador", "votes_one": "1 votante", "votes_many": "{{ count }} votantes", "votes_other": "{{ count }} votantes", - "voting_anonymous_advice": "" + "voting_anonymous_advice": "Por favor, espera mientras sucede la magia.\nEsto puede tardar varios minutos; no cierres la ventana." }, "banner": { "start_now": "¡Crea una votación ahora!", @@ -33,6 +33,7 @@ "cancel": "Cancelar el proceso inmediatamente, sin permitir nuevos votos y sin contar ningún resultado (precaución: no puede revertirse)", "cancel_description": "Cancelando el proceso de votación \"{{ election.title.default }}\"...\nAtención: esta acción no se puede revertir y cancelará los votos que haya recibido.", "continue": "Reanudar el proceso inmediatamente, en caso de que se haya pausado", + "continue_description": "Reanudando el proceso de votación \"{{ election.title.default }}\"", "end": "Finalizar el proceso inmediatamente, impidiendo que se presenten nuevos votos, y muestra los resultados de la votación (precaución: no puede revertirse)", "end_description": "Finalizando el proceso de votación \"{{ election.title.default }}\"...\nAtención: esta acción no se puede revertir y los votantes no podrán emitir más votos.", "error_title": "Hubo algún error al ejecutar la transacción", @@ -76,12 +77,12 @@ "upcoming": "Próxima" }, "validation": { - "choices_count": "", + "choices_count": "Debes seleccionar {{ count }} opciones", "min_length": "Este campo debe tener al menos {{ min }} caracteres", "required": "Este campo es obligatorio" }, "vote": { - "abstain": "", + "abstain": "Abstenerse", "button": "Votar", "button_update": "Reenviar voto", "confirm": "Confirma tus selecciones:", @@ -93,6 +94,9 @@ "census": { "chain_required": "Necesitas seleccionar una red", "description": "Selecciona cómo los votantes se autenticarán e identificarán.", + "gitcoin_description": "Con este censo, solo los votantes con Gitcoin Passport podrán votar, proporcionando un método de resistencia contra sybil-attack. Puedes definir la puntuación general (GPS) y diferentes stamps que el votante debe tener.", + "gitcoin_title": "Gitcoin Passport", + "pro": "", "request_custom_token": "Solicitar Tokens Personalizados", "social_address_title": "Usuarios de Github", "spreadsheet_title": "Datos personalizados", @@ -119,7 +123,7 @@ "balance": "Dispones de: {{ balance }} tokens", "button": "Obtener más Tokens", "census_total_one": "Votantes:{{ count }} votante", - "census_total_many": "Votantes:{{ count }} votantes", + "census_total_many": "", "census_total_other": "Votantes:{{ count }} votantes", "duration": "Duración: {{ date, duration }}", "not_enough_tokens": "No tienes suficientes tokens para crear este proceso.", @@ -129,7 +133,7 @@ }, "csp_census": { "github": { - "error_fetching_users": "", + "error_fetching_users": "Error al recuperar usuarios", "selected_users": "Usuarios seleccionados:" } }, @@ -146,15 +150,15 @@ "faucet": { "advanced_settings": "Avanzado...", "connect": "Por favor, conecta tu wallet", - "copy_package": "", - "copy_package_done": "", + "copy_package": "Copiar Paquete de Faucet", + "copy_package_done": "¡Copiado!", "description": "Para desarrollar con Vocdoni, necesitas tokens para ciertas acciones (crear una elección, cambiar el estado de una elección, etc.). El número de diferentes factores como el tamaño del censo, la duración de la elección, los parámetros de la elección, etc.", "general_information": { "description": "Cuando solicites tokens de desarrollo, recibirás {{ amount }} tokens. Puedes reclamar desde el faucet una vez cada {{ waitHours }} horas.", "description2": "Si necesitas más tokens para desarrollar sobre la plataforma de Vocdoni, puedes ponerte en contacto con nosotros en tokens (at) vocdoni.io", "title": "Información General" }, - "package_success": "", + "package_success": "¡Felicidades! Aquí está tu Paquete de Faucet:", "recipient_address": "Ingrese la dirección del destinatario en el que desea recibir los tokens.", "request_description": "Para obtener más tokens de Vocdoni, debes iniciar sesión con una cuenta social (para evitar el mal uso del grifo). Solo solicitamos acceso de lectura.", "request_tokens": { @@ -168,16 +172,30 @@ "blog": "Blog", "company": "Empresa", "contact": "Contacto", + "demo": "Tipos de Votación", + "demo1": "Elección Simple", + "demo2": "Elección Múltiple", + "demo3": "Votación por Aprobación", + "demo4": "Votación por Clasificación", + "demo5": "Presupuestos Participativos", + "demo6": "Votación Ponderada", "developer_portal": "Portal para desarrolladores", "developers": "Desarrolladores", "discord": "Discord", "follow_us": "Síguenos", + "footer_subtitle": "El Protocolo de Votación Global", "homepage": "Inicio", "repos": "Código abierto", "resources": "Recursos", "terms_and_privacy": "Términos de uso y Política de privacidad", "text": "El futurode la votación en Web3 comienza aquí", "tutorials": "Tutoriales", + "uses_cases": "Casos de Uso", + "uses_cases1": "Asambleas Generales de Accionistas (AGMs)", + "uses_cases2": "Elecciones", + "uses_cases3": "Presupuestos Participativos", + "uses_cases4": "Votación Digital", + "uses_cases5": "Encuesta Digital Cerrada", "vocdoni_api": "API de Vocdoni", "vocdoni_app": "APP de Vocdoni" }, @@ -202,7 +220,7 @@ "avatar_error": "URL no válida", "field_is_required": "Este campo es obligatorio", "min_address": "Por favor, proporciona al menos una dirección", - "min_users_address": "", + "min_users_address": "Por favor, proporciona al menos una dirección", "recipient_address_invalid": "La dirección especificada no parece válida" }, "process_create": { @@ -211,6 +229,10 @@ "description": "La identidad de los votantes será completamente anonimizada, incluyendo para el organizador. Cada votante deberá establecer una contraseña para garantizar la confidencialidad de su identidad.", "title": "Anónimo" }, + "customization": { + "description": "Selecciona tu tema deseado, colores, textos y añade tu marca en la página de votación. También hay opciones más complejas disponibles.", + "title": "Personalización" + }, "overwrite": { "description": "Si se selecciona, los votantes tendrán la opción de corregir su voto una vez.", "title": "Sobrescribir voto" @@ -237,6 +259,12 @@ "add_button": "Añadir", "add_new_address": "Añade una nueva dirección", "delete_web3_address": "Eliminar direccion con índice {{ index }}", + "gitcoin_description": "Crear un censo de votantes elegibles proporcionando una lista", + "gitcoin_passport_score": "Puntuación de Gitcoin Pasaport", + "gitcoin_stamps": "Stamps", + "gitcoin_strategy_description_AND": "Para votar, los usuarios deben poseer todos los stamps seleccionados y tener una puntuación del Gitcoin Passport igual o superior.", + "gitcoin_strategy_description_OR": "Para votar, los usuarios deben poseer al menos un stamp y tener una puntuación del Gitcoin Passport igual o superior.", + "gitcoin_title": "Gitcoin Passport", "holders": "{{ holders }} titulares", "max_census_resum": "Tienes un grupo de {{ uniTokenHolders }} posibles votantes de {{ symbol }}. Para reducir el costo del proceso de votación, puedes optar por limitar el número total de participantes en el proceso de votación. Esto no afectará a votantes específicos ni reducirá el censo; simplemente reducirá el número máximo de votos que se pueden emitir.", "max_census_slider_arialabel": "Selector deslizante de participación máxima esperada", @@ -252,7 +280,10 @@ "token_base_title": "Basado en tokens", "tokens_placeholder": "Busca o pega la dirección del token", "wallet_address_description": "Identifica a tus votantes por sus direcciones de monedero digital.", - "wallet_address_title": "Direcciones de monedero" + "wallet_address_title": "Direcciones de monedero", + "weight": "(Peso)", + "weighted_voting_description": "Cuando se activa, tu poder de voto se alinea con tu puntuación GPS. Si se desactiva, el voto de cada usuario tiene el mismo peso, establecido en 1 punto.", + "weighted_voting_title": "Peso del Voto Basado en GPS" }, "confirm": { "anonymous": "Anónimo", @@ -334,9 +365,9 @@ "weight": "Peso" } }, - "spreadsheet_total_rows_one": "", + "spreadsheet_total_rows_one": "El censo tiene un solo registro", "spreadsheet_total_rows_many": "", - "spreadsheet_total_rows_other": "", + "spreadsheet_total_rows_other": "El censo tiene {{ count }} registros", "steps": { "census": "Censo", "checks": "Organización", @@ -363,6 +394,218 @@ "info": "Si necesita más, contacte a info@onvote.app", "title": "Obtener VOC Tokens" }, + "home": { + "benefits": { + "card_1": { + "description": "Nuestra solución reduce costos hasta en un 90%, haciendo la votación asequible para todos y mejorando la democracia.", + "title": "90% más económico" + }, + "card_2": { + "description": "Ahorra tiempo y esfuerzo con nuestros procesos optimizados, disfrutando de menores costos operativos.", + "title": "Ahorro de costos" + }, + "card_3": { + "description": "Incrementa la participación con una plataforma fácil de usar, accesible para todos.", + "title": "Mayor participación" + }, + "card_4": { + "description": "Vota desde cualquier lugar, en cualquier dispositivo. Así de simple.", + "title": "Votación sin complicaciones" + }, + "card_5": { + "description": "Tus votos están seguros con nuestros métodos criptográficos avanzados, garantizando la máxima seguridad y la integridad de cada voto.", + "title": "Seguridad sin igual" + }, + "card_6": { + "description": "Promueve la confianza y la transparencia con tecnología blockchain, ofreciendo una visibilidad completa del proceso de votación.", + "title": "Transparente y verificable" + }, + "subtitle_1": "Con Vocdoni, obtienes beneficios diseñados para tus necesidades de votación. Nuestra tecnología facilita la transición hacia la democracia digital para organizaciones, comunidades y gobiernos. Descubre cómo Vocdoni agrega valor:", + "title": "Beneficios" + }, + "clients_title": "Más de 100 organizaciones confían en nosotros", + "contactus": { + "btn": "Contáctanos", + "card_1": { + "description": "Cuéntanos tus necesidades y crearemos una solución personalizada para tus desafíos.", + "title": "Soluciones personalizadas para tus necesidades" + }, + "card_2": { + "description": "Ofrecemos soluciones de autoservicio y personalizadas, adaptadas para ti.", + "title": "Te acompañamos en cada paso" + }, + "header": "Contáctanos", + "title": "Trabajemos juntos para encontrar tu solución ideal" + }, + "create_process": { + "btn": "Prueba gratis", + "helper_1": "Procesos de votación listos para usar y responsivos", + "helper_2": "Gratis hasta 100 votantes", + "subtitle": "Participación fácil. Vocdoni ofrece una experiencia de votación accesible, fácil de usar y conforme al GDPR, disponible desde cualquier lugar con PC, laptop, tablet y móvil.", + "title": "Votación segura, rápida y verificable" + }, + "demo": { + "btn": "Ver demos", + "census_type": { + "card_1": "Correo electrónico (GMail)", + "card_3": "Censo Abierto", + "radio": "Censo / Autenticación", + "title": "Por Tipo de Censo" + }, + "description": "Explora el poder de Vocdoni con demos interactivas. Experimenta la transición a la democracia digital y encuentra el tipo de votación que necesitas.", + "header": "Demostración", + "title": "Explora nuestra solución", + "use_cases": { + "card_1": "Asambleas Generales", + "card_2": "Elecciones", + "card_3": "Presupuestos Participativos", + "card_4": "Votación en línea", + "card_5": "Integración de software", + "card_6": "Encuestas en línea", + "radio": "Por Caso de Uso", + "title": "Por Caso de Uso" + }, + "voting_type": { + "card_1": "Elección Simple", + "card_2": "Elección Múltiple", + "card_3": "Voto de Aprobación", + "card_4": "Voto de Presupuesto", + "card_5": "Votación Clasificada", + "card_6": "Votación Ponderada", + "radio": "Por Tipo de Votación", + "title": "Por Tipo de Votación" + } + }, + "faqs": { + "faq_1": { + "description": "Vocdoni se distingue por su experiencia de usuario intuitiva, seguridad avanzada y transparencia total. Usamos tecnología Blockchain para asegurar votaciones seguras y anónimas.", + "title": "¿Qué hace único a Vocdoni?" + }, + "faq_2": { + "description": "Sí, cumplimos estrictamente con el GDPR, asegurando el tratamiento confidencial y respetuoso de los datos de usuario.", + "title": "¿Cumple Vocdoni con el GDPR?" + }, + "faq_3": { + "description": "¡Por supuesto! Ofrecemos personalización para integrarse perfectamente con tu marca y sistemas existentes.", + "title": "¿Se puede personalizar Vocdoni?" + }, + "faq_4": { + "description": "Con nuestro SDK y componentes UI, la integración es sencilla. Nuestro equipo de expertos te guiará para asegurar una transición fluida.", + "title": "¿Es fácil integrar Vocdoni?" + }, + "faq_5": { + "description": "Ofrecemos soporte completo para asegurar que aproveches al máximo nuestra plataforma, desde la configuración inicial hasta el mantenimiento continuo.", + "title": "¿Qué soporte ofrece Vocdoni?" + }, + "faq_6": { + "description": "Sí, Vocdoni puede manejar elecciones de cualquier tamaño, garantizando un rendimiento confiable y una participación efectiva.", + "title": "¿Puede Vocdoni manejar elecciones grandes?" + }, + "faq_7": { + "description": "Priorizamos la seguridad usando criptografía avanzada y blockchain para proteger cada voto y mantener la integridad del proceso.", + "title": "¿Qué tan seguro es Vocdoni?" + }, + "faq_8": { + "description": "Aseguramos la integridad del voto con tecnología blockchain, ofreciendo un proceso transparente y a prueba de manipulaciones.", + "title": "¿Cómo asegura Vocdoni la integridad del voto?" + }, + "subtitle": "Aquí encontrarás respuestas a preguntas frecuentes, para que tengas claridad y confianza en tu experiencia de votación digital.", + "title": "Preguntas Frecuentes" + }, + "features": { + "card_1": { + "description": "Desde votaciones de opción única hasta elecciones de preferencia múltiple o ponderadas, Vocdoni soporta diversos tipos de votación.", + "title": "Diversidad de votaciones" + }, + "card_2": { + "description": "Ajusta cada detalle de tu votación para que se alinee perfectamente con la identidad de tu organización.", + "title": "Personalización completa" + }, + "card_3": { + "description": "Con interfaces intuitivas y guías paso a paso, aseguramos una navegación sencilla desde el inicio.", + "title": "Registro fácil para usuarios" + }, + "card_4": { + "description": "Aseguramos la integridad del voto con autenticación de dos factores y otras medidas de seguridad.", + "title": "Autenticación robusta" + }, + "card_5": { + "description": "Crea listas de votantes personalizadas para dirigirte específicamente a tu audiencia. Puedes usar datos personalizados (ej: usuario y password), correos, certificados digitales, CRMs y más.", + "title": "Censos a medida" + }, + "card_6": { + "description": "Configura tu votación con opciones como votación secreta, resultados en tiempo real y personalización de boletas.", + "title": "Flexibilidad en la votación" + }, + "card_7": { + "description": "Alcanza a una audiencia global con opciones de idioma, cada votante puede elegir su idioma preferido.", + "title": "Soporte multilenguaje" + }, + "card_8": { + "description": "Monitorea los resultados de las votaciones en tiempo real, con actualizaciones y notificaciones instantáneas.", + "title": "Resultados instantáneos" + }, + "card_9": { + "description": "Permite votar desde cualquier dispositivo, ofreciendo comodidad y accesibilidad total. Sin desplazamientos, ahorra tiempo y dinero a tus votantes.", + "title": "Accesibilidad multiplataforma" + }, + "header": "Características destacadas", + "subtitle_1": "Descubre el potencial de Vocdoni con herramientas que ofrecen valor excepcional a organizadores y votantes. Personaliza tus votaciones, asegura procesos transparentes y empodera a los participantes con opciones intuitivas y accesibles.", + "title": "Funcionalidades" + }, + "process": { + "description_1": "Crea tu proceso de votación en 4 pasos sencillos con la APP de Vocdoni.", + "description_2": "Configura votaciones anónimas, resultados secretos, tipos de votación y más, todo con un clic.", + "header": "Proceso", + "step_1": { + "description": "Control total con nuestra plataforma de autoservicio. ¿Necesitas personalización o asistencia? Estamos aquí para ayudarte.", + "title": "Inicia tu votación" + }, + "step_2": { + "description": "Te guiamos en la selección del tipo de votación, configuración de detalles y registro de participantes.", + "title": "Configura y publica" + }, + "step_3": { + "description": "Los votantes participan fácilmente a través de nuestra interfaz amigable, accesible desde cualquier dispositivo.", + "title": "Vota" + }, + "step_4": { + "description": "Al finalizar, los resultados se calculan y publican automáticamente, disponibles para todos.", + "title": "Resultados inmediatos" + }, + "title": "Proceso simple de 4 pasos" + }, + "solutions": { + "card_1": { + "description": "Optimiza la toma de decisiones con votaciones seguras y transparentes. Engancha a los miembros eficazmente y promueve una cultura de participación.", + "title": "Para Organizaciones" + }, + "card_2": { + "description": "Mejora el compromiso comunitario con una plataforma fácil de usar que fomenta la participación activa de todos los miembros.", + "title": "Para Comunidades" + }, + "card_3": { + "description": "Incrementa la participación electoral, reduce costos y mejora la confianza ciudadana con un proceso de votación seguro y verificable.", + "title": "Para Gobiernos e Instituciones Públicas" + }, + "header": "Soluciones adaptativas", + "img_card_1": "Organizaciones", + "img_card_2": "Comunidades", + "img_card_3": "Gobierno e Instituciones Públicas", + "subtitle_1": "Brindamos soluciones para una amplia gama de necesidades, demostrando la versatilidad y adaptabilidad de Vocdoni.", + "title": "Soluciones personalizadas" + }, + "support": { + "btn_contact": "Contáctanos", + "btn_watch": "Agenda una llamada", + "header": "¡Pruébalo! Soporte gratuito", + "helper_1": "Consulta gratuita con nuestros expertos", + "helper_2": "Reduce costos y tiempo de trabajo", + "helper_3": "Precios competitivos", + "subtitle": "Te ayudamos a crear tu experiencia de votación ideal", + "title": "Empieza a dar voz a tu comunidad" + } + }, "home_features": { "anonymous_description": "¡Desbloquea la verdadera democracia con zkSNARKs! Los participantes pueden emitir sus votos con completa anonimato, asegurando que cada voz sea escuchada sin compromisos.", "anonymous_title": "Anónimo", @@ -400,7 +643,7 @@ "documentation": "Documentación", "get_more": "Obtener más", "languages": "Idiomas", - "languages_list": "", + "languages_list": "Mostrar idiomas disponibles", "login": "Iniciar sesión", "logout": "Cerrar sesión", "my_org": "Mi Org", @@ -408,6 +651,9 @@ "organization": "Editar Organización", "privacy": "Privacidad", "terms": "Términos", + "vocdoni": { + "login": "Login" + }, "wallet": "Billetera" }, "meta": { @@ -440,6 +686,7 @@ "census": "Censo", "census_strategies": { "csp": "CSP", + "gitcoin": "Gitcoin", "spreadsheet": "CSV", "token": "Token ({{ token.type, uppercase }}): {{ token.symbol }}", "web3": "Direcciones web3" @@ -480,7 +727,7 @@ "canceled": "Proceso cancelado", "ended": "Finalizado", "paused": "Proceso pausado", - "paused_description": "", + "paused_description": "No es posible votar en este momento.", "unknown": "Desconocido", "upcoming": "Próximo" }, @@ -490,6 +737,8 @@ "text": "

Tu voto ha sido emitido y almacenado de forma segura en la cadena de bloques de Vocdoni. Puedes comprobarlo aquí.

La democracia es importante, puedes compartirla con tu comunidad y amigos:

", "title": "Voto emitido correctamente!" }, + "total_census_size": "{{maxCensusSize}} votantes permitidos de {{censusSize}} totales en el censo", + "total_census_size_tooltip": "El número máximo de votantes permitidos está limitado a {{maxCensusSize}} de un censo de {{censusSize}} ({{percent}}% del total). Solo los primeros {{maxCensusSize}} votantes pueden votar.", "voters": "Votantes" }, "process_actions": { @@ -504,7 +753,19 @@ "legal_note": "Aviso legal: " }, "census": { - "mandatory_max_census_size": "Debe haber al menos un votante", + "crm_description": "Si ya tienes tu propio CRM, podemos integrarlo para permitir que tu comunidad vote con las mismas credenciales.", + "crm_title": "Integración CRM", + "database_description": "Integra la votación directamente con los usuarios en tu base de datos. Una manera sencilla de votar para tu comunidad.", + "database_title": "Base de Datos", + "digital_certificate_description": "Apoyamos diferentes certificados digitales, para obtener la máxima seguridad y validación de identidad.", + "digital_certificate_title": "Certificado Digital", + "email_description": "Los votantes validarán su correo electrónico autenticándose en su cuenta de Google Mail (OAuth).", + "email_title": "Por Correo Electrónico", + "mandatory_max_census_size": "Al menos un votante debe ser seleccionado", + "others_description": "¿Necesitas una solución específica? Podemos ayudarte con una integración personalizada que se adapte a tus necesidades.", + "others_title": "Otros", + "phone_description": "Los votantes tendrán que validar su teléfono móvil, ingresando su número y el código de verificación que recibirán.", + "phone_title": "Por Número de Teléfono", "total_tokens_one": "1 token", "total_tokens_many": "", "total_tokens_other": "{{ count }} tokens" @@ -516,8 +777,54 @@ }, "creation_steps_description": "Necesitas firmar las transacciones para crear el proceso de votación en la cadena de bloques de Vocdoni. No cierres esta ventana.", "creation_steps_title": "Progreso", + "modal_pro": { + "description": "

La opción seleccionada no está disponible en el plan de autoatención (gratuito). Para poder utilizarla, necesitas comprar el plan de proyecto personalizado

", + "error": "Error sending email, try it later or contact us at info@vocdoni.org", + "form_btn": "ENVIAR", + "form_description": "Puedes proporcionarnos tu información de contacto y nosotros te contactaremos para hablar sobre tus necesidades", + "form_email_label": "Correo electrónico:", + "form_email_placeholder": "Tu correo electrónico", + "form_name_label": "Nombre:", + "form_name_placeholder": "Tu nombre", + "scheudle_btn": "RESERVAR AHORA", + "scheudle_description": " O puedes reservar directamente una llamada rápida (15 minutos) con nuestros expertos para ayudarte a crear tu proyecto de votación deseado", + "success": "Correo enviado correctamente", + "title": "Contáctanos" + }, "preview": { "accuracy": "Precisión de los Saldos de Censo Anónimo: " + }, + "question": { + "approval_voting": { + "description": "Los votantes pueden votar por tantos candidatos como aprueben, y el candidato con más votos gana.", + "title": "Votación por Aprobación" + }, + "borda_count": { + "description": "Los votantes clasifican a los candidatos, y se asignan puntos en función de su clasificación. El candidato con más puntos gana.", + "title": "

Elección por Rango

(Cuenta Borda)

" + }, + "multi_choice": { + "description": "Los votantes eligen un número predefinido de opciones, y la opción con más votantes gana.", + "title": "

Votación Plurinominal

(Elección Múltiple)

" + }, + "others": { + "description": "", + "title": "" + }, + "participation_budgeting": { + "description": "Los votantes pueden distribuir un número predefinido entre todas las opciones/proyectos (que representan el presupuesto total disponible) como deseen.", + "title": "Presupuesto Participativo" + }, + "single_choice": { + "description": "Los votantes eligen una opción/candidato, y la opción/candidato con más votos gana.", + "title": "

Votación Plurinominal

(Elección Individual)

" + }, + "voting_type": { + "description": "Selecciona el tipo de votación para esta elección. Si no encuentras lo que necesitas, puedes contactarnos.", + "pro": "", + "pro_census": "", + "title": "Tipos de Votación" + } } }, "rainbow": { @@ -527,30 +834,13 @@ "recovery": "Recuperar cuenta" }, "retry": "Reintentar", - "roadmap": { - "census_description": "Crear un censo descentralizado fuera de la cadena y compatible con zk a partir de una instantánea de bloques de Ethereum, con verificación en cadena.", - "census_title": "zkCensus:", - "chainlink_description": "Habilitar la ejecución automática con soporte optimista.", - "chainlink_title": "Integración de Chainlink:", - "complex_startegies_description": "Integrar cualquier token de cualquier cadena de bloques utilizando una combinación de lógica y operaciones algebraicas, directamente. Por ejemplo,", - "complex_startegies_title": "Estrategias complejas:", - "execution_description": "Enviar la prueba final a la cadena para una ejecución vinculante.", - "execution_title": "Ejecución:", - "milestone1": "Hitos 1: Gobernanza flexible sin gas", - "milestone2": "Hitos 2: zkRollup para votaciones vinculantes seguras y anónimas", - "private_description": "Mostrar el contenido de la propuesta de votación solo a los miembros de la comunidad.", - "private_title": "Metadatos de elección privada:", - "registry_description": "Un registro global en cadena para identidades zk (conocimiento cero).", - "registry_title": "zkRegistry:", - "rollup_description": "Consolidar todos los votos en una única prueba zk segura mediante la tecnología de zkRollup personalizada.", - "rollup_title": "zkRollup:", - "social_census_desciption": "Mejora tu censo con identidades sociales de plataformas como Github o Twitter. Incluye mecanismos de resistencia a Sybil como Holonym o Proof-of-Humanity.", - "social_census_title": "Censo social:", - "title": "Hoja de ruta 2024 ¿Qué viene después?", - "versatil_description": "Ya sea votación simple, escalonada o cuadrática, Onvote admite múltiples métodos de votación para adaptarse a las diversas necesidades de la comunidad.", - "versatil_title": "Métodos de votación versátiles:", - "voting_description": "Facilitar la votación anónima, fuera de la cadena (sin gas) para cualquier votante elegible utilizando la red p2p de Vocdoni.", - "voting_title": "zkVoting:" + "share": { + "copy": "Copiar", + "election_share_btn_text": "Comparte", + "election_share_text": "¡Mira esta elección!", + "icon_title": "compartir enlace", + "mail_subject": "Plataforma de votación", + "modal_title": "Compartir con amigos" }, "uploader": { "click_or_drag_and_drop": "Sube o arrastra y suelta aqui la lista de votantes(Formap permitidos: CSV, XLSX y ODS)", @@ -560,6 +850,29 @@ "read_less": "Leer menos", "read_more": "Leer más" }, + "web3cards": { + "farcaster": { + "btn": "Ir a Farcaster.vote", + "description": "Con \"Farcaster.vote\" ofrecemos una manera simple y social de votar. Tus miembros solo tienen que votar a través de Farcaster en 2 simples clics. Prueba creando una votación con Farcaster frame", + "title": "Farcaster" + }, + "onvote": { + "btn": "Crear", + "description": "Sistema Web3 de votación segura, sin tasas y transparente. Permite crear censo de tokens, censo multichain, votación anónima y censo sybil-resistant con un tipo de votación flexible.", + "title": "Onvote" + }, + "others": { + "btn": "Contáctanos", + "description": "Hemos construido un conjunto de votaciones que permite una integración fácil con nuestra Blockchain dedicada a votaciones a través de nuestro SDK &; Componentes UI.

¡Empieza a integrarlo ahora!", + "title": "Otros" + }, + "plugins": { + "btn": "Prueba la Aragon App", + "description": "Hemos implementado una solución de votación sin tasas personalizada para la DAO de Aragon, ¡y podemos hacer lo mismo por ti!

Empieza con votación sin tasas!", + "title": "Complementos DAO" + }, + "title": "Nuestras soluciones de gobernanza Web3" + }, "welcome": { "description": "Para empezar, debes registrar tu cuenta y tu SIK (clave de identidad segura) firmando dos transacciones (es seguro y gratuito).", "intro": "Con esta dApp puedes crear votaciones anónimas, seguras y verificables.", diff --git a/src/importmeta.d.ts b/src/importmeta.d.ts index 9f918824..45a97cbe 100644 --- a/src/importmeta.d.ts +++ b/src/importmeta.d.ts @@ -7,25 +7,34 @@ interface ImportMeta { CUSTOM_ORGANIZATION_DOMAINS: { [key: string]: string } + EMAILJS_SERVICE_ID: string + EMAILJS_TEMPLATE_ID: string + EMAILJS_PUBLIC_ID: string features: { faucet: boolean vote: { anonymous: boolean overwrite: boolean secret: boolean + customization: boolean } login: string[] census: string[] + unimplemented_census: string[] + voting_type: string[] + unimplemented_voting_type: string[] languages: string[] _census: { spreadsheet: boolean token: boolean web3: boolean csp: boolean + gitcoin: boolean } } theme: string CSP_URL: string CSP_PUBKEY: string + DEFAULT_CENSUS_SIZE: number } } diff --git a/src/themes/default/breakpoints.ts b/src/themes/default/breakpoints.ts index da664b8a..17c01962 100644 --- a/src/themes/default/breakpoints.ts +++ b/src/themes/default/breakpoints.ts @@ -10,4 +10,6 @@ export const breakpoints = { xl2: '84em', xl3: '95em', xl5: '115em', + benefits1: '780px', + benefits2: '1164px', } diff --git a/src/themes/default/colors.ts b/src/themes/default/colors.ts index 50be0e2f..f37b21e6 100644 --- a/src/themes/default/colors.ts +++ b/src/themes/default/colors.ts @@ -12,6 +12,7 @@ export const colorsBase = { main: '#CBD5E0', dark: '#606f88', }, + green: '#48BB78', primary: { main: '#24656e', dark: '#175b64', @@ -23,6 +24,7 @@ export const colorsBase = { dark: '#fafafa', dark2: 'rgb(245, 247, 250)', }, + yellow: '#FFB116', } export const colors = { @@ -52,6 +54,35 @@ export const colors = { }, error: colorsBase.red, + footer_link: colorsBase.gray.dark, + + home: { + benefits: { + dark_bg: colorsBase.primary.main, + dark_color: colorsBase.white.pure, + light_bg: colorsBase.white.pure, + light_color: colorsBase.primary.main, + }, + demo: { + icon: colorsBase.gray.dark, + radio: colorsBase.primary.main, + }, + icon_bg: colorsBase.primary.main, + section: { + bg: colorsBase.gray.light, + title: colorsBase.primary.main, + }, + support: { + bg: colorsBase.primary.main, + title: colorsBase.yellow, + }, + step: { + icon: colorsBase.white.pure, + icon_bg: colorsBase.primary.main, + title: colorsBase.primary.main, + }, + }, + success: colorsBase.green, link: { primary: colorsBase.primary.main, @@ -146,6 +177,8 @@ export const colors = { color: colorsBase.blue.dark, }, advanced_checkbox_bg: colorsBase.white.pure, + pro_bg: colorsBase.primary.main, + pro_color: colorsBase.white.pure, bg: colorsBase.white.dark2, wallet_addresses_border: colorsBase.gray.main, calendar_start_date_selected: colorsBase.blue.main, @@ -157,6 +190,10 @@ export const colors = { }, description: colorsBase.gray.dark, description_logo: colorsBase.blue.main, + modal_pro: { + description: colorsBase.gray.dark, + border: colorsBase.gray.light, + }, preview_option_question_before: colorsBase.black, preview_negative_balance: colorsBase.red, section: colorsBase.white.pure, @@ -175,6 +212,5 @@ export const colors = { bg: colorsBase.primary.main, color_active: colorsBase.white.pure, }, - tabs_selected_color: colorsBase.primary.main, }, } diff --git a/src/themes/default/components/Card.ts b/src/themes/default/components/Card.ts index 664b0e2d..5f3985b2 100644 --- a/src/themes/default/components/Card.ts +++ b/src/themes/default/components/Card.ts @@ -217,11 +217,218 @@ const aside = definePartsStyle({ borderRadius: 'lg', }, }) +const benefits = definePartsStyle({ + container: { + w: '350px', + borderRadius: 'xl', + boxShadow: 'var(--box-shadow-darker)', + }, + header: { + p: '30px', + pb: '20px', + fontSize: { base: '28.5px', sm: '28px' }, + lineHeight: { base: '38px', sm: '37px' }, + fontWeight: 'bold', + textAlign: 'center', + }, + body: { + p: '30px', + pt: 0, + textAlign: 'center', + }, +}) + +const iconCard = definePartsStyle({ + container: { + bgColor: 'transparent', + }, + body: { + p: 0, + display: 'flex', + gap: '24px', + + 'div:first-of-type': { + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + borderRadius: 'lg', + minW: '45px', + h: '45px', + bgColor: 'home.icon_bg', + + svg: { + width: '25px', + height: '25px', + color: 'white', + }, + }, + + 'div:last-of-type': { + 'p:first-of-type': { + fontWeight: 'bold', + mb: '6px', + fontSize: '20px', + lineHeight: '24px', + }, + + 'p:last-of-type': { + color: 'gray', + }, + }, + }, +}) + +const step = definePartsStyle({ + body: { + p: 0, + display: 'flex', + gap: '24px', + + 'div:first-of-type': { + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + borderRadius: 'lg', + minW: '45px', + h: '45px', + border: '1px solid gray', + bgColor: 'home.step.icon_bg', + + svg: { + width: '25px', + height: '25px', + color: 'home.step.icon', + }, + }, + + 'div:last-of-type': { + 'p:first-of-type': { + fontSize: '17px', + lineHeight: '20.4px', + color: 'home.step.title', + fontWeight: 'bold', + mb: '6px', + }, + 'p:nth-of-type(2)': { + fontSize: '20px', + lineHeight: '24px', + fontWeight: 'bold', + mb: '8px', + }, + 'p:last-of-type': { + color: 'gray', + fontSize: '16px', + lineHeight: '28px', + }, + }, + }, +}) +const demo = definePartsStyle({ + container: { + w: 'full', + maxW: '400px', + _hover: { + boxShadow: 'var(--box-shadow-darker)', + }, + }, + body: { + bgColor: 'white', + p: '15px 20px', + borderRadius: 'lg', + + div: { + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + bgSize: 'cover', + bgPosition: 'start', + mb: '15px', + borderRadius: 'lg', + + svg: { + width: '75px', + height: '75px', + color: 'home.demo.icon', + }, + }, + '& p': { + fontSize: '14px', + fontWeight: 'bold', + textAlign: 'center', + }, + }, +}) +const faqs = definePartsStyle({ + container: { + borderRadius: 'none', + borderBottom: '1px solid rgb(229, 229, 229)', + }, + header: { + p: 0, + '& p': { + fontWeight: 'bold', + mb: '18px', + lineHeight: '30px', + }, + }, + body: { + p: 0, + mb: '17px', + + '& p': { + fontSize: '15px', + lineHeight: '32px', + }, + }, +}) +const client = definePartsStyle({ + container: { + border: 'none', + + _hover: { + lg: { + '& div:first-of-type': { + filter: 'none', + }, + '& span': { + display: 'block', + }, + }, + }, + }, + header: { + p: 0, + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + filter: 'grayscale(100%)', + h: { base: '35px', lg: '45px' }, + }, + body: { + p: 0, + fontSize: '10px', + minH: '40px', + + span: { + display: 'none', + textAlign: 'center', + fontSize: '12px', + fontWeight: 'bold', + color: '#666', + }, + }, +}) const variantsCards = { aside, + benefits, + client, detailed, + demo, + faqs, + 'icon-card': iconCard, 'no-elections': noElections, + step, 'types-voting': typesVoting, } export const Card = defineMultiStyleConfig({ variants: variantsCards }) diff --git a/src/themes/default/components/Checkbox.ts b/src/themes/default/components/Checkbox.ts index 8cee537c..11da2ba3 100644 --- a/src/themes/default/components/Checkbox.ts +++ b/src/themes/default/components/Checkbox.ts @@ -33,23 +33,48 @@ const radiobox = definePartsStyle({ top: 1, rounded: 'full', ml: 'auto', + + _checked: { + border: 'none', + }, }), label: defineStyle({ fontSize: 'sm', alignSelf: 'start', - '& div:first-of-type': { + '& > div:first-of-type': { display: 'flex', alignItems: 'center', gap: 2, fontWeight: 'bold', mb: 2, + maxW: '80%', }, '& > p': { fontSize: '12px', color: 'process_create.description', }, + + //pro plan, it allows opening the modal + '& > span': { + bgColor: 'process_create.pro_bg', + borderRadius: '10px', + position: 'absolute', + top: '3px', + right: '3px', + px: 2, + color: 'process_create.pro_color', + fontSize: '12px', + lineHeight: '24px', + }, + '& div:nth-of-type(2)': { + position: 'absolute', + h: '100%', + w: '100%', + top: 0, + left: 0, + }, }), }) diff --git a/src/themes/default/components/Footer.tsx b/src/themes/default/components/Footer.tsx index 9ceee91e..19501cc9 100644 --- a/src/themes/default/components/Footer.tsx +++ b/src/themes/default/components/Footer.tsx @@ -1,127 +1,103 @@ -import { Box, Code, Flex, Icon, Img, Link, List, ListItem, Text } from '@chakra-ui/react' -import { HR } from '@vocdoni/chakra-components' +import { Box, Flex, Icon, Image, Link, Text } from '@chakra-ui/react' import { Trans, useTranslation } from 'react-i18next' -import { FaDiscord, FaGithub, FaTwitter } from 'react-icons/fa' -import logo from '/assets/vocdoni_logo.svg' +import { FaDiscord, FaGithub } from 'react-icons/fa' +import { FaXTwitter } from 'react-icons/fa6' +import vcdLogo from '/assets/logo-classic.svg' const Footer = () => { const { t } = useTranslation() return ( - - - - vocdoni icon - - - {t('footer.company').toUpperCase()} - - - - {' '} - {t('footer.homepage')} - - - - - - {' '} - {t('footer.about')} - - - - - - {t('footer.blog')} - - - - - - {t('footer.developers').toUpperCase()} - - - - {t('footer.developer_portal')} - - - - - {t('footer.vocdoni_api')} - - - - - - {t('footer.vocdoni_app')} - - - - - - {t('footer.resources').toUpperCase()} - - - - {t('footer.tutorials')} - - - - - {t('footer.repos')} - - - - - {t('footer.discord')} - - - + <> + + + + + {t('footer.footer_subtitle')} + - - {t('footer.contact').toUpperCase()} - - - info@vocdoni.io - - - + + + {t('footer.company')} + + + Vocdoni + + + About Us + + + Contact + + + SDK + + + Developer Portal + + + Blog + + + + + {t('footer.uses_cases')} + + {t('footer.uses_cases1')} + {t('footer.uses_cases2')} + {t('footer.uses_cases3')} + {t('footer.uses_cases4')} + {t('footer.uses_cases5')} + + + + {t('footer.demo')} + + {t('footer.demo1')} + {t('footer.demo2')} + {t('footer.demo3')} + {t('footer.demo4')} + {t('footer.demo5')} + {t('footer.demo6')} + + -
- - Copyrights 2023 Vocdoni, Inc. All rights reserved - - + { link2: , }} /> - - - - - +
+ + + - - + + - - + +
-
+ ) } diff --git a/src/themes/default/components/Home.tsx b/src/themes/default/components/Home.tsx deleted file mode 100644 index d9b22356..00000000 --- a/src/themes/default/components/Home.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { Text } from '@chakra-ui/react' -import { useTranslation } from 'react-i18next' -import Banner from '~components/Home/Banner' -import VotingTypesBanner from '~components/Home/Voting' - -const Home = () => { - const { t } = useTranslation() - - return ( -
- - - - {t('banner_voting_types.bottom_text')} - -
- ) -} -export default Home diff --git a/src/themes/default/components/Home/Benefits.tsx b/src/themes/default/components/Home/Benefits.tsx new file mode 100644 index 00000000..8d66ce2f --- /dev/null +++ b/src/themes/default/components/Home/Benefits.tsx @@ -0,0 +1,131 @@ +import { Box, Card, CardBody, CardHeader, Flex, Text } from '@chakra-ui/react' +import { useTranslation } from 'react-i18next' + +const Benefits = () => { + const { t } = useTranslation() + + return ( + <> +
+
+ + + + + +
+
+ + + + {t('home.benefits.title')} + + + {t('home.benefits.subtitle_1')} + + + + {t('home.benefits.card_1.title')} + {t('home.benefits.card_1.description')} + + + {t('home.benefits.card_2.title')} + {t('home.benefits.card_2.description')} + + + {t('home.benefits.card_3.title')} + {t('home.benefits.card_3.description')} + + + {t('home.benefits.card_4.title')} + {t('home.benefits.card_4.description')} + + + {t('home.benefits.card_5.title')} + {t('home.benefits.card_5.description')} + + + {t('home.benefits.card_6.title')} + {t('home.benefits.card_6.description')} + + + + +
+
+ + + + + +
+
+ + ) +} + +export default Benefits diff --git a/src/themes/default/components/Home/Clients.tsx b/src/themes/default/components/Home/Clients.tsx new file mode 100644 index 00000000..8499cf01 --- /dev/null +++ b/src/themes/default/components/Home/Clients.tsx @@ -0,0 +1,115 @@ +import { Card, CardBody, CardHeader, Grid, Image, Text } from '@chakra-ui/react' +import { useTranslation } from 'react-i18next' +import barca from '/assets/barca.png' +import bellpuig from '/assets/bellpuig.svg.png' +import berga from '/assets/berga.svg.png' +import bisbal from '/assets/bisbal.svg' +import bloock from '/assets/bloock.png' +import coec from '/assets/coec.png' +import decidim from '/assets/decidim.jpg' +import erc from '/assets/erc.svg' +import omnium from '/assets/omnium.png' +import pirates from '/assets/pirates.svg' + +const Clients = () => { + const { t } = useTranslation() + + return ( + <> + + {t('home.clients_title')} + + + + + + + + F.C. Barcelona + + + + + + + + Omnium Cultural + + + + + + + + Ajuntament Berga + + + + + + + + Ajuntament la Bisbal + + + + + + + + COEC + + + + + + + + Esquerra Republicana + + + + + + + + Ajuntament Bellpuig + + + + + + + + Partit Pirata + + + + + + + + Decidim + + + + + + + + Bloock + + + + + ) +} +export default Clients diff --git a/src/themes/default/components/Home/ContactUs.tsx b/src/themes/default/components/Home/ContactUs.tsx new file mode 100644 index 00000000..0c0cc224 --- /dev/null +++ b/src/themes/default/components/Home/ContactUs.tsx @@ -0,0 +1,101 @@ +import { Box, Button, Card, CardBody, Flex, Image, Text } from '@chakra-ui/react' +import { useTranslation } from 'react-i18next' +import { FaFingerprint } from 'react-icons/fa' +import { MdDesignServices } from 'react-icons/md' +import advFeature from '/assets/vocdoni.jpeg' +import { Link as ReactRouterLink } from 'react-router-dom' + +const ContactUs = () => { + const { t } = useTranslation() + + return ( + + + + + + + + + + + + {t('home.contactus.header')} + + + {t('home.contactus.title')} + + + + + + + + + {t('home.contactus.card_1.title')} + {t('home.contactus.card_1.description')} + + + + + + + + + + {t('home.contactus.card_2.title')} + {t('home.contactus.card_2.description')} + + + + + + + + + ) +} + +export default ContactUs diff --git a/src/themes/default/components/Home/CreateProcess.tsx b/src/themes/default/components/Home/CreateProcess.tsx new file mode 100644 index 00000000..d7642601 --- /dev/null +++ b/src/themes/default/components/Home/CreateProcess.tsx @@ -0,0 +1,87 @@ +import { Box, Button, Flex, Image, Text } from '@chakra-ui/react' +import { useTranslation } from 'react-i18next' +import { FaRegCheckCircle } from 'react-icons/fa' +import devices from '/assets/devices_vocdoni.png' +import { Link as ReactRouterLink } from 'react-router-dom' +import { useClient } from '@vocdoni/react-providers' +import { useAccount } from 'wagmi' +import { useConnectModal } from '@rainbow-me/rainbowkit' + +const CreateProcess = () => { + const { t } = useTranslation() + const { isConnected } = useAccount() + const { account } = useClient() + const { openConnectModal } = useConnectModal() + + return ( + + + + {t('home.create_process.title')} + + + {t('home.create_process.subtitle')} + + + {isConnected && ( + + )} + + {!isConnected && ( + + )} + + + + + {t('home.create_process.helper_1')} + + + + {t('home.create_process.helper_2')} + + + + + + + + + ) +} + +export default CreateProcess diff --git a/src/themes/default/components/Home/Demo.tsx b/src/themes/default/components/Home/Demo.tsx new file mode 100644 index 00000000..ef8f9dd9 --- /dev/null +++ b/src/themes/default/components/Home/Demo.tsx @@ -0,0 +1,302 @@ +import { Box, Button, Card, CardBody, Flex, Grid, Radio, RadioGroup, Text, VStack } from '@chakra-ui/react' +import { useState } from 'react' +import { useTranslation } from 'react-i18next' +import { BsFillCheckCircleFill } from 'react-icons/bs' +import { FaGoogle, FaSlack } from 'react-icons/fa' +import { GiChoice } from 'react-icons/gi' +import { GoNumber } from 'react-icons/go' +import { HiCheckBadge } from 'react-icons/hi2' +import { ImListNumbered } from 'react-icons/im' +import { MdAutoGraph, MdOutlineLibraryAddCheck } from 'react-icons/md' +import { VscListFilter } from 'react-icons/vsc' +import agm from '/assets/agm.avif' +import participatory from '/assets/budgeting.avif' +import elections from '/assets/elections.avif' +import onlineSurvey from '/assets/online-survey.avif' +import onlineVoting from '/assets/online-voting.avif' +import softInt from '/assets/software-integration.avif' + +const Demo = () => { + const { t } = useTranslation() + + const [value, setValue] = useState('1') + + return ( + <> +
+
+ + + + + +
+
+ + + + + + {t('home.demo.header')} + + + {t('home.demo.title')} + + + {t('home.demo.description')} + + + + + + + + + + + + + {t('home.demo.use_cases.radio')} + + + + + + {t('home.demo.voting_type.radio')} + + + + + + {t('home.demo.census_type.radio')} + + + + {value === '1' && ( + + + {t('home.demo.use_cases.title')} + + + + + + + {t('home.demo.use_cases.card_1')} + + + + + + + + {t('home.demo.use_cases.card_2')} + + + + + + + + {t('home.demo.use_cases.card_3')} + + + + + + + + {t('home.demo.use_cases.card_4')} + + + + + + + + {t('home.demo.use_cases.card_5')} + + + + + + + + {t('home.demo.use_cases.card_6')} + + + + + + )} + {value === '2' && ( + + + {t('home.demo.voting_type.title')} + + + + + + + + + {t('home.demo.voting_type.card_1')} + + + + + + + + + + {t('home.demo.voting_type.card_2')} + + + + + + + + + + {t('home.demo.voting_type.card_3')} + + + + + + + + + + {t('home.demo.voting_type.card_4')} + + + + + + + + + + {t('home.demo.voting_type.card_5')} + + + + + + + + + + {t('home.demo.voting_type.card_6')} + + + + + + )} + {value === '3' && ( + + + {t('home.demo.census_type.title')} + + + + + + + + + {t('home.demo.census_type.card_1')} + + + + + + + + + + {t('home.demo.voting_type.card_6')} + + + + + + + + + + {t('home.demo.census_type.card_3')} + + + + + + )} + + +
+
+ + + + + +
+
+ + ) +} + +export default Demo diff --git a/src/themes/default/components/Home/Faqs.tsx b/src/themes/default/components/Home/Faqs.tsx new file mode 100644 index 00000000..7b00da85 --- /dev/null +++ b/src/themes/default/components/Home/Faqs.tsx @@ -0,0 +1,95 @@ +import { Box, Card, CardBody, CardHeader, Flex, Text } from '@chakra-ui/react' +import { useTranslation } from 'react-i18next' + +const Faqs = () => { + const { t } = useTranslation() + + return ( + + + {t('home.faqs.title')} + + + {t('home.faqs.subtitle')} + + + + + + {t('home.faqs.faq_1.title')} + + + {t('home.faqs.faq_1.description')} + + + + + {t('home.faqs.faq_2.title')} + + + {t('home.faqs.faq_2.description')} + + + + + {t('home.faqs.faq_3.title')} + + + {t('home.faqs.faq_3.description')} + + + + + {t('home.faqs.faq_8.title')} + + + {t('home.faqs.faq_8.description')} + + + + + + + {t('home.faqs.faq_4.title')} + + + {t('home.faqs.faq_4.description')} + + + + + {t('home.faqs.faq_5.title')} + + + {t('home.faqs.faq_5.description')} + + + + + {t('home.faqs.faq_6.title')} + + + {t('home.faqs.faq_6.description')} + + + + + {t('home.faqs.faq_7.title')} + + + {t('home.faqs.faq_7.description')} + + + + + + ) +} + +export default Faqs diff --git a/src/themes/default/components/Home/Features.tsx b/src/themes/default/components/Home/Features.tsx new file mode 100644 index 00000000..e75a3887 --- /dev/null +++ b/src/themes/default/components/Home/Features.tsx @@ -0,0 +1,165 @@ +import { Box, Card, CardBody, Flex, Text } from '@chakra-ui/react' +import { useTranslation } from 'react-i18next' +import { + FaVoteYea, + FaPalette, + FaUsers, + FaUserLock, + FaTasks, + FaNewspaper, + FaLanguage, + FaBullseye, + FaCubes, +} from 'react-icons/fa' + +const Features = () => { + const { t } = useTranslation() + + return ( + + + + {t('home.features.header')} + + + {t('home.features.title')} + + + {t('home.features.subtitle_1')} + + + + + + + + + + + + + {t('home.features.card_1.title')} + + {t('home.features.card_1.description')} + + + + + + + + + + + {t('home.features.card_2.title')} + + {t('home.features.card_2.description')} + + + + + + + + + + + {t('home.features.card_3.title')} + + {t('home.features.card_3.description')} + + + + + + + + + + + + + {t('home.features.card_4.title')} + + {t('home.features.card_4.description')} + + + + + + + + + + + {t('home.features.card_5.title')} + + {t('home.features.card_5.description')} + + + + + + + + + + + {t('home.features.card_6.title')} + + {t('home.features.card_6.description')} + + + + + + + + + + + + + {t('home.features.card_7.title')} + + {t('home.features.card_7.description')} + + + + + + + + + + + {t('home.features.card_8.title')} + + {t('home.features.card_8.description')} + + + + + + + + + + + {t('home.features.card_9.title')} + + {t('home.features.card_9.description')} + + + + + + + ) +} + +export default Features diff --git a/src/themes/default/components/Home/Process.tsx b/src/themes/default/components/Home/Process.tsx new file mode 100644 index 00000000..5c1134dd --- /dev/null +++ b/src/themes/default/components/Home/Process.tsx @@ -0,0 +1,82 @@ +import { Box, Card, CardBody, Flex, Image, Text } from '@chakra-ui/react' +import { useTranslation } from 'react-i18next' +import { + PiNumberSquareFourFill, + PiNumberSquareOneFill, + PiNumberSquareThreeFill, + PiNumberSquareTwoFill, +} from 'react-icons/pi' +import process from '/assets/vocdoni_usage.jpg' + +const Process = () => { + const { t } = useTranslation() + + return ( + + + {t('home.process.header')} + + + {t('home.process.title')} + + + {t('home.process.description_1')} + + + {t('home.process.description_2')} + + + + + + + + + + + + + {t('home.process.step_1.title')} + {t('home.process.step_1.description')} + + + + + + + + + + {t('home.process.step_2.title')} + {t('home.process.step_2.description')} + + + + + + + + + + {t('home.process.step_3.title')} + {t('home.process.step_3.description')} + + + + + + + + + + {t('home.process.step_4.title')} + {t('home.process.step_4.description')} + + + + + + + ) +} +export default Process diff --git a/src/themes/default/components/Home/Solutions.tsx b/src/themes/default/components/Home/Solutions.tsx new file mode 100644 index 00000000..4c133440 --- /dev/null +++ b/src/themes/default/components/Home/Solutions.tsx @@ -0,0 +1,136 @@ +import { Box, Card, CardBody, Flex, Image, Text } from '@chakra-ui/react' +import { useTranslation } from 'react-i18next' +import { FaArrowsToCircle, FaCircleCheck } from 'react-icons/fa6' +import { IoSettingsSharp } from 'react-icons/io5' +import { MdDesignServices } from 'react-icons/md' +import solutions from '/assets/solutions.png' + +const Solutions = () => { + const { t } = useTranslation() + return ( + + + + + + {t('home.solutions.header')} + + + {t('home.solutions.title')} + + + {t('home.solutions.subtitle_1')} + + + + + + + + + {t('home.solutions.card_1.title')} + {t('home.solutions.card_1.description')} + + + + + + + + + + {t('home.solutions.card_2.title')} + {t('home.solutions.card_2.description')} + + + + + + + + + + {t('home.solutions.card_3.title')} + {t('home.solutions.card_3.description')} + + + + + + + + + + + + + + + {t('home.solutions.img_card_1')} + + + + + + + {t('home.solutions.img_card_2')} + + + + + + + {t('home.solutions.img_card_3')} + + + + + + ) +} + +export default Solutions diff --git a/src/themes/default/components/Home/Support.tsx b/src/themes/default/components/Home/Support.tsx new file mode 100644 index 00000000..3968834d --- /dev/null +++ b/src/themes/default/components/Home/Support.tsx @@ -0,0 +1,114 @@ +import { Box, Button, Flex, Text } from '@chakra-ui/react' +import { useTranslation } from 'react-i18next' +import { FaPhoneVolume, FaRegCheckCircle } from 'react-icons/fa' +import { Link as ReactRouterLink } from 'react-router-dom' + +const Support = () => { + const { t } = useTranslation() + + return ( + + + + + + {t('home.support.header')} + + + {t('home.support.title')} + + + {t('home.support.subtitle')} + + + + + + + + + + {t('home.support.helper_1')} + + + + + + {t('home.support.helper_2')} + + + + + + {t('home.support.helper_3')} + + + + + + ) +} + +export default Support diff --git a/src/themes/default/components/Home/index.tsx b/src/themes/default/components/Home/index.tsx new file mode 100644 index 00000000..43746190 --- /dev/null +++ b/src/themes/default/components/Home/index.tsx @@ -0,0 +1,26 @@ +import Benefits from './Benefits' +import Clients from './Clients' +import ContactUs from './ContactUs' +import CreateProcess from './CreateProcess' +import Demo from './Demo' +import Faqs from './Faqs' +import Features from './Features' +import Process from './Process' +import Solutions from './Solutions' +import Support from './Support' + +const Home = () => ( + <> + + + + + + + + + + +) + +export default Home diff --git a/src/components/Navbar/LanguagesList.tsx b/src/themes/default/components/Navbar/LanguagesList.tsx similarity index 100% rename from src/components/Navbar/LanguagesList.tsx rename to src/themes/default/components/Navbar/LanguagesList.tsx diff --git a/src/components/Navbar/Menu.tsx b/src/themes/default/components/Navbar/Menu.tsx similarity index 100% rename from src/components/Navbar/Menu.tsx rename to src/themes/default/components/Navbar/Menu.tsx diff --git a/src/components/Navbar/index.tsx b/src/themes/default/components/Navbar/index.tsx similarity index 100% rename from src/components/Navbar/index.tsx rename to src/themes/default/components/Navbar/index.tsx diff --git a/src/themes/default/components/Radio.ts b/src/themes/default/components/Radio.ts new file mode 100644 index 00000000..0bd93c50 --- /dev/null +++ b/src/themes/default/components/Radio.ts @@ -0,0 +1,61 @@ +import { radioAnatomy } from '@chakra-ui/anatomy' +import { createMultiStyleConfigHelpers } from '@chakra-ui/react' + +const { definePartsStyle, defineMultiStyleConfig } = createMultiStyleConfigHelpers(radioAnatomy.keys) + +// define custom variant +const demo = definePartsStyle({ + control: { + display: 'none', + }, + label: { + display: 'flex', + alignItems: 'center', + gap: 2, + px: 3, + py: 5, + bgColor: 'white', + w: 'full', + borderRadius: 'md', + fontWeight: 'bold', + boxShadow: 'rgba(12, 8, 0, 0.03) 0px 2px 4.8px -1px, rgba(12, 8, 0, 0.06) 0px 4.4px 12px -1px', + + '& > div:first-of-type': { + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + border: '1px solid gray', + borderRadius: 'full', + width: '25px', + height: '25px', + + '& svg': { + display: 'none', + }, + }, + + '& > span': { + whiteSpace: 'nowrap', + }, + + _checked: { + '& > div:first-of-type': { + border: 'none', + '& svg': { + display: 'block', + color: 'home.demo.radio', + size: 25, + }, + }, + }, + + _hover: { + boxShadow: 'var(--box-shadow-darker)', + }, + }, +}) + +// export the component theme +export const Radio = defineMultiStyleConfig({ + variants: { demo }, +}) diff --git a/src/themes/default/components/Tabs.ts b/src/themes/default/components/Tabs.ts index dd91daee..c95b2108 100644 --- a/src/themes/default/components/Tabs.ts +++ b/src/themes/default/components/Tabs.ts @@ -5,11 +5,12 @@ const { definePartsStyle, defineMultiStyleConfig } = createMultiStyleConfigHelpe const card = definePartsStyle({ tablist: { display: 'flex', - justifyContent: 'flex-start', - flexWrap: { base: 'unset', md: 'wrap' }, - flexDirection: { base: 'column', md: 'row' }, - mb: 10, - gap: { base: 5 }, + flexDirection: 'column', + '& > div': { + display: 'grid', + gridTemplateColumns: 'repeat(auto-fill, minmax(250px, 1fr))', + gap: 5, + }, }, tab: { position: 'relative', @@ -21,11 +22,11 @@ const card = definePartsStyle({ flex: { md: '0 0 30%' }, p: 4, px: 6, - bgColor: 'white', boxShadow: 'var(--box-shadow)', + bgColor: 'process_create.advanced_checkbox_bg', borderBottom: 'none', color: 'process_create.description', - borderRadius: 0, + borderRadius: 'md', '& > p:nth-of-type(1)': { color: 'process_create.description', @@ -33,11 +34,26 @@ const card = definePartsStyle({ fontSize: 'xs', }, - '& > div': { + '& > span': { + bgColor: 'process_create.pro_bg', + borderRadius: '10px', + position: 'absolute', + top: '3px', + right: '3px', + px: 4, + color: 'process_create.pro_color', + pt: '2px', + fontSize: '12px', + }, + + '& > div:nth-of-type(1)': { display: 'flex', alignItems: 'center', w: 'full', gap: 3, + mt: 2, + fontSize: 'sm', + color: 'black', '& p': { fontWeight: 'bold', @@ -45,6 +61,16 @@ const card = definePartsStyle({ }, }, + '& > div:nth-of-type(2)': { + position: 'absolute', + top: 2.5, + right: 2.5, + w: 4, + h: 4, + borderRadius: 'full', + border: '1px solid lightgray', + }, + '& > svg': { w: 5, h: 5, @@ -60,10 +86,14 @@ const card = definePartsStyle({ '& > svg': { display: 'block', }, - '& > div > p': { - color: 'process_create.tabs_selected_color', + + '& > div:nth-of-type(2)': { + display: 'none', }, }, + _hover: { + boxShadow: 'var(--box-shadow-darker)', + }, }, }) const process = definePartsStyle({ diff --git a/src/themes/default/components/index.ts b/src/themes/default/components/index.ts index c244692f..1db49bad 100644 --- a/src/themes/default/components/index.ts +++ b/src/themes/default/components/index.ts @@ -16,6 +16,7 @@ import { Stepper } from './Stepper' import { Tabs } from './Tabs' import { Textarea } from './Textarea' import { Avatar } from './Avatar' +import { Radio } from './Radio' export { Alert, @@ -32,6 +33,7 @@ export { Link, Modal, QuestionsConfirmation, + Radio, SpreadsheetAccess, Stepper, Tabs, @@ -52,6 +54,7 @@ export const components = { Modal, ElectionQuestions, QuestionsConfirmation, + Radio, ElectionResults, SpreadsheetAccess, Stepper, diff --git a/src/themes/default/icons.tsx b/src/themes/default/icons.tsx index c66fd952..4120ebc3 100644 --- a/src/themes/default/icons.tsx +++ b/src/themes/default/icons.tsx @@ -9,4 +9,6 @@ export const Check = AiFillCheckCircle export const Logo = () => vocdoni icon +export const LogoMbl = () => vocdoni icon + export const GoBack = () => diff --git a/src/themes/onvote/colors.ts b/src/themes/onvote/colors.ts index 28912a47..4e34f148 100644 --- a/src/themes/onvote/colors.ts +++ b/src/themes/onvote/colors.ts @@ -8,6 +8,7 @@ export const colorsBase = { main: '#CBD5E0', dark: '#606f88', }, + green: '#48BB78', primary: { gray_light: '#b8bdc7', gray: '#656E81', @@ -24,6 +25,13 @@ export const colorsBase = { dark: '#fafafa', dark2: 'rgb(245, 247, 250)', }, + + cta: { + black: '#000000', + purple: 'rgb(127, 86, 214)', + blue: 'rgb(0, 0, 255)', + gray: 'rgb(194, 194, 194)', + }, } export const colors = { @@ -54,6 +62,7 @@ export const colors = { }, error: colorsBase.red, + success: colorsBase.green, checkbox: colorsBase.primary.main, @@ -164,8 +173,14 @@ export const colors = { }, description: colorsBase.gray.dark, description_logo: colorsBase.primary.main, + modal_pro: { + description: colorsBase.gray.dark, + border: colorsBase.primary.gray_light, + }, preview_option_question_before: colorsBase.black, preview_negative_balance: colorsBase.red, + pro_bg: colorsBase.primary.main, + pro_color: colorsBase.white.pure, section: colorsBase.white.pure, section_border: colorsBase.primary.gray_light, spreadsheet: { @@ -188,4 +203,10 @@ export const colors = { title: colorsBase.primary.main, }, results_progressbar_bg: colorsBase.white.pure, + web3_cta: { + onvote: colorsBase.cta.black, + farcaster: colorsBase.cta.purple, + plugins: colorsBase.cta.blue, + others: colorsBase.cta.gray, + }, } diff --git a/src/themes/onvote/components/Button.ts b/src/themes/onvote/components/Button.ts index 6a062d7b..4927ec70 100644 --- a/src/themes/onvote/components/Button.ts +++ b/src/themes/onvote/components/Button.ts @@ -395,12 +395,6 @@ const closeForm = defineStyle({ const goBack = defineStyle({ p: 0, - - '& img': { - w: 1.5, - mr: 1, - mb: '1px', - }, '& span': { color: 'organization.go_back_btn', overflow: 'hidden', @@ -410,6 +404,17 @@ const goBack = defineStyle({ }, }) +const tryItNow = defineStyle({ + background: 'web3_cta.onvote', + color: 'white', + px: 8, + height: 9, + fontWeight: 'normal', + borderRadius: 'var(--chakra-radii-md)', + fontSize: { base: '13px' }, + boxShadow: 'rgba(0, 0, 0, 0.1) 0px 0px 10px', +}) + export const Button = defineStyleConfig({ baseStyle, variants: { @@ -423,6 +428,7 @@ export const Button = defineStyleConfig({ process, secondary, transparent, + 'try-it-now': tryItNow, }, defaultProps: { colorScheme: 'primary', diff --git a/src/themes/onvote/components/Card.ts b/src/themes/onvote/components/Card.ts index e72cabc7..9ac12736 100644 --- a/src/themes/onvote/components/Card.ts +++ b/src/themes/onvote/components/Card.ts @@ -213,6 +213,7 @@ const typesVoting = definePartsStyle({ lineHeight: '24px', }, }) + const aside = definePartsStyle({ container: { direction: 'column', @@ -232,10 +233,88 @@ const aside = definePartsStyle({ }, }) +const imageCard = definePartsStyle({ + container: { + ...cardCommonStyles.container, + w: '300px', + height: '480px', + display: 'flex', + flexDirection: 'column', + position: 'relative', + justify: 'center', + alignItems: 'center', + bgColor: 'transparent', + }, + + header: { + ...cardCommonStyles.header, + height: 40, + width: 60, + minH: 40, + mt: 7, + overflow: 'hidden', + position: 'relative', + display: 'flex', + justify: 'center', + alignItems: 'center', + + '& > div': { + height: 40, + width: 60, + overflow: 'hidden', + position: 'relative', + display: 'flex', + justify: 'center', + alignItems: 'center', + w: '100%', + h: '100%', + boxShadow: 'rgba(0, 0, 0, 0.08) 0px 0px 0px', + borderRadius: 'var(--chakra-radii-md)', + }, + + '& img': { + width: '100%', + minH: '100%', + }, + }, + + body: { + ...cardCommonStyles.header, + + width: 60, + display: 'flex', + justify: 'center', + alignItems: 'start', + flexDirection: 'column', + + '& > p:first-of-type': { + fontWeight: 'bold', + fontSize: { base: '18px' }, + mt: 4, + mb: 6, + textAlign: { base: 'start' }, + fontFamily: 'pixeloidsans, monospace', + }, + + '& p:nth-of-type(2)': { + fontSize: { base: '16px' }, + }, + }, + + footer: { + ...cardCommonStyles.footer, + pb: 3, + '& a': { + fontWeight: '600', + }, + }, +}) + const variantsCards = { aside, detailed, 'no-elections': noElections, 'types-voting': typesVoting, + 'image-card': imageCard, } export const Card = defineMultiStyleConfig({ variants: variantsCards }) diff --git a/src/themes/onvote/components/Checkbox.ts b/src/themes/onvote/components/Checkbox.ts index 1bdabca0..d4eddb04 100644 --- a/src/themes/onvote/components/Checkbox.ts +++ b/src/themes/onvote/components/Checkbox.ts @@ -45,7 +45,7 @@ const radiobox = definePartsStyle({ fontSize: 'sm', alignSelf: 'start', - '& div:first-of-type': { + '& > div:first-of-type': { display: 'flex', alignItems: 'center', gap: 2, @@ -62,6 +62,25 @@ const radiobox = definePartsStyle({ fontSize: '14px', color: 'process_create.description', }, + //pro plan, it allows opening the modal + '& > span': { + bgColor: 'process_create.pro_bg', + borderRadius: '10px', + position: 'absolute', + top: '3px', + right: '3px', + px: 2, + color: 'process_create.pro_color', + fontSize: '12px', + lineHeight: '24px', + }, + '& div:nth-of-type(2)': { + position: 'absolute', + h: '100%', + w: '100%', + top: 0, + left: 0, + }, }), }) diff --git a/src/themes/onvote/components/Home.tsx b/src/themes/onvote/components/Home.tsx index 1a7c6075..b29ce56f 100644 --- a/src/themes/onvote/components/Home.tsx +++ b/src/themes/onvote/components/Home.tsx @@ -1,9 +1,9 @@ import { Box } from '@chakra-ui/react' import Banner from '~components/Home/BannerOnVote' import Features from '~components/Home/Features' -import Roadmap from '~components/Home/Roadmap' import VotingTypesBanner from '~components/Home/Voting' import homeBg from '/assets/home-bg.png' +import Governance from '~components/Home/Governance' const Home = () => { return ( @@ -19,12 +19,12 @@ const Home = () => { > - + - + ) diff --git a/src/themes/onvote/components/Navbar/LanguagesList.tsx b/src/themes/onvote/components/Navbar/LanguagesList.tsx new file mode 100644 index 00000000..49b7fd03 --- /dev/null +++ b/src/themes/onvote/components/Navbar/LanguagesList.tsx @@ -0,0 +1,68 @@ +import { ChevronDownIcon, ChevronUpIcon } from '@chakra-ui/icons' +import { Button, Menu, MenuButton, MenuItem, MenuList } from '@chakra-ui/react' +import { useTranslation } from 'react-i18next' +import { FaGlobeAmericas } from 'react-icons/fa' +import { LanguagesSlice } from '~i18n/languages.mjs' + +export const LanguagesList = ({ closeOnSelect }: { closeOnSelect: boolean }) => { + const { i18n } = useTranslation() + + const languages = LanguagesSlice as { [key: string]: string } + + const isAnyLanguageSelected = + Object.keys(languages).some((l) => l === i18n.language) && + import.meta.env.features.languages.includes(i18n.language) + + return ( + <> + {import.meta.env.features.languages.map((k: string) => ( + { + i18n.changeLanguage(k) + }} + closeOnSelect={closeOnSelect} + w='full' + display='flex' + justifyContent={closeOnSelect ? 'center' : 'start'} + fontWeight={ + k === i18n.language || (k === import.meta.env.features.languages[0] && !isAnyLanguageSelected) + ? 'extrabold' + : '' + } + borderRadius='none' + > + {k.toUpperCase()} + + ))} + + ) +} + +export const LanguagesMenu = () => { + const { t } = useTranslation() + + if (import.meta.env.features.languages.length <= 1) return null + + return ( + + {({ isOpen, onClose }) => ( + <> + : } + minW='none' + > + + + + + + + )} + + ) +} diff --git a/src/themes/onvote/components/Navbar/Menu.tsx b/src/themes/onvote/components/Navbar/Menu.tsx new file mode 100644 index 00000000..41e8dfbe --- /dev/null +++ b/src/themes/onvote/components/Navbar/Menu.tsx @@ -0,0 +1,183 @@ +import { ChevronDownIcon, ChevronUpIcon, CopyIcon } from '@chakra-ui/icons' +import { + Box, + Button, + Flex, + HStack, + Icon, + IconButton, + Link, + MenuItem, + MenuList, + Text, + useClipboard, +} from '@chakra-ui/react' +import { Balance, HR } from '@vocdoni/chakra-components' +import { useClient } from '@vocdoni/react-providers' +import { useState } from 'react' +import { useTranslation } from 'react-i18next' +import { FaWallet } from 'react-icons/fa' +import { HiShoppingCart } from 'react-icons/hi' +import { MdOutlineLogout } from 'react-icons/md' +import { Link as ReactRouterLink } from 'react-router-dom' +import { useDisconnect } from 'wagmi' +import { useOrganizationModal } from '~components/Organization/OrganizationModalProvider' +import { addressTextOverflow } from '~constants' +import { LanguagesList } from './LanguagesList' + +const MenuDropdown = () => { + const { t } = useTranslation() + const { disconnect } = useDisconnect() + const { account, clear } = useClient() + const { onCopy } = useClipboard(account?.address as string) + + const [isOpenMenuLanguages, setIsOpenMenuLanguages] = useState(false) + + const { onOpen } = useOrganizationModal() + + return ( + + {account && ( + <> + { + onCopy() + }} + > + {t('menu.wallet')} + + + + + {addressTextOverflow((account?.address as string) || '', 10)} + } + aria-label={t('menu.copy_aria_label')} + onClick={() => { + onCopy() + }} + /> + + + + + + + + {import.meta.env.features.faucet && ( + + )} + + + {t('menu.organization')} + + )} + + {import.meta.env.features.languages.length > 1 && ( + <> + setIsOpenMenuLanguages((prev) => !prev)} + display='flex' + flexDirection='column' + px={0} + pb={0} + > + + {t('menu.languages')} + {isOpenMenuLanguages ? : } + + + {isOpenMenuLanguages && } + + )} + + {t('menu.documentation')} + +
+ { + disconnect() + clear() + }} + fontWeight='bold' + > + + {t('menu.logout')} + + + {t('menu.terms')} + + + {t('menu.privacy')} + +
+ ) +} + +export default MenuDropdown diff --git a/src/themes/onvote/components/Navbar/index.tsx b/src/themes/onvote/components/Navbar/index.tsx new file mode 100644 index 00000000..21e4f4da --- /dev/null +++ b/src/themes/onvote/components/Navbar/index.tsx @@ -0,0 +1,93 @@ +import { AddIcon, ChevronDownIcon, ChevronUpIcon } from '@chakra-ui/icons' +import { Avatar, Box, Button, Flex, Icon, List, ListItem, Menu, MenuButton, Text } from '@chakra-ui/react' +import { useConnectModal } from '@rainbow-me/rainbowkit' +import { useClient } from '@vocdoni/react-providers' +import { ensure0x } from '@vocdoni/sdk' +import { useTranslation } from 'react-i18next' +import { MdHowToVote } from 'react-icons/md' +import { Link as ReactRouterLink } from 'react-router-dom' +import { useAccount } from 'wagmi' +import Logo from '~components/Layout/Logo' +import { LanguagesMenu } from './LanguagesList' +import MenuDropdown from './Menu' + +const Navbar = () => { + const { isConnected } = useAccount() + const { t } = useTranslation() + const { account } = useClient() + const { openConnectModal } = useConnectModal() + + return ( + + + + + {isConnected && ( + + + + )} + + {account && account?.account?.name?.default.length > 0 && ( + + + + )} + + {!isConnected && ( + <> + + + + + + + + )} + {isConnected && ( + + + {({ isOpen }) => ( + <> + + + + {isOpen ? ( + + ) : ( + + )} + + + + + )} + + + )} + + + ) +} + +export default Navbar diff --git a/src/themes/onvote/components/Tabs.ts b/src/themes/onvote/components/Tabs.ts index 8b752ee6..67c53b2d 100644 --- a/src/themes/onvote/components/Tabs.ts +++ b/src/themes/onvote/components/Tabs.ts @@ -6,10 +6,12 @@ const { definePartsStyle, defineMultiStyleConfig } = createMultiStyleConfigHelpe const card = definePartsStyle({ tablist: { display: 'flex', - justifyContent: 'space-around', - flexDirection: { base: 'column', md: 'row' }, - mb: 10, - gap: { base: 5, xl: 0 }, + flexDirection: 'column', + '& > div': { + display: 'grid', + gridTemplateColumns: 'repeat(auto-fill, minmax(250px, 1fr))', + gap: 5, + }, }, tab: { position: 'relative', diff --git a/src/themes/onvote/icons.tsx b/src/themes/onvote/icons.tsx index 5bafaef7..6d5a2cd1 100644 --- a/src/themes/onvote/icons.tsx +++ b/src/themes/onvote/icons.tsx @@ -3,6 +3,7 @@ import check from '/assets/check-icon.svg' import close from '/assets/close-icon.svg' import goback from '/assets/goback-icon.svg' import logo from '/assets/onvote-icon.svg' +import logoMbl from '/assets/vocdoni_icon.png' export const Close = (props: ImgProps) => close @@ -10,4 +11,6 @@ export const Check = (props: ImgProps) => check onvote logo +export const LogoMbl = (props: ImgProps) => vocdoni mbl icon + export const GoBack = (props: ImgProps) => go back diff --git a/src/themes/theme.d.ts b/src/themes/theme.d.ts index 294214f8..4cd4189d 100644 --- a/src/themes/theme.d.ts +++ b/src/themes/theme.d.ts @@ -19,6 +19,7 @@ declare module '~theme/icons' { export const Check: import('react').FC export const Close: import('react').FC export const Logo: import('react').FC + export const LogoMbl: import('react').FC export const GoBack: import('react').FC } @@ -26,6 +27,9 @@ declare module '~theme/Fonts' { export default import('react').FC } +declare module '~theme/components/Navbar' { + export default import('react').FC +} declare module '~theme/components/Footer' { export default import('react').FC } diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts index 11f02fe2..b1f45c78 100644 --- a/src/vite-env.d.ts +++ b/src/vite-env.d.ts @@ -1 +1,2 @@ /// +/// diff --git a/vite.config.ts b/vite.config.ts index 9542f3c0..5786a3f7 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -5,6 +5,7 @@ import { createHtmlPlugin } from 'vite-plugin-html' import tsconfigPaths from 'vite-tsconfig-paths' import features from './vite/features' import themes from './vite/themes' +import svgr from 'vite-plugin-svgr' // https://vitejs.dev/config/ const viteconfig = ({ mode }) => { @@ -21,6 +22,11 @@ const viteconfig = ({ mode }) => { const commit = execSync('git rev-parse --short HEAD').toString() + let defaultCensusSize = Number(process.env.DEFAULT_CENSUS_SIZE) + if (!defaultCensusSize) { + defaultCensusSize = 5000 + } + return defineConfig({ base, build: { @@ -32,12 +38,17 @@ const viteconfig = ({ mode }) => { 'import.meta.env.CUSTOM_FAUCET_URL': JSON.stringify(process.env.CUSTOM_FAUCET_URL), 'import.meta.env.CSP_PUBKEY': JSON.stringify(process.env.CSP_PUBKEY), 'import.meta.env.CSP_URL': JSON.stringify(process.env.CSP_URL), + 'import.meta.env.DEFAULT_CENSUS_SIZE': JSON.stringify(defaultCensusSize), + 'import.meta.env.EMAILJS_SERVICE_ID': JSON.stringify(process.env.EMAILJS_SERVICE_ID), + 'import.meta.env.EMAILJS_TEMPLATE_ID': JSON.stringify(process.env.EMAILJS_TEMPLATE_ID), + 'import.meta.env.EMAILJS_PUBLIC_ID': JSON.stringify(process.env.EMAILJS_PUBLIC_ID), }, plugins: [ tsconfigPaths(), themes(), features(), react(), + svgr(), createHtmlPlugin({ template: `public/${process.env.THEME || 'default'}/index.html`, minify: { diff --git a/vite/features.ts b/vite/features.ts index 733757ae..6aa1d535 100644 --- a/vite/features.ts +++ b/vite/features.ts @@ -7,12 +7,26 @@ const features = () => { anonymous: true, overwrite: true, secret: true, + customization: false, }, login: ['web3', 'web2'], - census: ['spreadsheet', 'token', 'web3', 'csp'], + census: ['spreadsheet', 'token', 'web3', 'csp', 'gitcoin'], + unimplemented_census: [], + voting_type: ['single'], + unimplemented_voting_type: [], languages: ['ca', 'en', 'es'], } + const features = merge.withOptions({ mergeArrays: false }, defaults, JSON.parse(process.env.FEATURES || '{}')) + const unimplemented_census = ['phone', 'email', 'crm', 'database', 'digital_certificate'] + const unimplemented_voting_type = ['multi', 'approval', 'participatory', 'borda'] + + features.unimplemented_census.forEach((el) => { + if (!unimplemented_census.includes(el)) throw new Error(`Unimplemented census ${el} does not exist`) + }) + features.unimplemented_voting_type.forEach((el) => { + if (!unimplemented_voting_type.includes(el)) throw new Error(`Unimplemented voting type ${el} does not exist`) + }) // Ensure at least one item is loaded in each feature array if (!features.login.length) { features.login = ['web3'] @@ -37,6 +51,7 @@ const features = () => { token: false, web3: false, csp: false, + gitcoin: false, } for (const census of features.census) { features._census[census] = true diff --git a/yarn.lock b/yarn.lock index f67512f3..a03cbf7e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -33,6 +33,27 @@ resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.23.5.tgz#ffb878728bb6bdcb6f4510aa51b1be9afb8cfd98" integrity sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw== +"@babel/core@^7.21.3": + version "7.24.0" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.24.0.tgz#56cbda6b185ae9d9bed369816a8f4423c5f2ff1b" + integrity sha512-fQfkg0Gjkza3nf0c7/w6Xf34BW4YvzNfACRLmmb7XRLa6XHdR+K9AlJlxneFfWYf6uhOzuzZVTjF/8KfndZANw== + dependencies: + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.23.5" + "@babel/generator" "^7.23.6" + "@babel/helper-compilation-targets" "^7.23.6" + "@babel/helper-module-transforms" "^7.23.3" + "@babel/helpers" "^7.24.0" + "@babel/parser" "^7.24.0" + "@babel/template" "^7.24.0" + "@babel/traverse" "^7.24.0" + "@babel/types" "^7.24.0" + convert-source-map "^2.0.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" + "@babel/core@^7.23.5": version "7.23.9" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.23.9.tgz#b028820718000f267870822fec434820e9b1e4d1" @@ -156,6 +177,15 @@ "@babel/traverse" "^7.23.9" "@babel/types" "^7.23.9" +"@babel/helpers@^7.24.0": + version "7.24.0" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.24.0.tgz#a3dd462b41769c95db8091e49cfe019389a9409b" + integrity sha512-ulDZdc0Aj5uLc5nETsa7EPx2L7rM0YJM8r7ck7U73AXi7qOV44IHHRAYZHY6iU1rr3C5N4NtTmMRUJP6kwCWeA== + dependencies: + "@babel/template" "^7.24.0" + "@babel/traverse" "^7.24.0" + "@babel/types" "^7.24.0" + "@babel/highlight@^7.23.4": version "7.23.4" resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.23.4.tgz#edaadf4d8232e1a961432db785091207ead0621b" @@ -170,6 +200,11 @@ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.9.tgz#7b903b6149b0f8fa7ad564af646c4c38a77fc44b" integrity sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA== +"@babel/parser@^7.24.0": + version "7.24.0" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.24.0.tgz#26a3d1ff49031c53a97d03b604375f028746a9ac" + integrity sha512-QuP/FxEAzMSjXygs8v4N9dvdXzEHN4W1oF3PxuWAtPo08UdM17u89RDMgjLn/mlc56iM0HlLmVkO/wgR+rDgHg== + "@babel/plugin-transform-react-jsx-self@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.23.3.tgz#ed3e7dadde046cce761a8e3cf003a13d1a7972d9" @@ -200,6 +235,15 @@ "@babel/parser" "^7.23.9" "@babel/types" "^7.23.9" +"@babel/template@^7.24.0": + version "7.24.0" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.24.0.tgz#c6a524aa93a4a05d66aaf31654258fae69d87d50" + integrity sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA== + dependencies: + "@babel/code-frame" "^7.23.5" + "@babel/parser" "^7.24.0" + "@babel/types" "^7.24.0" + "@babel/traverse@^7.23.9": version "7.23.9" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.9.tgz#2f9d6aead6b564669394c5ce0f9302bb65b9d950" @@ -216,6 +260,22 @@ debug "^4.3.1" globals "^11.1.0" +"@babel/traverse@^7.24.0": + version "7.24.0" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.24.0.tgz#4a408fbf364ff73135c714a2ab46a5eab2831b1e" + integrity sha512-HfuJlI8qq3dEDmNU5ChzzpZRWq+oxCZQyMzIMEqLho+AQnhMnKQUzH6ydo3RBl/YjPCuk68Y6s0Gx0AeyULiWw== + dependencies: + "@babel/code-frame" "^7.23.5" + "@babel/generator" "^7.23.6" + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-function-name" "^7.23.0" + "@babel/helper-hoist-variables" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + "@babel/parser" "^7.24.0" + "@babel/types" "^7.24.0" + debug "^4.3.1" + globals "^11.1.0" + "@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.22.15", "@babel/types@^7.22.5", "@babel/types@^7.23.0", "@babel/types@^7.23.6", "@babel/types@^7.23.9": version "7.23.9" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.9.tgz#1dd7b59a9a2b5c87f8b41e52770b5ecbf492e002" @@ -225,6 +285,15 @@ "@babel/helper-validator-identifier" "^7.22.20" to-fast-properties "^2.0.0" +"@babel/types@^7.21.3", "@babel/types@^7.24.0": + version "7.24.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.24.0.tgz#3b951f435a92e7333eba05b7566fd297960ea1bf" + integrity sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w== + dependencies: + "@babel/helper-string-parser" "^7.23.4" + "@babel/helper-validator-identifier" "^7.22.20" + to-fast-properties "^2.0.0" + "@chakra-ui/accordion@2.3.1": version "2.3.1" resolved "https://registry.yarnpkg.com/@chakra-ui/accordion/-/accordion-2.3.1.tgz#a326509e286a5c4e8478de9bc2b4b05017039e6b" @@ -1061,6 +1130,11 @@ stream-browserify "^3.0.0" util "^0.12.4" +"@emailjs/browser@^4.1.0": + version "4.1.0" + resolved "https://registry.npmjs.org/@emailjs/browser/-/browser-4.1.0.tgz#141225969a2abfa1ec6e6f3240b3637a78cace16" + integrity sha512-AWLTmFli2NIKjBXs4Fql1B5z8i3W2r+CKCaxp0DdcFzcIQxrCS2TzmqJtIiPZKh9FpZgqueQTivBx6QOxaz89Q== + "@emotion/babel-plugin@^11.11.0": version "11.11.0" resolved "https://registry.yarnpkg.com/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz#c2d872b6a7767a9d176d007f5b31f7d504bb5d6c" @@ -2324,6 +2398,15 @@ estree-walker "^2.0.1" picomatch "^2.2.2" +"@rollup/pluginutils@^5.0.5": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-5.1.0.tgz#7e53eddc8c7f483a4ad0b94afb1f7f5fd3c771e0" + integrity sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g== + dependencies: + "@types/estree" "^1.0.0" + estree-walker "^2.0.2" + picomatch "^2.3.1" + "@safe-global/safe-apps-provider@^0.18.1": version "0.18.2" resolved "https://registry.yarnpkg.com/@safe-global/safe-apps-provider/-/safe-apps-provider-0.18.2.tgz#336f3f4bb6ebbad9354e6551687491efc73991bc" @@ -2549,6 +2632,89 @@ "@stablelib/random" "^1.0.2" "@stablelib/wipe" "^1.0.1" +"@svgr/babel-plugin-add-jsx-attribute@8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz#4001f5d5dd87fa13303e36ee106e3ff3a7eb8b22" + integrity sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g== + +"@svgr/babel-plugin-remove-jsx-attribute@8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-8.0.0.tgz#69177f7937233caca3a1afb051906698f2f59186" + integrity sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA== + +"@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-8.0.0.tgz#c2c48104cfd7dcd557f373b70a56e9e3bdae1d44" + integrity sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA== + +"@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-8.0.0.tgz#8fbb6b2e91fa26ac5d4aa25c6b6e4f20f9c0ae27" + integrity sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ== + +"@svgr/babel-plugin-svg-dynamic-title@8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-8.0.0.tgz#1d5ba1d281363fc0f2f29a60d6d936f9bbc657b0" + integrity sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og== + +"@svgr/babel-plugin-svg-em-dimensions@8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-8.0.0.tgz#35e08df300ea8b1d41cb8f62309c241b0369e501" + integrity sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g== + +"@svgr/babel-plugin-transform-react-native-svg@8.1.0": + version "8.1.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-8.1.0.tgz#90a8b63998b688b284f255c6a5248abd5b28d754" + integrity sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q== + +"@svgr/babel-plugin-transform-svg-component@8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-8.0.0.tgz#013b4bfca88779711f0ed2739f3f7efcefcf4f7e" + integrity sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw== + +"@svgr/babel-preset@8.1.0": + version "8.1.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-preset/-/babel-preset-8.1.0.tgz#0e87119aecdf1c424840b9d4565b7137cabf9ece" + integrity sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug== + dependencies: + "@svgr/babel-plugin-add-jsx-attribute" "8.0.0" + "@svgr/babel-plugin-remove-jsx-attribute" "8.0.0" + "@svgr/babel-plugin-remove-jsx-empty-expression" "8.0.0" + "@svgr/babel-plugin-replace-jsx-attribute-value" "8.0.0" + "@svgr/babel-plugin-svg-dynamic-title" "8.0.0" + "@svgr/babel-plugin-svg-em-dimensions" "8.0.0" + "@svgr/babel-plugin-transform-react-native-svg" "8.1.0" + "@svgr/babel-plugin-transform-svg-component" "8.0.0" + +"@svgr/core@^8.1.0": + version "8.1.0" + resolved "https://registry.yarnpkg.com/@svgr/core/-/core-8.1.0.tgz#41146f9b40b1a10beaf5cc4f361a16a3c1885e88" + integrity sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA== + dependencies: + "@babel/core" "^7.21.3" + "@svgr/babel-preset" "8.1.0" + camelcase "^6.2.0" + cosmiconfig "^8.1.3" + snake-case "^3.0.4" + +"@svgr/hast-util-to-babel-ast@8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-8.0.0.tgz#6952fd9ce0f470e1aded293b792a2705faf4ffd4" + integrity sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q== + dependencies: + "@babel/types" "^7.21.3" + entities "^4.4.0" + +"@svgr/plugin-jsx@^8.1.0": + version "8.1.0" + resolved "https://registry.yarnpkg.com/@svgr/plugin-jsx/-/plugin-jsx-8.1.0.tgz#96969f04a24b58b174ee4cd974c60475acbd6928" + integrity sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA== + dependencies: + "@babel/core" "^7.21.3" + "@svgr/babel-preset" "8.1.0" + "@svgr/hast-util-to-babel-ast" "8.0.0" + svg-parser "^2.0.4" + "@tanstack/query-core@4.36.1": version "4.36.1" resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-4.36.1.tgz#79f8c1a539d47c83104210be2388813a7af2e524" @@ -2678,6 +2844,11 @@ dependencies: "@types/ms" "*" +"@types/estree@^1.0.0": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4" + integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw== + "@types/hast@^2.0.0": version "2.3.10" resolved "https://registry.yarnpkg.com/@types/hast/-/hast-2.3.10.tgz#5c9d9e0b304bbb8879b857225c5ebab2d81d7643" @@ -2917,9 +3088,9 @@ react-refresh "^0.14.0" "@vocdoni/chakra-components@^0.7.12": - version "0.7.13" - resolved "https://registry.yarnpkg.com/@vocdoni/chakra-components/-/chakra-components-0.7.13.tgz#cd61ff7aa2ea1d035750952cf9047c2e98b00b67" - integrity sha512-3yJFAcNwRu8Q7rChFwZ7DCVSWewl0a3pYhw7HQyBN4+GBWfofiXfcW/KHh5qlfst1zwIGfA4JsxXPUYWqHa1bg== + version "0.7.15" + resolved "https://registry.yarnpkg.com/@vocdoni/chakra-components/-/chakra-components-0.7.15.tgz#8cffba1cb0d07217d26fe90bdd253c8a63781212" + integrity sha512-03WGQelsek1WJnEHl3Fz4HPfkP2sLdjLO+yJ1Kw4+BePVD82UCsGTxq/YfQB8QOIUxb9JZHzGsKKbf6k/c1H/Q== dependencies: "@vocdoni/react-providers" "~0.3.8" @@ -3771,6 +3942,11 @@ camelcase@^5.0.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== +camelcase@^6.2.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" + integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== + caniuse-lite@^1.0.30001580: version "1.0.30001584" resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001584.tgz#5e3ea0625d048d5467670051687655b1f7bf7dfd" @@ -4049,6 +4225,16 @@ cosmiconfig@^7.0.0: path-type "^4.0.0" yaml "^1.10.0" +cosmiconfig@^8.1.3: + version "8.3.6" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-8.3.6.tgz#060a2b871d66dba6c8538ea1118ba1ac16f5fae3" + integrity sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA== + dependencies: + import-fresh "^3.3.0" + js-yaml "^4.1.0" + parse-json "^5.2.0" + path-type "^4.0.0" + cross-fetch@^3.1.4: version "3.1.8" resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.8.tgz#0327eba65fd68a7d119f8fb2bf9334a1a7956f82" @@ -4558,7 +4744,7 @@ escape-string-regexp@^5.0.0: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz#4683126b500b61762f2dbebace1806e8be31b1c8" integrity sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw== -estree-walker@^2.0.1: +estree-walker@^2.0.1, estree-walker@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac" integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w== @@ -5217,7 +5403,7 @@ ieee754@^1.2.1: resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== -import-fresh@^3.2.1: +import-fresh@^3.2.1, import-fresh@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== @@ -5635,7 +5821,7 @@ js-sha3@0.8.0: resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== -js-yaml@4.1.0: +js-yaml@4.1.0, js-yaml@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== @@ -6655,7 +6841,7 @@ parent-module@^1.0.0: dependencies: callsites "^3.0.0" -parse-json@^5.0.0: +parse-json@^5.0.0, parse-json@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== @@ -7516,6 +7702,14 @@ slash@^4.0.0: resolved "https://registry.yarnpkg.com/slash/-/slash-4.0.0.tgz#2422372176c4c6c5addb5e2ada885af984b396a7" integrity sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew== +snake-case@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/snake-case/-/snake-case-3.0.4.tgz#4f2bbd568e9935abdfd593f34c691dadb49c452c" + integrity sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg== + dependencies: + dot-case "^3.0.4" + tslib "^2.0.3" + sonic-boom@^2.2.1: version "2.8.0" resolved "https://registry.yarnpkg.com/sonic-boom/-/sonic-boom-2.8.0.tgz#c1def62a77425090e6ad7516aad8eb402e047611" @@ -7713,6 +7907,11 @@ supports-preserve-symlinks-flag@^1.0.0: resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== +svg-parser@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/svg-parser/-/svg-parser-2.0.4.tgz#fdc2e29e13951736140b76cb122c8ee6630eb6b5" + integrity sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ== + symlink-or-copy@^1.1.8, symlink-or-copy@^1.2.0, symlink-or-copy@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/symlink-or-copy/-/symlink-or-copy-1.3.1.tgz#9506dd64d8e98fa21dcbf4018d1eab23e77f71fe" @@ -8189,6 +8388,15 @@ vite-plugin-html@^3.2.0: node-html-parser "^5.3.3" pathe "^0.2.0" +vite-plugin-svgr@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/vite-plugin-svgr/-/vite-plugin-svgr-4.2.0.tgz#9f3bf5206b0ec510287e56d16f1915e729bb4e6b" + integrity sha512-SC7+FfVtNQk7So0XMjrrtLAbEC8qjFPifyD7+fs/E6aaNdVde6umlVVh0QuwDLdOMu7vp5RiGFsB70nj5yo0XA== + dependencies: + "@rollup/pluginutils" "^5.0.5" + "@svgr/core" "^8.1.0" + "@svgr/plugin-jsx" "^8.1.0" + vite-tsconfig-paths@^4.2.1: version "4.3.1" resolved "https://registry.yarnpkg.com/vite-tsconfig-paths/-/vite-tsconfig-paths-4.3.1.tgz#28762938151e7c80aec9d70c57e65ddce43a576f"