There are no historic polls.
}
+ {!pastPolls.length && !isLoading && There are no polls yet.
}
{visiblePolls.length > 0 && (
{visiblePolls.map((pollResult: PollResult, index: number) => {
@@ -104,4 +104,4 @@ const HistoricPoll: React.FC = () => {
)
}
-export default HistoricPoll
+export default AllPolls
diff --git a/examples/CRISP/client/src/pages/Landing/components/DailyPoll.tsx b/examples/CRISP/client/src/pages/Landing/components/DailyPoll.tsx
index 619b498c4..bcf04af2d 100644
--- a/examples/CRISP/client/src/pages/Landing/components/DailyPoll.tsx
+++ b/examples/CRISP/client/src/pages/Landing/components/DailyPoll.tsx
@@ -46,8 +46,17 @@ const FaceoffSlot: React.FC<{
}
const DailyPollSection: React.FC
= ({ loading, endTime, title = 'The Faceoff' }) => {
- const { user, pollOptions, setPollOptions, roundState, hasVotedInCurrentRound, voteStatusLoading, isLoading, getWebResultByRound } =
- useVoteManagementContext()
+ const {
+ user,
+ pollOptions,
+ setPollOptions,
+ roundState,
+ hasVotedInCurrentRound,
+ voteStatusLoading,
+ isLoading,
+ getWebResultByRound,
+ displayedRoundIsFallback,
+ } = useVoteManagementContext()
const navigate = useNavigate()
const client = usePublicClient()
const [isEnded, setIsEnded] = useState(false)
@@ -90,29 +99,56 @@ const DailyPollSection: React.FC = ({ loading, endTime, t
}
}, [roundState, client])
+ // Keep a stable reference so the polling effect below isn't torn down
+ // and rebuilt every time the parent context re-renders (which otherwise
+ // produced an eager `check()` call per rebuild and stacked requests).
+ const getWebResultByRoundRef = useRef(getWebResultByRound)
+ useEffect(() => {
+ getWebResultByRoundRef.current = getWebResultByRound
+ }, [getWebResultByRound])
+
// Once the poll is over, poll the backend until the FHE tally is published.
+ // FHE decryption takes minutes, so poll slowly with exponential backoff and
+ // skip ticks while the tab is hidden to avoid bombarding the server.
useEffect(() => {
if (!isEnded || !roundState || tallyReady) return
+ const BASE_DELAY_MS = 30_000
+ const MAX_DELAY_MS = 5 * 60_000
+
let cancelled = false
- const check = async () => {
+ let timer: ReturnType | null = null
+ let delay = BASE_DELAY_MS
+
+ const tick = async () => {
+ if (cancelled) return
+
+ if (typeof document !== 'undefined' && document.hidden) {
+ timer = setTimeout(tick, BASE_DELAY_MS)
+ return
+ }
+
try {
- const result = await getWebResultByRound(roundState.id)
- if (!cancelled && result && Array.isArray(result.tally) && result.tally.length > 0) {
+ const result = await getWebResultByRoundRef.current(roundState.id)
+ if (cancelled) return
+ if (result && Array.isArray(result.tally) && result.tally.length > 0) {
setTallyReady(true)
+ return
}
+ delay = Math.min(delay * 2, MAX_DELAY_MS)
} catch {
- // Transient failure — keep polling on the next interval tick.
+ delay = Math.min(delay * 2, MAX_DELAY_MS)
}
+
+ timer = setTimeout(tick, delay)
}
- check()
- const interval = setInterval(check, 8000)
+ timer = setTimeout(tick, BASE_DELAY_MS)
return () => {
cancelled = true
- clearInterval(interval)
+ if (timer) clearTimeout(timer)
}
- }, [isEnded, roundState, tallyReady, getWebResultByRound])
+ }, [isEnded, roundState, tallyReady])
const handleChecked = (selectedPoll: Poll) => {
const isAlreadySelected = pollSelected?.value === selectedPoll.value
@@ -158,6 +194,9 @@ const DailyPollSection: React.FC = ({ loading, endTime, t
{title}
{hasPoll && Choose your favorite
}
{!roundState && !isLoading && No active poll found. Check back when the next round opens.
}
+ {displayedRoundIsFallback && (
+ Showing the latest completed poll — the current round is still being tallied under encryption.
+ )}
{roundState && (
diff --git a/examples/CRISP/client/src/pages/Landing/components/PastPoll.tsx b/examples/CRISP/client/src/pages/Landing/components/PastPoll.tsx
index aac5a5676..d3d311538 100644
--- a/examples/CRISP/client/src/pages/Landing/components/PastPoll.tsx
+++ b/examples/CRISP/client/src/pages/Landing/components/PastPoll.tsx
@@ -30,7 +30,7 @@ const PastPollSection: React.FC