Create profile errors (#174)
* Change clear buttons * Add name creating errors * Disable Next button on error * Add useNameError hook
This commit is contained in:
parent
e03b020397
commit
bf2e1b0ee2
|
@ -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;
|
||||||
|
`;
|
|
@ -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};
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
|
@ -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} />;
|
||||||
};
|
};
|
||||||
|
|
|
@ -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};
|
||||||
|
`;
|
|
@ -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;
|
||||||
|
|
|
@ -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 && (
|
||||||
<NameInput
|
<NameInputWrapper>
|
||||||
placeholder="Display name"
|
<NameInput
|
||||||
value={customNameInput}
|
placeholder="Display name"
|
||||||
onChange={(e) => setCustomNameInput(e.currentTarget.value)}
|
value={customNameInput}
|
||||||
/>
|
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>
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
Loading…
Reference in New Issue