Skip to content

Commit a840887

Browse files
committed
beginning
1 parent 79fc7e0 commit a840887

File tree

3 files changed

+273
-24
lines changed

3 files changed

+273
-24
lines changed
Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
import { useState, Fragment, useMemo } from "react";
2+
import { Listbox, Transition } from "@headlessui/react";
3+
import { getChains, NATIVE } from "common";
4+
import { useAccount } from "wagmi";
5+
import { getBalance } from "@wagmi/core";
6+
import { config } from "../../app/wagmi";
7+
import React from "react";
8+
import { zeroAddress } from "viem";
9+
import { stringToBlobUrl } from "common";
10+
import { Checkbox } from "@chakra-ui/react";
11+
12+
type DonateToGitcoinProps = {
13+
divider?: "none" | "top" | "bottom";
14+
};
15+
16+
export function DonateToGitcoin({ divider = "none" }: DonateToGitcoinProps) {
17+
const [isEnabled, setIsEnabled] = useState(false);
18+
const chains = getChains().filter((c) => c.type === "mainnet");
19+
const [selectedChainId, setSelectedChainId] = useState<number | null>(null);
20+
const [selectedToken, setSelectedToken] = useState<string>("");
21+
const [amount, setAmount] = useState<string>("");
22+
const [tokenBalances, setTokenBalances] = useState<
23+
{ token: string; balance: number }[]
24+
>([]);
25+
26+
const selectedChain = selectedChainId
27+
? chains.find((c) => c.id === selectedChainId)
28+
: null;
29+
const tokenDetails = selectedChain?.tokens.find(
30+
(t) => t.address === selectedToken
31+
);
32+
const { address } = useAccount();
33+
34+
const handleCheckboxChange = (checked: boolean) => {
35+
setIsEnabled(checked);
36+
if (!checked) {
37+
// Reset form when checkbox is unchecked
38+
setSelectedChainId(null);
39+
setSelectedToken("");
40+
setAmount("");
41+
setTokenBalances([]);
42+
}
43+
};
44+
45+
React.useEffect(() => {
46+
if (!address || !selectedChain) return;
47+
48+
const fetchBalances = async () => {
49+
const balances = await Promise.all(
50+
selectedChain.tokens
51+
.filter((token) => token.address !== zeroAddress)
52+
.map(async (token) => {
53+
const { value } = await getBalance(config, {
54+
address,
55+
token:
56+
token.address.toLowerCase() === NATIVE.toLowerCase()
57+
? undefined
58+
: token.address,
59+
chainId: selectedChainId,
60+
});
61+
return {
62+
token: token.address,
63+
balance: Number(value) / 10 ** (token.decimals || 18),
64+
};
65+
})
66+
);
67+
setTokenBalances(balances);
68+
};
69+
70+
fetchBalances();
71+
}, [address, selectedChainId, selectedChain]);
72+
73+
const selectedTokenBalance =
74+
tokenBalances.find(
75+
(b) => b.token.toLowerCase() === selectedToken.toLowerCase()
76+
)?.balance || 0;
77+
78+
const isAmountValid = useMemo(() => {
79+
if (!amount || !selectedToken) return true;
80+
const numAmount = Number(amount);
81+
return numAmount > 0 && numAmount <= selectedTokenBalance;
82+
}, [amount, selectedToken, selectedTokenBalance]);
83+
84+
const borderClass = useMemo(() => {
85+
switch (divider) {
86+
case "top":
87+
return "border-t";
88+
case "bottom":
89+
return "border-b";
90+
default:
91+
return "";
92+
}
93+
}, [divider]);
94+
95+
return (
96+
<div className={`flex flex-col justify-center mt-2 py-4 ${borderClass}`}>
97+
<div className={`${!isEnabled ? "opacity-50" : ""}`}>
98+
<p className="font-sans font-medium flex items-center">
99+
<Checkbox
100+
className="mr-2"
101+
border={"1px"}
102+
borderRadius={"4px"}
103+
colorScheme="whiteAlpha"
104+
iconColor="black"
105+
size="lg"
106+
isChecked={isEnabled}
107+
onChange={(e) => handleCheckboxChange(e.target.checked)}
108+
/>
109+
<img
110+
className="inline mr-2 w-5 h-5"
111+
alt="Gitcoin"
112+
src="/logos/gitcoin-gist-logo.svg"
113+
/>
114+
<span className="font-sans font-medium">Donate to Gitcoin</span>
115+
</p>
116+
</div>
117+
118+
{isEnabled && (
119+
<div className="ml-7 mt-4 space-y-3">
120+
<div className="flex items-center">
121+
<div className="relative flex items-center bg-gray-50 rounded-lg p-2 flex-grow max-w-lg">
122+
{selectedChain && (
123+
<img
124+
className="w-5 h-5 mr-2"
125+
alt={selectedChain.prettyName}
126+
src={stringToBlobUrl(selectedChain.icon)}
127+
/>
128+
)}
129+
<select
130+
className="bg-transparent border-none focus:ring-0 text-sm flex-grow font-medium"
131+
value={selectedChainId || ""}
132+
onChange={(e) => {
133+
const newChainId = Number(e.target.value);
134+
setSelectedChainId(newChainId || null);
135+
setSelectedToken("");
136+
setAmount("");
137+
}}
138+
>
139+
<option value="">Select chain</option>
140+
{chains
141+
.sort((a, b) => a.prettyName.localeCompare(b.prettyName))
142+
.map((c) => (
143+
<option key={c.id} value={c.id}>
144+
{c.prettyName}
145+
</option>
146+
))}
147+
</select>
148+
</div>
149+
</div>
150+
151+
{selectedChain && (
152+
<div className="flex items-center gap-3">
153+
<Listbox value={selectedToken} onChange={setSelectedToken}>
154+
<div className="relative">
155+
<Listbox.Button className="relative w-40 cursor-default rounded-lg border border-gray-200 bg-white py-2 pl-3 pr-8 text-left text-sm shadow-sm hover:border-gray-300">
156+
{selectedToken
157+
? selectedChain?.tokens.find(
158+
(t) => t.address === selectedToken
159+
)?.code
160+
: "Select token"}
161+
</Listbox.Button>
162+
<Transition
163+
as={Fragment}
164+
leave="transition ease-in duration-100"
165+
leaveFrom="opacity-100"
166+
leaveTo="opacity-0"
167+
>
168+
<Listbox.Options
169+
className="absolute z-50 mt-1 w-full overflow-auto rounded-lg bg-white py-1 text-sm shadow-lg ring-1 ring-black ring-opacity-5"
170+
style={{ maxHeight: "40vh" }}
171+
>
172+
<div className="max-h-[40vh] overflow-y-auto">
173+
{selectedChain?.tokens
174+
.filter((token) => token.address !== zeroAddress)
175+
.map((token) => {
176+
const balance =
177+
tokenBalances.find(
178+
(b) =>
179+
b.token.toLowerCase() ===
180+
token.address.toLowerCase()
181+
)?.balance || 0;
182+
return (
183+
<Listbox.Option
184+
key={token.address}
185+
value={token.address}
186+
className={({ active }) =>
187+
`relative cursor-default select-none py-2 pl-3 pr-9 ${
188+
active ? "bg-gray-50" : ""
189+
}`
190+
}
191+
>
192+
<div className="flex justify-between items-center">
193+
<span>{token.code}</span>
194+
<span className="text-xs text-gray-500">
195+
{balance.toFixed(3)}
196+
</span>
197+
</div>
198+
</Listbox.Option>
199+
);
200+
})}
201+
</div>
202+
</Listbox.Options>
203+
</Transition>
204+
</div>
205+
</Listbox>
206+
207+
<div className="relative flex-grow max-w-[200px]">
208+
<input
209+
type="number"
210+
className={`w-full rounded-lg border py-2 px-3 text-sm shadow-sm hover:border-gray-300 ${
211+
isAmountValid ? "border-gray-200" : "border-red-300"
212+
}`}
213+
value={amount}
214+
onChange={(e) => setAmount(e.target.value)}
215+
placeholder="Enter amount"
216+
max={selectedTokenBalance}
217+
/>
218+
{selectedToken && (
219+
<div className="absolute right-3 top-2.5 text-xs text-gray-500">
220+
{tokenDetails?.code}
221+
</div>
222+
)}
223+
</div>
224+
225+
{selectedToken && (
226+
<span className="text-sm text-gray-500 whitespace-nowrap">
227+
Balance: {selectedTokenBalance.toFixed(3)}
228+
</span>
229+
)}
230+
</div>
231+
)}
232+
233+
{!isAmountValid && amount && (
234+
<p className="text-sm text-red-500">
235+
Amount must be greater than 0 and less than your balance
236+
</p>
237+
)}
238+
</div>
239+
)}
240+
</div>
241+
);
242+
}

