From 37a3f7bf069987b39cae9fc17630a1adf03d721c Mon Sep 17 00:00:00 2001 From: yuetloo Date: Thu, 18 Apr 2024 16:57:55 -0400 Subject: [PATCH 01/11] make static app not to query subgraph --- vue-app/src/App.vue | 139 ++----------------------- vue-app/src/api/claims.ts | 22 ++-- vue-app/src/api/core.ts | 2 + vue-app/src/api/leaderboard.ts | 4 +- vue-app/src/api/maci-factory.ts | 11 +- vue-app/src/api/round.ts | 15 ++- vue-app/src/api/rounds.ts | 18 ++-- vue-app/src/components.d.ts | 2 + vue-app/src/components/ActiveApp.vue | 139 +++++++++++++++++++++++++ vue-app/src/components/ClaimButton.vue | 24 ++--- vue-app/src/components/StaticApp.vue | 43 ++++++++ vue-app/src/stores/app.ts | 38 ++++++- vue-app/src/views/RoundList.vue | 2 +- 13 files changed, 288 insertions(+), 171 deletions(-) create mode 100644 vue-app/src/components/ActiveApp.vue create mode 100644 vue-app/src/components/StaticApp.vue diff --git a/vue-app/src/App.vue b/vue-app/src/App.vue index 019d15523..1d0d676b7 100644 --- a/vue-app/src/App.vue +++ b/vue-app/src/App.vue @@ -4,25 +4,12 @@
- -
+
-
- - -
-
- -
+ +
@@ -32,33 +19,23 @@ diff --git a/vue-app/src/locales/cn.json b/vue-app/src/locales/cn.json index dbc945f34..2a15e8262 100644 --- a/vue-app/src/locales/cn.json +++ b/vue-app/src/locales/cn.json @@ -719,6 +719,7 @@ "tooltip2": "{chain} 链上钱包余额", "h2_3": "项目", "div1": "正在审核", + "withdraw_button": "取回 {contribution} {tokenSymbol}", "btn2_1": "预览", "btn2_2": "查看", "div2": "您尚未提交任何项目" @@ -891,7 +892,6 @@ "div8": "匹配池", "div9": "剩余的将会加入匹配池", "div10": "平均分配 {contribution} {tokenSymbol}", - "button1": "取回 {contribution} {tokenSymbol}", "div11": "不可以", "div11_if2": "重新分配", "div11_if3": "捐赠", diff --git a/vue-app/src/locales/en.json b/vue-app/src/locales/en.json index 3811acfbc..891ec0dd0 100644 --- a/vue-app/src/locales/en.json +++ b/vue-app/src/locales/en.json @@ -719,6 +719,7 @@ "tooltip2": "Balance of wallet on {chain} chain", "h2_3": "Projects", "div1": "Under review", + "withdraw_button": "Withdraw {contribution} {tokenSymbol}", "btn2_1": "Preview", "btn2_2": "View", "div2": "You haven't submitted any projects" @@ -891,7 +892,6 @@ "div8": "Matching pool", "div9": "Remaining funds go to matching pool", "div10": "Split {contribution} {tokenSymbol} evenly", - "button1": "Withdraw {contribution} {tokenSymbol}", "div11": "Can't", "div11_if2": "reallocate", "div11_if3": "contribute", diff --git a/vue-app/src/locales/es.json b/vue-app/src/locales/es.json index ae103f13e..c0226917f 100644 --- a/vue-app/src/locales/es.json +++ b/vue-app/src/locales/es.json @@ -719,6 +719,7 @@ "tooltip2": "Saldo de la billetera en la cadena {chain}", "h2_3": "Proyectos", "div1": "En revisión", + "withdraw_button": "Retirar {contribution} {tokenSymbol}", "btn2_1": "Vista previa", "btn2_2": "Ver", "div2": "No has enviado ningún proyecto" @@ -891,7 +892,6 @@ "div8": "Matching pool", "div9": "Los fondos restantes se destinarán al matching pool", "div10": "Distribuir {contribution} {tokenSymbol} de manera uniforme", - "button1": "Retirar {contribution} {tokenSymbol}", "div11": "No puedes", "div11_if2": "reasignar", "div11_if3": "contribuir", diff --git a/vue-app/src/router/index.ts b/vue-app/src/router/index.ts index e6e415a07..86a75d5b9 100644 --- a/vue-app/src/router/index.ts +++ b/vue-app/src/router/index.ts @@ -1,6 +1,6 @@ import { createRouter, createWebHashHistory } from 'vue-router' import type { RouteRecordRaw } from 'vue-router' -import { isUserRegistrationRequired, isOptimisticRecipientRegistry } from '@/api/core' +import { isUserRegistrationRequired, isOptimisticRecipientRegistry, isActiveApp } from '@/api/core' const Landing = () => import('@/views/Landing.vue') const JoinLanding = () => import('@/views/JoinLanding.vue') @@ -262,7 +262,7 @@ if (isUserRegistrationRequired) { ) } -if (isOptimisticRecipientRegistry) { +if (isOptimisticRecipientRegistry && isActiveApp) { routes.push({ path: '/recipients', name: 'recipients', diff --git a/vue-app/src/stores/app.ts b/vue-app/src/stores/app.ts index 39e39948c..d977e04b7 100644 --- a/vue-app/src/stores/app.ts +++ b/vue-app/src/stores/app.ts @@ -497,6 +497,9 @@ export const useAppStore = defineStore('app', { maxRecipients = currentRoundInfo.maxRecipients } } + if (!this.clrFund) { + this.clrFund = await getClrFundInfo() + } await this.loadMACIFactoryInfo(maxRecipients) }, async loadClrFundInfo() { diff --git a/vue-app/src/views/Profile.vue b/vue-app/src/views/Profile.vue index 768352a67..6dd20f285 100644 --- a/vue-app/src/views/Profile.vue +++ b/vue-app/src/views/Profile.vue @@ -56,6 +56,16 @@
+
+ +

{{ $t('profile.h2_3') }}

@@ -93,13 +103,19 @@ import CopyButton from '@/components/CopyButton.vue' import Loader from '@/components/Loader.vue' import FundsNeededWarning from '@/components/FundsNeededWarning.vue' -import { userRegistryType, UserRegistryType, chain } from '@/api/core' -import { type Project, getProjects } from '@/api/projects' +import { userRegistryType, UserRegistryType, chain, isActiveApp } from '@/api/core' +import { type Project, getProjects, staticDataToProjectInterface } from '@/api/projects' import { isSameAddress } from '@/utils/accounts' import { getTokenLogo } from '@/utils/tokens' import { useAppStore, useUserStore, useRecipientStore, useWalletStore } from '@/stores' import { storeToRefs } from 'pinia' import { useRouter } from 'vue-router' +import { getLeaderboardData } from '@/api/leaderboard' +import { formatAmount } from '@/utils/amounts' + +import WithdrawalModal from '@/components/WithdrawalModal.vue' +import { useModal } from 'vue-final-modal' +import { RoundStatus } from '@/api/round' interface Props { balance: string @@ -111,7 +127,14 @@ const emit = defineEmits(['close']) const router = useRouter() const appStore = useAppStore() -const { hasContributionPhaseEnded, nativeTokenSymbol, currentRound } = storeToRefs(appStore) +const { + hasContributionPhaseEnded, + nativeTokenSymbol, + nativeTokenDecimals, + currentRound, + hasUserContributed, + contribution, +} = storeToRefs(appStore) const userStore = useUserStore() const { currentUser } = storeToRefs(userStore) const recipientStore = useRecipientStore() @@ -139,17 +162,41 @@ const displayAddress = computed(() => { return currentUser.value.ensName ?? currentUser.value.walletAddress }) +const canWithdrawContribution = computed( + () => currentRound.value?.status === RoundStatus.Cancelled && hasUserContributed, +) + +const { open: openWithdrawalModal, close: closeWithdrawalModal } = useModal({ + component: WithdrawalModal, + attrs: { + onClose() { + closeWithdrawalModal() + }, + }, +}) + watch(recipientRegistryAddress, () => loadProjects()) async function loadProjects(): Promise { - if (!recipientRegistryAddress.value) return - isLoading.value = true - const _projects: Project[] = await getProjects( - recipientRegistryAddress.value, - currentRound.value?.startTime.toSeconds(), - currentRound.value?.votingDeadline.toSeconds(), - ) + let _projects: Project[] = [] + + if (isActiveApp) { + if (!recipientRegistryAddress.value) return + _projects = await getProjects( + recipientRegistryAddress.value, + currentRound.value?.startTime.toSeconds(), + currentRound.value?.votingDeadline.toSeconds(), + ) + } else { + const currentRoundAddress = currentRound.value?.fundingRoundAddress || '' + const network = currentRound.value?.network || '' + const data = await getLeaderboardData(currentRoundAddress, network) + if (data) { + _projects = data.projects.map(p => staticDataToProjectInterface(p)) + } + } + const userProjects: Project[] = _projects.filter( ({ address, requester }) => isSameAddress(address, currentUser.value?.walletAddress as string) || From 5f6e2a5ec6b618a1e13fba0e4f7de0742fb7b82f Mon Sep 17 00:00:00 2001 From: yuetloo Date: Mon, 22 Apr 2024 19:28:11 -0400 Subject: [PATCH 05/11] catch error querying round contributors information --- vue-app/src/api/user.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/vue-app/src/api/user.ts b/vue-app/src/api/user.ts index f65069a26..f2ed80189 100644 --- a/vue-app/src/api/user.ts +++ b/vue-app/src/api/user.ts @@ -54,8 +54,12 @@ export async function isVerifiedUser(userRegistryAddress: string, walletAddress: export async function isRegisteredUser(fundingRoundAddress: string, walletAddress: string): Promise { const round = new Contract(fundingRoundAddress, FundingRound, provider) - const contributor = await round.contributors(walletAddress) - return contributor.isRegistered + try { + const contributor = await round.contributors(walletAddress) + return contributor.isRegistered + } catch { + return false + } } export async function getTokenBalance(tokenAddress: string, walletAddress: string): Promise { From 51ddc9971e3df4107ab33d8a96b95c5a45feca8b Mon Sep 17 00:00:00 2001 From: yuetloo Date: Mon, 22 Apr 2024 20:23:12 -0400 Subject: [PATCH 06/11] remove console log --- vue-app/src/components/ClaimButton.vue | 1 - 1 file changed, 1 deletion(-) diff --git a/vue-app/src/components/ClaimButton.vue b/vue-app/src/components/ClaimButton.vue index bdff5ae11..5db5eba51 100644 --- a/vue-app/src/components/ClaimButton.vue +++ b/vue-app/src/components/ClaimButton.vue @@ -81,7 +81,6 @@ async function checkAllocation() { } if (tally.value) { - console.log('tally.value', tally.value) allocatedAmount.value = await getAllocatedAmount( currentRound.value.fundingRoundAddress, currentRound.value.nativeTokenDecimals, From 2454097009aff2062f544536db2e378ba5b85abc Mon Sep 17 00:00:00 2001 From: yuetloo Date: Mon, 22 Apr 2024 20:25:49 -0400 Subject: [PATCH 07/11] fix error displaying project profile page --- vue-app/src/api/projects.ts | 16 +++++++++++++++ vue-app/src/api/rounds.ts | 3 ++- vue-app/src/components/ProjectListItem.vue | 24 +++++++++++++++------- vue-app/src/views/Profile.vue | 7 ++----- vue-app/src/views/ProjectList.vue | 15 +++++++++++--- vue-app/src/views/RoundList.vue | 3 +-- 6 files changed, 50 insertions(+), 18 deletions(-) diff --git a/vue-app/src/api/projects.ts b/vue-app/src/api/projects.ts index edc75abf4..2795ad82e 100644 --- a/vue-app/src/api/projects.ts +++ b/vue-app/src/api/projects.ts @@ -291,3 +291,19 @@ export function staticDataToProjectInterface(project: any): Project { isLocked: false, } } + +/** + * Get the list of projects for a static round + * @param roundAddress The funding round contract address + * @param network The network + * @returns Array of projects + */ +export async function getProjectsForStaticRound(roundAddress: string, network: string): Promise { + const data = await getLeaderboardData(roundAddress, network) + if (!data) { + return [] + } + + const projects = data.projects.map(staticDataToProjectInterface) + return projects +} diff --git a/vue-app/src/api/rounds.ts b/vue-app/src/api/rounds.ts index e83661d47..952523d0c 100644 --- a/vue-app/src/api/rounds.ts +++ b/vue-app/src/api/rounds.ts @@ -1,5 +1,6 @@ import sdk from '@/graphql/sdk' import extraRounds from '@/rounds/rounds.json' +import { getNumber } from 'ethers' import { chain, voidedRounds, isActiveApp, clrfundContractAddress } from './core' export interface Round { @@ -36,7 +37,7 @@ export async function getRounds(): Promise { } const rounds: Round[] = extraRounds.map(({ address, network, startTime, votingDeadline }, index): Round => { - return { index, address, network, hasLeaderboard: true, startTime, votingDeadline } + return { index, address, network, hasLeaderboard: true, startTime: getNumber(startTime), votingDeadline } }) const leaderboardRounds = new Set(rounds.map(r => toRoundId({ network: r.network || '', address: r.address }))) diff --git a/vue-app/src/components/ProjectListItem.vue b/vue-app/src/components/ProjectListItem.vue index 6b7435811..009502493 100644 --- a/vue-app/src/components/ProjectListItem.vue +++ b/vue-app/src/components/ProjectListItem.vue @@ -36,10 +36,12 @@ import { markdown } from '@/utils/markdown' import { useRoute, type RouteLocationRaw } from 'vue-router' import { useAppStore } from '@/stores' import { storeToRefs } from 'pinia' +import { isActiveApp } from '@/api/core' const route = useRoute() const appStore = useAppStore() -const { isRoundContributionPhase, canUserReallocate, currentRoundAddress, categoryLocaleKey } = storeToRefs(appStore) +const { isRoundContributionPhase, canUserReallocate, currentRoundAddress, categoryLocaleKey, currentRound } = + storeToRefs(appStore) interface Props { project: Project roundAddress: string @@ -81,12 +83,20 @@ const shouldShowCartInput = computed(() => { }) const projectRoute = computed(() => { - return route.name === 'round' - ? { - name: 'round-project', - params: { address: props.roundAddress, id: props.project.id }, - } - : { name: 'project', params: { id: props.project.id } } + if (isActiveApp) { + return route.name === 'round' + ? { + name: 'round-project', + params: { address: props.roundAddress, id: props.project.id }, + } + : { name: 'project', params: { id: props.project.id } } + } else { + const network = currentRound.value?.network || '' + return { + name: 'leaderboard-project', + params: { address: props.roundAddress, network, id: props.project.id }, + } + } }) diff --git a/vue-app/src/views/Profile.vue b/vue-app/src/views/Profile.vue index 6dd20f285..b4c6612e9 100644 --- a/vue-app/src/views/Profile.vue +++ b/vue-app/src/views/Profile.vue @@ -104,7 +104,7 @@ import Loader from '@/components/Loader.vue' import FundsNeededWarning from '@/components/FundsNeededWarning.vue' import { userRegistryType, UserRegistryType, chain, isActiveApp } from '@/api/core' -import { type Project, getProjects, staticDataToProjectInterface } from '@/api/projects' +import { type Project, getProjects, getProjectsForStaticRound } from '@/api/projects' import { isSameAddress } from '@/utils/accounts' import { getTokenLogo } from '@/utils/tokens' import { useAppStore, useUserStore, useRecipientStore, useWalletStore } from '@/stores' @@ -191,10 +191,7 @@ async function loadProjects(): Promise { } else { const currentRoundAddress = currentRound.value?.fundingRoundAddress || '' const network = currentRound.value?.network || '' - const data = await getLeaderboardData(currentRoundAddress, network) - if (data) { - _projects = data.projects.map(p => staticDataToProjectInterface(p)) - } + _projects = await getProjectsForStaticRound(currentRoundAddress, network) } const userProjects: Project[] = _projects.filter( diff --git a/vue-app/src/views/ProjectList.vue b/vue-app/src/views/ProjectList.vue index d6345fb3b..2bf0fc69f 100644 --- a/vue-app/src/views/ProjectList.vue +++ b/vue-app/src/views/ProjectList.vue @@ -60,7 +60,7 @@ import { ref, computed, onMounted } from 'vue' import { getCurrentRound, getRoundInfo } from '@/api/round' -import { type Project, getProjects, getRecipientRegistryAddress } from '@/api/projects' +import { type Project, getProjects, getRecipientRegistryAddress, getProjectsForStaticRound } from '@/api/projects' import CallToActionCard from '@/components/CallToActionCard.vue' import ProjectListItem from '@/components/ProjectListItem.vue' @@ -70,6 +70,7 @@ import { useRoute } from 'vue-router' import { useAppStore, useUserStore } from '@/stores' import { storeToRefs } from 'pinia' import { DateTime } from 'luxon' +import { isActiveApp } from '@/api/core' type ProjectRoundInfo = { recipientRegistryAddress: string @@ -119,8 +120,16 @@ onMounted(async () => { try { roundAddress.value = (route.params.address as string) || currentRoundAddress.value || (await getCurrentRound()) || '' - const round = await loadProjectRoundInfo(roundAddress.value) - await loadProjects(round) + if (isActiveApp) { + const round = await loadProjectRoundInfo(roundAddress.value) + await loadProjects(round) + } else { + await appStore.loadStaticClrFundInfo() + const network = currentRound.value?.network || '' + const visibleProjects = await getProjectsForStaticRound(roundAddress.value, network) + shuffleArray(visibleProjects) + projects.value = visibleProjects + } } catch (err) { /* eslint-disable-next-line no-console */ console.error('Error loading projects', err) diff --git a/vue-app/src/views/RoundList.vue b/vue-app/src/views/RoundList.vue index 774fb753a..eb1ac6273 100644 --- a/vue-app/src/views/RoundList.vue +++ b/vue-app/src/views/RoundList.vue @@ -41,12 +41,11 @@ import { onMounted, ref } from 'vue' import { type Round, getRounds } from '@/api/rounds' import Links from '@/components/Links.vue' import { DateTime } from 'luxon' -import { clrfundContractAddress } from '@/api/core' const rounds = ref([]) onMounted(async () => { - rounds.value = await getRounds(clrfundContractAddress) + rounds.value = await getRounds() }) From 55c95bced1caf072f4ceae1d6e99ad796a76f557 Mon Sep 17 00:00:00 2001 From: yuetloo Date: Mon, 22 Apr 2024 21:07:03 -0400 Subject: [PATCH 08/11] code refactoring --- vue-app/src/views/ProjectList.vue | 38 +++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/vue-app/src/views/ProjectList.vue b/vue-app/src/views/ProjectList.vue index 2bf0fc69f..8bc21b7c5 100644 --- a/vue-app/src/views/ProjectList.vue +++ b/vue-app/src/views/ProjectList.vue @@ -71,11 +71,14 @@ import { useAppStore, useUserStore } from '@/stores' import { storeToRefs } from 'pinia' import { DateTime } from 'luxon' import { isActiveApp } from '@/api/core' +import { getSecondsFromNow } from '@/utils/dates' type ProjectRoundInfo = { + fundingRoundAddress: string recipientRegistryAddress: string startTime: number votingDeadline: number + network: string } const SHUFFLE_RANDOM_SEED = Math.random() @@ -120,16 +123,11 @@ onMounted(async () => { try { roundAddress.value = (route.params.address as string) || currentRoundAddress.value || (await getCurrentRound()) || '' - if (isActiveApp) { - const round = await loadProjectRoundInfo(roundAddress.value) - await loadProjects(round) - } else { - await appStore.loadStaticClrFundInfo() - const network = currentRound.value?.network || '' - const visibleProjects = await getProjectsForStaticRound(roundAddress.value, network) - shuffleArray(visibleProjects) - projects.value = visibleProjects - } + + const round = isActiveApp + ? await loadProjectRoundInfo(roundAddress.value) + : await loadStaticRoundInfo(roundAddress.value) + await loadProjects(round) } catch (err) { /* eslint-disable-next-line no-console */ console.error('Error loading projects', err) @@ -157,11 +155,27 @@ async function loadProjectRoundInfo(roundAddress: string): Promise { + await appStore.loadClrFundInfo() + const network = currentRound.value?.network || '' + const recipientRegistryAddress = currentRound.value?.recipientRegistryAddress || '' + const startTime = getSecondsFromNow(currentRound.value?.startTime || DateTime.now()) + const votingDeadline = getSecondsFromNow(currentRound.value?.votingDeadline || DateTime.now()) + return { recipientRegistryAddress, startTime, votingDeadline, fundingRoundAddress: roundAddress, network } } async function loadProjects(round: ProjectRoundInfo) { - const _projects = await getProjects(round.recipientRegistryAddress, round.startTime, round.votingDeadline) + const _projects = isActiveApp + ? await getProjects(round.recipientRegistryAddress, round.startTime, round.votingDeadline) + : await getProjectsForStaticRound(roundAddress.value, round.network) const visibleProjects = _projects.filter(project => { return !project.isHidden && !project.isLocked }) From 8489b006c08cad675e7b128ff2a1db26dc392a38 Mon Sep 17 00:00:00 2001 From: yuetloo Date: Mon, 22 Apr 2024 23:14:20 -0400 Subject: [PATCH 09/11] fix filename case issue --- vue-app/src/api/leaderboard.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vue-app/src/api/leaderboard.ts b/vue-app/src/api/leaderboard.ts index cc10e5615..a09b0d0d6 100644 --- a/vue-app/src/api/leaderboard.ts +++ b/vue-app/src/api/leaderboard.ts @@ -14,5 +14,5 @@ export async function getLeaderboardData(roundAddress: string, network: string) return r.address.toLowerCase() === lowercaseRoundAddress && r.network.toLowerCase() === lowercaseNetwork }) - return found ? import(`../rounds/${lowercaseNetwork}/${lowercaseRoundAddress}.json`) : null + return found ? import(`../rounds/${found.network}/${found.address}.json`) : null } From ec6f0d6e2e275ec52ce050e35b47f12eb7428248 Mon Sep 17 00:00:00 2001 From: yuetloo Date: Mon, 22 Apr 2024 23:41:20 -0400 Subject: [PATCH 10/11] fix incorrect round index --- vue-app/src/api/rounds.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vue-app/src/api/rounds.ts b/vue-app/src/api/rounds.ts index 952523d0c..a4e7d9ecd 100644 --- a/vue-app/src/api/rounds.ts +++ b/vue-app/src/api/rounds.ts @@ -62,11 +62,12 @@ export async function getRounds(): Promise { } } + const lastIndex = rounds.length - 1 return rounds .sort((a, b) => b.startTime - a.startTime) .map((r, index) => { return { - index, + index: lastIndex - index, address: r.address, hasLeaderboard: r.hasLeaderboard, startTime: r.startTime, From 716ccd305eb8e0df709a166605955743c380368d Mon Sep 17 00:00:00 2001 From: yuetloo Date: Tue, 23 Apr 2024 15:08:25 -0400 Subject: [PATCH 11/11] remove redirect to leaderboard page --- vue-app/src/components/StaticApp.vue | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/vue-app/src/components/StaticApp.vue b/vue-app/src/components/StaticApp.vue index 9567e0cce..68accedfb 100644 --- a/vue-app/src/components/StaticApp.vue +++ b/vue-app/src/components/StaticApp.vue @@ -28,16 +28,6 @@ const { user: walletUser } = storeToRefs(wallet) onMounted(async () => { await appStore.loadStaticClrFundInfo() appStore.isAppReady = true - - if (currentRound.value) { - router.push({ - name: 'leaderboard', - params: { - network: currentRound.value.network, - address: currentRound.value.fundingRoundAddress, - }, - }) - } }) watch(walletUser, async () => {