-
{STAKING_BANNER_TITLE}
-
{STAKING_BANNER_TEXT}
+
+
+
+ {isRoundView
+ ? isClaimPeriod
+ ? STAKING_BANNER_TITLE_ROUND_VIEW_CLAIM_PERIOD
+ : STAKING_BANNER_TITLE_ROUND_VIEW
+ : isClaimPeriod
+ ? STAKING_BANNER_TITLE_CLAIM_PERIOD
+ : STAKING_BANNER_TITLE}
+
+ {!isRoundView && (
+
+ {isClaimPeriod
+ ? STAKING_BANNER_TEXT_CLAIM_PERIOD
+ : STAKING_BANNER_TEXT}
+
+ )}
{children}
diff --git a/packages/grant-explorer/src/features/round/ViewProjectDetails/components/StakingBannerAndModal/StakingBannerAndModal.tsx b/packages/grant-explorer/src/features/round/ViewProjectDetails/components/StakingBannerAndModal/StakingBannerAndModal.tsx
index 156074f2b4..634811a7de 100644
--- a/packages/grant-explorer/src/features/round/ViewProjectDetails/components/StakingBannerAndModal/StakingBannerAndModal.tsx
+++ b/packages/grant-explorer/src/features/round/ViewProjectDetails/components/StakingBannerAndModal/StakingBannerAndModal.tsx
@@ -7,15 +7,16 @@ import { useDonationPeriod } from "./hooks/useDonationPeriod";
import { StakingButton } from "./StakingButton";
import { StakingCountDownLabel } from "./StakingCountDownLabel";
-const STAKING_APP_URL = "https://staking-hub-mu.vercel.app"; // TODO: from env
+const STAKING_APP_URL = process.env.REACT_APP_STAKING_APP;
-const COUNTDOWN_DAYS = 3;
-const DAY_IN_MILLISECONDS = 24 * 60 * 60 * 1000;
-const COUNTDOWN_STARTS_IN_MILLISECONDS = COUNTDOWN_DAYS * DAY_IN_MILLISECONDS;
const COUNTDOWN_LABEL = "Staking begins in";
const COUNTDOWN_LIMIT_MINUTES = 3;
-export const StakingBannerAndModal = () => {
+export const StakingBannerAndModal = ({
+ isRoundView,
+}: {
+ isRoundView?: boolean;
+}) => {
const [isOpen, setIsOpen] = useState(false);
const {
@@ -24,12 +25,16 @@ export const StakingBannerAndModal = () => {
applicationId: paramApplicationId,
} = useProjectDetailsParams();
- const applicationId = paramApplicationId.includes("-")
+ const applicationId = paramApplicationId?.includes("-")
? paramApplicationId.split("-")[1]
: paramApplicationId;
const stakeProjectUrl = `${STAKING_APP_URL}/#/staking-round/${chainId}/${roundId}?id=${applicationId}`;
+ const stakeRoundUrl = `${STAKING_APP_URL}/#/staking-round/${chainId}/${roundId}`;
+
+ const claimRewardsUrl = `${STAKING_APP_URL}/#/claim-rewards`;
+
const handleCloseModal = useCallback(() => {
setIsOpen(false);
}, []);
@@ -43,6 +48,15 @@ export const StakingBannerAndModal = () => {
handleCloseModal();
}, [handleCloseModal, stakeProjectUrl]);
+ const handleStakeRound = useCallback(() => {
+ window.open(stakeRoundUrl, "_blank");
+ handleCloseModal();
+ }, [handleCloseModal, stakeRoundUrl]);
+
+ const handleClaimRewards = useCallback(() => {
+ window.open(claimRewardsUrl, "_blank");
+ }, [claimRewardsUrl]);
+
const chainIdNumber = chainId ? parseInt(chainId, 10) : 0;
const isStakable = useIsStakable({
@@ -50,26 +64,41 @@ export const StakingBannerAndModal = () => {
roundId,
});
- const { isDonationPeriod, timeToDonationStart } = useDonationPeriod({
- chainId: chainIdNumber,
- roundId,
- applicationId,
- refreshInterval: 60 * 1000, // 1 minute
- });
+ const { isDonationPeriod, timeToDonationStart, timeToDonationEnd } =
+ useDonationPeriod({
+ chainId: chainIdNumber,
+ roundId,
+ refreshInterval: 60 * 1000, // 1 minute
+ });
const isCountDownToStartPeriod =
- timeToDonationStart &&
- timeToDonationStart.totalMilliseconds > 0 &&
- timeToDonationStart.totalMilliseconds < COUNTDOWN_STARTS_IN_MILLISECONDS;
+ timeToDonationStart && timeToDonationStart.totalMilliseconds > 0;
+ const isRoundEnded =
+ timeToDonationEnd && timeToDonationEnd.totalMilliseconds < 0;
+
+ if (isStakable && isRoundEnded) {
+ return (
+
+
+
+
+
+ );
+ }
if (isStakable && isCountDownToStartPeriod) {
return (
-
+
@@ -79,13 +108,14 @@ export const StakingBannerAndModal = () => {
if (isStakable && isDonationPeriod) {
return (
-
-
+
+
);
diff --git a/packages/grant-explorer/src/features/round/ViewProjectDetails/components/StakingBannerAndModal/StakingButton.tsx b/packages/grant-explorer/src/features/round/ViewProjectDetails/components/StakingBannerAndModal/StakingButton.tsx
index 2ffc10c55c..1c4faf22db 100644
--- a/packages/grant-explorer/src/features/round/ViewProjectDetails/components/StakingBannerAndModal/StakingButton.tsx
+++ b/packages/grant-explorer/src/features/round/ViewProjectDetails/components/StakingBannerAndModal/StakingButton.tsx
@@ -1,14 +1,27 @@
import { Button } from "common/src/styles";
const STAKING_BUTTON_TEXT = "Stake on this project";
-
-export const StakingButton = ({ onClick }: { onClick?: () => void }) => {
+const STAKING_BUTTON_TEXT_ROUND_VIEW = "Stake GTC";
+const STAKING_BUTTON_TEXT_CLAIM_PERIOD = "Claim rewards";
+export const StakingButton = ({
+ onClick,
+ isRoundView,
+ isClaimPeriod,
+}: {
+ onClick?: () => void;
+ isRoundView?: boolean;
+ isClaimPeriod?: boolean;
+}) => {
return (
);
};
diff --git a/packages/grant-explorer/src/features/round/ViewProjectDetails/components/StakingBannerAndModal/StakingCountDownLabel.tsx b/packages/grant-explorer/src/features/round/ViewProjectDetails/components/StakingBannerAndModal/StakingCountDownLabel.tsx
index de3c31533d..8fc09263b7 100644
--- a/packages/grant-explorer/src/features/round/ViewProjectDetails/components/StakingBannerAndModal/StakingCountDownLabel.tsx
+++ b/packages/grant-explorer/src/features/round/ViewProjectDetails/components/StakingBannerAndModal/StakingCountDownLabel.tsx
@@ -39,16 +39,24 @@ export const StakingCountDownLabel = ({
label = "Staking begins in",
timeLeft,
limitMinutes = 3,
+ isRoundView = false,
}: {
label?: string;
timeLeft: TimeRemaining;
limitMinutes?: number;
+ isRoundView?: boolean;
}) => {
if (timeLeft.totalMilliseconds <= 0) {
return null;
}
return (
-
+
{label}
{generateCountDownLabel({ ...timeLeft, limitMinutes })}
diff --git a/packages/grant-explorer/src/features/round/ViewProjectDetails/components/StakingBannerAndModal/StakingModal.tsx b/packages/grant-explorer/src/features/round/ViewProjectDetails/components/StakingBannerAndModal/StakingModal.tsx
index 6fd078efe6..4aa4d1ca04 100644
--- a/packages/grant-explorer/src/features/round/ViewProjectDetails/components/StakingBannerAndModal/StakingModal.tsx
+++ b/packages/grant-explorer/src/features/round/ViewProjectDetails/components/StakingBannerAndModal/StakingModal.tsx
@@ -3,6 +3,7 @@ import { BaseModal } from "../../../../common/BaseModal";
import { ArrowRightIcon } from "@heroicons/react/20/solid";
const TITLE = "You're about to stake GTC on this project";
+const TITLE_ROUND_VIEW = "You're about to stake GTC in this round!";
const DESCRIPTION =
"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.";
@@ -11,10 +12,10 @@ const CHECK_POINTS = [
"Earn a share of the 3% rewards pool",
];
-const Title = () => (
+const Title = ({ isRoundView }: { isRoundView: boolean }) => (
- {TITLE}
+ {isRoundView ? TITLE_ROUND_VIEW : TITLE}
{DESCRIPTION}
@@ -33,9 +34,9 @@ const CheckPoints = () => (
);
-const Content = () => (
+const Content = ({ isRoundView }: { isRoundView: boolean }) => (
-
+
);
@@ -74,15 +75,17 @@ export const StakingModal = ({
isOpen,
onClose,
onStake,
+ isRoundView,
}: {
isOpen: boolean;
onClose: () => void;
onStake: () => void;
+ isRoundView: boolean;
}) => {
return (
diff --git a/packages/grant-explorer/src/features/round/ViewProjectDetails/components/StakingBannerAndModal/hooks/useDonationPeriod.tsx b/packages/grant-explorer/src/features/round/ViewProjectDetails/components/StakingBannerAndModal/hooks/useDonationPeriod.tsx
index a38d906251..369c49894b 100644
--- a/packages/grant-explorer/src/features/round/ViewProjectDetails/components/StakingBannerAndModal/hooks/useDonationPeriod.tsx
+++ b/packages/grant-explorer/src/features/round/ViewProjectDetails/components/StakingBannerAndModal/hooks/useDonationPeriod.tsx
@@ -1,13 +1,11 @@
-import { useApplication } from "../../../../../projects/hooks/useApplication";
-import { useDataLayer } from "data-layer";
import { useEffect, useMemo, useState } from "react";
import { DonationPeriodResult, TimeRemaining } from "../types";
import { calculateDonationPeriod, isValidStringDate } from "../utils";
+import { useRoundById } from "../../../../../../context/RoundContext";
interface Params {
chainId: number;
roundId: string;
- applicationId: string;
refreshInterval?: number;
}
@@ -21,27 +19,21 @@ interface Result {
export const useDonationPeriod = ({
chainId,
roundId,
- applicationId,
refreshInterval = 5 * 60 * 1000,
}: Params): Result => {
- const dataLayer = useDataLayer();
- const { data: application } = useApplication(
- {
- chainId,
- roundId,
- applicationId,
- },
- dataLayer
- );
+ const { round } = useRoundById(chainId, roundId);
const hasValidDonationDates = useMemo(() => {
- if (!application) return false;
- const { donationsStartTime, donationsEndTime } = application?.round ?? {};
+ if (!round) return false;
+ const {
+ roundStartTime: donationsStartTime,
+ roundEndTime: donationsEndTime,
+ } = round;
return (
- isValidStringDate(donationsStartTime) &&
- isValidStringDate(donationsEndTime)
+ isValidStringDate(donationsStartTime.toISOString()) &&
+ isValidStringDate(donationsEndTime.toISOString())
);
- }, [application]);
+ }, [round]);
const [currentTime, setCurrentTime] = useState(new Date());
@@ -57,11 +49,14 @@ export const useDonationPeriod = ({
useMemo
(
() =>
calculateDonationPeriod({
- application,
+ roundDonationPeriod: {
+ roundStartTime: round?.roundStartTime.toISOString() ?? "",
+ roundEndTime: round?.roundEndTime.toISOString() ?? "",
+ },
currentTime,
hasValidDonationDates,
}),
- [application, currentTime, hasValidDonationDates]
+ [round, currentTime, hasValidDonationDates]
);
return { isDonationPeriod, timeToDonationStart, timeToDonationEnd };
};
diff --git a/packages/grant-explorer/src/features/round/ViewProjectDetails/components/StakingBannerAndModal/hooks/useIsStakable.tsx b/packages/grant-explorer/src/features/round/ViewProjectDetails/components/StakingBannerAndModal/hooks/useIsStakable.tsx
index efad9ceb5d..f12e77ebf1 100644
--- a/packages/grant-explorer/src/features/round/ViewProjectDetails/components/StakingBannerAndModal/hooks/useIsStakable.tsx
+++ b/packages/grant-explorer/src/features/round/ViewProjectDetails/components/StakingBannerAndModal/hooks/useIsStakable.tsx
@@ -6,15 +6,8 @@ const STAKABLE_ROUNDS: Array<{ chainId: number; roundId: string }> = [
{ chainId: 42161, roundId: "863" },
{ chainId: 42161, roundId: "865" },
{ chainId: 42161, roundId: "867" },
- // { chainId: 42220, roundId: "27" },
- // { chainId: 42220, roundId: "28" },
- // { chainId: 42220, roundId: "29" },
- // { chainId: 42220, roundId: "30" },
- // { chainId: 42220, roundId: "31" },
- // { chainId: 42220, roundId: "32" },
- // { chainId: 42220, roundId: "33" },
- // { chainId: 42220, roundId: "34" },
- // { chainId: 42220, roundId: "35" },
+ { chainId: 11155111, roundId: "709" },
+ { chainId: 11155111, roundId: "710" },
];
export const useIsStakable = ({
diff --git a/packages/grant-explorer/src/features/round/ViewProjectDetails/components/StakingBannerAndModal/utils.ts b/packages/grant-explorer/src/features/round/ViewProjectDetails/components/StakingBannerAndModal/utils.ts
index 304f4a0e95..7e0ef8ff47 100644
--- a/packages/grant-explorer/src/features/round/ViewProjectDetails/components/StakingBannerAndModal/utils.ts
+++ b/packages/grant-explorer/src/features/round/ViewProjectDetails/components/StakingBannerAndModal/utils.ts
@@ -1,6 +1,5 @@
import { isInfiniteDate } from "common";
import { DonationPeriodResult, TimeRemaining } from "./types";
-import { Application } from "data-layer";
export const isValidStringDate = (date?: string): boolean => {
return !!date && !isInfiniteDate(new Date(date));
@@ -20,17 +19,22 @@ export const calculateTimeRemaining = (
};
};
+interface RoundDonationPeriod {
+ roundStartTime: string;
+ roundEndTime: string;
+}
+
export const calculateDonationPeriod = ({
- application,
+ roundDonationPeriod,
currentTime,
hasValidDonationDates,
}: {
- application?: Application;
+ roundDonationPeriod?: RoundDonationPeriod;
currentTime: Date;
hasValidDonationDates: boolean;
}): DonationPeriodResult => {
- const { donationsStartTime, donationsEndTime } = application?.round ?? {};
- if (!hasValidDonationDates || !donationsStartTime || !donationsEndTime) {
+ const { roundStartTime, roundEndTime } = roundDonationPeriod ?? {};
+ if (!hasValidDonationDates || !roundStartTime || !roundEndTime) {
return {
isDonationPeriod: undefined,
timeToDonationStart: undefined,
@@ -38,18 +42,23 @@ export const calculateDonationPeriod = ({
};
}
- const donationsStartTimeDate = new Date(donationsStartTime);
- const donationsEndTimeDate = new Date(donationsEndTime);
+ const donationsStartTimeDate = new Date(roundStartTime);
+ const donationsEndTimeDate = new Date(roundEndTime);
const isBeforeDonationPeriod = currentTime < donationsStartTimeDate;
const isAfterDonationPeriod = currentTime > donationsEndTimeDate;
const isDonationPeriod = !isBeforeDonationPeriod && !isAfterDonationPeriod;
+ const timeToDonationEnd = calculateTimeRemaining(
+ donationsEndTimeDate,
+ currentTime
+ );
+
if (isAfterDonationPeriod) {
return {
isDonationPeriod: false,
timeToDonationStart: undefined,
- timeToDonationEnd: undefined,
+ timeToDonationEnd,
};
}
@@ -58,11 +67,6 @@ export const calculateDonationPeriod = ({
currentTime
);
- const timeToDonationEnd = calculateTimeRemaining(
- donationsEndTimeDate,
- currentTime
- );
-
return {
isDonationPeriod,
timeToDonationStart,
diff --git a/packages/grant-explorer/src/features/round/ViewRoundPage/ProjectCard.tsx b/packages/grant-explorer/src/features/round/ViewRoundPage/ProjectCard.tsx
index 8d94cc6e1c..6d0adef240 100644
--- a/packages/grant-explorer/src/features/round/ViewRoundPage/ProjectCard.tsx
+++ b/packages/grant-explorer/src/features/round/ViewRoundPage/ProjectCard.tsx
@@ -15,6 +15,7 @@ import {
import { useCartStorage } from "../../../store";
import { CartButton } from "./CartButton";
+import { useIsStakable } from "../ViewProjectDetails/components/StakingBannerAndModal/hooks/useIsStakable";
export function ProjectCard(props: {
project: Project;
@@ -28,12 +29,17 @@ export function ProjectCard(props: {
setShowCartNotification: React.Dispatch>;
crowdfundedUSD: number;
uniqueContributorsCount: number;
+ totalStaked?: number;
}) {
const { project, roundRoutePath } = props;
- const projectRecipient =
- project.recipient.slice(0, 5) + "..." + project.recipient.slice(-4);
const { projects, add, remove } = useCartStorage();
+ const isStakableRound = useIsStakable({
+ chainId: Number(props.chainId),
+ roundId: props.roundId,
+ });
+
+ const isStakingPeriodStarted = props.showProjectCardFooter;
const isAlreadyInCart = projects.some(
(cartProject) =>
@@ -41,6 +47,9 @@ export function ProjectCard(props: {
cartProject.grantApplicationId === project.grantApplicationId &&
cartProject.roundId === props.roundId
);
+ if (!project) return null;
+ const projectRecipient =
+ project.recipient.slice(0, 5) + "..." + project.recipient.slice(-4);
const cartProject = project as CartProject;
cartProject.roundId = props.roundId;
@@ -55,7 +64,7 @@ export function ProjectCard(props: {
to={`${roundRoutePath}/${project.grantApplicationId}`}
data-testid="project-detail-link"
>
-
+
+ {isStakableRound &&
+ props.totalStaked !== undefined &&
+ isStakingPeriodStarted && (
+
+ )}
@@ -131,3 +145,42 @@ export function ProjectCard(props: {
);
}
+
+const StakedAmountCard = ({ totalStaked }: { totalStaked: number }) => {
+ return (
+
+
+
+
+
+ {totalStaked}
+
+
+ GTC
+
+
+
+ Total staked
+
+
+
+ );
+};
diff --git a/packages/grant-explorer/src/features/round/ViewRoundPage/ProjectList.tsx b/packages/grant-explorer/src/features/round/ViewRoundPage/ProjectList.tsx
index 78ee3c3a69..1bab2907d6 100644
--- a/packages/grant-explorer/src/features/round/ViewRoundPage/ProjectList.tsx
+++ b/packages/grant-explorer/src/features/round/ViewRoundPage/ProjectList.tsx
@@ -1,12 +1,16 @@
import { useMemo } from "react";
import { Project, Round } from "../../api/types";
-
-import { BasicCard } from "../../common/styles";
-
import { Application, useDataLayer } from "data-layer";
import { useRoundApprovedApplications } from "../../projects/hooks/useRoundApplications";
+import { useRoundStakingSummary } from "../../projects/hooks/useRoundStakingSummary";
+import {
+ SortOption,
+ useSortApplications,
+} from "../../projects/hooks/useSortApplications";
import { ProjectCard } from "./ProjectCard";
+import { useSearchParams } from "react-router-dom";
+import { useIsStakable } from "../ViewProjectDetails/components/StakingBannerAndModal/hooks/useIsStakable";
export const ProjectList = (props: {
projects?: Project[];
@@ -20,22 +24,87 @@ export const ProjectList = (props: {
setCurrentProjectAddedToCart: React.Dispatch>;
setShowCartNotification: React.Dispatch>;
}): JSX.Element => {
- const { projects, roundRoutePath, chainId, roundId } = props;
+ const { projects: _projects, roundRoutePath, chainId, roundId } = props;
const dataLayer = useDataLayer();
+ const [searchParams] = useSearchParams();
+ const sortOption = searchParams.get("orderBy");
+ const isStakableRound = useIsStakable({
+ chainId,
+ roundId,
+ });
- const { data: applications } = useRoundApprovedApplications(
- {
- chainId,
- roundId,
- },
+ enum SortOptionEnum {
+ TOTAL_STAKED_DESC = "totalStakedDesc",
+ TOTAL_DONATIONS_DESC = "totalDonationsDesc",
+ TOTAL_CONTRIBUTORS_DESC = "totalContributorsDesc",
+ TOTAL_STAKED_ASC = "totalStakedAsc",
+ TOTAL_CONTRIBUTORS_ASC = "totalContributorsAsc",
+ TOTAL_DONATIONS_ASC = "totalDonationsAsc",
+ }
+ const params = isStakableRound
+ ? {}
+ : {
+ chainId,
+ roundId,
+ };
+
+ const { data: _applications } = useRoundApprovedApplications(
+ params,
dataLayer
);
+ const { data: poolSummary, isLoading } = useRoundStakingSummary(
+ roundId,
+ chainId.toString(),
+ isStakableRound
+ );
+
+ const _stakedApplications = useSortApplications(
+ poolSummary,
+ chainId.toString(),
+ roundId,
+ SortOptionEnum[sortOption as keyof typeof SortOptionEnum] as SortOption
+ );
+
+ const applications = useMemo(() => {
+ return _applications?.length ? _applications : (_stakedApplications ?? []);
+ }, [_stakedApplications, _applications]);
+
+ const isDonationPeriodStarted = props.showProjectCardFooter;
+
+ const LeaderboardTitle = useMemo(() => {
+ return sortOption === "TOTAL_STAKED_DESC"
+ ? "Leaderboard - Most GTC Staked"
+ : sortOption === "TOTAL_DONATIONS_DESC"
+ ? "Leaderboard - Most Donations"
+ : sortOption === "TOTAL_CONTRIBUTORS_DESC"
+ ? "Leaderboard - Most Contributors"
+ : sortOption === "TOTAL_STAKED_ASC"
+ ? "Leaderboard - Least GTC Staked"
+ : sortOption === "TOTAL_CONTRIBUTORS_ASC"
+ ? "Leaderboard - Least Contributors"
+ : sortOption === "TOTAL_DONATIONS_ASC"
+ ? "Leaderboard - Least Donations"
+ : isStakableRound && isDonationPeriodStarted
+ ? "Leaderboard - Most GTC Staked"
+ : "";
+ }, [sortOption, isStakableRound, isDonationPeriodStarted]);
+
+ const projects = useMemo(() => {
+ return (applications.map((application) => {
+ return _projects?.find(
+ (project) =>
+ project.anchorAddress?.toLowerCase() ===
+ application.anchorAddress?.toLowerCase()
+ );
+ }) ?? []) as Project[];
+ }, [applications, _projects]);
+
const applicationsMapByGrantApplicationId:
- | Map
+ | Map
| undefined = useMemo(() => {
if (!applications) return;
- const map: Map = new Map();
+ const map: Map = new Map();
applications.forEach((application) =>
map.set(application.projectId, application)
);
@@ -43,16 +112,21 @@ export const ProjectList = (props: {
}, [applications]);
return (
- <>
+
+ {LeaderboardTitle && !props.isProjectsLoading && !isLoading && (
+
+ {LeaderboardTitle}
+
+ )}
- {props.isProjectsLoading ? (
+ {props.isProjectsLoading || isLoading ? (
<>
{Array(6)
.fill("")
.map((item, index) => (
-
))}
>
@@ -61,7 +135,7 @@ export const ProjectList = (props: {
{projects.map((project) => {
return (
);
})}
@@ -91,6 +170,6 @@ export const ProjectList = (props: {
No projects
)}
- >
+
);
};
diff --git a/packages/grant-explorer/src/features/round/ViewRoundPage/RoundPage.tsx b/packages/grant-explorer/src/features/round/ViewRoundPage/RoundPage.tsx
index a9ff035cc2..a82a9f1d63 100644
--- a/packages/grant-explorer/src/features/round/ViewRoundPage/RoundPage.tsx
+++ b/packages/grant-explorer/src/features/round/ViewRoundPage/RoundPage.tsx
@@ -26,7 +26,7 @@ const builderURL = process.env.REACT_APP_BUILDER_URL;
import CartNotification from "../../common/CartNotification";
import { useAccount, useToken } from "wagmi";
import { getAddress } from "viem";
-import { DefaultLayout } from "../../common/DefaultLayout";
+import { RoundViewLayout } from "../../common/DefaultLayout";
import { getUnixTime } from "date-fns";
import { PresentationChartBarIcon } from "@heroicons/react/24/outline";
@@ -36,7 +36,10 @@ import { ProjectList } from "./ProjectList";
import { RoundStatsTabContent } from "./RoundStatsTabContent";
import { RoundTabs } from "./RoundTabs";
import { getAlloVersion } from "common/src/config";
+import { StakingBannerAndModal } from "../ViewProjectDetails/components/StakingBannerAndModal";
+import { useIsStakable } from "../ViewProjectDetails/components/StakingBannerAndModal/hooks/useIsStakable";
+import { SortDropdown } from "./SortDropdown";
const alloVersion = getAlloVersion();
export function RoundPage(props: {
@@ -54,6 +57,11 @@ export function RoundPage(props: {
const [projects, setProjects] = useState();
const [randomizedProjects, setRandomizedProjects] = useState();
const { address: walletAddress } = useAccount();
+
+ const isStakableRound = useIsStakable({
+ chainId: Number(chainId),
+ roundId,
+ });
const isSybilDefenseEnabled =
round?.roundMetadata?.quadraticFundingConfig?.sybilDefense === true ||
round?.roundMetadata?.quadraticFundingConfig?.sybilDefense !== "none";
@@ -254,7 +262,7 @@ export function RoundPage(props: {
return (
<>
-
+ }>
{showCartNotification && renderCartNotification()}
{props.isAfterRoundEndDate && (
@@ -442,24 +450,32 @@ export function RoundPage(props: {
onChange={handleTabChange}
/>
{selectedTab === 0 && (
-
-
-
) =>
- setSearchQuery(e.target.value)
- }
- />
+
+
+
+ ) =>
+ setSearchQuery(e.target.value)
+ }
+ />
+
+ {isStakableRound && props.isAfterRoundStartDate && (
+
+ )}
)}
{projectDetailsTabs[selectedTab].content}
-
+
>
);
}
diff --git a/packages/grant-explorer/src/features/round/ViewRoundPage/SortDropdown.tsx b/packages/grant-explorer/src/features/round/ViewRoundPage/SortDropdown.tsx
new file mode 100644
index 0000000000..bcd7a0a314
--- /dev/null
+++ b/packages/grant-explorer/src/features/round/ViewRoundPage/SortDropdown.tsx
@@ -0,0 +1,82 @@
+import { useSearchParams } from "react-router-dom";
+import { Dropdown, DropdownItem } from "../../common/Dropdown";
+
+type SortOption =
+ | "TOTAL_STAKED_DESC"
+ | "TOTAL_CONTRIBUTORS_DESC"
+ | "TOTAL_DONATIONS_DESC"
+ | "TOTAL_STAKED_ASC"
+ | "TOTAL_CONTRIBUTORS_ASC"
+ | "TOTAL_DONATIONS_ASC";
+
+export type RoundApplicationsSortParams = {
+ orderBy: SortOption;
+};
+
+export const toQueryString = (
+ filterParams: Partial = {}
+): string => new URLSearchParams(filterParams).toString();
+
+interface RoundApplicationsSortOption {
+ label: string;
+ orderBy: SortOption;
+}
+
+export const SORT_OPTIONS: RoundApplicationsSortOption[] = [
+ {
+ label: "Most GTC Staked",
+ orderBy: "TOTAL_STAKED_DESC",
+ },
+ {
+ label: "Most contributors",
+ orderBy: "TOTAL_CONTRIBUTORS_DESC",
+ },
+ {
+ label: "Most donations",
+ orderBy: "TOTAL_DONATIONS_DESC",
+ },
+ {
+ label: "Least GTC Staked",
+ orderBy: "TOTAL_STAKED_ASC",
+ },
+ {
+ label: "Least contributors",
+ orderBy: "TOTAL_CONTRIBUTORS_ASC",
+ },
+ {
+ label: "Least donations",
+ orderBy: "TOTAL_DONATIONS_ASC",
+ },
+];
+
+const getSortOptionFromUrlParams = (params: URLSearchParams) => {
+ const orderBy = params.get("orderBy");
+ return SORT_OPTIONS.find((option) => option.orderBy === orderBy);
+};
+
+export function SortDropdown() {
+ const [params] = useSearchParams();
+ const selected = getSortOptionFromUrlParams(params);
+ const pathname = window.location.hash.substring(1);
+
+ return (
+ (
+ {
+ e.preventDefault();
+ close();
+ // Remove any existing query parameters from pathname
+ const basePath = pathname.split("?")[0];
+ window.location.hash = `${basePath}?orderBy=${orderBy}`;
+ }}
+ >
+ {label}
+
+ )}
+ />
+ );
+}