Skip to content

Commit

Permalink
feat(ui-ux): added ux for faucet (#357)
Browse files Browse the repository at this point in the history
* added loader and link to metascan

* fixing issue that txnHash does not exist on metascan yet

* reverted linking to metascan

* revert unnecessayr change for sectionDesc

* added more text

* Update apps/web/src/pages/faucet/index.tsx

Co-authored-by: Harsh R <53080940+fullstackninja864@users.noreply.github.com>

* did UI comments & ran prettier

* changed to using react-icon

* removed react-spinners

* set isLoading back to false

* ui comments

* Update apps/web/src/pages/faucet/index.tsx

Co-authored-by: Harsh R <53080940+fullstackninja864@users.noreply.github.com>

* Update apps/web/src/pages/faucet/index.tsx

Co-authored-by: Harsh R <53080940+fullstackninja864@users.noreply.github.com>

* used animate-spin

* used tailwind color

* revert isLoading value to false

* changed to divs

* will do button variants in a diff PR

* added invalid address error text

* enable button even after errors

* print default error msg

* made recaptcha dark

* minor fixes

---------

Co-authored-by: Harsh R <53080940+fullstackninja864@users.noreply.github.com>
Co-authored-by: Harsh <harshrathi.dev@gmail.com>
  • Loading branch information
3 people authored Nov 24, 2023
1 parent a0e87eb commit 39585f8
Show file tree
Hide file tree
Showing 4 changed files with 167 additions and 92 deletions.
1 change: 1 addition & 0 deletions apps/web/src/api/FaucetApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,5 @@ export interface FaucetTransactionResponse {
s?: string;
v?: string;
};
statusCode?: number
}
18 changes: 16 additions & 2 deletions apps/web/src/layouts/components/SectionDesc.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,25 @@
import clsx from "clsx";

