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

Feat/more chains #109

Merged
merged 24 commits into from
Jun 19, 2024
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
1a24fb7
Remove localhost types + logic from NetworksDropdown.tsx
portdeveloper Jun 13, 2024
33ee5d0
Add "See All Chains" option to NetworksDropdown.tsx
portdeveloper Jun 13, 2024
7a1adfc
Get all chains in getTargetNetworks, create a new function for popula…
portdeveloper Jun 13, 2024
8f28f9d
Use getPopularTargetNetworks to render the list in NetworksDropdown.tsx
portdeveloper Jun 13, 2024
e652a45
Fix not setting the address on localhost
portdeveloper Jun 13, 2024
e9365cb
Focus the search bar as soon as the all chains modal opens
portdeveloper Jun 13, 2024
977deb2
Fix padding + width problems on All chains modal
portdeveloper Jun 13, 2024
66bd2a9
Add hardhat to scaffoldConfig
portdeveloper Jun 13, 2024
b4f0954
Add localhost to NetworksDropdown in its own group
portdeveloper Jun 13, 2024
51114ed
Disable the link on the address if the chain has the id 31337
portdeveloper Jun 13, 2024
c134b0a
Show localhost as chain for both hardhat and foundry
portdeveloper Jun 13, 2024
f99b8bb
Use a ref for modal
portdeveloper Jun 18, 2024
eb9e848
Make the modal close button a simple X icon that changes color on hover
portdeveloper Jun 18, 2024
240a701
Rename all chains to other chains, move it to the bottom, exclude cha…
portdeveloper Jun 18, 2024
80e1e99
Show localhost as chain name if chain id is 31337 rainbowkitcustomcon…
portdeveloper Jun 18, 2024
cf4681e
Filter chains according to id as well as the name in other chains modal
portdeveloper Jun 18, 2024
ab5abd3
Add the selected network from other chains to dropdown
portdeveloper Jun 18, 2024
f36303a
Merge remote-tracking branch 'origin/main' into feat/more-chains
portdeveloper Jun 18, 2024
12c63b5
Specify options for other chains in the reducer
portdeveloper Jun 18, 2024
b238667
Exclude chains from other chains if they are in the dropdown
portdeveloper Jun 18, 2024
ef30fa7
Return if seeAllModalRef.current or searchInputRef.current is empty i…
portdeveloper Jun 18, 2024
50b3430
Reset searchbar input when closing modal
portdeveloper Jun 18, 2024
4094f21
Use "1" as the default value of network state
portdeveloper Jun 19, 2024
f414e5c
use mainnet.id for default instead of hardcoding id
technophile-04 Jun 19, 2024
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
166 changes: 127 additions & 39 deletions packages/nextjs/components/NetworksDropdown.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { useEffect, useState } from "react";
import { ReactNode, useEffect, useRef, useState } from "react";
import Image from "next/image";
import * as chains from "@wagmi/core/chains";
import { useTheme } from "next-themes";
import Select, { OptionProps, components } from "react-select";
import { getTargetNetworks } from "~~/utils/scaffold-eth";
import Select, { MultiValue, OptionProps, SingleValue, components } from "react-select";
import { EyeIcon, WrenchScrewdriverIcon } from "@heroicons/react/24/outline";
import { getPopularTargetNetworks } from "~~/utils/scaffold-eth";

type Options = {
value: number;
value: number | string;
label: string;
icon?: string;
icon?: string | ReactNode;
};
type GroupedOptions = Record<
"mainnet" | "testnet" | "localhost",
Expand All @@ -17,15 +19,25 @@ type GroupedOptions = Record<
}
>;

const networks = getTargetNetworks();
const getIconComponent = (iconName: string | undefined) => {
switch (iconName) {
case "EyeIcon":
return <EyeIcon className="h-6 w-6 mr-2 text-gray-500" />;
case "localhost":
return <WrenchScrewdriverIcon className="h-6 w-6 mr-2 text-gray-500" />;
default:
return <Image src={iconName || "/mainnet.svg"} alt="default icon" width={24} height={24} className="mr-2" />;
}
};

