-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
239 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
import React from "react"; | ||
import { BigNumber } from "ethers"; | ||
import { useAccount } from "wagmi"; | ||
import { BanknotesIcon, QuestionMarkCircleIcon } from "@heroicons/react/24/outline"; | ||
import { Address, Balance } from "~~/components/scaffold-eth"; | ||
import { useDeployedContractInfo, useScaffoldContractRead } from "~~/hooks/scaffold-eth"; | ||
|
||
export const StreamContractInfo2 = () => { | ||
const { address } = useAccount(); | ||
const { data: streamContract } = useDeployedContractInfo("SandGardenStreams"); | ||
|
||
const { data: owner } = useScaffoldContractRead({ | ||
contractName: "SandGardenStreams", | ||
functionName: "owner", | ||
}); | ||
|
||
const { data: builderData } = useScaffoldContractRead({ | ||
contractName: "SandGardenStreams", | ||
functionName: "streamedBuilders", | ||
args: [address], | ||
}) as { | ||
data: { cap: BigNumber } | undefined; | ||
}; | ||
|
||
const amIAStreamdBuilder = builderData?.cap.gt(0); | ||
|
||
return ( | ||
<> | ||
<div className="mt-16"> | ||
<p className="font-bold mb-2 text-secondary"> | ||
Stream Contract | ||
<span | ||
className="tooltip text-white font-normal" | ||
data-tip="All streams and contributions are handled by a contract on Optimism" | ||
> | ||
<QuestionMarkCircleIcon className="h-5 w-5 inline-block ml-2" /> | ||
</span> | ||
</p> | ||
<div className="flex gap-2 items-baseline"> | ||
<div className="flex flex-col items-center"> | ||
<Address address={streamContract?.address} /> | ||
<span className="text-xs text-[#f01a37]">Optimism</span> | ||
</div>{" "} | ||
/ | ||
<Balance address={streamContract?.address} className="text-3xl" /> | ||
</div> | ||
{address && amIAStreamdBuilder && ( | ||
<div className="mt-3"> | ||
<label | ||
htmlFor="withdraw-modal" | ||
className="btn btn-primary btn-sm px-2 rounded-full font-normal space-x-2 normal-case" | ||
> | ||
<BanknotesIcon className="h-4 w-4" /> | ||
<span>Withdraw</span> | ||
</label> | ||
</div> | ||
)} | ||
</div> | ||
<div className="mt-8"> | ||
<p className="font-bold mb-2 text-secondary">Owner</p> | ||
<Address address={owner} /> | ||
</div> | ||
</> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,174 @@ | ||
import React, { useEffect, useState } from "react"; | ||
import { ethers } from "ethers"; | ||
import type { NextPage } from "next"; | ||
import { StreamContractInfo2 } from "~~/components/StreamContractInfo2"; | ||
import { Address, EtherInput } from "~~/components/scaffold-eth"; | ||
import { useScaffoldContractRead, useScaffoldContractWrite, useScaffoldEventHistory } from "~~/hooks/scaffold-eth"; | ||
|
||
const Members: NextPage = () => { | ||
const [reason, setReason] = useState(""); | ||
const [amount, setAmount] = useState(""); | ||
const [selectedAddress, setSelectedAddress] = useState(""); | ||
const [filteredEvents, setFilteredEvents] = useState<any[]>([]); | ||
|
||
const [builderList, setBuilderList] = useState<string[]>([]); | ||
|
||
const { data: allBuildersData, isLoading: isLoadingBuilderData } = useScaffoldContractRead({ | ||
contractName: "SandGardenStreams", | ||
functionName: "allBuildersData", | ||
args: [builderList], | ||
}); | ||
|
||
const { writeAsync: doWithdraw } = useScaffoldContractWrite({ | ||
contractName: "SandGardenStreams", | ||
functionName: "streamWithdraw", | ||
args: [ethers.utils.parseEther(amount || "0"), reason], | ||
}); | ||
|
||
const { data: withdrawEvents } = useScaffoldEventHistory({ | ||
contractName: "SandGardenStreams", | ||
eventName: "Withdraw", | ||
fromBlock: Number(process.env.NEXT_PUBLIC_DEPLOY_BLOCK) || 0, | ||
blockData: true, | ||
}); | ||
|
||
const { data: addBuilderEvents, isLoading: isLoadingBuilderEvents } = useScaffoldEventHistory({ | ||
contractName: "SandGardenStreams", | ||
eventName: "AddBuilder", | ||
fromBlock: Number(process.env.NEXT_PUBLIC_DEPLOY_BLOCK) || 0, | ||
}); | ||
|
||
useEffect(() => { | ||
if (addBuilderEvents && addBuilderEvents.length > 0) { | ||
const fetchedBuilderList = addBuilderEvents.map((event: any) => event.args.to); | ||
setBuilderList(fetchedBuilderList); | ||
} | ||
}, [addBuilderEvents]); | ||
|
||
const sortedBuilders = allBuildersData && [...allBuildersData].reverse(); | ||
|
||
return ( | ||
<> | ||
<div className="max-w-3xl px-4 py-8"> | ||
<h1 className="text-4xl font-bold mb-8 text-primary-content bg-primary inline-block p-2">Members</h1> | ||
<div className="mb-16"> | ||
<p className="mt-0 mb-10"> | ||
These are the BG Sand Garden active builders and their streams. You can click on any builder to see their | ||
detailed contributions. | ||
</p> | ||
{isLoadingBuilderData || isLoadingBuilderEvents ? ( | ||
<div className="m-10"> | ||
<div className="text-5xl animate-bounce mb-2">👾</div> | ||
<div className="text-lg loading-dots">Loading...</div> | ||
</div> | ||
) : ( | ||
<div className="flex flex-col gap-6"> | ||
{sortedBuilders?.map(builderData => { | ||
if (builderData.cap.isZero()) return; | ||
const cap = ethers.utils.formatEther(builderData.cap || 0); | ||
const unlocked = ethers.utils.formatEther(builderData.unlockedAmount || 0); | ||
const percentage = Math.floor((parseFloat(unlocked) / parseFloat(cap)) * 100); | ||
return ( | ||
<div className="flex flex-col md:flex-row gap-2 md:gap-6" key={builderData.builderAddress}> | ||
<div className="flex flex-col md:items-center"> | ||
<div> | ||
Ξ {parseFloat(unlocked).toFixed(4)} / {cap} | ||
</div> | ||
<progress | ||
className="progress w-56 progress-primary bg-white" | ||
value={percentage} | ||
max="100" | ||
></progress> | ||
</div> | ||
<div className="md:w-1/2 flex"> | ||
<label | ||
htmlFor="withdraw-events-modal" | ||
className="cursor-pointer" | ||
onClick={() => { | ||
setSelectedAddress(builderData.builderAddress); | ||
setFilteredEvents( | ||
withdrawEvents?.filter((event: any) => event.args.to === builderData.builderAddress) || [], | ||
); | ||
}} | ||
> | ||
<Address address={builderData.builderAddress} disableAddressLink={true} /> | ||
</label> | ||
</div> | ||
</div> | ||
); | ||
})} | ||
</div> | ||
)} | ||
</div> | ||
<StreamContractInfo2 /> | ||
</div> | ||
|
||
<input type="checkbox" id="withdraw-modal" className="modal-toggle" /> | ||
<label htmlFor="withdraw-modal" className="modal cursor-pointer"> | ||
<label className="modal-box relative bg-primary"> | ||
{/* dummy input to capture event onclick on modal box */} | ||
<input className="h-0 w-0 absolute top-0 left-0" /> | ||
<h3 className="text-xl font-bold mb-8">Withdraw from your stream</h3> | ||
<label htmlFor="withdraw-modal" className="btn btn-ghost btn-sm btn-circle absolute right-3 top-3"> | ||
✕ | ||
</label> | ||
<div className="space-y-3"> | ||
<div className="flex flex-col gap-6 items-center"> | ||
<input | ||
type="text" | ||
className="input input-ghost focus:outline-none focus:bg-transparent focus:text-gray-400 h-[2.2rem] min-h-[2.2rem] px-4 w-full font-medium placeholder:text-accent/50 text-gray-400 border-2 border-base-300 bg-base-200 rounded-full text-accent" | ||
placeholder="Reason for withdrawing & links" | ||
value={reason} | ||
onChange={event => setReason(event.target.value)} | ||
/> | ||
<EtherInput value={amount} onChange={value => setAmount(value)} /> | ||
<button className="btn btn-secondary btn-sm" onClick={doWithdraw}> | ||
Withdraw | ||
</button> | ||
</div> | ||
</div> | ||
</label> | ||
</label> | ||
<input type="checkbox" id="withdraw-events-modal" className="modal-toggle" /> | ||
<label htmlFor="withdraw-events-modal" className="modal cursor-pointer"> | ||
<label className="modal-box relative"> | ||
{/* dummy input to capture event onclick on modal box */} | ||
<input className="h-0 w-0 absolute top-0 left-0" /> | ||
<h3 className="text-xl font-bold mb-8"> | ||
<p className="mb-1">Contributions</p> | ||
<Address address={selectedAddress} /> | ||
</h3> | ||
<label htmlFor="withdraw-events-modal" className="btn btn-ghost btn-sm btn-circle absolute right-3 top-3"> | ||
✕ | ||
</label> | ||
<div className="space-y-3"> | ||
<ul> | ||
{filteredEvents.length > 0 ? ( | ||
<div className="flex flex-col"> | ||
{filteredEvents.map(event => ( | ||
<div key={event.log.transactionHash} className="flex flex-col"> | ||
<div> | ||
<span className="font-bold">Date: </span> | ||
{new Date(event.block.timestamp * 1000).toISOString().split("T")[0]} | ||
</div> | ||
<div> | ||
<span className="font-bold">Amount: </span>Ξ{" "} | ||
{ethers.utils.formatEther(event.args.amount.toString())} | ||
</div> | ||
<div>{event.args.reason}</div> | ||
<hr className="my-8" /> | ||
</div> | ||
))} | ||
</div> | ||
) : ( | ||
<p>No contributions</p> | ||
)} | ||
</ul> | ||
</div> | ||
</label> | ||
</label> | ||
</> | ||
); | ||
}; | ||
|
||
export default Members; |