fix: clicking on a pending posts correctly links to it (#318)

This commit is contained in:
Vojtech Simetka 2023-04-02 18:02:21 +02:00 committed by GitHub
parent 71930ae827
commit 0486feacf8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 272 additions and 26 deletions

View File

@ -66,6 +66,7 @@ export class Firebase implements Adapter {
})
private subscriptions: Array<() => unknown> = []
private userSubscriptions: Array<() => unknown> = []
private votes = new Map<string, { promote: string[]; demote: string[] }>()
async start() {
const personasQuery = query(collection(db, 'personas'))
@ -340,6 +341,7 @@ export class Firebase implements Adapter {
const { text, images, timestamp, demote, promote, address } = d.data() as PendingPost
const loggedUser = get(profile)
let yourVote: '+' | '-' | undefined = undefined
this.votes.set(d.id, { promote, demote })
if (loggedUser.address && promote.includes(loggedUser.address)) yourVote = '+'
if (loggedUser.address && demote.includes(loggedUser.address)) yourVote = '-'
newPending.push({
@ -363,6 +365,30 @@ export class Firebase implements Adapter {
})
})
// Ensures that votuse and whether the post is yours is updated after user logs in
const subscribeProfileChangePending = profile.subscribe(({ address }) => {
if (address) {
posts.update(({ data }) => {
const personaPostData = data.get(groupId)
if (!personaPostData) return { data }
const pending = personaPostData.pending.map((p) => {
if (p.postId === undefined) return p
const vt = this.votes.get(p.postId)
if (vt === undefined) return p
let yourVote: '+' | '-' | undefined = undefined
if (vt.promote.includes(address)) yourVote = '+'
if (vt.demote.includes(address)) yourVote = '-'
return { ...p, myPost: p.address === address, yourVote }
})
data.set(groupId, { ...personaPostData, pending })
return { data }
})
}
})
const subscribePosts = onSnapshot(postsCollection, (res) => {
const newPostst: Post[] = []
@ -396,13 +422,17 @@ export class Firebase implements Adapter {
})
return () => {
subscribeProfileChangePending()
subscribePending()
subscribePosts()
}
}
async voteOnPost(groupId: string, postId: number, vote: '+' | '-', signer: Signer) {
await signer.signMessage(`This "transaction" votes ${vote === '+' ? 'promote' : 'demote'}`)
const promoteDemote: 'promote' | 'demote' = vote === '+' ? 'promote' : 'demote'
await signer.signMessage(
`By confirming this "transaction" you are casting ${promoteDemote} vote on the post`,
)
const address = await signer.getAddress()
const postData = get(posts).data.get(groupId)?.pending[postId]
@ -410,7 +440,7 @@ export class Firebase implements Adapter {
const postDoc = doc(db, `personas/${groupId}/pending/${postData.postId}`)
updateDoc(postDoc, {
[vote === '+' ? 'promote' : 'demote']: arrayUnion(address),
[promoteDemote]: arrayUnion(address),
})
const { go } = get(tokens)
@ -422,7 +452,7 @@ export class Firebase implements Adapter {
timestamp: Date.now(),
goChange: -VOTE_GO_PRICE,
repChange: 0,
type: vote === '+' ? 'promote' : 'demote',
type: promoteDemote,
personaId: groupId,
})
}

View File

@ -5,6 +5,8 @@ export const ROUTES = {
PERSONA_PENDING: (slug: string | number) => `/persona/${slug}/pending`,
PERSONA_NEW: '/persona/new',
PERSONA_POST: (id: string | number, postId: string | number) => `/persona/${id}/post/${postId}`,
PERSONA_PENDING_POST: (id: string | number, postId: string | number) =>
`/persona/${id}/pending/${postId}`,
PERSONA_DRAFT: (id: string | number) => `/persona/draft/${id}`,
POST_NEW: (slug: string) => `/persona/${slug}/post/new`,
CHATS: '/chat',

View File

@ -15,7 +15,6 @@ interface PostData {
}
export interface PostStore extends Writable<PostData> {
setLoading: (groupId: string, loading: boolean) => void
addPending: (post: Post, groupId: string) => void
addApproved: (post: Post, groupId: string) => void
}
@ -25,16 +24,6 @@ function createPostStore(): PostStore {
return {
...store,
setLoading: (groupId, loading) => {
store.update(({ data }) => {
const personaPostData = data.get(groupId)
const approved = personaPostData?.approved ?? []
const pending = personaPostData?.pending ?? []
data.set(groupId, { loading, approved, pending })
return { data }
})
},
addPending: (post: Post, groupId: string) => {
store.update(({ data }) => {
const personaPostData = data.get(groupId)

View File

@ -19,6 +19,13 @@
import InfoBox from '$lib/components/info-box.svelte'
import Banner from '$lib/components/message-banner.svelte'
import SingleColumn from '$lib/components/single-column.svelte'
import SectionTitle from '$lib/components/section-title.svelte'
import Dropdown from '$lib/components/dropdown.svelte'
import DropdownItem from '$lib/components/dropdown-item.svelte'
import Search from '$lib/components/search.svelte'
import InfoScreen from '$lib/components/info_screen.svelte'
import LearnMore from '$lib/components/learn-more.svelte'
import BorderBox from '$lib/components/border-box.svelte'
import { posts } from '$lib/stores/post'
import { personas } from '$lib/stores/persona'
@ -29,15 +36,8 @@
import adapter from '$lib/adapters'
import { canConnectWallet } from '$lib/services'
import { onDestroy, onMount } from 'svelte'
import SectionTitle from '$lib/components/section-title.svelte'
import Dropdown from '$lib/components/dropdown.svelte'
import DropdownItem from '$lib/components/dropdown-item.svelte'
import Search from '$lib/components/search.svelte'
import { tokens } from '$lib/stores/tokens'
import { VOTE_GO_PRICE } from '$lib/constants'
import InfoScreen from '$lib/components/info_screen.svelte'
import LearnMore from '$lib/components/learn-more.svelte'
import BorderBox from '$lib/components/border-box.svelte'
const groupId = $page.params.id
const persona = $personas.all.get(groupId)
@ -240,7 +240,7 @@
{:else}
<Grid>
{#each personaPosts.pending as post, index}
<Post {post} on:click={() => goto(ROUTES.PERSONA_POST(groupId, index))}>
<Post {post} on:click={() => goto(ROUTES.PERSONA_PENDING_POST(groupId, index))}>
{#if post.yourVote === '+' && $profile.signer !== undefined}
<Button icon={FavoriteFilled} variant="accent" label="You promoted this" />
{:else if post.yourVote === '-' && $profile.signer !== undefined}

View File

@ -0,0 +1,212 @@
<script lang="ts">
import Post from '$lib/components/post.svelte'
import Button from '$lib/components/button.svelte'
import ChatBot from '$lib/components/icons/chat-bot.svelte'
import Wallet from '$lib/components/icons/wallet.svelte'
import ThumbsDown from '$lib/components/icons/thumbs-down.svelte'
import Favorite from '$lib/components/icons/favorite.svelte'
import FavoriteFilled from '$lib/components/icons/favorite-filled.svelte'
import Info from '$lib/components/icons/information.svelte'
import Checkmark from '$lib/components/icons/checkmark.svelte'
import Close from '$lib/components/icons/close.svelte'
import Banner from '$lib/components/message-banner.svelte'
import Header from '$lib/components/header.svelte'
import Container from '$lib/components/container.svelte'
import InfoBox from '$lib/components/info-box.svelte'
import InfoScreen from '$lib/components/info_screen.svelte'
import SingleColumn from '$lib/components/single-column.svelte'
import BorderBox from '$lib/components/border-box.svelte'
import LearnMore from '$lib/components/learn-more.svelte'
import { posts } from '$lib/stores/post'
import { profile } from '$lib/stores/profile'
import type { DraftChat } from '$lib/stores/chat'
import { personas } from '$lib/stores/persona'
import { goto } from '$app/navigation'
import { page } from '$app/stores'
import { ROUTES } from '$lib/routes'
import adapter from '$lib/adapters'
import { canConnectWallet } from '$lib/services'
import ChatScreen from '$lib/components/chat-screen.svelte'
import { VOTE_GO_PRICE } from '$lib/constants'
import { tokens } from '$lib/stores/tokens'
import { onDestroy, onMount } from 'svelte'
type Vote = {
index: number
vote: '+' | '-'
}
let vote: Vote | undefined = undefined
let unsubscribe: () => unknown
onMount(() => {
adapter.subscribePersonaPosts(groupId).then((unsub) => (unsubscribe = unsub))
})
onDestroy(() => {
if (unsubscribe) unsubscribe()
})
const postId = $page.params.postId as unknown as number
const groupId = $page.params.id
let post = $posts.data.get(groupId)?.pending[postId]
$: post = $posts.data.get(groupId)?.pending[postId]
const persona = $personas.all.get(groupId)
let draftChat: DraftChat | undefined = undefined
const startChat = async () => {
if (!persona || !post) return
draftChat = {
persona,
post,
messages: [],
}
}
async function sendMessage(text: string) {
if (!draftChat) return
const chat = {
...draftChat,
messages: [{ timestamp: Date.now(), text, address: $profile.address }],
}
const chatId = await adapter.startChat(chat)
goto(ROUTES.CHAT(chatId))
}
let y: number
export let onBack: () => unknown = () => history.back()
</script>
<svelte:window bind:scrollY={y} />
{#if $tokens.go > 0}
<Banner icon={Info}>{$tokens.go} GO left in this cycle</Banner>
{:else}
<Banner icon={Info} variant="danger">No GO left in this cycle</Banner>
{/if}
{#if post === undefined}
<Container>
<InfoBox>
<div>There is no post with post ID {$page.params.postId}</div>
</InfoBox>
</Container>
{:else if persona === undefined}
<Container>
<InfoBox>
<div>There is no persona with ID {$page.params.id}</div>
</InfoBox>
</Container>
{:else if draftChat !== undefined}
<ChatScreen chat={draftChat} {sendMessage} title="New chat" onBack={() => history.back()} />
{:else if vote !== undefined && $tokens.go >= VOTE_GO_PRICE}
<InfoScreen title={vote.vote === '+' ? 'Promote' : 'Demote'} onBack={() => (vote = undefined)}>
<SingleColumn>
<InfoBox>
<div class="icon">
<Info size={32} />
</div>
<h2>This will use {VOTE_GO_PRICE} GO.</h2>
<p>
You will earn REP if the majority of the community also votes to {vote.vote === '+'
? 'promote'
: 'demote'} this content.
</p>
<p><LearnMore href="/" /></p>
</InfoBox>
<BorderBox
title="Currently available"
amount={$tokens.go.toFixed()}
tokenName="GO"
explanation="Until new cycle begins"
/>
</SingleColumn>
<svelte:fragment slot="buttons">
<Button
label="I agree"
variant="primary"
icon={Checkmark}
on:click={async () => {
if (!vote || !$profile.signer) return
await adapter.voteOnPost(groupId, vote.index, vote.vote, $profile.signer)
vote = undefined
}}
/>
<Button label="Nope" icon={Close} on:click={() => (vote = undefined)} />
</svelte:fragment>
</InfoScreen>
{:else if vote !== undefined}
<InfoScreen title="Not enough token" onBack={() => (vote = undefined)}>
<SingleColumn>
<InfoBox>
<div class="icon">
<Info size={32} />
</div>
<h2>Sorry, you can't vote now.</h2>
<p>
You need {VOTE_GO_PRICE} GO to promote or demote content.
</p>
<p><LearnMore href="/" /></p>
</InfoBox>
<BorderBox
title="Currently available"
amount={$tokens.go.toFixed()}
tokenName="GO"
explanation="Until new cycle begins"
error
/>
</SingleColumn>
<svelte:fragment slot="buttons">
<Button
label="I agree"
variant="primary"
icon={Checkmark}
on:click={() => {
if (!vote || !$profile.signer) return
adapter.voteOnPost(groupId, vote.index, vote.vote, $profile.signer)
vote = undefined
}}
/>
<Button label="Nope" icon={Close} on:click={() => (vote = undefined)} />
</svelte:fragment>
</InfoScreen>
{:else}
<Header title="Post" {onBack} />
<!-- TODO: This is the post page so I'm thinking there shouldn't be an action on the post -->
<Post {post} on:click noHover>
{#if post.yourVote === '+' && $profile.signer !== undefined}
<Button icon={FavoriteFilled} variant="accent" label="You promoted this" />
{:else if post.yourVote === '-' && $profile.signer !== undefined}
<Button icon={ThumbsDown} variant="accent" label="You demoted this" />
{:else}
<Button
variant="secondary"
label="Promote"
icon={Favorite}
disabled={$profile.signer === undefined}
on:click={() => (vote = { index: postId, vote: '+' })}
/>
<Button
variant="secondary"
label="Demote"
icon={ThumbsDown}
disabled={$profile.signer === undefined}
on:click={() => (vote = { index: postId, vote: '-' })}
/>
{/if}
{#if $profile.signer === undefined}
<Button
variant="primary"
icon={Wallet}
on:click={adapter.signIn}
disabled={!canConnectWallet()}
/>
{:else if $profile.signer !== undefined && $profile.address !== post.address}
<Button variant="primary" label="Chat with poster" icon={ChatBot} on:click={startChat} />
{/if}
</Post>
{/if}

View File

@ -17,8 +17,12 @@
import adapter from '$lib/adapters'
import { canConnectWallet } from '$lib/services'
import ChatScreen from '$lib/components/chat-screen.svelte'
import { onDestroy, onMount } from 'svelte'
const post = $posts.data.get($page.params.id)?.approved[$page.params.postId as unknown as number]
const postId = $page.params.postId as unknown as number
const groupId = $page.params.id
let post = $posts.data.get(groupId)?.approved[postId]
$: post = $posts.data.get(groupId)?.approved[postId]
const persona = $personas.all.get($page.params.id)
let draftChat: DraftChat | undefined = undefined
@ -41,6 +45,15 @@
const chatId = await adapter.startChat(chat)
goto(ROUTES.CHAT(chatId))
}
let unsubscribe: () => unknown
onMount(() => {
adapter.subscribePersonaPosts(groupId).then((unsub) => (unsubscribe = unsub))
})
onDestroy(() => {
if (unsubscribe) unsubscribe()
})
let y: number
@ -68,15 +81,15 @@
<!-- TODO: This is the post page so I'm thinking there shouldn't be an action on the post -->
<Post {post} on:click noHover />
<div class="center">
{#if $profile.signer !== undefined}
<Button variant="primary" label="Chat with poster" icon={ChatBot} on:click={startChat} />
{:else}
{#if $profile.signer === undefined}
<Button
variant="primary"
icon={Wallet}
on:click={adapter.signIn}
disabled={!canConnectWallet()}
/>
{:else if $profile.signer !== undefined && $profile.address !== post.address}
<Button variant="primary" label="Chat with poster" icon={ChatBot} on:click={startChat} />
{/if}
</div>
{/if}