Skip to content

Commit 0734dfa

Browse files
fix: prediction results page (kuestcom#1334)
Upstream: 0f72d98
1 parent f3fd36d commit 0734dfa

14 files changed

Lines changed: 915 additions & 1108 deletions

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "kuest-prediction-market",
33
"type": "module",
44
"private": true,
5-
"packageManager": "pnpm@11.13.0",
5+
"packageManager": "pnpm@11.15.1",
66
"description": "Launch Your Own Decentralized Prediction Market in Minutes.",
77
"repository": {
88
"type": "git",
@@ -65,7 +65,7 @@
6565
"fumadocs-ui": "16.11.2",
6666
"input-otp": "1.4.2",
6767
"lucide-react": "1.24.0",
68-
"next": "16.3.0-preview.6",
68+
"next": "16.3.0-preview.8",
6969
"next-intl": "4.13.2",
7070
"next-themes": "0.4.6",
7171
"postgres": "3.4.9",

pnpm-lock.yaml

Lines changed: 553 additions & 822 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pnpm-workspace.yaml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,15 @@
11
minimumReleaseAge: 1440
2+
minimumReleaseAgeExclude:
3+
- next
4+
- '@next/env@16.3.0-preview.8'
5+
- '@next/swc-darwin-arm64@16.3.0-preview.8'
6+
- '@next/swc-darwin-x64@16.3.0-preview.8'
7+
- '@next/swc-linux-arm64-gnu@16.3.0-preview.8'
8+
- '@next/swc-linux-arm64-musl@16.3.0-preview.8'
9+
- '@next/swc-linux-x64-gnu@16.3.0-preview.8'
10+
- '@next/swc-linux-x64-musl@16.3.0-preview.8'
11+
- '@next/swc-win32-arm64-msvc@16.3.0-preview.8'
12+
- '@next/swc-win32-x64-msvc@16.3.0-preview.8'
213
shellEmulator: true
314
blockExoticSubdeps: true
415
trustPolicy: no-downgrade

src/app/[locale]/(platform)/predictions/[slug]/_components/PredictionResultsClient.tsx

Lines changed: 64 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -68,12 +68,12 @@ function getCurrentTimestampSnapshot() {
6868
return Math.floor(Date.now() / TIMESTAMP_REFRESH_MS) * TIMESTAMP_REFRESH_MS
6969
}
7070

71-
function resolvePrimaryMarket(event: Event): Market | null {
71+
function resolvePrimaryMarket(event: Event, isResolvedEvent: boolean): Market | null {
7272
if (event.markets.length === 0) {
7373
return null
7474
}
7575

76-
if (event.status === 'resolved') {
76+
if (isResolvedEvent) {
7777
return event.markets[0] ?? null
7878
}
7979

@@ -82,10 +82,12 @@ function resolvePrimaryMarket(event: Event): Market | null {
8282
?? null
8383
}
8484

85-
function buildDateLabel(event: Event, currentTimestamp: number | null) {
86-
if (event.status === 'resolved' && event.resolved_at) {
87-
const resolvedAt = new Date(event.resolved_at)
88-
return Number.isNaN(resolvedAt.getTime()) ? 'Resolved' : `Resolved ${formatDate(resolvedAt)}`
85+
function buildDateLabel(event: Event, currentTimestamp: number | null, isResolvedEvent: boolean) {
86+
if (isResolvedEvent) {
87+
const resolvedAt = event.resolved_at ? new Date(event.resolved_at) : null
88+
return resolvedAt && !Number.isNaN(resolvedAt.getTime())
89+
? `Resolved ${formatDate(resolvedAt)}`
90+
: 'Resolved'
8991
}
9092

9193
if (event.end_date) {
@@ -127,7 +129,7 @@ function buildDateLabel(event: Event, currentTimestamp: number | null) {
127129
return 'Ends soon'
128130
}
129131

130-
return event.status === 'resolved' ? 'Resolved' : 'Active'
132+
return 'Active'
131133
}
132134

133135
function getEventRecentVolume(event: Event) {
@@ -280,7 +282,6 @@ function usePredictionResultsFilters({
280282
key: routeScopeKey,
281283
value: initialStatus,
282284
})
283-
const [searchParamsString, setSearchParamsString] = useState('')
284285
const searchDebounceTimeoutRef = useRef<number | null>(null)
285286

286287
const currentTimestamp = useSyncExternalStore(
@@ -309,13 +310,11 @@ function usePredictionResultsFilters({
309310
isBookmarked,
310311
isDrawerOpen,
311312
searchDebounceTimeoutRef,
312-
searchParamsString,
313313
searchValue,
314314
selectedSort,
315315
selectedStatus,
316316
setIsBookmarkedState,
317317
setIsDrawerOpenState,
318-
setSearchParamsString,
319318
setSearchValueState,
320319
setSelectedSortState,
321320
setSelectedStatusState,
@@ -417,16 +416,14 @@ function useResolvedResultDisplay({
417416
isResolvedEvent,
418417
normalizeOutcomeLabel,
419418
resolvedLabel,
420-
showResolvedOutcomeLayout,
421419
}: {
422420
event: Event
423421
isResolvedEvent: boolean
424422
normalizeOutcomeLabel: (label: string) => string | null
425423
resolvedLabel: string
426-
showResolvedOutcomeLayout: boolean
427424
}) {
428425
return useMemo(() => {
429-
if (!showResolvedOutcomeLayout || !isResolvedEvent) {
426+
if (!isResolvedEvent) {
430427
return {
431428
label: null,
432429
outcomeIndex: null,
@@ -438,7 +435,7 @@ function useResolvedResultDisplay({
438435
label: resolvedDisplay.label ? (normalizeOutcomeLabel(resolvedDisplay.label) || resolvedDisplay.label) : resolvedLabel,
439436
outcomeIndex: resolvedDisplay.outcomeIndex,
440437
}
441-
}, [event, isResolvedEvent, normalizeOutcomeLabel, resolvedLabel, showResolvedOutcomeLayout])
438+
}, [event, isResolvedEvent, normalizeOutcomeLabel, resolvedLabel])
442439
}
443440

444441
export default function PredictionResultsClient({
@@ -466,13 +463,11 @@ export default function PredictionResultsClient({
466463
isBookmarked,
467464
isDrawerOpen,
468465
searchDebounceTimeoutRef,
469-
searchParamsString,
470466
searchValue,
471467
selectedSort,
472468
selectedStatus,
473469
setIsBookmarkedState,
474470
setIsDrawerOpenState,
475-
setSearchParamsString,
476471
setSearchValueState,
477472
setSelectedSortState,
478473
setSelectedStatusState,
@@ -549,16 +544,10 @@ export default function PredictionResultsClient({
549544
selectedStatus,
550545
})
551546

552-
const handleSearchParamsChange = useCallback(({
553-
searchParamsString: nextSearchParamsString,
554-
sort,
555-
status,
556-
}: {
557-
searchParamsString: string
547+
const handleSearchParamsChange = useCallback(({ sort, status }: {
558548
sort: PredictionResultsSortOption
559549
status: PredictionResultsStatusOption
560550
}) => {
561-
setSearchParamsString(current => current === nextSearchParamsString ? current : nextSearchParamsString)
562551
setSelectedSortState((current) => {
563552
const currentValue = current.key === routeScopeKey ? current.value : initialSort
564553
return currentValue === sort ? current : { key: routeScopeKey, value: sort }
@@ -571,40 +560,44 @@ export default function PredictionResultsClient({
571560
initialSort,
572561
initialStatus,
573562
routeScopeKey,
574-
setSearchParamsString,
575563
setSelectedSortState,
576564
setSelectedStatusState,
577565
])
578566

579567
const isEmptyState = !isPending && !isFetching && visibleEvents.length === 0
580568
const showInitialSkeleton = visibleEvents.length === 0 && (isPending || isFetching)
581569

582-
function replaceRoute({
583-
nextSearchValue = null,
570+
function clearPendingSearchRoute() {
571+
if (!searchDebounceTimeoutRef.current) {
572+
return
573+
}
574+
575+
window.clearTimeout(searchDebounceTimeoutRef.current)
576+
searchDebounceTimeoutRef.current = null
577+
}
578+
579+
function replaceSearchRoute({
580+
nextSearchValue,
584581
nextSort = selectedSort,
585582
nextStatus = selectedStatus,
586-
immediate = true,
587583
}: {
588-
nextSearchValue?: string | null
584+
nextSearchValue: string
589585
nextSort?: PredictionResultsSortOption
590586
nextStatus?: PredictionResultsStatusOption
591-
immediate?: boolean
592587
}) {
593-
const nextPath = nextSearchValue == null
594-
? pathname
595-
: buildPredictionResultsPath(nextSearchValue)
588+
const nextPath = buildPredictionResultsPath(nextSearchValue)
596589

597590
if (!nextPath) {
598591
return
599592
}
600593

601-
const nextParams = buildPredictionResultsUrlSearchParams(searchParamsString, {
594+
const nextParams = buildPredictionResultsUrlSearchParams(window.location.search, {
602595
sort: nextSort,
603596
status: nextStatus,
604597
})
605598
const nextQuery = nextParams.toString()
606599
const nextUrl = nextQuery ? `${nextPath}?${nextQuery}` : nextPath
607-
const currentUrl = searchParamsString ? `${pathname}?${searchParamsString}` : pathname
600+
const currentUrl = `${pathname}${window.location.search}`
608601

609602
if (nextUrl === currentUrl) {
610603
return
@@ -616,11 +609,6 @@ export default function PredictionResultsClient({
616609
})
617610
}
618611

619-
if (immediate) {
620-
runReplace()
621-
return
622-
}
623-
624612
if (searchDebounceTimeoutRef.current) {
625613
window.clearTimeout(searchDebounceTimeoutRef.current)
626614
}
@@ -630,6 +618,32 @@ export default function PredictionResultsClient({
630618
}, 300)
631619
}
632620

621+
function replaceFilterSearchParams({
622+
nextSort = selectedSort,
623+
nextStatus = selectedStatus,
624+
}: {
625+
nextSort?: PredictionResultsSortOption
626+
nextStatus?: PredictionResultsStatusOption
627+
}) {
628+
const nextParams = buildPredictionResultsUrlSearchParams(window.location.search, {
629+
sort: nextSort,
630+
status: nextStatus,
631+
})
632+
const nextQuery = nextParams.toString()
633+
const currentUrl = `${window.location.pathname}${window.location.search}${window.location.hash}`
634+
const nextUrl = `${window.location.pathname}${nextQuery ? `?${nextQuery}` : ''}${window.location.hash}`
635+
636+
if (nextUrl === currentUrl) {
637+
return
638+
}
639+
640+
clearPendingSearchRoute()
641+
642+
startTransition(() => {
643+
window.history.replaceState(null, '', nextUrl)
644+
})
645+
}
646+
633647
function handleRetryLoadMore() {
634648
setCanRetryLoadMoreState({ key: infiniteScrollScopeKey, value: true })
635649
setInfiniteScrollErrorState({ key: infiniteScrollScopeKey, value: null })
@@ -641,25 +655,24 @@ export default function PredictionResultsClient({
641655

642656
function handleSearchValueChange(nextValue: string) {
643657
setSearchValueState({ key: searchScopeKey, value: nextValue })
644-
replaceRoute({
658+
replaceSearchRoute({
645659
nextSearchValue: nextValue,
646660
nextSort: selectedSort,
647661
nextStatus: selectedStatus,
648-
immediate: false,
649662
})
650663
}
651664

652665
function handleClearFilters() {
666+
clearPendingSearchRoute()
667+
653668
setIsBookmarkedState({ key: routeScopeKey, value: false })
654669
setIsDrawerOpenState({ key: routeScopeKey, value: false })
655670
setSearchValueState({ key: searchScopeKey, value: initialInputValue })
656671
setSelectedSortState({ key: routeScopeKey, value: DEFAULT_PREDICTION_RESULTS_SORT })
657672
setSelectedStatusState({ key: routeScopeKey, value: DEFAULT_PREDICTION_RESULTS_STATUS })
658-
replaceRoute({
659-
nextSearchValue: initialInputValue,
673+
replaceFilterSearchParams({
660674
nextSort: DEFAULT_PREDICTION_RESULTS_SORT,
661675
nextStatus: DEFAULT_PREDICTION_RESULTS_STATUS,
662-
immediate: true,
663676
})
664677
}
665678

@@ -686,11 +699,11 @@ export default function PredictionResultsClient({
686699
onSearchValueChange={handleSearchValueChange}
687700
onSortChange={((value) => {
688701
setSelectedSortState({ key: routeScopeKey, value })
689-
replaceRoute({ nextSort: value, immediate: true })
702+
replaceFilterSearchParams({ nextSort: value })
690703
})}
691704
onStatusChange={((value) => {
692705
setSelectedStatusState({ key: routeScopeKey, value })
693-
replaceRoute({ nextStatus: value, immediate: true })
706+
replaceFilterSearchParams({ nextStatus: value })
694707
})}
695708
/>
696709
)
@@ -811,7 +824,6 @@ export default function PredictionResultsClient({
811824
key={event.id}
812825
event={event}
813826
currentTimestamp={currentTimestamp}
814-
showResolvedOutcomeLayout={selectedStatus === 'resolved'}
815827
/>
816828
))}
817829
</div>
@@ -875,34 +887,31 @@ export default function PredictionResultsClient({
875887
function PredictionResultRow({
876888
currentTimestamp,
877889
event,
878-
showResolvedOutcomeLayout = false,
879890
}: {
880891
currentTimestamp: number | null
881892
event: Event
882-
showResolvedOutcomeLayout?: boolean
883893
}) {
884894
const t = useExtracted()
885895
const locale = useLocale()
886896
const normalizeOutcomeLabel = useOutcomeLabel()
887897
const { data: commentMetrics } = useCommentMetrics(event.slug)
888-
const primaryMarket = resolvePrimaryMarket(event)
898+
const isResolvedEvent = isEventResolvedLike(event)
899+
const primaryMarket = resolvePrimaryMarket(event, isResolvedEvent)
889900
const primaryProbability = primaryMarket?.probability ?? 0
890901
const supportingTags = event.tags.slice(0, 2)
891902
const isMultiMarket = Math.max(event.total_markets_count ?? 0, event.markets.length) > 1
892-
const isResolvedEvent = isEventResolvedLike(event)
893903
const recentVolume = getEventRecentVolume(event)
894904
const commentsCount = commentMetrics?.comments_count ?? null
895905
const eventPath = resolveEventPagePath(event)
896906
const resolvedLabel = t('Resolved')
897907
const selectedMarketLabel = primaryMarket?.short_title?.trim()
898908
|| primaryMarket?.title?.trim()
899-
|| (event.status === 'resolved' ? resolvedLabel : t('Market'))
909+
|| (isResolvedEvent ? resolvedLabel : t('Market'))
900910
const resolvedResultDisplay = useResolvedResultDisplay({
901911
event,
902912
isResolvedEvent,
903913
normalizeOutcomeLabel,
904914
resolvedLabel,
905-
showResolvedOutcomeLayout,
906915
})
907916
const resolvedBadgeOutcome = resolvedResultDisplay.outcomeIndex === OUTCOME_INDEX.NO
908917
? 'no'
@@ -1002,13 +1011,13 @@ function PredictionResultRow({
10021011
</a>
10031012
<span className="flex items-center gap-1 whitespace-nowrap">
10041013
<Clock3Icon className="size-3.5 text-muted-foreground" />
1005-
<span>{buildDateLabel(event, currentTimestamp)}</span>
1014+
<span>{buildDateLabel(event, currentTimestamp, isResolvedEvent)}</span>
10061015
</span>
10071016
</div>
10081017
</div>
10091018

10101019
<div className="flex max-w-[42%] min-w-[112px] shrink-0 items-center gap-3 self-center">
1011-
{showResolvedOutcomeLayout && isResolvedEvent
1020+
{isResolvedEvent
10121021
? (
10131022
<div className="flex min-w-0 flex-1 flex-col items-end justify-center text-right">
10141023
<div className="flex max-w-full items-center gap-2">

src/app/[locale]/(platform)/predictions/[slug]/_components/PredictionResultsSearchParamsSync.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import {
1212

1313
function useSyncFiltersFromSearchParams(
1414
onChange: (nextState: {
15-
searchParamsString: string
1615
sort: PredictionResultsSortOption
1716
status: PredictionResultsStatusOption
1817
}) => void,
@@ -28,7 +27,6 @@ export default function PredictionResultsSearchParamsSync({
2827
onChange,
2928
}: {
3029
onChange: (nextState: {
31-
searchParamsString: string
3230
sort: PredictionResultsSortOption
3331
status: PredictionResultsStatusOption
3432
}) => void

0 commit comments

Comments
 (0)