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

feat: 회원가입 시 유저명과 프로필 이모지를 입력받도록 폼 수정 #880

Merged
merged 6 commits into from
Jan 5, 2023
35 changes: 21 additions & 14 deletions frontend/src/api/join.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
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;
organization: string;
userName: string;
}

interface SocialJoinParams {
emoji: string;
email: string;
organization: string;
userName: string;
oauthProvider: 'GITHUB' | 'GOOGLE';
}

Expand All @@ -27,17 +30,21 @@ export const queryValidateEmail: QueryFunction = ({ queryKey }) => {
return api.get(`/members?email=${email}`);
};

export const postJoin = ({ email, password, organization }: JoinParams): Promise<AxiosResponse> => {
return api.post('/members', { email, password, organization });
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 = (params: JoinParams): Promise<AxiosResponse> => {
return api.post('/members', params);
};

export const postSocialJoin = ({
email,
organization,
oauthProvider,
}: SocialJoinParams): Promise<AxiosResponse> =>
api.post(`/members/oauth`, {
email,
organization,
oauthProvider,
});
export const postSocialJoin = (params: SocialJoinParams): Promise<AxiosResponse> =>
api.post(`/members/oauth`, params);

export const getEmojiList: QueryFunction<AxiosResponse<QueryEmojiListSuccess>> = () => {
return api.get('/members/emojis');
};
4 changes: 4 additions & 0 deletions frontend/src/constants/manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ const MANAGER = {
MIN_LENGTH: 8,
MAX_LENGTH: 20,
},
USERNAME: {
MIN_LENGTH: 1,
MAX_LENGTH: 20,
},
ORGANIZATION: {
MIN_LENGTH: 1,
},
Expand Down
7 changes: 6 additions & 1 deletion frontend/src/constants/message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,14 @@ const MESSAGE = {
INVALID_PASSWORD: '영어와 숫자를 포함하여 8~20자로 입력해주세요.',
VALID_PASSWORD_CONFIRM: '비밀번호가 일치합니다.',
INVALID_PASSWORD_CONFIRM: '비밀번호가 서로 다릅니다.',
VALID_USERNAME: '사용 가능한 이름입니다.',
INVALID_USERNAME: '특수문자는 _ . , ! ? 만 허용됩니다.',
VALID_ORGANIZATION: '유효한 조직명입니다.',
INVALID_ORGANIZATION: '특수문자는 _ . , ! ? 만 허용됩니다.',
UNEXPECTED_ERROR: '이메일 중복 확인에 문제가 발생했습니다. 잠시 후에 다시 시도해주세요.',
CHECK_EMAIL_UNEXPECTED_ERROR:
'이메일 중복 확인에 문제가 발생했습니다. 잠시 후에 다시 시도해주세요.',
CHECK_USERNAME_UNEXPECTED_ERROR:
'이름 중복 확인에 문제가 발생했습니다. 잠시 후에 다시 시도해주세요.',
},
LOGIN: {
UNEXPECTED_ERROR: '로그인에 문제가 발생했습니다. 잠시 후에 다시 시도해주세요.',
Expand Down
1 change: 1 addition & 0 deletions frontend/src/constants/regexp.ts
Original file line number Diff line number Diff line change
@@ -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,}$/,
};

Expand Down
1 change: 1 addition & 0 deletions frontend/src/constants/throwError.ts
Original file line number Diff line number Diff line change
@@ -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: '프리셋을 찾을 수 없습니다.',
Expand Down
14 changes: 14 additions & 0 deletions frontend/src/hooks/query/useEmojiList.ts
Original file line number Diff line number Diff line change
@@ -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 = <TData = AxiosResponse<QueryEmojiListSuccess>>(
options?: UseQueryOptions<AxiosResponse<QueryEmojiListSuccess>, AxiosError, TData, [QueryKey]>
): UseQueryResult<TData, AxiosError> =>
useQuery(['getEmojiList'], getEmojiList, {
...options,
refetchOnWindowFocus: false,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

해당 쿼리에 refetchOnWindowFocusfalse로 주신 이유가 있을까용?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이모지 리스트는 실시간으로 데이터를 업데이트할 필요가 없다고 생각해서 반복적으로 fetch해오는 상황을 막아주고 싶었습니다.

});

export default useEmojiList;
9 changes: 5 additions & 4 deletions frontend/src/pages/ManagerJoin/ManagerJoin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@ import * as Styled from './ManagerJoin.styles';
import JoinForm from './units/JoinForm';

export interface JoinParams {
emoji: string;
email: string;
password: string;
organization: string;
userName: string;
}

const ManagerJoin = (): JSX.Element => {
Expand All @@ -32,10 +33,10 @@ const ManagerJoin = (): JSX.Element => {
},
});

const handleSubmit = ({ email, password, organization }: JoinParams) => {
if (!email || !password || !organization) return;
const handleSubmit = ({ emoji, email, password, userName }: JoinParams) => {
if (!emoji || !email || !password || !userName) return;

join.mutate({ email, password, organization });
join.mutate({ emoji, email, password, userName });
};

return (
Expand Down
78 changes: 78 additions & 0 deletions frontend/src/pages/ManagerJoin/units/EmojiSelector.styles.ts
Original file line number Diff line number Diff line change
@@ -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;
`;
41 changes: 41 additions & 0 deletions frontend/src/pages/ManagerJoin/units/EmojiSelector.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<Styled.EmojiSelector>
<Styled.LabelText>프로필 이모지 선택</Styled.LabelText>
<Styled.EmojiList>
{emojiList.map((emoji) => (
<Styled.EmojiItem key={emoji.name}>
<Styled.Radio
type="radio"
name="emoji"
id={emoji.name}
onChange={() => handleSelect(emoji.name)}
/>
<Styled.EmojiCode>{emoji.code}</Styled.EmojiCode>
</Styled.EmojiItem>
))}
</Styled.EmojiList>
</Styled.EmojiSelector>
);
};

export default EmojiSelector;
6 changes: 3 additions & 3 deletions frontend/src/pages/ManagerJoin/units/JoinForm.styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
`;
Loading