Create profile errors (#174)

* Change clear buttons

* Add name creating errors

* Disable Next button on error

* Add useNameError hook
This commit is contained in:
Maria Rushkova 2022-01-05 16:55:52 +01:00 committed by GitHub
parent e03b020397
commit bf2e1b0ee2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 178 additions and 27 deletions

View File

@ -0,0 +1,44 @@
import React from "react";
import styled from "styled-components";
import { NameErrors } from "../../hooks/useNameError";
import { Hint } from "../Modals/ModalStyle";
type NameErrorProps = {
error: NameErrors;
};
export function NameError({ error }: NameErrorProps) {
switch (error) {
case NameErrors.NoError:
return null;
case NameErrors.NameExists:
return (
<ErrorText>
Sorry, the name you have chosen is not allowed, try picking another
username
</ErrorText>
);
case NameErrors.BadCharacters:
return (
<ErrorText>
Only letters, numbers, underscores and hypens allowed
</ErrorText>
);
case NameErrors.EndingWithEth:
return (
<ErrorText>
Usernames ending with _eth or "-eth" are not allowed
</ErrorText>
);
case NameErrors.TooLong:
return <ErrorText>24 character username limit</ErrorText>;
}
}
const ErrorText = styled(Hint)`
color: ${({ theme }) => theme.redColor};
text-align: center;
width: 328px;
margin: 8px 0;
`;

View File

@ -69,9 +69,25 @@ export const InputBtn = styled.button`
border-radius: 6px; border-radius: 6px;
`; `;
export const NameInputWrapper = styled.div`
position: relative;
`;
export const NameInput = styled.input` export const NameInput = styled.input`
width: 328px; width: 328px;
padding: 11px 16px; padding: 11px 16px;
${inputStyles} ${inputStyles}
`; `;
export const ClearBtn = styled.button`
position: absolute;
right: 16px;
top: 50%;
transform: translateY(-50%);
border-radius: 50%;
& > svg {
fill: ${({ theme }) => theme.secondary};
}
`;

View File

@ -26,6 +26,21 @@ export function ClearSvg({ height, width, className }: ClearSvgProps) {
); );
} }
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M8 15C11.866 15 15 11.866 15 8C15 4.13401 11.866 1 8 1C4.13401 1 1 4.13401 1 8C1 11.866 4.13401 15 8 15ZM11 5C11.2441 5.24408 11.2441 5.63981 11 5.88388L8.88393 8L11 10.1161C11.2441 10.3602 11.2441 10.7559 11 11C10.756 11.2441 10.3602 11.2441 10.1162 11L8.00005 8.88389L5.88393 11C5.63985 11.2441 5.24412 11.2441 5.00005 11C4.75597 10.7559 4.75597 10.3602 5.00005 10.1161L7.11616 8L5.00005 5.88389C4.75597 5.63981 4.75597 5.24408 5.00005 5C5.24412 4.75593 5.63985 4.75593 5.88393 5L8.00005 7.11612L10.1162 5C10.3602 4.75592 10.756 4.75592 11 5Z"
fill="#939BA1"
/>
</svg>;
export const ClearIcon = () => { export const ClearIcon = () => {
return <Icon width={16} height={16} />; return <Icon width={16} height={16} />;
}; };

View File

@ -0,0 +1,31 @@
import React from "react";
import styled from "styled-components";
type ClearSvgFullProps = {
width: number;
height: number;
className?: string;
};
export function ClearSvgFull({ height, width, className }: ClearSvgFullProps) {
return (
<Icon
width={width}
height={height}
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M8 15C11.866 15 15 11.866 15 8C15 4.13401 11.866 1 8 1C4.13401 1 1 4.13401 1 8C1 11.866 4.13401 15 8 15ZM11 5C11.2441 5.24408 11.2441 5.63981 11 5.88388L8.88393 8L11 10.1161C11.2441 10.3602 11.2441 10.7559 11 11C10.756 11.2441 10.3602 11.2441 10.1162 11L8.00005 8.88389L5.88393 11C5.63985 11.2441 5.24412 11.2441 5.00005 11C4.75597 10.7559 4.75597 10.3602 5.00005 10.1161L7.11616 8L5.00005 5.88389C4.75597 5.63981 4.75597 5.24408 5.00005 5C5.24412 4.75593 5.63985 4.75593 5.88393 5L8.00005 7.11612L10.1162 5C10.3602 4.75592 10.756 4.75592 11 5Z"
/>
</Icon>
);
}
const Icon = styled.svg`
fill: ${({ theme }) => theme.secondary};
`;

View File

@ -9,8 +9,13 @@ import { useToasts } from "../../contexts/toastProvider";
import { useManageContact } from "../../hooks/useManageContact"; import { useManageContact } from "../../hooks/useManageContact";
import { copy } from "../../utils"; import { copy } from "../../utils";
import { buttonStyles } from "../Buttons/buttonStyle"; import { buttonStyles } from "../Buttons/buttonStyle";
import { inputStyles, NameInput } from "../Form/inputStyles"; import {
import { ClearSvg } from "../Icons/ClearIcon"; ClearBtn,
inputStyles,
NameInput,
NameInputWrapper,
} from "../Form/inputStyles";
import { ClearSvgFull } from "../Icons/ClearIconFull";
import { CopySvg } from "../Icons/CopyIcon"; import { CopySvg } from "../Icons/CopyIcon";
import { EditSvg } from "../Icons/EditIcon"; import { EditSvg } from "../Icons/EditIcon";
import { LeftIconSvg } from "../Icons/LeftIcon"; import { LeftIconSvg } from "../Icons/LeftIcon";
@ -124,14 +129,14 @@ export const ProfileModal = () => {
value={customNameInput} value={customNameInput}
onChange={(e) => setCustomNameInput(e.currentTarget.value)} onChange={(e) => setCustomNameInput(e.currentTarget.value)}
/> />
{contact.customName && ( {customNameInput && (
<ClearBtn <ClearBtn
onClick={() => { onClick={() => {
setCustomName(undefined); setCustomName(undefined);
setCustomNameInput(""); setCustomNameInput("");
}} }}
> >
<ClearSvg width={16} height={16} className="profile" /> <ClearSvgFull width={16} height={16} />
</ClearBtn> </ClearBtn>
)} )}
</NameInputWrapper> </NameInputWrapper>
@ -401,22 +406,6 @@ const CopyButton = styled.button`
} }
`; `;
const ClearBtn = styled.button`
position: absolute;
right: 16px;
top: 50%;
transform: translateY(-50%);
border-radius: 50%;
& > svg {
fill: ${({ theme }) => theme.secondary};
}
`;
const NameInputWrapper = styled.div`
position: relative;
`;
const RequestSection = styled.div` const RequestSection = styled.div`
width: 100%; width: 100%;
display: flex; display: flex;

