Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
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>
);
};

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]);

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]);
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));
};

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