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

[#126] [FEATURE] E#10-S#7 비밀번호 재설정 뷰 #128

Open
wants to merge 5 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
156 changes: 156 additions & 0 deletions src/components/Password/AuthNumForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import useClipboard from 'hooks/useClipboard';
import { ChangeEvent, KeyboardEvent, useCallback, useEffect, useRef, useState } from 'react';
import shortid from 'shortid';
import styled from 'styled-components';
import { theme } from 'styles/theme';

function AuthNumForm() {
const [authNums, setAuthNums] = useState(new Array(4).fill('')); // authNums 인증번호 4자리를 배열로 관리
const inputParentRef = useRef<HTMLDivElement>(null); // input 태그 4개를 감싸는 부모 태그의 ref
const focusIdx = useRef(0); // 포커스를 줄 input 태그의 인덱스

useClipboard(inputParentRef, setAuthNums);

const focusTargetInput = useCallback((targetIdx: number) => {
const inputParent = inputParentRef.current;
if (inputParent) {
const targetChild = inputParent.children[targetIdx];
if (targetChild instanceof HTMLInputElement) targetChild.focus();
}
}, []);

const handleChangeAuthNum = (e: ChangeEvent<HTMLInputElement>, authNumIdx: number) => {
setAuthNums((prevAuthNums) => {
const nextAuthNums = [...prevAuthNums];
nextAuthNums.splice(authNumIdx, 1, e.target.value);
// 첫번째인자 : 인덱스, 두번째 인자: 몇개바꿀건지, 3번째인자: 뭐로 바꿀건지??
// authNumIdx 번째에있는 값에서 첫번쨰를, e.target.value로 바꿔라
// 내가 입력한 값으로 해당하는 배열원소의 값을 바까라! 이해됨?
if (e.target.value) focusIdx.current = authNumIdx + 1;
else focusIdx.current = authNumIdx - 1 < 0 ? 0 : authNumIdx - 1;
return nextAuthNums;
});
};

const handleKeyPressAtEmpty = (e: KeyboardEvent) => {
if (!(e.target instanceof HTMLInputElement)) return;
// 백스페이스를 눌러서 포커스가 <- 이 방향으로 갱신되어야할 때.
if (e.key === 'Backspace' && !e.target.value) {
setAuthNums([...authNums]);
focusIdx.current = focusIdx.current - 1 < 0 ? 0 : focusIdx.current - 1;
}
};

useEffect(() => {
focusTargetInput(focusIdx.current);
}, [authNums, focusTargetInput]);

return (
<StyledRoot>
<StyledTitleWrapper>
<h3>인증번호 입력</h3>
<p>회원님의 이메일로 발송된 인증번호 네자리를 입력해주세요</p>
</StyledTitleWrapper>
<StyledInputWrapper ref={inputParentRef}>
{authNums.map((authNum, authNumIdx) => (
<input
// eslint-disable-next-line jsx-a11y/no-autofocus
autoFocus={authNumIdx === 0 && focusIdx.current === 0}
key={shortid()}
type="tel"
maxLength={1}
min="0"
max="9"
value={authNum}
onChange={(e) => handleChangeAuthNum(e, authNumIdx)}
onKeyDown={handleKeyPressAtEmpty}
/>
))}
</StyledInputWrapper>
<StyledWarningMessage>인증번호가 일치하지 않습니다.</StyledWarningMessage>
<StyledOkBtn type="submit">확인</StyledOkBtn>
<StyledResendBtn type="submit">인증번호 재발송</StyledResendBtn>
</StyledRoot>
);
}
const StyledRoot = styled.div`
width: 39.1rem;
height: 100%;
padding: 12rem 0 9.8rem 0;
display: flex;
flex-direction: column;
align-items: center;
`;

const StyledTitleWrapper = styled.div`
width: 100%;
text-align: center;
margin-bottom: 5.8rem;

& > h3 {
margin-bottom: 1.6rem;
font-weight: 700;
font-size: 2.6rem;
line-height: 3.8rem;
color: ${theme.colors.purpleText};
}

& > p {
font-weight: 500;
font-size: 1.5rem;
line-height: 2.2rem;
color: ${theme.colors.black2};
}
`;

const StyledInputWrapper = styled.div`
width: 26.2rem;
display: flex;
justify-content: space-between;
margin-bottom: 3.2rem;

& > input {
width: 5.5rem;
height: 7.1rem;
border: 1px solid ${theme.colors.purpleText};
border-radius: 5px;
font-size: 2.4rem;
font-weight: 700;
line-height: 3.4rem;
color: ${theme.colors.black2};
text-align: center;
}
`;

const StyledWarningMessage = styled.p`
font-size: 1.4rem;
margin-bottom: 1.6rem;
line-height: 2rem;
margin-bottom: 1.6rem;
color: ${theme.colors.purpleText};
`;

const StyledOkBtn = styled.button`
width: 39.1rem;
height: 4.6rem;
margin-bottom: 1.6rem;
background-color: ${theme.colors.gray2};
border-radius: 5px;
border: 0;
color: white;
font-size: 1.5rem;
font-weight: 700;
`;

const StyledResendBtn = styled.button`
width: 39.1rem;
height: 4.6rem;
background-color: ${theme.colors.purpleMain};
border-radius: 5px;
border: 0;
color: white;
font-size: 1.5rem;
font-weight: 700;
`;

export default AuthNumForm;
108 changes: 108 additions & 0 deletions src/components/Password/NewPasswordForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import styled from 'styled-components';
import { theme } from 'styles/theme';

function NewPasswordForm() {
return (
<StyledRoot>
<StyledTitleWrapper>
<h3>비밀번호를 변경해주세요!</h3>
</StyledTitleWrapper>
<StyledExplain>
<p>안전한 사용을 위하여, 기존 비밀번호를 변경해야 합니다</p>
<p>아래에 새 비밀번호를 입력해주세요</p>
</StyledExplain>
<StyledForm>
<p>‘영문, 숫자’ 포함하여 8글자 이상 15자 미만</p>
<input placeholder="새 비밀번호" />
<input placeholder="새 비밀번호 확인" />
</StyledForm>
<StyledAlert>입력 형식에 맞지 않습니다</StyledAlert>
<StyledButton type="submit">변경 완료</StyledButton>
</StyledRoot>
);
}

const StyledRoot = styled.section`
width: 39.1rem;
padding: 12rem 0 16rem 0;
display: flex;
flex-direction: column;
align-items: center;
`;

const StyledTitleWrapper = styled.div`
text-align: center;
margin-bottom: 1.6rem;

& > h3 {
font-weight: 700;
font-size: 2.6rem;
line-height: 3.8rem;
color: ${theme.colors.purpleText};
}
`;

const StyledExplain = styled.div`
margin-bottom: 3.5rem;
display: flex;
flex-direction: column;
justify-content: space-between;
text-align: center;
& > p {
font-weight: 500;
font-size: 1.5rem;
line-height: 2.2rem;
color: ${theme.colors.black2};
}
`;

const StyledForm = styled.form`
width: 100%;

& > input {
width: 100%;
height: 5rem;
margin-bottom: 1.6rem;
background-color: white;
border: 1px solid ${theme.colors.purpleText};
border-radius: 5px;
font-size: 1.3rem;
font-weight: 500;
padding-left: 1.6rem;
color: ${theme.colors.black2};

& ::placeholder {
color: ${theme.colors.gray1};
}
}

& > p {
text-align: right;
font-size: 1rem;
margin-bottom: 0.9rem;
line-height: 2rem;
color: ${theme.colors.purpleText};
}
`;

const StyledAlert = styled.p`
text-align: center;
font-size: 1.2rem;
margin-top: 1.5rem;
line-height: 2rem;
color: ${theme.colors.purpleText};
`;

const StyledButton = styled.button`
width: 100%;
height: 4.6rem;
margin-top: 1.6rem;
background-color: ${theme.colors.gray2};
border-radius: 5px;
border: 0;
color: white;
font-size: 1.5rem;
font-weight: 700;
`;

export default NewPasswordForm;
98 changes: 98 additions & 0 deletions src/components/Password/PasswordChangeForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import styled from 'styled-components';
import { theme } from 'styles/theme';

function PasswordChangeForm() {
return (
<StyledRoot>
<StyledTitleWrapper>
<h3>비밀번호를 잊어버리셨나요?</h3>
</StyledTitleWrapper>
<StyledExplain>
<p>소담에 가입했던 이메일을 입력해주세요</p>
<p>비밀번호 재설정에 필요한 인증번호를 전송해드립니다</p>
</StyledExplain>
<StyledForm>
<input placeholder="[email protected]" />
<p>존재하지 않는 이메일입니다</p>
<button type="submit">인증번호 발송</button>
</StyledForm>
</StyledRoot>
);
}

const StyledRoot = styled.section`
width: 39.1rem;
height: 100%;
padding: 12rem 0 16rem 0;
display: flex;
flex-direction: column;
align-items: center;
`;

const StyledTitleWrapper = styled.div`
text-align: center;
margin-bottom: 1.6rem;

& > h3 {
font-weight: 700;
font-size: 2.6rem;
line-height: 3.8rem;
color: ${theme.colors.purpleText};
}
`;

const StyledExplain = styled.div`
margin-bottom: 5.8rem;
display: flex;
flex-direction: column;
justify-content: space-between;
text-align: center;
& > p {
font-weight: 500;
font-size: 1.5rem;
line-height: 2.2rem;
color: ${theme.colors.black2};
}
`;

const StyledForm = styled.form`
width: 100%;

& > input {
width: 100%;
height: 5rem;
margin-bottom: 2.8rem;
background-color: white;
border: 1px solid ${theme.colors.purpleText};
border-radius: 5px;
font-size: 1.3rem;
font-weight: 500;
padding-left: 1.6rem;
color: ${theme.colors.black2};

& ::placeholder {
color: ${theme.colors.gray1};
}
}

& > p {
text-align: center;
font-size: 1.4rem;
margin-bottom: 1.6rem;
line-height: 2rem;
color: ${theme.colors.purpleText};
}

& > button {
width: 100%;
height: 4.6rem;
background-color: ${theme.colors.gray2};
border-radius: 5px;
border: 0;
color: white;
font-size: 1.5rem;
font-weight: 700;
}
`;

export default PasswordChangeForm;
32 changes: 32 additions & 0 deletions src/hooks/useClipboard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { RefObject, useEffect } from 'react';

/**
* @param {RefObject<HTMLElement>} containerRef -> 붙여넣기 이벤트를 어디에다가 등록할건지 useRef 객체 받음.
* @param {(copiedTextArray:string[])=>void} pasteHandler -> 붙여넣기 이벤트가 일어났을 때 무슨 일을 할건지 정의.
*/

function useClipboard(
containerRef: RefObject<HTMLElement>,
pasteHandler: (copiedTextArray: string[]) => void,
) {
useEffect(() => {
const container = containerRef.current;
function parseCopiedTextToArray() {
if ('clipboard' in navigator) {
// 클립보드에 복사되어있는 텍스트를 가져옴.
navigator.clipboard.readText().then((copiedText) => {
// 5678 -> ['5','6','7','8'];
pasteHandler(Array.from(copiedText).slice(0, 4));
});
}
}

container?.addEventListener('paste', parseCopiedTextToArray);

return () => {
container?.removeEventListener('paste', parseCopiedTextToArray);
};
}, []);
}

export default useClipboard;
Loading