From 9fce941c9b93c03acd086d32dc060b6ae90e2646 Mon Sep 17 00:00:00 2001 From: kemuru <102478601+kemuru@users.noreply.github.com> Date: Fri, 9 Feb 2024 06:01:10 +0100 Subject: [PATCH 1/9] feat(web): almost list page, stylings --- web/src/assets/svgs/icons/etherscan.svg | 10 ++ web/src/assets/svgs/icons/history.svg | 10 ++ web/src/assets/svgs/icons/plus.svg | 10 ++ web/src/components/ChainIcon.tsx | 21 ++- .../components/RegistriesDisplay/Filters.tsx | 10 +- .../RegistriesDisplay/RegistriesGrid.tsx | 4 +- .../components/RegistriesDisplay/Search.tsx | 8 +- .../components/RegistriesDisplay/index.tsx | 4 +- .../components/RegistryCard/StatusBanner.tsx | 4 +- web/src/components/RegistryCard/index.tsx | 8 +- web/src/hooks/useNavigateAndScrollTop.ts | 15 ++ web/src/pages/AllLists/RegistriesFetcher.tsx | 4 +- .../AllLists/RegistryDetails/Filters.tsx | 47 ++++++ .../RegistryDetails/History/index.tsx | 8 + .../InformationCard/Policies.tsx | 80 +++++++++ .../RegistryDetails/InformationCard/index.tsx | 159 ++++++++++++++++++ .../AllLists/RegistryDetails/List/index.tsx | 15 ++ .../pages/AllLists/RegistryDetails/Search.tsx | 92 ++++++++++ .../pages/AllLists/RegistryDetails/Stats.tsx | 46 +++++ .../RegistryDetails/StatsAndFilters.tsx | 22 +++ .../pages/AllLists/RegistryDetails/Tabs.tsx | 61 +++++++ .../pages/AllLists/RegistryDetails/index.tsx | 59 +++++++ web/src/pages/AllLists/index.tsx | 4 +- web/src/pages/Home/Highlights/index.tsx | 17 +- web/src/utils/getIpfsUrl.ts | 13 ++ web/src/utils/uri.ts | 27 ++- 26 files changed, 716 insertions(+), 42 deletions(-) create mode 100644 web/src/assets/svgs/icons/etherscan.svg create mode 100644 web/src/assets/svgs/icons/history.svg create mode 100644 web/src/assets/svgs/icons/plus.svg create mode 100644 web/src/hooks/useNavigateAndScrollTop.ts create mode 100644 web/src/pages/AllLists/RegistryDetails/Filters.tsx create mode 100644 web/src/pages/AllLists/RegistryDetails/History/index.tsx create mode 100644 web/src/pages/AllLists/RegistryDetails/InformationCard/Policies.tsx create mode 100644 web/src/pages/AllLists/RegistryDetails/InformationCard/index.tsx create mode 100644 web/src/pages/AllLists/RegistryDetails/List/index.tsx create mode 100644 web/src/pages/AllLists/RegistryDetails/Search.tsx create mode 100644 web/src/pages/AllLists/RegistryDetails/Stats.tsx create mode 100644 web/src/pages/AllLists/RegistryDetails/StatsAndFilters.tsx create mode 100644 web/src/pages/AllLists/RegistryDetails/Tabs.tsx create mode 100644 web/src/pages/AllLists/RegistryDetails/index.tsx create mode 100644 web/src/utils/getIpfsUrl.ts diff --git a/web/src/assets/svgs/icons/etherscan.svg b/web/src/assets/svgs/icons/etherscan.svg new file mode 100644 index 0000000..776c369 --- /dev/null +++ b/web/src/assets/svgs/icons/etherscan.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/web/src/assets/svgs/icons/history.svg b/web/src/assets/svgs/icons/history.svg new file mode 100644 index 0000000..3f7ae3d --- /dev/null +++ b/web/src/assets/svgs/icons/history.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/web/src/assets/svgs/icons/plus.svg b/web/src/assets/svgs/icons/plus.svg new file mode 100644 index 0000000..16d74e8 --- /dev/null +++ b/web/src/assets/svgs/icons/plus.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/web/src/components/ChainIcon.tsx b/web/src/components/ChainIcon.tsx index e24fa45..d37fea1 100644 --- a/web/src/components/ChainIcon.tsx +++ b/web/src/components/ChainIcon.tsx @@ -6,7 +6,7 @@ import PolygonIcon from "svgs/chains/polygon.svg"; import GnosisIcon from "svgs/chains/gnosis.svg"; import styled from "styled-components"; -const getChainIcon = (chainId: number) => { +export const getChainIcon = (chainId: number) => { switch (chainId) { case mainnet.id: case sepolia.id: @@ -25,6 +25,25 @@ const getChainIcon = (chainId: number) => { } }; +export const getChainName = (chainId: number) => { + switch (chainId) { + case mainnet.id: + case sepolia.id: + return "Ethereum"; + case arbitrum.id: + case arbitrumSepolia.id: + return "Arbitrum"; + case gnosis.id: + case gnosisChiado.id: + return "Gnosis"; + case polygon.id: + case polygonMumbai.id: + return "Polygon"; + default: + return "Ethereum"; + } +}; + const SVGContainer = styled.div` display: flex; align-items: center; diff --git a/web/src/components/RegistriesDisplay/Filters.tsx b/web/src/components/RegistriesDisplay/Filters.tsx index 52ec0b5..a125195 100644 --- a/web/src/components/RegistriesDisplay/Filters.tsx +++ b/web/src/components/RegistriesDisplay/Filters.tsx @@ -7,7 +7,7 @@ import { useIsList } from "context/IsListProvider"; import ListIcon from "svgs/icons/list.svg"; import GridIcon from "svgs/icons/grid.svg"; import { BREAKPOINT_LANDSCAPE } from "styles/landscapeStyle"; -import { decodeURIFilter, encodeURIFilter, useRootPath } from "utils/uri"; +import { decodeListURIFilter, encodeListURIFilter, useListRootPath } from "utils/uri"; const Container = styled.div` display: flex; @@ -44,18 +44,18 @@ const StyledListIcon = styled(ListIcon)` const Filters: React.FC = () => { const theme = useTheme(); const { order, filter } = useParams(); - const { ruled, period, ...filterObject } = decodeURIFilter(filter ?? "all"); + const { ruled, period, ...filterObject } = decodeListURIFilter(filter ?? "all"); const navigate = useNavigate(); - const location = useRootPath(); + const location = useListRootPath(); const handleStatusChange = (value: string | number) => { const parsedValue = JSON.parse(value as string); - const encodedFilter = encodeURIFilter({ ...filterObject, ...parsedValue }); + const encodedFilter = encodeListURIFilter({ ...filterObject, ...parsedValue }); navigate(`${location}/1/${order}/${encodedFilter}`); }; const handleOrderChange = (value: string | number) => { - const encodedFilter = encodeURIFilter({ ruled, period, ...filterObject }); + const encodedFilter = encodeListURIFilter({ ruled, period, ...filterObject }); navigate(`${location}/1/${value}/${encodedFilter}`); }; diff --git a/web/src/components/RegistriesDisplay/RegistriesGrid.tsx b/web/src/components/RegistriesDisplay/RegistriesGrid.tsx index f856522..47a2fd0 100644 --- a/web/src/components/RegistriesDisplay/RegistriesGrid.tsx +++ b/web/src/components/RegistriesDisplay/RegistriesGrid.tsx @@ -7,7 +7,7 @@ import { StandardPagination } from "@kleros/ui-components-library"; import { BREAKPOINT_LANDSCAPE } from "styles/landscapeStyle"; import { useIsList } from "context/IsListProvider"; import { isUndefined } from "utils/index"; -import { decodeURIFilter } from "utils/uri"; +import { decodeListURIFilter } from "utils/uri"; // import { RegistryDetailsFragment } from "queries/useCasesQuery"; import RegistryCard from "components/RegistryCard"; @@ -48,7 +48,7 @@ const RegistriesGrid: React.FC = ({ setCurrentPage, }) => { const { filter } = useParams(); - const decodedFilter = decodeURIFilter(filter ?? "all"); + const decodedFilter = decodeListURIFilter(filter ?? "all"); const { id: searchValue } = decodedFilter; const { isList } = useIsList(); const { width } = useWindowSize(); diff --git a/web/src/components/RegistriesDisplay/Search.tsx b/web/src/components/RegistriesDisplay/Search.tsx index 9235c3f..fe5f91f 100644 --- a/web/src/components/RegistriesDisplay/Search.tsx +++ b/web/src/components/RegistriesDisplay/Search.tsx @@ -4,7 +4,7 @@ import { landscapeStyle } from "styles/landscapeStyle"; import { useNavigate, useParams } from "react-router-dom"; import { useDebounce } from "react-use"; import { Searchbar, DropdownSelect } from "@kleros/ui-components-library"; -import { decodeURIFilter, encodeURIFilter, useRootPath } from "utils/uri"; +import { decodeListURIFilter, encodeListURIFilter, useListRootPath } from "utils/uri"; import { StyledGlobeIcon } from "../StyledIcons/GlobeIcon"; import { StyledEthereumIcon } from "../StyledIcons/EthereumIcon"; import { StyledGnosisIcon } from "../StyledIcons/GnosisIcon"; @@ -45,15 +45,15 @@ const StyledSearchbar = styled(Searchbar)` const Search: React.FC = () => { const { page, order, filter } = useParams(); - const location = useRootPath(); - const decodedFilter = decodeURIFilter(filter ?? "all"); + const location = useListRootPath(); + const decodedFilter = decodeListURIFilter(filter ?? "all"); const { id: searchValue, ...filterObject } = decodedFilter; const [search, setSearch] = useState(searchValue ?? ""); const navigate = useNavigate(); useDebounce( () => { const newFilters = search === "" ? { ...filterObject } : { ...filterObject, id: search }; - const encodedFilter = encodeURIFilter(newFilters); + const encodedFilter = encodeListURIFilter(newFilters); navigate(`${location}/${page}/${order}/${encodedFilter}`); }, 500, diff --git a/web/src/components/RegistriesDisplay/index.tsx b/web/src/components/RegistriesDisplay/index.tsx index f8972db..7d45742 100644 --- a/web/src/components/RegistriesDisplay/index.tsx +++ b/web/src/components/RegistriesDisplay/index.tsx @@ -6,7 +6,7 @@ import Search from "./Search"; import StatsAndFilters from "./StatsAndFilters"; import RegistriesGrid, { IRegistriesGrid } from "./RegistriesGrid"; import HomeIcon from "svgs/icons/home.svg"; -import Header from "~src/pages/Home/Header"; +import Header from "pages/Home/Header"; const StyledTitle = styled.h1` margin-bottom: ${responsiveSize(32, 48)}; @@ -26,7 +26,7 @@ const StyledBreadcrumb = styled(Breadcrumb)` const breadcrumbItems = [ { text: , value: "0" }, - { text: "All Lists", value: "0" }, + { text: "All Lists", value: "1" }, ]; interface IRegistriesDisplay extends IRegistriesGrid { diff --git a/web/src/components/RegistryCard/StatusBanner.tsx b/web/src/components/RegistryCard/StatusBanner.tsx index b533b77..9bc32d0 100644 --- a/web/src/components/RegistryCard/StatusBanner.tsx +++ b/web/src/components/RegistryCard/StatusBanner.tsx @@ -45,7 +45,7 @@ interface IStatusBanner { isList?: boolean; } -const getStatusColor = (status: Status, theme: Theme): [string, string] => { +export const getStatusColor = (status: Status, theme: Theme): [string, string] => { switch (status) { case Status.Pending: return [theme.primaryBlue, theme.mediumBlue]; @@ -60,7 +60,7 @@ const getStatusColor = (status: Status, theme: Theme): [string, string] => { } }; -const getStatusLabel = (status: Status): string => { +export const getStatusLabel = (status: Status): string => { switch (status) { case Status.Pending: return "Pending"; diff --git a/web/src/components/RegistryCard/index.tsx b/web/src/components/RegistryCard/index.tsx index 2487d12..f4a2568 100644 --- a/web/src/components/RegistryCard/index.tsx +++ b/web/src/components/RegistryCard/index.tsx @@ -1,12 +1,12 @@ import React from "react"; import styled, { css } from "styled-components"; -import { useNavigate } from "react-router-dom"; import { Card } from "@kleros/ui-components-library"; import { useIsList } from "context/IsListProvider"; import { landscapeStyle } from "styles/landscapeStyle"; import { lists } from "consts/index"; import StatusBanner from "./StatusBanner"; import RegistryInfo from "./RegistryInfo"; +import { useNavigateAndScrollTop } from "hooks/useNavigateAndScrollTop"; const StyledCard = styled(Card)` width: 100%; @@ -32,17 +32,17 @@ interface IListCard extends List { const RegistryCard: React.FC = ({ id, title, logoURI, totalItems, status, chainId, overrideIsList }) => { const { isList } = useIsList(); + const navigateAndScrollTop = useNavigateAndScrollTop(); - const navigate = useNavigate(); return ( <> {!isList || overrideIsList ? ( - navigate(`/lists/${id.toString()}`)}> + navigateAndScrollTop(`/lists/${id.toString()}/display/1/desc/all`)}> ) : ( - navigate(`/lists/${id.toString()}`)}> + navigateAndScrollTop(`/lists/${id.toString()}/display/desc/all`)}> )} diff --git a/web/src/hooks/useNavigateAndScrollTop.ts b/web/src/hooks/useNavigateAndScrollTop.ts new file mode 100644 index 0000000..0929bfa --- /dev/null +++ b/web/src/hooks/useNavigateAndScrollTop.ts @@ -0,0 +1,15 @@ +import { useContext } from "react"; +import { useNavigate } from "react-router-dom"; +import { OverlayScrollContext } from "context/OverlayScrollContext"; + +export const useNavigateAndScrollTop = () => { + const navigate = useNavigate(); + const osInstanceRef = useContext(OverlayScrollContext); + + const navigateAndScrollTop = (path) => { + navigate(path); + osInstanceRef?.current?.osInstance().elements().viewport.scroll({ top: 0 }); + }; + + return navigateAndScrollTop; +}; diff --git a/web/src/pages/AllLists/RegistriesFetcher.tsx b/web/src/pages/AllLists/RegistriesFetcher.tsx index 8fbc2ea..8620719 100644 --- a/web/src/pages/AllLists/RegistriesFetcher.tsx +++ b/web/src/pages/AllLists/RegistriesFetcher.tsx @@ -1,7 +1,7 @@ import React, { useMemo } from "react"; import { useParams, useNavigate } from "react-router-dom"; import { useWindowSize } from "react-use"; -import { useRootPath, decodeURIFilter } from "utils/uri"; +import { useListRootPath, decodeListURIFilter } from "utils/uri"; import { useAccount } from "wagmi"; // import { useRegistriesQuery } from "hooks/queries/useRegistriesQuery"; import RegistriesDisplay from "components/RegistriesDisplay"; @@ -13,7 +13,7 @@ const RegistriesFetcher: React.FC = () => { const { page, order, filter } = useParams(); const navigate = useNavigate(); const { width } = useWindowSize(); - const location = useRootPath(); + const location = useListRootPath(); const screenIsBig = width > BREAKPOINT_LANDSCAPE; const registriesPerPage = screenIsBig ? 9 : 3; const pageNumber = parseInt(page ?? "1", 10); diff --git a/web/src/pages/AllLists/RegistryDetails/Filters.tsx b/web/src/pages/AllLists/RegistryDetails/Filters.tsx new file mode 100644 index 0000000..2c62fd6 --- /dev/null +++ b/web/src/pages/AllLists/RegistryDetails/Filters.tsx @@ -0,0 +1,47 @@ +import React from "react"; +import styled from "styled-components"; +import { useNavigate, useParams } from "react-router-dom"; +import { DropdownSelect } from "@kleros/ui-components-library"; +import { decodeItemURIFilter, encodeItemURIFilter, useItemRootPath } from "utils/uri"; + +const Container = styled.div` + display: flex; + justify-content: end; + gap: 12px; + width: fit-content; +`; + +const Filters: React.FC = () => { + const { order, filter } = useParams(); + const { ruled, period, ...filterObject } = decodeItemURIFilter(filter ?? "all"); + const navigate = useNavigate(); + const location = useItemRootPath(); + + const handleStatusChange = (value: string | number) => { + const parsedValue = JSON.parse(value as string); + const encodedFilter = encodeItemURIFilter({ ...filterObject, ...parsedValue }); + navigate(`${location}/${order}/${encodedFilter}`); + }; + + const handleOrderChange = (value: string | number) => { + const encodedFilter = encodeItemURIFilter({ ruled, period, ...filterObject }); + navigate(`${location}/${value}/${encodedFilter}`); + }; + + return ( + + + + ); +}; + +export default Filters; diff --git a/web/src/pages/AllLists/RegistryDetails/History/index.tsx b/web/src/pages/AllLists/RegistryDetails/History/index.tsx new file mode 100644 index 0000000..4b163f9 --- /dev/null +++ b/web/src/pages/AllLists/RegistryDetails/History/index.tsx @@ -0,0 +1,8 @@ +import React from "react"; + +interface IHistory {} + +const History: React.FC = ({}) => { + return
History
; +}; +export default History; diff --git a/web/src/pages/AllLists/RegistryDetails/InformationCard/Policies.tsx b/web/src/pages/AllLists/RegistryDetails/InformationCard/Policies.tsx new file mode 100644 index 0000000..5549344 --- /dev/null +++ b/web/src/pages/AllLists/RegistryDetails/InformationCard/Policies.tsx @@ -0,0 +1,80 @@ +import React from "react"; +import styled, { css } from "styled-components"; +import { landscapeStyle } from "styles/landscapeStyle"; +import PolicyIcon from "svgs/icons/policy.svg"; +import { responsiveSize } from "styles/responsiveSize"; +// import { getIpfsUrl } from "utils/getIpfsUrl"; + +const ShadeArea = styled.div` + display: flex; + flex-direction: column; + justify-content: center; + width: 100%; + padding: ${responsiveSize(16, 20)} ${responsiveSize(16, 32)}; + margin-top: 16px; + background-color: ${({ theme }) => theme.mediumBlue}; + + ${landscapeStyle( + () => css` + flex-direction: row; + justify-content: space-between; + ` + )}; +`; + +const StyledP = styled.p` + font-size: 14px; + margin-top: 0; + margin-bottom: 16px; + color: ${({ theme }) => theme.primaryBlue}; + ${landscapeStyle( + () => css` + margin-bottom: 0; + ` + )}; +`; + +const StyledA = styled.a` + display: flex; + align-items: center; + gap: 4px; +`; + +const StyledPolicyIcon = styled(PolicyIcon)` + width: 16px; + fill: ${({ theme }) => theme.primaryBlue}; +`; + +const LinkContainer = styled.div` + display: flex; + gap: ${responsiveSize(16, 24)}; + flex-wrap: wrap; +`; + +type Attachment = { + label?: string; + uri: string; +}; +interface IPolicies { + disputePolicyURI?: string; + courtId?: string; + attachment?: Attachment; +} + +export const Policies: React.FC = ({ disputePolicyURI, courtId, attachment }) => { + return ( + + Make sure you read and understand the Policies + + + + Curation Policy + + + + List Policy + + + + ); +}; diff --git a/web/src/pages/AllLists/RegistryDetails/InformationCard/index.tsx b/web/src/pages/AllLists/RegistryDetails/InformationCard/index.tsx new file mode 100644 index 0000000..16ab27a --- /dev/null +++ b/web/src/pages/AllLists/RegistryDetails/InformationCard/index.tsx @@ -0,0 +1,159 @@ +import React from "react"; +import { Button, Card } from "@kleros/ui-components-library"; +import styled from "styled-components"; +import { responsiveSize } from "styles/responsiveSize"; +import { getChainIcon, getChainName } from "components/ChainIcon"; +import { getStatusColor, getStatusLabel } from "components/RegistryCard/StatusBanner"; +import AliasDisplay from "components/RegistryInfo/AliasDisplay"; +import { Policies } from "./Policies"; +import EtherscanIcon from "svgs/icons/etherscan.svg"; +import { Status } from "consts/status"; + +const StyledCard = styled(Card)` + display: flex; + width: 100%; + height: auto; + flex-direction: column; + margin-bottom: 64px; +`; + +const StatusContainer = styled.div<{ status: Status; isList: boolean }>` + display: flex; + margin-top: 18px; + .dot { + ::before { + content: ""; + display: inline-block; + height: 8px; + width: 8px; + border-radius: 50%; + margin-right: 8px; + } + } + ${({ theme, status }) => { + const [frontColor] = getStatusColor(status, theme); + return ` + .front-color { + color: ${frontColor}; + } + .dot { + ::before { + background-color: ${frontColor}; + } + } + `; + }}; +`; + +const TopInfo = styled.div` + display: flex; + justify-content: space-between; + flex-wrap: wrap; + gap: 12px; + padding: 12px 32px; +`; + +const LogoAndTitle = styled.div` + display: flex; + align-items: center; +`; + +const TopLeftInfo = styled.div` + display: flex; + flex-direction: column; +`; + +const TopRightInfo = styled.div` + display: flex; + flex-direction: row; + gap: 48px; +`; + +const ChainContainer = styled.div` + display: flex; + gap: 8px; + align-items: top; + justify-content: center; +`; + +const StyledEtherscanIcon = styled(EtherscanIcon)` + display: flex; + height: 16px; + width: 16px; + margin-top: 20px; +`; + +const StyledLogo = styled.img<{ isList: boolean }>` + width: ${({ isList }) => (isList ? "48px" : "125px")}; + height: ${({ isList }) => (isList ? "48px" : "125px")}; + object-fit: contain; + margin-bottom: ${({ isList }) => (isList ? "0px" : "8px")}; +`; + +const StyledP = styled.p` + color: ${({ theme }) => theme.secondaryText}; + margin: 0; +`; + +const Divider = styled.hr` + border: none; + height: 1px; + background-color: ${({ theme }) => theme.stroke}; + margin: ${responsiveSize(20, 28)} 32px; +`; + +const BottomInfo = styled.div` + display: flex; + padding: 0 32px; + padding-bottom: 12px; + flex-wrap: wrap; + gap: 12px; + justify-content: space-between; +`; + +interface IInformationCard { + title: string; + logoURI: string; + description: string; + chainId: number; + status: string; +} + +const InformationCard: React.FC = ({ + title, + logoURI, + description, + chainId = 100, + status = Status.Included, +}) => { + return ( + + + + + +

{title}

+
+ {description} +
+ + +

{getChainIcon(chainId)}

+

{getChainName(chainId)}

+
+ + + + +
+
+ + + +