Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add navbar settings and notifications pop-ups #824

Merged
merged 12 commits into from
Jun 7, 2023
1 change: 1 addition & 0 deletions web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
25 changes: 15 additions & 10 deletions web/src/components/ConnectButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<StyledContainer>
<small>{chainName}</small>
<label>{shortAddress}</label>
<ChainDisplay />
<AddressDisplay />
</StyledContainer>
);
};

export const ChainDisplay: React.FC = () => {
const { chainId } = useWeb3();
const chainName = chainId ? SUPPORTED_CHAINS[chainId].chainName : undefined;
return <small>{chainName}</small>;
};

export const AddressDisplay: React.FC = () => {
const { account } = useWeb3();
const shortAddress = account ? shortenAddress(account) : undefined;
return <label>{shortAddress}</label>;
};

const StyledContainer = styled.div`
width: fit-content;
height: 34px;
Expand All @@ -42,11 +51,7 @@ const StyledContainer = styled.div`
const ConnectButton: React.FC = () => {
const { active } = useWeb3();
const { activate, connecting } = useConnect();
return active ? (
<AccountDisplay />
) : (
<Button disabled={connecting} small text={"Connect"} onClick={activate} />
);
return active ? <AccountDisplay /> : <Button disabled={connecting} small text={"Connect"} onClick={activate} />;
};

export default ConnectButton;
8 changes: 2 additions & 6 deletions web/src/layout/Header/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -61,11 +61,7 @@ const Header: React.FC = () => {
</Link>
<div ref={containerRef}>
<NavBar />
<StyledLightButton
text=""
Icon={HamburgerIcon}
onClick={toggleIsOpen}
/>
<StyledLightButton text="" Icon={HamburgerIcon} onClick={toggleIsOpen} />
</div>
</OpenContext.Provider>
</Container>
Expand Down
75 changes: 75 additions & 0 deletions web/src/layout/Header/navbar/Menu/Settings/General.tsx
Original file line number Diff line number Diff line change
@@ -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 ? (
<Container>
<StyledChainContainer>
<ChainDisplay />
</StyledChainContainer>
{account && (
<StyledIdenticon>
<Identicon size="24" string={account} />
</StyledIdenticon>
)}
<StyledAddressContainer>
<AddressDisplay />
</StyledAddressContainer>
</Container>
) : (
<StyledConnectButtonContainer>
<ConnectButton />
</StyledConnectButtonContainer>
);
};

export default General;
Original file line number Diff line number Diff line change
@@ -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<SetStateAction<string>>;
setEmailIsValid: Dispatch<SetStateAction<boolean>>;
}

const FormEmail: React.FC<IFormEmail> = ({ emailInput, setEmailInput, setEmailIsValid, emailIsValid }) => {
useEffect(() => {
setEmailIsValid(/^[\w-.]+@([\w-]+\.)+[\w-]{2,4}$/.test(emailInput));
}, [emailInput]);

const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
event.preventDefault();
setEmailInput(event.target.value);
};

const fieldVariant = useMemo(() => {
if (emailInput === "") {
return undefined;
}
return emailIsValid ? "success" : "error";
}, [emailInput, emailIsValid]);

return (
<StyledField
variant={fieldVariant}
value={emailInput}
onChange={handleInputChange}
placeholder="[email protected]"
/>
);
};

export default FormEmail;
Original file line number Diff line number Diff line change
@@ -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<boolean[]>(new Array(OPTIONS.length));
const [emailInput, setEmailInput] = useState<string>("");
const [emailIsValid, setEmailIsValid] = useState<boolean>(false);

const handleCheckboxChange = (index: number) => (e: React.ChangeEvent<HTMLInputElement>) => {
const newCheckboxStates = [...checkboxStates];
newCheckboxStates[index] = e.target.checked;
setCheckboxStates(newCheckboxStates);
};

return (
<FormContainer>
{OPTIONS.map(({ label }, index) => (
<StyledCheckbox
key={label}
onChange={handleCheckboxChange(index)}
checked={checkboxStates[index]}
small={true}
label={label}
/>
))}
<FormEmailContainer>
<FormEmail
emailInput={emailInput}
emailIsValid={emailIsValid}
setEmailInput={setEmailInput}
setEmailIsValid={setEmailIsValid}
/>
</FormEmailContainer>

<ButtonContainer>
<Button text="Save" disabled={!emailIsValid} />
</ButtonContainer>
</FormContainer>
);
};

export default FormNotifs;
Original file line number Diff line number Diff line change
@@ -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 <HeaderContainer>Send Me Notifications</HeaderContainer>;
};

const SendMeNotifications: React.FC = () => {
return (
<Container>
<HeaderNotifs />
<FormNotifs />
</Container>
);
};

export default SendMeNotifications;
83 changes: 83 additions & 0 deletions web/src/layout/Header/navbar/Menu/Settings/index.tsx
Original file line number Diff line number Diff line change
@@ -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<SetStateAction<boolean>>;
}

const Settings: React.FC<ISettings> = ({ setIsSettingsOpen }) => {
const [currentTab, setCurrentTab] = useState<number>(0);
const containerRef = useRef(null);
useFocusOutside(containerRef, () => setIsSettingsOpen(false));

return (
<>
<Overlay />
<Container ref={containerRef}>
<StyledSettingsText>Settings</StyledSettingsText>
<StyledTabs
currentValue={currentTab}
items={TABS}
callback={(n: number) => {
setCurrentTab(n);
}}
/>
{currentTab === 0 ? <General /> : <SendMeNotifications />}
</Container>
</>
);
};

export default Settings;
Loading