diff --git a/web/package.json b/web/package.json
index 3f62f8d40..401252a84 100644
--- a/web/package.json
+++ b/web/package.json
@@ -72,6 +72,7 @@
"react-chartjs-2": "^4.3.1",
"react-dom": "^18.2.0",
"react-error-boundary": "^3.1.4",
+ "react-identicons": "^1.2.5",
"react-is": "^18.2.0",
"react-jazzicon": "^1.0.4",
"react-loading-skeleton": "^3.2.0",
diff --git a/web/src/components/ConnectButton.tsx b/web/src/components/ConnectButton.tsx
index 86c9a765b..7ef37da99 100644
--- a/web/src/components/ConnectButton.tsx
+++ b/web/src/components/ConnectButton.tsx
@@ -7,17 +7,26 @@ import { useConnect } from "hooks/useConnect";
import { SUPPORTED_CHAINS } from "consts/chains";
const AccountDisplay: React.FC = () => {
- const { account, chainId } = useWeb3();
- const chainName = chainId ? SUPPORTED_CHAINS[chainId].chainName : undefined;
- const shortAddress = account ? shortenAddress(account) : undefined;
return (
- {chainName}
-
+
+
);
};
+export const ChainDisplay: React.FC = () => {
+ const { chainId } = useWeb3();
+ const chainName = chainId ? SUPPORTED_CHAINS[chainId].chainName : undefined;
+ return {chainName};
+};
+
+export const AddressDisplay: React.FC = () => {
+ const { account } = useWeb3();
+ const shortAddress = account ? shortenAddress(account) : undefined;
+ return ;
+};
+
const StyledContainer = styled.div`
width: fit-content;
height: 34px;
@@ -42,11 +51,7 @@ const StyledContainer = styled.div`
const ConnectButton: React.FC = () => {
const { active } = useWeb3();
const { activate, connecting } = useConnect();
- return active ? (
-
- ) : (
-
- );
+ return active ? : ;
};
export default ConnectButton;
diff --git a/web/src/layout/Header/index.tsx b/web/src/layout/Header/index.tsx
index b82f299a4..7a680ed6d 100644
--- a/web/src/layout/Header/index.tsx
+++ b/web/src/layout/Header/index.tsx
@@ -3,9 +3,9 @@ import styled from "styled-components";
import { Link } from "react-router-dom";
import HamburgerIcon from "svgs/header/hamburger.svg";
import KlerosCourtLogo from "svgs/header/kleros-court.svg";
-import { useFocusOutside } from "hooks/useFocusOutside";
import LightButton from "components/LightButton";
import NavBar from "./navbar";
+import { useFocusOutside } from "hooks/useFocusOutside";
const Container = styled.div`
position: sticky;
@@ -61,11 +61,7 @@ const Header: React.FC = () => {
-
+
diff --git a/web/src/layout/Header/navbar/Menu/Settings/General.tsx b/web/src/layout/Header/navbar/Menu/Settings/General.tsx
new file mode 100644
index 000000000..76d6cf2ac
--- /dev/null
+++ b/web/src/layout/Header/navbar/Menu/Settings/General.tsx
@@ -0,0 +1,75 @@
+import React from "react";
+import styled from "styled-components";
+import Identicon from "react-identicons";
+import ConnectButton, { AddressDisplay, ChainDisplay } from "components/ConnectButton";
+import { useWeb3 } from "hooks/useWeb3";
+
+const Container = styled.div`
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ margin-top: 32px;
+`;
+
+const StyledChainContainer = styled.div`
+ display: flex;
+ height: 34px;
+ gap: 0.5rem;
+ justify-content: center;
+ align-items: center;
+ :before {
+ content: "";
+ width: 8px;
+ height: 8px;
+ border-radius: 50%;
+ background-color: ${({ theme }) => theme.success};
+ }
+ > small {
+ color: ${({ theme }) => theme.success};
+ }
+`;
+
+const StyledAddressContainer = styled.div`
+ display: flex;
+ justify-content: center;
+ margin-top: 12px;
+ padding-bottom: 32px;
+`;
+
+const StyledIdenticon = styled.div`
+ display: flex;
+ justify-content: center;
+ margin-top: 32px;
+`;
+
+const StyledConnectButtonContainer = styled.div`
+ display: flex;
+ justify-content: center;
+ padding: 16px;
+`;
+
+const General: React.FC = () => {
+ const { account } = useWeb3();
+
+ return account ? (
+
+
+
+
+ {account && (
+
+
+
+ )}
+
+
+
+
+ ) : (
+
+
+
+ );
+};
+
+export default General;
diff --git a/web/src/layout/Header/navbar/Menu/Settings/SendMeNotifications/FormNotifs/FormEmail.tsx b/web/src/layout/Header/navbar/Menu/Settings/SendMeNotifications/FormNotifs/FormEmail.tsx
new file mode 100644
index 000000000..645d41332
--- /dev/null
+++ b/web/src/layout/Header/navbar/Menu/Settings/SendMeNotifications/FormNotifs/FormEmail.tsx
@@ -0,0 +1,45 @@
+import React, { Dispatch, SetStateAction, useEffect, useMemo } from "react";
+import styled from "styled-components";
+import { Field } from "@kleros/ui-components-library";
+
+const StyledField = styled(Field)`
+ display: flex;
+ width: 100%;
+ margin-top: 34px;
+`;
+
+interface IFormEmail {
+ emailInput: string;
+ emailIsValid: boolean;
+ setEmailInput: Dispatch>;
+ setEmailIsValid: Dispatch>;
+}
+
+const FormEmail: React.FC = ({ emailInput, setEmailInput, setEmailIsValid, emailIsValid }) => {
+ useEffect(() => {
+ setEmailIsValid(/^[\w-.]+@([\w-]+\.)+[\w-]{2,4}$/.test(emailInput));
+ }, [emailInput]);
+
+ const handleInputChange = (event: React.ChangeEvent) => {
+ event.preventDefault();
+ setEmailInput(event.target.value);
+ };
+
+ const fieldVariant = useMemo(() => {
+ if (emailInput === "") {
+ return undefined;
+ }
+ return emailIsValid ? "success" : "error";
+ }, [emailInput, emailIsValid]);
+
+ return (
+
+ );
+};
+
+export default FormEmail;
diff --git a/web/src/layout/Header/navbar/Menu/Settings/SendMeNotifications/FormNotifs/index.tsx b/web/src/layout/Header/navbar/Menu/Settings/SendMeNotifications/FormNotifs/index.tsx
new file mode 100644
index 000000000..f0da559bb
--- /dev/null
+++ b/web/src/layout/Header/navbar/Menu/Settings/SendMeNotifications/FormNotifs/index.tsx
@@ -0,0 +1,76 @@
+import React, { useState } from "react";
+import styled from "styled-components";
+import { Checkbox, Button } from "@kleros/ui-components-library";
+import FormEmail from "./FormEmail";
+
+const FormContainer = styled.div`
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ padding: 0 calc(12px + (32 - 12) * ((100vw - 300px) / (1250 - 300)));
+ padding-bottom: 32px;
+`;
+
+const StyledCheckbox = styled(Checkbox)`
+ margin-top: 20px;
+`;
+
+const ButtonContainer = styled.div`
+ display: flex;
+ justify-content: end;
+ margin-top: 16px;
+`;
+
+const FormEmailContainer = styled.div`
+ position: relative;
+`;
+
+const EmailErrorContainer = styled.div`
+ position: absolute;
+ color: ${({ theme }) => theme.error};
+ font-size: 12px;
+ padding-top: 4px;
+ padding-left: 16px;
+`;
+
+const OPTIONS = [{ label: "When x." }, { label: "When y." }, { label: "When z." }, { label: "When w." }];
+
+const FormNotifs: React.FC = () => {
+ const [checkboxStates, setCheckboxStates] = useState(new Array(OPTIONS.length));
+ const [emailInput, setEmailInput] = useState("");
+ const [emailIsValid, setEmailIsValid] = useState(false);
+
+ const handleCheckboxChange = (index: number) => (e: React.ChangeEvent) => {
+ const newCheckboxStates = [...checkboxStates];
+ newCheckboxStates[index] = e.target.checked;
+ setCheckboxStates(newCheckboxStates);
+ };
+
+ return (
+
+ {OPTIONS.map(({ label }, index) => (
+
+ ))}
+
+
+
+
+
+
+
+
+ );
+};
+
+export default FormNotifs;
diff --git a/web/src/layout/Header/navbar/Menu/Settings/SendMeNotifications/index.tsx b/web/src/layout/Header/navbar/Menu/Settings/SendMeNotifications/index.tsx
new file mode 100644
index 000000000..63cf235a8
--- /dev/null
+++ b/web/src/layout/Header/navbar/Menu/Settings/SendMeNotifications/index.tsx
@@ -0,0 +1,33 @@
+import React from "react";
+import styled from "styled-components";
+import FormNotifs from "./FormNotifs";
+
+const Container = styled.div`
+ display: flex;
+ flex-direction: column;
+`;
+
+const HeaderContainer = styled.div`
+ display: flex;
+ justify-content: center;
+ font-size: 16px;
+ font-weight: 600;
+ color: ${({ theme }) => theme.primaryText};
+ margin-top: 32px;
+ margin-bottom: 12px;
+`;
+
+const HeaderNotifs: React.FC = () => {
+ return Send Me Notifications;
+};
+
+const SendMeNotifications: React.FC = () => {
+ return (
+
+
+
+
+ );
+};
+
+export default SendMeNotifications;
diff --git a/web/src/layout/Header/navbar/Menu/Settings/index.tsx b/web/src/layout/Header/navbar/Menu/Settings/index.tsx
new file mode 100644
index 000000000..60ce1d8fb
--- /dev/null
+++ b/web/src/layout/Header/navbar/Menu/Settings/index.tsx
@@ -0,0 +1,83 @@
+import React, { Dispatch, SetStateAction, useRef, useState } from "react";
+import styled from "styled-components";
+import { Tabs } from "@kleros/ui-components-library";
+import General from "./General";
+import SendMeNotifications from "./SendMeNotifications";
+import { useFocusOutside } from "hooks/useFocusOutside";
+
+const Container = styled.div`
+ display: flex;
+ position: absolute;
+ z-index: 1;
+ background-color: ${({ theme }) => theme.whiteBackground};
+ flex-direction: column;
+ border: 1px solid ${({ theme }) => theme.stroke};
+ border-radius: 3px;
+ overflow-y: auto;
+ top: 5%;
+ left: 50%;
+ transform: translateX(-50%);
+`;
+
+const StyledSettingsText = styled.div`
+ display: flex;
+ justify-content: center;
+ font-size: 24px;
+ color: ${({ theme }) => theme.primaryText};
+ margin-top: 24px;
+`;
+
+const StyledTabs = styled(Tabs)`
+ padding: 0 calc(8px + (32 - 8) * ((100vw - 300px) / (1250 - 300)));
+ width: calc(300px + (424 - 300) * ((100vw - 300px) / (1250 - 300)));
+`;
+
+const Overlay = styled.div`
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100vw;
+ height: 100vh;
+ background-color: ${({ theme }) => theme.blackLowOpacity};
+ z-index: 1;
+`;
+
+const TABS = [
+ {
+ text: "General",
+ value: 0,
+ },
+ {
+ text: "Notifications",
+ value: 1,
+ },
+];
+
+interface ISettings {
+ setIsSettingsOpen: Dispatch>;
+}
+
+const Settings: React.FC = ({ setIsSettingsOpen }) => {
+ const [currentTab, setCurrentTab] = useState(0);
+ const containerRef = useRef(null);
+ useFocusOutside(containerRef, () => setIsSettingsOpen(false));
+
+ return (
+ <>
+
+
+ Settings
+ {
+ setCurrentTab(n);
+ }}
+ />
+ {currentTab === 0 ? : }
+
+ >
+ );
+};
+
+export default Settings;
diff --git a/web/src/layout/Header/navbar/Menu.tsx b/web/src/layout/Header/navbar/Menu/index.tsx
similarity index 77%
rename from web/src/layout/Header/navbar/Menu.tsx
rename to web/src/layout/Header/navbar/Menu/index.tsx
index 1ce5b1426..e0f28c800 100644
--- a/web/src/layout/Header/navbar/Menu.tsx
+++ b/web/src/layout/Header/navbar/Menu/index.tsx
@@ -1,12 +1,13 @@
-import React from "react";
+import React, { useState } from "react";
import styled from "styled-components";
-import { useToggleTheme } from "hooks/useToggleThemeContext";
import LightButton from "components/LightButton";
import NotificationsIcon from "svgs/menu-icons/notifications.svg";
import DarkModeIcon from "svgs/menu-icons/dark-mode.svg";
import LightModeIcon from "svgs/menu-icons/light-mode.svg";
import HelpIcon from "svgs/menu-icons/help.svg";
import SettingsIcon from "svgs/menu-icons/settings.svg";
+import Settings from "./Settings";
+import { useToggleTheme } from "hooks/useToggleThemeContext";
const Container = styled.div``;
@@ -19,10 +20,16 @@ const ButtonContainer = styled.div`
const Menu: React.FC = () => {
const [theme, toggleTheme] = useToggleTheme();
+ const [isSettingsOpen, setIsSettingsOpen] = useState(false);
+
const isLightTheme = theme === "light";
const buttons = [
{ text: "Notifications", Icon: NotificationsIcon },
- { text: "Settings", Icon: SettingsIcon },
+ {
+ text: "Settings",
+ Icon: SettingsIcon,
+ onClick: () => setIsSettingsOpen(true),
+ },
{ text: "Help", Icon: HelpIcon },
{
text: `${isLightTheme ? "Dark" : "Light"} Mode`,
@@ -36,6 +43,7 @@ const Menu: React.FC = () => {
{buttons.map(({ text, Icon, onClick }) => (
+ {text === "Settings" && isSettingsOpen && }
))}
diff --git a/web/src/layout/Header/navbar/index.tsx b/web/src/layout/Header/navbar/index.tsx
index 942ef3e21..25975a9e8 100644
--- a/web/src/layout/Header/navbar/index.tsx
+++ b/web/src/layout/Header/navbar/index.tsx
@@ -1,5 +1,6 @@
import React from "react";
import styled from "styled-components";
+import { useLockBodyScroll } from "react-use";
import KlerosSolutionsIcon from "svgs/menu-icons/kleros-solutions.svg";
import LightButton from "components/LightButton";
import Explore from "./Explore";
@@ -33,6 +34,8 @@ const Container = styled.div<{ isOpen: boolean }>`
const NavBar: React.FC = () => {
const { isOpen } = useOpenContext();
+ useLockBodyScroll(isOpen);
+
return (
diff --git a/web/src/styles/themes.ts b/web/src/styles/themes.ts
index 602b8a1e7..c463aea09 100644
--- a/web/src/styles/themes.ts
+++ b/web/src/styles/themes.ts
@@ -18,6 +18,7 @@ export const lightTheme = {
defaultShadow: "#00000002",
hoveredShadow: "#00000002",
whiteLowOpacity: "#FFFFFF0F",
+ blackLowOpacity: "#00000080",
success: "#00C42B",
successLight: "#F0FBF2",
@@ -52,6 +53,7 @@ export const darkTheme = {
defaultShadow: "#00000000",
hoveredShadow: "#42498f80",
whiteLowOpacity: "#FFFFFF0F",
+ blackLowOpacity: "#00000080",
success: "#65DC7F",
successLight: "#32355B",
diff --git a/yarn.lock b/yarn.lock
index 17b37c55b..42bee9657 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3844,6 +3844,7 @@ __metadata:
react-chartjs-2: ^4.3.1
react-dom: ^18.2.0
react-error-boundary: ^3.1.4
+ react-identicons: ^1.2.5
react-is: ^18.2.0
react-jazzicon: ^1.0.4
react-loading-skeleton: ^3.2.0
@@ -22104,6 +22105,16 @@ __metadata:
languageName: node
linkType: hard
+"react-identicons@npm:^1.2.5":
+ version: 1.2.5
+ resolution: "react-identicons@npm:1.2.5"
+ peerDependencies:
+ react: ^17.0.1
+ react-dom: ^17.0.1
+ checksum: e2d5f088a334200d5be3b46bb9deb25ee3bc0f915d96a472007bd5c8b847683c50c809ba3ae4f000fcb018fc0094731298f9f337a3c02f658732b6df520ec4c7
+ languageName: node
+ linkType: hard
+
"react-is@npm:^16.12.0, react-is@npm:^16.13.1, react-is@npm:^16.7.0":
version: 16.13.1
resolution: "react-is@npm:16.13.1"