Skip to content
This repository has been archived by the owner on Apr 25, 2023. It is now read-only.

feat: add password validation #86

Merged
merged 23 commits into from
Oct 18, 2021
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 50 additions & 38 deletions src/components/atoms/TextBox/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { useEffect, useState, useCallback, useRef } from "react";

import Flex from "@reearth/components/atoms/Flex";
import Text from "@reearth/components/atoms/Text";
import { styled, metrics } from "@reearth/theme";
import fonts from "@reearth/theme/fonts";
import { metricsSizes } from "@reearth/theme/metrics";
Expand All @@ -15,6 +16,7 @@ export type Props = {
prefix?: string;
suffix?: string;
placeholder?: string;
message?: string;
throttle?: boolean;
throttleTimeout?: number;
color?: string;
Expand All @@ -34,6 +36,7 @@ const TextBox: React.FC<Props> = ({
prefix,
suffix,
placeholder,
message,
throttle,
throttleTimeout: throttleTimeout = 3000,
color,
Expand Down Expand Up @@ -103,44 +106,47 @@ const TextBox: React.FC<Props> = ({
}, [innerValue, onChange, throttle, throttleTimeout]);

return (
<FormWrapper className={className} align="center">
{prefix && (
<FloatedText floatedTextColor={floatedTextColor} color={color}>
{prefix}
</FloatedText>
)}
{multiline ? (
<StyledTextarea
ref={textAreaRef}
value={innerValue ?? ""}
onChange={handleChangeTextArea}
onBlur={handleBlur}
color={color}
backgroundColor={backgroundColor}
disabled={disabled}
placeholder={placeholder}
rows={rows}
/>
) : (
<StyledInput
value={innerValue ?? ""}
onChange={handleChange}
onKeyPress={handleKeyPress}
onBlur={handleBlur}
color={color}
type={type}
backgroundColor={backgroundColor}
disabled={disabled}
placeholder={placeholder}
borderColor={borderColor}
/>
)}
{suffix && (
<FloatedText floatedTextColor={floatedTextColor} color={color}>
{suffix}
</FloatedText>
)}
</FormWrapper>
<div className={className}>
<FormWrapper align="center">
{prefix && (
<FloatedText floatedTextColor={floatedTextColor} color={color}>
{prefix}
</FloatedText>
)}
{multiline ? (
<StyledTextarea
ref={textAreaRef}
value={innerValue ?? ""}
onChange={handleChangeTextArea}
onBlur={handleBlur}
color={color}
backgroundColor={backgroundColor}
disabled={disabled}
placeholder={placeholder}
rows={rows}
/>
) : (
<StyledInput
value={innerValue ?? ""}
onChange={handleChange}
onKeyPress={handleKeyPress}
onBlur={handleBlur}
color={color}
type={type}
backgroundColor={backgroundColor}
disabled={disabled}
placeholder={placeholder}
borderColor={borderColor}
/>
)}
{suffix && (
<FloatedText floatedTextColor={floatedTextColor} color={color}>
{suffix}
</FloatedText>
)}
</FormWrapper>
{message && <StyledText size="xs">{message}</StyledText>}
</div>
);
};

Expand Down Expand Up @@ -194,4 +200,10 @@ const FloatedText = styled.span<InputProps>`
user-select: none;
`;

const StyledText = styled(Text)`
margin-left: ${metricsSizes.m}px;
margin-top: ${metricsSizes["2xs"]}px;
font-style: italic;
`;

export default TextBox;
192 changes: 131 additions & 61 deletions src/components/molecules/Settings/Account/PasswordModal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, { useState, useCallback, useEffect } from "react";
import { useIntl } from "react-intl";

import Button from "@reearth/components/atoms/Button";
import Flex from "@reearth/components/atoms/Flex";
import Modal from "@reearth/components/atoms/Modal";
import Text from "@reearth/components/atoms/Text";
import TextBox from "@reearth/components/atoms/TextBox";
Expand Down Expand Up @@ -30,32 +31,97 @@ const PasswordModal: React.FC<Props> = ({ isVisible, onClose, hasPassword, updat
const theme = useTheme();

const [password, setPassword] = useState("");
const [regexMessage, setRegexMessage] = useState("");
const [passwordConfirmation, setPasswordConfirmation] = useState("");
const [disabled, setDisabled] = useState(true);

const tooShortRegex = /^(?=.{1,7}$)/;
const tooLongRegex = /^(?=.{25,}$)/;
const whitespaceRegex = /(?=.*\s)/;
const highSecurityRegex = /^(?=.*[a-z])(?=.*[A-Z])((?=(.*\d){2}))/;
const medSecurityRegex = /^((?=.*[a-z])(?=.*[A-Z])|(?=.*[A-Z])(?=.*\d)|(?=.*[a-z])(?=.*\d))/;
const lowSecurityRegex = /^((?=\d)|(?=[a-z])|(?=[A-Z]))/;

const handlePasswordChange = useCallback(
(password: string) => {
switch (true) {
case whitespaceRegex.test(password):
setPassword(password);
setRegexMessage(
intl.formatMessage({
defaultMessage: "No whitespace is allowed.",
}),
);
break;
case tooShortRegex.test(password):
setPassword(password);
setRegexMessage(
intl.formatMessage({
defaultMessage: "Too short.",
}),
);
break;
case tooLongRegex.test(password):
setPassword(password);
setRegexMessage(
intl.formatMessage({
defaultMessage: "That is terribly long.",
}),
);
break;
case highSecurityRegex.test(password):
setPassword(password);
setRegexMessage(intl.formatMessage({ defaultMessage: "That password is great!" }));
break;
case medSecurityRegex.test(password):
setPassword(password);
setRegexMessage(intl.formatMessage({ defaultMessage: "That password is better." }));
break;
case lowSecurityRegex.test(password):
setPassword(password);
setRegexMessage(intl.formatMessage({ defaultMessage: "That password is okay." }));
break;
default:
setPassword(password);
setRegexMessage(
intl.formatMessage({
defaultMessage: "That password confuses me, but might be okay.",
}),
);
break;
}
},
[intl, password], // eslint-disable-line react-hooks/exhaustive-deps
);

const handleClose = useCallback(() => {
setPassword("");
setPasswordConfirmation("");
onClose?.();
}, [onClose]);

const save = useCallback(() => {
const handleSave = useCallback(() => {
if (password === passwordConfirmation) {
updatePassword?.(password, passwordConfirmation);
handleClose();
}
}, [updatePassword, handleClose, password, passwordConfirmation]);

useEffect(() => {
if (password !== passwordConfirmation || password === "" || passwordConfirmation === "") {
if (
password !== passwordConfirmation ||
tooShortRegex.test(password) ||
tooLongRegex.test(password)
) {
setDisabled(true);
} else {
setDisabled(false);
}
}, [password, passwordConfirmation]);
}, [password, passwordConfirmation]); // eslint-disable-line react-hooks/exhaustive-deps

return (
<Modal
size="sm"
title={intl.formatMessage({ defaultMessage: "Change Password" })}
isVisible={isVisible}
onClose={handleClose}
Expand All @@ -64,65 +130,71 @@ const PasswordModal: React.FC<Props> = ({ isVisible, onClose, hasPassword, updat
large
disabled={disabled}
buttonType="primary"
text={intl.formatMessage({ defaultMessage: "Change your password now" })}
onClick={save}
text={intl.formatMessage({ defaultMessage: "Change password" })}
onClick={handleSave}
/>
}>
{hasPassword ? (
<div>
<Text size="s" color={theme.main.text} otherProperties={{ margin: "22px auto" }}>
<p>
<Text size="m">
{intl.formatMessage({
defaultMessage: `In order to protect your account, make sure your password:`,
})}
</Text>
<SubText>
<Text size="m">
{intl.formatMessage({
defaultMessage: `In order to protect your account, make sure your password:`,
defaultMessage: `* Is between 8 and 25 characters in length`,
})}
</p>
<StyledList>
<li>
{intl.formatMessage({
defaultMessage: `Is Longer than 8 characters`,
})}
</li>
<li>
{intl.formatMessage({
defaultMessage: `At least 2 different numbers`,
})}
</li>
<li>
{intl.formatMessage({
defaultMessage: `Use lowercase and uppercase letters`,
})}
</li>
</StyledList>
</Text>
<Label size="s">
{intl.formatMessage({ defaultMessage: "New password" })}
{/* {intl.formatMessage({
defaultMessage:
"8 characters or more, 2 types or more from numbers, lowercase letters, uppercase letters",
})} */}
</Label>
<StyledTextBox
type="password"
borderColor={"#3f3d45"}
value={password}
onChange={setPassword}
/>
<Label size="s">
{intl.formatMessage({ defaultMessage: "New password (for confirmation)" })}
</Label>
<StyledTextBox
type="password"
borderColor={"#3f3d45"}
value={passwordConfirmation}
onChange={setPasswordConfirmation}
/>
</Text>

<Text size="m">
{intl.formatMessage({
defaultMessage: `* Has at least 2 different numbers`,
})}
</Text>

<Text size="m">
{intl.formatMessage({
defaultMessage: `* Uses lowercase and uppercase letters`,
})}
</Text>
</SubText>
<PasswordField direction="column">
<Text size="m">{intl.formatMessage({ defaultMessage: "New password" })}</Text>
<TextBox
type="password"
borderColor={theme.main.border}
value={password}
message={password ? regexMessage : undefined}
onChange={handlePasswordChange}
doesChangeEveryTime
color={
whitespaceRegex.test(password) || tooLongRegex.test(password)
? theme.main.danger
: undefined
}
/>
</PasswordField>
<PasswordField direction="column">
<Text size="m">
{intl.formatMessage({ defaultMessage: "New password (for confirmation)" })}
</Text>
<TextBox
type="password"
borderColor={theme.main.border}
value={passwordConfirmation}
onChange={setPasswordConfirmation}
doesChangeEveryTime
/>
</PasswordField>
</div>
) : (
<div>
<Label size="s">{intl.formatMessage({ defaultMessage: "New password" })}</Label>
<StyledTextBox
<Text size="s">{intl.formatMessage({ defaultMessage: "New password" })}</Text>
<TextBox
type="password"
borderColor={"#3f3d45"}
borderColor={theme.main.border}
value={passwordConfirmation}
onChange={setPasswordConfirmation}
/>
Expand All @@ -131,24 +203,22 @@ const PasswordModal: React.FC<Props> = ({ isVisible, onClose, hasPassword, updat
disabled={disabled}
buttonType="primary"
text={intl.formatMessage({ defaultMessage: "Set your password now" })}
onClick={save}
onClick={handleSave}
/>
</div>
)}
</Modal>
);
};

const StyledTextBox = styled(TextBox)`
padding: 0;
margin: 20px -5px 40px;
const SubText = styled.div`
margin: ${({ theme }) =>
`${theme.metrics["xl"]}px auto ${theme.metrics["xl"]}px ${theme.metrics.l}px`};
`;

const Label = styled(Text)`
margin: 20px auto;
const PasswordField = styled(Flex)`
// background: red;
height: 75px;
`;

const StyledList = styled.ul`
margin: 20px auto;
`;
export default PasswordModal;