diff --git a/web/global.d.ts b/web/global.d.ts index b98930e9..64647a54 100644 --- a/web/global.d.ts +++ b/web/global.d.ts @@ -1,6 +1,3 @@ -import {} from "styled-components"; -import { lightTheme } from "./src/styles/themes"; - declare global { module "*.svg" { const content: React.FC>; @@ -12,8 +9,4 @@ declare global { } } -declare module "styled-components" { - type Theme = typeof lightTheme; - //eslint-disable-next-line @typescript-eslint/no-empty-interface - export interface DefaultTheme extends Theme {} -} +export {}; diff --git a/web/package.json b/web/package.json index b0bb3bb5..b1aed968 100644 --- a/web/package.json +++ b/web/package.json @@ -51,7 +51,6 @@ "@types/react": "^18.2.59", "@types/react-dom": "^18.2.18", "@types/react-modal": "^3.16.3", - "@types/styled-components": "^5.1.34", "@typescript-eslint/eslint-plugin": "^5.62.0", "@typescript-eslint/parser": "^5.62.0", "@typescript-eslint/utils": "^5.62.0", @@ -70,17 +69,19 @@ "dependencies": { "@cyntler/react-doc-viewer": "^1.16.3", "@kleros/kleros-app": "^2.1.0", - "@kleros/ui-components-library": "^2.19.0", + "@kleros/ui-components-library": "^3.6.0", "@mdxeditor/editor": "^3.45.0", "@reown/appkit": "^1.6.6", "@reown/appkit-adapter-wagmi": "^1.6.6", "@sentry/react": "^7.93.0", "@sentry/tracing": "^7.93.0", + "@tailwindcss/vite": "^4.1.17", "@tanstack/react-query": "^5.66.0", "@yornaath/batshit": "^0.9.0", "alchemy-sdk": "^3.3.1", "chart.js": "^3.9.1", "chartjs-adapter-moment": "^1.0.1", + "clsx": "^2.1.1", "core-js": "^3.35.0", "graphql": "^16.9.0", "graphql-request": "^7.1.2", @@ -103,8 +104,9 @@ "rehype-raw": "^6.1.1", "rehype-sanitize": "^5.0.1", "remark-gfm": "^3.0.1", - "styled-components": "^5.3.11", "subgraph-status": "^1.2.4", + "tailwind-merge": "^3.4.0", + "tailwindcss": "^4.1.17", "viem": "^2.27.2", "wagmi": "^2.14.16" } diff --git a/web/src/app.tsx b/web/src/app.tsx index 301a884e..cfa6f2cb 100644 --- a/web/src/app.tsx +++ b/web/src/app.tsx @@ -3,10 +3,12 @@ import { Navigate, Route } from "react-router-dom"; import { SentryRoutes } from "./utils/sentry"; import "react-loading-skeleton/dist/skeleton.css"; import "react-toastify/dist/ReactToastify.css"; +import "overlayscrollbars/styles/overlayscrollbars.css"; +import "./global.css"; import Web3Provider from "context/Web3Provider"; import IsListProvider from "context/IsListProvider"; import QueryClientProvider from "context/QueryClientProvider"; -import StyledComponentsProvider from "context/StyledComponentsProvider"; +import ThemeProvider from "context/ThemeProvider"; import GraphqlBatcherProvider from "context/GraphqlBatcher"; import Layout from "layout/index"; import NewTransaction from "./pages/NewTransaction"; @@ -18,7 +20,7 @@ import Settings from "./pages/Settings"; const App: React.FC = () => { return ( - + @@ -41,7 +43,7 @@ const App: React.FC = () => { - + ); }; diff --git a/web/src/components/ConnectWallet/AccountDisplay.tsx b/web/src/components/ConnectWallet/AccountDisplay.tsx index bc44bafc..7961f78d 100644 --- a/web/src/components/ConnectWallet/AccountDisplay.tsx +++ b/web/src/components/ConnectWallet/AccountDisplay.tsx @@ -1,5 +1,4 @@ import React from "react"; -import styled, { css } from "styled-components"; import Identicon from "react-identicons"; import { isAddress } from "viem"; @@ -9,111 +8,7 @@ import { useAccount, useChainId, useEnsAvatar, useEnsName } from "wagmi"; import { getChain } from "consts/chains"; import { shortenAddress } from "utils/shortenAddress"; -import { landscapeStyle } from "styles/landscapeStyle"; - -const Container = styled.div` - display: flex; - flex-direction: column; - justify-content: space-between; - height: auto; - align-items: flex-start; - gap: 8px; - align-items: center; - background-color: ${({ theme }) => theme.whiteBackground}; - padding: 0px; - cursor: pointer; - - &:hover { - label { - color: ${({ theme }) => theme.white} !important; - transition: color 0.2s; - } - } - - ${landscapeStyle( - () => css` - background-color: ${({ theme }) => theme.whiteLowOpacitySubtle}; - &:hover { - transition: background-color 0.1s; - background-color: ${({ theme }) => theme.whiteLowOpacityStrong}; - } - flex-direction: row; - align-content: center; - border-radius: 300px; - gap: 0px; - padding: 0 12px; - ` - )} -`; - -const AccountContainer = styled.div` - min-height: 32px; - display: flex; - align-items: center; - width: fit-content; - gap: 8px; - - > label { - font-size: 16px; - font-weight: 600; - } - - ${landscapeStyle( - () => css` - gap: 12px; - > label { - color: ${({ theme }) => theme.white}CC !important; - font-weight: 400; - font-size: 14px; - } - ` - )} -`; - -const ChainConnectionContainer = styled.div` - display: flex; - width: fit-content; - min-height: 32px; - align-items: center; - padding-left: 0px; - > label { - color: ${({ theme }) => theme.success}; - font-size: 16px; - - font-weight: 500; - } - - :before { - content: ""; - width: 8px; - height: 8px; - margin: 0px 13px 0px 3px; - border-radius: 50%; - background-color: ${({ theme }) => theme.success}; - } - - ${landscapeStyle( - () => css` - display: none; - ` - )} -`; - -const StyledIdenticon = styled(Identicon)<{ size: `${number}` }>` - align-items: center; - svg { - width: ${({ size }) => size + "px"}; - height: ${({ size }) => size + "px"}; - } -`; - -const StyledAvatar = styled.img<{ size: `${number}` }>` - align-items: center; - object-fit: cover; - border-radius: 50%; - width: ${({ size }) => size + "px"}; - height: ${({ size }) => size + "px"}; -`; +import clsx from "clsx"; interface IIdenticonOrAvatar { size?: `${number}`; @@ -134,9 +29,9 @@ export const IdenticonOrAvatar: React.FC = ({ size = "16", a }); return avatar ? ( - + avatar ) : ( - + ); }; @@ -164,15 +59,34 @@ export const ChainDisplay: React.FC = () => { const AccountDisplay: React.FC = () => { return ( - - +
+
label]:text-base [&>label]:font-semibold lg:[&>label]:text-sm lg:[&>label]:font-normal" + )} + > - - +
+
label]:text-klerosUIComponentsSuccess [&>label]:text-base [&>label]:font-medium", + "before:content-[''] before:w-2 before:h-2 before:rounded-full before:bg-klerosUIComponentsSuccess before:my-0 before:mr-[13px] before:ml-[3px]" + )} + > - - +
+
); }; diff --git a/web/src/components/ConnectWallet/index.tsx b/web/src/components/ConnectWallet/index.tsx index 4102e141..eddf305a 100644 --- a/web/src/components/ConnectWallet/index.tsx +++ b/web/src/components/ConnectWallet/index.tsx @@ -22,9 +22,9 @@ export const SwitchChainButton: React.FC<{ className?: string }> = ({ className ); export default SimpleToggleButton; diff --git a/web/src/components/StyledIcons/CheckCircleOutlineIcon.tsx b/web/src/components/StyledIcons/CheckCircleOutlineIcon.tsx index fc4b2f67..474e295c 100644 --- a/web/src/components/StyledIcons/CheckCircleOutlineIcon.tsx +++ b/web/src/components/StyledIcons/CheckCircleOutlineIcon.tsx @@ -1,14 +1,8 @@ import React from "react"; -import styled from "styled-components"; import CheckCircle from "svgs/icons/check-circle-outline.svg"; -const StyledCheckCircle = styled(CheckCircle)` - path { - fill: ${({ theme }) => theme.success}; - } -`; - -const CheckCircleIcon: React.FC = () => { - return ; +const CheckCircleOutlineIcon: React.FC = () => { + return ; }; -export default CheckCircleIcon; + +export default CheckCircleOutlineIcon; diff --git a/web/src/components/StyledIcons/ClosedCircleIcon.tsx b/web/src/components/StyledIcons/ClosedCircleIcon.tsx deleted file mode 100644 index 29b7e47a..00000000 --- a/web/src/components/StyledIcons/ClosedCircleIcon.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import React from "react"; -import styled from "styled-components"; -import ClosedCircle from "svgs/icons/close-circle.svg"; - -const StyledClosedCircle = styled(ClosedCircle)` - path { - fill: ${({ theme }) => theme.error}; - } -`; - -const ClosedCircleIcon: React.FC = () => { - return ; -}; -export default ClosedCircleIcon; diff --git a/web/src/components/StyledIcons/HourglassIcon.tsx b/web/src/components/StyledIcons/HourglassIcon.tsx deleted file mode 100644 index 8037617f..00000000 --- a/web/src/components/StyledIcons/HourglassIcon.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import React from "react"; -import styled from "styled-components"; -import Hourglass from "svgs/icons/hourglass.svg"; - -const StyledHourglass = styled(Hourglass)` - path { - fill: ${({ theme }) => theme.primaryBlue}; - } -`; - -const HourglassIcon: React.FC = () => { - return ; -}; -export default HourglassIcon; diff --git a/web/src/components/StyledIcons/LawBalanceIcon.tsx b/web/src/components/StyledIcons/LawBalanceIcon.tsx index a9994a8e..f44423af 100644 --- a/web/src/components/StyledIcons/LawBalanceIcon.tsx +++ b/web/src/components/StyledIcons/LawBalanceIcon.tsx @@ -1,14 +1,8 @@ import React from "react"; -import styled from "styled-components"; import LawBalance from "svgs/icons/law-balance.svg"; -const StyledCheckCircle = styled(LawBalance)` - path { - fill: ${({ theme }) => theme.secondaryPurple}; - } -`; - const LawBalanceIcon: React.FC = () => { - return ; + return ; }; + export default LawBalanceIcon; diff --git a/web/src/components/StyledSkeleton.tsx b/web/src/components/StyledSkeleton.tsx deleted file mode 100644 index 22da58b9..00000000 --- a/web/src/components/StyledSkeleton.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import React from "react"; -import styled from "styled-components"; -import Skeleton from "react-loading-skeleton"; -import { responsiveSize } from "styles/responsiveSize"; - -export const StyledSkeleton = styled(Skeleton)` - z-index: 0; -`; - -const SkeletonTransactionCardContainer = styled.div` - width: 100%; -`; - -const StyledSkeletonTransactionCard = styled(Skeleton)` - height: ${responsiveSize(270, 296)}; -`; - -const StyledSkeletonTransactionListItem = styled(Skeleton)` - height: 80px; -`; - -const StyledSkeletonEvidenceCard = styled(Skeleton)` - height: 146px; - width: 76vw; -`; - -const StyledSkeletonButton = styled(Skeleton)` - width: 168px; - height: 45px; -`; - -export const StyledSkeletonTitle = styled(Skeleton)` - margin-left: 92px; - width: 200px; -`; - -export const SkeletonTransactionCard = () => ( - - - -); - -export const SkeletonTransactionListItem = () => ; - -export const SkeletonEvidenceCard = () => ; - -export const SkeletonButton = () => ; diff --git a/web/src/components/TransactionCard/StatusBanner.tsx b/web/src/components/TransactionCard/StatusBanner.tsx index 88113e50..c1ff97e5 100644 --- a/web/src/components/TransactionCard/StatusBanner.tsx +++ b/web/src/components/TransactionCard/StatusBanner.tsx @@ -1,98 +1,41 @@ -import React, { useMemo } from "react"; -import styled, { Theme, useTheme, css } from "styled-components"; +import React from "react"; import { Statuses } from "consts/statuses"; -import { isUndefined } from "utils/index"; +import { cn, isUndefined } from "utils/index"; -interface IContainer { - isCard: boolean; - frontColor: string; - backgroundColor: string; - isPreview: boolean; -} - -const Container = styled.div` - display: flex; - height: ${({ isCard }) => (isCard ? "45px" : "100%")}; - border-top-right-radius: 3px; - border-top-left-radius: 3px; - align-items: center; - padding: ${({ isPreview }) => (isPreview ? "0" : "0 24px")}; - justify-content: ${({ isCard }) => (isCard ? "space-between" : "start")}; - ${({ isCard, frontColor, backgroundColor, isPreview }) => { - if (isPreview) { - return css` - border: none; - background-color: transparent; - height: auto; - `; - } else { - return ` - ${isCard ? `border-top: 5px solid ${frontColor}` : `border-left: 5px solid ${frontColor}`}; - ${isCard ? `background-color: ${backgroundColor}` : null}; - `; - } - }}; -`; - -const StyledLabel = styled.label<{ frontColor: string; withDot?: boolean; isCard?: boolean }>` - display: flex; - align-items: center; - color: ${({ frontColor }) => frontColor}; - - ${({ withDot, frontColor }) => - withDot - ? css` - ::before { - content: ""; - height: 8px; - width: 8px; - border-radius: 50%; - margin-right: 8px; - background-color: ${frontColor}; - } - ` - : null} - - ${({ isCard }) => - !isCard - ? css` - width: 104px; - ` - : null} -`; - -const StyledNumberLabel = styled.label<{ frontColor: string; withDot?: boolean; isCard?: boolean }>` - display: flex; - align-items: center; - color: ${({ frontColor }) => frontColor}; +const borderStyles: Record = { + [Statuses.inProgress]: "border-klerosUIComponentsPrimaryBlue", + [Statuses.settlementWaitingBuyer]: "border-klerosUIComponentsWarning", + [Statuses.settlementWaitingSeller]: "border-klerosUIComponentsWarning", + [Statuses.raisingDisputeWaitingBuyer]: "border-klerosUIComponentsWarning", + [Statuses.raisingDisputeWaitingSeller]: "border-klerosUIComponentsWarning", + [Statuses.disputed]: "border-klerosUIComponentsSecondaryPurple", + [Statuses.concluded]: "border-klerosUIComponentsSuccess", +}; - ${({ isCard }) => - !isCard - ? css` - width: 32px; - ` - : null} -`; +const bgStyles: Record = { + [Statuses.inProgress]: "bg-klerosUIComponentsMediumBlue", + [Statuses.settlementWaitingBuyer]: "bg-klerosUIComponentsWarningLight", + [Statuses.settlementWaitingSeller]: "bg-klerosUIComponentsWarningLight", + [Statuses.raisingDisputeWaitingBuyer]: "bg-klerosUIComponentsWarningLight", + [Statuses.raisingDisputeWaitingSeller]: "bg-klerosUIComponentsWarningLight", + [Statuses.disputed]: "bg-klerosUIComponentsMediumPurple", + [Statuses.concluded]: "bg-klerosUIComponentsSuccessLight", +}; -const getStatusColors = (status: Statuses, theme: Theme): [string, string] => { - switch (status) { - case Statuses.inProgress: - return [theme.primaryBlue, theme.mediumBlue]; - case Statuses.settlementWaitingBuyer: - return [theme.warning, theme.warningLight]; - case Statuses.settlementWaitingSeller: - return [theme.warning, theme.warningLight]; - case Statuses.raisingDisputeWaitingBuyer: - return [theme.warning, theme.warningLight]; - case Statuses.raisingDisputeWaitingSeller: - return [theme.warning, theme.warningLight]; - case Statuses.disputed: - return [theme.secondaryPurple, theme.mediumPurple]; - case Statuses.concluded: - return [theme.success, theme.successLight]; - default: - return [theme.lightGrey, theme.lightGrey]; - } +const dotAndFrontColorStyles: Record = { + [Statuses.inProgress]: + "[&_.front-color]:text-klerosUIComponentsPrimaryBlue [&_.dot::before]:bg-klerosUIComponentsPrimaryBlue", + [Statuses.settlementWaitingBuyer]: + "[&_.front-color]:text-klerosUIComponentsWarning [&_.dot::before]:bg-klerosUIComponentsWarning", + [Statuses.settlementWaitingSeller]: + "[&_.front-color]:text-klerosUIComponentsWarning [&_.dot::before]:bg-klerosUIComponentsWarning", + [Statuses.raisingDisputeWaitingBuyer]: + "[&_.front-color]:text-klerosUIComponentsWarning [&_.dot::before]:bg-klerosUIComponentsWarning", + [Statuses.raisingDisputeWaitingSeller]: + "[&_.front-color]:text-klerosUIComponentsWarning [&_.dot::before]:bg-klerosUIComponentsWarning", + [Statuses.disputed]: + "[&_.front-color]:text-klerosUIComponentsSecondaryPurple [&_.dot::before]:bg-klerosUIComponentsSecondaryPurple", + [Statuses.concluded]: "[&_.front-color]:text-klerosUIComponentsSuccess [&_.dot::before]:bg-klerosUIComponentsSuccess", }; const getStatusLabel = (status: Statuses): string => { @@ -124,16 +67,27 @@ export interface IStatusBanner { } const StatusBanner: React.FC = ({ id, status, isCard = true, isPreview = false }) => { - const theme = useTheme(); - const [frontColor, backgroundColor] = useMemo(() => getStatusColors(status, theme), [theme, status]); - return ( - - +
+ + {!isUndefined(id) ? : null} +
); }; diff --git a/web/src/components/TransactionCard/index.tsx b/web/src/components/TransactionCard/index.tsx index 2803481a..9a6c3875 100644 --- a/web/src/components/TransactionCard/index.tsx +++ b/web/src/components/TransactionCard/index.tsx @@ -1,7 +1,4 @@ import React from "react"; -import styled, { css } from "styled-components"; - -import { landscapeStyle } from "styles/landscapeStyle"; import { Card } from "@kleros/ui-components-library"; import { formatEther } from "viem"; @@ -17,51 +14,13 @@ import { useTokenMetadata } from "hooks/useTokenMetadata"; import { TransactionDetailsFragment } from "src/graphql/graphql"; -import { StyledSkeleton, StyledSkeletonTitle } from "../StyledSkeleton"; import TransactionInfo from "../TransactionInfo"; import StatusBanner from "./StatusBanner"; - -const StyledCard = styled(Card)` - width: 100%; - height: 260px; -`; - -const StyledListItem = styled(Card)` - display: flex; - flex-grow: 1; - width: 100%; - height: 82px; -`; - -const CardContainer = styled.div` - height: calc(100% - 45px); - padding: 20px 16px; - display: flex; - flex-direction: column; - justify-content: space-between; - - ${landscapeStyle( - () => css` - padding: 20px 24px; - ` - )} -`; - -const ListContainer = styled.div` - display: flex; - align-items: center; - width: 100%; -`; - -const ListTitle = styled.div``; - -const StyledTitle = styled.h3` - margin: 0; -`; +import Skeleton from "react-loading-skeleton"; const TruncatedTitle = ({ text, maxLength }) => { const truncatedText = text.length <= maxLength ? text : text.slice(0, maxLength) + "…"; - return {truncatedText}; + return

{truncatedText}

; }; interface ITransactionCard extends TransactionDetailsFragment { @@ -93,10 +52,14 @@ const TransactionCard: React.FC = ({ return ( <> {!isList || overrideIsList ? ( - navigateAndScrollTop(`/transactions/${id.toString()}`)}> + navigateAndScrollTop(`/transactions/${id.toString()}`)}> - - {!isUndefined(title) ? {title} : } +
+ {!isUndefined(title) ? ( +

{title}

+ ) : ( + + )} = ({ isPreview={false} {...{ assetSymbol }} /> - - +
+
) : ( - navigateAndScrollTop(`/transactions/${id.toString()}`)}> + navigateAndScrollTop(`/transactions/${id.toString()}`)} + > - +
{!isUndefined(title) ? ( - +
- +
) : ( - + )} = ({ isPreview={false} {...{ assetSymbol }} /> - - +
+
)} ); diff --git a/web/src/components/TransactionInfo/Field.tsx b/web/src/components/TransactionInfo/Field.tsx index 52200490..f0b633dd 100644 --- a/web/src/components/TransactionInfo/Field.tsx +++ b/web/src/components/TransactionInfo/Field.tsx @@ -1,105 +1,48 @@ import React from "react"; -import styled, { css } from "styled-components"; -import { landscapeStyle } from "styles/landscapeStyle"; import { Link } from "react-router-dom"; - -type FieldContainerProps = { - width?: string; - isList?: boolean; - isPreview?: boolean; -}; - -const FieldContainer = styled.div` - display: flex; - align-items: center; - justify-content: flex-start; - flex-wrap: wrap; - word-break: break-word; - width: 100%; - - svg { - fill: ${({ theme }) => theme.secondaryPurple}; - margin-right: 8px; - width: 14px; - } - - ${({ isList }) => - isList && - css` - ${landscapeStyle( - () => css` - width: auto; - ` - )} - `}; - ${({ isPreview }) => - isPreview && - css` - width: auto; - gap: 8px; - svg { - margin-right: 0; - } - `}; -`; - -const StyledValue = styled.label<{ isPreview?: boolean }>` - flex-grow: 1; - text-align: end; - color: ${({ theme }) => theme.primaryText}; - ${({ isPreview }) => - isPreview && - css` - font-weight: 600; - text-align: start; - `} -`; - -const StyledLink = styled(Link)` - flex-grow: 1; - text-align: end; - color: ${({ theme }) => theme.primaryBlue}; - &:hover { - cursor: pointer; - } -`; - -const NameLabel = styled.label<{ isList?: boolean; name: string; isPreview?: boolean }>` - ${({ isList, name }) => - isList && - css` - display: ${name === "Buyer" || name === "Seller" ? "flex" : "none"}; - margin-right: ${name === "Buyer" || name === "Seller" ? "8px" : "0"}; - `} - ${({ isPreview }) => - isPreview && - css` - display: flex; - margin-right: 0; - `} -`; +import { cn } from "src/utils"; interface IField { icon: React.FunctionComponent>; name: string; - value: string; + value: React.ReactNode | string; link?: string; - width?: string; displayAsList?: boolean; isPreview?: boolean; } -const Field: React.FC = ({ icon: Icon, name, value, link, width, displayAsList, isPreview }) => { +const Field: React.FC = ({ icon: Icon, name, value, link, displayAsList, isPreview }) => { return ( - +
<> - - + + + - {link ? {value} : {value}} - + {link ? ( + + {value} + + ) : ( + + )} +
); }; diff --git a/web/src/components/TransactionInfo/index.tsx b/web/src/components/TransactionInfo/index.tsx index ab624c1d..cdea69ec 100644 --- a/web/src/components/TransactionInfo/index.tsx +++ b/web/src/components/TransactionInfo/index.tsx @@ -1,6 +1,4 @@ import React from "react"; -import styled, { css } from "styled-components"; -import { landscapeStyle } from "styles/landscapeStyle"; import { Copiable } from "@kleros/ui-components-library"; import { useEnsName } from "wagmi"; import Skeleton from "react-loading-skeleton"; @@ -12,77 +10,10 @@ import CalendarIcon from "svgs/icons/calendar.svg"; import PileCoinsIcon from "svgs/icons/pile-coins.svg"; import UserIcon from "svgs/icons/user.svg"; import Field from "./Field"; +import { cn } from "src/utils"; -const Container = styled.div<{ isList: boolean; isPreview?: boolean }>` - display: flex; - width: 100%; - gap: 8px; - flex-direction: column; - justify-content: center; - - ${({ isList }) => - isList && - css` - ${landscapeStyle( - () => css` - gap: 0; - height: 100%; - flex: 1; - ` - )} - `}; -`; - -const RestOfFieldsContainer = styled.div<{ isList?: boolean; isPreview?: boolean }>` - display: flex; - flex-direction: column; - gap: 8px; - justify-content: center; - align-items: center; - width: 100%; - height: 100%; - - ${({ isPreview }) => - isPreview && - css` - gap: 16px 32px; - flex-direction: row; - flex-wrap: wrap; - justify-content: flex-start; - `}; - - ${({ isList, isPreview }) => - isList && - !isPreview && - css` - ${landscapeStyle( - () => css` - display: flex; - flex-direction: row; - justify-content: space-between; - align-self: flex-end; - width: auto; - max-width: 360px; - height: auto; - flex-wrap: wrap; - align-items: center; - gap: 8px 32px; - margin-right: 35px; - ` - )} - `}; - -`; - -const StyledA = styled.a` - color: ${({ theme }) => theme.primaryText}; - font-weight: 600; - - &:hover { - text-decoration: underline; - color: ${({ theme }) => theme.primaryBlue}; - } -`; +const aStyle = + "text-klerosUIComponentsPrimaryText font-semibold hover:underline hover:text-klerosUIComponentsPrimaryBlue"; export interface ITransactionInfo { amount?: string; @@ -107,21 +38,32 @@ const TransactionInfo: React.FC = ({ const { isList } = useIsList(); const displayAsList = isList && !overrideIsList; - const { data: buyerEns } = useEnsName({ - address: buyerAddress as `0x${string}`, - chainId: 1 + const { data: buyerEns } = useEnsName({ + address: buyerAddress as `0x${string}`, + chainId: 1, }); - const { data: sellerEns } = useEnsName({ - address: sellerAddress as `0x${string}`, - chainId: 1 + const { data: sellerEns } = useEnsName({ + address: sellerAddress as `0x${string}`, + chainId: 1, }); - const displayBuyerAddress = buyerEns || shortenAddress(buyerAddress); - const displaySellerAddress = sellerEns || shortenAddress(sellerAddress); + const displayBuyerAddress = buyerEns || shortenAddress(buyerAddress ?? ""); + const displaySellerAddress = sellerEns || shortenAddress(sellerAddress ?? ""); return ( - - +
+
{amount ? ( = ({ name="Buyer" value={ isPreview ? ( - - + {displayBuyerAddress} - + ) : ( displayBuyerAddress @@ -173,14 +122,21 @@ const TransactionInfo: React.FC = ({ name="Seller" value={ isPreview ? ( - - + {displaySellerAddress} - + ) : ( displaySellerAddress @@ -190,9 +146,9 @@ const TransactionInfo: React.FC = ({ isPreview={isPreview} /> ) : null} - - +
+
); }; -export default TransactionInfo; \ No newline at end of file +export default TransactionInfo; diff --git a/web/src/components/TransactionsDisplay/Filters.tsx b/web/src/components/TransactionsDisplay/Filters.tsx index e2f1b9b2..ce8ba6a8 100644 --- a/web/src/components/TransactionsDisplay/Filters.tsx +++ b/web/src/components/TransactionsDisplay/Filters.tsx @@ -1,8 +1,5 @@ import React from "react"; -import styled, { css } from "styled-components"; -import { hoverShortTransitionTiming } from "styles/commonStyles"; - import { useNavigate, useParams } from "react-router-dom"; import { DropdownSelect } from "@kleros/ui-components-library"; @@ -12,41 +9,12 @@ import { decodeURIFilter, encodeURIFilter, useRootPath } from "utils/uri"; import ListIcon from "svgs/icons/list.svg"; import GridIcon from "svgs/icons/grid.svg"; +import clsx from "clsx"; -const Container = styled.div` - display: flex; - justify-content: end; - gap: 12px; - width: fit-content; -`; - -const IconsContainer = styled.div` - display: flex; - justify-content: center; - align-items: center; - gap: 4px; -`; - -const BaseIconStyles = css` - ${hoverShortTransitionTiming} - cursor: pointer; - fill: ${({ theme }) => theme.primaryBlue}; - width: 16px; - height: 16px; - overflow: hidden; - - :hover { - fill: ${({ theme }) => theme.secondaryBlue}; - } -`; - -const StyledGridIcon = styled(GridIcon)` - ${BaseIconStyles} -`; - -const StyledListIcon = styled(ListIcon)` - ${BaseIconStyles} -`; +const iconStyles = clsx( + "transition duration-100 cursor-pointer overflow-hidden", + "fill-klerosUIComponentsPrimaryBlue hover:fill-klerosUIComponentsSecondaryBlue" +); const Filters: React.FC = () => { const { order, filter } = useParams(); @@ -63,23 +31,26 @@ const Filters: React.FC = () => { const isDesktop = useIsDesktop(); return ( - +
handleOrderChange(item.itemValue)} /> {isDesktop ? ( - +
{isList ? ( - setIsList(false)} /> + setIsList(false)} /> ) : ( - { if (isDesktop) { setIsList(true); @@ -87,9 +58,9 @@ const Filters: React.FC = () => { }} /> )} - +
) : null} - +
); }; diff --git a/web/src/components/TransactionsDisplay/Search.tsx b/web/src/components/TransactionsDisplay/Search.tsx index b6ead52d..471a07a4 100644 --- a/web/src/components/TransactionsDisplay/Search.tsx +++ b/web/src/components/TransactionsDisplay/Search.tsx @@ -1,53 +1,30 @@ import React, { useState } from "react"; -import styled, { css } from "styled-components"; -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 { isEmpty } from "src/utils"; -const Container = styled.div` - display: flex; - flex-direction: column; - gap: 8px; - - ${landscapeStyle( - () => - css` - flex-direction: row; - gap: 16px; - ` -)} -`; - -const StyledDropdownSelect = styled(DropdownSelect)` - [class*="button__Container"] { - [class*="base-item__Item"] { - border-left: 1px solid transparent; - } - } -` - -const SearchBarContainer = styled.div` - width: 100%; - display: flex; - flex-wrap: wrap; - gap: 8px; - margin-bottom: 5px; - z-index: 0; -`; - -const StyledSearchbar = styled(Searchbar)` - flex: 1; - flex-basis: 310px; - input { - font-size: 16px; - height: 45px; - padding-top: 0px; - padding-bottom: 0px; - } -`; +const dropdownItems = [ + { id: "all", text: "All States", dot: "grey", itemValue: "all" }, + { id: "NoDispute", text: "In Progress", dot: "blue", itemValue: "NoDispute" }, + { + id: "WaitingSettlementBuyer", + text: "Settlement - Waiting Buyer", + dot: "orange", + itemValue: "WaitingSettlementBuyer", + }, + { + id: "WaitingSettlementSeller", + text: "Settlement - Waiting Seller", + dot: "orange", + itemValue: "WaitingSettlementSeller", + }, + { id: "WaitingBuyer", text: "Raising a Dispute - Waiting Buyer", dot: "blue", itemValue: "WaitingBuyer" }, + { id: "WaitingSeller", text: "Raising a Dispute - Waiting Seller", dot: "blue", itemValue: "WaitingSeller" }, + { id: "DisputeCreated", text: "Disputed", dot: "purple", itemValue: "DisputeCreated" }, + { id: "TransactionResolved", text: "Concluded", dot: "green", itemValue: "TransactionResolved" }, +]; const Search: React.FC = () => { const { page, order, filter } = useParams(); @@ -74,30 +51,23 @@ const Search: React.FC = () => { }; return ( - - handleStatusChange(value)} +
+ handleStatusChange(item.itemValue)} /> - - + setSearch(e.target.value)} + onChange={setSearch} /> - - +
+ ); }; diff --git a/web/src/components/TransactionsDisplay/Stats.tsx b/web/src/components/TransactionsDisplay/Stats.tsx index 1b76f045..d9fb9f02 100644 --- a/web/src/components/TransactionsDisplay/Stats.tsx +++ b/web/src/components/TransactionsDisplay/Stats.tsx @@ -1,28 +1,13 @@ import React from "react"; -import styled from "styled-components"; - -const FieldWrapper = styled.div` - display: inline-flex; - gap: 8px; -`; - -const SeparatorLabel = styled.label` - margin: 0 8px; - color: ${({ theme }) => theme.primaryText}; -`; - -const StyledLabel = styled.label` - color: ${({ theme }) => theme.primaryText}; -`; const Field: React.FC<{ label: string; value: string }> = ({ label, value }) => ( - - {label} - {value} - +
+ + {value} +
); -const Separator: React.FC = () => |; +const Separator: React.FC = () => ; export interface IStats { totalTransactions: number; diff --git a/web/src/components/TransactionsDisplay/StatsAndFilters.tsx b/web/src/components/TransactionsDisplay/StatsAndFilters.tsx index a360c666..3694b22e 100644 --- a/web/src/components/TransactionsDisplay/StatsAndFilters.tsx +++ b/web/src/components/TransactionsDisplay/StatsAndFilters.tsx @@ -1,25 +1,13 @@ import React from "react"; -import styled from "styled-components"; - -import { responsiveSize } from "styles/responsiveSize"; import Filters from "./Filters"; import Stats, { IStats } from "./Stats"; -const Container = styled.div` - display: flex; - flex-wrap: wrap; - gap: 8px; - margin-top: ${responsiveSize(4, 8)}; - margin-bottom: ${responsiveSize(16, 32)}; - justify-content: space-between; -`; - const StatsAndFilters: React.FC = ({ totalTransactions, resolvedTransactions }) => ( - +
- +
); export default StatsAndFilters; diff --git a/web/src/components/TransactionsDisplay/TransactionsGrid.tsx b/web/src/components/TransactionsDisplay/TransactionsGrid.tsx index 5a74bf19..5408ff9a 100644 --- a/web/src/components/TransactionsDisplay/TransactionsGrid.tsx +++ b/web/src/components/TransactionsDisplay/TransactionsGrid.tsx @@ -1,36 +1,15 @@ import React, { useMemo } from "react"; -import styled from "styled-components"; import { useWindowSize } from "react-use"; import { useParams } from "react-router-dom"; -import { SkeletonTransactionCard, SkeletonTransactionListItem } from "../StyledSkeleton"; 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 TransactionCard from "components/TransactionCard"; import { TransactionDetailsFragment } from "src/graphql/graphql"; - -const GridContainer = 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: center; - gap: var(--gap); -`; - -const ListContainer = styled.div` - display: flex; - flex-direction: column; - justify-content: center; - gap: 8px; -`; - -const StyledPagination = styled(StandardPagination)` - margin-top: 24px; - margin-left: auto; - margin-right: auto; -`; +import clsx from "clsx"; +import { LG_BREAKPOINT } from "src/styles/breakpoints"; +import Skeleton from "react-loading-skeleton"; export interface ITransactionsGrid { transactions?: TransactionDetailsFragment[]; @@ -52,30 +31,36 @@ const TransactionsGrid: React.FC = ({ const { id: searchValue } = decodedFilter; const { isList } = useIsList(); const { width } = useWindowSize(); - const screenIsBig = useMemo(() => width > BREAKPOINT_LANDSCAPE, [width]); + const screenIsBig = useMemo(() => width > LG_BREAKPOINT, [width]); return ( <> {isList && screenIsBig ? ( - +
{isUndefined(transactions) - ? [...Array(transactionsPerPage)].map((_, i) => ) + ? [...Array(transactionsPerPage)].map((_, i) => ) : transactions.map((transaction) => { return ; })} - +
) : ( - +
{isUndefined(transactions) - ? [...Array(transactionsPerPage)].map((_, i) => ) + ? [...Array(transactionsPerPage)].map((_, i) => ) : transactions.map((transaction) => { return ; })} - +
)} {isUndefined(searchValue) ? ( - setCurrentPage(page)} diff --git a/web/src/components/TransactionsDisplay/index.tsx b/web/src/components/TransactionsDisplay/index.tsx index 6bf02f50..0c671924 100644 --- a/web/src/components/TransactionsDisplay/index.tsx +++ b/web/src/components/TransactionsDisplay/index.tsx @@ -1,20 +1,9 @@ import React from "react"; -import styled from "styled-components"; import Search from "./Search"; import StatsAndFilters from "./StatsAndFilters"; import TransactionsGrid, { ITransactionsGrid } from "./TransactionsGrid"; -import { responsiveSize } from "styles/responsiveSize"; import { TransactionDetailsFragment } from "src/graphql/graphql"; -const StyledTitle = styled.h1` - margin-bottom: ${responsiveSize(12, 24)}; - font-size: ${responsiveSize(20, 24)}; -`; - -const StyledLabel = styled.label` - font-size: ${responsiveSize(14, 16)}; -`; - interface ITransactionsDisplay extends ITransactionsGrid { transactions?: TransactionDetailsFragment[]; totalTransactions?: number; @@ -36,12 +25,12 @@ const TransactionsDisplay: React.FC = ({ }) => { return (
- {title} +

{title}

{transactions?.length === 0 ? ( - No transactions found + ) : ( theme.secondaryText}; - margin: 0 0 0 8px; - - ${landscapeStyle( - () => css` - height: 14px; - width: 14px; - ` - )} -`; - -interface IWithHelpTooltip { - tooltipMsg: string; - place?: "bottom" | "left" | "right" | "top"; - children?: React.ReactNode; -} - -const WithHelpTooltip: React.FC = ({ tooltipMsg, children, place }) => ( - - {children} - - - - -); - -export default WithHelpTooltip; diff --git a/web/src/consts/community-elements.tsx b/web/src/consts/community-elements.tsx deleted file mode 100644 index 41898e2f..00000000 --- a/web/src/consts/community-elements.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import React from "react"; -import styled, { css } from "styled-components"; -import { IElement } from "../pages/Home/Community/Element"; -import PNKIcon from "svgs/icons/pnk.svg"; -import SnapshotIcon from "svgs/icons/snapshot-color.svg"; -import FrenchFlagIcon from "svgs/icons/french-flag.svg"; -import TelegramIcon from "svgs/socialmedia/telegram.svg"; - -const StyledLabel = styled.label` - color: ${({ theme }) => theme.primaryBlue}; - :hover { - cursor: pointer; - } -`; - -const fillWithSecondaryPurple = css` - fill: ${({ theme }) => theme.secondaryPurple}; -`; - -const StyledPNKIcon = styled(PNKIcon)` - ${fillWithSecondaryPurple} -`; - -const fillWithPrimaryBlue = css` - fill: ${({ theme }) => theme.primaryBlue}; -`; - -const StyledTelegramIcon = styled(TelegramIcon)` - ${fillWithPrimaryBlue} -`; - -export const section: IElement[] = [ - { - Icon: StyledPNKIcon, - urls: [ - { - node: Kleros Forum, - link: "https://forum.kleros.io/", - }, - ], - }, - { - Icon: SnapshotIcon, - urls: [ - { - node: Vote on proposals, - link: "https://snapshot.org/#/kleros.eth/", - }, - ], - }, - { - Icon: StyledTelegramIcon, - primaryText: "Wednesday, 18h UTC", - urls: [ - { - node: Community Calls, - link: "https://t.me/kleros", - }, - ], - }, - { - Icon: FrenchFlagIcon, - urls: [ - { - node: Join the Coopérative, - link: "https://kleros.io/coop/", - }, - ], - }, -]; diff --git a/web/src/context/StyledComponentsProvider.tsx b/web/src/context/StyledComponentsProvider.tsx deleted file mode 100644 index 7d11c340..00000000 --- a/web/src/context/StyledComponentsProvider.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import React from "react"; -import { ThemeProvider } from "styled-components"; -import { useLocalStorage } from "hooks/useLocalStorage"; -import { ToggleThemeProvider } from "hooks/useToggleThemeContext"; -import { GlobalStyle } from "styles/global-style"; -import { lightTheme, darkTheme } from "styles/themes"; - -const StyledComponentsProvider: React.FC<{ - children: React.ReactNode; -}> = ({ children }) => { - const [theme, setTheme] = useLocalStorage("theme", "dark"); - const toggleTheme = () => { - if (theme === "light") setTheme("dark"); - else setTheme("light"); - }; - return ( - - - - {children} - - - ); -}; - -export default StyledComponentsProvider; diff --git a/web/src/context/ThemeProvider.tsx b/web/src/context/ThemeProvider.tsx new file mode 100644 index 00000000..9530ccea --- /dev/null +++ b/web/src/context/ThemeProvider.tsx @@ -0,0 +1,29 @@ +import React, { useEffect } from "react"; +import { useLocalStorage } from "hooks/useLocalStorage"; +import { ToggleThemeProvider } from "hooks/useToggleThemeContext"; + +type Theme = "light" | "dark"; + +const ThemeProvider: React.FC<{ + children: React.ReactNode; +}> = ({ children }) => { + const [theme, setTheme] = useLocalStorage("theme", "dark"); + + const toggleTheme = () => { + if (theme === "light") setTheme("dark"); + else setTheme("light"); + }; + + useEffect(() => { + const root = document.documentElement; + if (theme === "dark") { + root.classList.add("dark"); + } else { + root.classList.remove("dark"); + } + }, [theme]); + + return {children}; +}; + +export default ThemeProvider; diff --git a/web/src/context/Web3Provider.tsx b/web/src/context/Web3Provider.tsx index 23b263f9..e665fc87 100644 --- a/web/src/context/Web3Provider.tsx +++ b/web/src/context/Web3Provider.tsx @@ -1,10 +1,17 @@ import React from "react"; import { fallback, http, WagmiProvider, webSocket } from "wagmi"; -import { mainnet, arbitrumSepolia, gnosisChiado, type AppKitNetwork, arbitrum, sepolia, gnosis } from "@reown/appkit/networks"; +import { + mainnet, + arbitrumSepolia, + gnosisChiado, + type AppKitNetwork, + arbitrum, + sepolia, + gnosis, +} from "@reown/appkit/networks"; import { createAppKit } from "@reown/appkit/react"; import { WagmiAdapter } from "@reown/appkit-adapter-wagmi"; -import { lightTheme } from "styles/themes"; import { isProductionDeployment } from "consts/index"; import { ALL_CHAINS, DEFAULT_CHAIN } from "consts/chains"; @@ -82,7 +89,7 @@ createAppKit({ projectId, allowUnsupportedChain: true, themeVariables: { - "--w3m-color-mix": lightTheme.primaryPurple, + "--w3m-color-mix": "#4D00B4", "--w3m-color-mix-strength": 20, // overlay portal is at 9999 "--w3m-z-index": 10000, diff --git a/web/src/global.css b/web/src/global.css new file mode 100644 index 00000000..21285c15 --- /dev/null +++ b/web/src/global.css @@ -0,0 +1,168 @@ +@import "../../node_modules/@kleros/ui-components-library/dist/assets/theme.css"; +@tailwind utilities; +@source "../../node_modules/@kleros/ui-components-library"; +@import "tailwindcss"; +@custom-variant dark (&:where(.dark, .dark *)); + +@theme { + --color-white: #ffffff; + --color-white-73: #ffffffba; + --color-black: #000000; + --color-light-blue-65: #2a1260a6; + --color-light-grey: #f0f0f0; + --color-dark-purple: #220050; + --color-violet-purple: #6a1dcd; + --color-lavender-purple: #bb72ff; + --color-pale-cyan: #acffff; + --color-lime-green: #f3ffd9; + --color-white-low-opacity-subtle: #ffffff0d; + --color-white-low-opacity-strong: #ffffff26; + --color-black-low-opacity: #00000080; + --color-primary-text-73: color-mix(in srgb, var(--klerosUIComponentsPrimaryText) 73%, transparent); + --color-skeleton-bg: #ebebeb; + --color-skeleton-highlight: #f5f5f5; + --max-width-landscape: 87.5rem; + --shadow-custom: 0px 2px 3px rgba(0, 0, 0, 0.06); + --leading-18px: 18px; + + /* + * Spacing utils for responsive resizing + * Default max screen width is 1250px + * Default min screen width is 375px + */ + --spacing-fluid-0-132: clamp(0px, calc(0px + (132 - 0) * (100vw - 375px) / (1250 - 375)), 132px); + --spacing-fluid-4-8: clamp(4px, calc(4px + (8 - 4) * (100vw - 375px) / (1250 - 375)), 8px); + --spacing-fluid-4-20: clamp(4px, calc(4px + (20 - 4) * (100vw - 375px) / (1250 - 375)), 20px); + --spacing-fluid-5-6: clamp(5px, calc(5px + (6 - 5) * (100vw - 375px) / (1250 - 375)), 6px); + --spacing-fluid-6-8-300: clamp(6px, calc(6px + (8 - 6) * (100vw - 300px) / (1250 - 300)), 8px); + --spacing-fluid-8-24: clamp(8px, calc(8px + (24 - 8) * (100vw - 375px) / (1250 - 375)), 24px); + --spacing-fluid-8-32-300: clamp(8px, calc(8px + (32 - 8) * (100vw - 300px) / (1250 - 300)), 32px); + --spacing-fluid-12-24: clamp(12px, calc(12px + (24 - 12) * (100vw - 375px) / (1250 - 375)), 24px); + --spacing-fluid-12-32-300: clamp(12px, calc(12px + (32 - 12) * (100vw - 300px) / (1250 - 300)), 32px); + --spacing-fluid-14-16: clamp(14px, calc(14px + (16 - 14) * (100vw - 375px) / (1250 - 375)), 16px); + --spacing-fluid-16-0: clamp(0px, calc(16px + (0 - 16) * (100vw - 375px) / (1250 - 375)), 16px); + --spacing-fluid-16-24: clamp(16px, calc(16px + (24 - 16) * (100vw - 375px) / (1250 - 375)), 24px); + --spacing-fluid-16-32: clamp(16px, calc(16px + (32 - 16) * (100vw - 375px) / (1250 - 375)), 32px); + --spacing-fluid-20-24: clamp(20px, calc(20px + (24 - 20) * (100vw - 375px) / (1250 - 375)), 24px); + --spacing-fluid-24-18: clamp(18px, calc(24px + (18 - 24) * (100vw - 375px) / (1250 - 375)), 24px); + --spacing-fluid-24-136: clamp(24px, calc(24px + (136 - 24) * (100vw - 375px) / (1250 - 375)), 136px); + --spacing-fluid-24-32-900: clamp(24px, calc(24px + (32 - 24) * (100vw - 900px) / (1250 - 900)), 32px); + --spacing-fluid-32-80: clamp(32px, calc(32px + (80 - 32) * (100vw - 375px) / (1250 - 375)), 80px); + --spacing-fluid-64-36: clamp(36px, calc(64px + (36 - 64) * (100vw - 375px) / (1250 - 375)), 64px); + --spacing-fluid-76-96: clamp(76px, calc(76px + (96 - 76) * (100vw - 375px) / (1250 - 375)), 96px); + --spacing-fluid-100-130: clamp(100px, calc(100px + (130 - 100) * (100vw - 375px) / (1250 - 375)), 130px); + --spacing-fluid-270-296: clamp(270px, calc(270px + (296 - 270) * (100vw - 375px) / (1250 - 375)), 296px); + --spacing-fluid-300-424-300: clamp(300px, calc(300px + (424 - 300) * (100vw - 300px) / (1250 - 300)), 424px); + --spacing-fluid-300-480: clamp(300px, calc(300px + (480 - 300) * (100vw - 375px) / (1250 - 375)), 480px); + --spacing-fluid-320-500: clamp(320px, calc(320px + (500 - 320) * (100vw - 375px) / (1250 - 375)), 500px); + --spacing-fluid-342-476: clamp(342px, calc(342px + (476 - 342) * (100vw - 375px) / (1250 - 375)), 476px); + --spacing-fluid-342-500: clamp(342px, calc(342px + (500 - 342) * (100vw - 375px) / (1250 - 375)), 500px); + --spacing-fluid-342-574: clamp(342px, calc(342px + (574 - 342) * (100vw - 375px) / (1250 - 375)), 574px); + --spacing-fluid-342-618: clamp(342px, calc(342px + (618 - 342) * (100vw - 375px) / (1250 - 375)), 618px); + --spacing-fluid-342-699: clamp(342px, calc(342px + (699 - 342) * (100vw - 375px) / (1250 - 375)), 699px); +} + +.dark { + --color-skeleton-bg: #3a2270; + --color-skeleton-highlight: #3e307c; +} + +:root { + --toastify-color-info: var(--klerosUIComponentsPrimaryBlue); + --toastify-color-success: var(--klerosUIComponentsSuccess); + --toastify-color-warning: var(--klerosUIComponentsWarning); + --toastify-color-error: var(--klerosUIComponentsError); +} + +.react-loading-skeleton { + z-index: 0; + --base-color: var(--color-skeleton-bg); + --highlight-color: var(--color-skeleton-highlight); +} + +body { + font-family: "Open Sans", sans-serif; + margin: 0; + background-color: var(--klerosUIComponentsLightBlue); +} + +html { + box-sizing: border-box; +} + +*, +*:before, +*:after { + box-sizing: inherit; +} + +h1 { + font-weight: 600; + font-size: 24px; + line-height: 32px; + color: var(--klerosUIComponentsPrimaryText); +} + +label { + font-weight: 400; + font-size: 14px; + line-height: 18px; + color: var(--klerosUIComponentsSecondaryText); +} + +a { + font-weight: 400; + font-size: 14px; + text-decoration: none; + color: var(--klerosUIComponentsPrimaryBlue); +} + +hr { + opacity: 1; + border: 1px solid var(--klerosUIComponentsStroke); +} + +.os-theme-dark { + --os-handle-bg: var(--color-violet-purple); + --os-handle-bg-hover: var(--klerosUIComponentsSecondaryPurple); + --os-handle-bg-active: var(--color-lavender-purple); +} + +.Toastify__toast-container.Toastify__toast-container { + top: unset; + padding-top: 20px; +} + +/* @cyntler/react-doc-viewer injects a canvas to load pdf, this is alters the height of body tag, so set to hidden */ +.hiddenCanvasElement { + display: none; +} + +/* Custom Scrollbar */ +.custom-scrollbar::-webkit-scrollbar { + width: 6px; + height: 6px; +} + +.custom-scrollbar::-webkit-scrollbar-track { + background: transparent; +} + +.custom-scrollbar::-webkit-scrollbar-thumb { + background-color: var(--color-violet-purple); + border-radius: 10px; + transition: opacity 0.15s, background-color 0.15s, border-color 0.15s, width 0.15s; +} + +.custom-scrollbar::-webkit-scrollbar-thumb:hover { + background-color: var(--klerosUIComponentsSecondaryPurple); +} + +.custom-scrollbar::-webkit-scrollbar-thumb:active { + background-color: var(--color-lavender-purple); +} + +.custom-scrollbar { + scrollbar-width: thin; + scrollbar-color: var(--color-violet-purple) transparent; +} diff --git a/web/src/hooks/useEscrowTimelineItems.tsx b/web/src/hooks/useEscrowTimelineItems.tsx index 0aff36ca..e0b2a0c7 100644 --- a/web/src/hooks/useEscrowTimelineItems.tsx +++ b/web/src/hooks/useEscrowTimelineItems.tsx @@ -1,21 +1,29 @@ import React from "react"; import { useEffect, useMemo, useState } from "react"; -import { DefaultTheme, useTheme } from "styled-components"; import { formatEther } from "viem"; import { getFormattedDate } from "utils/getFormattedDate"; import { resolutionToString } from "utils/resolutionToString"; import { formatTimeoutDuration } from "utils/formatTimeoutDuration"; import CheckCircleOutlineIcon from "components/StyledIcons/CheckCircleOutlineIcon"; import LawBalanceIcon from "components/StyledIcons/LawBalanceIcon"; -import { StyledSkeleton } from "components/StyledSkeleton"; import { DisputeRequest, HasToPayFee, Payment, SettlementProposal, TransactionResolved } from "src/graphql/graphql"; +import Skeleton from "react-loading-skeleton"; + +type Variant = "primaryBlue" | "secondaryBlue" | "warning" | "secondaryPurple" | "success"; +const variantToCssVar: Record = { + primaryBlue: "--klerosUIComponentsPrimaryBlue", + secondaryBlue: "--klerosUIComponentsSecondaryBlue", + warning: "--klerosUIComponentsWarning", + secondaryPurple: "--klerosUIComponentsSecondaryPurple", + success: "--klerosUIComponentsSuccess", +}; interface TimelineItem { title: string; party?: string; subtitle: string; rightSided: boolean; - variant: keyof DefaultTheme; + variant: string; Icon?: React.ElementType; } @@ -23,11 +31,15 @@ function calculateTimeLeft(timestamp: number, timeout: number, currentTime: numb return Math.max(timeout - (currentTime - timestamp), 0); } +function getThemeColor(variant: Variant): string { + return getComputedStyle(document.documentElement).getPropertyValue(variantToCssVar[variant]).trim(); +} + function createTimelineItem( formattedDate: string, title: string, party: string, - variant: keyof DefaultTheme, + variant: Variant, Icon?: React.ElementType ): TimelineItem { return { @@ -35,7 +47,7 @@ function createTimelineItem( party, subtitle: formattedDate, rightSided: true, - variant, + variant: getThemeColor(variant), ...(Icon && { Icon }), }; } @@ -55,7 +67,6 @@ const useEscrowTimelineItems = ( feeTimeout: number, settlementTimeout: number ): TimelineItem[] => { - const theme = useTheme(); const [currentTime, setCurrentTime] = useState(Math.floor(Date.now() / 1000)); useEffect(() => { @@ -69,7 +80,7 @@ const useEscrowTimelineItems = ( const formattedCreationDate = isPreview ? getFormattedDate(new Date()) : getFormattedDate(new Date(transactionCreationTimestamp * 1000)); - timelineItems.push(createTimelineItem(formattedCreationDate, "Escrow created", "", theme.primaryBlue)); + timelineItems.push(createTimelineItem(formattedCreationDate, "Escrow created", "", "primaryBlue")); if (!isPreview) { payments?.forEach((payment) => { @@ -78,11 +89,11 @@ const useEscrowTimelineItems = ( const title = ( <> The {isBuyer ? "buyer" : "seller"} paid {formatEther(payment.amount)}{" "} - {assetSymbol ? assetSymbol : } + {assetSymbol ? assetSymbol : } ); - timelineItems.push(createTimelineItem(formattedDate, title, "", theme.secondaryBlue)); + timelineItems.push(createTimelineItem(formattedDate, title, "", "secondaryBlue")); }); settlementProposals?.forEach((proposal, index) => { @@ -109,10 +120,10 @@ const useEscrowTimelineItems = ( const title = ( <> The {proposal.party === "1" ? "buyer" : "seller"} proposed: Pay {formatEther(proposal.amount)}{" "} - {assetSymbol ? assetSymbol : } + {assetSymbol ? assetSymbol : } ); - timelineItems.push(createTimelineItem(formattedDate, title, subtitle, theme.warning)); + timelineItems.push(createTimelineItem(formattedDate, title, subtitle, "warning")); }); hasToPayFees?.forEach((fee) => { @@ -127,7 +138,7 @@ const useEscrowTimelineItems = ( ? "Arbitration fees deposited" : `${fee.party === "2" ? "Seller" : "Buyer"}${timeoutCountdownMessage}`; - timelineItems.push(createTimelineItem(formattedDate, title, party, theme.secondaryPurple)); + timelineItems.push(createTimelineItem(formattedDate, title, party, "secondaryPurple")); }); if (disputeRequest) { @@ -137,7 +148,7 @@ const useEscrowTimelineItems = ( formattedDate, "Dispute created", `Case #${disputeRequest.id}`, - theme.secondaryPurple, + "secondaryPurple", LawBalanceIcon ) ); @@ -152,7 +163,7 @@ const useEscrowTimelineItems = ( formattedDate, "Concluded", resolutionToString(resolutionEvent.resolution), - theme.success, + "success", CheckCircleOutlineIcon ) ); @@ -173,7 +184,6 @@ const useEscrowTimelineItems = ( feeTimeout, settlementTimeout, currentTime, - theme, assetSymbol, buyer, seller, diff --git a/web/src/hooks/useFilteredTokens.ts b/web/src/hooks/useFilteredTokens.ts index bdad3f2e..f8c167f2 100644 --- a/web/src/hooks/useFilteredTokens.ts +++ b/web/src/hooks/useFilteredTokens.ts @@ -23,20 +23,26 @@ export const useFilteredTokens = ( filtered = [sendingToken, ...tokens.filter((token) => token.address !== sendingToken.address)]; } } else if (tokenMetadata) { - const resultToken = { - symbol: tokenMetadata.symbol, - address: searchQuery.toLowerCase(), - logo: tokenMetadata.logo, - }; - - const updatedTokens = [...tokens, resultToken]; - const uniqueTokens = Array.from(new Set(updatedTokens.map((a) => a.address))).map((address) => { - return updatedTokens.find((a) => a.address === address); - }); - - filtered = [resultToken]; - setTokens(uniqueTokens); - localStorage.setItem("tokens", JSON.stringify(uniqueTokens)); + const existingToken = tokens.find((t) => t.address.toLowerCase() === searchQuery.toLowerCase()); + + if (existingToken) { + filtered = [existingToken]; + } else { + const resultToken = { + symbol: tokenMetadata.symbol, + address: searchQuery.toLowerCase(), + logo: tokenMetadata.logo, + }; + + const updatedTokens = [...tokens, resultToken]; + const uniqueTokens = Array.from(new Set(updatedTokens.map((a) => a.address))).map((address) => { + return updatedTokens.find((a) => a.address === address); + }); + + filtered = [resultToken]; + setTokens(uniqueTokens); + localStorage.setItem("tokens", JSON.stringify(uniqueTokens)); + } } else { filtered = tokens.filter((token) => token.symbol.toLowerCase().includes(searchQuery.toLowerCase())); } diff --git a/web/src/hooks/useIsDesktop.tsx b/web/src/hooks/useIsDesktop.tsx index 1ff105d5..5bc0526e 100644 --- a/web/src/hooks/useIsDesktop.tsx +++ b/web/src/hooks/useIsDesktop.tsx @@ -1,12 +1,11 @@ import { useMemo } from "react"; import { useWindowSize } from "react-use"; - -import { BREAKPOINT_LANDSCAPE } from "styles/landscapeStyle"; +import { LG_BREAKPOINT } from "src/styles/breakpoints"; const useIsDesktop = () => { const { width } = useWindowSize(); - return useMemo(() => width > BREAKPOINT_LANDSCAPE, [width]); + return useMemo(() => width > LG_BREAKPOINT, [width]); }; export default useIsDesktop; diff --git a/web/src/hooks/useToggleThemeContext.tsx b/web/src/hooks/useToggleThemeContext.tsx index 9c2017de..610bb469 100644 --- a/web/src/hooks/useToggleThemeContext.tsx +++ b/web/src/hooks/useToggleThemeContext.tsx @@ -12,11 +12,9 @@ export const ToggleThemeProvider: React.FC<{ theme: string; toggleTheme: () => void; }> = ({ theme, toggleTheme, children }) => { - return ( - {children} - ); + return {children}; }; -export const useToggleTheme: () => [string, () => void] = () => { +export const useTheme: () => [string, () => void] = () => { return useContext(Context); }; diff --git a/web/src/layout/Footer/index.tsx b/web/src/layout/Footer/index.tsx index bebb00c2..688bd095 100644 --- a/web/src/layout/Footer/index.tsx +++ b/web/src/layout/Footer/index.tsx @@ -1,79 +1,39 @@ import React from "react"; -import styled, { css } from "styled-components"; - -import { landscapeStyle } from "styles/landscapeStyle"; -import { hoverShortTransitionTiming } from "styles/commonStyles"; import SecuredByKlerosLogo from "svgs/footer/secured-by-kleros.svg"; import { socialmedia } from "consts/socialmedia"; import LightButton from "components/LightButton"; -import { ExternalLink } from "components/ExternalLink"; - -const Container = styled.div` - height: 114px; - width: 100%; - background-color: ${({ theme }) => (theme.name === "dark" ? theme.lightBlue : theme.primaryPurple)}; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - padding: 8px; - gap: 16px; - - ${landscapeStyle( - () => css` - height: 64px; - flex-direction: row; - justify-content: space-between; - padding: 0 32px; - ` - )} -`; - -const StyledSecuredByKlerosLogo = styled(SecuredByKlerosLogo)` - ${hoverShortTransitionTiming} - min-height: 24px; - - path { - fill: ${({ theme }) => theme.white}BF; - } - - :hover path { - fill: ${({ theme }) => theme.white}; - } -`; - -const StyledSocialMedia = styled.div` - display: flex; - - .button-svg { - margin-right: 0; - } -`; +import clsx from "clsx"; const SecuredByKleros: React.FC = () => ( - - - + + + ); const SocialMedia = () => ( - +
{Object.values(socialmedia).map((site, i) => ( - + - + ))} - +
); const Footer: React.FC = () => ( - +
- +
); export default Footer; diff --git a/web/src/layout/Header/DesktopHeader.tsx b/web/src/layout/Header/DesktopHeader.tsx index 9b4cc072..73e26854 100644 --- a/web/src/layout/Header/DesktopHeader.tsx +++ b/web/src/layout/Header/DesktopHeader.tsx @@ -1,8 +1,4 @@ import React, { useCallback, useEffect, useState } from "react"; -import styled, { css } from "styled-components"; - -import { landscapeStyle } from "styles/landscapeStyle"; -import { responsiveSize } from "styles/responsiveSize"; import { useToggle } from "react-use"; import { useAccount } from "wagmi"; @@ -24,62 +20,6 @@ import Help from "./navbar/Menu/Help"; import Settings from "./navbar/Menu/Settings"; import Logo from "./Logo"; -const Container = styled.div` - display: none; - position: absolute; - height: 64px; - - ${landscapeStyle( - () => css` - display: flex; - align-items: center; - justify-content: space-between; - width: 100%; - position: relative; - ` - )}; -`; - -const LeftSide = styled.div` - display: flex; - gap: 8px; -`; - -const MiddleSide = styled.div` - display: flex; - position: absolute; - left: 50%; - top: 50%; - transform: translate(-50%, -50%); - color: ${({ theme }) => theme.white} !important; -`; - -const RightSide = styled.div` - display: flex; - gap: ${responsiveSize(4, 8)}; - - margin-left: 8px; - canvas { - width: 20px; - } -`; - -const LightButtonContainer = styled.div` - display: flex; - align-items: center; -`; - -const StyledKlerosSolutionsIcon = styled(KlerosSolutionsIcon)` - fill: ${({ theme }) => theme.white} !important; -`; - -const ConnectWalletContainer = styled.div<{ isConnected: boolean; isDefaultChain: boolean }>` - label { - color: ${({ theme }) => theme.white}; - cursor: pointer; - } -`; - const DesktopHeader = () => { const [isDappListOpen, toggleIsDappListOpen] = useToggle(false); const [isHelpOpen, toggleIsHelpOpen] = useToggle(false); @@ -101,34 +41,28 @@ const DesktopHeader = () => { return ( <> - - - - { - toggleIsDappListOpen(); - }} - Icon={StyledKlerosSolutionsIcon} - /> - +
+
+
+ +
- +
- +
- +
- - +
- +
- - +
+
{(isDappListOpen || isHelpOpen || isSettingsOpen) && ( diff --git a/web/src/layout/Header/Logo.tsx b/web/src/layout/Header/Logo.tsx index 9e793904..0eaf24f0 100644 --- a/web/src/layout/Header/Logo.tsx +++ b/web/src/layout/Header/Logo.tsx @@ -1,38 +1,14 @@ import React from "react"; -import styled from "styled-components"; - -import { hoverShortTransitionTiming } from "styles/commonStyles"; - import { Link } from "react-router-dom"; - import EscrowLogo from "svgs/header/escrow.svg"; -const Container = styled.div` - display: flex; - flex-direction: row; - align-items: center; - gap: 16px; -`; - -const StyledEscrowLogo = styled(EscrowLogo)` - ${hoverShortTransitionTiming} - max-height: 48px; - width: auto; - - &:hover { - path { - fill: ${({ theme }) => theme.white}BF; - } - } -`; - const Logo: React.FC = () => ( - +
{" "} - + - +
); export default Logo; diff --git a/web/src/layout/Header/MobileHeader.tsx b/web/src/layout/Header/MobileHeader.tsx index 07ae91bc..2e646b0e 100644 --- a/web/src/layout/Header/MobileHeader.tsx +++ b/web/src/layout/Header/MobileHeader.tsx @@ -1,39 +1,12 @@ import React, { useContext, useMemo, useRef } from "react"; -import styled, { css } from "styled-components"; import { useClickAway, useToggle } from "react-use"; -import { landscapeStyle } from "styles/landscapeStyle"; - -import { Link } from "react-router-dom"; - import HamburgerIcon from "svgs/header/hamburger.svg"; import LightButton from "components/LightButton"; import NavBar from "./navbar"; import Logo from "./Logo"; -const Container = styled.div` - display: flex; - align-items: center; - justify-content: space-between; - width: 100%; - height: 64px; - - ${landscapeStyle( - () => css` - display: none; - ` - )} -`; - -const StyledLightButton = styled(LightButton)` - padding: 0 !important; - - .button-svg { - margin-right: 0px; - } -`; - const OpenContext = React.createContext({ isOpen: false, toggleIsOpen: () => { @@ -51,15 +24,13 @@ const MobileHeader = () => { useClickAway(containerRef, () => toggleIsOpen(false)); const memoizedContext = useMemo(() => ({ isOpen, toggleIsOpen }), [isOpen, toggleIsOpen]); return ( - +
- - - + - + - +
); }; export default MobileHeader; diff --git a/web/src/layout/Header/index.tsx b/web/src/layout/Header/index.tsx index 2c7338ec..39203985 100644 --- a/web/src/layout/Header/index.tsx +++ b/web/src/layout/Header/index.tsx @@ -1,68 +1,36 @@ import React from "react"; -import styled, { useTheme } from "styled-components"; - import DesktopHeader from "./DesktopHeader"; import MobileHeader from "./MobileHeader"; import { StatusBanner } from "subgraph-status"; import { getGraphqlUrl } from "utils/getGraphqlUrl"; +import clsx from "clsx"; -const Container = styled.div` - display: flex; - flex-wrap: wrap; - position: sticky; - z-index: 10; - top: 0; - width: 100%; - background-color: ${({ theme }) => (theme.name === "dark" ? `${theme.lightBlue}A6` : theme.primaryPurple)}; - backdrop-filter: ${({ theme }) => (theme.name === "dark" ? "blur(12px)" : "none")}; - -webkit-backdrop-filter: ${({ theme }) => (theme.name === "dark" ? "blur(12px)" : "none")}; // Safari support -`; - -export const PopupContainer = styled.div` - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - z-index: 30; - background-color: ${({ theme }) => theme.blackLowOpacity}; -`; - -const HeaderContainer = styled.div` - width: 100%; - padding: 0px 24px; -`; - -const StyledBanner = styled(StatusBanner)` - position: sticky !important; - .status-text { - h2 { - margin: 0; - line-height: 24px; - } - } -`; const Header: React.FC = () => { - const theme = useTheme(); return ( - - + - +
- - +
+ ); }; diff --git a/web/src/layout/Header/navbar/DappList.tsx b/web/src/layout/Header/navbar/DappList.tsx index 508d40b1..9a463db5 100644 --- a/web/src/layout/Header/navbar/DappList.tsx +++ b/web/src/layout/Header/navbar/DappList.tsx @@ -1,8 +1,4 @@ import React, { useRef } from "react"; -import styled, { css } from "styled-components"; - -import { landscapeStyle } from "styles/landscapeStyle"; -import { responsiveSize } from "styles/responsiveSize"; import { useClickAway } from "react-use"; @@ -15,61 +11,7 @@ import POH from "svgs/icons/poh-image.png"; import Vea from "svgs/icons/vea.svg"; import Product from "./Product"; - -const Container = styled.div` - display: flex; - position: absolute; - max-height: 340px; - top: 5%; - left: 50%; - transform: translate(-50%); - z-index: 1; - flex-direction: column; - align-items: center; - - width: 86vw; - max-width: 480px; - border-radius: 3px; - border: 1px solid ${({ theme }) => theme.stroke}; - background-color: ${({ theme }) => theme.whiteBackground}; - box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.06); - - svg { - visibility: visible; - } - - ${landscapeStyle( - () => css` - margin-top: 64px; - top: 0; - left: 0; - right: auto; - transform: none; - width: ${responsiveSize(300, 480)}; - max-height: 80vh; - ` - )} -`; - -const Header = styled.h1` - padding-top: 24px; - font-size: 24px; - font-weight: 600; - line-height: 32.68px; -`; - -const ItemsDiv = styled.div` - display: grid; - overflow-y: auto; - padding: 4px ${responsiveSize(8, 24)} 16px ${responsiveSize(8, 24)}; - row-gap: 8px; - column-gap: 2px; - justify-items: center; - max-width: 480px; - min-width: 300px; - width: ${responsiveSize(300, 480)}; - grid-template-columns: repeat(auto-fit, minmax(100px, 1fr)); -`; +import clsx from "clsx"; const ITEMS = [ { @@ -143,14 +85,30 @@ const DappList: React.FC = ({ toggleIsDappListOpen }) => { useClickAway(containerRef, () => toggleIsDappListOpen()); return ( - -
Kleros Solutions
- +
+

Kleros Solutions

+
{ITEMS.map((item) => { return ; })} - - +
+
); }; export default DappList; diff --git a/web/src/layout/Header/navbar/Debug.tsx b/web/src/layout/Header/navbar/Debug.tsx index 1be22814..f2d4db23 100644 --- a/web/src/layout/Header/navbar/Debug.tsx +++ b/web/src/layout/Header/navbar/Debug.tsx @@ -1,22 +1,16 @@ import React from "react"; -import styled from "styled-components"; // import { useSortitionModulePhase } from "hooks/contracts/generated"; import { GIT_BRANCH, GIT_DIRTY, GIT_HASH, GIT_TAGS, GIT_URL, RELEASE_VERSION } from "consts/index"; -const Container = styled.div` - label, - a { - font-family: "Roboto Mono", monospace; - line-height: 10px; - font-size: 10px; - color: ${({ theme }) => theme.stroke}; - } -`; - const Version = () => ( -