From 1d8a38e59fe5ab3e99ecc439ff9ee7a2c9d4ae6b Mon Sep 17 00:00:00 2001 From: Shim MunSeong Date: Sun, 12 Mar 2023 18:46:06 +0900 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20=EB=82=B4=20=EC=A0=95=EB=B3=B4=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=ED=8E=98=EC=9D=B4=EC=A7=80=20UI=20?= =?UTF-8?q?=EB=B0=8F=20API=20=EC=97=B0=EB=8F=99=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/api/member.ts | 12 ++ .../EmojiSelector}/EmojiSelector.styles.ts | 0 .../EmojiSelector}/EmojiSelector.tsx | 9 +- .../MemberInfo/MemberInfo.styled.ts | 6 +- .../src/components/MemberInfo/MemberInfo.tsx | 11 +- frontend/src/constants/message.ts | 4 + frontend/src/constants/path.ts | 1 + frontend/src/constants/routes.tsx | 6 + .../src/pages/ManagerJoin/units/JoinForm.tsx | 2 +- .../ManagerProfileEdit.styles.ts | 32 +++++ .../ManagerProfileEdit/ManagerProfileEdit.tsx | 48 ++++++++ .../units/ProfileEditForm.styles.ts | 9 ++ .../units/ProfileEditForm.tsx | 109 ++++++++++++++++++ .../units/SocialJoinForm.tsx | 2 +- 14 files changed, 243 insertions(+), 8 deletions(-) rename frontend/src/{pages/ManagerJoin/units => components/EmojiSelector}/EmojiSelector.styles.ts (100%) rename frontend/src/{pages/ManagerJoin/units => components/EmojiSelector}/EmojiSelector.tsx (75%) create mode 100644 frontend/src/pages/ManagerProfileEdit/ManagerProfileEdit.styles.ts create mode 100644 frontend/src/pages/ManagerProfileEdit/ManagerProfileEdit.tsx create mode 100644 frontend/src/pages/ManagerProfileEdit/units/ProfileEditForm.styles.ts create mode 100644 frontend/src/pages/ManagerProfileEdit/units/ProfileEditForm.tsx diff --git a/frontend/src/api/member.ts b/frontend/src/api/member.ts index 63238e2e4..a1ce3c9ad 100644 --- a/frontend/src/api/member.ts +++ b/frontend/src/api/member.ts @@ -11,6 +11,18 @@ export interface QueryMemberSuccess { organization: string | null; } +export interface PutMemberParams { + userName: string; + emoji: string; +} + export const queryMember: QueryFunction> = () => { return api.get('/members/me'); }; + +export const putMember = ({ userName, emoji }: PutMemberParams) => { + return api.put('/members/me', { + userName, + emoji, + }); +}; diff --git a/frontend/src/pages/ManagerJoin/units/EmojiSelector.styles.ts b/frontend/src/components/EmojiSelector/EmojiSelector.styles.ts similarity index 100% rename from frontend/src/pages/ManagerJoin/units/EmojiSelector.styles.ts rename to frontend/src/components/EmojiSelector/EmojiSelector.styles.ts diff --git a/frontend/src/pages/ManagerJoin/units/EmojiSelector.tsx b/frontend/src/components/EmojiSelector/EmojiSelector.tsx similarity index 75% rename from frontend/src/pages/ManagerJoin/units/EmojiSelector.tsx rename to frontend/src/components/EmojiSelector/EmojiSelector.tsx index 90605130f..5ace61074 100644 --- a/frontend/src/pages/ManagerJoin/units/EmojiSelector.tsx +++ b/frontend/src/components/EmojiSelector/EmojiSelector.tsx @@ -1,20 +1,24 @@ -import { useMemo } from 'react'; +import { useMemo, useState } from 'react'; import useEmojiList from 'hooks/query/useEmojiList'; import * as Styled from './EmojiSelector.styles'; interface EmojiSelectorProps { + initialEmoji?: string; onSelect?: (emoji: string) => void; } -const EmojiSelector = ({ onSelect }: EmojiSelectorProps): JSX.Element => { +const EmojiSelector = ({ initialEmoji, onSelect }: EmojiSelectorProps): JSX.Element => { const emojiListQuery = useEmojiList(); + const [selectedEmoji, setSelectedEmoji] = useState(initialEmoji ?? null); + const emojiList = useMemo( () => emojiListQuery.data?.data.emojis ?? [], [emojiListQuery.data?.data.emojis] ); const handleSelect = (emoji: string) => { + setSelectedEmoji(emoji); onSelect?.(emoji); }; @@ -28,6 +32,7 @@ const EmojiSelector = ({ onSelect }: EmojiSelectorProps): JSX.Element => { type="radio" name="emoji" id={emoji.name} + checked={selectedEmoji === emoji.name} onChange={() => handleSelect(emoji.name)} /> {emoji.code} diff --git a/frontend/src/components/MemberInfo/MemberInfo.styled.ts b/frontend/src/components/MemberInfo/MemberInfo.styled.ts index f3bcd6d61..edb2e903c 100644 --- a/frontend/src/components/MemberInfo/MemberInfo.styled.ts +++ b/frontend/src/components/MemberInfo/MemberInfo.styled.ts @@ -27,7 +27,7 @@ export const InfoContainer = styled.div` flex-direction: column; & > * { - margin-bottom: 12px; + margin-bottom: 6px; ::last-of-type { margin-bottom: 0; @@ -37,6 +37,8 @@ export const InfoContainer = styled.div` export const NameTextContainer = styled.div` font-size: 1.5rem; + display: flex; + align-items: center; `; export const NameText = styled.span` @@ -44,6 +46,6 @@ export const NameText = styled.span` margin-right: 0.375rem; `; -export const OranizationTextContainer = styled.div` +export const OrganizationTextContainer = styled.div` font-size: 0.875rem; `; diff --git a/frontend/src/components/MemberInfo/MemberInfo.tsx b/frontend/src/components/MemberInfo/MemberInfo.tsx index 173fc4b37..95afd8433 100644 --- a/frontend/src/components/MemberInfo/MemberInfo.tsx +++ b/frontend/src/components/MemberInfo/MemberInfo.tsx @@ -1,4 +1,8 @@ import React from 'react'; +import { Link } from 'react-router-dom'; +import { theme } from 'App.styles'; +import { ReactComponent as CaretIcon } from 'assets/svg/caret-right.svg'; +import PATH from 'constants/path'; import useMember from 'hooks/query/useMember'; import * as Styled from './MemberInfo.styled'; @@ -13,11 +17,14 @@ const MemberInfo = (): JSX.Element => { {data?.data.userName}님 + + + {data?.data.organization && ( - + {data?.data.organization} - + )} diff --git a/frontend/src/constants/message.ts b/frontend/src/constants/message.ts index 95d949bbe..f2df4e279 100644 --- a/frontend/src/constants/message.ts +++ b/frontend/src/constants/message.ts @@ -19,6 +19,10 @@ const MESSAGE = { LOGIN: { UNEXPECTED_ERROR: '로그인에 문제가 발생했습니다. 잠시 후에 다시 시도해주세요.', }, + MEMBER: { + EDIT_PROFILE_UNEXPECTED_ERROR: + '내 정보를 수정하는데 문제가 발생했습니다. 잠시 후에 다시 시도해주세요.', + }, RESERVATION: { CREATE: '예약하기', EDIT: '예약 수정하기', diff --git a/frontend/src/constants/path.ts b/frontend/src/constants/path.ts index 026903c23..1d341ea84 100644 --- a/frontend/src/constants/path.ts +++ b/frontend/src/constants/path.ts @@ -27,6 +27,7 @@ const PATH = { MANAGER_SOCIAL_JOIN: '/join/social', MANAGER_GITHUB_OAUTH_REDIRECT: '/login/oauth/github', MANAGER_GOOGLE_OAUTH_REDIRECT: '/login/oauth/google', + MANAGER_PROFILE_EDIT: '/profile/edit', MANAGER_MAP_DETAIL: '/map/:mapId', MANAGER_MAP_LIST: '/map/list', MANAGER_MAP_CREATE: '/map/create', diff --git a/frontend/src/constants/routes.tsx b/frontend/src/constants/routes.tsx index 7adda95d0..b0c4ac0cd 100644 --- a/frontend/src/constants/routes.tsx +++ b/frontend/src/constants/routes.tsx @@ -2,6 +2,7 @@ import React, { ReactNode } from 'react'; import GuestMain from 'pages/GuestMain/GuestMain'; import GuestMapContainer from 'pages/GuestMap/GuestMapContainer'; import ManagerMapList from 'pages/ManagerMapList/ManagerMapList'; +import ManagerProfileEdit from 'pages/ManagerProfileEdit/ManagerProfileEdit'; import PATH from './path'; const GuestMap = React.lazy(() => import('pages/GuestMap/GuestMap')); @@ -85,6 +86,11 @@ export const PRIVATE_ROUTES: PrivateRoute[] = [ component: , redirectPath: PATH.LOGIN, }, + { + path: PATH.MANAGER_PROFILE_EDIT, + component: , + redirectPath: PATH.LOGIN, + }, { path: PATH.MANAGER_RESERVATION, component: , diff --git a/frontend/src/pages/ManagerJoin/units/JoinForm.tsx b/frontend/src/pages/ManagerJoin/units/JoinForm.tsx index dd1ed64da..a9678939b 100644 --- a/frontend/src/pages/ManagerJoin/units/JoinForm.tsx +++ b/frontend/src/pages/ManagerJoin/units/JoinForm.tsx @@ -3,6 +3,7 @@ import React, { FormEventHandler, useEffect, useState } from 'react'; import { useQuery } from 'react-query'; import { queryValidateEmail, queryValidateUserName } from 'api/join'; import Button from 'components/Button/Button'; +import EmojiSelector from 'components/EmojiSelector/EmojiSelector'; import Input from 'components/Input/Input'; import MANAGER from 'constants/manager'; import MESSAGE from 'constants/message'; @@ -10,7 +11,6 @@ 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 { diff --git a/frontend/src/pages/ManagerProfileEdit/ManagerProfileEdit.styles.ts b/frontend/src/pages/ManagerProfileEdit/ManagerProfileEdit.styles.ts new file mode 100644 index 000000000..e06d4da17 --- /dev/null +++ b/frontend/src/pages/ManagerProfileEdit/ManagerProfileEdit.styles.ts @@ -0,0 +1,32 @@ +import styled from 'styled-components'; +import { FORM_MAX_WIDTH } from 'constants/style'; + +export const Container = styled.div` + width: 100%; + max-width: ${FORM_MAX_WIDTH}; + margin: 0 auto; +`; + +export const PageTitle = styled.h2` + font-size: 1.5rem; + font-weight: 400; + text-align: center; + margin: 2.125rem auto; +`; + +export const PasswordChangeLinkMessage = styled.p` + margin: 1rem 0; + text-align: center; + font-size: 0.75rem; + color: ${({ theme }) => theme.gray[500]}; + + a { + color: ${({ theme }) => theme.primary[400]}; + text-decoration: none; + margin-left: 0.375rem; + + &:hover { + font-weight: 700; + } + } +`; diff --git a/frontend/src/pages/ManagerProfileEdit/ManagerProfileEdit.tsx b/frontend/src/pages/ManagerProfileEdit/ManagerProfileEdit.tsx new file mode 100644 index 000000000..c293f66fc --- /dev/null +++ b/frontend/src/pages/ManagerProfileEdit/ManagerProfileEdit.tsx @@ -0,0 +1,48 @@ +import { AxiosError } from 'axios'; +import React from 'react'; +import { useMutation } from 'react-query'; +import { Link, useHistory } from 'react-router-dom'; +import { putMember } from 'api/member'; +import Header from 'components/Header/Header'; +import Layout from 'components/Layout/Layout'; +import MESSAGE from 'constants/message'; +import PATH from 'constants/path'; +import { ErrorResponse } from 'types/response'; +import * as Styled from './ManagerProfileEdit.styles'; +import ProfileEditForm from './units/ProfileEditForm'; + +const ManagerProfileEdit = () => { + const history = useHistory(); + + const editProfile = useMutation(putMember, { + onSuccess: () => { + history.push(PATH.MANAGER_MAP_LIST); + }, + + onError: (error: AxiosError) => { + alert(error?.response?.data.message ?? MESSAGE.MEMBER.EDIT_PROFILE_UNEXPECTED_ERROR); + }, + }); + + const handleSubmit = ({ userName, emoji }: { userName: string; emoji: string }) => { + editProfile.mutate({ userName, emoji }); + }; + + return ( + <> +
+ + + 내 정보 수정 + + + 비밀번호를 변경하고 싶으신가요? + 변경하기 + + + + + ); +}; + +export default ManagerProfileEdit; diff --git a/frontend/src/pages/ManagerProfileEdit/units/ProfileEditForm.styles.ts b/frontend/src/pages/ManagerProfileEdit/units/ProfileEditForm.styles.ts new file mode 100644 index 000000000..413d3fac3 --- /dev/null +++ b/frontend/src/pages/ManagerProfileEdit/units/ProfileEditForm.styles.ts @@ -0,0 +1,9 @@ +import styled from 'styled-components'; + +export const Form = styled.form` + margin: 3.75rem 0 1rem; +`; + +export const InputWrapper = styled.div` + margin-bottom: 3rem; +`; diff --git a/frontend/src/pages/ManagerProfileEdit/units/ProfileEditForm.tsx b/frontend/src/pages/ManagerProfileEdit/units/ProfileEditForm.tsx new file mode 100644 index 000000000..9a89d4e26 --- /dev/null +++ b/frontend/src/pages/ManagerProfileEdit/units/ProfileEditForm.tsx @@ -0,0 +1,109 @@ +import { AxiosError } from 'axios'; +import React, { useEffect, useState } from 'react'; +import { useQuery } from 'react-query'; +import { queryValidateUserName } from 'api/join'; +import Button from 'components/Button/Button'; +import EmojiSelector from 'components/EmojiSelector/EmojiSelector'; +import Input from 'components/Input/Input'; +import MANAGER from 'constants/manager'; +import MESSAGE from 'constants/message'; +import useMember from 'hooks/query/useMember'; +import useInputs from 'hooks/useInputs'; +import { ErrorResponse } from 'types/response'; +import * as Styled from './ProfileEditForm.styles'; + +interface ProfileEditFormProps { + onSubmit?: ({ userName, emoji }: { userName: string; emoji: string }) => void; +} + +const ProfileEditForm = ({ onSubmit }: ProfileEditFormProps) => { + const member = useMember(); + const initialUserName = member.data?.data.userName; + const initialEmoji = member.data?.data.emoji.name; + + const [emoji, setEmoji] = useState(''); + const [{ userName }, onChangeForm, setInputs] = useInputs<{ userName: string }>({ + userName: '', + }); + + const [userNameMessage, setUserNameMessage] = useState(''); + + const checkValidateUserName = useQuery( + ['checkValidateUserName', userName], + queryValidateUserName, + { + enabled: false, + retry: false, + + onSuccess: () => { + setUserNameMessage(MESSAGE.JOIN.VALID_USERNAME); + }, + + onError: (error: AxiosError) => { + setUserNameMessage( + error.response?.data.message ?? MESSAGE.JOIN.CHECK_USERNAME_UNEXPECTED_ERROR + ); + }, + } + ); + + const handleSelectEmoji = (emoji: string) => { + setEmoji(emoji); + }; + + const handleChangeUserName = (event: React.ChangeEvent) => { + onChangeForm(event); + setUserNameMessage(''); + }; + + const handleSubmit: React.FormEventHandler = (event) => { + event.preventDefault(); + + onSubmit?.({ userName, emoji }); + }; + + const isSubmitButtonDisabled = !(emoji && userName); + + useEffect(() => { + if (!initialUserName) return; + + setInputs((prevInputs) => ({ + ...prevInputs, + userName: initialUserName, + })); + }, [initialUserName, setInputs]); + + useEffect(() => { + if (!initialEmoji) return; + + setEmoji(initialEmoji); + }, [initialEmoji]); + + return ( + + {initialEmoji && } + + {initialUserName && ( + + + + )} + + + ); +}; + +export default ProfileEditForm; diff --git a/frontend/src/pages/ManagerSocialJoin/units/SocialJoinForm.tsx b/frontend/src/pages/ManagerSocialJoin/units/SocialJoinForm.tsx index 04899836e..0a20ce837 100644 --- a/frontend/src/pages/ManagerSocialJoin/units/SocialJoinForm.tsx +++ b/frontend/src/pages/ManagerSocialJoin/units/SocialJoinForm.tsx @@ -2,12 +2,12 @@ import { AxiosError } from 'axios'; import { FormEventHandler, useState } from 'react'; import { useQuery } from 'react-query'; import { queryValidateUserName } from 'api/join'; +import EmojiSelector from 'components/EmojiSelector/EmojiSelector'; import Input from 'components/Input/Input'; import SocialJoinButton from 'components/SocialAuthButton/SocialJoinButton'; import MANAGER from 'constants/manager'; import MESSAGE from 'constants/message'; 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'; From 9456720f992b7be62b2e04cd8216498ee2c253a8 Mon Sep 17 00:00:00 2001 From: Shim MunSeong Date: Sun, 12 Mar 2023 19:04:33 +0900 Subject: [PATCH 2/2] =?UTF-8?q?refactor:=20ProfileEditForm=EC=9D=98=20onSu?= =?UTF-8?q?bmit=20prop=EC=9D=B4=20required=EA=B0=80=20=EB=90=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/pages/ManagerProfileEdit/units/ProfileEditForm.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/pages/ManagerProfileEdit/units/ProfileEditForm.tsx b/frontend/src/pages/ManagerProfileEdit/units/ProfileEditForm.tsx index 9a89d4e26..149874829 100644 --- a/frontend/src/pages/ManagerProfileEdit/units/ProfileEditForm.tsx +++ b/frontend/src/pages/ManagerProfileEdit/units/ProfileEditForm.tsx @@ -13,7 +13,7 @@ import { ErrorResponse } from 'types/response'; import * as Styled from './ProfileEditForm.styles'; interface ProfileEditFormProps { - onSubmit?: ({ userName, emoji }: { userName: string; emoji: string }) => void; + onSubmit: ({ userName, emoji }: { userName: string; emoji: string }) => void; } const ProfileEditForm = ({ onSubmit }: ProfileEditFormProps) => { @@ -59,7 +59,7 @@ const ProfileEditForm = ({ onSubmit }: ProfileEditFormProps) => { const handleSubmit: React.FormEventHandler = (event) => { event.preventDefault(); - onSubmit?.({ userName, emoji }); + onSubmit({ userName, emoji }); }; const isSubmitButtonDisabled = !(emoji && userName);