From d80f22c5239a31d3c96c627c3e953534d6ce7ff4 Mon Sep 17 00:00:00 2001 From: juliopavila Date: Wed, 15 May 2024 15:14:23 -0300 Subject: [PATCH] feat: apply app feedback --- packages/client/src/App.tsx | 2 +- packages/client/src/components/Cards/Card.tsx | 9 +- .../client/src/components/Cards/PollCard.tsx | 35 ++-- .../src/components/Cards/PollCardResult.tsx | 28 ++-- packages/client/src/components/NavMenu.tsx | 4 +- packages/client/src/components/VotesBadge.tsx | 7 +- .../voteManagement/VoteManagement.context.tsx | 14 +- .../voteManagement/VoteManagement.types.ts | 2 + packages/client/src/mocks/polls.ts | 151 ------------------ packages/client/src/model/poll.model.ts | 1 + .../client/src/pages/DailyPoll/DailyPoll.tsx | 10 +- .../DailyPoll/components/ConfirmVote.tsx | 68 ++++---- .../src/pages/HistoricPoll/HistoricPoll.tsx | 6 +- .../pages/Landing/components/DailyPoll.tsx | 10 +- .../src/pages/Landing/components/Hero.tsx | 2 +- .../src/pages/Landing/components/PastPoll.tsx | 4 +- .../src/pages/PollResult/PollResult.tsx | 54 +++---- packages/client/src/utils/methods.ts | 18 ++- 18 files changed, 161 insertions(+), 264 deletions(-) delete mode 100644 packages/client/src/mocks/polls.ts diff --git a/packages/client/src/App.tsx b/packages/client/src/App.tsx index b6f6e3c..6fd6e77 100644 --- a/packages/client/src/App.tsx +++ b/packages/client/src/App.tsx @@ -32,7 +32,7 @@ const App: React.FC = () => { } /> } /> - } /> + } /> } /> } /> } /> diff --git a/packages/client/src/components/Cards/Card.tsx b/packages/client/src/components/Cards/Card.tsx index 9c048f4..f687b06 100644 --- a/packages/client/src/components/Cards/Card.tsx +++ b/packages/client/src/components/Cards/Card.tsx @@ -3,11 +3,12 @@ import React, { useEffect, useState } from 'react' interface CardProps { children: React.ReactNode isDetails?: boolean + isActive?: boolean checked?: boolean onChecked?: (clicked: boolean) => void } -const Card: React.FC = ({ children, isDetails, checked, onChecked }) => { +const Card: React.FC = ({ children, isActive, isDetails, checked, onChecked }) => { const [isClicked, setIsClicked] = useState(checked ?? false) useEffect(() => { @@ -20,6 +21,12 @@ const Card: React.FC = ({ children, isDetails, checked, onChecked }) setIsClicked(!isClicked) } + useEffect(() => { + if (isActive) { + setIsClicked(false) + } + }, [isActive]) + return (
= ({ roundId, pollOptions, totalVotes, date }) => { +const PollCard: React.FC = ({ roundId, options, totalVotes, date, endTime }) => { const navigate = useNavigate() - const [results, setResults] = useState(pollOptions) + const [results, setResults] = useState(options) + + const isActive = !hasPollEndedByTimestamp(endTime) useEffect(() => { - const newPollOptions = markWinner(pollOptions) + const newPollOptions = markWinner(options) setResults(newPollOptions) - }, [pollOptions]) + }, [options]) const handleNavigation = () => { - navigate(`/result/${roundId}`) + isActive ? navigate('/current') : navigate(`/result/${roundId}`) } return (
-
+
{formatDate(date)}
- +
- + {isActive &&

Active

}
- +
) diff --git a/packages/client/src/components/Cards/PollCardResult.tsx b/packages/client/src/components/Cards/PollCardResult.tsx index 1621961..ebc64c5 100644 --- a/packages/client/src/components/Cards/PollCardResult.tsx +++ b/packages/client/src/components/Cards/PollCardResult.tsx @@ -9,8 +9,9 @@ type PollCardResultProps = { height?: number width?: number isResult?: boolean + isActive?: boolean } -const PollCardResult: React.FC = ({ isResult, results, totalVotes }) => { +const PollCardResult: React.FC = ({ isResult, results, totalVotes, isActive }) => { const calculatePercentage = (votes: number) => { return ((votes / totalVotes) * 100).toFixed(0) } @@ -22,19 +23,22 @@ const PollCardResult: React.FC = ({ isResult, results, tota
- +

{poll.label}

-
-

- {totalVotes ? calculatePercentage(poll.votes) : 0}% -

-

- {poll.votes} votes -

-
+ + {!isActive && ( +
+

0 && poll.checked ? 'text-lime-400' : 'text-slate-600/50'}`} + > + {totalVotes ? calculatePercentage(poll.votes) : 0}% +

+

+ {poll.votes} votes +

+
+ )}
))} diff --git a/packages/client/src/components/NavMenu.tsx b/packages/client/src/components/NavMenu.tsx index e29a756..edbe2ff 100644 --- a/packages/client/src/components/NavMenu.tsx +++ b/packages/client/src/components/NavMenu.tsx @@ -10,9 +10,9 @@ interface NavMenuProps {} const NAV_MENU_OPTIONS = [ { - name: 'Daily Poll', + name: 'Current Poll', icon: , - path: '/daily', + path: '/current', }, { name: 'Historic Polls', diff --git a/packages/client/src/components/VotesBadge.tsx b/packages/client/src/components/VotesBadge.tsx index bbb424e..7bda636 100644 --- a/packages/client/src/components/VotesBadge.tsx +++ b/packages/client/src/components/VotesBadge.tsx @@ -2,11 +2,14 @@ import React from 'react' type VotesBadgeProps = { totalVotes: number + isActive?: boolean } -const VotesBadge: React.FC = ({ totalVotes }) => { +const VotesBadge: React.FC = ({ totalVotes, isActive }) => { return ( -
+
{totalVotes} votes
) diff --git a/packages/client/src/context/voteManagement/VoteManagement.context.tsx b/packages/client/src/context/voteManagement/VoteManagement.context.tsx index 2d1c26b..d161b65 100644 --- a/packages/client/src/context/voteManagement/VoteManagement.context.tsx +++ b/packages/client/src/context/voteManagement/VoteManagement.context.tsx @@ -25,6 +25,7 @@ const VoteManagementProvider = ({ children }: VoteManagementProviderProps) => { const [isLoading, setIsLoading] = useState(false) const [pollOptions, setPollOptions] = useState([]) const [pastPolls, setPastPolls] = useState([]) + const [txUrl, setTxUrl] = useState(undefined) /** * Voting Management Methods @@ -60,6 +61,13 @@ const VoteManagementProvider = ({ children }: VoteManagementProviderProps) => { const getRoundStateLite = async (roundCount: number) => { const roundState = await getRoundStateLiteRequest(roundCount) + + if (roundState?.pk.length === 1 && roundState.pk[0] === 0) { + handleGenericError('getRoundStateLite', { + message: 'Enclave server failed generating the necessary pk bytes', + name: 'getRoundStateLite', + }) + } if (roundState) { setRoundState(roundState) setVotingRound({ round_id: roundState.id, pk_bytes: roundState.pk }) @@ -71,8 +79,8 @@ const VoteManagementProvider = ({ children }: VoteManagementProviderProps) => { const getPastPolls = async (roundCount: number) => { let results: PollResult[] = [] try { - for (let i = 0; i < roundCount; i++) { - const result = await getWebResult(i + 1) + for (let i = roundCount; i > 0; i--) { + const result = await getWebResult(i) if (result) { const convertedPoll = convertPollData(result) results.push(convertedPoll) @@ -104,6 +112,8 @@ const VoteManagementProvider = ({ children }: VoteManagementProviderProps) => { pollOptions, roundState, pastPolls, + txUrl, + setTxUrl, existNewRound, getWebResult, setPastPolls, diff --git a/packages/client/src/context/voteManagement/VoteManagement.types.ts b/packages/client/src/context/voteManagement/VoteManagement.types.ts index ff98a91..c6290e9 100644 --- a/packages/client/src/context/voteManagement/VoteManagement.types.ts +++ b/packages/client/src/context/voteManagement/VoteManagement.types.ts @@ -14,6 +14,8 @@ export type VoteManagementContextType = { pollOptions: Poll[] roundState: VoteStateLite | null pastPolls: PollResult[] + txUrl: string | undefined + setTxUrl: React.Dispatch> setPollOptions: React.Dispatch> initialLoad: () => Promise existNewRound: () => Promise diff --git a/packages/client/src/mocks/polls.ts b/packages/client/src/mocks/polls.ts deleted file mode 100644 index 96175cb..0000000 --- a/packages/client/src/mocks/polls.ts +++ /dev/null @@ -1,151 +0,0 @@ -import { PollResult } from '../model/poll.model' - -export const PAST_POLLS: PollResult[] = [ - { - roundId: 1, - totalVotes: 451, - date: 'March 25, 2024', - options: [ - { value: 0, votes: 230, label: '🌮' }, - { value: 1, votes: 221, label: '🍕' }, - ], - }, - { - roundId: 2, - totalVotes: 100, - date: 'March 24, 2024', - options: [ - { value: 0, votes: 49, label: '🍔' }, - { value: 1, votes: 51, label: '🍜' }, - ], - }, - { - roundId: 3, - totalVotes: 200, - date: 'March 23, 2024', - options: [ - { value: 0, votes: 187, label: '🫓' }, - { value: 1, votes: 13, label: '🍣' }, - ], - }, -] - -export const EMOJI_LIST: string[] = [ - '🍇', - '🍈', - '🍉', - '🍊', - '🍋', - '🍌', - '🍍', - '🥭', - '🍎', - '🍏', - '🍐', - '🍑', - '🍒', - '🍓', - '🫐', - '🥝', - '🍅', - '🫒', - '🥥', - '🥑', - '🍆', - '🥔', - '🥕', - '🌽', - '🌶️', - '🫑', - '🥒', - '🥬', - '🥦', - '🧄', - '🧅', - '🍄', - '🥜', - '🫘', - '🌰', - '🍞', - '🥐', - '🥖', - '🫓', - '🥨', - '🥯', - '🥞', - '🧇', - '🧀', - '🍖', - '🍗', - '🥩', - '🥓', - '🍔', - '🍟', - '🍕', - '🌭', - '🥪', - '🌮', - '🌯', - '🫔', - '🥙', - '🧆', - '🥚', - '🍳', - '🥘', - '🍲', - '🫕', - '🥣', - '🥗', - '🍿', - '🧈', - '🧂', - '🥫', - '🍱', - '🍘', - '🍙', - '🍚', - '🍛', - '🍜', - '🍝', - '🍠', - '🍢', - '🍣', - '🍤', - '🍥', - '🥮', - '🍡', - '🥟', - '🥠', - '🥡', - '🦀', - '🦞', - '🦐', - '🦑', - '🦪', - '🍦', - '🍧', - '🍨', - '🍩', - '🍪', - '🎂', - '🍰', - '🧁', - '🥧', - '🍫', - '🍬', - '🍭', - '🍮', - '🍯', - '🍼', - '🥛', - '☕', - '🍵', - '🍾', - '🍷', - '🍸', - '🍹', - '🍺', - '🍻', - '🥂', - '🥃', -] diff --git a/packages/client/src/model/poll.model.ts b/packages/client/src/model/poll.model.ts index cae2180..596c94f 100644 --- a/packages/client/src/model/poll.model.ts +++ b/packages/client/src/model/poll.model.ts @@ -10,6 +10,7 @@ export interface PollResult { totalVotes: number date: string options: PollOption[] + endTime: number } export interface PollRequestResult { diff --git a/packages/client/src/pages/DailyPoll/DailyPoll.tsx b/packages/client/src/pages/DailyPoll/DailyPoll.tsx index 45ec5fc..10d22db 100644 --- a/packages/client/src/pages/DailyPoll/DailyPoll.tsx +++ b/packages/client/src/pages/DailyPoll/DailyPoll.tsx @@ -4,13 +4,15 @@ import { Poll } from '@/model/poll.model' import { useVoteManagementContext } from '@/context/voteManagement' import { useNotificationAlertContext } from '@/context/NotificationAlert' import { useNavigate } from 'react-router-dom' +import { convertTimestampToDate } from '@/utils/methods' const DailyPoll: React.FC = () => { const navigate = useNavigate() const { showToast } = useNotificationAlertContext() - const { encryptVote, broadcastVote, getRoundStateLite, existNewRound, votingRound, roundState } = useVoteManagementContext() + const { encryptVote, broadcastVote, getRoundStateLite, existNewRound, setTxUrl, votingRound, roundState } = useVoteManagementContext() const [loading, setLoading] = useState(false) const [newRoundLoading, setNewRoundLoading] = useState(false) + const endTime = roundState && convertTimestampToDate(roundState?.start_time, roundState?.poll_length) useEffect(() => { const checkRound = async () => { @@ -38,10 +40,12 @@ const DailyPoll: React.FC = () => { }) await getRoundStateLite(votingRound.round_id) if (broadcastVoteResponse) { + const url = `https://sepolia.etherscan.io/tx/${broadcastVoteResponse?.tx_hash}` + setTxUrl(url) showToast({ type: 'success', message: 'Successfully voted', - linkUrl: `https://sepolia.etherscan.io/tx/${broadcastVoteResponse?.tx_hash}`, + linkUrl: url, }) navigate(`/result/${votingRound.round_id}/confirmation`) return @@ -53,7 +57,7 @@ const DailyPoll: React.FC = () => { } return ( - + ) } diff --git a/packages/client/src/pages/DailyPoll/components/ConfirmVote.tsx b/packages/client/src/pages/DailyPoll/components/ConfirmVote.tsx index 1c49fde..9e6b7c4 100644 --- a/packages/client/src/pages/DailyPoll/components/ConfirmVote.tsx +++ b/packages/client/src/pages/DailyPoll/components/ConfirmVote.tsx @@ -1,44 +1,42 @@ -import React from 'react' -import CountdownTimer from '@/components/CountdownTime' +import React, { useEffect } from 'react' import CardContent from '@/components/Cards/CardContent' +import { useVoteManagementContext } from '@/context/voteManagement' + +const ConfirmVote: React.FC<{ confirmationUrl: string }> = ({ confirmationUrl }) => { + const { setTxUrl } = useVoteManagementContext() + + useEffect(() => { + return () => { + setTxUrl(undefined) + } + }, []) -type ConfirmVoteProps = { - endTime: Date -} -const ConfirmVote: React.FC = ({ endTime }) => { return ( -
-
-

daily poll

-

Thanks for voting!

-
-
- - {/* */} -
- -
-

WHAT JUST HAPPENED?

-
-

- Your vote was encrypted and posted onchain by a relayer. When the poll is over, the results will be tallied using - Fully Homomorphic Encryption (FHE) and the results decrypted using threshold cryptography, without revealing your - identity or choice. -

-
-
-
-

WHAT DOES THIS MEAN?

+ +
+

WHAT JUST HAPPENED?

+

- Your participation has directly contributed to a transparent and fair decision-making process, showcasing the power of - privacy-preserving technology in governance and beyond. The use of CRISP in this vote represents a significant step towards - secure, anonymous, and tamper-proof digital elections and polls. This innovation ensures that every vote counts equally while - safeguarding against the risks of fraud and collusion, enhancing the reliability and trustworthiness of digital decision-making - platforms. + Your vote was encrypted and{' '} + + posted onchain + {' '} + by a relayer. When the poll is over, the results will be tallied using Fully Homomorphic Encryption (FHE) and the results + decrypted using threshold cryptography, without revealing your identity or choice.

- -
+
+
+

WHAT DOES THIS MEAN?

+

+ Your participation has directly contributed to a transparent and fair decision-making process, showcasing the power of + privacy-preserving technology in governance and beyond. The use of CRISP in this vote represents a significant step towards + secure, anonymous, and tamper-proof digital elections and polls. This innovation ensures that every vote counts equally while + safeguarding against the risks of fraud and collusion, enhancing the reliability and trustworthiness of digital decision-making + platforms. +

+
+
) } diff --git a/packages/client/src/pages/HistoricPoll/HistoricPoll.tsx b/packages/client/src/pages/HistoricPoll/HistoricPoll.tsx index 827ccb6..f53615e 100644 --- a/packages/client/src/pages/HistoricPoll/HistoricPoll.tsx +++ b/packages/client/src/pages/HistoricPoll/HistoricPoll.tsx @@ -30,9 +30,9 @@ const HistoricPoll: React.FC = () => {
)}
- {pastPolls.map(({ totalVotes, options, roundId, date }: PollResult) => ( -
- + {pastPolls.map((pollResult: PollResult) => ( +
+
))}
diff --git a/packages/client/src/pages/Landing/components/DailyPoll.tsx b/packages/client/src/pages/Landing/components/DailyPoll.tsx index bbd413d..26c2055 100644 --- a/packages/client/src/pages/Landing/components/DailyPoll.tsx +++ b/packages/client/src/pages/Landing/components/DailyPoll.tsx @@ -7,13 +7,15 @@ import RegisterModal from '@/pages/Register/Register' import { useVoteManagementContext } from '@/context/voteManagement' import LoadingAnimation from '@/components/LoadingAnimation' import { hasPollEnded } from '@/utils/methods' +import CountdownTimer from '@/components/CountdownTime' type DailyPollSectionProps = { onVoted?: (vote: Poll) => void loading?: boolean + endTime: Date | null } -const DailyPollSection: React.FC = ({ onVoted, loading }) => { +const DailyPollSection: React.FC = ({ onVoted, loading, endTime }) => { const { user, pollOptions, setPollOptions, roundState } = useVoteManagementContext() const isEnded = roundState ? hasPollEnded(roundState?.poll_length, roundState?.start_time) : false const status = roundState?.status @@ -70,6 +72,12 @@ const DailyPollSection: React.FC = ({ onVoted, loading })
)} + + {endTime && !isEnded && ( +
+ +
+ )} {loading && }
{pollOptions.map((poll) => ( diff --git a/packages/client/src/pages/Landing/components/Hero.tsx b/packages/client/src/pages/Landing/components/Hero.tsx index e339904..9a016da 100644 --- a/packages/client/src/pages/Landing/components/Hero.tsx +++ b/packages/client/src/pages/Landing/components/Hero.tsx @@ -51,7 +51,7 @@ const HeroSection: React.FC = () => {
Learn more.
- +
diff --git a/packages/client/src/pages/Landing/components/PastPoll.tsx b/packages/client/src/pages/Landing/components/PastPoll.tsx index 4273666..ada3747 100644 --- a/packages/client/src/pages/Landing/components/PastPoll.tsx +++ b/packages/client/src/pages/Landing/components/PastPoll.tsx @@ -14,8 +14,8 @@ const PastPollSection: React.FC = ({ customLabel = 'Past p

{customLabel}

- {pastPolls.map(({ totalVotes, options, roundId, date }: PollResult) => ( - + {pastPolls.map((poll: PollResult) => ( + ))}
diff --git a/packages/client/src/pages/PollResult/PollResult.tsx b/packages/client/src/pages/PollResult/PollResult.tsx index ea44005..d3ebde0 100644 --- a/packages/client/src/pages/PollResult/PollResult.tsx +++ b/packages/client/src/pages/PollResult/PollResult.tsx @@ -10,6 +10,7 @@ import { PollResult as PollResultType } from '@/model/poll.model' import { useVoteManagementContext } from '@/context/voteManagement' import CircularTiles from '@/components/CircularTiles' import CountdownTimer from '@/components/CountdownTime' +import ConfirmVote from '../DailyPoll/components/ConfirmVote' const PollResult: React.FC = () => { const params = useParams() @@ -17,7 +18,8 @@ const PollResult: React.FC = () => { const { pastPolls, getWebResult } = useVoteManagementContext() const [loading, setLoading] = useState(true) const [poll, setPoll] = useState(null) - const { roundEndDate } = useVoteManagementContext() + const { roundEndDate, txUrl } = useVoteManagementContext() + useEffect(() => { if (pastPolls.length && roundId) { const currentPoll = pastPolls.find((poll) => poll.roundId === parseInt(roundId)) @@ -74,35 +76,33 @@ const PollResult: React.FC = () => {
- -
-

WHAT JUST HAPPENED?

-
+ {type === 'confirmation' && txUrl && } + {type !== 'confirmation' && ( + +
+

WHAT JUST HAPPENED?

+
+

+ After casting your vote, CRISP securely processed your selection using a blend of Fully Homomorphic Encryption (FHE), + threshold cryptography, and zero-knowledge proofs (ZKPs), without revealing your identity or choice. Your vote was + encrypted and anonymously aggregated with others, ensuring the integrity of the voting process while strictly + maintaining confidentiality. The protocol's advanced cryptographic techniques guarantee that your vote contributes to + the final outcome without any risk of privacy breaches or undue influence. +

+
+
+
+

WHAT DOES THIS MEAN?

- After casting your vote, CRISP securely processed your selection using a blend of Fully Homomorphic Encryption (FHE), - threshold cryptography, and zero-knowledge proofs (ZKPs), without revealing your identity or choice. Your vote was - encrypted and anonymously aggregated with others, ensuring the integrity of the voting process while strictly - maintaining confidentiality. The protocol's advanced cryptographic techniques guarantee that your vote contributes to - the final outcome without any risk of privacy breaches or undue influence. + Your participation has directly contributed to a transparent and fair decision-making process, showcasing the power of + privacy-preserving technology in governance and beyond. The use of CRISP in this vote represents a significant step + towards secure, anonymous, and tamper-proof digital elections and polls. This innovation ensures that every vote counts + equally while safeguarding against the risks of fraud and collusion, enhancing the reliability and trustworthiness of + digital decision-making platforms.

- {/*
setShowCode(!showCode)}> -

See what's happening under the hood

- -
- {showCode && } */}
-
-
-

WHAT DOES THIS MEAN?

-

- Your participation has directly contributed to a transparent and fair decision-making process, showcasing the power of - privacy-preserving technology in governance and beyond. The use of CRISP in this vote represents a significant step - towards secure, anonymous, and tamper-proof digital elections and polls. This innovation ensures that every vote counts - equally while safeguarding against the risks of fraud and collusion, enhancing the reliability and trustworthiness of - digital decision-making platforms. -

-
- + + )} {pastPolls.length > 0 && (
diff --git a/packages/client/src/utils/methods.ts b/packages/client/src/utils/methods.ts index 63efe2f..d445f2e 100644 --- a/packages/client/src/utils/methods.ts +++ b/packages/client/src/utils/methods.ts @@ -13,22 +13,35 @@ export const convertTimestampToDate = (timestamp: number, secondsToAdd: number = date.setSeconds(date.getMinutes() + secondsToAdd) return date } + export const hasPollEnded = (pollLength: number, startTime: number): boolean => { const endTime = (startTime + pollLength) * 1000 const currentTime = Date.now() return currentTime >= endTime } +export const hasPollEndedByTimestamp = (endTime: number): boolean => { + const endTimeMillis = endTime * 1000 + const currentTime = Date.now() + return currentTime >= endTimeMillis +} + export const formatDate = (isoDateString: string): string => { const date = new Date(isoDateString) - const formatter = new Intl.DateTimeFormat('en-US', { + const dateFormatter = new Intl.DateTimeFormat('en-US', { year: 'numeric', month: 'long', day: 'numeric', }) - return formatter.format(date) + const timeFormatter = new Intl.DateTimeFormat('en-US', { + hour: 'numeric', + minute: 'numeric', + hour12: true, + }) + + return `${dateFormatter.format(date)} - ${timeFormatter.format(date)}` } export const fixPollResult = (poll: PollRequestResult): PollRequestResult => { @@ -58,6 +71,7 @@ export const convertPollData = (request: PollRequestResult): PollResult => { const date = new Date(request.end_time * 1000).toISOString() return { + endTime: request.end_time, roundId: request.round_id, totalVotes: totalVotes, date: date,