Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extract the dropdown menu from VRF v2 cost calculator #1424

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 25 additions & 7 deletions src/features/vrf/v2/components/CostTable.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { network, vrfChain } from "~/features/vrf/v2/data"
import "./costTable.css"
import { useEffect, useReducer } from "preact/hooks"
import { useEffect, useReducer, useState } from "preact/hooks"
import { BigNumber, utils } from "ethers"
import button from "@chainlink/design-system/button.module.css"
import useQueryString from "~/hooks/useQueryString"

interface Props {
mainChain: vrfChain
chain: network
method: "subscription" | "directFunding"
options: vrfChain[]
}

interface directFundingResponse {
Expand Down Expand Up @@ -139,8 +139,11 @@ const reducer = (state: State, action: Action) => {

const cache: Cache = {}

export const CostTable = ({ mainChain, chain, method }: Props) => {
export const CostTable = ({ options, method }: Props) => {
const [state, dispatch] = useReducer(reducer, initialState)
const [mainChain, setMainChain] = useState<vrfChain | null>(null)
const [chain, setChain] = useState<network | null>(null)
const [network] = useQueryString("network")

const getDataResponse = async (mainChainName: string, networkName: string): Promise<dataResponse> => {
const cacheKey = `${mainChainName}-${networkName === mainChainName ? chain.type : networkName}-${method}`
Expand All @@ -164,11 +167,23 @@ export const CostTable = ({ mainChain, chain, method }: Props) => {
}

useEffect(() => {
const mainChainName =
let mainChainName, networkName
if (typeof network === "string") {
mainChainName = network.split("-")[0]
networkName = network.split("-")[1]
const newMainChain = options.filter((chain) => chain.name.toLowerCase() === mainChainName)[0]
setMainChain(newMainChain)
const newChain = newMainChain.nets.filter((network) => network.name.toLowerCase() === networkName)[0]
setChain(newChain)
}
if (!mainChain || !chain) {
return
}
mainChainName =
mainChain.name === "BNB Chain"
? mainChain.name.replace("Chain", "").replace(" ", "").toLowerCase()
: mainChain.name.toLowerCase()
const networkName =
networkName =
chain.name === "BNB Chain"
? chain.name.replace("Chain", "").replace(" ", "").toLowerCase()
: chain.name.toLowerCase()
Expand Down Expand Up @@ -221,7 +236,7 @@ export const CostTable = ({ mainChain, chain, method }: Props) => {
})

return () => dispatch({ type: "SET_LOADING", payload: false })
}, [method, mainChain, chain])
}, [method, network])

const handleRadioChange = (event) => {
dispatch({ type: "SET_CURRENT_GAS_LANE", payload: parseInt(event.target.value) })
Expand Down Expand Up @@ -471,6 +486,9 @@ export const CostTable = ({ mainChain, chain, method }: Props) => {
return res
}

if (!mainChain || !chain) {
return
}
if (state.isLoading) {
return <p className="loading-text">Data is being fetched. Please wait a moment...</p>
} else {
Expand Down
134 changes: 134 additions & 0 deletions src/features/vrf/v2/components/Dropdown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import { useEffect, useMemo, useRef, useState } from "preact/hooks"
import "./dropdown.css"
import { vrfChain, network } from "~/features/vrf/v2/data"
import { RefObject } from "preact"
import useQueryString from "~/hooks/useQueryString"

interface Props {
placeholder?: string
options: vrfChain[]
}

export const Dropdown = ({ placeholder, options }: Props) => {
const [network, setNetwork] = useQueryString("network")
const [searchValue, setSearchValue] = useState<string>("")
const [showMenu, setShowMenu] = useState<boolean>(false)
const [showSubMenu, setShowSubMenu] = useState<number>(-1)
const wrapperRef = useRef(null)

const useOutsideAlerter = (ref: RefObject<HTMLDivElement>) => {
useEffect(() => {
/**
* quit menu if click outside of dropdown
*/
function handleClickOutside(event: MouseEvent) {
if (ref.current && event.target instanceof Node && !ref.current.contains(event.target)) {
setShowMenu(false)
setShowSubMenu(-1)
}
}
// Bind the event listener
document.addEventListener("mousedown", handleClickOutside)

return () => {
// Unbind the event listener on clean up
document.removeEventListener("mousedown", handleClickOutside)
}
}, [ref])
}

const matchingOptions: vrfChain[] = useMemo(() => {
const formattedSearchValue = searchValue.replaceAll(" ", "")
const splittedSearchValueArr = formattedSearchValue.split("-")
if (splittedSearchValueArr.length >= 2) {
return options
}
return options.filter((chain: vrfChain) => {
return searchValue === "" ? chain : chain.name.toLowerCase().includes(searchValue.toLowerCase())
})
}, [searchValue, options])

const handleSelectedChain = (net: network, chain: vrfChain) => {
let res: string = chain.name + " - "
if (chain.name === net.name) {
res += net.type
} else {
res += net.name
}

setSearchValue(res)
}

const handleInputChange = (event: Event) => {
const { target } = event
if (target instanceof HTMLInputElement) {
const inputValue = target.value
setSearchValue(inputValue)
}
}

useOutsideAlerter(wrapperRef)

return (
<div className="dropdown">
<div className="dropdown-btn-container">
<div style={{ position: "relative" }}>
<input
className="dropInput"
placeholder={placeholder}
value={searchValue}
onFocus={() => {
setShowMenu(true)
}}
onInput={handleInputChange}
/>
<img
src="https://smartcontract.imgix.net/icons/Caret2.svg"
alt="Caret icon"
style={{ position: "absolute", top: "30%", right: 0, transform: "translateX(-50%)" }}
/>
</div>
</div>
<div className="dropdown-content-container">
<div
className={showMenu && matchingOptions.length ? "dropdown-content show" : "dropdown-content"}
ref={wrapperRef}
>
{matchingOptions &&
matchingOptions.map((item: vrfChain, index: number) => (
<div className="dropdown-item-container">
<div
className="dropown-main-menu-content"
onMouseEnter={() => {
setShowSubMenu(index)
}}
>
<img src={item.img} width="20" height="20" />
<a className={showMenu ? "show" : "nothing"}>{item.name}</a>
</div>
<div className="dropdown-sub-menu-content">
{showSubMenu === index &&
item.nets &&
item.nets.length > 0 &&
item.nets.map((network: network) => (
<div className="subdropdown-menu-container">
<a
onClick={() => {
setNetwork(`${item.name.toLowerCase()}-${network.name.toLowerCase()}`)
handleSelectedChain(network, item)
setShowMenu(false)
setShowSubMenu(-1)
}}
>
{network.name !== item.name ? network.name : network.type}
</a>
</div>
))}
</div>
</div>
))}
</div>
</div>
</div>
)
}
131 changes: 4 additions & 127 deletions src/features/vrf/v2/components/DropdownMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { useEffect, useMemo, useRef, useState } from "preact/hooks"
import "./dropdown.css"
import { CostTable } from "./CostTable"
import { vrfChain, network } from "~/features/vrf/v2/data"
import { RefObject } from "preact"
import { Dropdown } from "./Dropdown"
import { vrfChain } from "~/features/vrf/v2/data"

interface Props {
placeholder?: string
Expand All @@ -11,132 +10,10 @@ interface Props {
}

export const DropDownMenu = ({ placeholder = "Select a network...", options, method }: Props) => {
const [selectedMainChain, setSelectedMainChain] = useState<vrfChain | null>(null)
const [selectedChain, setSelectedChain] = useState<network | null>(null)
const [selectedNet, setSelectNet] = useState<string>(placeholder)
const [searchValue, setSearchValue] = useState<string>("")
const [showMenu, setShowMenu] = useState<boolean>(false)
const [showSubMenu, setShowSubMenu] = useState<number>(-1)
const wrapperRef = useRef(null)

const useOutsideAlerter = (ref: RefObject<HTMLDivElement>) => {
useEffect(() => {
/**
* quit menu if click outside of dropdown
*/
function handleClickOutside(event: MouseEvent) {
if (ref.current && event.target instanceof Node && !ref.current.contains(event.target)) {
setShowMenu(false)
setShowSubMenu(-1)
}
}
// Bind the event listener
document.addEventListener("mousedown", handleClickOutside)

return () => {
// Unbind the event listener on clean up
document.removeEventListener("mousedown", handleClickOutside)
}
}, [ref])
}

const matchingOptions: vrfChain[] = useMemo(() => {
const formattedSearchValue = searchValue.replaceAll(" ", "")
const splittedSearchValueArr = formattedSearchValue.split("-")
if (splittedSearchValueArr.length >= 2) {
return options
}
return options.filter((chain: vrfChain) => {
return searchValue === "" ? chain : chain.name.toLowerCase().includes(searchValue.toLowerCase())
})
}, [searchValue, options])

const handleSelectedChain = (net: network, chain: vrfChain) => {
let res: string = chain.name + " - "
if (chain.name === net.name) {
res += net.type
} else {
res += net.name
}

setSelectNet(res)
setSearchValue(res)
}

const handleInputChange = (event: Event) => {
const { target } = event
if (target instanceof HTMLInputElement) {
const inputValue = target.value
setSearchValue(inputValue)
}
}

useOutsideAlerter(wrapperRef)

return (
<div className="main-container">
<div className="dropdown">
<div className="dropdown-btn-container">
<div style={{ position: "relative" }}>
<input
className="dropInput"
placeholder={placeholder}
value={searchValue}
onFocus={() => {
setShowMenu(true)
}}
onInput={handleInputChange}
/>
<img
src="https://smartcontract.imgix.net/icons/Caret2.svg"
alt="Caret icon"
style={{ position: "absolute", top: "30%", right: 0, transform: "translateX(-50%)" }}
/>
</div>
</div>
<div className="dropdown-content-container">
<div
className={showMenu && matchingOptions.length ? "dropdown-content show" : "dropdown-content"}
ref={wrapperRef}
>
{matchingOptions &&
matchingOptions.map((item: vrfChain, index: number) => (
<div className="dropdown-item-container">
<div
className="dropown-main-menu-content"
onMouseEnter={() => {
setShowSubMenu(index)
}}
>
<img src={item.img} width="20" height="20" />
<a className={showMenu ? "show" : "nothing"}>{item.name}</a>
</div>
<div className="dropdown-sub-menu-content">
{showSubMenu === index &&
item.nets &&
item.nets.length > 0 &&
item.nets.map((network: network) => (
<div className="subdropdown-menu-container">
<a
onClick={() => {
setSelectedChain(network)
setSelectedMainChain(item)
handleSelectedChain(network, item)
setShowMenu(false)
setShowSubMenu(-1)
}}
>
{network.name !== item.name ? network.name : network.type}
</a>
</div>
))}
</div>
</div>
))}
</div>
</div>
</div>
{selectedNet !== placeholder && <CostTable method={method} mainChain={selectedMainChain} chain={selectedChain} />}
<Dropdown placeholder={placeholder} options={options} />
<CostTable method={method} options={options} />
</div>
)
}
Loading
Loading