Skip to content

Commit 0feb022

Browse files
feat: add switchNetwork modal
1 parent 38f1194 commit 0feb022

File tree

4 files changed

+199
-98
lines changed

4 files changed

+199
-98
lines changed
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// --- Chakra Elements
2+
import {
3+
Modal,
4+
ModalOverlay,
5+
ModalContent,
6+
ModalHeader,
7+
ModalCloseButton,
8+
ModalBody,
9+
} from "@chakra-ui/react";
10+
11+
export type ToggleModalProps = {
12+
isOpen: boolean;
13+
onClose: () => void;
14+
};
15+
16+
export type BaseModalProps = ToggleModalProps & {
17+
children?: JSX.Element;
18+
title?: string;
19+
size?: string;
20+
footer?: JSX.Element;
21+
closeOnOverlayClick?: boolean;
22+
hideCloseButton?: boolean;
23+
};
24+
25+
export function BaseModal({
26+
isOpen,
27+
onClose,
28+
children,
29+
size,
30+
title,
31+
footer,
32+
closeOnOverlayClick,
33+
hideCloseButton,
34+
}: BaseModalProps): JSX.Element {
35+
return (
36+
<Modal
37+
isOpen={isOpen}
38+
onClose={onClose}
39+
closeOnOverlayClick={closeOnOverlayClick ?? true}
40+
size={size || "xl"}
41+
isCentered
42+
>
43+
<ModalOverlay />
44+
<ModalContent>
45+
<>
46+
{title && (
47+
<ModalHeader px={8} pb={1} pt={6}>
48+
{title}
49+
</ModalHeader>
50+
)}
51+
{(hideCloseButton === undefined || hideCloseButton === false) && (
52+
<ModalCloseButton mr={2} />
53+
)}
54+
<ModalBody p={0}>
55+
<div className="p-6">
56+
{/* RSX Element passed in to show desired stamp output */}
57+
{children}
58+
</div>
59+
</ModalBody>
60+
61+
{footer && <div>{footer}</div>}
62+
</>
63+
</ModalContent>
64+
</Modal>
65+
);
66+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { useNavigate } from "react-router-dom";
2+
3+
export enum ButtonVariants {
4+
primary = "primary",
5+
secondary = "secondary",
6+
outline = "outline",
7+
danger = "danger",
8+
outlineDanger = "danger-outline",
9+
}
10+
11+
interface ButtonProps {
12+
onClick?: () => void;
13+
path?: string;
14+
children: React.ReactNode;
15+
variant: ButtonVariants;
16+
disabled?: boolean;
17+
styles?: string[];
18+
dataTrackEvent?: string;
19+
}
20+
21+
function Button({
22+
onClick,
23+
path,
24+
children,
25+
variant,
26+
disabled,
27+
styles,
28+
dataTrackEvent,
29+
}: ButtonProps) {
30+
const navigate = useNavigate();
31+
32+
const clickHandler = () => {
33+
if (onClick === undefined && path !== undefined) {
34+
navigate(path);
35+
}
36+
if (onClick) onClick();
37+
};
38+
39+
return (
40+
<button
41+
disabled={disabled}
42+
onClick={clickHandler}
43+
className={`base-btn ${variant} ${styles?.join(" ")}`}
44+
type="button"
45+
data-track-event={dataTrackEvent}
46+
>
47+
{children}
48+
</button>
49+
);
50+
}
51+
52+
export default Button;
Lines changed: 52 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -1,101 +1,60 @@
1-
import { Dialog, Transition } from "@headlessui/react";
2-
import { Button } from "common/src/styles";
3-
import React, { Fragment, ReactNode, useRef } from "react";
1+
import { BaseModal } from "./BaseModal";
2+
import Button, { ButtonVariants } from "./Button";
43

5-
interface ModalProps {
6-
title?: string;
7-
body?: JSX.Element;
8-
confirmButtonText?: string;
4+
function SwitchNetworkModal({
5+
networkName,
6+
onSwitchNetwork,
7+
action,
8+
isOpen,
9+
setIsOpen,
10+
}: {
11+
networkName: string;
12+
onSwitchNetwork: () => void;
13+
action?: string;
914
isOpen: boolean;
1015
setIsOpen: React.Dispatch<React.SetStateAction<boolean>>;
11-
confirmButtonAction: () => void;
12-
cancelButtonAction?: () => void;
13-
children?: ReactNode;
14-
}
15-
16-
export default function SwitchNetworkModal({
17-
title = "Please Confirm Decision",
18-
isOpen = false,
19-
setIsOpen = () => {
20-
/**/
21-
},
22-
confirmButtonText = "Confirm",
23-
cancelButtonAction = () => setIsOpen(false),
24-
children,
25-
...props
26-
}: ModalProps) {
27-
const cancelButtonRef = useRef(null);
28-
16+
}) {
2917
return (
30-
<Transition.Root show={isOpen} as={Fragment}>
31-
<Dialog
32-
as="div"
33-
className="relative z-10"
34-
initialFocus={cancelButtonRef}
35-
onClose={setIsOpen}
36-
data-testid="confirm-modal"
37-
>
38-
<Transition.Child
39-
as={Fragment}
40-
enter="ease-out duration-300"
41-
enterFrom="opacity-0"
42-
enterTo="opacity-100"
43-
leave="ease-in duration-200"
44-
leaveFrom="opacity-100"
45-
leaveTo="opacity-0"
46-
>
47-
<div className="fixed inset-0 bg-grey-400 bg-opacity-75 transition-opacity" />
48-
</Transition.Child>
49-
50-
<div className="fixed z-10 inset-0 overflow-y-auto">
51-
<div className="flex items-end sm:items-center justify-center min-h-full p-4 text-center sm:p-0">
52-
<Transition.Child
53-
as={Fragment}
54-
enter="ease-out duration-300"
55-
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
56-
enterTo="opacity-100 translate-y-0 sm:scale-100"
57-
leave="ease-in duration-200"
58-
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
59-
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
60-
>
61-
<Dialog.Panel className="relative bg-white px-4 pt-5 pb-4 text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:max-w-md sm:w-full sm:p-6">
62-
<div className="sm:items-start">
63-
<div className="mt-3 text-center sm:mt-0 sm:text-left">
64-
<Dialog.Title
65-
as="h3"
66-
className="text-base leading-6 font-semibold text-grey-500"
67-
>
68-
{title}
69-
</Dialog.Title>
70-
<div className="mt-2">{props.body}</div>
71-
</div>
72-
</div>
73-
<div className="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
74-
<Button
75-
type="button"
76-
className="w-full inline-flex text-sm sm:ml-3 sm:w-auto"
77-
onClick={props.confirmButtonAction}
78-
data-testid={"confirm-continue"}
79-
>
80-
{confirmButtonText}
81-
</Button>
82-
<Button
83-
type="button"
84-
$variant="outline"
85-
className="w-full inline-flex text-sm sm:ml-3 sm:w-auto"
86-
onClick={cancelButtonAction}
87-
ref={cancelButtonRef}
88-
data-testid={"confirm-cancel"}
89-
>
90-
Cancel
91-
</Button>
92-
</div>
93-
</Dialog.Panel>
94-
</Transition.Child>
18+
// eslint-disable-next-line @typescript-eslint/no-empty-function
19+
<BaseModal isOpen={isOpen} onClose={() => {}} hideCloseButton>
20+
<>
21+
<div data-testid="switch-networks-modal" className="flex">
22+
<div className="text-center">
23+
<div data-testid="switch-networks-modal-title">
24+
<p className="text-primary-text text-[18px] flex justify-center p-2">
25+
Switch Networks to Continue
26+
</p>
27+
<p className="text-gitcoin-grey-400 text-[16px] flex justify-center p-2">
28+
To {action || "donate"}, you need to switch the network on your
29+
wallet to {networkName}.
30+
</p>
31+
</div>
9532
</div>
9633
</div>
97-
{children}
98-
</Dialog>
99-
</Transition.Root>
34+
<div
35+
data-testid="switch-networks-modal-button"
36+
className="w-full justify-center text-center grid grid-cols-2 gap-3"
37+
>
38+
<Button
39+
variant={ButtonVariants.outline}
40+
onClick={() => setIsOpen(false)}
41+
styles={["cancel-button"]}
42+
>
43+
<span className="inline-flex flex-1 justify-center items-center">
44+
Cancel
45+
</span>
46+
</Button>
47+
<Button
48+
styles={["p-3", "justify-center"]}
49+
onClick={onSwitchNetwork}
50+
variant={ButtonVariants.outline}
51+
>
52+
Switch Network
53+
</Button>
54+
</div>
55+
</>
56+
</BaseModal>
10057
);
10158
}
59+
60+
export default SwitchNetworkModal;

packages/grant-explorer/src/features/projects/ViewProject.tsx

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ import { config } from "../../app/wagmi";
5757
import { ethers } from "ethers";
5858
import { useNavigate } from "react-router-dom";
5959
import { Logger } from "ethers/lib/utils";
60+
import SwitchNetworkModal from "../common/SwitchNetworkModal";
61+
import { set } from "lodash";
6062

6163
const CalendarIcon = (props: React.SVGProps<SVGSVGElement>) => {
6264
return (
@@ -85,11 +87,13 @@ export default function ViewProject() {
8587
const { openConnectModal } = useConnectModal();
8688
const [showDirectAllocationModal, setShowDirectAllocationModal] =
8789
useState<boolean>(false);
90+
const [showSwitchNetworkModal, setShowSwitchNetworkModal] = useState(false);
8891
const [openProgressModal, setOpenProgressModal] = useState(false);
8992
const [openErrorModal, setOpenErrorModal] = useState(false);
9093
const [errorModalSubHeading, setErrorModalSubHeading] = useState<
9194
string | undefined
9295
>();
96+
const [hasNetworkSwitched, setHasNetworkSwitched] = useState(false);
9397

9498
const [directDonationAmount, setDirectDonationAmount] = useState<string>("");
9599
const payoutTokenOptions: TToken[] = getVotingTokenOptions(
@@ -313,6 +317,22 @@ export default function ViewProject() {
313317
]
314318
);
315319

320+
const onSwitchNetwork = () => {
321+
if (switchChain && project?.chainId) {
322+
switchChain({ chainId: project?.chainId });
323+
setHasNetworkSwitched(true);
324+
}
325+
};
326+
327+
useEffect(() => {
328+
if (hasNetworkSwitched) {
329+
if (chainId === project?.chainId) {
330+
setShowSwitchNetworkModal(false);
331+
setShowDirectAllocationModal(true);
332+
}
333+
}
334+
}, [chainId, project?.chainId, hasNetworkSwitched]);
335+
316336
const handleTabChange = (tabIndex: number) => {
317337
setSelectedTab(tabIndex);
318338
};
@@ -358,11 +378,8 @@ export default function ViewProject() {
358378
return;
359379
}
360380
if (chainId !== project?.chainId) {
361-
if (switchChain) {
362-
switchChain({ chainId: project?.chainId });
363-
}
364-
}
365-
if (chainId === project?.chainId) {
381+
setShowSwitchNetworkModal(true);
382+
} else {
366383
setShowDirectAllocationModal(true);
367384
}
368385
}}
@@ -410,6 +427,13 @@ export default function ViewProject() {
410427
function DonationModals() {
411428
return (
412429
<>
430+
<SwitchNetworkModal
431+
networkName={getChainById(project?.chainId ?? 1)?.name ?? ""}
432+
onSwitchNetwork={onSwitchNetwork}
433+
action="donate to this project"
434+
isOpen={showSwitchNetworkModal}
435+
setIsOpen={setShowSwitchNetworkModal}
436+
/>
413437
<GenericModal
414438
body={
415439
<DirectDonationModalComponent

0 commit comments

Comments
 (0)