From 934dfed97fd2dd2fd2666c4606dc0b12fa958c58 Mon Sep 17 00:00:00 2001 From: M-RB3 Date: Thu, 30 May 2024 14:30:26 +0400 Subject: [PATCH] finish new payout processing --- src/components/Inputs/Text/Text.tsx | 6 +- src/modals/ModalDonation/Banners/Alert.tsx | 4 +- src/pages/Pot/components/Header/Header.tsx | 14 +- .../components/PayoutsModal/PayoutsModal.tsx | 147 ++++++++++++++++++ .../Pot/components/PayoutsModal/styles.ts | 58 +++++++ src/types.ts | 2 +- 6 files changed, 219 insertions(+), 12 deletions(-) create mode 100644 src/pages/Pot/components/PayoutsModal/PayoutsModal.tsx create mode 100644 src/pages/Pot/components/PayoutsModal/styles.ts diff --git a/src/components/Inputs/Text/Text.tsx b/src/components/Inputs/Text/Text.tsx index 5a63ecdc..10774e41 100644 --- a/src/components/Inputs/Text/Text.tsx +++ b/src/components/Inputs/Text/Text.tsx @@ -10,6 +10,7 @@ type Props = { onBlur?: (value: any) => void; validate?: () => void; error?: string; + defaultValue?: string; preInputChildren?: any; postInputChildren?: any; disabled?: boolean; @@ -22,7 +23,7 @@ type Props = { const Text = (props: Props) => { const label = props.label ?? ""; const placeholder = props.placeholder ?? ""; - const value = props.value ?? ""; + const value = props.value; const onChange = props.onChange ?? (() => {}); const onBlur = props.onBlur ?? (() => {}); const validate = props.validate ?? (() => {}); @@ -35,8 +36,9 @@ const Text = (props: Props) => { {props.preInputChildren && props.preInputChildren} onChange(value)} onBlur={(value) => { validate(); diff --git a/src/modals/ModalDonation/Banners/Alert.tsx b/src/modals/ModalDonation/Banners/Alert.tsx index 0e384225..d047f41d 100644 --- a/src/modals/ModalDonation/Banners/Alert.tsx +++ b/src/modals/ModalDonation/Banners/Alert.tsx @@ -1,7 +1,7 @@ import { AlertBanner } from "./styles"; -const Alert = ({ error }: any) => ( - +const Alert = ({ error, style }: { error: string; style?: React.CSSProperties }) => ( +
{ const [flaggedAddresses, setFlaggedAddresses] = useState(null); const [potDetail, setPotDetail] = useState(null); const [allDonations, setAlldonations] = useState(null); + const [payoutsToProcess, setPayoutsToProcess] = useState(null); // set fund mathcing pool success const [fundDonation, setFundDonation] = useState(null); @@ -165,13 +167,7 @@ const Header = () => { const handleSetPayouts = () => { if (allDonations && flaggedAddresses !== null) { calculatePayouts(allDonations, matching_pool_balance, flaggedAddresses).then((calculatedPayouts: any) => { - const payouts = Object.entries(calculatedPayouts) - .map(([projectId, { matchingAmount }]: any) => ({ - project_id: projectId, - amount: matchingAmount, - })) - .filter((payout) => payout.amount !== "0"); - PotSDK.chefSetPayouts(potId, payouts); + setPayoutsToProcess(calculatedPayouts); }); } else { console.log("error fetching donations or flagged addresses"); @@ -293,6 +289,10 @@ const Header = () => { }} /> )} + {/* Admin process Payout */} + {payoutsToProcess && ( + + )} ); }; diff --git a/src/pages/Pot/components/PayoutsModal/PayoutsModal.tsx b/src/pages/Pot/components/PayoutsModal/PayoutsModal.tsx new file mode 100644 index 00000000..259abef7 --- /dev/null +++ b/src/pages/Pot/components/PayoutsModal/PayoutsModal.tsx @@ -0,0 +1,147 @@ +import { Big, useMemo, useState } from "alem"; +import PotSDK from "@app/SDK/pot"; +import Button from "@app/components/Button"; +import Text from "@app/components/Inputs/Text/Text"; +import Alert from "@app/modals/ModalDonation/Banners/Alert"; +import ModalOverlay from "@app/modals/ModalOverlay"; +import { CalculatedPayout } from "@app/types"; +import _address from "@app/utils/_address"; +import { ButtonWrapper, Container, PayoutItem, PayoutsView, Title, Total, ExitIcon } from "./styles"; + +const PayoutsModal = ({ + originalPayouts, + setPayoutsToProcess, + potId, +}: { + originalPayouts: Record; + setPayoutsToProcess: (payouts: null) => void; + potId: string; +}) => { + const [payouts, setPayouts] = useState(originalPayouts); + const [error, setError] = useState(""); + + const calcNear = (amount: string) => Big(amount).div(Big(10).pow(24)).toNumber().toFixed(2); + const calcYoctos = (amount: string) => new Big(amount).mul(new Big(10).pow(24)).toString(); + + const sumAmount = (payouts: any) => + payouts.reduce( + (acc: any, payout: any) => + Big(acc) + .plus(new Big(payout.matchingAmount || payout.amount)) + .toString(), + 0, + ); + + const originalTotalAmountYoctos = useMemo(() => sumAmount(Object.values(originalPayouts)), [originalPayouts]); + + const originalTotalAmount = calcNear(originalTotalAmountYoctos); + + const [payoutsList, totalAmount, remainder] = useMemo(() => { + const payoutsArr = Object.entries(payouts).map(([projectId, { matchingAmount }]: any) => ({ + project_id: projectId, + amount: calcNear(matchingAmount), + })); + + const totalAmountYoctos = sumAmount(Object.values(payouts)); + + const totalAmount = calcNear(totalAmountYoctos); + + const remainderYoctos = Big(originalTotalAmountYoctos).minus(Big(totalAmountYoctos)).toNumber(); + if (remainderYoctos < 0) setError("The payout's total can not be greater than the original amount."); + else setError(""); + const remainder = calcNear(remainderYoctos.toString()); + + return [payoutsArr, totalAmount, remainder, remainderYoctos]; + }, [payouts]); + + const handleChange = (projectId: string, amount: string) => { + setPayouts({ + ...payouts, + [projectId]: { + ...payouts[projectId], + matchingAmount: calcYoctos(amount), + }, + }); + }; + + const handlePayout = () => { + let payoutsArr = Object.entries(payouts) + .map(([projectId, { matchingAmount }]: any) => ({ + project_id: projectId, + amount: matchingAmount, + })) + .filter((payout) => payout.amount !== "0"); + let yoctos = sumAmount(payoutsArr); + + const remainder = Big(originalTotalAmountYoctos).minus(Big(yoctos)); + + payoutsArr[0].amount = Big(payoutsArr[0].amount).plus(remainder).toString(); + + yoctos = sumAmount(payoutsArr); + + console.log("check if the original amount equal to the new one", Big(yoctos).cmp(Big(originalTotalAmountYoctos))); + + PotSDK.chefSetPayouts(potId, payoutsArr); + }; + + return ( + + + + setPayoutsToProcess(null)} + className="close-icon" + viewBox="0 0 14 14" + fill="none" + xmlns="http://www.w3.org/2000/svg" + > + + + + {potId} + +
Total amount
+
+ {totalAmount} / {originalTotalAmount} N +
+ + +
Remainder
+
{remainder} N
+
+ {error && ( + + )} + + + + + {payoutsList.map(({ project_id, amount }) => ( + +
{_address(project_id, 20)}
+ handleChange(project_id, amount)} + defaultValue={amount} + /> +
+ ))} +
+ + + ); +}; + +export default PayoutsModal; diff --git a/src/pages/Pot/components/PayoutsModal/styles.ts b/src/pages/Pot/components/PayoutsModal/styles.ts new file mode 100644 index 00000000..8d89a805 --- /dev/null +++ b/src/pages/Pot/components/PayoutsModal/styles.ts @@ -0,0 +1,58 @@ +import styled from "styled-components"; + +export const Container = styled.div` + display: flex; + flex-direction: column; + gap: 1rem; + position: relative; + height: 80vh; +`; + +export const Title = styled.div` + font-size: 18px; + font-weight: 500; + overflow-wrap: break-word; +`; + +export const PayoutsView = styled.div` + display: flex; + gap: 0.5rem; + flex-direction: column; + max-height: 100%; + overflow-y: scroll; +`; + +export const PayoutItem = styled.div` + display: flex; + gap: 1rem; + align-items: center; + justify-content: space-between; + .id { + width: 172px; + } +`; + +export const Total = styled.div` + display: flex; + gap: 0.5rem; + .original { + color: #656565; + } + span { + font-weight: 500; + color: var(--Primary-600); + } +`; +export const ButtonWrapper = styled.div` + display: flex; + gap: 1rem; +`; + +export const ExitIcon = styled.div` + display: flex; + justify-content: flex-end; + svg { + cursor: pointer; + width: 18px; + } +`; diff --git a/src/types.ts b/src/types.ts index 33286515..e0d58f91 100644 --- a/src/types.ts +++ b/src/types.ts @@ -132,7 +132,7 @@ export type Payout = { export type CalculatedPayout = { project_id: string; - amount: number; + matchingAmount: string; donorCount: number; totalAmount: string; };