Skip to content

Commit 0919bd1

Browse files
authored
Merge pull request #824 from kleros/feat(web)/add-navbar-settings
feat: add navbar settings and notifications pop-ups
2 parents b5b4427 + 1369e54 commit 0919bd1

File tree

12 files changed

+357
-19
lines changed

12 files changed

+357
-19
lines changed

web/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@
7272
"react-chartjs-2": "^4.3.1",
7373
"react-dom": "^18.2.0",
7474
"react-error-boundary": "^3.1.4",
75+
"react-identicons": "^1.2.5",
7576
"react-is": "^18.2.0",
7677
"react-jazzicon": "^1.0.4",
7778
"react-loading-skeleton": "^3.2.0",

web/src/components/ConnectButton.tsx

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,26 @@ import { useConnect } from "hooks/useConnect";
77
import { SUPPORTED_CHAINS } from "consts/chains";
88

99
const AccountDisplay: React.FC = () => {
10-
const { account, chainId } = useWeb3();
11-
const chainName = chainId ? SUPPORTED_CHAINS[chainId].chainName : undefined;
12-
const shortAddress = account ? shortenAddress(account) : undefined;
1310
return (
1411
<StyledContainer>
15-
<small>{chainName}</small>
16-
<label>{shortAddress}</label>
12+
<ChainDisplay />
13+
<AddressDisplay />
1714
</StyledContainer>
1815
);
1916
};
2017

18+
export const ChainDisplay: React.FC = () => {
19+
const { chainId } = useWeb3();
20+
const chainName = chainId ? SUPPORTED_CHAINS[chainId].chainName : undefined;
21+
return <small>{chainName}</small>;
22+
};
23+
24+
export const AddressDisplay: React.FC = () => {
25+
const { account } = useWeb3();
26+
const shortAddress = account ? shortenAddress(account) : undefined;
27+
return <label>{shortAddress}</label>;
28+
};
29+
2130
const StyledContainer = styled.div`
2231
width: fit-content;
2332
height: 34px;
@@ -42,11 +51,7 @@ const StyledContainer = styled.div`
4251
const ConnectButton: React.FC = () => {
4352
const { active } = useWeb3();
4453
const { activate, connecting } = useConnect();
45-
return active ? (
46-
<AccountDisplay />
47-
) : (
48-
<Button disabled={connecting} small text={"Connect"} onClick={activate} />
49-
);
54+
return active ? <AccountDisplay /> : <Button disabled={connecting} small text={"Connect"} onClick={activate} />;
5055
};
5156

5257
export default ConnectButton;

web/src/layout/Header/index.tsx

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ import styled from "styled-components";
33
import { Link } from "react-router-dom";
44
import HamburgerIcon from "svgs/header/hamburger.svg";
55
import KlerosCourtLogo from "svgs/header/kleros-court.svg";
6-
import { useFocusOutside } from "hooks/useFocusOutside";
76
import LightButton from "components/LightButton";
87
import NavBar from "./navbar";
8+
import { useFocusOutside } from "hooks/useFocusOutside";
99

