From be25b56862aba130e2fd9d16630414cc632ce037 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EA=B4=80=EC=8B=9D?= <39869096+gwansikk@users.noreply.github.com> Date: Tue, 26 Mar 2024 01:17:39 +0900 Subject: [PATCH 001/119] =?UTF-8?q?refactor:=20error=20message=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20(#76)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/member/src/constants/message.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/member/src/constants/message.ts b/apps/member/src/constants/message.ts index eaa07de8..fd4de1bd 100644 --- a/apps/member/src/constants/message.ts +++ b/apps/member/src/constants/message.ts @@ -1,5 +1,5 @@ export const ERROR_MESSAGE = { - default: '⚠️ 뭔가 잘못됐어요..', + DEFAULT: '오류가 발생했어요. 잠시후에 다시 시도해주세요.', } as const; export const COMMUNITY_MESSAGE = { From 3caa45dd2213a339b3fe5eac45e11e67c1eccc64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EA=B4=80=EC=8B=9D?= <39869096+gwansikk@users.noreply.github.com> Date: Tue, 26 Mar 2024 01:25:12 +0900 Subject: [PATCH 002/119] refactor: add key to modal hook (#76) --- apps/member/src/hooks/common/useModal.ts | 22 +++++++++++++++++++--- apps/member/src/store/modal.ts | 2 ++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/apps/member/src/hooks/common/useModal.ts b/apps/member/src/hooks/common/useModal.ts index b88c2d98..81ca9a5e 100644 --- a/apps/member/src/hooks/common/useModal.ts +++ b/apps/member/src/hooks/common/useModal.ts @@ -1,6 +1,8 @@ import { useSetModalStore } from '@store/modal'; +import { now } from '@utils/date'; interface OpenModalProps { + key?: string; title?: string; content: React.ReactNode; accept?: { @@ -15,14 +17,18 @@ interface OpenModalProps { const useModal = () => { const setModal = useSetModalStore(); - + /** + * open modal + */ const openModal = ({ + key = now().toString(), title = 'C-Lab PLAY', content, accept, cancel, }: OpenModalProps) => { setModal({ + key, isOpen: true, title, content, @@ -33,12 +39,22 @@ const useModal = () => { }, }); }; - + /** + * close modal + */ const closeModal = () => { setModal((prev) => ({ ...prev, isOpen: false })); }; + /** + * update modal + * - when you need to update modal content + * - ex) when you need to update modal content after user input + */ + const updateModal = () => { + setModal((prev) => ({ ...prev, key: now().toString() })); + }; - return { openModal, closeModal }; + return { openModal, closeModal, updateModal }; }; export default useModal; diff --git a/apps/member/src/store/modal.ts b/apps/member/src/store/modal.ts index 758cbbaa..b477080a 100644 --- a/apps/member/src/store/modal.ts +++ b/apps/member/src/store/modal.ts @@ -7,6 +7,7 @@ import { } from 'recoil'; interface ModalState { + key: string; isOpen: boolean; title: string; content: React.ReactNode; @@ -23,6 +24,7 @@ interface ModalState { export const modalState = atom({ key: ATOM_KEY.MODAL, default: { + key: '', isOpen: false, title: '', content: null, From f6f42922b48ea8dceb26c8bc53a1c13c5910d0bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EA=B4=80=EC=8B=9D?= <39869096+gwansikk@users.noreply.github.com> Date: Tue, 26 Mar 2024 01:29:33 +0900 Subject: [PATCH 003/119] =?UTF-8?q?refactor:=20useUsrInfoMutation=20onErro?= =?UTF-8?q?r=20=EB=A9=94=EC=84=B8=EC=A7=80=20=EC=B6=94=EA=B0=80=20(#76)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/member/src/hooks/queries/useUserInfoMutation.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/apps/member/src/hooks/queries/useUserInfoMutation.ts b/apps/member/src/hooks/queries/useUserInfoMutation.ts index 1635d049..c7b36748 100644 --- a/apps/member/src/hooks/queries/useUserInfoMutation.ts +++ b/apps/member/src/hooks/queries/useUserInfoMutation.ts @@ -2,7 +2,11 @@ import { useMutation, useQueryClient } from '@tanstack/react-query'; import { patchUserInfo } from '@api/member'; import { QUERY_KEY } from '@constants/key'; import useToast from '@hooks/common/useToast'; +import { ERROR_MESSAGE } from '@constants/message'; +/** + * 회원의 정보를 수정합니다. + */ export const useUserInfoMutation = () => { const queryClient = useQueryClient(); const toast = useToast(); @@ -13,6 +17,9 @@ export const useUserInfoMutation = () => { queryClient.invalidateQueries({ queryKey: [QUERY_KEY.MY_PROFILE] }); toast({ state: 'success', message: '프로필이 수정되었습니다.' }); }, + onError: () => { + toast({ state: 'error', message: ERROR_MESSAGE.DEFAULT }); + }, }); return { userInfoMutate: userInfoMutation.mutate }; From 1a552867e857ceb1d4c469448aa0e13ac17e3517 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EA=B4=80=EC=8B=9D?= <39869096+gwansikk@users.noreply.github.com> Date: Tue, 26 Mar 2024 01:29:59 +0900 Subject: [PATCH 004/119] =?UTF-8?q?chore:=20member=20api=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EA=B0=9C=EC=84=A0=20(#76)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/member/src/api/member.ts | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/apps/member/src/api/member.ts b/apps/member/src/api/member.ts index a0b43807..a69fa034 100644 --- a/apps/member/src/api/member.ts +++ b/apps/member/src/api/member.ts @@ -9,6 +9,7 @@ interface PatchUserInfoArgs { body: ProfileData; multipartFile: FormData | null; } + // 내 정보 export const getMyProfile = async () => { const { data } = await server.get>({ @@ -24,21 +25,14 @@ export const patchUserInfo = async ({ body, multipartFile, }: PatchUserInfoArgs) => { - let userInfoData; if (multipartFile) { const data = await postUploadedFileProfileImage(multipartFile); - - userInfoData = { - ...body, - imageUrl: data.fileUrl, - }; - } else { - userInfoData = body; + body['imageUrl'] = data.fileUrl; } const { data } = await server.patch>({ url: END_POINT.MY_INFO_EDIT(id), - body: userInfoData, + body, }); return data; From 896ad283ca03e928530ef8c15b62163c353da1c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EA=B4=80=EC=8B=9D?= <39869096+gwansikk@users.noreply.github.com> Date: Tue, 26 Mar 2024 01:30:27 +0900 Subject: [PATCH 005/119] =?UTF-8?q?fix:=20=EB=B9=84=EB=B0=80=EB=B2=88?= =?UTF-8?q?=ED=98=B8=20=EC=B4=88=EA=B8=B0=ED=99=94=20=EC=98=A4=EB=A5=98=20?= =?UTF-8?q?(#76)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../my/ProfileSection/ProfileSection.tsx | 163 +++++++++--------- 1 file changed, 86 insertions(+), 77 deletions(-) diff --git a/apps/member/src/components/my/ProfileSection/ProfileSection.tsx b/apps/member/src/components/my/ProfileSection/ProfileSection.tsx index 157e61ca..4c171991 100644 --- a/apps/member/src/components/my/ProfileSection/ProfileSection.tsx +++ b/apps/member/src/components/my/ProfileSection/ProfileSection.tsx @@ -1,4 +1,4 @@ -import { ChangeEvent, useState } from 'react'; +import { ChangeEvent, useCallback, useState } from 'react'; import { Button } from '@clab/design-system'; import Image from '@components/common/Image/Image'; import Section from '@components/common/Section/Section'; @@ -17,51 +17,45 @@ interface ProfileSectionProps { data: ProfileData; } -interface ProfileType extends ProfileData { - password: string; - passwordCheck: string; -} - const ProfileSection = ({ data }: ProfileSectionProps) => { const setIsLoggedIn = useSetIsLoggedInStore(); const { userInfoMutate } = useUserInfoMutation(); - const { openModal } = useModal(); + const { openModal, updateModal } = useModal(); const toast = useToast(); const [profileImage, setProfileImage] = useState(data.imageUrl); const [isEdit, setIsEdit] = useState(false); - const [inputs, setInputs] = useState({ - ...data, + const [inputs, setInputs] = useState(data); + const [newPassword, setNewPassword] = useState({ password: '', passwordCheck: '', }); - const onClickEdit = () => { - setIsEdit((prev) => { - if (prev) { - const formData = new FormData(); - const file = document.getElementById('imageUrl') as HTMLInputElement; - if (file.files?.length) formData.append(FORM_DATA_KEY, file.files[0]); - let newPassword = undefined; - if (inputs.password === inputs.passwordCheck) { - newPassword = inputs.passwordCheck; - } else { - toast({ - state: 'error', - message: '비밀번호 변경이 일치하지 않습니다. 다시 확인해주세요.', - }); - return prev; - } - userInfoMutate({ - id: data.id, - body: { - ...inputs, - password: newPassword, - }, - multipartFile: file.files?.length ? formData : null, - }); - } - return !prev; + const onClickChangePassword = () => { + openModal({ + title: '비밀번호 변경', + content: ( +
+ + +
+ ), + accept: { + text: '변경하기', + onClick: onClickChangePasswordAccept, + }, }); }; @@ -79,34 +73,64 @@ const ProfileSection = ({ data }: ProfileSectionProps) => { }); }; - const handleInputsChange = (e: ChangeEvent) => { + const handleIsEditClick = () => { + setIsEdit((prev) => { + if (prev) { + const formData = new FormData(); + const file = document.getElementById('imageUrl') as HTMLInputElement; + if (file.files?.length) formData.append(FORM_DATA_KEY, file.files[0]); + + userInfoMutate({ + id: data.id, + body: inputs, + multipartFile: file.files?.length ? formData : null, + }); + } + return !prev; + }); + }; + + const handleInputsChange = useCallback((e: ChangeEvent) => { const { name, value } = e.target; setInputs((prev) => ({ ...prev, [name]: value })); + }, []); + + const handlePasswordChange = (e: ChangeEvent) => { + const { name, value } = e.target; + setNewPassword((prev) => ({ ...prev, [name]: value })); + updateModal(); }; - const onChangeProfileImage = (e: ChangeEvent) => { - const reader = new FileReader(); - reader.onload = () => { - if (reader.readyState === 2) { - setProfileImage(reader.result as string); - } - }; - if (e.target.files && e.target.files.length > 0) { - reader.readAsDataURL(e.target.files[0]); + const onClickChangePasswordAccept = () => { + if (newPassword.password !== newPassword.passwordCheck) { + return toast({ + state: 'error', + message: '비밀번호가 일치하지 않습니다.', + }); } + userInfoMutate({ + id: data.id, + body: { ...inputs, password: newPassword.password }, + multipartFile: null, + }); }; - const { - name, - id, - interests, - contact, - email, - address, - password, - passwordCheck, - githubUrl, - } = inputs; + const onChangeProfileImage = useCallback( + (e: ChangeEvent) => { + const reader = new FileReader(); + reader.onload = () => { + if (reader.readyState === 2) { + setProfileImage(reader.result as string); + } + }; + if (e.target.files && e.target.files.length > 0) { + reader.readAsDataURL(e.target.files[0]); + } + }, + [], + ); + + const { name, id, interests, contact, email, address, githubUrl } = inputs; return (
@@ -115,10 +139,15 @@ const ProfileSection = ({ data }: ProfileSectionProps) => { + {isEdit && ( + + )} @@ -204,26 +233,6 @@ const ProfileSection = ({ data }: ProfileSectionProps) => { onChange={handleInputsChange} /> - -
From 946762fb96d8a052ea57632e5027b6c721a17ae0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EA=B4=80=EC=8B=9D?= <39869096+gwansikk@users.noreply.github.com> Date: Tue, 26 Mar 2024 15:52:29 +0900 Subject: [PATCH 006/119] feat(design-system): add grid layout (#78) --- packages/design-system/src/Grid/Grid.style.ts | 19 ++++++++++++++ packages/design-system/src/Grid/Grid.tsx | 26 +++++++++++++++++++ packages/design-system/src/Grid/Grid.types.ts | 2 ++ packages/design-system/src/Grid/index.ts | 1 + packages/design-system/src/index.ts | 7 +++++ 5 files changed, 55 insertions(+) create mode 100644 packages/design-system/src/Grid/Grid.style.ts create mode 100644 packages/design-system/src/Grid/Grid.tsx create mode 100644 packages/design-system/src/Grid/Grid.types.ts create mode 100644 packages/design-system/src/Grid/index.ts diff --git a/packages/design-system/src/Grid/Grid.style.ts b/packages/design-system/src/Grid/Grid.style.ts new file mode 100644 index 00000000..1b5b8725 --- /dev/null +++ b/packages/design-system/src/Grid/Grid.style.ts @@ -0,0 +1,19 @@ +export const gridStyleCols = { + 1: 'grid-cols-1', + 2: 'grid-cols-2', + 3: 'grid-cols-3', + 4: 'grid-cols-4', + 5: 'grid-cols-5', + 6: 'grid-cols-6', + 7: 'grid-cols-7', + 8: 'grid-cols-8', + 9: 'grid-cols-9', + 10: 'grid-cols-10', +} as const; + +export const gridStyleGaps = { + none: 'gap-0', + sm: 'gap-2', + md: 'gap-4', + lg: 'gap-6', +} as const; diff --git a/packages/design-system/src/Grid/Grid.tsx b/packages/design-system/src/Grid/Grid.tsx new file mode 100644 index 00000000..29f5ec2a --- /dev/null +++ b/packages/design-system/src/Grid/Grid.tsx @@ -0,0 +1,26 @@ +import React, { ComponentPropsWithRef, forwardRef } from 'react'; +import { twMerge } from 'tailwind-merge'; +import { gridStyleCols, gridStyleGaps } from './Grid.style'; +import { GridStyleColsType, GridStyleGapsType } from './Grid.types'; + +interface GridProps extends ComponentPropsWithRef<'div'> { + col?: GridStyleColsType; + gap?: GridStyleGapsType; +} + +const Grid = forwardRef( + ({ col = 1, gap = 'none', children }, ref) => { + return ( +
+ {children} +
+ ); + }, +); + +Grid.displayName = 'Grid'; + +export default Grid; diff --git a/packages/design-system/src/Grid/Grid.types.ts b/packages/design-system/src/Grid/Grid.types.ts new file mode 100644 index 00000000..ef5a1a3b --- /dev/null +++ b/packages/design-system/src/Grid/Grid.types.ts @@ -0,0 +1,2 @@ +export type GridStyleColsType = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10; +export type GridStyleGapsType = 'none' | 'sm' | 'md' | 'lg'; diff --git a/packages/design-system/src/Grid/index.ts b/packages/design-system/src/Grid/index.ts new file mode 100644 index 00000000..35a32bd9 --- /dev/null +++ b/packages/design-system/src/Grid/index.ts @@ -0,0 +1 @@ +export { default as Grid } from './Grid'; diff --git a/packages/design-system/src/index.ts b/packages/design-system/src/index.ts index 5eb1c102..8be79c1b 100644 --- a/packages/design-system/src/index.ts +++ b/packages/design-system/src/index.ts @@ -1,2 +1,9 @@ +/** + * layout + */ +export * from './Grid'; +/** + * components + */ export * from './Button'; export * from './Input'; From 8721f330da9263bfa6461f0cbc60a04d3ef70d28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EA=B4=80=EC=8B=9D?= <39869096+gwansikk@users.noreply.github.com> Date: Tue, 26 Mar 2024 20:30:13 +0900 Subject: [PATCH 007/119] =?UTF-8?q?fix:=20activity,=20book=20API=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20(#78)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/member/src/api/activity.ts | 8 ++++---- apps/member/src/api/book.ts | 2 +- apps/member/src/constants/api.ts | 2 +- apps/member/src/hooks/queries/useMyActivity.ts | 13 +++++-------- 4 files changed, 11 insertions(+), 14 deletions(-) diff --git a/apps/member/src/api/activity.ts b/apps/member/src/api/activity.ts index 56d86108..c36e88e4 100644 --- a/apps/member/src/api/activity.ts +++ b/apps/member/src/api/activity.ts @@ -45,15 +45,15 @@ interface PatchActivityBoardArgs { // 나의 활동 일정 조회 export const getMyActivities = async ( - startDateTime: string, - endDateTime: string, + startDate: string, + endDate: string, page: number, size: number, ) => { const { data } = await server.get>({ url: createCommonPagination(END_POINT.MY_ACTIVITY, { - startDateTime, - endDateTime, + startDate, + endDate, page, size, }), diff --git a/apps/member/src/api/book.ts b/apps/member/src/api/book.ts index bacddf30..473065f4 100644 --- a/apps/member/src/api/book.ts +++ b/apps/member/src/api/book.ts @@ -90,7 +90,7 @@ export const getBookLoanByMemberId = async ( ) => { const params = { borrowerId, page, size }; const { data } = await server.get>({ - url: createCommonPagination(END_POINT.BOOK_LOAN_SEARCH, params), + url: createCommonPagination(END_POINT.BOOK_LOAN_CONDITIONS, params), }); return data; diff --git a/apps/member/src/constants/api.ts b/apps/member/src/constants/api.ts index 500dc328..4f8919ae 100644 --- a/apps/member/src/constants/api.ts +++ b/apps/member/src/constants/api.ts @@ -23,7 +23,7 @@ export const END_POINT = { BOOK: `/books`, BOOK_DETAIL: (id: number) => `/books/${id}`, BOOK_LOAN: `/book-loan-records`, - BOOK_LOAN_SEARCH: `/book-loan-records/search`, + BOOK_LOAN_CONDITIONS: `/book-loan-records/conditions`, BOOK_LOAN_BORROW: `/book-loan-records/borrow`, BOOK_LOAN_EXTEND: `/book-loan-records/extend`, BOOK_LOAN_RETURN: `/book-loan-records/return`, diff --git a/apps/member/src/hooks/queries/useMyActivity.ts b/apps/member/src/hooks/queries/useMyActivity.ts index 23517896..445f874c 100644 --- a/apps/member/src/hooks/queries/useMyActivity.ts +++ b/apps/member/src/hooks/queries/useMyActivity.ts @@ -1,22 +1,19 @@ import { getMyActivities } from '@api/activity'; import { QUERY_KEY } from '@constants/key'; import { useSuspenseQuery } from '@tanstack/react-query'; -import dayjs from 'dayjs'; - -const today = dayjs().format('YYYY-MM-DDTHH:mm:ss.SSS'); -const endDay = dayjs().add(6, 'month').format('YYYY-MM-DDTHH:mm:ss.SSS'); +import { now } from '@utils/date'; /** * 내 활동을 조회합니다. (최근 6개월) */ export const useMyActivity = ( - startDateTime = String(today), - endDateTime = String(endDay), + startDate = now().format('YYYY-MM-DD'), + endDate = now().add(6, 'month').format('YYYY-MM-DD'), page = 0, size = 20, ) => { return useSuspenseQuery({ - queryFn: () => getMyActivities(startDateTime, endDateTime, page, size), - queryKey: [QUERY_KEY.MY_ACTIVITY, startDateTime, endDateTime, page, size], + queryFn: () => getMyActivities(startDate, endDate, page, size), + queryKey: [QUERY_KEY.MY_ACTIVITY, startDate, endDate, page, size], }); }; From ae77095dc8f9a00e2740cf672d60f0a9a133e4fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EA=B4=80=EC=8B=9D?= <39869096+gwansikk@users.noreply.github.com> Date: Tue, 26 Mar 2024 20:32:32 +0900 Subject: [PATCH 008/119] refactor: add custom filed to modal (#78) --- .../src/components/common/Modal/Modal.tsx | 60 +++++++++++-------- .../common/Modal/ModalContainer.tsx | 38 ++++++------ apps/member/src/hooks/common/useModal.ts | 11 ++-- apps/member/src/store/modal.ts | 2 + 4 files changed, 64 insertions(+), 47 deletions(-) diff --git a/apps/member/src/components/common/Modal/Modal.tsx b/apps/member/src/components/common/Modal/Modal.tsx index 7e32840b..9d3841ff 100644 --- a/apps/member/src/components/common/Modal/Modal.tsx +++ b/apps/member/src/components/common/Modal/Modal.tsx @@ -1,28 +1,22 @@ -import { ReactNode } from 'react'; +import { PropsWithChildren } from 'react'; import useModal from '@hooks/common/useModal'; import classNames from 'classnames'; -interface ModalProps { - children: ReactNode; +interface ModalProps extends PropsWithChildren { + className?: string; } -interface ModalHeaderProps { - title: string; - children: ReactNode; -} - -interface ModalButtonProps { +interface ModalButtonProps extends ModalProps { color: 'gray' | 'red' | 'sky' | 'orange'; - children: ReactNode; onClick?: () => void; } -const Modal = ({ children }: ModalProps) => { +const Modal = ({ children }: PropsWithChildren) => { const { closeModal } = useModal(); return (
{
+ />
-
+
{children}
@@ -42,28 +36,43 @@ const Modal = ({ children }: ModalProps) => { ); }; -Modal.Body = ({ title, children }: ModalHeaderProps) => { +Modal.Header = ({ className, children }: ModalProps) => { return ( -
-
-

{title}

-
-
+
+

{children} -

-
+ + + ); +}; + +Modal.Body = ({ className, children }: ModalProps) => { + return ( +
+ {children} +
); }; -Modal.Footer = ({ children }: { children: React.ReactNode }) => { +Modal.Footer = ({ className, children }: ModalProps) => { return ( -