export default function SectionDesc({ title }: { title: string }): JSX.Element {
export default function SectionDesc({
title,
customStyle,
customTextStyle,
}: {
title: string;
customStyle?: string;
customTextStyle?: string;
}): JSX.Element {
return (
<div className="flex flex-col items-center text-center my-4 mx-1 md:mx-[148px] lg:mx-0">
<div
className={clsx(
"flex flex-col items-center text-center my-4 mx-1 md:mx-[148px] lg:mx-0",
customStyle,
)}
>
<span
className={clsx(
"font-semibold text-white-50 text-sm leading-10 -tracking-[0.01em]",
customTextStyle,
)}
>
{title}
Expand Down
85 changes: 46 additions & 39 deletions apps/web/src/layouts/components/WalletAddressTextInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,50 +31,57 @@ export default function WalletAddressTextInput({
}, [walletAddress]);

return (
<div
className={clsx(
"relative group flex w-full rounded-lg p-[1px] bg-black-500 black-gradient-1-shadow backdrop-blur-[6px]",
invalidAddressInput
? "focus-within:bg-red-500"
: "focus-within:bg-lightBlue",
transitionClass,
)}
>
{!isFocused && (
<div
className={clsx(
"absolute opacity-0 inset-0 rounded-lg transition brand-gradient-1 group-hover:opacity-100",
transitionClass,
)}
/>
)}
<div className="relative flex w-full px-8 py-[22px] rounded-lg bg-black-500 black-gradient-1">
<div className="flex w-full gap-2">
<input
ref={inputRef}
onFocus={() => setIsFocused(true)}
onBlur={() => setIsFocused(false)}
placeholder="0xxxxxxx"
<div>
<div
className={clsx(
"relative group flex w-full rounded-lg p-[1px] bg-black-500 black-gradient-1-shadow backdrop-blur-[6px]",
invalidAddressInput
? "focus-within:bg-red-500"
: "focus-within:bg-lightBlue",
transitionClass,
)}
>
{!isFocused && (
<div
className={clsx(
"h-full w-full focus:outline-none bg-transparent caret-lightBlue placeholder-white-700 placeholder-opacity-50 text-white-50 text-xl tracking-[0.01em]",
"absolute opacity-0 inset-0 rounded-lg transition brand-gradient-1 group-hover:opacity-100",
transitionClass,
)}
data-testid="wallet_address_input"
onChange={changeHandler}
/>
</div>
{(walletAddress !== "" || isFocused) && (
<IoCloseCircleSharp
size={24}
onClick={() => {
setWalletAddress(""); // Clear the walletAddress
if (inputRef.current) {
inputRef.current.value = ""; // Clear the display in the input field if inputRef.current is not null
}
}}
className="text-white-50 self-center"
/>
)}
<div className="relative flex w-full px-8 py-[22px] rounded-lg bg-black-500 black-gradient-1">
<div className="flex w-full gap-2">
<input
ref={inputRef}
onFocus={() => setIsFocused(true)}
onBlur={() => setIsFocused(false)}
placeholder="0xxxxxxx"
className={clsx(
"h-full w-full focus:outline-none bg-transparent caret-lightBlue placeholder-white-700 placeholder-opacity-50 text-white-50 text-xl tracking-[0.01em]",
)}
data-testid="wallet_address_input"
onChange={changeHandler}
/>
</div>
{(walletAddress !== "" || isFocused) && (
<IoCloseCircleSharp
size={24}
onClick={() => {
setWalletAddress(""); // Clear the walletAddress
if (inputRef.current) {
inputRef.current.value = ""; // Clear the display in the input field if inputRef.current is not null
}
}}
className="text-white-50 self-center"
/>
)}
</div>
</div>
{invalidAddressInput && (
<div className="text-red-500 pt-2 text-sm">
Please input a valid Testnet DFI Address.
</div>
)}
</div>
);
}
155 changes: 104 additions & 51 deletions apps/web/src/pages/faucet/index.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,40 @@
import Container from "@components/commons/Container";
import GradientCardContainer from "@components/commons/GradientCardContainer";
import Button from "@components/commons/Button";
import ReCAPTCHA from "react-google-recaptcha";
import React, { useEffect, useState } from "react";
import React, { useState } from "react";
import { useNetwork } from "@contexts/NetworkContext";
import { NetworkConnection } from "@contexts/Environment";
import { useRouter } from "next/router";
import FaucetApi, { FaucetTransactionResponse } from "@api/FaucetApi";
import { RiLoader2Fill } from "react-icons/ri";
import { ActionButton } from "pages/address/verify/_components/StepOne";
import Page404 from "pages/404";
import SectionTitle from "../../layouts/components/SectionTitle";
import WalletAddressTextInput from "../../layouts/components/WalletAddressTextInput";

import SectionDesc from "../../layouts/components/SectionDesc";

// hide this page if not on testnet
function Loader() {
return (
<div className="flex items-center justify-center h-[24px]">
<RiLoader2Fill
className="text-white-50 animate-spin"
data-testid="spinner"
size={24}
/>
</div>
);
}

export default function Faucet() {
const { connection } = useNetwork();
const router = useRouter();
const recaptcha = React.useRef<ReCAPTCHA>(null);

const [isCaptchaSuccessful, setIsCaptchaSuccess] = useState(false);
const [validEvmAddress, setValidEvmAddress] = useState<boolean>(false);
const [walletAddress, setWalletAddress] = useState("");

const [isLoading, setIsLoading] = useState(false);
const [data, setData] = useState<FaucetTransactionResponse>();
const [errorMsg, setErrorMsg] = useState<string>();

function onCaptchaChange() {
if (recaptcha.current !== null) {
setIsCaptchaSuccess(true);
Expand All @@ -31,70 +43,111 @@ export default function Faucet() {

async function handleSendFunds(recaptchaVal: string) {
try {
setIsLoading(true);
const res = await FaucetApi.sendFundsToUser(
connection,
recaptchaVal,
walletAddress,
);
setData(res);
if(res.statusCode !== 200) {
setErrorMsg("Error occurred, please try again later")
}
} catch (error) {
setData(undefined);
} finally {
recaptcha.current?.reset();
setIsLoading(false);
setIsCaptchaSuccess(false);
}
}

useEffect(() => {
if (connection !== NetworkConnection.TestNet) {
router.push(`/404?network=${connection}`);
}
}, [connection]);
const isDisabled = isLoading || !isCaptchaSuccessful || !validEvmAddress
if (connection !== NetworkConnection.TestNet) {
return <Page404 />
}

return (
<Container className="px-1 md:px-0 mt-12">
<SectionTitle title="Testnet Faucet" />
<SectionTitle title="DeFiChain Testnet Faucet" />
<div className="text-center mb-4 font-bold text-xl text-white-50">
This faucet gives 100 testnet DFI per request
</div>
<GradientCardContainer>
<div data-testid="blocks-list" className="p-5 md:p-10">
<div className="flex flex-col md:flex-row py-6 md:py-4 justify-between md:items-center relative">
<h1 className="font-bold text-2xl text-white-50">Wallet Address</h1>
</div>
<WalletAddressTextInput
walletAddress={walletAddress}
setWalletAddress={setWalletAddress}
validEvmAddress={validEvmAddress}
setValidEvmAddress={setValidEvmAddress}
/>
<div className="py-6 flex gap-x-4 flex-row justify-end">
<ReCAPTCHA
ref={recaptcha}
sitekey={process.env.NEXT_PUBLIC_SITE_KEY || ""}
onChange={() => onCaptchaChange()}
className="text-center items-center"
/>
<Button
testId="send_tokens_btn"
label="Send Tokens"
customStyle="font-medium text-sm md:text-base !py-2 !px-4 md:!py-3 md:!px-8 disabled:opacity-50 disabled:cursor-not-allowed"
disabled={!isCaptchaSuccessful || !validEvmAddress}
onClick={() => {
if (
recaptcha.current !== null &&
recaptcha.current.getValue() !== null
) {
handleSendFunds(recaptcha.current.getValue()!);
}
}}
/>
<div>
<div data-testid="blocks-list" className="p-5 md:p-10">
<div className="flex flex-col py-2 items-start relative">
<div className="font-bold text-xl text-white-50">
Testnet DFI Address
</div>
<SectionDesc
title="Enter a Testnet DFI address to receive funds."
customStyle="mt-0"
/>
</div>
<div className="space-y-6">
<WalletAddressTextInput
walletAddress={walletAddress}
setWalletAddress={setWalletAddress}
validEvmAddress={validEvmAddress}
setValidEvmAddress={setValidEvmAddress}
/>
<ReCAPTCHA
ref={recaptcha}
sitekey={process.env.NEXT_PUBLIC_SITE_KEY || ""}
onChange={() => onCaptchaChange()}
onExpired={() => setIsCaptchaSuccess(false)}
className="flex justify-center"
theme="dark"
/>
<div className="flex justify-center">
<ActionButton
label="Send Testnet DFI"
testId="send_tokens_btn"
onClick={() => {
if (
recaptcha.current !== null &&
recaptcha.current.getValue() !== null
) {
handleSendFunds(recaptcha.current.getValue()!);
}
}}
labelStyle="font-medium text-sm"
disabled={isDisabled}
customStyle="px-5"
/>
</div>
</div>
{errorMsg && <SectionDesc title={errorMsg} />}
</div>
{isLoading ? (
<div>
<Loader />
<SectionDesc title="Sending funds..." customStyle="!my-0 pb-4" />
</div>
) : (
data?.hash && (
<div>
<SectionDesc
title="Your transaction has been sent!"
customStyle="mb-0"
/>
<SectionDesc
title="You should receive your DFI shortly."
customStyle="!my-0"
customTextStyle="font-normal text-xs"
/>
<SectionDesc title="Transaction Hash" customStyle="mb-0" />
<SectionDesc
title={data.hash}
customTextStyle="font-normal text-xs"
customStyle="!my-0 pb-4"
/>
</div>
)
)}
</div>
</GradientCardContainer>
{data?.hash && (
<section>
<SectionDesc title="Transaction success!" />
<SectionDesc title={data.hash} />
</section>
)}
{data?.message && <SectionDesc title={data?.message} />}
</Container>
);
}

0 comments on commit 39585f8

Please sign in to comment.