View File

@ -9,15 +9,18 @@ import {
useWalletIdentity, useWalletIdentity,
} from "../../contexts/identityProvider"; } from "../../contexts/identityProvider";
import { useModal } from "../../contexts/modalProvider"; import { useModal } from "../../contexts/modalProvider";
import { useNameError } from "../../hooks/useNameError";
import { Contact } from "../../models/Contact"; import { Contact } from "../../models/Contact";
import { import {
decryptIdentity, decryptIdentity,
loadEncryptedIdentity, loadEncryptedIdentity,
saveIdentity, saveIdentity,
} from "../../utils"; } from "../../utils";
import { NameInput } from "../Form/inputStyles"; import { NameError } from "../Form/NameError";
import { ClearBtn, NameInput, NameInputWrapper } from "../Form/inputStyles";
import { AddIcon } from "../Icons/AddIcon"; import { AddIcon } from "../Icons/AddIcon";
import { ChainIcon } from "../Icons/ChainIcon"; import { ChainIcon } from "../Icons/ChainIcon";
import { ClearSvgFull } from "../Icons/ClearIconFull";
import { LeftIconSvg } from "../Icons/LeftIcon"; import { LeftIconSvg } from "../Icons/LeftIcon";
import { UserLogo } from "../Members/UserLogo"; import { UserLogo } from "../Members/UserLogo";
@ -45,8 +48,10 @@ export function UserCreationModal() {
const encryptedIdentity = useMemo(() => loadEncryptedIdentity(), []); const encryptedIdentity = useMemo(() => loadEncryptedIdentity(), []);
const [customNameInput, setCustomNameInput] = useState(""); const [customNameInput, setCustomNameInput] = useState("");
const error = useNameError(customNameInput);
const [nextStep, setNextStep] = useState(false); const [nextStep, setNextStep] = useState(false);
const { setModal } = useModal(UserCreationModalName); const { setModal } = useModal(UserCreationModalName);
return ( return (
<Modal name={UserCreationModalName}> <Modal name={UserCreationModalName}>
<Section> <Section>
@ -89,12 +94,23 @@ export function UserCreationModal() {
)} )}
</LogoWrapper> </LogoWrapper>
{!nextStep && ( {!nextStep && (
<NameInputWrapper>
<NameInput <NameInput
placeholder="Display name" placeholder="Display name"
value={customNameInput} value={customNameInput}
onChange={(e) => setCustomNameInput(e.currentTarget.value)} onChange={(e) => setCustomNameInput(e.currentTarget.value)}
maxLength={24}
/> />
{customNameInput && (
<ClearBtn onClick={() => setCustomNameInput("")}>
<ClearSvgFull width={16} height={16} />
</ClearBtn>
)} )}
</NameInputWrapper>
)}
<NameError error={error} />
{!nextStep && encryptedIdentity && !walletIdentity && ( {!nextStep && encryptedIdentity && !walletIdentity && (
<button <button
onClick={async () => { onClick={async () => {
@ -150,7 +166,7 @@ export function UserCreationModal() {
setNextStep(true); setNextStep(true);
} }
}} }}
disabled={!customNameInput} disabled={!customNameInput || error !== 0}
> >
Next Next
</Btn> </Btn>

View File

@ -0,0 +1,40 @@
import { useMemo } from "react";
import { useMessengerContext } from "../contexts/messengerProvider";
export enum NameErrors {
NoError = 0,
NameExists = 1,
BadCharacters = 2,
EndingWithEth = 3,
TooLong = 4,
}
export function useNameError(name: string) {
const { contacts } = useMessengerContext();
const RegName = new RegExp("^[a-z0-9_-]+$");
const error = useMemo(() => {
if (name === "") {
return NameErrors.NoError;
}
const nameExists = Object.values(contacts).find(
(contact) => contact.trueName === name
);
if (nameExists) {
return NameErrors.NameExists;
}
if (!name.match(RegName)) {
return NameErrors.BadCharacters;
}
if (name.slice(-4) === "_eth" || name.slice(-4) === "-eth") {
return NameErrors.EndingWithEth;
}
if (name.length >= 24) {
return NameErrors.TooLong;
}
return NameErrors.NoError;
}, [name, contacts]);
return error;
}