Skip to content

Commit 9b97263

Browse files
authored
Merge pull request #1219 from kleros/feat(web)/cases-grid-and-list-display
feat(web): cases-grid-and-list-display
2 parents 0dd543c + 475f6dd commit 9b97263

File tree

17 files changed

+410
-80
lines changed

17 files changed

+410
-80
lines changed

web/src/app.tsx

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import "react-toastify/dist/ReactToastify.css";
66
import Web3Provider from "context/Web3Provider";
77
import QueryClientProvider from "context/QueryClientProvider";
88
import StyledComponentsProvider from "context/StyledComponentsProvider";
9+
import { FilterProvider } from "context/FilterProvider";
910
import RefetchOnBlock from "context/RefetchOnBlock";
1011
import Layout from "layout/index";
1112
import Home from "./pages/Home";
@@ -20,16 +21,18 @@ const App: React.FC = () => {
2021
<QueryClientProvider>
2122
<RefetchOnBlock />
2223
<Web3Provider>
23-
<SentryRoutes>
24-
<Route path="/" element={<Layout />}>
25-
<Route index element={<Home />} />
26-
<Route path="cases/*" element={<Cases />} />
27-
<Route path="courts/*" element={<Courts />} />
28-
<Route path="dashboard" element={<Dashboard />} />
29-
<Route path="disputeTemplate" element={<DisputeTemplateView />} />
30-
<Route path="*" element={<h1>Justice not found here ¯\_( ͡° ͜ʖ ͡°)_/¯</h1>} />
31-
</Route>
32-
</SentryRoutes>
24+
<FilterProvider>
25+
<SentryRoutes>
26+
<Route path="/" element={<Layout />}>
27+
<Route index element={<Home />} />
28+
<Route path="cases/*" element={<Cases />} />
29+
<Route path="courts/*" element={<Courts />} />
30+
<Route path="dashboard" element={<Dashboard />} />
31+
<Route path="disputeTemplate" element={<DisputeTemplateView />} />
32+
<Route path="*" element={<h1>Justice not found here ¯\_( ͡° ͜ʖ ͡°)_/¯</h1>} />
33+
</Route>
34+
</SentryRoutes>
35+
</FilterProvider>
3336
</Web3Provider>
3437
</QueryClientProvider>
3538
</StyledComponentsProvider>

web/src/assets/svgs/icons/grid.svg

Lines changed: 3 additions & 0 deletions
Loading

web/src/assets/svgs/icons/list.svg

Lines changed: 3 additions & 0 deletions
Loading

web/src/components/CasesDisplay/CasesGrid.tsx

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,38 @@
11
import React from "react";
2-
import styled from "styled-components";
2+
import styled, { css } from "styled-components";
33
import { StandardPagination } from "@kleros/ui-components-library";
4+
import { landscapeStyle } from "styles/landscapeStyle";
5+
import { useFiltersContext } from "context/FilterProvider";
46
import { CasesPageQuery } from "queries/useCasesQuery";
57
import DisputeCard from "components/DisputeCard";
8+
import CasesListHeader from "./CasesListHeader";
9+
import { useLocation } from "react-router-dom";
610

7-
const Container = styled.div`
11+
const GridContainer = styled.div<{ path: string }>`
812
display: flex;
913
flex-wrap: wrap;
1014
justify-content: center;
15+
align-items: center;
16+
gap: 8px;
17+
${({ path }) =>
18+
landscapeStyle(() =>
19+
path === "/dashboard"
20+
? css`
21+
display: flex;
22+
`
23+
: css`
24+
display: grid;
25+
row-gap: 16px;
26+
column-gap: 8px;
27+
grid-template-columns: repeat(auto-fit, minmax(380px, 1fr));
28+
justify-content: space-between;
29+
`
30+
)}
31+
`;
32+
const ListContainer = styled.div`
33+
display: flex;
34+
flex-direction: column;
35+
justify-content: center;
1136
gap: 8px;
1237
`;
1338

@@ -26,13 +51,26 @@ export interface ICasesGrid {
2651
}
2752

2853
const CasesGrid: React.FC<ICasesGrid> = ({ disputes, currentPage, setCurrentPage, numberDisputes, casesPerPage }) => {
54+
const { isList } = useFiltersContext();
55+
const location = useLocation();
56+
57+
const path = location.pathname;
2958
return (
3059
<>
31-
<Container>
32-
{disputes.map((dispute, i) => {
33-
return <DisputeCard key={i} {...dispute} />;
34-
})}
35-
</Container>
60+
{!isList ? (
61+
<GridContainer path={path}>
62+
{disputes.map((dispute) => {
63+
return <DisputeCard key={dispute?.id} {...dispute} />;
64+
})}
65+
</GridContainer>
66+
) : (
67+
<ListContainer>
68+
{isList && <CasesListHeader />}
69+
{disputes.map((dispute) => {
70+
return <DisputeCard key={dispute?.id} {...dispute} />;
71+
})}
72+
</ListContainer>
73+
)}
3674
<StyledPagination
3775
{...{ currentPage }}
3876
numPages={Math.ceil(numberDisputes / casesPerPage)}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import React from "react";
2+
import styled, { css } from "styled-components";
3+
import { landscapeStyle } from "styles/landscapeStyle";
4+
import WithHelpTooltip from "pages/Dashboard/WithHelpTooltip";
5+
6+
const Container = styled.div`
7+
display: flex;
8+
justify-content: space-between;
9+
gap: calc(15vw + (40 - 15) * ((100vw - 300px) / (1250 - 300)));
10+
width: 100%;
11+
height: 100%;
12+
`;
13+
14+
const CasesData = styled.div`
15+
display: flex;
16+
align-items: center;
17+
justify-content: space-around;
18+
width: 100%;
19+
margin-left: calc(0px + (33) * (100vw - 370px) / (1250 - 370));
20+
flex-wrap: wrap;
21+
padding: 0 3%;
22+
gap: calc(24px + (48 - 24) * ((100vw - 300px) / (1250 - 300)));
23+
`;
24+
25+
const CaseTitle = styled.div`
26+
display: none;
27+
margin-left: 32px;
28+
gap: 36px;
29+
label {
30+
font-weight: 400;
31+
font-size: 14px;
32+
line-height: 19px;
33+
color: ${({ theme }) => theme.secondaryText} !important;
34+
}
35+
36+
${landscapeStyle(
37+
() =>
38+
css`
39+
display: flex;
40+
`
41+
)}
42+
`;
43+
44+
const StyledLabel = styled.label`
45+
padding-left: calc(4px + (8 - 4) * ((100vw - 300px) / (900 - 300)));
46+
`;
47+
48+
const tooltipMsg =
49+
"Users have an economic interest in serving as jurors in Kleros: " +
50+
"collecting the Juror Rewards in exchange for their work. Each juror who " +
51+
"is coherent with the final ruling receive the Juror Rewards composed of " +
52+
"arbitration fees (ETH) + PNK redistribution between jurors.";
53+
54+
const CasesListHeader: React.FC = () => {
55+
return (
56+
<Container>
57+
<CaseTitle>
58+
<label>#</label>
59+
<label>Title</label>
60+
</CaseTitle>
61+
<CasesData>
62+
<StyledLabel>Court</StyledLabel>
63+
<StyledLabel>Category</StyledLabel>
64+
<WithHelpTooltip place="top" {...{ tooltipMsg }}>
65+
<label> Rewards: </label>
66+
</WithHelpTooltip>
67+
<label>Next Deadline</label>
68+
</CasesData>
69+
</Container>
70+
);
71+
};
72+
73+
export default CasesListHeader;

web/src/components/CasesDisplay/Filters.tsx

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
import React from "react";
2-
import styled, { useTheme } from "styled-components";
2+
import styled, { useTheme, css } from "styled-components";
3+
import { useWindowSize } from "react-use";
34
import { DropdownSelect } from "@kleros/ui-components-library";
5+
import { useFiltersContext } from "context/FilterProvider";
6+
import { BREAKPOINT_LANDSCAPE } from "styles/landscapeStyle";
7+
import ListIcon from "svgs/icons/list.svg";
8+
import GridIcon from "svgs/icons/grid.svg";
49

510
const Container = styled.div`
611
display: flex;
@@ -9,8 +14,44 @@ const Container = styled.div`
914
width: fit-content;
1015
`;
1116

17+
const glowingEffect = css`
18+
filter: drop-shadow(0 0 4px ${({ theme }) => theme.klerosUIComponentsSecondaryPurple});
19+
`;
20+
21+
const StyledGridIcon = styled(GridIcon)<{ isList: boolean }>`
22+
cursor: pointer;
23+
transition: filter 0.2s ease;
24+
fill: ${({ theme }) => theme.primaryBlue};
25+
width: 16px;
26+
height: 16px;
27+
overflow: hidden;
28+
${({ isList }) => !isList && glowingEffect}
29+
`;
30+
31+
const IconsContainer = styled.div`
32+
display: flex;
33+
justify-content: center;
34+
align-items: center;
35+
gap: 4px;
36+
`;
37+
38+
const StyledListIcon = styled(ListIcon)<{ isList: boolean; isScreenBig: boolean }>`
39+
cursor: pointer;
40+
display: ${({ isScreenBig }) => (isScreenBig ? "block" : "none")};
41+
transition: filter 0.2s ease;
42+
fill: ${({ theme }) => theme.primaryBlue};
43+
width: 16px;
44+
height: 16px;
45+
overflow: hidden;
46+
${({ isList }) => isList && glowingEffect}
47+
`;
48+
1249
const Filters: React.FC = () => {
1350
const theme = useTheme();
51+
const { width } = useWindowSize();
52+
const { isList, setIsList } = useFiltersContext();
53+
const screenIsBig = width > BREAKPOINT_LANDSCAPE;
54+
1455
return (
1556
<Container>
1657
<DropdownSelect
@@ -35,6 +76,18 @@ const Filters: React.FC = () => {
3576
defaultValue={0}
3677
callback={() => {}}
3778
/>
79+
<IconsContainer>
80+
<StyledGridIcon isList={isList} onClick={() => setIsList(false)} />
81+
<StyledListIcon
82+
isList={isList}
83+
isScreenBig={screenIsBig}
84+
onClick={() => {
85+
if (screenIsBig) {
86+
setIsList(true);
87+
}
88+
}}
89+
/>
90+
</IconsContainer>
3891
</Container>
3992
);
4093
};

web/src/components/DisputeCard/DisputeInfo.tsx

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React from "react";
2-
import styled from "styled-components";
2+
import styled, { css } from "styled-components";
3+
import { useFiltersContext } from "context/FilterProvider";
34
import { Periods } from "consts/periods";
45
import BookmarkIcon from "svgs/icons/bookmark.svg";
56
import CalendarIcon from "svgs/icons/calendar.svg";
@@ -8,10 +9,20 @@ import PileCoinsIcon from "svgs/icons/pile-coins.svg";
89
import RoundIcon from "svgs/icons/round.svg";
910
import Field from "../Field";
1011

11-
const Container = styled.div`
12+
const Container = styled.div<{ isList: boolean }>`
1213
display: flex;
13-
flex-direction: column;
14+
flex-direction: ${({ isList }) => (isList ? "row" : "column")};
1415
gap: 8px;
16+
17+
${({ isList }) =>
18+
isList &&
19+
css`
20+
gap: calc(4px + (24px - 4px) * ((100vw - 300px) / (900 - 300)));
21+
`};
22+
justify-content: ${({ isList }) => (isList ? "space-around" : "center")};
23+
align-items: center;
24+
width: 100%;
25+
height: 100%;
1526
`;
1627

1728
const getPeriodPhrase = (period: Periods): string => {
@@ -37,15 +48,29 @@ export interface IDisputeInfo {
3748
round?: number;
3849
}
3950

51+
const formatDate = (date: number) => {
52+
const options: Intl.DateTimeFormatOptions = { year: "numeric", month: "long", day: "numeric" };
53+
const startingDate = new Date(date * 1000);
54+
const formattedDate = startingDate.toLocaleDateString("en-US", options);
55+
return formattedDate;
56+
};
57+
4058
const DisputeInfo: React.FC<IDisputeInfo> = ({ courtId, court, category, rewards, period, date, round }) => {
59+
const { isList } = useFiltersContext();
60+
4161
return (
42-
<Container>
62+
<Container isList={isList}>
4363
{court && courtId && <Field icon={LawBalanceIcon} name="Court" value={court} link={`/courts/${courtId}`} />}
4464
{category && <Field icon={BookmarkIcon} name="Category" value={category} />}
65+
{!category && isList && <Field icon={BookmarkIcon} name="Category" value="General" />}
4566
{round && <Field icon={RoundIcon} name="Round" value={round.toString()} />}
4667
{rewards && <Field icon={PileCoinsIcon} name="Juror Rewards" value={rewards} />}
4768
{typeof period !== "undefined" && date && (
48-
<Field icon={CalendarIcon} name={getPeriodPhrase(period)} value={new Date(date * 1000).toLocaleString()} />
69+
<Field
70+
icon={CalendarIcon}
71+
name={getPeriodPhrase(period)}
72+
value={!isList ? new Date(date * 1000).toLocaleString() : formatDate(date)}
73+
/>
4974
)}
5075
</Container>
5176
);

web/src/components/DisputeCard/PeriodBanner.tsx

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import styled, { Theme } from "styled-components";
33
import { Periods } from "consts/periods";
44

55
const Container = styled.div<Omit<IPeriodBanner, "id">>`
6-
height: 45px;
6+
height: ${({ isCard }) => (isCard ? "45px" : "100%")};
77
width: auto;
88
border-top-right-radius: 3px;
99
border-top-left-radius: 3px;
@@ -21,11 +21,11 @@ const Container = styled.div<Omit<IPeriodBanner, "id">>`
2121
margin-right: 8px;
2222
}
2323
}
24-
${({ theme, period }) => {
24+
${({ theme, period, isCard }) => {
2525
const [frontColor, backgroundColor] = getPeriodColors(period, theme);
2626
return `
27-
border-top: 5px solid ${frontColor};
28-
background-color: ${backgroundColor};
27+
${isCard ? `border-top: 5px solid ${frontColor}` : `border-left: 5px solid ${frontColor}`};
28+
${isCard && `background-color: ${backgroundColor}`};
2929
.front-color {
3030
color: ${frontColor};
3131
}
@@ -41,6 +41,7 @@ const Container = styled.div<Omit<IPeriodBanner, "id">>`
4141
export interface IPeriodBanner {
4242
id: number;
4343
period: Periods;
44+
isCard?: boolean;
4445
}
4546

4647
const getPeriodColors = (period: Periods, theme: Theme): [string, string] => {
@@ -65,9 +66,9 @@ const getPeriodLabel = (period: Periods): string => {
6566
}
6667
};
6768

68-
const PeriodBanner: React.FC<IPeriodBanner> = ({ id, period }) => (
69-
<Container {...{ period }}>
70-
<label className="front-color dot">{getPeriodLabel(period)}</label>
69+
const PeriodBanner: React.FC<IPeriodBanner> = ({ id, period, isCard = true }) => (
70+
<Container period={period} isCard={isCard}>
71+
{isCard && <label className="front-color dot">{getPeriodLabel(period)}</label>}
7172
<label className="front-color">#{id}</label>
7273
</Container>
7374
);

0 commit comments

Comments
 (0)