Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
carina-akaia committed Dec 29, 2024
1 parent efe3d70 commit 83a4bb9
Show file tree
Hide file tree
Showing 21 changed files with 169 additions and 74 deletions.
4 changes: 2 additions & 2 deletions src/common/api/indexer/hooks.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { isAccountId, isEthereumAddress } from "@/common/lib";
import { ByAccountId, ByListId, type ConditionalExecution } from "@/common/types";
import { ByAccountId, ByListId, type ConditionalActivation } from "@/common/types";

import * as generatedClient from "./internal/client.generated";
import { INDEXER_CLIENT_CONFIG } from "./internal/config";
Expand Down Expand Up @@ -42,7 +42,7 @@ export const useAccounts = (params?: generatedClient.V1AccountsRetrieveParams) =
/**
* https://test-dev.potlock.io/api/schema/swagger-ui/#/v1/v1_accounts_retrieve_2
*/
export const useAccount = ({ accountId, enabled = true }: ByAccountId & ConditionalExecution) => {
export const useAccount = ({ accountId, enabled = true }: ByAccountId & ConditionalActivation) => {
const queryResult = generatedClient.useV1AccountsRetrieve2(accountId, {
...INDEXER_CLIENT_CONFIG,
swr: { enabled: enabled && isAccountId(accountId) && !isEthereumAddress(accountId) },
Expand Down
10 changes: 5 additions & 5 deletions src/common/contracts/core/voting/hooks.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import useSWR from "swr";

import type { ByPotId } from "@/common/api/indexer";
import { ByAccountId, type ConditionalExecution } from "@/common/types";
import { ByAccountId, type ConditionalActivation } from "@/common/types";

import { AccountId, ElectionId } from "./interfaces";
import { votingContractClient } from "./singleton.client";
Expand All @@ -10,10 +10,10 @@ export interface ByElectionId {
electionId: ElectionId;
}

type BasicElectionQueryKey = ByElectionId & ConditionalExecution;
type BasicElectionQueryKey = ByElectionId & ConditionalActivation;

export const useElections = () =>
useSWR(["get_elections"], () => votingContractClient.get_elections({}));
export const useElections = ({ enabled = true }: ConditionalActivation | undefined = {}) =>
useSWR(["get_elections"], () => (!enabled ? undefined : votingContractClient.get_elections({})));

export const useActiveElections = () =>
useSWR(["get_active_elections"], () => votingContractClient.get_active_elections());
Expand Down Expand Up @@ -100,7 +100,7 @@ export const useUniqueVoters = ({ electionId, enabled = true }: BasicElectionQue
!enabled ? undefined : votingContractClient.get_unique_voters({ election_id }),
);

export const usePotElections = ({ potId }: ByPotId) => {
export const usePotElections = ({ potId, enabled = true }: ByPotId & ConditionalActivation) => {
const { data: elections, isLoading } = useElections();

return {
Expand Down
2 changes: 1 addition & 1 deletion src/common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ export type U128String = string;

export type ClientConfig = { swr?: SWRConfiguration };

export interface ConditionalExecution {
export interface ConditionalActivation {
enabled?: boolean;
}

Expand Down
4 changes: 2 additions & 2 deletions src/entities/_shared/account/hooks/social-profile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useEffect, useMemo, useState } from "react";

import { useRegistration } from "@/common/_deprecated/useRegistration";
import { NEARSocialUserProfile, getSocialProfile } from "@/common/contracts/social";
import type { ByAccountId, ConditionalExecution } from "@/common/types";
import type { ByAccountId, ConditionalActivation } from "@/common/types";

import {
ACCOUNT_PROFILE_COVER_IMAGE_PLACEHOLDER_SRC,
Expand All @@ -14,7 +14,7 @@ import {
export const useAccountSocialProfile = ({
accountId,
enabled = true,
}: ByAccountId & ConditionalExecution) => {
}: ByAccountId & ConditionalActivation) => {
const [profile, setProfile] = useState<NEARSocialUserProfile | undefined>(undefined);
const [isReady, setProfileReady] = useState(false);

Expand Down
10 changes: 7 additions & 3 deletions src/entities/pot/components/PotTimeline.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,17 +68,21 @@ const Loader = styled.div`
`;

export type PotTimelineProps = ByPotId & {
hasVoting?: boolean;
hasProportionalFundingMechanism?: boolean;

classNames?: {
root?: string;
};
};

export const PotTimeline: React.FC<PotTimelineProps> = ({ potId, classNames, hasVoting }) => {
export const PotTimeline: React.FC<PotTimelineProps> = ({
potId,
classNames,
hasProportionalFundingMechanism,
}) => {
const [isMobileMenuActive, setIsMobileMenuActive] = useState(false);
const toggleMobileMenu = useCallback(() => setIsMobileMenuActive((isActive) => !isActive), []);
const lifecycle = usePotLifecycle({ potId, hasVoting });
const lifecycle = usePotLifecycle({ potId, hasProportionalFundingMechanism });

const showActiveState =
(lifecycle.currentStage === undefined ? 0 : lifecycle.stages.indexOf(lifecycle.currentStage)) *
Expand Down
5 changes: 4 additions & 1 deletion src/entities/pot/hooks/feature-flags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ export const usePotFeatureFlags = ({ potId }: ByPotId) => {
);

return useMemo(
() => ({ isPotExtensionConfigLoading, hasVoting: (elections?.length ?? 0) > 0 }),
() => ({
isPotExtensionConfigLoading,
hasProportionalFundingMechanism: (elections?.length ?? 0) > 0,
}),
[isPotExtensionConfigLoading, elections?.length],
);
};
11 changes: 7 additions & 4 deletions src/entities/pot/hooks/lifecycle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,12 @@ import { PotLifecycleStageTagEnum } from "../types";
*/
const getDateTime = (date: string) => new Date(date).getTime();

export type PotLifecycleCalculationInputs = ByPotId & { hasVoting?: boolean };
export type PotLifecycleCalculationInputs = ByPotId & { hasProportionalFundingMechanism?: boolean };

export const usePotLifecycle = ({ potId, hasVoting }: PotLifecycleCalculationInputs) => {
export const usePotLifecycle = ({
potId,
hasProportionalFundingMechanism,
}: PotLifecycleCalculationInputs) => {
const { data: pot } = indexer.usePot({ potId });

const date = new Date();
Expand Down Expand Up @@ -44,7 +47,7 @@ export const usePotLifecycle = ({ potId, hasVoting }: PotLifecycleCalculationInp

{
tag: PotLifecycleStageTagEnum.Matching,
label: `${hasVoting ? "Voting" : "Matching"} round`,
label: `${hasProportionalFundingMechanism ? "Voting" : "Matching"} round`,
daysLeft: public_round_end_ms,
started: now >= public_round_start_ms,
completed: now > public_round_end_ms,
Expand Down Expand Up @@ -79,7 +82,7 @@ export const usePotLifecycle = ({ potId, hasVoting }: PotLifecycleCalculationInp
},
];
} else return [];
}, [hasVoting, now, pot]);
}, [hasProportionalFundingMechanism, now, pot]);

const currentStage = useMemo(
() => stages.find((stage) => stage.started && !stage.completed),
Expand Down
6 changes: 3 additions & 3 deletions src/entities/pot/hooks/tags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { usePotFeatureFlags } from "./feature-flags";

export const usePotTags = ({ potId }: ByPotId) => {
const { data: pot } = indexer.usePot({ potId });
const { hasVoting } = usePotFeatureFlags({ potId });
const { hasProportionalFundingMechanism } = usePotFeatureFlags({ potId });

return useMemo(() => {
if (pot) {
Expand Down Expand Up @@ -54,7 +54,7 @@ export const usePotTags = ({ potId }: ByPotId) => {
backgroundColor: "#F7FDE8",
borderColor: "#9ADD33",
textColor: "#192C07",
text: `${daysUntil(publicRoundEndMs)} left to ${hasVoting ? "vote" : "donate"}`,
text: `${daysUntil(publicRoundEndMs)} left to ${hasProportionalFundingMechanism ? "vote" : "donate"}`,
preElementsProps: { colorOuter: "#D7F5A1", colorInner: "#9ADD33", animate: true },
visibility: publicRoundOpen,
},
Expand Down Expand Up @@ -96,5 +96,5 @@ export const usePotTags = ({ potId }: ByPotId) => {
},
];
} else return [];
}, [hasVoting, pot]);
}, [hasProportionalFundingMechanism, pot]);
};
Original file line number Diff line number Diff line change
Expand Up @@ -8,30 +8,30 @@ import { LabeledIcon } from "@/common/ui/components";
import { cn } from "@/common/ui/utils";
import { AccountHandle, AccountProfilePicture } from "@/entities/_shared/account";
import { TokenIcon } from "@/entities/_shared/token";
import { useVotingRoundResults } from "@/entities/voting-round";

export type PotVotingLeaderboardProps = ByPotId & {};
import { useVotingRoundResults } from "../hooks/results";

export const PotVotingLeaderboard: React.FC<PotVotingLeaderboardProps> = ({ potId }) => {
export type VotingRoundLeaderboardProps = ByPotId & {};

export const VotingRoundLeaderboard: React.FC<VotingRoundLeaderboardProps> = ({ potId }) => {
const votingRoundResults = useVotingRoundResults({ potId });

const leadingPositionAccountIds = useMemo(
() =>
values(votingRoundResults?.candidates ?? {})
values(votingRoundResults?.winners ?? {})
.sort(
(candidateA, candidateB) => candidateB.accumulatedWeight - candidateA.accumulatedWeight,
)
.slice(0, 3)
.map(({ accountId }) => accountId),

[votingRoundResults?.candidates],
[votingRoundResults?.winners],
);

return votingRoundResults === undefined ? null : (
<div className="md:max-w-126.5 flex w-full flex-col gap-3 rounded-3xl bg-neutral-50 p-3">
{leadingPositionAccountIds.map((accountId, index) => {
const { accumulatedWeight, estimatedPayoutAmount } =
votingRoundResults.candidates[accountId];
const { accumulatedWeight, estimatedPayoutAmount } = votingRoundResults.winners[accountId];

return (
<div
Expand Down
50 changes: 50 additions & 0 deletions src/entities/voting-round/components/WinnerList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { useWindowSize } from "@uidotdev/usehooks";

import { ScrollArea } from "@/common/ui/components";

import type { VotingRoundWinner } from "../types";

export type VotingRoundWinnerListProps = {
data: VotingRoundWinner[];
};

// TODO: Use VirtualScroll for better performance
export const VotingRoundWinnerList: React.FC<VotingRoundWinnerListProps> = ({ data }) => {
const { height: windowHeight } = useWindowSize();

return (
<section className="flex w-full flex-col">
<div className="flex justify-between bg-neutral-50 text-xs text-neutral-500">
<div className="mr-a inline-flex h-10 items-center justify-start gap-2 px-4 py-2">
<span className="font-600 shrink grow basis-0 uppercase leading-none">{"Rank"}</span>
</div>

<div className="mr-a inline-flex h-10 items-center justify-start gap-2 px-4 py-2">
<span className="font-600 shrink grow basis-0 uppercase leading-none">{"Projects"}</span>
</div>

<div className="hidden md:flex">
<div className="flex h-10 w-24 items-center px-4 py-2">
<span className="font-600 shrink grow basis-0 text-right uppercase leading-none">
{"Votes"}
</span>
</div>

<div className="flex h-10 w-24 items-center px-4 py-2">
<span className="font-600 shrink grow basis-0 text-center uppercase leading-none">
{"Pool Allocation"}
</span>
</div>
</div>
</div>

<ScrollArea style={{ height: (windowHeight ?? 820) - 320 }}>
<div className="flex flex-col gap-2 pb-8 pt-2">
{/* {data.map((winner) => (
<VotingRoundWinnerListItem key={winner.accountId} data={winner} />
))} */}
</div>
</ScrollArea>
</section>
);
};
4 changes: 2 additions & 2 deletions src/entities/voting-round/hooks/results.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import { useVotingRound } from "./rounds";

export const useVotingRoundResults = ({ potId }: VotingRoundKey) => {
const { data: pot } = indexer.usePot({ potId });
const { hasVoting } = usePotFeatureFlags({ potId });
const votingRound = useVotingRound({ potId });
const { hasProportionalFundingMechanism } = usePotFeatureFlags({ potId });
const votingRound = useVotingRound({ enabled: hasProportionalFundingMechanism, potId });

const { data: votes } = votingContractHooks.useElectionVotes({
enabled: votingRound !== undefined,
Expand Down
8 changes: 6 additions & 2 deletions src/entities/voting-round/hooks/rounds.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import { useMemo } from "react";

import { votingContractHooks } from "@/common/contracts/core/voting";
import type { ConditionalActivation } from "@/common/types";

import type { VotingRound, VotingRoundKey } from "../types";

// TODO: Figure out a way to know exactly which ONE election to pick ( Pots V2 milestone )
export const useVotingRound = ({ potId }: VotingRoundKey): VotingRound | undefined => {
const { elections } = votingContractHooks.usePotElections({ potId });
export const useVotingRound = ({
potId,
enabled = true,
}: VotingRoundKey & ConditionalActivation): VotingRound | undefined => {
const { elections } = votingContractHooks.usePotElections({ enabled, potId });

return useMemo(() => {
const election = elections?.at(0);
Expand Down
1 change: 1 addition & 0 deletions src/entities/voting-round/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export * from "./types";
export * from "./components/badges";
export * from "./components/CandidateList";
export * from "./components/HistoryEntry";
export * from "./components/Leaderboard";
export * from "./components/RuleList";
export * from "./components/VoteWeightBreakdown";

Expand Down
20 changes: 10 additions & 10 deletions src/entities/voting-round/model/round-results.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,21 @@ import { PRICES_REQUEST_CONFIG, intearPricesClient } from "@/common/api/intear-p
import { is_human } from "@/common/contracts/core/sybil";
import { AccountId, type ElectionId, Vote } from "@/common/contracts/core/voting";
import { ftClient } from "@/common/contracts/tokens/ft";
import type { ByAccountId } from "@/common/types";

import { VOTING_ROUND_CONFIG_MPDAO } from "./hardcoded";
import type { VoterProfile, VotingRoundCandidateResult } from "../types";
import type { VoterProfile, VotingRoundWinner } from "../types";

type VotingRoundCandidateIntermediateResult = {
accountId: string;
type VotingRoundWinnerIntermediateData = ByAccountId & {
accumulatedWeight: Big;
};

type VotingRoundCandidateResultRegistry = {
candidates: Record<AccountId, VotingRoundCandidateResult>;
type VotingRoundWinnerRegistry = {
winners: Record<AccountId, VotingRoundWinner>;
};

interface VotingRoundResultsState {
resultsCache: Record<ElectionId, VotingRoundCandidateResultRegistry & { totalVoteCount: number }>;
resultsCache: Record<ElectionId, VotingRoundWinnerRegistry & { totalVoteCount: number }>;

updateResults: (params: {
electionId: number;
Expand Down Expand Up @@ -155,7 +155,7 @@ export const useRoundResultsStore = create<VotingRoundResultsState>()(

// First pass: Calculate accumulated weights for each candidate
const intermediateResults = Object.entries(votesByCandidate).reduce<
Record<AccountId, VotingRoundCandidateIntermediateResult>
Record<AccountId, VotingRoundWinnerIntermediateData>
>((acc, [candidateAccountId, candidateVotes]) => {
// Calculate total weight for this candidate
const accumulatedWeight = candidateVotes.reduce((sum, vote) => {
Expand All @@ -172,15 +172,15 @@ export const useRoundResultsStore = create<VotingRoundResultsState>()(
return acc;
}, {});

// Calculate total accumulated weight across all candidates
// Calculate total accumulated weight across all winners
const totalAccumulatedWeight = Object.values(intermediateResults).reduce(
(sum, result) => sum.add(result.accumulatedWeight),
Big(0),
);

// Second pass: Calculate estimated payouts using total accumulated weight
const candidateResults = Object.entries(intermediateResults).reduce<
VotingRoundCandidateResultRegistry["candidates"]
VotingRoundWinnerRegistry["winners"]
>((acc, [candidateAccountId, result]) => {
acc[candidateAccountId as AccountId] = {
...result,
Expand All @@ -200,7 +200,7 @@ export const useRoundResultsStore = create<VotingRoundResultsState>()(

[electionId]: {
totalVoteCount: votes.length,
candidates: candidateResults,
winners: candidateResults,
},
},
}));
Expand Down
2 changes: 1 addition & 1 deletion src/entities/voting-round/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export type VotingRound = ByElectionId & { election: Election };

export type VotingRoundKey = ByPotId;

export type VotingRoundCandidateResult = ByAccountId & {
export type VotingRoundWinner = ByAccountId & {
accumulatedWeight: number;
estimatedPayoutAmount: number;
};
8 changes: 4 additions & 4 deletions src/features/pot-application/hooks/clearance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ import { POT_APPLICATION_REQUIREMENTS_MPDAO } from "../constants";
*/
export const usePotApplicationUserClearance = ({
potId,
hasVoting,
}: ByPotId & { hasVoting?: boolean }): ClearanceCheckResult => {
hasProportionalFundingMechanism,
}: ByPotId & { hasProportionalFundingMechanism?: boolean }): ClearanceCheckResult => {
const { staking } = POT_APPLICATION_REQUIREMENTS_MPDAO;
const { data: pot } = indexer.usePot({ potId });

Expand All @@ -45,7 +45,7 @@ export const usePotApplicationUserClearance = ({
]
: []),

...(hasVoting
...(hasProportionalFundingMechanism
? [
{
title: `An equivalent of ${staking.minAmountUsd} USD staked in NEAR on ${staking.platformName}`,
Expand Down Expand Up @@ -73,7 +73,7 @@ export const usePotApplicationUserClearance = ({
error: null,
};
}, [
hasVoting,
hasProportionalFundingMechanism,
isAccountInfoLoading,
isVerifiedPublicGoodsProvider,
pot?.sybil_wrapper_provider,
Expand Down
Loading

0 comments on commit 83a4bb9

Please sign in to comment.