Skip to content

Commit 7edaf11

Browse files
authored
Merge pull request #6 from kleros/feat(web)/Home-page-ui
Feat(web)/home page UI
2 parents ee42865 + 2aed8c7 commit 7edaf11

File tree

17 files changed

+705
-1
lines changed

17 files changed

+705
-1
lines changed
Lines changed: 10 additions & 0 deletions
Loading
Lines changed: 9 additions & 0 deletions
Loading
Lines changed: 19 additions & 0 deletions
Loading
Lines changed: 10 additions & 0 deletions
Loading
Lines changed: 11 additions & 0 deletions
Loading

web/src/components/ChainIcon.tsx

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import React from "react";
2+
import { arbitrum, arbitrumSepolia, gnosis, gnosisChiado, polygon, polygonMumbai } from "viem/chains";
3+
import { mainnet, sepolia } from "wagmi";
4+
import EthIcon from "svgs/icons/eth-chain.svg";
5+
import PolygonIcon from "svgs/icons/polygon.svg";
6+
import GnosisIcon from "svgs/icons/gnosis.svg";
7+
import styled from "styled-components";
8+
9+
const getChainIcon = (chainId: number) => {
10+
switch (chainId) {
11+
case mainnet.id:
12+
case sepolia.id:
13+
return <EthIcon />;
14+
case arbitrum.id:
15+
case arbitrumSepolia.id:
16+
return <EthIcon />;
17+
case gnosis.id:
18+
case gnosisChiado.id:
19+
return <GnosisIcon />;
20+
case polygon.id:
21+
case polygonMumbai.id:
22+
return <PolygonIcon />;
23+
default:
24+
return <EthIcon />;
25+
}
26+
};
27+
28+
const SVGContainer = styled.div`
29+
display: flex;
30+
align-items: center;
31+
justify-content: center;
32+
svg {
33+
fill: ${({ theme }) => theme.secondaryPurple};
34+
height: 24px;
35+
width: 24px;
36+
}
37+
`;
38+
39+
const ChainIcon: React.FC<{ chainId: number }> = ({ chainId }) => <SVGContainer>{getChainIcon(chainId)}</SVGContainer>;
40+
41+
export default ChainIcon;
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import React from "react";
2+
import styled, { css } from "styled-components";
3+
import { Status } from "consts/status";
4+
import { responsiveSize } from "styles/responsiveSize";
5+
import ChainIcon from "../ChainIcon";
6+
import StatusBanner from "./StatusBanner";
7+
import { Button } from "@kleros/ui-components-library";
8+
import ArrowIcon from "svgs/icons/arrow.svg";
9+
import { landscapeStyle } from "styles/landscapeStyle";
10+
11+
const Container = styled.div<{ isList: boolean }>`
12+
height: calc(100% - 45px);
13+
display: flex;
14+
flex-direction: column;
15+
justify-content: center;
16+
align-items: center;
17+
gap: 8px;
18+
19+
// css for isList view
20+
${({ isList }) =>
21+
isList &&
22+
css`
23+
width: 100%;
24+
height: max-content;
25+
display: grid;
26+
grid-template-rows: repeat(3, min-content);
27+
grid-template-columns: 21px max-content 1fr max-content;
28+
column-gap: ${responsiveSize(8, 12, 900)};
29+
row-gap: 16px;
30+
padding: 16px;
31+
h3,
32+
img {
33+
grid-column: span 4;
34+
}
35+
${landscapeStyle(
36+
() => css`
37+
height: 64px;
38+
justify-content: space-between;
39+
grid-template-rows: 1fr;
40+
grid-template-columns: auto 1fr 60px ${responsiveSize(80, 100, 900)} ${responsiveSize(100, 150, 900)} max-content;
41+
padding: 0 32px;
42+
img {
43+
grid-column: 1;
44+
}
45+
h3 {
46+
grid-column: 2;
47+
}
48+
`
49+
)}
50+
`}
51+
`;
52+
53+
const StyledLogo = styled.img<{ isList: boolean }>`
54+
width: ${({ isList }) => (isList ? "48px" : "125px")};
55+
height: ${({ isList }) => (isList ? "48px" : "125px")};
56+
object-fit: contain;
57+
margin-bottom: ${({ isList }) => (isList ? "0px" : "8px")};
58+
`;
59+
60+
const StyledLabel = styled.label`
61+
color: ${({ theme }) => theme.secondaryText};
62+
`;
63+
64+
const StyledTitle = styled.h3`
65+
font-weight: 400;
66+
margin: 0px;
67+
`;
68+
69+
const TruncatedTitle = ({ text, maxLength }) => {
70+
const truncatedText = text.length <= maxLength ? text : text.slice(0, maxLength) + "…";
71+
return <StyledTitle>{truncatedText}</StyledTitle>;
72+
};
73+
74+
const StyledButton = styled(Button)`
75+
background-color: transparent;
76+
padding: 0;
77+
flex-direction: row-reverse;
78+
gap: 8px;
79+
.button-text {
80+
color: ${({ theme }) => theme.primaryBlue};
81+
font-weight: 400;
82+
}
83+
.button-svg {
84+
fill: ${({ theme }) => theme.secondaryPurple};
85+
}
86+
87+
:focus,
88+
:hover {
89+
background-color: transparent;
90+
}
91+
`;
92+
interface IListInfo {
93+
title: string;
94+
totalItems: number;
95+
logoURI: string;
96+
chainId: number;
97+
status: Status;
98+
isList?: boolean;
99+
}
100+
101+
const ListInfo: React.FC<IListInfo> = ({ title, totalItems, logoURI, chainId, status, isList = false }) => {
102+
return (
103+
<Container {...{ isList }}>
104+
<StyledLogo src={logoURI} alt="List Img" isList={isList} />
105+
<TruncatedTitle text={title} maxLength={100} />
106+
{isList && <ChainIcon {...{ chainId }} />}
107+
<StyledLabel>{totalItems} items</StyledLabel>
108+
{isList && <StatusBanner {...{ status, isList }} />}
109+
{isList && <StyledButton text="Open" Icon={ArrowIcon} />}
110+
</Container>
111+
);
112+
};
113+
114+
export default ListInfo;
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import React from "react";
2+
import styled, { Theme } from "styled-components";
3+
import { Status } from "consts/status";
4+
import ChainIcon from "../ChainIcon";
5+
6+
const Container = styled.div<{ status: Status; isList: boolean }>`
7+
height: ${({ isList }) => (isList ? "min-content" : "45px")};
8+
border-top-right-radius: 3px;
9+
border-top-left-radius: 3px;
10+
display: flex;
11+
align-items: center;
12+
justify-content: space-between;
13+
padding: 0 24px;
14+
.dot {
15+
::before {
16+
content: "";
17+
display: inline-block;
18+
height: 8px;
19+
width: 8px;
20+
border-radius: 50%;
21+
margin-right: 8px;
22+
}
23+
}
24+
${({ theme, status, isList }) => {
25+
const [frontColor, backgroundColor] = getStatusColor(status, theme);
26+
return `
27+
${!isList && `border-top: 5px solid ${frontColor}`};
28+
${!isList && `background-color: ${backgroundColor}`};
29+
${isList && `padding: 0px`};
30+
.front-color {
31+
color: ${frontColor};
32+
}
33+
.dot {
34+
::before {
35+
background-color: ${frontColor};
36+
}
37+
}
38+
`;
39+
}};
40+
`;
41+
42+
interface IStatusBanner {
43+
status: Status;
44+
chainId?: number;
45+
isList?: boolean;
46+
}
47+
48+
const getStatusColor = (status: Status, theme: Theme): [string, string] => {
49+
switch (status) {
50+
case Status.Pending:
51+
return [theme.primaryBlue, theme.mediumBlue];
52+
case Status.Disputed:
53+
return [theme.secondaryPurple, theme.mediumPurple];
54+
case Status.Included:
55+
return [theme.success, theme.successLight];
56+
default:
57+
return [theme.primaryBlue, theme.mediumBlue];
58+
}
59+
};
60+
61+
const getStatusLabel = (status: Status): string => {
62+
switch (status) {
63+
case Status.Pending:
64+
return "Pending";
65+
case Status.Disputed:
66+
return "Disputed";
67+
case Status.Included:
68+
return "Included";
69+
default:
70+
return "Pending";
71+
}
72+
};
73+
74+
const StatusBanner: React.FC<IStatusBanner> = ({ status, chainId, isList = false }) => (
75+
<Container {...{ status, isList }}>
76+
<label className="front-color dot">{getStatusLabel(status)}</label>
77+
{!isList && <ChainIcon chainId={chainId ?? 1} />}
78+
</Container>
79+
);
80+
81+
export default StatusBanner;
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import React from "react";
2+
import styled, { css } from "styled-components";
3+
import { useNavigate } from "react-router-dom";
4+
import { Card } from "@kleros/ui-components-library";
5+
import { useIsList } from "context/IsListProvider";
6+
import { landscapeStyle } from "styles/landscapeStyle";
7+
import { lists } from "consts/index";
8+
import StatusBanner from "./StatusBanner";
9+
import ListInfo from "./ListInfo";
10+
11+
const StyledCard = styled(Card)`
12+
width: 100%;
13+
height: 274px;
14+
15+
${landscapeStyle(
16+
() =>
17+
css`
18+
width: max(calc((100% - 48px) * 0.333), 348px);
19+
`
20+
)}
21+
`;
22+
23+
const StyledListItem = styled(Card)`
24+
display: flex;
25+
flex-grow: 1;
26+
width: 100%;
27+
height: max-content;
28+
${landscapeStyle(
29+
() => css`
30+
height: 64px;
31+
`
32+
)}
33+
`;
34+
35+
type List = (typeof lists)[number];
36+
interface IListCard extends List {
37+
overrideIsList?: boolean;
38+
}
39+
40+
const ListCard: React.FC<IListCard> = ({ id, title, logoURI, totalItems, status, chainId, overrideIsList }) => {
41+
const { isList } = useIsList();
42+
43+
const navigate = useNavigate();
44+
return (
45+
<>
46+
{!isList || overrideIsList ? (
47+
<StyledCard hover onClick={() => navigate(`/lists/${id.toString()}`)}>
48+
<StatusBanner {...{ status, chainId }} />
49+
<ListInfo {...{ title, logoURI, totalItems, status, chainId }} />
50+
</StyledCard>
51+
) : (
52+
<StyledListItem hover onClick={() => navigate(`/lists/${id.toString()}`)}>
53+
<ListInfo {...{ title, logoURI, totalItems, status, chainId }} isList />
54+
</StyledListItem>
55+
)}
56+
</>
57+
);
58+
};
59+
60+
export default ListCard;

0 commit comments

Comments
 (0)