diff --git a/src/modals/ModalOverlay.tsx b/src/modals/ModalOverlay.tsx index 48859eaa..6d333be4 100644 --- a/src/modals/ModalOverlay.tsx +++ b/src/modals/ModalOverlay.tsx @@ -7,7 +7,7 @@ type Props = { overlayStyle?: React.CSSProperties; }; -const ModalOverlay = ({ children, onOverlayClick, contentStyle }: Props) => { +const ModalOverlay = ({ children, onOverlayClick, contentStyle, overlayStyle }: Props) => { const ModalOverlay = styled.div` position: fixed; padding: 0 10px; @@ -46,7 +46,7 @@ const ModalOverlay = ({ children, onOverlayClick, contentStyle }: Props) => { `; return ( - + {children} diff --git a/src/pages/Pot/components/PayoutsModal/PayoutsModal.tsx b/src/pages/Pot/components/PayoutsModal/PayoutsModal.tsx index 259abef7..19a71588 100644 --- a/src/pages/Pot/components/PayoutsModal/PayoutsModal.tsx +++ b/src/pages/Pot/components/PayoutsModal/PayoutsModal.tsx @@ -6,7 +6,7 @@ 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"; +import { ButtonWrapper, Container, PayoutItem, PayoutsView, Title, Total, ExitIcon, TableHeader } from "./styles"; const PayoutsModal = ({ originalPayouts, @@ -18,25 +18,41 @@ const PayoutsModal = ({ potId: string; }) => { const [payouts, setPayouts] = useState(originalPayouts); + const [assigendWeights, setAssignedWeights] = useState>({}); + const [qfWeight, setQfWeight] = useState("100"); + const [jdgWeight, setJdgWeight] = useState("0"); 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 sumAmount = (payouts: any) => { + let sum = Big(0); + payouts.forEach((payout: any) => { + sum = sum.plus(Big(payout.matchingAmount || payout.amount)); + }); + return sum.toString(); + }; + // payouts.reduce( + // (acc: any, payout: any) => + // Big(acc) + // .plus(new Big(payout.matchingAmount || payout.amount)) + // .toString(), + // 0, + // ); + + const [originalTotalAmountYoctos, originalPayoutList] = useMemo(() => { + const totalAmount = sumAmount(Object.values(originalPayouts)); + const payoutsArr = Object.entries(originalPayouts).map(([projectId, { matchingAmount }]: any) => ({ + project_id: projectId, + amount: calcNear(matchingAmount), + })); + return [totalAmount, payoutsArr]; + }, [originalPayouts]); const originalTotalAmount = calcNear(originalTotalAmountYoctos); - const [payoutsList, totalAmount, remainder] = useMemo(() => { + const [payoutsList, totalAmount] = useMemo(() => { const payoutsArr = Object.entries(payouts).map(([projectId, { matchingAmount }]: any) => ({ project_id: projectId, amount: calcNear(matchingAmount), @@ -46,12 +62,7 @@ const PayoutsModal = ({ 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]; + return [payoutsArr, totalAmount]; }, [payouts]); const handleChange = (projectId: string, amount: string) => { @@ -64,16 +75,40 @@ const PayoutsModal = ({ }); }; - const handlePayout = () => { - let payoutsArr = Object.entries(payouts) - .map(([projectId, { matchingAmount }]: any) => ({ + // get the final amount and the list of assigned weights calculations + const [finalPayoutList, finalAmountNear, sumAssignedWeightsCalc, remainder] = useMemo(() => { + let finalAmount = Big(0); + let sumAssignedWeightsCalc = 0; + + const payoutList = Object.entries(payouts).map(([projectId, { matchingAmount }]: any) => { + const qfWeightCalc = Big(matchingAmount).mul(parseFloat(qfWeight) / 100); + + const jdWeightCalc = Big(originalTotalAmountYoctos) + .mul((assigendWeights[projectId] || 0) * parseFloat(jdgWeight)) + .div(10e4); + const projectFinalAmount = qfWeightCalc.add(jdWeightCalc); + finalAmount = finalAmount.plus(projectFinalAmount); + + return { project_id: projectId, - amount: matchingAmount, - })) - .filter((payout) => payout.amount !== "0"); + amount: projectFinalAmount.toString(), + }; + }); + + const remainderYoctos = Big(originalTotalAmountYoctos).minus(finalAmount).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 [payoutList, calcNear(finalAmount.toString()), sumAssignedWeightsCalc, remainder]; + }, [assigendWeights, payoutsList, qfWeight, jdgWeight]); + + const handlePayout = () => { + let payoutsArr = finalPayoutList.filter((payout) => payout.amount !== "0"); + let yoctos = sumAmount(payoutsArr); - const remainder = Big(originalTotalAmountYoctos).minus(Big(yoctos)); + const remainder = Big(originalTotalAmountYoctos).minus(yoctos); payoutsArr[0].amount = Big(payoutsArr[0].amount).plus(remainder).toString(); @@ -84,8 +119,12 @@ const PayoutsModal = ({ PotSDK.chefSetPayouts(potId, payoutsArr); }; + const sumWeight = Object.values(assigendWeights) + .filter((value) => !isNaN(value)) + .reduce((total, num) => total + num, 0); + return ( - + {potId} - + {/*
Total amount
{totalAmount} / {originalTotalAmount} N
-
+
*/}
Remainder
{remainder} N
@@ -125,20 +164,86 @@ const PayoutsModal = ({ Set Payouts +
+ { + setQfWeight(value); + setJdgWeight((100 - parseFloat(value)).toString()); + }} + value={qfWeight} + /> + { + setJdgWeight(value); + setQfWeight((100 - parseFloat(value)).toString()); + }} + value={jdgWeight} + /> +
+ +
Project
+
Actual QF
+
QF Override
+
QF Weight Adjusted
+
Assigned Weight (%)
+
Assigned Weight Calculation
+
Final Calculation
+
- {payoutsList.map(({ project_id, amount }) => ( - -
{_address(project_id, 20)}
- handleChange(project_id, amount)} - defaultValue={amount} - /> -
- ))} + {payoutsList.map(({ project_id, amount }, idx) => { + const qfWeightCalc = parseFloat(amount) * (parseFloat(qfWeight) / 100); + const jdWeightCalc = + (parseFloat(originalTotalAmount) * (assigendWeights[project_id] || 0) * parseFloat(jdgWeight)) / 10000; + + const finalAmount = qfWeightCalc + jdWeightCalc; + return ( + + {/* Project address */} +
{_address(project_id, 15)}
+ {/* Origial Payout */} + + {/* Overide Payout */} + handleChange(project_id, amount)} defaultValue={amount} /> + {/* QF Weight Adjusted */} + + {/* Assigned Weight */} + + setAssignedWeights({ + ...assigendWeights, + [project_id]: parseFloat(value), + }) + } + percent={true} + /> + {/* Assigned Weight Calculation */} + + {/* Final Calculation */} + +
+ ); + })}
+ +
+
{originalTotalAmount} N
+
{totalAmount} N
+
{(parseFloat(originalTotalAmount) * (parseFloat(qfWeight) / 100)).toFixed(2)} N
+
{sumWeight} %
+
{sumAssignedWeightsCalc.toFixed(2)} N
+
{finalAmountNear} N
+
); diff --git a/src/pages/Pot/components/PayoutsModal/styles.ts b/src/pages/Pot/components/PayoutsModal/styles.ts index 8d89a805..74186f28 100644 --- a/src/pages/Pot/components/PayoutsModal/styles.ts +++ b/src/pages/Pot/components/PayoutsModal/styles.ts @@ -5,6 +5,7 @@ export const Container = styled.div` flex-direction: column; gap: 1rem; position: relative; + overflow-x: scroll; height: 80vh; `; @@ -20,6 +21,7 @@ export const PayoutsView = styled.div` flex-direction: column; max-height: 100%; overflow-y: scroll; + font-size: 12px; `; export const PayoutItem = styled.div` @@ -27,8 +29,8 @@ export const PayoutItem = styled.div` gap: 1rem; align-items: center; justify-content: space-between; - .id { - width: 172px; + > div { + width: 140px; } `; @@ -56,3 +58,19 @@ export const ExitIcon = styled.div` width: 18px; } `; +export const TableHeader = styled.div` + display: flex; + justify-content: space-between; + font-size: 12px; + padding-right: 1rem; + font-display: 500; + > div { + width: 140px; + display: flex; + align-items: center; + } + > div:not(:first-of-type) { + justify-content: center; + text-align: center; + } +`; diff --git a/src/services/getPotData.ts b/src/services/getPotData.ts index 99719697..c7b07119 100644 --- a/src/services/getPotData.ts +++ b/src/services/getPotData.ts @@ -200,7 +200,7 @@ export const getPayout = ({ // get matched donations export const asyncGetPublicDonations = (potDetail: PotDetail, potId: string) => { - const limit = 350; // number of donations to fetch per req + const limit = 300; // number of donations to fetch per req const donationsCount = potDetail.public_donations_count; const paginations = [...Array(Math.ceil(donationsCount / limit)).keys()]; diff --git a/src/utils/calculatePayouts.ts b/src/utils/calculatePayouts.ts index 1bf81001..905f1e11 100644 --- a/src/utils/calculatePayouts.ts +++ b/src/utils/calculatePayouts.ts @@ -8,7 +8,6 @@ const calculatePayouts = ( blacklistedAccounts: any, ): Promise> => { const { NADABOT_CONTRACT_ID } = constants; - // * QF/CLR logic taken from https://github.com/gitcoinco/quadratic-funding/blob/master/quadratic-funding/clr.py * return new Promise((resolve, reject) => { // console.log("Calculting payouts; ignoring blacklisted donors &/or projects: ", blacklistedAccounts.join(", "));