Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Feat: Enhance Responsiveness and UI Components for Multi-Device Support #10

Merged
merged 2 commits into from
May 15, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ const App: React.FC = () => {
<Route path='/about' element={<About />} />
<Route path='/daily' element={<DailyPoll />} />
<Route path='/historic' element={<HistoricPoll />} />
<Route path='/result/:roundId' element={<PollResult />} />
<Route path='/result/:roundId/:type?' element={<PollResult />} />
<Route path='*' element={<Navigate to='/' replace />} />
</Routes>
</div>
Expand Down
2 changes: 1 addition & 1 deletion packages/client/src/components/Cards/CardContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ interface CardContentProps {

const CardContent: React.FC<CardContentProps> = ({ children }) => {
return (
<div className='bg-white w-full max-w-screen-md space-y-10 rounded-2xl border-2 border-slate-600/20 p-8 md:p-12 shadow-2xl'>
<div className='z-50 w-full max-w-screen-md space-y-10 rounded-2xl border-2 border-slate-600/20 bg-white p-8 shadow-2xl md:p-12'>
{children}
</div>
)
Expand Down
10 changes: 6 additions & 4 deletions packages/client/src/components/Cards/PollCardResult.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,16 @@ const PollCardResult: React.FC<PollCardResultProps> = ({ isResult, results, tota
}

return (
<div className={`grid ${isResult ? 'sm:w-full md:w-1/3' : 'w-full'} grid-cols-2 gap-4 md:gap-8`}>
<div className={`grid ${isResult ? 'sm:w-full md:w-1/3' : 'w-full'} z-50 grid-cols-2 gap-4 md:gap-8`}>
{results.map((poll) => (
<div className='col-span-1 w-full' key={`${poll.label}-${poll.value}`}>
<div className={`flex w-full flex-col items-center justify-center ${isResult ? 'aspect-square space-y-6' : 'space-y-4'}`}>
<div
className={`flex w-full flex-col items-center justify-center ${isResult ? 'aspect-square space-y-6 max-sm:space-y-2' : 'space-y-4'}`}
>
<Card isDetails checked={poll.checked}>
<p className={isResult ? 'text-8xl' : 'text-5xl'}>{poll.label}</p>
<p className={isResult ? 'text-8xl max-sm:p-5 max-sm:text-6xl' : 'text-5xl'}>{poll.label}</p>
</Card>
<div className={isResult ? 'space-y-2' : ''}>
<div className={isResult ? 'space-y-2 max-sm:space-y-0' : ''}>
<h3
className={`text-center ${isResult ? 'text-h1' : 'text-h3'} font-bold ${poll.checked ? 'text-lime-400' : 'text-slate-600/50'}`}
>
Expand Down
14 changes: 9 additions & 5 deletions packages/client/src/components/CircularTile.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import { useMediaQuery } from '@/hooks/generic/useMediaQuery'

interface CircularTileProps {
className?: string;
rotation?: number;
className?: string
rotation?: number
}

const CircularTile = ({className, rotation}: CircularTileProps) => {
const CircularTile = ({ className, rotation }: CircularTileProps) => {
const isMediumOrLarger = useMediaQuery('(min-width: 768px)')
const viewBox = isMediumOrLarger ? '0 0 256 256' : '-80 -80 350 350'
return (
<svg
className={`fill-slate-100 ${className}`}
style={{ transform: `rotate(${rotation}deg)` }}
viewBox='0 0 256 256'
viewBox={viewBox}
xmlns='http://www.w3.org/2000/svg'
>
<path
Expand All @@ -20,4 +24,4 @@ const CircularTile = ({className, rotation}: CircularTileProps) => {
)
}

export default CircularTile;
export default CircularTile
3 changes: 2 additions & 1 deletion packages/client/src/components/CountdownTime.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ const CountdownTimer: React.FC<CountdownTimerProps> = ({ endTime }) => {
return (
<div className='flex flex-col items-center justify-center space-y-2'>
<p className='text-base font-bold uppercase text-slate-600/50'>Poll ends in:</p>
{loading && <LoadingAnimation isLoading={loading} />}

{loading && <LoadingAnimation isLoading={true} />}
{!loading && remainingTime && (
<div className='flex space-x-6'>
<p className='text-2xl font-bold text-slate-600'>
Expand Down
20 changes: 13 additions & 7 deletions packages/client/src/components/LoadingAnimation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ const LoadingAnimation = ({ className, isLoading }: { className?: string; isLoad
const [rotations, setRotations] = useState([0, 0, 0, 0])

// Determine if the screen width is medium or larger
const isMediumOrLarger = useMediaQuery('(min-width: 768px)')
const isMobile = useMediaQuery('(max-width: 767px)')
const isTablet = useMediaQuery(
'(min-width: 768px) and (max-device-width: 1180px), (min-device-width: 768px) and (max-device-width: 1180px)',
)
const isDesktop = useMediaQuery('(min-width: 1181px)')

const getRandRotation = () => {
const rand_index = Math.floor(Math.random() * 4)
Expand All @@ -29,13 +33,15 @@ const LoadingAnimation = ({ className, isLoading }: { className?: string; isLoad
}, [rotations, isLoading])

// Adjust size based on screen width
const sizeClasses = isMediumOrLarger ? 'w-10 h-10' : 'w-6 h-6'

const sizeClasses = isDesktop ? 'w-10 h-10' : isTablet ? 'w-5 h-5' : 'w-7 h-7'
const gapClasses = isDesktop ? 'gap-1' : isTablet ? 'gap-5 mr-4 mb-4' : 'gap-4 mr-4'
return (
<div className={`grid grid-cols-2 gap-6 md:gap-1 ${sizeClasses} ${className}`}>
{rotations.map((rotation, i) => {
return <CircularTile key={i} className='!fill-slate-600 duration-500 ease-in-out' rotation={rotation} />
})}
<div className={`flex h-full items-center justify-center ${className}`}>
<div className={`grid grid-cols-2 ${gapClasses} ${sizeClasses}`}>
{rotations.map((rotation, i) => {
return <CircularTile key={i} className='!fill-slate-600 duration-500 ease-in-out' rotation={rotation} />
})}
</div>
</div>
)
}
Expand Down
10 changes: 10 additions & 0 deletions packages/client/src/components/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React from 'react'
import Logo from '@/assets/icons/logo.svg'
import { Link } from 'react-router-dom'
import NavMenu from '@/components/NavMenu'
import { useVoteManagementContext } from '@/context/voteManagement'

const PAGES = [
{
Expand All @@ -15,6 +16,7 @@ const PAGES = [
]

const Navbar: React.FC = () => {
const { user } = useVoteManagementContext()
return (
<nav className='absolute left-0 top-0 z-10 w-screen px-6 lg:px-9'>
<div className='mx-auto max-w-screen-xl'>
Expand All @@ -36,6 +38,14 @@ const Navbar: React.FC = () => {
{label}
</Link>
))}
{!user && (
<Link
to={PAGES[1].path}
className='hover:text-twilight-blue-600 cursor-pointer font-bold text-slate-600 duration-300 ease-in-out hover:opacity-70 md:hidden'
>
{PAGES[1].label}
</Link>
)}
<NavMenu />
</div>
</div>
Expand Down
16 changes: 12 additions & 4 deletions packages/client/src/hooks/twitter/useTwitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ import { useApi } from '@/hooks/generic/useFetchApi'
const TWITTER_API = import.meta.env.VITE_TWITTER_SERVERLESS_API

if (!TWITTER_API) handleGenericError('useTwitter', { name: 'TWITTER_API', message: 'Missing env VITE_TWITTER_SERVERLESS_API' })
const MSG =
'I am authenticating this Twitter account to cast my first encrypted vote with CRISP!\n\nVisit https://t.co/19mpilrPXR to vote.\n\n#FHE #ZKP #CRISP'

const MSG_REGEX =
/I am authenticating this Twitter account to cast my first encrypted vote with CRISP!\s*\n\nVisit https:\/\/t.co\/[^\s]+ to vote.\s*\n\n#FHE #ZKP #CRISP/i

export const useTwitter = () => {
const url = `${TWITTER_API}/twitter-data`
const { fetchData, isLoading } = useApi()
Expand All @@ -21,17 +23,23 @@ export const useTwitter = () => {
return match ? match[1] : null
}

const extractPostId = (url: string): string | null => {
const regex = /\/status\/(\d+)$/
const match = url.match(regex)
return match ? match[1] : null
}

const handleTwitterPostVerification = async (postUrl: string) => {
const username = extractUsernameFromUrl(postUrl)
const result = await verifyPost(postUrl)
if (result) {
const descriptionLowerCase = result.description.toLowerCase()
const authMsgLowerCase = MSG.toLowerCase()
if (descriptionLowerCase.includes(authMsgLowerCase)) {
if (MSG_REGEX.test(descriptionLowerCase)) {
const user = {
validationDate: new Date(),
avatar: result.open_graph.images[0].url ?? '',
username: username ?? '',
postId: extractPostId(result.open_graph.url) ?? '',
}
setUser(user)
setSocialAuth(user)
Expand Down
1 change: 1 addition & 0 deletions packages/client/src/model/twitter.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,5 @@ export interface SocialAuth {
validationDate: Date
avatar: string
username: string
postId: string
}
21 changes: 10 additions & 11 deletions packages/client/src/pages/About/About.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import React from 'react'
// import CircleIcon from '@/assets/icons/caretCircle.svg'
import CardContent from '@/components/Cards/CardContent'
import CircularTiles from '@/components/CircularTiles'
import { Link } from 'react-router-dom'

const About: React.FC = () => {
return (
Expand All @@ -17,9 +16,9 @@ const About: React.FC = () => {
<p className='text-base font-extrabold uppercase text-slate-600/50'>what is crisp?</p>
<div className='space-y-2'>
<p className='leading-8 text-slate-600'>
CRISP (Collusion-Resistant Impartial Selection Protocol) is a secure protocol for digital decision-making,
leveraging fully homomorphic encryption (FHE) and threshold cryptography to enable verifiable secret ballots;
a critical component for democracies and many other decision-making applications.
CRISP (Collusion-Resistant Impartial Selection Protocol) is a secure protocol for digital decision-making, leveraging fully
homomorphic encryption (FHE) and threshold cryptography to enable verifiable secret ballots; a critical component for
democracies and many other decision-making applications.
</p>
{/* <div className='flex cursor-pointer items-center space-x-2'>
<p className='text-lime-400 underline'>See what&apos;s happening under the hood</p>
Expand All @@ -30,9 +29,9 @@ const About: React.FC = () => {
<div className='space-y-4'>
<p className='text-base font-extrabold uppercase text-slate-600/50'>why is this important?</p>
<p className='leading-8 text-slate-600'>
Open ballots are known to produce suboptimal outcomes due to bribery and various forms of collusion.
CRISP mitigates collusion and other vulnerabilities by ensuring ballots are secret and receipt-free,
enabling a secure and impartial decision-making environment.
Open ballots are known to produce suboptimal outcomes due to bribery and various forms of collusion. CRISP mitigates collusion
and other vulnerabilities by ensuring ballots are secret and receipt-free, enabling a secure and impartial decision-making
environment.
</p>
{/* <div className='flex cursor-pointer items-center space-x-2'>
<p className='text-lime-400 underline'>See what&apos;s happening under the hood</p>
Expand All @@ -42,10 +41,10 @@ const About: React.FC = () => {
<div className='space-y-4'>
<p className='text-base font-extrabold uppercase text-slate-600/50'>Proof of Concept</p>
<p className='leading-8 text-slate-600'>
This application is a Proof of Concept (PoC), demonstrating the viability of Enclave as a network and CRISP
as an application for secret ballots. For the sake of getting a demonstration of CRISP into the wild, this
PoC application is not yet leveraging Enclave and omits several key components of CRISP. Future iterations
of this and other applications will be progressively more complete.
This application is a Proof of Concept (PoC), demonstrating the viability of Enclave as a network and CRISP as an application
for secret ballots. For the sake of getting a demonstration of CRISP into the wild, this PoC application is not yet leveraging
Enclave and omits several key components of CRISP. Future iterations of this and other applications will be progressively more
complete.
</p>
{/* <div className='flex cursor-pointer items-center space-x-2'>
<p className='text-lime-400 underline'>See what&apos;s happening under the hood</p>
Expand Down
12 changes: 5 additions & 7 deletions packages/client/src/pages/DailyPoll/DailyPoll.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import React, { Fragment, useEffect, useState } from 'react'
import DailyPollSection from '@/pages/Landing/components/DailyPoll'
import ConfirmVote from '@/pages/DailyPoll/components/ConfirmVote'
import { Poll } from '@/model/poll.model'
import { useVoteManagementContext } from '@/context/voteManagement'
import { useNotificationAlertContext } from '@/context/NotificationAlert'
import { useNavigate } from 'react-router-dom'

const DailyPoll: React.FC = () => {
const navigate = useNavigate()
const { showToast } = useNotificationAlertContext()
const { encryptVote, broadcastVote, getRoundStateLite, existNewRound, votingRound, roundEndDate, roundState } = useVoteManagementContext()
const [voteCompleted, setVotedCompleted] = useState<boolean>(false)
const { encryptVote, broadcastVote, getRoundStateLite, existNewRound, votingRound, roundState } = useVoteManagementContext()
const [loading, setLoading] = useState<boolean>(false)
const [newRoundLoading, setNewRoundLoading] = useState<boolean>(false)

Expand All @@ -26,7 +26,6 @@ const DailyPoll: React.FC = () => {
}
}, [roundState])

console.log('newRoundLoading', newRoundLoading)
const handleVoted = async (vote: Poll | null) => {
if (vote && votingRound) {
setLoading(true)
Expand All @@ -44,7 +43,7 @@ const DailyPoll: React.FC = () => {
message: 'Successfully voted',
linkUrl: `https://sepolia.etherscan.io/tx/${broadcastVoteResponse?.tx_hash}`,
})
setVotedCompleted(true)
navigate(`/result/${votingRound.round_id}/confirmation`)
return
}
showToast({ type: 'danger', message: 'Error broadcasting the vote' })
Expand All @@ -54,8 +53,7 @@ const DailyPoll: React.FC = () => {
}
return (
<Fragment>
{!voteCompleted && <DailyPollSection onVoted={handleVoted} loading={loading || newRoundLoading} />}
{voteCompleted && roundEndDate && <ConfirmVote endTime={roundEndDate} />}
<DailyPollSection onVoted={handleVoted} loading={loading || newRoundLoading} />
</Fragment>
)
}
Expand Down
4 changes: 2 additions & 2 deletions packages/client/src/pages/Landing/components/DailyPoll.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ const DailyPollSection: React.FC<DailyPollSectionProps> = ({ onVoted, loading })
</div>
)}
{loading && <LoadingAnimation isLoading={loading} />}
<div className='grid w-full grid-cols-2 gap-4 md:gap-8'>
<div className=' grid w-full grid-cols-2 gap-4 md:gap-8'>
{pollOptions.map((poll) => (
<div key={poll.label} className='col-span-2 md:col-span-1'>
<Card checked={poll.checked} onChecked={() => handleChecked(poll.value)}>
Expand All @@ -81,7 +81,7 @@ const DailyPollSection: React.FC<DailyPollSectionProps> = ({ onVoted, loading })
))}
</div>
<div className='space-y-4'>
{noPollSelected && <div className='text-center text-sm leading-none text-slate-500'>Select your favorite</div>}
{noPollSelected && !isEnded && <div className='text-center text-sm leading-none text-slate-500'>Select your favorite</div>}
<button
className={`button-outlined button-max ${noPollSelected ? 'button-disabled' : ''}`}
disabled={noPollSelected || loading || status !== 'Active' || isEnded}
Expand Down
5 changes: 3 additions & 2 deletions packages/client/src/pages/Landing/components/PastPoll.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ import { Link } from 'react-router-dom'

type PastPollSectionProps = {
customLabel?: string
useFullHeight?: boolean
}
const PastPollSection: React.FC<PastPollSectionProps> = ({ customLabel = 'Past polls' }) => {
const PastPollSection: React.FC<PastPollSectionProps> = ({ customLabel = 'Past polls', useFullHeight = true }) => {
const { pastPolls } = useVoteManagementContext()
return (
<div className={`flex min-h-screen w-screen flex-col items-center justify-center space-y-12 px-6 py-32`}>
<div className={`flex ${useFullHeight ? 'min-h-screen' : ''} w-screen flex-col items-center justify-center space-y-12 px-6 py-32`}>
<h1 className='text-h1 font-bold text-slate-600'>{customLabel}</h1>
<div className='flex w-full flex-wrap justify-center gap-16 md:gap-8'>
{pastPolls.map(({ totalVotes, options, roundId, date }: PollResult) => (
Expand Down
Loading