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

[#221] drep list search, filters and sorting #580

Merged
merged 1 commit into from
Mar 28, 2024
Merged
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
19 changes: 9 additions & 10 deletions govtool/frontend/src/components/atoms/StatusPill.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { Chip, ChipProps, styled } from "@mui/material";
import { cyan, errorRed, successGreen } from "@/consts";

type Status = 'Active' | 'Inactive' | 'Retired';
import { DRepStatus } from "@/models";

interface StatusPillProps {
status: Status;
status: DRepStatus;
label?: string;
size?: 'small' | 'medium';
sx?: ChipProps['sx'];
Expand All @@ -25,18 +24,18 @@ export const StatusPill = ({
);

const bgColor = {
Active: successGreen.c200,
Inactive: cyan.c100,
Retired: errorRed.c100,
[DRepStatus.Active]: successGreen.c200,
[DRepStatus.Inactive]: cyan.c100,
[DRepStatus.Retired]: errorRed.c100,
};

const textColor = {
Active: successGreen.c700,
Inactive: cyan.c500,
Retired: errorRed.c500,
[DRepStatus.Active]: successGreen.c700,
[DRepStatus.Inactive]: cyan.c500,
[DRepStatus.Retired]: errorRed.c500,
};

const StyledChip = styled(Chip)<{ status: Status }>(({ theme, status }) => ({
const StyledChip = styled(Chip)<{ status: DRepStatus }>(({ theme, status }) => ({
backgroundColor: bgColor[status],
color: textColor[status],
border: `2px solid ${theme.palette.neutralWhite}`,
Expand Down
7 changes: 7 additions & 0 deletions govtool/frontend/src/components/organisms/DRepCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { Card } from "@molecules";
import { correctAdaFormat } from "@/utils";
import { ICONS, PATHS } from "@/consts";
import { DRepData } from "@/models";
import { useSnackbar } from "@/context";

type DRepCardProps = {
dRep: DRepData;
Expand All @@ -30,6 +31,7 @@ export const DRepCard = ({
}: DRepCardProps) => {
const navigate = useNavigate();
const { t } = useTranslation();
const { addSuccessAlert } = useSnackbar();

return (
<Card
Expand Down Expand Up @@ -71,6 +73,11 @@ export const DRepCard = ({
<Box flex={1} minWidth={0}>
<Typography sx={ellipsisStyles}>{type}</Typography>
<ButtonBase
onClick={(e) => {
jdyczka marked this conversation as resolved.
Show resolved Hide resolved
navigator.clipboard.writeText(view);
addSuccessAlert(t("alerts.copiedToClipboard"));
e.stopPropagation();
}}
sx={{
gap: 1,
maxWidth: "100%",
Expand Down
6 changes: 6 additions & 0 deletions govtool/frontend/src/consts/dRepDirectory/filters.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { DRepStatus } from "@/models";

export const DREP_DIRECTORY_FILTERS = Object.values(DRepStatus).map((status) => ({
key: status,
label: status,
}));
2 changes: 2 additions & 0 deletions govtool/frontend/src/consts/dRepDirectory/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./filters";
export * from "./sorting";
14 changes: 14 additions & 0 deletions govtool/frontend/src/consts/dRepDirectory/sorting.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export const DREP_DIRECTORY_SORTING = [
{
key: "NewestRegistered",
label: "Newest registered",
},
{
key: "VotingPower",
label: "Voting power",
},
{
key: "Status",
label: "Status",
},
];
1 change: 1 addition & 0 deletions govtool/frontend/src/consts/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export * from "./externalDataModalConfig";
export * from "./colors";
export * from "./dRepActions";
export * from "./dRepDirectory";
export * from "./governanceAction";
export * from "./icons";
export * from "./images";
Expand Down
22 changes: 16 additions & 6 deletions govtool/frontend/src/hooks/queries/useGetDRepListQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,34 @@ import { UseQueryOptions, useQuery } from "react-query";

import { QUERY_KEYS } from "@consts";
import { useCardano } from "@context";
import { getDRepList } from "@services";
import { GetDRepListParams, getDRepList } from "@services";
import { DRepData } from "@/models";

export const useGetDRepListQuery = (dRepView?: string, options?: UseQueryOptions<DRepData[]>) => {
export const useGetDRepListQuery = (
params?: GetDRepListParams,
options?: UseQueryOptions<DRepData[]>
) => {
const { drepView, sort, status } = params || {};
const { pendingTransaction } = useCardano();

const { data, isLoading } = useQuery<DRepData[]>({
const { data, isLoading, isPreviousData } = useQuery<DRepData[]>({
queryKey: [
QUERY_KEYS.useGetDRepListKey,
(pendingTransaction.registerAsSoleVoter ||
pendingTransaction.registerAsDrep ||
pendingTransaction.retireAsSoleVoter ||
pendingTransaction.retireAsDrep)?.transactionHash,
dRepView
drepView,
sort,
status,
],
queryFn: () => getDRepList(dRepView),
queryFn: () => getDRepList({
...(drepView && { drepView }),
...(sort && { sort }),
...(status && { status }),
}),
...options
});

return { data, isLoading };
return { data, isLoading, isPreviousData };
};
3 changes: 3 additions & 0 deletions govtool/frontend/src/i18n/locales/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -240,13 +240,16 @@ export const en = {
automatedVotingOptions: "Automated Voting Options",
editBtn: "Edit DRep data",
delegationOptions: "Delegation Options",
filterTitle: "DRep Status",
meAsDRep: "This DRep ID is connected to your wallet",
myDelegation: "You have delegated <strong>₳ {{ada}}</strong> to:",
myDRep: "This is your DRep",
listTitle: "Find a DRep",
noConfidenceDescription:
"Select this to signal no confidence in the current constitutional committee by voting NO on every proposal and voting YES to no confidence proposals",
noConfidenceTitle: "Signal No Confidence on Every Vote",
noResultsForTheSearchTitle: "No DReps found",
noResultsForTheSearchDescription: "Please try a different search",
title: "DRep Directory",
votingPower: "Voting Power",
},
Expand Down
8 changes: 7 additions & 1 deletion govtool/frontend/src/models/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,20 @@ export interface VoterInfo {
deposit: number;
}

export enum DRepStatus {
Active = "Active",
Inactive = "Inactive",
Retired = "Retired",
}

export interface DRepData {
drepId: string;
view: string;
url: string;
metadataHash: string;
deposit: number;
votingPower: number;
status: 'Active' | 'Inactive' | 'Retired';
status: DRepStatus;
type: 'DRep' | 'SoleVoter';
}

Expand Down
2 changes: 1 addition & 1 deletion govtool/frontend/src/pages/DRepDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export const DRepDetails = ({ isConnected }: DRepDetailsProps) => {
const { delegate, isDelegating } = useDelegateTodRep();

const { currentDelegation } = useGetAdaHolderCurrentDelegationQuery(stakeKey);
const { data, isLoading } = useGetDRepListQuery(dRepParam);
const { data, isLoading } = useGetDRepListQuery({ drepView: dRepParam });
const dRep = data?.[0];

if (!dRep && isLoading) return <CircularProgress sx={{ display: 'block', mx: 'auto', mt: 4 }} />;
Expand Down
56 changes: 51 additions & 5 deletions govtool/frontend/src/pages/DRepDirectoryContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,17 @@ import { FC } from "react";
import { AutomatedVotingOptions, DRepCard } from "@organisms";
import { Typography } from "@atoms";
import { Trans, useTranslation } from "react-i18next";
import { Card, DataActionsBar } from "@molecules";
import { useCardano } from "@/context";
import {
useDataActionsBar,
useDelegateTodRep,
useGetAdaHolderCurrentDelegationQuery,
useGetAdaHolderVotingPowerQuery,
useGetDRepListQuery
} from "@/hooks";
import { correctAdaFormat, formHexToBech32, isSameDRep } from "@/utils";
import { DREP_DIRECTORY_FILTERS, DREP_DIRECTORY_SORTING } from "@/consts";

interface DRepDirectoryContentProps {
isConnected?: boolean;
Expand All @@ -25,6 +28,8 @@ export const DRepDirectoryContent: FC<DRepDirectoryContentProps> = ({
stakeKey,
} = useCardano();
const { t } = useTranslation();
const { debouncedSearchText, ...dataActionsBarProps } = useDataActionsBar();
const { chosenFilters, chosenSorting } = dataActionsBarProps;

const { delegate } = useDelegateTodRep();

Expand All @@ -33,20 +38,29 @@ export const DRepDirectoryContent: FC<DRepDirectoryContentProps> = ({
const inProgressDelegation = pendingTransaction.delegate?.resourceId;

const { data: myDRepList } = useGetDRepListQuery(
currentDelegation?.startsWith('drep') ? currentDelegation : formHexToBech32(currentDelegation),
{ drepView: currentDelegation?.startsWith('drep')
? currentDelegation
: formHexToBech32(currentDelegation) },
{ enabled: !!inProgressDelegation || !!currentDelegation }
);
const myDrep = myDRepList?.[0];
const { data: dRepList } = useGetDRepListQuery();
const { data: dRepList, isPreviousData } = useGetDRepListQuery({
drepView: debouncedSearchText,
sort: chosenSorting,
status: chosenFilters,
}, {
keepPreviousData: true,
});

if ((stakeKey && votingPower === undefined) || !dRepList) {
if (stakeKey && votingPower === undefined) {
return <CircularProgress sx={{ display: 'block', mx: 'auto', mt: 4 }} />;
}

const ada = correctAdaFormat(votingPower);

return (
<Box display="flex" flexDirection="column" gap={4}>
{/* My delegation */}
{myDrep && (
<div>
<Typography variant="title2" sx={{ mb: 2 }}>
Expand All @@ -60,6 +74,7 @@ export const DRepDirectoryContent: FC<DRepDirectoryContentProps> = ({
</div>
)}

{/* Automated voting options */}
{isConnected && (
<div>
<Typography variant="title2" sx={{ mb: 2 }}>
Expand All @@ -83,11 +98,42 @@ export const DRepDirectoryContent: FC<DRepDirectoryContentProps> = ({
</div>
)}

{/* DRep list */}
<div>
<Typography fontSize={18} fontWeight={500}>
<Typography fontSize={18} fontWeight={500} sx={{ mb: 3 }}>
{t('dRepDirectory.listTitle')}
</Typography>
<Box component="ul" p={0} display="flex" flexDirection="column" gap={3}>
<DataActionsBar
{...dataActionsBarProps}
filterOptions={DREP_DIRECTORY_FILTERS}
filtersTitle={t("dRepDirectory.filterTitle")}
sortOptions={DREP_DIRECTORY_SORTING}
/>
<Box
component="ul"
display="flex"
flexDirection="column"
gap={3}
mt={4}
p={0}
sx={{ opacity: isPreviousData ? 0.5 : 1, transition: 'opacity 0.2s' }}
>
{dRepList?.length === 0 && (
<Card
border
elevation={0}
sx={{
alignItems: 'center',
display: 'flex',
flexDirection: 'column',
gap: 1,
py: 5,
}}
>
<Typography fontSize={22}>{t('dRepDirectory.noResultsForTheSearchTitle')}</Typography>
<Typography fontWeight={400}>{t('dRepDirectory.noResultsForTheSearchDescription')}</Typography>
</Card>
)}
{dRepList?.map((dRep) =>
(isSameDRep(dRep, myDrep?.view) ? null : (
<Box key={dRep.drepId} component="li" sx={{ listStyle: 'none' }}>
Expand Down
10 changes: 8 additions & 2 deletions govtool/frontend/src/services/requests/getDRepList.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import type { DRepData } from "@models";
import { API } from "../API";

export const getDRepList = async (drepView?: string) => {
const response = await API.get<DRepData[]>("/drep/list", drepView ? { params: { drepView } } : undefined);
export type GetDRepListParams = {
drepView?: string;
sort?: string;
status?: string[];
};

export const getDRepList = async (params: GetDRepListParams) => {
const response = await API.get<DRepData[]>("/drep/list", { params });
return response.data;
};
7 changes: 4 additions & 3 deletions govtool/frontend/src/stories/StatusPill.stories.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Meta, StoryObj } from "@storybook/react";

import { StatusPill } from "@atoms";
import { DRepStatus } from "@/models";

const meta = {
title: "Example/StatusPill",
Expand All @@ -16,18 +17,18 @@ type Story = StoryObj<typeof meta>;

export const StatusPillActive: Story = {
args: {
status: "Active",
status: DRepStatus.Active,
},
};

export const StatusPillInactive: Story = {
args: {
status: "Inactive",
status: DRepStatus.Inactive,
},
};

export const StatusPillRetired: Story = {
args: {
status: "Retired",
status: DRepStatus.Retired,
},
};
Loading