Skip to content

Commit fae50ef

Browse files
chore: refactor code
1 parent 33b82f1 commit fae50ef

File tree

1 file changed

+165
-128
lines changed

1 file changed

+165
-128
lines changed

packages/grant-explorer/src/features/projects/ViewProject.tsx

Lines changed: 165 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ import ErrorModal from "../common/ErrorModal";
4747
import ProgressModal, { errorModalDelayMs } from "../common/ProgressModal";
4848
import { useDirectAllocation } from "./hooks/useDirectAllocation";
4949
import { getDirectAllocationPoolId } from "common/dist/allo/backends/allo-v2";
50-
import { zeroAddress } from "viem";
50+
import { getAddress, zeroAddress } from "viem";
5151
import GenericModal from "../common/GenericModal";
5252
import { BoltIcon, InformationCircleIcon } from "@heroicons/react/24/outline";
5353
import { useConnectModal } from "@rainbow-me/rainbowkit";
@@ -89,53 +89,16 @@ export default function ViewProject() {
8989
const [errorModalSubHeading, setErrorModalSubHeading] = useState<
9090
string | undefined
9191
>();
92-
const [directDonationAmount, setDirectDonationAmount] = useState<string>("");
9392

93+
const [directDonationAmount, setDirectDonationAmount] = useState<string>("");
9494
const payoutTokenOptions: TToken[] = getVotingTokenOptions(
9595
Number(chainId)
9696
).filter((p) => p.canVote);
97-
9897
const [payoutToken, setPayoutToken] = useState<TToken | undefined>(
9998
payoutTokenOptions[0]
10099
);
101-
102-
const [tokenBalance, setTokenBalance] = useState<bigint>(BigInt("0"));
103100
const directAllocationPoolId = getDirectAllocationPoolId(chainId ?? 1);
104-
105-
useEffect(() => {
106-
const runner = async () => {
107-
const { value } = await getBalance(config, {
108-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
109-
address: address!,
110-
token:
111-
payoutToken?.address === zeroAddress ||
112-
payoutToken?.address.toLowerCase() === NATIVE.toLowerCase()
113-
? undefined
114-
: payoutToken?.address,
115-
chainId,
116-
});
117-
118-
setTokenBalance(value);
119-
};
120-
if (address && address !== zeroAddress) runner();
121-
}, [payoutToken, chainId, address]);
122-
123-
const hasEnoughFunds =
124-
Number(directDonationAmount) <=
125-
Number(ethers.utils.formatUnits(tokenBalance, payoutToken?.decimals ?? 18));
126-
127-
const [hasClickedSubmit, setHasClickedSubmit] = useState(false);
128-
const [isEmptyInput, setIsEmptyInput] = useState(false);
129101
const [transactionReplaced, setTransactionReplaced] = useState(false);
130-
131-
useEffect(() => {
132-
if (directDonationAmount === "" || Number(directDonationAmount) === 0) {
133-
setIsEmptyInput(true);
134-
} else {
135-
setIsEmptyInput(false);
136-
}
137-
}, [directDonationAmount]);
138-
139102
const { projectId } = useParams();
140103

141104
const dataLayer = useDataLayer();
@@ -394,7 +357,7 @@ export default function ViewProject() {
394357
<p>Couldn't load project data.</p>
395358
)}
396359
</div>
397-
<DirectDonationModals />
360+
<DonationModals />
398361
</div>
399362
</DefaultLayout>
400363
) : (
@@ -403,98 +366,26 @@ export default function ViewProject() {
403366
</>
404367
);
405368

406-
function DirectDonationModals() {
369+
function DonationModals() {
407370
return (
408371
<>
409372
<GenericModal
410373
body={
411-
<>
412-
<div>
413-
<p className="mb-4">
414-
<BoltIcon className="w-4 h-4 mb-1 inline-block mr-2" />
415-
Donate now
416-
</p>
417-
</div>
418-
419-
<div className="mb-4 flex flex-col lg:flex-row justify-between sm:px-2 px-2 py-4 rounded-md">
420-
<div className="flex">
421-
<div className="flex relative overflow-hidden bg-no-repeat bg-cover mt-auto mb-auto">
422-
<img
423-
className="inline-block rounded-full w-10 my-auto mr-2"
424-
src={
425-
projectData?.project.metadata.logoImg
426-
? `${ipfsGateway}/ipfs/${projectData?.project.metadata.logoImg}`
427-
: DefaultLogoImage
428-
}
429-
alt={"Project Logo"}
430-
/>
431-
<p className="font-semibold text-md my-auto text-ellipsis line-clamp-1 max-w-[500px] 2xl:max-w-none">
432-
{projectData?.project?.metadata.title}
433-
</p>
434-
</div>
435-
</div>
436-
<div className="flex sm:space-x-4 space-x-2 h-16 sm:pl-4 pt-3 justify-center">
437-
<p className="mt-4 md:mt-3 text-xs md:text-sm amount-text font-medium">
438-
Amount
439-
</p>
440-
<Input
441-
aria-label={"Donation amount for all projects "}
442-
id={"input-donationamount"}
443-
min="0"
444-
type="text"
445-
value={directDonationAmount}
446-
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
447-
const value = e.target.value.replace(",", ".");
448-
if (/^\d*\.?\d*$/.test(value) || value === "") {
449-
setDirectDonationAmount(value);
450-
}
451-
}}
452-
className="w-16 lg:w-18"
453-
/>
454-
<PayoutTokenDropdown
455-
selectedPayoutToken={payoutToken}
456-
setSelectedPayoutToken={(token) => {
457-
setPayoutToken(token);
458-
}}
459-
payoutTokenOptions={payoutTokenOptions}
460-
style="max-h-16"
461-
/>
462-
</div>
463-
</div>
464-
{isEmptyInput && hasClickedSubmit && (
465-
<p
466-
data-testid="emptyInput"
467-
className="rounded-md bg-red-50 py-2 text-pink-500 flex justify-center my-4 text-sm"
468-
>
469-
<InformationCircleIcon className="w-4 h-4 mr-1 mt-0.5" />
470-
<span>You must enter donation for the project</span>
471-
</p>
472-
)}
473-
{!hasEnoughFunds && (
474-
<p
475-
data-testid="hasEnoughFunds"
476-
className="rounded-md bg-red-50 py-2 text-pink-500 flex justify-center my-4 text-sm"
477-
>
478-
<InformationCircleIcon className="w-4 h-4 mr-1 mt-0.5" />
479-
<span>You don't have enough funds</span>
480-
</p>
481-
)}
482-
483-
<button
484-
type="button"
485-
className="w-full font-normal rounded-lg bg-gitcoin-violet-400 text-white focus-visible:outline-indigo-600 py-2 leading-6"
486-
onClick={() => {
487-
handleDonate();
488-
}}
489-
disabled={!hasEnoughFunds}
490-
>
491-
Submit your donation
492-
</button>
493-
</>
374+
<DirectDonationModalComponent
375+
chainId={chainId!}
376+
address={address!}
377+
directDonationAmount={directDonationAmount}
378+
setDirectDonationAmount={setDirectDonationAmount}
379+
payoutToken={payoutToken}
380+
setPayoutToken={setPayoutToken}
381+
payoutTokenOptions={payoutTokenOptions}
382+
projectData={{ project: project! }}
383+
handleDonate={handleDonate}
384+
/>
494385
}
495386
isOpen={showDirectAllocationModal}
496387
setIsOpen={setShowDirectAllocationModal}
497-
/>
388+
></GenericModal>
498389
<ProgressModal
499390
isOpen={openProgressModal}
500391
subheading={"Please hold while we donate your funds to the project."}
@@ -514,10 +405,8 @@ export default function ViewProject() {
514405
if (
515406
directDonationAmount === undefined ||
516407
allo === null ||
517-
payoutToken === undefined ||
518-
isEmptyInput
408+
payoutToken === undefined
519409
) {
520-
setHasClickedSubmit(true);
521410
return;
522411
}
523412

@@ -572,6 +461,154 @@ export default function ViewProject() {
572461
}
573462
}
574463

464+
export function DirectDonationModalComponent(props: {
465+
chainId: number;
466+
address: string;
467+
directDonationAmount: string;
468+
setDirectDonationAmount: (value: string) => void;
469+
payoutToken: TToken | undefined;
470+
setPayoutToken: (token: TToken | undefined) => void;
471+
payoutTokenOptions: TToken[];
472+
projectData: { project: v2Project };
473+
handleDonate: () => void;
474+
}) {
475+
const [tokenBalance, setTokenBalance] = useState<bigint>(BigInt("0"));
476+
const [hasEnoughFunds, setHasEnoughFunds] = useState(false);
477+
478+
useEffect(() => {
479+
const runner = async () => {
480+
const { value } = await getBalance(config, {
481+
address: getAddress(props.address),
482+
token:
483+
props.payoutToken?.address === zeroAddress ||
484+
props.payoutToken?.address.toLowerCase() === NATIVE.toLowerCase()
485+
? undefined
486+
: props.payoutToken?.address,
487+
chainId: props.chainId,
488+
});
489+
490+
setTokenBalance(value);
491+
};
492+
if (props.address && props.address !== zeroAddress) runner();
493+
}, [props.payoutToken, props.chainId, props.address]);
494+
495+
useMemo(() => {
496+
setHasEnoughFunds(
497+
Number(props.directDonationAmount) <=
498+
Number(
499+
ethers.utils.formatUnits(
500+
tokenBalance,
501+
props.payoutToken?.decimals ?? 18
502+
)
503+
)
504+
);
505+
}, [props.directDonationAmount, tokenBalance, props.payoutToken]);
506+
507+
const [hasClickedSubmit, setHasClickedSubmit] = useState(false);
508+
const [isEmptyInput, setIsEmptyInput] = useState(false);
509+
510+
useEffect(() => {
511+
if (
512+
props.directDonationAmount === "" ||
513+
Number(props.directDonationAmount) === 0
514+
) {
515+
setIsEmptyInput(true);
516+
} else {
517+
setIsEmptyInput(false);
518+
}
519+
}, [props.directDonationAmount]);
520+
521+
return (
522+
<>
523+
<div>
524+
<p className="mb-4">
525+
<BoltIcon className="w-4 h-4 mb-1 inline-block mr-2" />
526+
Donate now
527+
</p>
528+
</div>
529+
530+
<div className="mb-4 flex flex-col lg:flex-row justify-between sm:px-2 px-2 py-4 rounded-md">
531+
<div className="flex">
532+
<div className="flex relative overflow-hidden bg-no-repeat bg-cover mt-auto mb-auto">
533+
<img
534+
className="inline-block rounded-full w-10 my-auto mr-2"
535+
src={
536+
props.projectData?.project.metadata.logoImg
537+
? `${ipfsGateway}/ipfs/${props.projectData?.project.metadata.logoImg}`
538+
: DefaultLogoImage
539+
}
540+
alt={"Project Logo"}
541+
/>
542+
<p className="font-semibold text-md my-auto text-ellipsis line-clamp-1 max-w-[500px] 2xl:max-w-none">
543+
{props.projectData?.project?.metadata.title}
544+
</p>
545+
</div>
546+
</div>
547+
<div className="flex sm:space-x-4 space-x-2 h-16 sm:pl-4 pt-3 justify-center">
548+
<p className="mt-4 md:mt-3 text-xs md:text-sm amount-text font-medium">
549+
Amount
550+
</p>
551+
<Input
552+
aria-label={"Donation amount for all projects "}
553+
id={"input-donationamount"}
554+
min="0"
555+
type="text"
556+
value={props.directDonationAmount}
557+
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
558+
const value = e.target.value.replace(",", ".");
559+
if (/^\d*\.?\d*$/.test(value) || value === "") {
560+
props.setDirectDonationAmount(value);
561+
}
562+
}}
563+
className="w-16 lg:w-18"
564+
/>
565+
<PayoutTokenDropdown
566+
selectedPayoutToken={props.payoutToken}
567+
setSelectedPayoutToken={(token) => {
568+
props.setPayoutToken(token);
569+
}}
570+
payoutTokenOptions={props.payoutTokenOptions}
571+
style="max-h-16"
572+
/>
573+
</div>
574+
</div>
575+
{isEmptyInput && hasClickedSubmit && (
576+
<p
577+
data-testid="emptyInput"
578+
className="rounded-md bg-red-50 py-2 text-pink-500 flex justify-center my-4 text-sm"
579+
>
580+
<InformationCircleIcon className="w-4 h-4 mr-1 mt-0.5" />
581+
<span>You must enter donation for the project</span>
582+
</p>
583+
)}
584+
{!hasEnoughFunds && (
585+
<p
586+
data-testid="hasEnoughFunds"
587+
className="rounded-md bg-red-50 py-2 text-pink-500 flex justify-center my-4 text-sm"
588+
>
589+
<InformationCircleIcon className="w-4 h-4 mr-1 mt-0.5" />
590+
<span>You don't have enough funds</span>
591+
</p>
592+
)}
593+
594+
<button
595+
type="button"
596+
className="w-full font-normal rounded-lg bg-gitcoin-violet-400 text-white focus-visible:outline-indigo-600 py-2 leading-6"
597+
onClick={() => {
598+
if (isEmptyInput) {
599+
setHasClickedSubmit(true);
600+
return;
601+
}
602+
props.handleDonate();
603+
}}
604+
disabled={!hasEnoughFunds}
605+
>
606+
Submit your donation
607+
</button>
608+
</>
609+
);
610+
}
611+
575612
function ProjectDetailsTabs(props: {
576613
tabs: string[];
577614
onChange?: (tabIndex: number) => void;

0 commit comments

Comments
 (0)