diff --git a/web/package.json b/web/package.json deleted file mode 100644 index 1d99e4e9..00000000 --- a/web/package.json +++ /dev/null @@ -1,107 +0,0 @@ -{ - "name": "@kleros/escrow-v2-web", - "version": "2.0.0", - "source": "src/index.html", - "browserslist": "> 0.5%, not dead", - "repository": "", - "author": "", - "license": "MIT", - "type": "module", - "alias": { - "src": "./src", - "utils": "./src/utils", - "assets": "./src/assets", - "components": "./src/components", - "connectors": "./src/connectors", - "context": "./src/context", - "layout": "./src/layout", - "consts": "./src/consts", - "hooks": "./src/hooks", - "queries": "./src/hooks/queries", - "pages": "./src/pages", - "styles": "./src/styles", - "svgs": "./src/assets/svgs" - }, - "volta": { - "node": "20.18.3" - }, - "scripts": { - "start": "yarn start-devnet", - "start-local": "scripts/runEnv.sh local 'yarn generate && vite'", - "start-devnet": "scripts/runEnv.sh devnet 'yarn generate && vite'", - "start-testnet": "scripts/runEnv.sh testnet 'yarn generate && vite'", - "start-mainnet": "scripts/runEnv.sh mainnet 'yarn generate && vite'", - "build": "yarn build-devnet", - "build-local": "scripts/runEnv.sh local 'yarn generate && vite build'", - "build-devnet": "scripts/runEnv.sh devnet 'yarn generate && vite build'", - "build-testnet": "scripts/runEnv.sh testnet 'yarn generate && vite build'", - "build-mainnet": "scripts/runEnv.sh mainnet 'yarn generate && vite build'", - "build-netlify": "scripts/generateBuildInfo.sh && yarn generate && vite build", - "check-style": "eslint 'src/**/*.{js,jsx,ts,tsx}'", - "check-types": "tsc --noEmit", - "generate": "yarn generate:gql && yarn generate:hooks", - "generate:gql": "graphql-codegen --require tsconfig-paths/register", - "generate:hooks": "NODE_NO_WARNINGS=1 wagmi generate" - }, - "prettier": "@kleros/escrow-v2-prettier-config", - "devDependencies": { - "@graphql-codegen/cli": "^4.0.1", - "@graphql-codegen/client-preset": "^4.6.2", - "@kleros/kleros-v2-contracts": "^0.10.0", - "@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", - "@wagmi/cli": "^2.2.1", - "eslint": "^8.56.0", - "eslint-config-prettier": "^8.10.0", - "eslint-plugin-react": "^7.33.2", - "eslint-plugin-react-hooks": "^4.6.0", - "lru-cache": "^7.18.3", - "typescript": "^5.7.3", - "vite": "^5.4.2", - "vite-plugin-node-polyfills": "^0.21.0", - "vite-plugin-svgr": "^4.2.0", - "vite-tsconfig-paths": "^4.3.2" - }, - "dependencies": { - "@cyntler/react-doc-viewer": "^1.16.3", - "@kleros/kleros-app": "^2.1.0", - "@kleros/ui-components-library": "^2.19.0", - "@reown/appkit": "^1.6.6", - "@reown/appkit-adapter-wagmi": "^1.6.6", - "@sentry/react": "^7.93.0", - "@sentry/tracing": "^7.93.0", - "@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", - "core-js": "^3.35.0", - "graphql": "^16.9.0", - "graphql-request": "^7.1.2", - "moment": "^2.30.1", - "overlayscrollbars": "^2.4.6", - "overlayscrollbars-react": "^0.5.3", - "react": "^18.3.1", - "react-chartjs-2": "^4.3.1", - "react-dom": "^18.3.1", - "react-error-boundary": "^3.1.4", - "react-identicons": "^1.2.5", - "react-is": "^18.2.0", - "react-loading-skeleton": "^3.3.1", - "react-markdown": "^8.0.7", - "react-modal": "^3.16.1", - "react-router-dom": "^6.21.2", - "react-scripts": "^5.0.1", - "react-toastify": "^9.1.3", - "react-use": "^17.4.3", - "styled-components": "^5.3.11", - "subgraph-status": "^1.2.4", - "viem": "^2.27.2", - "wagmi": "^2.14.16" - } -} diff --git a/web/src/layout/Header/navbar/Explore.tsx b/web/src/layout/Header/navbar/Explore.tsx index e216591f..7c14cf37 100644 --- a/web/src/layout/Header/navbar/Explore.tsx +++ b/web/src/layout/Header/navbar/Explore.tsx @@ -5,6 +5,7 @@ import { landscapeStyle } from "styles/landscapeStyle"; import { Link, useLocation } from "react-router-dom"; import { useOpenContext } from "../MobileHeader"; +import Policies from "./Policies"; const Container = styled.div` display: flex; @@ -76,6 +77,7 @@ const Explore: React.FC = ({ isMobileNavbar }) => { {text} ))} + ); }; diff --git a/web/src/layout/Header/navbar/Policies.tsx b/web/src/layout/Header/navbar/Policies.tsx new file mode 100644 index 00000000..57f4cac4 --- /dev/null +++ b/web/src/layout/Header/navbar/Policies.tsx @@ -0,0 +1,262 @@ +import React, { useState, useRef, useEffect } from "react"; +import styled, { css } from "styled-components"; +import { landscapeStyle } from "styles/landscapeStyle"; +import { hoverShortTransitionTiming } from "styles/commonStyles"; + +import { useOpenContext } from "../MobileHeader"; + +const Container = styled.div` + position: relative; + display: flex; + flex-direction: column; + + ${landscapeStyle( + () => css` + flex-direction: row; + ` + )}; +`; + +const Title = styled.h1` + display: block; + margin-bottom: 8px; + + ${landscapeStyle( + () => css` + display: none; + ` + )}; +`; + +const PoliciesButton = styled.button<{ isActive: boolean; isMobileNavbar?: boolean }>` + ${hoverShortTransitionTiming} + display: flex; + align-items: center; + gap: 8px; + background: none; + border: none; + cursor: pointer; + font-size: 16px; + color: ${({ isActive, theme }) => (isActive ? theme.primaryText : `${theme.primaryText}BA`)}; + font-weight: ${({ isActive, isMobileNavbar }) => (isMobileNavbar && isActive ? "600" : "normal")}; + padding: 8px 8px 8px 0; + border-radius: 7px; + + &:hover { + color: ${({ theme, isMobileNavbar }) => (isMobileNavbar ? theme.primaryText : theme.white)} !important; + } + + ${landscapeStyle( + () => css` + padding: 16px 8px; + ` + )} + + @media (min-width: 900px) { + color: ${({ isActive, theme }) => (isActive ? theme.white : `${theme.white}BA`)}; + } +`; + +const ChevronIcon = styled.span<{ isOpen: boolean }>` + display: inline-block; + width: 6px; + height: 6px; + border-right: 2px solid currentColor; + border-bottom: 2px solid currentColor; + transform: rotate(45deg); + transition: transform 0.2s ease; + margin-top: -4px; +`; + +const DropdownContainer = styled.div<{ isOpen: boolean }>` + position: absolute; + top: 100%; + left: 0; + background: ${({ theme }) => theme.whiteBackground}; + border: 1px solid ${({ theme }) => theme.stroke}; + border-radius: 3px; + box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.15); + width: 247px; + z-index: 1000; + opacity: ${({ isOpen }) => (isOpen ? 1 : 0)}; + visibility: ${({ isOpen }) => (isOpen ? "visible" : "hidden")}; + transform: translateY(${({ isOpen }) => (isOpen ? "0" : "-10px")}); + transition: all 0.2s ease; + + ${landscapeStyle( + () => css` + top: calc(100% + 8px); + left: 50%; + transform: translateX(-50%) translateY(0); + ` + )}; +`; + +const DropdownItem = styled.a` + display: flex; + align-items: center; + gap: 12px; + padding: 12px 16px; + text-decoration: none; + color: ${({ theme }) => theme.primaryText}; + background: transparent; + border-left: 3px solid transparent; + height: 45px; + transition: all 0.2s ease; + + &:hover { + background: ${({ theme }) => theme.mediumBlue}; + border-left-color: ${({ theme }) => theme.primaryBlue}; + } + + &:first-child { + border-top-left-radius: 3px; + border-top-right-radius: 3px; + } + + &:last-child { + border-bottom-left-radius: 3px; + border-bottom-right-radius: 3px; + } +`; + +const ItemIcon = styled.div` + width: 20px; + height: 20px; + display: flex; + align-items: center; + justify-content: center; + color: ${({ theme }) => theme.primaryBlue}; +`; + +const CheckIcon = styled.div` + width: 18px; + height: 18px; + border: 2px solid currentColor; + border-radius: 50%; + position: relative; + + &::after { + content: ""; + position: absolute; + top: 2px; + left: 5px; + width: 5px; + height: 8px; + border: solid currentColor; + border-width: 0 2px 2px 0; + transform: rotate(45deg); + } +`; + +const DocumentIcon = styled.div` + width: 18px; + height: 22px; + border: 2px solid currentColor; + border-radius: 2px; + position: relative; + + &::after { + content: ""; + position: absolute; + top: 3px; + left: 3px; + right: 3px; + height: 2px; + background: currentColor; + border-radius: 1px; + } + + &::before { + content: ""; + position: absolute; + top: 8px; + left: 3px; + right: 3px; + height: 1px; + background: currentColor; + border-radius: 0.5px; + } +`; + +interface IPolicies { + isMobileNavbar?: boolean; +} + +const Policies: React.FC = ({ isMobileNavbar }) => { + const [isOpen, setIsOpen] = useState(false); + const dropdownRef = useRef(null); + const { toggleIsOpen } = useOpenContext(); + + const policies = [ + { + id: "general-policy", + name: "General Policy", + url: "https://cdn.kleros.link/ipfs/QmU2GuwcSs8tFp8gWf5hcXVbcJKRqwoecNnERz9XjKr18d", + icon: CheckIcon, + }, + { + id: "good-practices", + name: "Good Practices", + url: "https://cdn.kleros.link/ipfs/QmcCyR68RmwWfdVKinY8Fmiy73a6xEzGqYDcvAh9EFUnLF", + icon: DocumentIcon, + }, + ]; + + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) { + setIsOpen(false); + } + }; + + document.addEventListener("mousedown", handleClickOutside); + return () => document.removeEventListener("mousedown", handleClickOutside); + }, []); + + const handleItemClick = () => { + setIsOpen(false); + if (isMobileNavbar) { + toggleIsOpen(); + } + }; + + return ( + + Policies + setIsOpen(!isOpen)} + isActive={isOpen} + isMobileNavbar={isMobileNavbar} + aria-haspopup="menu" + aria-expanded={isOpen} + > + Policies + + + + + {policies.map((policy) => { + const IconComponent = policy.icon; + return ( + + + + + {policy.name} + + ); + })} + + + ); +}; + +export default Policies;