diff --git a/components/brain/BrainMobile.tsx b/components/brain/BrainMobile.tsx index 185b98bd3b..fa0a6276f3 100644 --- a/components/brain/BrainMobile.tsx +++ b/components/brain/BrainMobile.tsx @@ -1,7 +1,7 @@ "use client"; import type { ReactNode } from "react"; -import React, { useCallback, useEffect, useMemo, useState } from "react"; +import React, { useCallback, useMemo, useSyncExternalStore } from "react"; import { motion, AnimatePresence } from "framer-motion"; import BrainMobileTabs from "./mobile/BrainMobileTabs"; import { useRouter, useSearchParams, usePathname } from "next/navigation"; @@ -44,20 +44,14 @@ const BrainMobile: React.FC = ({ children }) => { const pathname = usePathname(); const { isApp } = useDeviceInfo(); const { connectedProfile } = useAuth(); - const { - closeQuickVote, - isQuickVoteOpen, - openQuickVote, - prefetchQuickVote, - quickVoteSessionId, - } = useMemesQuickVoteDialogController(); - const [hydrated, setHydrated] = useState(false); + const quickVote = useMemesQuickVoteDialogController(); + const hydrated = useSyncExternalStore( + () => () => {}, + () => true, + () => false + ); const myStream = useMyStreamOptional(); - useEffect(() => { - setHydrated(true); - }, []); - const dropId = searchParams.get("drop") ?? undefined; const { effectiveDropId, beginClosingDrop } = useClosingDropId(dropId); const { data: drop } = useQuery({ @@ -115,9 +109,9 @@ const BrainMobile: React.FC = ({ children }) => { waveId, }); - const onDropClick = (drop: ExtendedDrop) => { + const onDropClick = (selectedDrop: ExtendedDrop) => { const params = new URLSearchParams(searchParams.toString() || ""); - params.set("drop", drop.id); + params.set("drop", selectedDrop.id); router.push(`${pathname}?${params.toString()}`, { scroll: false }); }; @@ -144,7 +138,7 @@ const BrainMobile: React.FC = ({ children }) => { const closeCreateOverlay = useCallback(() => { const params = new URLSearchParams(searchParams.toString() || ""); params.delete("create"); - const base = pathname ?? "/"; + const base = pathname; const next = params.toString() ? `${base}?${params.toString()}` : base; router.replace(next, { scroll: false }); }, [router, pathname, searchParams]); @@ -186,7 +180,7 @@ const BrainMobile: React.FC = ({ children }) => { !isDropOpen && !isDm; const shouldMountQuickVoteDialog = - isQuickVoteOpen || + quickVote.isQuickVoteOpen || shouldMountFloatingQuickVoteEntry || activeView === BrainView.WAVES; @@ -232,8 +226,8 @@ const BrainMobile: React.FC = ({ children }) => { > {shouldMountFloatingQuickVoteEntry && ( )} = ({ children }) => { isMemesWave={isMemesWave} isRankWave={isRankWave} onDropClick={onDropClick} - onOpenQuickVote={openQuickVote} - onPrefetchQuickVote={prefetchQuickVote} + onOpenQuickVote={quickVote.openQuickVote} + onPrefetchQuickVote={quickVote.prefetchQuickVote} wave={wave} > {children} @@ -252,11 +246,7 @@ const BrainMobile: React.FC = ({ children }) => { {shouldMountQuickVoteDialog && ( - + )} ); diff --git a/components/brain/left-sidebar/waves/MemesWaveFooter.tsx b/components/brain/left-sidebar/waves/MemesWaveFooter.tsx index 1abee6e072..885758ec10 100644 --- a/components/brain/left-sidebar/waves/MemesWaveFooter.tsx +++ b/components/brain/left-sidebar/waves/MemesWaveFooter.tsx @@ -23,11 +23,23 @@ const MemesWaveFooter: React.FC = ({ onOpenQuickVote, onPrefetchQuickVote, }) => { - const { isReady, uncastPower, unratedCount, votingLabel } = + const { isAvailable, isReady, uncastPower, unratedCount, votingLabel } = useMemesWaveFooterStats(); + const buttonAriaLabel = + isReady && typeof uncastPower === "number" + ? `Uncast Power, ${formatNumberWithCommas(uncastPower)} ${ + votingLabel ?? "Votes" + } left, ${formatNumberWithCommas(unratedCount)} unrated` + : "Quick vote"; + const buttonTitle = isReady ? "Uncast votes" : "Quick vote"; + const votingPowerLabel = votingLabel ? ` ${votingLabel}` : " votes"; + const buttonValue = + isReady && typeof uncastPower === "number" + ? `${formatNumberWithCommas(uncastPower)}${votingPowerLabel}` + : "Open quick vote"; const handleOpenQuickVote = () => { - if (unratedCount <= 0) { + if (!isAvailable) { return; } @@ -35,7 +47,7 @@ const MemesWaveFooter: React.FC = ({ }; const handlePrefetchQuickVote = () => { - if (unratedCount <= 0) { + if (!isAvailable) { return; } @@ -44,7 +56,7 @@ const MemesWaveFooter: React.FC = ({ return ( - {isReady && typeof uncastPower === "number" && ( + {isAvailable && ( = ({ > {collapsed ? ( = ({ ) : ( )} diff --git a/components/brain/left-sidebar/waves/MemesWaveQuickVoteTrigger.tsx b/components/brain/left-sidebar/waves/MemesWaveQuickVoteTrigger.tsx index 78c2663804..6a4cc42fcf 100644 --- a/components/brain/left-sidebar/waves/MemesWaveQuickVoteTrigger.tsx +++ b/components/brain/left-sidebar/waves/MemesWaveQuickVoteTrigger.tsx @@ -5,6 +5,7 @@ import { formatNumberWithCommas } from "@/helpers/Helpers"; import React from "react"; interface MemesWaveQuickVoteTriggerProps { + readonly isAvailable?: boolean | undefined; readonly className?: string | undefined; readonly onOpenQuickVote: () => void; readonly onPrefetchQuickVote?: (() => void) | undefined; @@ -12,20 +13,27 @@ interface MemesWaveQuickVoteTriggerProps { } const MemesWaveQuickVoteTrigger: React.FC = ({ + isAvailable = true, className, onOpenQuickVote, onPrefetchQuickVote, unratedCount, }) => { - if (unratedCount <= 0) { + if (!isAvailable) { return null; } + const label = + unratedCount > 0 + ? `${unratedCount} unrated submissions in the memes wave` + : "Quick vote"; + const title = unratedCount > 0 ? `${unratedCount} unrated` : "Quick vote"; + return ( ); }; diff --git a/components/brain/left-sidebar/waves/memes-quick-vote/MemesQuickVoteControls.tsx b/components/brain/left-sidebar/waves/memes-quick-vote/MemesQuickVoteControls.tsx index 106dc46d55..456ffb7558 100644 --- a/components/brain/left-sidebar/waves/memes-quick-vote/MemesQuickVoteControls.tsx +++ b/components/brain/left-sidebar/waves/memes-quick-vote/MemesQuickVoteControls.tsx @@ -61,7 +61,7 @@ export default function MemesQuickVoteControls({ >
- {formatNumberWithCommas(remainingCount)} unexplored + {formatNumberWithCommas(remainingCount)} unrated
diff --git a/components/brain/left-sidebar/waves/memes-quick-vote/MemesQuickVoteDialog.tsx b/components/brain/left-sidebar/waves/memes-quick-vote/MemesQuickVoteDialog.tsx index 44505c6eac..1b04f9974d 100644 --- a/components/brain/left-sidebar/waves/memes-quick-vote/MemesQuickVoteDialog.tsx +++ b/components/brain/left-sidebar/waves/memes-quick-vote/MemesQuickVoteDialog.tsx @@ -5,8 +5,8 @@ import { getDefaultQuickVoteAmount, normalizeQuickVoteAmount, } from "@/hooks/memesQuickVote.helpers"; +import type { MemesQuickVoteDialogState } from "@/hooks/useMemesQuickVoteDialogController"; import { useMediaQuery } from "@/hooks/useMediaQuery"; -import { useMemesQuickVoteQueue } from "@/hooks/useMemesQuickVoteQueue"; import { XMarkIcon } from "@heroicons/react/24/outline"; import { CheckCircleIcon } from "@heroicons/react/24/solid"; import { @@ -27,23 +27,17 @@ const QUICK_VOTE_BAR_FEEDBACK_DURATION_MS = 650; type VoteFeedbackSource = "custom-submit" | "quick-amount"; type TimeoutHandle = ReturnType; -interface MemesQuickVoteDialogProps { - readonly isOpen: boolean; - readonly sessionId: number; - readonly onClose: () => void; -} +type MemesQuickVoteDialogProps = MemesQuickVoteDialogState; interface MemesQuickVoteDialogContentProps { - readonly activeDrop: NonNullable< - ReturnType["activeDrop"] - >; + readonly activeDrop: NonNullable; readonly isMobile: boolean; readonly latestUsedAmount: number | null; readonly onClose: () => void; readonly remainingCount: number; readonly recentAmounts: number[]; - readonly submitVote: ReturnType["submitVote"]; - readonly skipDrop: ReturnType["skipDrop"]; + readonly submitVote: MemesQuickVoteDialogProps["submitVote"]; + readonly skipDrop: MemesQuickVoteDialogProps["skipDrop"]; readonly uncastPower: number | null; readonly votingLabel: string | null; } @@ -169,9 +163,17 @@ function MemesQuickVoteDialogContent({ ] ); - const queueSkip = () => { - skipDrop(activeDrop); - }; + const queueSkip = useCallback(() => { + skipDrop(activeDrop) + .then((wasQueued) => { + if (!wasQueued) { + setIsAdvancing(false); + } + }) + .catch(() => { + setIsAdvancing(false); + }); + }, [activeDrop, skipDrop]); const handleSkip = () => { if (isControlsSubmitting) { @@ -198,7 +200,7 @@ function MemesQuickVoteDialogContent({
- {formatNumberWithCommas(remainingCount)} unexplored + {formatNumberWithCommas(remainingCount)} unrated
@@ -211,7 +213,7 @@ function MemesQuickVoteDialogContent({
@@ -272,7 +275,7 @@ function MemesQuickVoteDialogContent({
@@ -381,10 +385,10 @@ function MemesQuickVoteDialogErrorState({

- Couldn't load your queue + Couldn't load quick vote

- Quick vote couldn't reach the leaderboard. Try again. + Quick vote couldn't load the next meme. Try again.