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(web): starred-cases #1794

Merged
merged 8 commits into from
Dec 17, 2024
1 change: 1 addition & 0 deletions web/src/assets/svgs/icons/star.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
58 changes: 58 additions & 0 deletions web/src/components/CaseStarButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import React, { useMemo } from "react";
import styled, { css } from "styled-components";

import { Button, Tooltip } from "@kleros/ui-components-library";

import Star from "svgs/icons/star.svg";

import useIsDesktop from "hooks/useIsDesktop";
import useStarredCases from "hooks/useStarredCases";

const StyledButton = styled(Button)<{ starred: boolean }>`
background: none;
padding: 0 0 2px 0;

.button-svg {
width: 24px;
height: 24px;
margin: 0;
fill: none;

path {
stroke: ${({ theme }) => theme.secondaryPurple};
}
${({ starred }) =>
starred &&
css`
fill: ${({ theme }) => theme.secondaryPurple};
`};
}

:hover {
background: none;
}
`;

const CaseStarButton: React.FC<{ id: string }> = ({ id }) => {
const { starredCases, starCase } = useStarredCases();
const isDesktop = useIsDesktop();
const starred = useMemo(() => Boolean(starredCases.get(id)), [id, starredCases]);
const text = starred ? "Remove from favorite" : "Add to favorite";
return (
<Tooltip {...{ text }} place={isDesktop ? "top" : "bottom"}>
<StyledButton
Icon={Star}
text=""
starred={starred}
aria-label={text}
aria-checked={starred}
onClick={(e) => {
e.stopPropagation();
starCase(id);
}}
/>
</Tooltip>
);
};
Harman-singh-waraich marked this conversation as resolved.
Show resolved Hide resolved

export default CaseStarButton;
81 changes: 81 additions & 0 deletions web/src/components/FavoriteCases.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import React, { useMemo, useState } from "react";
import styled from "styled-components";

import { StandardPagination } from "@kleros/ui-components-library";

import useStarredCases from "hooks/useStarredCases";
import { isUndefined } from "utils/index";

import { DisputeDetailsFragment, useCasesQuery } from "queries/useCasesQuery";

import { responsiveSize } from "styles/responsiveSize";

import DisputeView from "components/DisputeView";
import { SkeletonDisputeCard } from "components/StyledSkeleton";

const Container = styled.div`
margin-top: ${responsiveSize(48, 80)};
`;

const Title = styled.h1`
margin-bottom: 12px;
`;

const DisputeContainer = styled.div`
--gap: 16px;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(min(100%, max(312px, (100% - var(--gap) * 2)/3)), 1fr));
align-items: stretch;
gap: var(--gap);
`;

const StyledLabel = styled.label`
display: block;
color: ${({ theme }) => theme.primaryBlue};
cursor: pointer;
margin-bottom: ${responsiveSize(16, 32)};
:hover {
color: ${({ theme }) => theme.secondaryBlue};
}
`;

const StyledPagination = styled(StandardPagination)`
margin-top: 24px;
margin-left: auto;
margin-right: auto;
`;

const FavoriteCases: React.FC = () => {
const { starredCaseIds, clearAll } = useStarredCases();

const [currentPage, setCurrentPage] = useState(1);
const casesPerPage = 3;
const totalPages = Math.ceil(starredCaseIds.length / casesPerPage);

const { data } = useCasesQuery((currentPage - 1) * casesPerPage, casesPerPage, {
id_in: starredCaseIds,
});

const disputes: DisputeDetailsFragment[] = useMemo(() => data?.disputes as DisputeDetailsFragment[], [data]);
Harman-singh-waraich marked this conversation as resolved.
Show resolved Hide resolved

return starredCaseIds.length > 0 && (isUndefined(disputes) || disputes.length > 0) ? (
<Container>
<Title>Favorite Cases</Title>
<StyledLabel onClick={clearAll}>Clear all</StyledLabel>
<DisputeContainer>
{isUndefined(disputes)
? Array.from({ length: 3 }).map((_, index) => <SkeletonDisputeCard key={index} />)
: disputes.map((dispute) => <DisputeView key={dispute.id} {...dispute} overrideIsList />)}
</DisputeContainer>
{totalPages > 1 ? (
<StyledPagination
currentPage={currentPage}
numPages={totalPages}
callback={(page: number) => setCurrentPage(page)}
/>
) : null}
</Container>
) : null;
};

