diff --git a/src/components/Password/AuthNumForm.tsx b/src/components/Password/AuthNumForm.tsx new file mode 100644 index 00000000..c586395d --- /dev/null +++ b/src/components/Password/AuthNumForm.tsx @@ -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(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, 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 ( + + +

인증번호 입력

+

회원님의 이메일로 발송된 인증번호 네자리를 입력해주세요

+
+ + {authNums.map((authNum, authNumIdx) => ( + handleChangeAuthNum(e, authNumIdx)} + onKeyDown={handleKeyPressAtEmpty} + /> + ))} + + 인증번호가 일치하지 않습니다. + 확인 + 인증번호 재발송 +
+ ); +} +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; diff --git a/src/components/Password/NewPasswordForm.tsx b/src/components/Password/NewPasswordForm.tsx new file mode 100644 index 00000000..d6319c46 --- /dev/null +++ b/src/components/Password/NewPasswordForm.tsx @@ -0,0 +1,108 @@ +import styled from 'styled-components'; +import { theme } from 'styles/theme'; + +function NewPasswordForm() { + return ( + + +

비밀번호를 변경해주세요!

+
+ +

안전한 사용을 위하여, 기존 비밀번호를 변경해야 합니다

+

아래에 새 비밀번호를 입력해주세요

+
+ +

‘영문, 숫자’ 포함하여 8글자 이상 15자 미만

+ + +
+ 입력 형식에 맞지 않습니다 + 변경 완료 +
+ ); +} + +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; diff --git a/src/components/Password/PasswordChangeForm.tsx b/src/components/Password/PasswordChangeForm.tsx new file mode 100644 index 00000000..fee0dcec --- /dev/null +++ b/src/components/Password/PasswordChangeForm.tsx @@ -0,0 +1,98 @@ +import styled from 'styled-components'; +import { theme } from 'styles/theme'; + +function PasswordChangeForm() { + return ( + + +

비밀번호를 잊어버리셨나요?

+
+ +

소담에 가입했던 이메일을 입력해주세요

+

비밀번호 재설정에 필요한 인증번호를 전송해드립니다

+
+ + +

존재하지 않는 이메일입니다

+ +
+
+ ); +} + +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; diff --git a/src/hooks/useClipboard.ts b/src/hooks/useClipboard.ts new file mode 100644 index 00000000..2eea979b --- /dev/null +++ b/src/hooks/useClipboard.ts @@ -0,0 +1,32 @@ +import { RefObject, useEffect } from 'react'; + +/** + * @param {RefObject} containerRef -> 붙여넣기 이벤트를 어디에다가 등록할건지 useRef 객체 받음. + * @param {(copiedTextArray:string[])=>void} pasteHandler -> 붙여넣기 이벤트가 일어났을 때 무슨 일을 할건지 정의. + */ + +function useClipboard( + containerRef: RefObject, + 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; diff --git a/src/pages/auth/findpw/authnum.tsx b/src/pages/auth/findpw/authnum.tsx new file mode 100644 index 00000000..db2907bf --- /dev/null +++ b/src/pages/auth/findpw/authnum.tsx @@ -0,0 +1,19 @@ +import AuthNumForm from 'components/Password/AuthNumForm'; +import styled from 'styled-components'; + +function findPw() { + return ( + + + + ); +} + +const StyledRoot = styled.div` + height: 60rem; + display: flex; + flex-direction: column; + align-items: center; +`; + +export default findPw; diff --git a/src/pages/auth/findpw/email.tsx b/src/pages/auth/findpw/email.tsx new file mode 100644 index 00000000..ff248fa0 --- /dev/null +++ b/src/pages/auth/findpw/email.tsx @@ -0,0 +1,19 @@ +import PasswordChangeForm from 'components/Password/PasswordChangeForm'; +import styled from 'styled-components'; + +function findPw() { + return ( + + + + ); +} + +const StyledRoot = styled.div` + height: 60rem; + display: flex; + flex-direction: column; + align-items: center; +`; + +export default findPw; diff --git a/src/pages/auth/findpw/newpw.tsx b/src/pages/auth/findpw/newpw.tsx new file mode 100644 index 00000000..f597e6cc --- /dev/null +++ b/src/pages/auth/findpw/newpw.tsx @@ -0,0 +1,19 @@ +import NewPasswordForm from 'components/Password/NewPasswordForm'; +import styled from 'styled-components'; + +function findPw() { + return ( + + + + ); +} + +const StyledRoot = styled.div` + height: 60rem; + display: flex; + flex-direction: column; + align-items: center; +`; + +export default findPw;