Skip to content

Commit

Permalink
Merge branch 'main' into challenge-view
Browse files Browse the repository at this point in the history
  • Loading branch information
karlavasquez8 authored Apr 19, 2024
2 parents 8e329c5 + 8f0dbb9 commit a7c2c69
Show file tree
Hide file tree
Showing 10 changed files with 540 additions and 5 deletions.
2 changes: 2 additions & 0 deletions packages/nextjs/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { useOutsideClick } from "~~/hooks/scaffold-stark";
import { CustomConnectButton } from "~~/components/scaffold-stark/CustomConnectButton";
import Image from "next/image";
import { usePathname } from "next/navigation";
import { FaucetButton } from "~~/components/scaffold-stark/FaucetButton";

type HeaderMenuLink = {
label: string;
Expand Down Expand Up @@ -55,6 +56,7 @@ export const Header = () => {
</div>
<div className="navbar-end flex-grow pr-8 py-[8px]">
<CustomConnectButton />
<FaucetButton />
</div>
</div>
);
Expand Down
111 changes: 111 additions & 0 deletions packages/nextjs/components/scaffold-stark/Faucet.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
"use client";

import { useState } from "react";
import { Address as AddressType, devnet } from "@starknet-react/chains";
import { BanknotesIcon } from "@heroicons/react/24/outline";
import {
Address,
AddressInput,
Balance,
EtherInput,
} from "~~/components/scaffold-stark";
import { useNetwork } from "@starknet-react/core";
import { mintEth } from "~~/services/web3/faucet";

/**
* Faucet modal which lets you send ETH to any address.
*/
export const Faucet = () => {
const [loading, setLoading] = useState(false);
const [inputAddress, setInputAddress] = useState<AddressType>();
const [faucetAddress] = useState<AddressType>(
"0x78662e7352d062084b0010068b99288486c2d8b914f6e2a55ce945f8792c8b1",
);
const [sendValue, setSendValue] = useState("");

const { chain: ConnectedChain } = useNetwork();

const sendETH = async () => {
if (!faucetAddress || !inputAddress) {
return;
}

try {
setLoading(true);
await mintEth(inputAddress, sendValue);
setLoading(false);
setInputAddress(undefined);
setSendValue("");
} catch (error) {
console.error("⚡️ ~ file: Faucet.tsx:sendETH ~ error", error);
setLoading(false);
}
};

// Render only on local chain
if (ConnectedChain?.id !== devnet.id) {
return null;
}

return (
<div>
<label
htmlFor="faucet-modal"
className="btn btn-primary btn-sm font-normal gap-1"
>
<BanknotesIcon className="h-4 w-4" />
<span>Faucet</span>
</label>
<input type="checkbox" id="faucet-modal" className="modal-toggle" />
<label htmlFor="faucet-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-3">Local Faucet</h3>
<label
htmlFor="faucet-modal"
className="btn btn-ghost btn-sm btn-circle absolute right-3 top-3"
>
</label>
<div className="space-y-3">
<div className="flex space-x-4">
<div>
<span className="text-sm font-bold">From:</span>
<Address address={faucetAddress} />
</div>
<div>
<span className="text-sm font-bold pl-3">Available:</span>
<Balance address={faucetAddress} />
</div>
</div>
<div className="flex flex-col space-y-3">
<AddressInput
placeholder="Destination Address"
value={inputAddress ?? ""}
onChange={(value) => setInputAddress(value as AddressType)}
/>
<EtherInput
placeholder="Amount to send"
value={sendValue}
onChange={(value) => setSendValue(value)}
/>
<button
className="h-10 btn btn-primary btn-sm px-2 rounded-full"
onClick={sendETH}
disabled={loading}
>
{!loading ? (
<BanknotesIcon className="h-6 w-6" />
) : (
<span className="loading loading-spinner loading-sm"></span>
)}
<span>Send</span>
</button>
</div>
</div>
</label>
</label>
</div>
);
};
73 changes: 73 additions & 0 deletions packages/nextjs/components/scaffold-stark/FaucetButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
"use client";

import { useState } from "react";
import { BanknotesIcon } from "@heroicons/react/24/outline";
import { mintEth } from "~~/services/web3/faucet";
import { Address, devnet } from "@starknet-react/chains";
import { useAccount, useBalance, useNetwork } from "@starknet-react/core";
import { useTargetNetwork } from "~~/hooks/scaffold-stark/useTargetNetwork";

// Number of ETH faucet sends to an address
const NUM_OF_ETH = "1";

/**
* FaucetButton button which lets you grab eth.
*/
export const FaucetButton = () => {
const { address } = useAccount();
const { data: balance, refetch } = useBalance({
address,
watch: true,
refetchInterval: 4500,
});

const { targetNetwork } = useTargetNetwork();

const [loading, setLoading] = useState(false);

const sendETH = async () => {
if (!address) {
return;
}

try {
setLoading(true);
await mintEth(address as Address, NUM_OF_ETH);
await refetch();
setLoading(false);
} catch (error) {
console.error("⚡️ ~ file: FaucetButton.tsx:sendETH ~ error", error);
setLoading(false);
}
};

// Render only on local chain
if (targetNetwork.id !== devnet.id) {
return null;
}

const isBalanceZero = balance && balance.value === 0n;

return (
<div
className={
!isBalanceZero
? "ml-1"
: "ml-1 tooltip tooltip-bottom tooltip-secondary tooltip-open font-bold before:left-auto before:transform-none before:content-[attr(data-tip)] before:right-0"
}
data-tip="Grab funds from faucet"
>
<button
className="btn btn-secondary btn-sm px-2 rounded-full"
onClick={sendETH}
disabled={loading}
>
{!loading ? (
<BanknotesIcon className="h-4 w-4" />
) : (
<span className="loading loading-spinner loading-xs"></span>
)}
</button>
</div>
);
};
134 changes: 134 additions & 0 deletions packages/nextjs/components/scaffold-stark/Input/AddressInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import { useCallback } from "react";
import { blo } from "blo";
import { useDebounceValue } from "usehooks-ts";
import { CommonInputProps, InputBase } from "~~/components/scaffold-stark";
import { Address } from "@starknet-react/chains";
import { isAddress } from "~~/utils/scaffold-stark/common";
import Image from "next/image";

/**
* Address input with ENS name resolution
*/
export const AddressInput = ({
value,
name,
placeholder,
onChange,
disabled,
}: CommonInputProps<Address | string>) => {
// Debounce the input to keep clean RPC calls when resolving ENS names
// If the input is an address, we don't need to debounce it
const [_debouncedValue] = useDebounceValue(value, 500);
//const debouncedValue = isAddress(value) ? value : _debouncedValue;
//const isDebouncedValueLive = debouncedValue === value;

// If the user changes the input after an ENS name is already resolved, we want to remove the stale result
//const settledValue = isDebouncedValueLive ? debouncedValue : undefined;

// const {
// data: ensAddress,
// isLoading: isEnsAddressLoading,
// isError: isEnsAddressError,
// isSuccess: isEnsAddressSuccess,
// } = useEnsAddress({
// name: settledValue,
// enabled: isDebouncedValueLive && isENS(debouncedValue),
// chainId: 1,
// cacheTime: 30_000,
// });
//
//const [enteredEnsName, setEnteredEnsName] = useState<string>();
// const {
// data: ensName,
// isLoading: isEnsNameLoading,
// isError: isEnsNameError,
// isSuccess: isEnsNameSuccess,
// } = useEnsName({
// address: settledValue as Address,
// enabled: isAddress(debouncedValue),
// chainId: 1,
// cacheTime: 30_000,
// });
//
// const { data: ensAvatar, isLoading: isEnsAvtarLoading } = useEnsAvatar({
// name: ensName,
// enabled: Boolean(ensName),
// chainId: 1,
// cacheTime: 30_000,
// });

// ens => address
// useEffect(() => {
// if (!ensAddress) return;
//
// // ENS resolved successfully
// setEnteredEnsName(debouncedValue);
// onChange(ensAddress);
// }, [ensAddress, onChange, debouncedValue]);

const handleChange = useCallback(
(newValue: Address) => {
//setEnteredEnsName(undefined);
onChange(newValue);
},
[onChange],
);

// const reFocus =
// isEnsAddressError ||
// isEnsNameError ||
// isEnsNameSuccess ||
// isEnsAddressSuccess ||
// ensName === null ||
// ensAddress === null;

return (
<InputBase<Address>
name={name}
placeholder={placeholder}
// error={ensAddress === null}
value={value as Address}
onChange={handleChange}
// disabled={isEnsAddressLoading || isEnsNameLoading || disabled}
disabled={disabled}
// reFocus={reFocus}
prefix={
null
// ensName ? (
// <div className="flex bg-base-300 rounded-l-full items-center">
// {isEnsAvtarLoading && <div className="skeleton bg-base-200 w-[35px] h-[35px] rounded-full shrink-0"></div>}
// {ensAvatar ? (
// <span className="w-[35px]">
// {
// // eslint-disable-next-line
// <img className="w-full rounded-full" src={ensAvatar} alt={`${ensAddress} avatar`} />
// }
// </span>
// ) : null}
// <span className="text-accent px-2">{enteredEnsName ?? ensName}</span>
// </div>
// ) : (
// (isEnsNameLoading || isEnsAddressLoading) && (
// <div className="flex bg-base-300 rounded-l-full items-center gap-2 pr-2">
// <div className="skeleton bg-base-200 w-[35px] h-[35px] rounded-full shrink-0"></div>
// <div className="skeleton bg-base-200 h-3 w-20"></div>
// </div>
// )
// )
}
suffix={
// Don't want to use nextJS Image here (and adding remote patterns for the URL)
// eslint-disable-next-line @next/next/no-img-element
value && (
<Image
alt=""
className="!rounded-full"
src={blo(value as `0x${string}`)}
width="35"
height="35"
/>
)
}
/>
);
};
Loading

0 comments on commit a7c2c69

Please sign in to comment.