export default FavoriteCases;
26 changes: 26 additions & 0 deletions web/src/hooks/useStarredCases.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { useMemo } from "react";

import { useLocalStorage } from "./useLocalStorage";

const useStarredCases = () => {
const initialValue = new Map<string, boolean>();

const [localStarredCases, setLocalStarredCases] = useLocalStorage("starredCases", Array.from(initialValue));

const starredCases = useMemo(() => new Map<string, boolean>(localStarredCases), [localStarredCases]);
Harman-singh-waraich marked this conversation as resolved.
Show resolved Hide resolved
const starredCaseIds = Array.from(starredCases.keys());

const starCase = (id: string) => {
if (starredCases.get(id)) starredCases.delete(id);
else starredCases.set(id, true);

setLocalStarredCases(Array.from(starredCases));
};
Harman-singh-waraich marked this conversation as resolved.
Show resolved Hide resolved

const clearAll = () => {
setLocalStarredCases(Array.from(initialValue));
};
return { starredCases, starredCaseIds, starCase, clearAll };
};

export default useStarredCases;
14 changes: 11 additions & 3 deletions web/src/pages/Cases/CaseDetails/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,16 @@ import { useDisputeDetailsQuery } from "queries/useDisputeDetailsQuery";

import { responsiveSize } from "styles/responsiveSize";

import CaseStarButton from "components/CaseStarButton";
import ScrollTop from "components/ScrollTop";

import Appeal from "./Appeal";
import Evidence from "./Evidence";
import MaintenanceButtons from "./MaintenanceButtons";
import Overview from "./Overview";
import Tabs from "./Tabs";
import Timeline from "./Timeline";
import Voting from "./Voting";
import ScrollTop from "components/ScrollTop";

const Container = styled.div``;

Expand All @@ -37,8 +39,11 @@ const HeaderContainer = styled.div`
`;

const Header = styled.h1`
margin: 0;
display: flex;
align-items: center;
flex: 1;
gap: 8px;
margin: 0;
`;

const CaseDetails: React.FC = () => {
Expand All @@ -52,7 +57,10 @@ const CaseDetails: React.FC = () => {
<VotingContextProvider>
<Container>
<HeaderContainer>
<Header>Case #{id}</Header>
<Header>
Case #{id} {id ? <CaseStarButton id={id} /> : null}
</Header>

<MaintenanceButtons />
</HeaderContainer>
<Tabs />
Expand Down
8 changes: 5 additions & 3 deletions web/src/pages/Dashboard/index.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import React, { useMemo } from "react";
import styled from "styled-components";

import { MAX_WIDTH_LANDSCAPE } from "styles/landscapeStyle";
import { responsiveSize } from "styles/responsiveSize";

import { useNavigate, useParams } from "react-router-dom";
import { useAccount } from "wagmi";

Expand All @@ -15,8 +12,12 @@ import { useUserQuery } from "queries/useUser";

import { OrderDirection } from "src/graphql/graphql";

import { MAX_WIDTH_LANDSCAPE } from "styles/landscapeStyle";
import { responsiveSize } from "styles/responsiveSize";

import CasesDisplay from "components/CasesDisplay";
import ConnectWallet from "components/ConnectWallet";
import FavoriteCases from "components/FavoriteCases";
import ScrollTop from "components/ScrollTop";

import Courts from "./Courts";
Expand Down Expand Up @@ -94,6 +95,7 @@ const Dashboard: React.FC = () => {
<ConnectWallet />
</ConnectWalletContainer>
)}
<FavoriteCases />
<ScrollTop />
</Container>
);
Expand Down
Loading