const networks = getPopularTargetNetworks();
const groupedOptions = networks.reduce<GroupedOptions>(
(groups, network) => {
// Handle the case for localhost
if (network.id === 31337) {
groups.localhost.options.push({
value: network.id,
label: network.name,
icon: network.icon,
label: "31337 - Localhost",
icon: getIconComponent("localhost"),
});
return groups;
}
Expand All @@ -47,11 +59,23 @@ const groupedOptions = networks.reduce<GroupedOptions>(
},
);

groupedOptions.mainnet.options.push({
value: "see-all",
label: "See All Chains",
icon: "EyeIcon",
});

const allChains = Object.values(chains).map(chain => ({
value: chain.id,
portdeveloper marked this conversation as resolved.
Show resolved Hide resolved
label: chain.name,
icon: "",
}));

const { Option } = components;
const IconOption = (props: OptionProps<Options>) => (
<Option {...props}>
<div className="flex items-center">
<Image src={props.data.icon || "/mainnet.svg"} alt={props.data.label} width={24} height={24} className="mr-2" />
{typeof props.data.icon === "string" ? getIconComponent(props.data.icon) : props.data.icon}
{props.data.label}
</div>
</Option>
Expand All @@ -60,6 +84,9 @@ const IconOption = (props: OptionProps<Options>) => (
export const NetworksDropdown = ({ onChange }: { onChange: (options: any) => any }) => {
const [isMobile, setIsMobile] = useState(false);
const { resolvedTheme } = useTheme();
const [selectedOption, setSelectedOption] = useState<SingleValue<Options>>(groupedOptions.mainnet.options[0]);
const [searchTerm, setSearchTerm] = useState("");
const searchInputRef = useRef<HTMLInputElement>(null);

const isDarkMode = resolvedTheme === "dark";

Expand All @@ -80,36 +107,97 @@ export const NetworksDropdown = ({ onChange }: { onChange: (options: any) => any
}
}, []);

const handleSelectChange = (newValue: SingleValue<Options> | MultiValue<Options>) => {
const selected = newValue as SingleValue<Options>;
if (selected?.value === "see-all") {
const modal = document.getElementById("see-all-modal") as HTMLDialogElement;
modal.showModal();
portdeveloper marked this conversation as resolved.
Show resolved Hide resolved
if (searchInputRef.current) {
searchInputRef.current.focus();
}
} else {
setSelectedOption(selected);
onChange(selected);
}
};

const filteredChains = allChains.filter(chain => chain.label.toLowerCase().includes(searchTerm.toLowerCase()));

portdeveloper marked this conversation as resolved.
Show resolved Hide resolved
if (!mounted) return null;
return (
<Select
defaultValue={groupedOptions["mainnet"].options[0]}
instanceId="network-select"
options={Object.values(groupedOptions)}
onChange={onChange}
components={{ Option: IconOption }}
isSearchable={!isMobile}
className="max-w-xs relative text-sm w-44"
theme={theme => ({
...theme,
colors: {
...theme.colors,
primary25: isDarkMode ? "#401574" : "#efeaff",
primary50: isDarkMode ? "#551d98" : "#c1aeff",
primary: isDarkMode ? "#BA8DE8" : "#551d98",
neutral0: isDarkMode ? "#130C25" : theme.colors.neutral0,
neutral80: isDarkMode ? "#ffffff" : theme.colors.neutral80,
},
})}
styles={{
menuList: provided => ({ ...provided, maxHeight: 280, overflow: "auto" }),
control: provided => ({ ...provided, borderRadius: 12 }),
indicatorSeparator: provided => ({ ...provided, display: "none" }),
menu: provided => ({
...provided,
border: `1px solid ${isDarkMode ? "#555555" : "#a3a3a3"}`,
}),
}}
/>
<>
<Select
value={selectedOption}
defaultValue={groupedOptions["mainnet"].options[0]}
instanceId="network-select"
options={Object.values(groupedOptions)}
onChange={handleSelectChange}
components={{ Option: IconOption }}
isSearchable={!isMobile}
className="max-w-xs relative text-sm w-44"
theme={theme => ({
...theme,
colors: {
...theme.colors,
primary25: isDarkMode ? "#401574" : "#efeaff",
primary50: isDarkMode ? "#551d98" : "#c1aeff",
primary: isDarkMode ? "#BA8DE8" : "#551d98",
neutral0: isDarkMode ? "#130C25" : theme.colors.neutral0,
neutral80: isDarkMode ? "#ffffff" : theme.colors.neutral80,
},
})}
styles={{
menuList: provided => ({ ...provided, maxHeight: 280, overflow: "auto" }),
control: provided => ({ ...provided, borderRadius: 12 }),
indicatorSeparator: provided => ({ ...provided, display: "none" }),
menu: provided => ({
...provided,
border: `1px solid ${isDarkMode ? "#555555" : "#a3a3a3"}`,
}),
}}
/>
<dialog id="see-all-modal" className="modal">
<div className="flex flex-col modal-box justify-center px-12 h-3/4 sm:w-1/2 max-w-5xl bg-base-200">
<div className="flex justify-between items-center mb-6">
<h3 className="font-bold text-xl">All Chains</h3>
<div className="modal-action mt-0">
<button
className="btn btn-error"
onClick={() => (document.getElementById("see-all-modal") as HTMLDialogElement)?.close()}
>
Close
</button>
</div>
portdeveloper marked this conversation as resolved.
Show resolved Hide resolved
</div>
<input
type="text"
placeholder="Search chains..."
className="input input-bordered w-full mb-4 bg-neutral"
value={searchTerm}
onChange={e => setSearchTerm(e.target.value)}
ref={searchInputRef}
/>

<div className="flex flex-wrap content-start justify-center gap-4 overflow-y-auto h-5/6 p-2">
{filteredChains.map(option => (
<div
key={`${option.label}-${option.value}`}
className="card shadow-md bg-base-100 cursor-pointer h-28 w-60 text-center"
onClick={() => {
setSelectedOption(option);
onChange(option);
(document.getElementById("see-all-modal") as HTMLDialogElement)?.close();
}}
>
<div className="card-body flex flex-col justify-center items-center p-4">
<span className="text-sm font-semibold">Chain Id: {option.value}</span>
<span className="text-sm">{option.label}</span>
</div>
</div>
))}
</div>
</div>
</dialog>
</>
);
};
7 changes: 1 addition & 6 deletions packages/nextjs/components/scaffold-eth/Address.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { useEffect, useState } from "react";
import Link from "next/link";
import { CopyToClipboard } from "react-copy-to-clipboard";
import { Address as AddressType, isAddress } from "viem";
import { hardhat } from "viem/chains";
Expand Down Expand Up @@ -87,12 +86,8 @@ export const Address = ({ address, disableAddressLink, format, size = "base" }:
size={(blockieSizeMap[size] * 24) / blockieSizeMap["base"]}
/>
</div>
{disableAddressLink ? (
{disableAddressLink || targetNetwork.id === hardhat.id ? (
<span className={`ml-1.5 text-${size} font-normal`}>{displayAddress}</span>
) : targetNetwork.id === hardhat.id ? (
<span className={`ml-1.5 text-${size} font-normal`}>
<Link href={blockExplorerAddressLink}>{displayAddress}</Link>
</span>
technophile-04 marked this conversation as resolved.
Show resolved Hide resolved
) : (
<a
className={`ml-1.5 text-${size} font-normal`}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,9 @@ export const ContractUI = ({ className = "", initialContractData }: ContractUIPr
{mainNetwork && (
<p className="my-0 text-sm">
<span className="font-bold">Network</span>:{" "}
<span style={{ color: networkColor }}>{mainNetwork.name}</span>
<span style={{ color: networkColor }}>
{mainNetwork.id == 31337 ? "Localhost" : mainNetwork.name}
</span>
</p>
)}
</div>
Expand Down
1 change: 1 addition & 0 deletions packages/nextjs/pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ const Home: NextPage = () => {
if (isAddress(verifiedContractAddress)) {
if (network === "31337") {
setActiveTab(TabName.addressAbi);
setLocalAbiContractAddress(verifiedContractAddress);
return;
}
fetchContractAbi();
Expand Down
1 change: 1 addition & 0 deletions packages/nextjs/scaffold.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const scaffoldConfig = {
chains.zkSyncTestnet,
chains.scroll,
chains.scrollSepolia,
chains.hardhat,
technophile-04 marked this conversation as resolved.
Show resolved Hide resolved
],

// The interval at which your front-end polls the RPC servers for new data
Expand Down
10 changes: 10 additions & 0 deletions packages/nextjs/utils/scaffold-eth/networks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,16 @@ export function getBlockExplorerAddressLink(network: chains.Chain, address: stri
* @returns targetNetworks array containing networks configured in scaffold.config including extra network metadata
*/
export function getTargetNetworks(): ChainWithAttributes[] {
// Get all chains from viem/chains
const allChains: ChainWithAttributes[] = Object.values(chains).map(chain => ({
...chain,
...NETWORKS_EXTRA_DATA[chain.id],
}));

return allChains;
}

export function getPopularTargetNetworks(): ChainWithAttributes[] {
return scaffoldConfig.targetNetworks.map(targetNetwork => ({
...targetNetwork,
...NETWORKS_EXTRA_DATA[targetNetwork.id],
Expand Down
Loading