Skip to content

Commit 1224a73

Browse files
committed
feat: implemented StakingBannerAndModal
1 parent 1736173 commit 1224a73

File tree

7 files changed

+189
-0
lines changed

7 files changed

+189
-0
lines changed

.env.example

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,3 +77,5 @@ REACT_APP_GRANT_EXPLORER=https://explorer.gitcoin.co
7777
# Coingecko
7878
REACT_APP_COINGECKO_API_KEY=
7979
# ---------------------------
80+
81+
REACT_APP_STAKING_APP=https://staking-hub-mu.vercel.app

packages/grant-explorer/src/features/round/ViewProjectDetails/ViewProjectDetails.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import {
3232
ProjectLinks,
3333
Sidebar,
3434
ProjectLogo,
35+
StakingBannerAndModal,
3536
} from "./components";
3637

3738
export default function ViewProjectDetails() {
@@ -256,6 +257,7 @@ export default function ViewProjectDetails() {
256257
</h1>
257258
</Skeleton>
258259
<ProjectLinks project={projectToRender} />
260+
<StakingBannerAndModal />
259261
<ProjectDetailsTabs
260262
selected={selectedTab}
261263
onChange={handleTabChange}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { Button } from "common/src/styles";
2+
3+
const STAKING_BANNER_TITLE =
4+
"🔥 Boost grants, earn rewards, and shape the round!";
5+
const STAKING_BANNER_TEXT =
6+
"Stake GTC during GG23 to upvote your favorite grants and increase their visibility in the round. The more you stake, the higher they rank—and the more rewards you can claim from the 3% rewards pool!";
7+
const STAKING_BUTTON_TEXT = "Stake on this project";
8+
const STAKING_BUTTON_TEXT_MOBILE = "Stake";
9+
10+
export const StakingBanner = ({ onClick }: { onClick?: () => void }) => {
11+
return (
12+
<div className="bg-[#F2FBF8] rounded-3xl p-6 flex items-center justify-between w-full gap-4">
13+
<div className="flex flex-col gap-4 font-sans text-black max-w-[609px]">
14+
<h3 className="text-2xl font-medium">{STAKING_BANNER_TITLE}</h3>
15+
<p className="text-base/[1.75rem] font-normal">{STAKING_BANNER_TEXT}</p>
16+
</div>
17+
<Button
18+
className="text-white bg-[#22635A] max-h-[40px] font-mono"
19+
onClick={onClick}
20+
>
21+
<span className="block lg:hidden whitespace-nowrap">
22+
{STAKING_BUTTON_TEXT_MOBILE}
23+
</span>
24+
<span className="hidden lg:block whitespace-nowrap">
25+
{STAKING_BUTTON_TEXT}
26+
</span>
27+
</Button>
28+
</div>
29+
);
30+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { StakingModal } from "./StakingModal";
2+
import { useCallback, useState } from "react";
3+
import { StakingBanner } from "./StakingBanner";
4+
import { useProjectDetailsParams } from "../../hooks/useProjectDetailsParams";
5+
6+
// TODO: either from metadata or from env value
7+
const STAKABLE_ROUNDS: Array<{ chainId: number; roundId: string }> = [
8+
{ chainId: 42161, roundId: "863" },
9+
{ chainId: 42161, roundId: "865" },
10+
{ chainId: 42161, roundId: "867" },
11+
{ chainId: 42220, roundId: "27" },
12+
{ chainId: 42220, roundId: "29" },
13+
{ chainId: 42220, roundId: "28" },
14+
{ chainId: 42220, roundId: "30" },
15+
{ chainId: 42220, roundId: "31" },
16+
{ chainId: 42220, roundId: "32" },
17+
];
18+
19+
export const StakingBannerAndModal = () => {
20+
const [isOpen, setIsOpen] = useState(false);
21+
22+
const { chainId, roundId, applicationId } = useProjectDetailsParams();
23+
24+
const stakingAppUrl =
25+
process.env.REACT_APP_STAKING_APP ?? "https://staking-hub-mu.vercel.app";
26+
const stakeProjectUrl = `${stakingAppUrl}/#/staking-round/${chainId}/${roundId}?id={${applicationId}}`;
27+
28+
console.log("stakeProjectUrl", stakeProjectUrl);
29+
30+
const handleCloseModal = useCallback(() => {
31+
setIsOpen(false);
32+
}, []);
33+
34+
const handleOpenModal = useCallback(() => {
35+
setIsOpen(true);
36+
}, []);
37+
38+
const handleStake = useCallback(() => {
39+
window.open(stakeProjectUrl, "_blank");
40+
handleCloseModal();
41+
}, [handleCloseModal, stakeProjectUrl]);
42+
43+
const chainIdNumber = chainId ? parseInt(chainId, 10) : 0;
44+
45+
const isStakable = STAKABLE_ROUNDS.some(
46+
(round) => round.chainId === chainIdNumber && round.roundId === roundId
47+
);
48+
49+
if (!isStakable) {
50+
return null;
51+
}
52+
53+
return (
54+
<div className="mt-2 mb-6">
55+
<StakingBanner onClick={handleOpenModal} />
56+
<StakingModal
57+
isOpen={isOpen}
58+
onClose={handleCloseModal}
59+
onStake={handleStake}
60+
/>
61+
</div>
62+
);
63+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import { Button } from "common/src/styles";
2+
import { BaseModal } from "../../../../common/BaseModal";
3+
import { ArrowRightIcon } from "@heroicons/react/20/solid";
4+
5+
const TITLE = "You're about to stake GTC on this project";
6+
const DESCRIPTION =
7+
"To complete your stake, you’ll be redirected to a new tab. Once you confirm your transaction, your support will be reflected on the round page.";
8+
9+
const CHECK_POINTS = [
10+
"Boost this project’s visibility",
11+
"Earn a share of the 3% rewards pool",
12+
];
13+
14+
const Title = () => (
15+
<div className="flex flex-col gap-2">
16+
<div className="text-3xl leading-[2.125rem] align-middle font-medium">
17+
{TITLE}
18+
</div>
19+
<div className="text-base leading-[1.625rem] align-middle font-normal">
20+
{DESCRIPTION}
21+
</div>
22+
</div>
23+
);
24+
25+
const CheckPoints = () => (
26+
<div className="flex flex-col gap-4">
27+
{CHECK_POINTS.map((point, index) => (
28+
<div
29+
key={index + point}
30+
className="text-base leading-[1.625rem] align-middle font-normal"
31+
>{`✅ ${point}`}</div>
32+
))}
33+
</div>
34+
);
35+
36+
const Content = () => (
37+
<div className="flex flex-col gap-6 font-sans text-black">
38+
<Title />
39+
<CheckPoints />
40+
</div>
41+
);
42+
43+
const ActionButtons = ({
44+
onCancel,
45+
onStake,
46+
}: {
47+
onCancel: () => void;
48+
onStake: () => void;
49+
}) => (
50+
<div className="flex justify-center gap-6 font-mono font-medium text-sm leading-6">
51+
<Button
52+
type="button"
53+
$variant="outline"
54+
className={`inline-flex text-black py-2 px-4`}
55+
onClick={onCancel}
56+
data-testid={"modal-cancel"}
57+
>
58+
Cancel
59+
</Button>
60+
<Button
61+
type="button"
62+
className={`inline-flex bg-[#22635A] text-white py-2 px-4`}
63+
onClick={onStake}
64+
data-testid={"modal-stake"}
65+
>
66+
<div className="flex items-center gap-2">
67+
Stake GTC <ArrowRightIcon className="w-4 h-4 no-shrink" />
68+
</div>
69+
</Button>
70+
</div>
71+
);
72+
73+
export const StakingModal = ({
74+
isOpen,
75+
onClose,
76+
onStake,
77+
}: {
78+
isOpen: boolean;
79+
onClose: () => void;
80+
onStake: () => void;
81+
}) => {
82+
return (
83+
<BaseModal isOpen={isOpen} onClose={onClose} size="2xl">
84+
<div className="flex flex-col gap-8">
85+
<Content />
86+
<ActionButtons onCancel={onClose} onStake={onStake} />
87+
</div>
88+
</BaseModal>
89+
);
90+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from "./StakingBannerAndModal";

packages/grant-explorer/src/features/round/ViewProjectDetails/components/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ export { ProjectLogo } from "./ProjectLogo";
66
export { Sidebar } from "./Sidebar";
77
export { ProjectStats } from "./ProjectStats";
88
export { Stat } from "./Stat";
9+
export { StakingBannerAndModal } from "./StakingBannerAndModal";

0 commit comments

Comments
 (0)