1010
const Container = styled.div`
1111
position: sticky;
@@ -61,11 +61,7 @@ const Header: React.FC = () => {
6161
</Link>
6262
<div ref={containerRef}>
6363
<NavBar />
64-
<StyledLightButton
65-
text=""
66-
Icon={HamburgerIcon}
67-
onClick={toggleIsOpen}
68-
/>
64+
<StyledLightButton text="" Icon={HamburgerIcon} onClick={toggleIsOpen} />
6965
</div>
7066
</OpenContext.Provider>
7167
</Container>
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import React from "react";
2+
import styled from "styled-components";
3+
import Identicon from "react-identicons";
4+
import ConnectButton, { AddressDisplay, ChainDisplay } from "components/ConnectButton";
5+
import { useWeb3 } from "hooks/useWeb3";
6+
7+
const Container = styled.div`
8+
display: flex;
9+
flex-direction: column;
10+
justify-content: center;
11+
margin-top: 32px;
12+
`;
13+
14+
const StyledChainContainer = styled.div`
15+
display: flex;
16+
height: 34px;
17+
gap: 0.5rem;
18+
justify-content: center;
19+
align-items: center;
20+
:before {
21+
content: "";
22+
width: 8px;
23+
height: 8px;
24+
border-radius: 50%;
25+
background-color: ${({ theme }) => theme.success};
26+
}
27+
> small {
28+
color: ${({ theme }) => theme.success};
29+
}
30+
`;
31+
32+
const StyledAddressContainer = styled.div`
33+
display: flex;
34+
justify-content: center;
35+
margin-top: 12px;
36+
padding-bottom: 32px;
37+
`;
38+
39+
const StyledIdenticon = styled.div`
40+
display: flex;
41+
justify-content: center;
42+
margin-top: 32px;
43+
`;
44+
45+
const StyledConnectButtonContainer = styled.div`
46+
display: flex;
47+
justify-content: center;
48+
padding: 16px;
49+
`;
50+
51+
const General: React.FC = () => {
52+
const { account } = useWeb3();
53+
54+
return account ? (
55+
<Container>
56+
<StyledChainContainer>
57+
<ChainDisplay />
58+
</StyledChainContainer>
59+
{account && (
60+
<StyledIdenticon>
61+
<Identicon size="24" string={account} />
62+
</StyledIdenticon>
63+
)}
64+
<StyledAddressContainer>
65+
<AddressDisplay />
66+
</StyledAddressContainer>
67+
</Container>
68+
) : (
69+
<StyledConnectButtonContainer>
70+
<ConnectButton />
71+
</StyledConnectButtonContainer>
72+
);
73+
};
74+
75+
export default General;
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import React, { Dispatch, SetStateAction, useEffect, useMemo } from "react";
2+
import styled from "styled-components";
3+
import { Field } from "@kleros/ui-components-library";
4+
5+
const StyledField = styled(Field)`
6+
display: flex;
7+
width: 100%;
8+
margin-top: 34px;
9+
`;
10+
11+
interface IFormEmail {
12+
emailInput: string;
13+
emailIsValid: boolean;
14+
setEmailInput: Dispatch<SetStateAction<string>>;
15+
setEmailIsValid: Dispatch<SetStateAction<boolean>>;
16+
}
17+
18+
const FormEmail: React.FC<IFormEmail> = ({ emailInput, setEmailInput, setEmailIsValid, emailIsValid }) => {
19+
useEffect(() => {
20+
setEmailIsValid(/^[\w-.]+@([\w-]+\.)+[\w-]{2,4}$/.test(emailInput));
21+
}, [emailInput]);
22+
23+
const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
24+
event.preventDefault();
25+
setEmailInput(event.target.value);
26+
};
27+
28+
const fieldVariant = useMemo(() => {
29+
if (emailInput === "") {
30+
return undefined;
31+
}
32+
return emailIsValid ? "success" : "error";
33+
}, [emailInput, emailIsValid]);
34+
35+
return (
36+
<StyledField
37+
variant={fieldVariant}
38+
value={emailInput}
39+
onChange={handleInputChange}
40+
placeholder="[email protected]"
41+
/>
42+
);
43+
};
44+
45+
export default FormEmail;
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import React, { useState } from "react";
2+
import styled from "styled-components";
3+
import { Checkbox, Button } from "@kleros/ui-components-library";
4+
import FormEmail from "./FormEmail";
5+
6+
const FormContainer = styled.div`
7+
position: relative;
8+
display: flex;
9+
flex-direction: column;
10+
padding: 0 calc(12px + (32 - 12) * ((100vw - 300px) / (1250 - 300)));
11+
padding-bottom: 32px;
12+
`;
13+
14+
const StyledCheckbox = styled(Checkbox)`
15+
margin-top: 20px;
16+
`;
17+
18+
const ButtonContainer = styled.div`
19+
display: flex;
20+
justify-content: end;
21+
margin-top: 16px;
22+
`;
23+
24+
const FormEmailContainer = styled.div`
25+
position: relative;
26+
`;
27+
28+
const EmailErrorContainer = styled.div`
29+
position: absolute;
30+
color: ${({ theme }) => theme.error};
31+
font-size: 12px;
32+
padding-top: 4px;
33+
padding-left: 16px;
34+
`;
35+
36+
const OPTIONS = [{ label: "When x." }, { label: "When y." }, { label: "When z." }, { label: "When w." }];
37+
38+
const FormNotifs: React.FC = () => {
39+
const [checkboxStates, setCheckboxStates] = useState<boolean[]>(new Array(OPTIONS.length));
40+
const [emailInput, setEmailInput] = useState<string>("");
41+
const [emailIsValid, setEmailIsValid] = useState<boolean>(false);
42+
43+
const handleCheckboxChange = (index: number) => (e: React.ChangeEvent<HTMLInputElement>) => {
44+
const newCheckboxStates = [...checkboxStates];
45+
newCheckboxStates[index] = e.target.checked;
46+
setCheckboxStates(newCheckboxStates);
47+
};
48+
49+
return (
50+
<FormContainer>
51+
{OPTIONS.map(({ label }, index) => (
52+
<StyledCheckbox
53+
key={label}
54+
onChange={handleCheckboxChange(index)}
55+
checked={checkboxStates[index]}
56+
small={true}
57+
label={label}
58+
/>
59+
))}
60+
<FormEmailContainer>
61+
<FormEmail
62+
emailInput={emailInput}
63+
emailIsValid={emailIsValid}
64+
setEmailInput={setEmailInput}
65+
setEmailIsValid={setEmailIsValid}
66+
/>
67+
</FormEmailContainer>
68+
69+
<ButtonContainer>
70+
<Button text="Save" disabled={!emailIsValid} />
71+
</ButtonContainer>
72+
</FormContainer>
73+
);
74+
};
75+
76+
export default FormNotifs;
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import React from "react";
2+
import styled from "styled-components";
3+
import FormNotifs from "./FormNotifs";
4+
5+
const Container = styled.div`
6+
display: flex;
7+
flex-direction: column;
8+
`;
9+
10+
const HeaderContainer = styled.div`
11+
display: flex;
12+
justify-content: center;
13+
font-size: 16px;
14+
font-weight: 600;
15+
color: ${({ theme }) => theme.primaryText};
16+
margin-top: 32px;
17+
margin-bottom: 12px;
18+
`;
19+
20+
const HeaderNotifs: React.FC = () => {
21+
return <HeaderContainer>Send Me Notifications</HeaderContainer>;
22+
};
23+
24+
const SendMeNotifications: React.FC = () => {
25+
return (
26+
<Container>
27+
<HeaderNotifs />
28+
<FormNotifs />
29+
</Container>
30+
);
31+
};
32+
33+
export default SendMeNotifications;
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import React, { Dispatch, SetStateAction, useRef, useState } from "react";
2+
import styled from "styled-components";
3+
import { Tabs } from "@kleros/ui-components-library";
4+
import General from "./General";
5+
import SendMeNotifications from "./SendMeNotifications";
6+
import { useFocusOutside } from "hooks/useFocusOutside";
7+
8+
const Container = styled.div`
9+
display: flex;
10+
position: absolute;
11+
z-index: 1;
12+
background-color: ${({ theme }) => theme.whiteBackground};
13+
flex-direction: column;
14+
border: 1px solid ${({ theme }) => theme.stroke};
15+
border-radius: 3px;
16+
overflow-y: auto;
17+
top: 5%;
18+
left: 50%;
19+
transform: translateX(-50%);
20+
`;
21+
22+
const StyledSettingsText = styled.div`
23+
display: flex;
24+
justify-content: center;
25+
font-size: 24px;
26+
color: ${({ theme }) => theme.primaryText};
27+
margin-top: 24px;
28+
`;
29+
30+
const StyledTabs = styled(Tabs)`
31+
padding: 0 calc(8px + (32 - 8) * ((100vw - 300px) / (1250 - 300)));
32+
width: calc(300px + (424 - 300) * ((100vw - 300px) / (1250 - 300)));
33+
`;
34+
35+
const Overlay = styled.div`
36+
position: fixed;
37+
top: 0;
38+
left: 0;
39+
width: 100vw;
40+
height: 100vh;
41+
background-color: ${({ theme }) => theme.blackLowOpacity};
42+
z-index: 1;
43+
`;
44+
45+
const TABS = [
46+
{
47+
text: "General",
48+
value: 0,
49+
},
50+
{
51+
text: "Notifications",
52+
value: 1,
53+
},
54+
];
55+
56+
interface ISettings {
57+
setIsSettingsOpen: Dispatch<SetStateAction<boolean>>;
58+
}
59+
60+
const Settings: React.FC<ISettings> = ({ setIsSettingsOpen }) => {
61+
const [currentTab, setCurrentTab] = useState<number>(0);
62+
const containerRef = useRef(null);
63+
useFocusOutside(containerRef, () => setIsSettingsOpen(false));
64+
65+
return (
66+
<>
67+
<Overlay />
68+
<Container ref={containerRef}>
69+
<StyledSettingsText>Settings</StyledSettingsText>
70+
<StyledTabs
71+
currentValue={currentTab}
72+
items={TABS}
73+
callback={(n: number) => {
74+
setCurrentTab(n);
75+
}}
76+
/>
77+
{currentTab === 0 ? <General /> : <SendMeNotifications />}
78+
</Container>
79+
</>
80+
);
81+
};
82+
83+
export default Settings;

0 commit comments

Comments
 (0)