From 8e1a497aa4917fe6f5b9ecb3f2f9a3134d5ed594 Mon Sep 17 00:00:00 2001 From: Shim MunSeong Date: Thu, 22 Dec 2022 19:38:51 +0900 Subject: [PATCH 1/6] =?UTF-8?q?feat:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EC=8B=9C=20=EC=9D=B4=EB=A6=84=EC=9D=84=20=EC=9E=85?= =?UTF-8?q?=EB=A0=A5=EB=B0=9B=EB=8F=84=EB=A1=9D=20=ED=8F=BC=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/api/join.ts | 13 +++++- frontend/src/constants/manager.ts | 3 ++ frontend/src/constants/message.ts | 2 + frontend/src/constants/regexp.ts | 1 + .../src/pages/ManagerJoin/ManagerJoin.tsx | 7 +-- .../src/pages/ManagerJoin/units/JoinForm.tsx | 44 +++++++++++++++---- .../ManagerSocialJoin/ManagerSocialJoin.tsx | 7 +-- .../units/SocialJoinForm.tsx | 37 +++++++++++++--- 8 files changed, 93 insertions(+), 21 deletions(-) diff --git a/frontend/src/api/join.ts b/frontend/src/api/join.ts index 456064f50..55100542c 100644 --- a/frontend/src/api/join.ts +++ b/frontend/src/api/join.ts @@ -6,11 +6,13 @@ import api from './api'; interface JoinParams { email: string; password: string; + userName: string; organization: string; } interface SocialJoinParams { email: string; + userName: string; organization: string; oauthProvider: 'GITHUB' | 'GOOGLE'; } @@ -27,17 +29,24 @@ export const queryValidateEmail: QueryFunction = ({ queryKey }) => { return api.get(`/members?email=${email}`); }; -export const postJoin = ({ email, password, organization }: JoinParams): Promise => { - return api.post('/members', { email, password, organization }); +export const postJoin = ({ + email, + password, + userName, + organization, +}: JoinParams): Promise => { + return api.post('/members', { email, password, userName, organization }); }; export const postSocialJoin = ({ email, + userName, organization, oauthProvider, }: SocialJoinParams): Promise => api.post(`/members/oauth`, { email, + userName, organization, oauthProvider, }); diff --git a/frontend/src/constants/manager.ts b/frontend/src/constants/manager.ts index e8b64cb8a..371630cee 100644 --- a/frontend/src/constants/manager.ts +++ b/frontend/src/constants/manager.ts @@ -3,6 +3,9 @@ const MANAGER = { MIN_LENGTH: 8, MAX_LENGTH: 20, }, + USERNAME: { + MIN_LENGTH: 1, + }, ORGANIZATION: { MIN_LENGTH: 1, }, diff --git a/frontend/src/constants/message.ts b/frontend/src/constants/message.ts index c6af8a53b..d3ad68a5f 100644 --- a/frontend/src/constants/message.ts +++ b/frontend/src/constants/message.ts @@ -7,6 +7,8 @@ const MESSAGE = { INVALID_PASSWORD: '영어와 숫자를 포함하여 8~20자로 입력해주세요.', VALID_PASSWORD_CONFIRM: '비밀번호가 일치합니다.', INVALID_PASSWORD_CONFIRM: '비밀번호가 서로 다릅니다.', + VALID_USERNAME: '유효한 이름입니다.', + INVALID_USERNAME: '특수문자는 _ . , ! ? 만 허용됩니다.', VALID_ORGANIZATION: '유효한 조직명입니다.', INVALID_ORGANIZATION: '특수문자는 _ . , ! ? 만 허용됩니다.', UNEXPECTED_ERROR: '이메일 중복 확인에 문제가 발생했습니다. 잠시 후에 다시 시도해주세요.', diff --git a/frontend/src/constants/regexp.ts b/frontend/src/constants/regexp.ts index edc70d510..9bedb0dd9 100644 --- a/frontend/src/constants/regexp.ts +++ b/frontend/src/constants/regexp.ts @@ -1,6 +1,7 @@ const REGEXP = { PASSWORD: /^(?=.*[a-zA-Z])(?=.*[0-9]).{8,20}$/, RESERVATION_PASSWORD: /^[0-9]{4}$/, + USERNAME: /^[a-zA-Z0-9ㄱ-ㅎ가-힣ㅏ-ㅣ-_!?.,\s]{1,}$/, ORGANIZATION: /^[a-zA-Z0-9ㄱ-ㅎ가-힣ㅏ-ㅣ-_!?.,\s]{1,}$/, }; diff --git a/frontend/src/pages/ManagerJoin/ManagerJoin.tsx b/frontend/src/pages/ManagerJoin/ManagerJoin.tsx index 9e2671da3..bc9bce9ae 100644 --- a/frontend/src/pages/ManagerJoin/ManagerJoin.tsx +++ b/frontend/src/pages/ManagerJoin/ManagerJoin.tsx @@ -15,6 +15,7 @@ import JoinForm from './units/JoinForm'; export interface JoinParams { email: string; password: string; + userName: string; organization: string; } @@ -32,10 +33,10 @@ const ManagerJoin = (): JSX.Element => { }, }); - const handleSubmit = ({ email, password, organization }: JoinParams) => { - if (!email || !password || !organization) return; + const handleSubmit = ({ email, password, userName, organization }: JoinParams) => { + if (!email || !password || !userName || !organization) return; - join.mutate({ email, password, organization }); + join.mutate({ email, password, userName, organization }); }; return ( diff --git a/frontend/src/pages/ManagerJoin/units/JoinForm.tsx b/frontend/src/pages/ManagerJoin/units/JoinForm.tsx index 49c4eb165..db20a8764 100644 --- a/frontend/src/pages/ManagerJoin/units/JoinForm.tsx +++ b/frontend/src/pages/ManagerJoin/units/JoinForm.tsx @@ -16,27 +16,32 @@ interface Form { email: string; password: string; passwordConfirm: string; + userName: string; organization: string; } interface Props { - onSubmit: ({ email, password, organization }: JoinParams) => void; + onSubmit: ({ email, password, userName, organization }: JoinParams) => void; } const JoinForm = ({ onSubmit }: Props): JSX.Element => { - const [{ email, password, passwordConfirm, organization }, onChangeForm] = useInputs
({ - email: '', - password: '', - passwordConfirm: '', - organization: '', - }); + const [{ email, password, passwordConfirm, userName, organization }, onChangeForm] = + useInputs({ + email: '', + password: '', + passwordConfirm: '', + userName: '', + organization: '', + }); const [emailMessage, setEmailMessage] = useState(''); const [passwordMessage, setPasswordMessage] = useState(''); const [passwordConfirmMessage, setPasswordConfirmMessage] = useState(''); + const [userNameMessage, setUserNameMessage] = useState(''); const [organizationMessage, setOrganizationMessage] = useState(''); const isValidPassword = REGEXP.PASSWORD.test(password); + const isValidUsername = REGEXP.ORGANIZATION.test(userName); const isValidOrganization = REGEXP.ORGANIZATION.test(organization); const checkValidateEmail = useQuery(['checkValidateEmail', email], queryValidateEmail, { @@ -67,7 +72,7 @@ const JoinForm = ({ onSubmit }: Props): JSX.Element => { return; } - onSubmit({ email, password, organization }); + onSubmit({ email, password, userName, organization }); }; useEffect(() => { @@ -88,6 +93,18 @@ const JoinForm = ({ onSubmit }: Props): JSX.Element => { ); }, [password, passwordConfirm]); + useEffect(() => { + if (!userName) { + setUserNameMessage(''); + + return; + } + + setUserNameMessage( + isValidUsername ? MESSAGE.JOIN.VALID_USERNAME : MESSAGE.JOIN.INVALID_USERNAME + ); + }, [userName, isValidUsername]); + useEffect(() => { if (!organization) { setOrganizationMessage(''); @@ -138,6 +155,17 @@ const JoinForm = ({ onSubmit }: Props): JSX.Element => { status={password === passwordConfirm ? 'success' : 'error'} required /> + { }, }); - const handleSubmit = ({ email, organization }: SocialJoinParams) => { - if (!email || !organization || !oauthProvider || socialJoin.isLoading) return; + const handleSubmit = ({ email, userName, organization }: SocialJoinParams) => { + if (!email || !userName || !organization || !oauthProvider || socialJoin.isLoading) return; - socialJoin.mutate({ email, organization, oauthProvider }); + socialJoin.mutate({ email, userName, organization, oauthProvider }); }; if (!email || !oauthProvider) { diff --git a/frontend/src/pages/ManagerSocialJoin/units/SocialJoinForm.tsx b/frontend/src/pages/ManagerSocialJoin/units/SocialJoinForm.tsx index 96c97c1f0..d3f58dc97 100644 --- a/frontend/src/pages/ManagerSocialJoin/units/SocialJoinForm.tsx +++ b/frontend/src/pages/ManagerSocialJoin/units/SocialJoinForm.tsx @@ -11,22 +11,37 @@ import * as Styled from './SocialJoinForm.styles'; interface Props { email: string; oauthProvider: 'GITHUB' | 'GOOGLE'; - onSubmit: ({ email, organization }: SocialJoinParams) => void; + onSubmit: ({ email, userName, organization }: SocialJoinParams) => void; } const SocialJoinForm = ({ email, oauthProvider, onSubmit }: Props): JSX.Element => { - const [organization, onChangeForm] = useInput(''); + const [userName, onChangeUserName] = useInput(''); + const [organization, onChangeOrganization] = useInput(''); + const [userNameMessage, setUserNameMessage] = useState(''); const [organizationMessage, setOrganizationMessage] = useState(''); + const isValidUsername = REGEXP.USERNAME.test(userName); const isValidOrganization = REGEXP.ORGANIZATION.test(organization); const handleSubmit: FormEventHandler = (event) => { event.preventDefault(); - onSubmit({ email, organization }); + onSubmit({ email, userName, organization }); }; + useEffect(() => { + if (!userName) { + setUserNameMessage(''); + + return; + } + + setUserNameMessage( + isValidUsername ? MESSAGE.JOIN.VALID_USERNAME : MESSAGE.JOIN.INVALID_USERNAME + ); + }, [userName, isValidUsername]); + useEffect(() => { if (!organization) { setOrganizationMessage(''); @@ -46,17 +61,29 @@ const SocialJoinForm = ({ email, oauthProvider, onSubmit }: Props): JSX.Element label="이메일" name="email" value={email} - onChange={onChangeForm} + onChange={onChangeOrganization} required disabled /> + Date: Thu, 29 Dec 2022 20:25:38 +0900 Subject: [PATCH 2/6] =?UTF-8?q?feat:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=ED=8F=BC=EC=97=90=20=ED=94=84=EB=A1=9C=ED=95=84=20?= =?UTF-8?q?=EC=9D=B4=EB=AA=A8=EC=A7=80=EB=A5=BC=20=EC=84=A0=ED=83=9D?= =?UTF-8?q?=ED=95=A0=20=EC=88=98=20=EC=9E=88=EB=8A=94=20=EA=B8=B0=EB=8A=A5?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EC=A1=B0=EC=A7=81=EB=AA=85?= =?UTF-8?q?=20=EC=9E=85=EB=A0=A5=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/api/join.ts | 19 +- frontend/src/constants/manager.ts | 1 + frontend/src/constants/throwError.ts | 1 + frontend/src/hooks/query/useEmojiList.ts | 14 ++ .../src/pages/ManagerJoin/ManagerJoin.tsx | 8 +- .../ManagerJoin/units/EmojiSelector.styles.ts | 78 +++++++ .../pages/ManagerJoin/units/EmojiSelector.tsx | 41 ++++ .../ManagerJoin/units/JoinForm.styles.ts | 6 +- .../src/pages/ManagerJoin/units/JoinForm.tsx | 210 ++++++++++-------- frontend/src/types/common.ts | 5 + frontend/src/types/response.ts | 14 +- 11 files changed, 288 insertions(+), 109 deletions(-) create mode 100644 frontend/src/hooks/query/useEmojiList.ts create mode 100644 frontend/src/pages/ManagerJoin/units/EmojiSelector.styles.ts create mode 100644 frontend/src/pages/ManagerJoin/units/EmojiSelector.tsx diff --git a/frontend/src/api/join.ts b/frontend/src/api/join.ts index 55100542c..442417c32 100644 --- a/frontend/src/api/join.ts +++ b/frontend/src/api/join.ts @@ -1,13 +1,14 @@ import { AxiosResponse } from 'axios'; import { QueryFunction } from 'react-query'; import THROW_ERROR from 'constants/throwError'; +import { QueryEmojiListSuccess } from 'types/response'; import api from './api'; interface JoinParams { + emoji: string; email: string; password: string; userName: string; - organization: string; } interface SocialJoinParams { @@ -29,13 +30,21 @@ export const queryValidateEmail: QueryFunction = ({ queryKey }) => { return api.get(`/members?email=${email}`); }; +export const queryValidateUserName: QueryFunction = ({ queryKey }) => { + const [, userName] = queryKey; + + if (typeof userName !== 'string') throw new Error(THROW_ERROR.INVALID_USER_NAME_FORMAT); + + return api.get(`/members?userName=${userName}`); +}; + export const postJoin = ({ + emoji, email, password, userName, - organization, }: JoinParams): Promise => { - return api.post('/members', { email, password, userName, organization }); + return api.post('/members', { emoji, email, password, userName }); }; export const postSocialJoin = ({ @@ -50,3 +59,7 @@ export const postSocialJoin = ({ organization, oauthProvider, }); + +export const getEmojiList: QueryFunction> = () => { + return api.get('/members/emojis'); +}; diff --git a/frontend/src/constants/manager.ts b/frontend/src/constants/manager.ts index 371630cee..9509f8769 100644 --- a/frontend/src/constants/manager.ts +++ b/frontend/src/constants/manager.ts @@ -5,6 +5,7 @@ const MANAGER = { }, USERNAME: { MIN_LENGTH: 1, + MAX_LENGTH: 20, }, ORGANIZATION: { MIN_LENGTH: 1, diff --git a/frontend/src/constants/throwError.ts b/frontend/src/constants/throwError.ts index 52a5ad4c3..04a0b7ca3 100644 --- a/frontend/src/constants/throwError.ts +++ b/frontend/src/constants/throwError.ts @@ -1,5 +1,6 @@ const THROW_ERROR = { INVALID_EMAIL_FORMAT: '이메일은 "string" 형식이어야 합니다.', + INVALID_USER_NAME_FORMAT: '이름은 "string" 형식이어야 합니다.', INVALID_MAP_ID: '맵 ID가 올바르지 않습니다. 다시 확인해주세요.', NOT_EXIST_CONTEXT: 'context가 존재하지 않습니다.', NOT_EXIST_PRESET: '프리셋을 찾을 수 없습니다.', diff --git a/frontend/src/hooks/query/useEmojiList.ts b/frontend/src/hooks/query/useEmojiList.ts new file mode 100644 index 000000000..430cfe89b --- /dev/null +++ b/frontend/src/hooks/query/useEmojiList.ts @@ -0,0 +1,14 @@ +import { AxiosError, AxiosResponse } from 'axios'; +import { QueryKey, useQuery, UseQueryOptions, UseQueryResult } from 'react-query'; +import { getEmojiList } from 'api/join'; +import { QueryEmojiListSuccess } from 'types/response'; + +const useEmojiList = >( + options?: UseQueryOptions, AxiosError, TData, [QueryKey]> +): UseQueryResult => + useQuery(['getEmojiList'], getEmojiList, { + ...options, + refetchOnWindowFocus: false, + }); + +export default useEmojiList; diff --git a/frontend/src/pages/ManagerJoin/ManagerJoin.tsx b/frontend/src/pages/ManagerJoin/ManagerJoin.tsx index bc9bce9ae..7d1c00c99 100644 --- a/frontend/src/pages/ManagerJoin/ManagerJoin.tsx +++ b/frontend/src/pages/ManagerJoin/ManagerJoin.tsx @@ -13,10 +13,10 @@ import * as Styled from './ManagerJoin.styles'; import JoinForm from './units/JoinForm'; export interface JoinParams { + emoji: string; email: string; password: string; userName: string; - organization: string; } const ManagerJoin = (): JSX.Element => { @@ -33,10 +33,10 @@ const ManagerJoin = (): JSX.Element => { }, }); - const handleSubmit = ({ email, password, userName, organization }: JoinParams) => { - if (!email || !password || !userName || !organization) return; + const handleSubmit = ({ emoji, email, password, userName }: JoinParams) => { + if (!emoji || !email || !password || !userName) return; - join.mutate({ email, password, userName, organization }); + join.mutate({ emoji, email, password, userName }); }; return ( diff --git a/frontend/src/pages/ManagerJoin/units/EmojiSelector.styles.ts b/frontend/src/pages/ManagerJoin/units/EmojiSelector.styles.ts new file mode 100644 index 000000000..6e1c408d4 --- /dev/null +++ b/frontend/src/pages/ManagerJoin/units/EmojiSelector.styles.ts @@ -0,0 +1,78 @@ +import styled from 'styled-components'; + +export const EmojiSelector = styled.div` + position: relative; + padding: 0.75rem; + margin-top: 0.5rem; + margin-bottom: 2rem; + width: 100%; + border-top: 1px solid ${({ theme }) => theme.gray[500]}; + background: none; + outline: none; + display: flex; + justify-content: center; +`; + +export const LabelText = styled.span` + position: absolute; + display: inline-block; + top: -0.375rem; + left: 50%; + transform: translateX(-50%); + padding: 0 0.25rem; + font-size: 0.75rem; + background-color: white; + color: ${({ theme }) => theme.gray[500]}; +`; + +export const EmojiList = styled.div` + margin-top: 1rem; + font-size: 2.5rem; + display: grid; + grid-template-rows: repeat(2, 4rem); + grid-template-columns: repeat(5, 4rem); + gap: 1.25rem; + + @media (max-width: ${({ theme: { breakpoints } }) => breakpoints.sm}px) { + font-size: 1.5rem; + grid-template-rows: repeat(2, 3rem); + grid-template-columns: repeat(5, 3rem); + gap: 0.5rem; + } +`; + +export const EmojiItem = styled.label` + position: relative; + margin-bottom: 0; + justify-self: center; + align-self: center; +`; + +export const EmojiCode = styled.div` + cursor: pointer; + border-radius: 999px; + width: 4rem; + height: 4rem; + display: inline-flex; + justify-content: center; + align-items: center; + background-color: ${({ theme }) => theme.gray[100]}; + + input:checked + & { + background-color: ${({ theme }) => theme.primary[400]}; + } + + @media (max-width: ${({ theme: { breakpoints } }) => breakpoints.sm}px) { + width: 3rem; + height: 3rem; + } +`; + +export const Radio = styled.input` + position: absolute; + top: 0; + left: 0; + width: 0; + height: 0; + visibility: hidden; +`; diff --git a/frontend/src/pages/ManagerJoin/units/EmojiSelector.tsx b/frontend/src/pages/ManagerJoin/units/EmojiSelector.tsx new file mode 100644 index 000000000..90605130f --- /dev/null +++ b/frontend/src/pages/ManagerJoin/units/EmojiSelector.tsx @@ -0,0 +1,41 @@ +import { useMemo } from 'react'; +import useEmojiList from 'hooks/query/useEmojiList'; +import * as Styled from './EmojiSelector.styles'; + +interface EmojiSelectorProps { + onSelect?: (emoji: string) => void; +} + +const EmojiSelector = ({ onSelect }: EmojiSelectorProps): JSX.Element => { + const emojiListQuery = useEmojiList(); + + const emojiList = useMemo( + () => emojiListQuery.data?.data.emojis ?? [], + [emojiListQuery.data?.data.emojis] + ); + + const handleSelect = (emoji: string) => { + onSelect?.(emoji); + }; + + return ( + + 프로필 이모지 선택 + + {emojiList.map((emoji) => ( + + handleSelect(emoji.name)} + /> + {emoji.code} + + ))} + + + ); +}; + +export default EmojiSelector; diff --git a/frontend/src/pages/ManagerJoin/units/JoinForm.styles.ts b/frontend/src/pages/ManagerJoin/units/JoinForm.styles.ts index 32813ffb5..413d3fac3 100644 --- a/frontend/src/pages/ManagerJoin/units/JoinForm.styles.ts +++ b/frontend/src/pages/ManagerJoin/units/JoinForm.styles.ts @@ -2,8 +2,8 @@ import styled from 'styled-components'; export const Form = styled.form` margin: 3.75rem 0 1rem; +`; - label { - margin-bottom: 3rem; - } +export const InputWrapper = styled.div` + margin-bottom: 3rem; `; diff --git a/frontend/src/pages/ManagerJoin/units/JoinForm.tsx b/frontend/src/pages/ManagerJoin/units/JoinForm.tsx index db20a8764..44d754c10 100644 --- a/frontend/src/pages/ManagerJoin/units/JoinForm.tsx +++ b/frontend/src/pages/ManagerJoin/units/JoinForm.tsx @@ -1,7 +1,7 @@ import { AxiosError } from 'axios'; import React, { FormEventHandler, useEffect, useState } from 'react'; import { useQuery } from 'react-query'; -import { queryValidateEmail } from 'api/join'; +import { queryValidateEmail, queryValidateUserName } from 'api/join'; import Button from 'components/Button/Button'; import Input from 'components/Input/Input'; import MANAGER from 'constants/manager'; @@ -10,6 +10,7 @@ import REGEXP from 'constants/regexp'; import useInputs from 'hooks/useInputs'; import { ErrorResponse } from 'types/response'; import { JoinParams } from '../ManagerJoin'; +import EmojiSelector from './EmojiSelector'; import * as Styled from './JoinForm.styles'; interface Form { @@ -17,32 +18,27 @@ interface Form { password: string; passwordConfirm: string; userName: string; - organization: string; } interface Props { - onSubmit: ({ email, password, userName, organization }: JoinParams) => void; + onSubmit: ({ emoji, email, password, userName }: JoinParams) => void; } const JoinForm = ({ onSubmit }: Props): JSX.Element => { - const [{ email, password, passwordConfirm, userName, organization }, onChangeForm] = - useInputs({ - email: '', - password: '', - passwordConfirm: '', - userName: '', - organization: '', - }); + const [emoji, setEmoji] = useState(''); + const [{ email, password, passwordConfirm, userName }, onChangeForm] = useInputs({ + email: '', + password: '', + passwordConfirm: '', + userName: '', + }); const [emailMessage, setEmailMessage] = useState(''); const [passwordMessage, setPasswordMessage] = useState(''); const [passwordConfirmMessage, setPasswordConfirmMessage] = useState(''); const [userNameMessage, setUserNameMessage] = useState(''); - const [organizationMessage, setOrganizationMessage] = useState(''); const isValidPassword = REGEXP.PASSWORD.test(password); - const isValidUsername = REGEXP.ORGANIZATION.test(userName); - const isValidOrganization = REGEXP.ORGANIZATION.test(organization); const checkValidateEmail = useQuery(['checkValidateEmail', email], queryValidateEmail, { enabled: false, @@ -57,12 +53,49 @@ const JoinForm = ({ onSubmit }: Props): JSX.Element => { }, }); + const checkValidateUserName = useQuery( + ['checkValidateUserName', userName], + queryValidateUserName, + { + enabled: false, + retry: false, + + onSuccess: () => { + setUserNameMessage(MESSAGE.JOIN.VALID_USERNAME); + }, + + onError: (error: AxiosError) => { + setUserNameMessage(error.response?.data.message ?? ''); + }, + } + ); + + const handleChangeEmail = (event: React.ChangeEvent) => { + onChangeForm(event); + setEmailMessage(''); + }; + + const handleChangeUserName = (event: React.ChangeEvent) => { + onChangeForm(event); + setUserNameMessage(''); + }; + const handleValidateEmail = () => { if (!email) return; checkValidateEmail.refetch(); }; + const handleValidateUserName = () => { + if (!userName) return; + + checkValidateUserName.refetch(); + }; + + const handleSelectEmoji = (emoji: string) => { + setEmoji(emoji); + }; + const handleSubmit: FormEventHandler = (event) => { event.preventDefault(); @@ -72,7 +105,7 @@ const JoinForm = ({ onSubmit }: Props): JSX.Element => { return; } - onSubmit({ email, password, userName, organization }); + onSubmit({ emoji, email, password, userName }); }; useEffect(() => { @@ -93,95 +126,76 @@ const JoinForm = ({ onSubmit }: Props): JSX.Element => { ); }, [password, passwordConfirm]); - useEffect(() => { - if (!userName) { - setUserNameMessage(''); - - return; - } - - setUserNameMessage( - isValidUsername ? MESSAGE.JOIN.VALID_USERNAME : MESSAGE.JOIN.INVALID_USERNAME - ); - }, [userName, isValidUsername]); - - useEffect(() => { - if (!organization) { - setOrganizationMessage(''); - - return; - } - - setOrganizationMessage( - isValidOrganization ? MESSAGE.JOIN.VALID_ORGANIZATION : MESSAGE.JOIN.INVALID_ORGANIZATION - ); - }, [organization, isValidOrganization]); - return ( - - - - - + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/types/common.ts b/frontend/src/types/common.ts index 2cf7e6055..41b15f4d6 100644 --- a/frontend/src/types/common.ts +++ b/frontend/src/types/common.ts @@ -176,3 +176,8 @@ export interface EditorBoard { y: number; scale: number; } + +export interface Emoji { + name: string; + code: string; +} diff --git a/frontend/src/types/response.ts b/frontend/src/types/response.ts index 8654aeea8..9343aec2d 100644 --- a/frontend/src/types/response.ts +++ b/frontend/src/types/response.ts @@ -1,4 +1,12 @@ -import { MapItem, Reservation, Space, SpaceReservation, ManagerSpaceAPI, Preset } from './common'; +import { + MapItem, + Reservation, + Space, + SpaceReservation, + ManagerSpaceAPI, + Preset, + Emoji, +} from './common'; export interface MapItemResponse extends Omit { mapDrawing: string; @@ -27,6 +35,10 @@ export interface QuerySocialEmailSuccess { oauthProvider: 'GITHUB' | 'GOOGLE'; } +export interface QueryEmojiListSuccess { + emojis: Emoji[]; +} + export type QueryGuestMapSuccess = MapItemResponse; export type QueryManagerMapSuccess = MapItemResponse; From ef7c2b1e74c724c53cb89d72283f99e2689eaeac Mon Sep 17 00:00:00 2001 From: Shim MunSeong Date: Thu, 29 Dec 2022 20:50:30 +0900 Subject: [PATCH 3/6] =?UTF-8?q?feat:=20=EC=86=8C=EC=85=9C=20=ED=9A=8C?= =?UTF-8?q?=EC=9B=90=EA=B0=80=EC=9E=85=20=ED=8F=BC=EC=97=90=20=ED=94=84?= =?UTF-8?q?=EB=A1=9C=ED=95=84=20=EC=9D=B4=EB=AA=A8=EC=A7=80=EB=A5=BC=20?= =?UTF-8?q?=EC=84=A0=ED=83=9D=ED=95=A0=20=EC=88=98=20=EC=9E=88=EB=8A=94=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EC=A1=B0?= =?UTF-8?q?=EC=A7=81=EB=AA=85=20=EC=9E=85=EB=A0=A5=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/api/join.ts | 11 +- .../ManagerSocialJoin/ManagerSocialJoin.tsx | 8 +- .../units/SocialJoinForm.styles.ts | 6 +- .../units/SocialJoinForm.tsx | 119 +++++++++--------- 4 files changed, 68 insertions(+), 76 deletions(-) diff --git a/frontend/src/api/join.ts b/frontend/src/api/join.ts index 442417c32..c1d7e4f26 100644 --- a/frontend/src/api/join.ts +++ b/frontend/src/api/join.ts @@ -12,9 +12,9 @@ interface JoinParams { } interface SocialJoinParams { + emoji: string; email: string; userName: string; - organization: string; oauthProvider: 'GITHUB' | 'GOOGLE'; } @@ -48,17 +48,12 @@ export const postJoin = ({ }; export const postSocialJoin = ({ + emoji, email, userName, - organization, oauthProvider, }: SocialJoinParams): Promise => - api.post(`/members/oauth`, { - email, - userName, - organization, - oauthProvider, - }); + api.post(`/members/oauth`, { emoji, email, userName, oauthProvider }); export const getEmojiList: QueryFunction> = () => { return api.get('/members/emojis'); diff --git a/frontend/src/pages/ManagerSocialJoin/ManagerSocialJoin.tsx b/frontend/src/pages/ManagerSocialJoin/ManagerSocialJoin.tsx index b340bb286..5c44afb5e 100644 --- a/frontend/src/pages/ManagerSocialJoin/ManagerSocialJoin.tsx +++ b/frontend/src/pages/ManagerSocialJoin/ManagerSocialJoin.tsx @@ -11,9 +11,9 @@ import * as Styled from './ManagerSocialJoin.styles'; import SocialJoinForm from './units/SocialJoinForm'; export interface SocialJoinParams { + emoji: string; email: string; userName: string; - organization: string; } interface SocialJoinState { @@ -38,10 +38,10 @@ const ManagerSocialJoin = (): JSX.Element => { }, }); - const handleSubmit = ({ email, userName, organization }: SocialJoinParams) => { - if (!email || !userName || !organization || !oauthProvider || socialJoin.isLoading) return; + const handleSubmit = ({ emoji, email, userName }: SocialJoinParams) => { + if (!emoji || !email || !userName || !oauthProvider || socialJoin.isLoading) return; - socialJoin.mutate({ email, userName, organization, oauthProvider }); + socialJoin.mutate({ emoji, email, userName, oauthProvider }); }; if (!email || !oauthProvider) { diff --git a/frontend/src/pages/ManagerSocialJoin/units/SocialJoinForm.styles.ts b/frontend/src/pages/ManagerSocialJoin/units/SocialJoinForm.styles.ts index 32813ffb5..413d3fac3 100644 --- a/frontend/src/pages/ManagerSocialJoin/units/SocialJoinForm.styles.ts +++ b/frontend/src/pages/ManagerSocialJoin/units/SocialJoinForm.styles.ts @@ -2,8 +2,8 @@ import styled from 'styled-components'; export const Form = styled.form` margin: 3.75rem 0 1rem; +`; - label { - margin-bottom: 3rem; - } +export const InputWrapper = styled.div` + margin-bottom: 3rem; `; diff --git a/frontend/src/pages/ManagerSocialJoin/units/SocialJoinForm.tsx b/frontend/src/pages/ManagerSocialJoin/units/SocialJoinForm.tsx index d3f58dc97..5a5ded13a 100644 --- a/frontend/src/pages/ManagerSocialJoin/units/SocialJoinForm.tsx +++ b/frontend/src/pages/ManagerSocialJoin/units/SocialJoinForm.tsx @@ -1,94 +1,91 @@ -import { FormEventHandler, useEffect, useState } from 'react'; +import { AxiosError } from 'axios'; +import { FormEventHandler, useState } from 'react'; +import { useQuery } from 'react-query'; +import { queryValidateUserName } from 'api/join'; import Input from 'components/Input/Input'; import SocialJoinButton from 'components/SocialAuthButton/SocialJoinButton'; import MANAGER from 'constants/manager'; import MESSAGE from 'constants/message'; -import REGEXP from 'constants/regexp'; import useInput from 'hooks/useInput'; +import EmojiSelector from 'pages/ManagerJoin/units/EmojiSelector'; +import { ErrorResponse } from 'types/response'; import { SocialJoinParams } from '../ManagerSocialJoin'; import * as Styled from './SocialJoinForm.styles'; interface Props { email: string; oauthProvider: 'GITHUB' | 'GOOGLE'; - onSubmit: ({ email, userName, organization }: SocialJoinParams) => void; + onSubmit: ({ emoji, email, userName }: SocialJoinParams) => void; } const SocialJoinForm = ({ email, oauthProvider, onSubmit }: Props): JSX.Element => { + const [emoji, setEmoji] = useState(''); const [userName, onChangeUserName] = useInput(''); - const [organization, onChangeOrganization] = useInput(''); const [userNameMessage, setUserNameMessage] = useState(''); - const [organizationMessage, setOrganizationMessage] = useState(''); - const isValidUsername = REGEXP.USERNAME.test(userName); - const isValidOrganization = REGEXP.ORGANIZATION.test(organization); + const checkValidateUserName = useQuery( + ['checkValidateUserName', userName], + queryValidateUserName, + { + enabled: false, + retry: false, - const handleSubmit: FormEventHandler = (event) => { - event.preventDefault(); + onSuccess: () => { + setUserNameMessage(MESSAGE.JOIN.VALID_USERNAME); + }, + + onError: (error: AxiosError) => { + setUserNameMessage(error.response?.data.message ?? ''); + }, + } + ); - onSubmit({ email, userName, organization }); + const handleChangeUserName = (event: React.ChangeEvent) => { + onChangeUserName(event); + setUserNameMessage(''); }; - useEffect(() => { - if (!userName) { - setUserNameMessage(''); + const handleValidateUserName = () => { + if (!userName) return; - return; - } + checkValidateUserName.refetch(); + }; - setUserNameMessage( - isValidUsername ? MESSAGE.JOIN.VALID_USERNAME : MESSAGE.JOIN.INVALID_USERNAME - ); - }, [userName, isValidUsername]); + const handleSelectEmoji = (emoji: string) => { + setEmoji(emoji); + }; - useEffect(() => { - if (!organization) { - setOrganizationMessage(''); + const handleSubmit: FormEventHandler = (event) => { + event.preventDefault(); - return; - } + if (!emoji || !email || !userName) return; - setOrganizationMessage( - isValidOrganization ? MESSAGE.JOIN.VALID_ORGANIZATION : MESSAGE.JOIN.INVALID_ORGANIZATION - ); - }, [organization, isValidOrganization]); + onSubmit({ emoji, email, userName }); + }; return ( - - - + + + + + + + ); From 8736f419a9665e704c8581a04c6f6ee66005993f Mon Sep 17 00:00:00 2001 From: Shim MunSeong Date: Sat, 31 Dec 2022 15:45:26 +0900 Subject: [PATCH 4/6] =?UTF-8?q?refactor:=20=EC=BD=94=EB=93=9C=EB=A6=AC?= =?UTF-8?q?=EB=B7=B0=EC=97=90=20=EB=94=B0=EB=A5=B8=20=EB=A6=AC=ED=8C=A9?= =?UTF-8?q?=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 비구조화 할당하지 않고 파라미터를 전달하도록 수정 --- frontend/src/api/join.ts | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/frontend/src/api/join.ts b/frontend/src/api/join.ts index c1d7e4f26..15a8534b7 100644 --- a/frontend/src/api/join.ts +++ b/frontend/src/api/join.ts @@ -38,22 +38,12 @@ export const queryValidateUserName: QueryFunction = ({ queryKey }) => { return api.get(`/members?userName=${userName}`); }; -export const postJoin = ({ - emoji, - email, - password, - userName, -}: JoinParams): Promise => { - return api.post('/members', { emoji, email, password, userName }); +export const postJoin = (params: JoinParams): Promise => { + return api.post('/members', params); }; -export const postSocialJoin = ({ - emoji, - email, - userName, - oauthProvider, -}: SocialJoinParams): Promise => - api.post(`/members/oauth`, { emoji, email, userName, oauthProvider }); +export const postSocialJoin = (params: SocialJoinParams): Promise => + api.post(`/members/oauth`, params); export const getEmojiList: QueryFunction> = () => { return api.get('/members/emojis'); From c355196a593e2198a101d93213f843daf7a4f296 Mon Sep 17 00:00:00 2001 From: Shim MunSeong Date: Sat, 31 Dec 2022 15:50:28 +0900 Subject: [PATCH 5/6] =?UTF-8?q?refactor:=20=EC=BD=94=EB=93=9C=EB=A6=AC?= =?UTF-8?q?=EB=B7=B0=EC=97=90=20=EB=94=B0=EB=A5=B8=20=EB=A6=AC=ED=8C=A9?= =?UTF-8?q?=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 이메일 중복 확인 및 이름 중복 확인 API 요청 후 알 수 없는 오류가 발생했을 때의 메시지를 보여주도록 수정 --- frontend/src/constants/message.ts | 7 +++++-- frontend/src/pages/ManagerJoin/units/JoinForm.tsx | 6 ++++-- .../src/pages/ManagerSocialJoin/units/SocialJoinForm.tsx | 4 +++- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/frontend/src/constants/message.ts b/frontend/src/constants/message.ts index d3ad68a5f..9d189605a 100644 --- a/frontend/src/constants/message.ts +++ b/frontend/src/constants/message.ts @@ -7,11 +7,14 @@ const MESSAGE = { INVALID_PASSWORD: '영어와 숫자를 포함하여 8~20자로 입력해주세요.', VALID_PASSWORD_CONFIRM: '비밀번호가 일치합니다.', INVALID_PASSWORD_CONFIRM: '비밀번호가 서로 다릅니다.', - VALID_USERNAME: '유효한 이름입니다.', + VALID_USERNAME: '사용 가능한 이름입니다.', INVALID_USERNAME: '특수문자는 _ . , ! ? 만 허용됩니다.', VALID_ORGANIZATION: '유효한 조직명입니다.', INVALID_ORGANIZATION: '특수문자는 _ . , ! ? 만 허용됩니다.', - UNEXPECTED_ERROR: '이메일 중복 확인에 문제가 발생했습니다. 잠시 후에 다시 시도해주세요.', + CHECK_EMAIL_UNEXPECTED_ERROR: + '이메일 중복 확인에 문제가 발생했습니다. 잠시 후에 다시 시도해주세요.', + CHECK_USERNAME_UNEXPECTED_ERROR: + '이름 중복 확인에 문제가 발생했습니다. 잠시 후에 다시 시도해주세요.', }, LOGIN: { UNEXPECTED_ERROR: '로그인에 문제가 발생했습니다. 잠시 후에 다시 시도해주세요.', diff --git a/frontend/src/pages/ManagerJoin/units/JoinForm.tsx b/frontend/src/pages/ManagerJoin/units/JoinForm.tsx index 44d754c10..dd1ed64da 100644 --- a/frontend/src/pages/ManagerJoin/units/JoinForm.tsx +++ b/frontend/src/pages/ManagerJoin/units/JoinForm.tsx @@ -49,7 +49,7 @@ const JoinForm = ({ onSubmit }: Props): JSX.Element => { }, onError: (error: AxiosError) => { - setEmailMessage(error.response?.data.message ?? ''); + setEmailMessage(error.response?.data.message ?? MESSAGE.JOIN.CHECK_EMAIL_UNEXPECTED_ERROR); }, }); @@ -65,7 +65,9 @@ const JoinForm = ({ onSubmit }: Props): JSX.Element => { }, onError: (error: AxiosError) => { - setUserNameMessage(error.response?.data.message ?? ''); + setUserNameMessage( + error.response?.data.message ?? MESSAGE.JOIN.CHECK_USERNAME_UNEXPECTED_ERROR + ); }, } ); diff --git a/frontend/src/pages/ManagerSocialJoin/units/SocialJoinForm.tsx b/frontend/src/pages/ManagerSocialJoin/units/SocialJoinForm.tsx index 5a5ded13a..04899836e 100644 --- a/frontend/src/pages/ManagerSocialJoin/units/SocialJoinForm.tsx +++ b/frontend/src/pages/ManagerSocialJoin/units/SocialJoinForm.tsx @@ -36,7 +36,9 @@ const SocialJoinForm = ({ email, oauthProvider, onSubmit }: Props): JSX.Element }, onError: (error: AxiosError) => { - setUserNameMessage(error.response?.data.message ?? ''); + setUserNameMessage( + error.response?.data.message ?? MESSAGE.JOIN.CHECK_USERNAME_UNEXPECTED_ERROR + ); }, } ); From 4a8940b58f369b2ff011576f1afec3a2b152eae8 Mon Sep 17 00:00:00 2001 From: Shim MunSeong Date: Tue, 3 Jan 2023 18:33:41 +0900 Subject: [PATCH 6/6] =?UTF-8?q?refactor:=20=EC=9D=B4=EB=A9=94=EC=9D=BC=20?= =?UTF-8?q?=EB=B0=8F=20=EC=9D=B4=EB=A6=84=20=EC=A4=91=EB=B3=B5=20=ED=99=95?= =?UTF-8?q?=EC=9D=B8=20API=20URL=20=EB=B3=80=EA=B2=BD=20=EB=8C=80=EC=9D=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/api/join.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/api/join.ts b/frontend/src/api/join.ts index 15a8534b7..d75ac4fb2 100644 --- a/frontend/src/api/join.ts +++ b/frontend/src/api/join.ts @@ -27,7 +27,7 @@ export const queryValidateEmail: QueryFunction = ({ queryKey }) => { if (typeof email !== 'string') throw new Error(THROW_ERROR.INVALID_EMAIL_FORMAT); - return api.get(`/members?email=${email}`); + return api.post(`/members/validations/email`, { email }); }; export const queryValidateUserName: QueryFunction = ({ queryKey }) => { @@ -35,7 +35,7 @@ export const queryValidateUserName: QueryFunction = ({ queryKey }) => { if (typeof userName !== 'string') throw new Error(THROW_ERROR.INVALID_USER_NAME_FORMAT); - return api.get(`/members?userName=${userName}`); + return api.post(`/members/validations/username`, { userName }); }; export const postJoin = (params: JoinParams): Promise => {