packages/grant-explorer/src/features/round/ViewCartPage/ChainConfirmationModalBody.tsx

Lines changed: 28 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { TToken, getChainById, stringToBlobUrl } from "common";
44
import { useCartStorage } from "../../../store";
55
import { parseChainId } from "common/src/chains";
66
import { Checkbox } from "@chakra-ui/react";
7-
7+
import { DonateToGitcoin } from "../DonateToGitcoin";
88
type ChainConfirmationModalBodyProps = {
99
projectsByChain: { [chain: number]: CartProject[] };
1010
totalDonationsPerChain: { [chain: number]: number };
@@ -43,35 +43,40 @@ export function ChainConfirmationModalBody({
4343
return (
4444
<>
4545
<p className="text-sm text-grey-400">
46-
{chainIdsBeingCheckedOut.length > 1 && (
46+
{chainIdsBeingCheckedOut.length > 1 && (
4747
<>
4848
Checkout all your carts across different networks or select the cart
4949
you wish to checkout now.
5050
</>
5151
)}
5252
</p>
5353
<div className="my-4">
54-
{Object.keys(projectsByChain)
55-
.map(parseChainId)
56-
.filter((chainId) => chainIdsBeingCheckedOut.includes(chainId))
57-
.map((chainId, index) => (
58-
<ChainSummary
59-
chainId={chainId}
60-
selectedPayoutToken={getVotingTokenForChain(chainId)}
61-
totalDonation={totalDonationsPerChain[chainId]}
62-
checked={
63-
chainIdsBeingCheckedOut.includes(chainId) &&
64-
enoughBalanceByChainId[chainId]
65-
}
66-
chainsBeingCheckedOut={chainIdsBeingCheckedOut.length}
67-
onChange={(checked) =>
68-
handleChainCheckboxChange(chainId, checked)
69-
}
70-
isLastItem={index === Object.keys(projectsByChain).length - 1}
71-
notEnoughBalance={!enoughBalanceByChainId[chainId]}
72-
handleSwap={() => handleSwap(chainId)}
73-
/>
74-
))}
54+
<>
55+
<DonateToGitcoin divider="bottom" />
56+
</>
57+
<>
58+
{Object.keys(projectsByChain)
59+
.map(parseChainId)
60+
.filter((chainId) => chainIdsBeingCheckedOut.includes(chainId))
61+
.map((chainId, index) => (
62+
<ChainSummary
63+
chainId={chainId}
64+
selectedPayoutToken={getVotingTokenForChain(chainId)}
65+
totalDonation={totalDonationsPerChain[chainId]}
66+
checked={
67+
chainIdsBeingCheckedOut.includes(chainId) &&
68+
enoughBalanceByChainId[chainId]
69+
}
70+
chainsBeingCheckedOut={chainIdsBeingCheckedOut.length}
71+
onChange={(checked) =>
72+
handleChainCheckboxChange(chainId, checked)
73+
}
74+
isLastItem={index === Object.keys(projectsByChain).length - 1}
75+
notEnoughBalance={!enoughBalanceByChainId[chainId]}
76+
handleSwap={() => handleSwap(chainId)}
77+
/>
78+
))}
79+
</>
7580
</div>
7681
</>
7782
);

packages/grant-explorer/src/features/round/ViewCartPage/SummaryContainer.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,9 @@ export function SummaryContainer(props: {
187187
handleSwap={props.handleSwap}
188188
/>
189189
}
190-
isOpen={openChainConfirmationModal}
190+
// todo: put back
191+
// isOpen={openChainConfirmationModal}
192+
isOpen={true}
191193
setIsOpen={setOpenChainConfirmationModal}
192194
disabled={chainIdsBeingCheckedOut.length === 0}
193195
/>

0 commit comments

Comments
 (0)