From 8da9720a2e82c84da6a2b594fb101bb5a5b4c68b Mon Sep 17 00:00:00 2001 From: harshbaz <106158714+harshbaz@users.noreply.github.com> Date: Wed, 15 Nov 2023 15:36:24 +0530 Subject: [PATCH] Update up-down game (#2010) * Update result card style * Fix date * Add loading indicator class * Update types * Return ... * Add result parsing * Remove observable * Update type * Add result parsing * Update colors * Fix positioning * Add option to vote again * Cleanup * Only update time & likes when enabled * Cleanup * Make prop optional * Update logic * Add id * Update vote page * Disable score on empty page * Allow scrolling * Update link * Update wallet page * Hide header on wallet page --- .../src/components/layout/PlayerLayout.svelte | 28 ++-- .../src/components/up-down/UpDownVote.svelte | 85 +++++------ .../up-down/UpDownVoteControls.svelte | 10 +- .../up-down/UpDownVoteOutcome.svelte | 143 +++++++++++++----- packages/experiments/src/css/app.css | 12 ++ packages/experiments/src/lib/db/db.types.ts | 40 +++++ .../experiments/src/lib/utils/countdown.ts | 2 +- .../up-down/[id=videoId]/+page.svelte | 2 +- .../(splash)/up-down/results/+page.svelte | 21 ++- .../up-down/results/ResultCard.svelte | 50 ++++-- .../up-down/votes/[id=videoId]/+page.svelte | 69 --------- .../up-down/votes/[voteId]/+page.svelte | 90 +++++++++++ .../(splash)/up-down/wallet/+page.svelte | 19 ++- .../src/routes/(feed)/+layout.svelte | 75 +++++---- packages/experiments/static/icons.sprite.svg | 4 +- 15 files changed, 429 insertions(+), 221 deletions(-) delete mode 100644 packages/experiments/src/routes/(feed)/(splash)/up-down/votes/[id=videoId]/+page.svelte create mode 100644 packages/experiments/src/routes/(feed)/(splash)/up-down/votes/[voteId]/+page.svelte diff --git a/packages/experiments/src/components/layout/PlayerLayout.svelte b/packages/experiments/src/components/layout/PlayerLayout.svelte index 08736b06e..8ee358612 100644 --- a/packages/experiments/src/components/layout/PlayerLayout.svelte +++ b/packages/experiments/src/components/layout/PlayerLayout.svelte @@ -19,6 +19,7 @@ import { import { getDb } from '$lib/db' import { doc, getDoc } from 'firebase/firestore' import { getMsLeftForResult, getVoteEndTime } from '$lib/utils/countdown' +import type { Readable } from 'svelte/store' export let index: number export let post: UpDownPost @@ -39,14 +40,20 @@ let watchProgress = { async function updateLikeDislikeStatus() { const db = getDb() - const likeDoc = ( - await getDoc(doc(db, `ud-videos/${post.id}/likes/${$authState.userId}`)) - ).data() as LikeRecord - const dislikeDoc = ( - await getDoc(doc(db, `ud-videos/${post.id}/dislikes/${$authState.userId}`)) - ).data() as DislikeRecord - likeDoc && (liked = likeDoc.liked) - dislikeDoc && (disliked = dislikeDoc.disliked) + if (showLikeButton) { + const likeDoc = ( + await getDoc(doc(db, `ud-videos/${post.id}/likes/${$authState.userId}`)) + ).data() as LikeRecord + likeDoc && (liked = likeDoc.liked) + } + if (showDislikeButton) { + const dislikeDoc = ( + await getDoc( + doc(db, `ud-videos/${post.id}/dislikes/${$authState.userId}`), + ) + ).data() as DislikeRecord + dislikeDoc && (disliked = dislikeDoc.disliked) + } } $: if ($authState.isLoggedIn) { @@ -192,7 +199,10 @@ async function updateStats() { $: avatarUrl = getDefaultImageUrl(post.ouid) const endTime = getVoteEndTime(new Date(post.created_at), new Date()) -let timeLeft = getMsLeftForResult(endTime, true) +let timeLeft: Readable +if (showTimer) { + timeLeft = getMsLeftForResult(endTime, true) +} @@ -23,6 +24,7 @@ import { onSnapshot, type Unsubscribe, limit, + orderBy, } from 'firebase/firestore' import UpDownVoteControls from './UpDownVoteControls.svelte' @@ -34,14 +36,14 @@ export let post: UpDownPost export let tutorialStep: number | undefined = undefined let loading = true +let voteDetails: UpDownVoteDetails | undefined = undefined let voteDocId: string | undefined = undefined -let unsubscribePost: Unsubscribe | undefined = undefined -let unsubscribeVote: Unsubscribe | undefined = undefined +let unsubscribe: Unsubscribe | undefined = undefined let postStore = writable(post) -let voteDetails = writable(undefined) +let voteAgain = false $: if (post.id && !tutorialStep && $authState.isLoggedIn && $authState.userId) { - observeVote() + loadVoteDetails() } if (post.id && !tutorialStep) { @@ -50,10 +52,10 @@ if (post.id && !tutorialStep) { async function observePost() { try { - if (unsubscribePost) return + if (unsubscribe) return const db = getDb() - const watchRef = doc(db, 'ud-videos' as CollectionName, post.id) - unsubscribePost = onSnapshot(watchRef, (doc) => { + const docRef = doc(db, 'ud-videos' as CollectionName, post.id) + unsubscribe = onSnapshot(docRef, (doc) => { $postStore = doc.data() as UpDownPost }) } catch (e) { @@ -61,79 +63,70 @@ async function observePost() { } } -async function observeVote() { - if (unsubscribeVote) return +async function loadVoteDetails() { + if (voteDetails) return const db = getDb() const docRef = await getDocs( query( - collection(db, 'votes' satisfies CollectionName), + collection(db, 'votes'), where('videoId', '==', post.id), where('uid', '==', $authState.userId), + orderBy('created_at', 'desc'), limit(1), ), ) if (!docRef.empty) { const vote = docRef.docs[0].data() as VoteRecord voteDocId = docRef.docs[0].id - console.log({ voteDocId }) - $voteDetails = { + voteDetails = { + score: vote.currentScore, direction: vote.voteDirection, - created_at: vote.created_at, + result_at: vote.result_at, status: vote.status, result: vote.result, voteAmount: vote.voteAmount, } - - const watchRef = doc( - db, - 'votes' satisfies CollectionName, - docRef.docs[0].id, - ) - unsubscribeVote = onSnapshot(watchRef, (doc) => { - const d = doc.data() as VoteRecord - $voteDetails = { - direction: d.voteDirection, - created_at: d.created_at, - status: d.status, - result: d.result, - voteAmount: d.voteAmount, - } - }) } loading = false } async function handlePlaceVote(vote: UpDownVoteDetails) { if (!post) return - const res = await placeVote( - { - videoId: post.id, - videoOid: post.oid, - videoUoid: post.ouid, - videoUid: post.video_uid, - }, - vote.voteAmount, - vote.direction, - ) - $voteDetails = vote + try { + const res = await placeVote( + { + videoId: post.id, + videoOid: post.oid, + videoUoid: post.ouid, + videoUid: post.video_uid, + }, + vote.voteAmount, + vote.direction, + ) + voteDetails = vote + } catch (e) { + console.error('Error while placing vote', e) + loading = false + } } onDestroy(() => { - unsubscribePost?.() - unsubscribeVote?.() + unsubscribe?.() }) - {#if $voteDetails && !tutorialStep} + {#if voteDetails && !tutorialStep && !voteAgain} + {voteDetails} + on:voteAgain={() => (voteAgain = true)} /> {:else} handlePlaceVote(detail)} /> diff --git a/packages/experiments/src/components/up-down/UpDownVoteControls.svelte b/packages/experiments/src/components/up-down/UpDownVoteControls.svelte index fba237a5c..1861caaba 100644 --- a/packages/experiments/src/components/up-down/UpDownVoteControls.svelte +++ b/packages/experiments/src/components/up-down/UpDownVoteControls.svelte @@ -4,8 +4,10 @@ import { createEventDispatcher } from 'svelte' import type { UpDownVoteDetails } from './UpDownVote.svelte' import { authState } from '$stores/auth' import c from 'clsx' +import { getVoteEndTime } from '$lib/utils/countdown' export let score: number +export let postCreatedAt: number | undefined = undefined export let disabled = false export let tutorialStep: number | undefined = undefined @@ -34,16 +36,20 @@ function placeVote(direction: 'up' | 'down', voteAmount: number) { dispatch('votePlaced', { direction, + score, voteAmount, status: 'pending', - created_at: Date.now(), + result_at: getVoteEndTime( + postCreatedAt ? new Date(postCreatedAt) : new Date(), + new Date(), + ).getTime(), }) }
() -onDestroy(() => unsubscribe?.()) +let voteDetailsStore = writable(voteDetails) +let timeLeft = getMsLeftForResult(new Date(voteDetails.result_at)) +let unsubscribe: Unsubscribe | undefined = undefined $: if (voteDocId) { observeDoc() @@ -29,10 +32,11 @@ async function observeDoc() { const docRef = doc(db, 'votes' as CollectionName, voteDocId) unsubscribe = onSnapshot(docRef, (doc) => { const data = doc.data() as VoteRecord - voteDetails = { + $voteDetailsStore = { direction: data.voteDirection, - created_at: data.created_at, + result_at: data.result_at, status: data.status, + score: data.currentScore, result: data.result, voteAmount: data.voteAmount, } @@ -41,41 +45,112 @@ async function observeDoc() { console.log('error while observing vote', e) } } + +onDestroy(() => unsubscribe?.())
+ class:opacity-50={disabled} + class="fade-in pointer-events-auto flex w-full select-none flex-col items-center justify-center gap-2 p-4 transition-opacity">
+ class="mx-8 flex items-center justify-between gap-1 rounded-xl bg-black/30 p-1">
- + class:bg-zinc-500={$voteDetailsStore.voteAmount === 10} + class="flex flex-nowrap items-center gap-1 rounded-lg p-3"> + {#if $voteDetailsStore.voteAmount === 10} + + {:else} +
+ {/if} +
10 Tokens
-
{voteDetails.direction}
-
-
- -
- - {voteDetails.voteAmount} - +
+ {#if $voteDetailsStore.voteAmount === 50} + + {:else} +
+ {/if} +
50 Tokens
+
+
+ {#if $voteDetailsStore.voteAmount === 100} + + {:else} +
+ {/if} +
100 Tokens
- {#if $timeLeft} -
+
+
- - {$timeLeft} + class="text-5xl font-bold drop-shadow-[0_1.2px_1.2px_rgba(0,0,0,0.8)]"> + {Math.round($voteDetailsStore.score)} +
+
+ Score +
+
+
-
Your vote has been placed
- {/if} + + {#if $voteDetailsStore.status === 'final'} +
+ {#if $voteDetailsStore.result?.status === 'won'} + You won {$voteDetailsStore.result?.won_amount + ? `${$voteDetailsStore.result.won_amount} tokens` + : ''} + {:else} + You lost + {/if} +
+ {#if showVoteAgainButton} + + {/if} + {:else if $timeLeft} +
+
+ + + {#if $timeLeft === '...'} + Loading ... + {:else} + {$timeLeft} + {/if} + +
+ +
+ {#if $timeLeft === '...'} + Processing vote result ... + {:else} + Your vote has been placed + {/if} +
+
+ {/if} +
diff --git a/packages/experiments/src/css/app.css b/packages/experiments/src/css/app.css index bdf2d367e..f89fd713b 100644 --- a/packages/experiments/src/css/app.css +++ b/packages/experiments/src/css/app.css @@ -53,3 +53,15 @@ button { -webkit-tap-highlight-color: transparent; } + +.loading { + display: inline-block; + clip-path: inset(0 2ch 0 0); + animation: loading-dots 2s steps(4) infinite; +} + +@keyframes loading-dots { + to { + clip-path: inset(0 -1ch 0 0); + } +} diff --git a/packages/experiments/src/lib/db/db.types.ts b/packages/experiments/src/lib/db/db.types.ts index 2b2152150..d73796fbc 100644 --- a/packages/experiments/src/lib/db/db.types.ts +++ b/packages/experiments/src/lib/db/db.types.ts @@ -3,6 +3,12 @@ export type CollectionName = | 'profile' | 'transactions' | 'votes' + | 'views' + +export type UpDownSubCollections = + | 'likes' + | 'dislikes' + | 'shares' | 'view-updates' export type UpDownPost = { @@ -79,6 +85,7 @@ export type ShareRecord = { } export type VoteRecord = { + id: string uid: string videoId: string videoOid: number @@ -94,6 +101,7 @@ export type VoteRecord = { status: 'pending' | 'final' result?: { status: 'won' | 'lost' | 'tie' + won_amount?: number published_at: number winning_transaction_id?: string } @@ -113,6 +121,38 @@ export type TransanctionRecord = { anon: boolean } +export type ViewUpdateRecord = { + uid: string + videoId: string + videoOid: number + videoUoid: string + videoUid: string + created_at: number + anon: boolean + secondsFromStart: number + minutesFromStart: number + like: number + dislike: number + share: number + fullyWatched: number + videoWatchedPercentage: number + thresholdView: number + minutePassedAge: number + viewsPerMinute: number + change: { + like: number + dislike: number + share: number + videoWatchedPercentage: number + thresholdView: number + fullyWatched: number + minutePassedAge: number + viewsPerMinute: number + totalChange: number + totalScore: number + } +} + export type VideoRef = { videoId: string videoOid: number diff --git a/packages/experiments/src/lib/utils/countdown.ts b/packages/experiments/src/lib/utils/countdown.ts index abe357a76..87efaef69 100644 --- a/packages/experiments/src/lib/utils/countdown.ts +++ b/packages/experiments/src/lib/utils/countdown.ts @@ -39,7 +39,7 @@ export function getMsLeftForResult(endTime: Date, loop: boolean = false) { } }) } else { - return readable('') + return readable('...') } } diff --git a/packages/experiments/src/routes/(feed)/(splash)/up-down/[id=videoId]/+page.svelte b/packages/experiments/src/routes/(feed)/(splash)/up-down/[id=videoId]/+page.svelte index 8ce2dc837..78c3654ab 100644 --- a/packages/experiments/src/routes/(feed)/(splash)/up-down/[id=videoId]/+page.svelte +++ b/packages/experiments/src/routes/(feed)/(splash)/up-down/[id=videoId]/+page.svelte @@ -157,7 +157,7 @@ beforeNavigate(() => { There are no more videos to vote on
- +
diff --git a/packages/experiments/src/routes/(feed)/(splash)/up-down/results/+page.svelte b/packages/experiments/src/routes/(feed)/(splash)/up-down/results/+page.svelte index 9b7f37d8e..7ea25f2be 100644 --- a/packages/experiments/src/routes/(feed)/(splash)/up-down/results/+page.svelte +++ b/packages/experiments/src/routes/(feed)/(splash)/up-down/results/+page.svelte @@ -4,7 +4,7 @@ import Icon from '$components/icon/Icon.svelte' import { getDb } from '$lib/db' import type { CollectionName, VoteRecord } from '$lib/db/db.types' import { authState } from '$stores/auth' -import { collection, getDocs, query, where } from 'firebase/firestore' +import { collection, getDocs, orderBy, query, where } from 'firebase/firestore' import { onMount, tick } from 'svelte' import { fade } from 'svelte/transition' import ResultCard from './ResultCard.svelte' @@ -20,9 +20,18 @@ async function getVotes() { const coll = collection(db, 'votes' as CollectionName) const votesDocs = await getDocs( - query(coll, where('uid', '==', $authState.userId)), + query( + coll, + where('uid', '==', $authState.userId), + orderBy('created_at', 'desc'), + ), + ) + votesDocs.forEach((doc) => + votes.push({ + ...(doc.data() as VoteRecord), + id: doc.id, + }), ) - votesDocs.forEach((doc) => votes.push(doc.data() as VoteRecord)) } catch (e) { console.error('Error loading votes', e) } finally { @@ -35,7 +44,9 @@ onMount(() => { }) -
+
{#if loading}
@@ -52,7 +63,7 @@ onMount(() => {
-
No transactions yet
+
No votes placed found
{/if} {:else} diff --git a/packages/experiments/src/routes/(feed)/(splash)/up-down/results/ResultCard.svelte b/packages/experiments/src/routes/(feed)/(splash)/up-down/results/ResultCard.svelte index cda36783e..6dcedde36 100644 --- a/packages/experiments/src/routes/(feed)/(splash)/up-down/results/ResultCard.svelte +++ b/packages/experiments/src/routes/(feed)/(splash)/up-down/results/ResultCard.svelte @@ -9,13 +9,11 @@ import userProfile from '$stores/userProfile' export let vote: VoteRecord -const timeLeft = getMsLeftForResult( - getVoteEndTime(new Date(vote.created_at), new Date()), -) +const timeLeft = getMsLeftForResult(new Date(vote.result_at))
- - {vote.voteDirection} from {vote.currentScore} - - - {vote.voteAmount} - {pluralize('Token', vote.voteAmount)} - - {#if $timeLeft} +
+
+ YOUR VOTE + + {vote.voteAmount} + {pluralize('Token', vote.voteAmount)} + +
+
+ {Math.round(vote.currentScore)} +
+ +
+
+
+ {#if vote.status === 'final'} +
+ {#if vote.result?.status === 'won'} + You won {vote.result?.won_amount + ? `${vote.result.won_amount} tokens` + : ''} + {:else} + You lost + {/if} +
+ {:else if $timeLeft}
- {$timeLeft} + {$timeLeft}
{/if}
diff --git a/packages/experiments/src/routes/(feed)/(splash)/up-down/votes/[id=videoId]/+page.svelte b/packages/experiments/src/routes/(feed)/(splash)/up-down/votes/[id=videoId]/+page.svelte deleted file mode 100644 index 5f6895ad1..000000000 --- a/packages/experiments/src/routes/(feed)/(splash)/up-down/votes/[id=videoId]/+page.svelte +++ /dev/null @@ -1,69 +0,0 @@ - - - - Your Votes | Hot or Not - - -
- {#if loading || !video} -
-
-
Loading
-
-
- {:else} - - hideSplashScreen(500)} - index={0} - playFormat="hls" - {Hls} - inView - uid={video.video_uid} /> - - - - - - {/if} -
diff --git a/packages/experiments/src/routes/(feed)/(splash)/up-down/votes/[voteId]/+page.svelte b/packages/experiments/src/routes/(feed)/(splash)/up-down/votes/[voteId]/+page.svelte new file mode 100644 index 000000000..24168a7a2 --- /dev/null +++ b/packages/experiments/src/routes/(feed)/(splash)/up-down/votes/[voteId]/+page.svelte @@ -0,0 +1,90 @@ + + + + Your Votes | Hot or Not + + +
+ {#if loading} +
+
+
Loading
+
+
+ {:else} + + hideSplashScreen(500)} + index={0} + playFormat="hls" + {Hls} + inView + uid={post.video_uid} /> + + {#if voteDetails} + + {/if} + + + {/if} +
diff --git a/packages/experiments/src/routes/(feed)/(splash)/up-down/wallet/+page.svelte b/packages/experiments/src/routes/(feed)/(splash)/up-down/wallet/+page.svelte index 06ca74186..f136774f9 100644 --- a/packages/experiments/src/routes/(feed)/(splash)/up-down/wallet/+page.svelte +++ b/packages/experiments/src/routes/(feed)/(splash)/up-down/wallet/+page.svelte @@ -18,6 +18,7 @@ import { doc, getDoc, getDocs, + orderBy, query, where, } from 'firebase/firestore' @@ -51,7 +52,11 @@ async function getTransactions() { const col = collection(db, 'transactions' as CollectionName) if ($authState.isLoggedIn) { const data = await getDocs( - query(col, where('uid', '==', $authState.userId)), + query( + col, + where('uid', '==', $authState.userId), + orderBy('created_at', 'desc'), + ), ) data.forEach((doc) => { transactions.push(doc.data() as TransanctionRecord) @@ -67,7 +72,7 @@ async function getTransactions() { onMount(() => getTransactions()) -
+
{#if loading}
@@ -77,7 +82,7 @@ onMount(() => getTransactions())
history.back()} iconName="caret-left" iconClass="text-white w-6 h-6" /> diff --git a/packages/experiments/src/routes/(feed)/+layout.svelte b/packages/experiments/src/routes/(feed)/+layout.svelte index 8603c2e45..b69b9e875 100644 --- a/packages/experiments/src/routes/(feed)/+layout.svelte +++ b/packages/experiments/src/routes/(feed)/+layout.svelte @@ -32,43 +32,52 @@ $: votesPage = pathname.includes('votes') diff --git a/packages/experiments/static/icons.sprite.svg b/packages/experiments/static/icons.sprite.svg index 3c2de54fa..dd1ccff85 100644 --- a/packages/experiments/static/icons.sprite.svg +++ b/packages/experiments/static/icons.sprite.svg @@ -119,7 +119,7 @@ - + @@ -470,7 +470,7 @@ - +