diff --git a/src/pages/common/register/RegisterPage.tsx b/src/pages/common/register/RegisterPage.tsx index a35b932f..c6a8b1ff 100644 --- a/src/pages/common/register/RegisterPage.tsx +++ b/src/pages/common/register/RegisterPage.tsx @@ -4,6 +4,7 @@ import { useForm } from 'react-hook-form'; import { RegisterFields, RegisterType, Tos } from './components'; import { useRegister } from './store/hooks'; import { FormValues } from './types'; +import { parsePhoneNumber } from '@/shared'; import { BasicButton } from '@/shared/components'; import { Divider } from '@chakra-ui/react'; import styled from '@emotion/styled'; @@ -29,12 +30,11 @@ const RegisterPage = () => { const requestData = { name: data.name, - phoneNumber: data.phoneNumber, + phoneNumber: parsePhoneNumber(data.phoneNumber), email: 'test1@example.com', // 임시 (카카오 로그인 후 넘겨받기) isSinitto, }; console.log(requestData); - // 회원가입 API 호출 mutation.mutate(requestData); }; diff --git a/src/pages/guard/register/SeniorRegisterPage.tsx b/src/pages/guard/register/SeniorRegisterPage.tsx index 530eabe7..60847a8f 100644 --- a/src/pages/guard/register/SeniorRegisterPage.tsx +++ b/src/pages/guard/register/SeniorRegisterPage.tsx @@ -1,58 +1,28 @@ -import { useState } from 'react'; - +import { useGetAllSeniorInfo } from '../mypage'; import { SeniorInfo } from './components'; -import { SENIOR_DATA } from './data'; -import { Box, Flex, Input, Text } from '@chakra-ui/react'; +import SeniorRegisterBox from './components/senior-register-box/SeniorRegisterBox'; +import { Box, Flex } from '@chakra-ui/react'; import styled from '@emotion/styled'; export const SeniorRegisterPage = () => { - const [name, setName] = useState(''); - const [seniorName, setSeniorName] = useState(''); - const [phoneNumber, setPhoneNumber] = useState(''); + const { data: seniors, isLoading, isError, refetch } = useGetAllSeniorInfo(); - const handleRegister = () => { - setName(''); - setSeniorName(''); - setPhoneNumber(''); - }; + if (isLoading) { + return
Loading...
; + } + + if (isError) { + return
Error
; + } return ( - - {SENIOR_DATA.map((senior) => ( - + + {seniors?.map((senior) => ( + ))} - - - - 등록 이름 - setName(e.target.value)} - /> - - - 시니어의 성함 - setSeniorName(e.target.value)} - /> - - - 시니어의 전화번호 - setPhoneNumber(e.target.value)} - /> - - 시니어 등록 - + + ); }; @@ -60,60 +30,16 @@ export const SeniorRegisterPage = () => { const Container = styled(Box)` position: relative; height: 100vh; -`; - -const RegisterBox = styled(Box)` - position: absolute; - bottom: 0; - width: 100%; - height: 360px; - left: 50%; - transform: translateX(-50%); - max-width: 370px; display: flex; flex-direction: column; - align-items: center; - background-color: var(--color-white-gray); - border: 1px solid var(--color-white-gray); - border-radius: 15px; `; -const InputBox = styled(Box)` - width: 300px; - height: 80px; - display: flex; +const SeniorInfoContainer = styled(Flex)` + flex-grow: 1; + overflow-y: auto; + height: 70vh; flex-direction: column; - margin: 0.5rem; -`; - -const InputText = styled(Text)` - font-size: 18px; - font-weight: bold; - margin-bottom: 0.5rem; -`; - -const RegisterInput = styled(Input)` - background-color: var(--color-white); - border: none; - font-size: 1rem; + align-items: center; `; -const StyledButton = styled.button` - width: 300px; - height: 40px; - background-color: #c69090; - color: #ffffff; - border: none; - border-radius: 5px; - cursor: pointer; - font-size: 16px; - transition: background-color 0.3s; - - &:hover { - background-color: #a67070; - } - - &:active { - transform: scale(0.98); - } -`; +export default SeniorRegisterPage; diff --git a/src/pages/guard/register/api/hooks/index.ts b/src/pages/guard/register/api/hooks/index.ts new file mode 100644 index 00000000..a772528f --- /dev/null +++ b/src/pages/guard/register/api/hooks/index.ts @@ -0,0 +1,3 @@ +export { useAddSeniorInfo } from './useAddSeniorInfo'; +export { useDeleteSeniorInfo } from './useDeleteSeniorInfo'; +export { useEditSeniorInfo } from './useEditSeniorInfo'; diff --git a/src/pages/guard/register/api/hooks/useAddSeniorInfo.ts b/src/pages/guard/register/api/hooks/useAddSeniorInfo.ts new file mode 100644 index 00000000..3498a74d --- /dev/null +++ b/src/pages/guard/register/api/hooks/useAddSeniorInfo.ts @@ -0,0 +1,20 @@ +import { addSeniorInfo } from '../senior-info.api'; +import { SeniorRegisterValues as SeniorRegisterRequest } from '../types/senior-register.type'; +import { useMutation, UseMutationResult } from '@tanstack/react-query'; + +// 시니어 추가 훅 +export const useAddSeniorInfo = ( + refetchCallback: () => void +): UseMutationResult => { + return useMutation({ + mutationFn: (seniorInfo: SeniorRegisterRequest) => + addSeniorInfo(seniorInfo), + onSuccess: (data: string) => { + alert(data); + refetchCallback(); + }, + onError: (error: Error) => { + console.error(error); + }, + }); +}; diff --git a/src/pages/guard/register/api/hooks/useDeleteSeniorInfo.ts b/src/pages/guard/register/api/hooks/useDeleteSeniorInfo.ts new file mode 100644 index 00000000..ae77c42e --- /dev/null +++ b/src/pages/guard/register/api/hooks/useDeleteSeniorInfo.ts @@ -0,0 +1,18 @@ +import { deleteSeniorInfo } from '../senior-info.api'; +import { useMutation, UseMutationResult } from '@tanstack/react-query'; + +// 시니어 삭제 훅 +export const useDeleteSeniorInfo = ( + refetchCallback: () => void +): UseMutationResult => { + return useMutation({ + mutationFn: (seniorId: number) => deleteSeniorInfo(seniorId), + onSuccess: (data: string) => { + alert(data); + refetchCallback(); + }, + onError: (error: Error) => { + console.error(error); + }, + }); +}; diff --git a/src/pages/guard/register/api/hooks/useEditSeniorInfo.ts b/src/pages/guard/register/api/hooks/useEditSeniorInfo.ts new file mode 100644 index 00000000..83892b00 --- /dev/null +++ b/src/pages/guard/register/api/hooks/useEditSeniorInfo.ts @@ -0,0 +1,24 @@ +import { editSeniorInfo } from '../senior-info.api'; +import { SeniorRegisterValues as SeniorRegisterRequest } from '../types/senior-register.type'; +import { useMutation, UseMutationResult } from '@tanstack/react-query'; + +// 시니어 수정 훅 +export const useEditSeniorInfo = ( + refetchCallback: () => void +): UseMutationResult< + string, + Error, + { seniorId: number; seniorInfo: SeniorRegisterRequest } +> => { + return useMutation({ + mutationFn: ({ seniorId, seniorInfo }) => + editSeniorInfo(seniorId, seniorInfo), + onSuccess: (data: string) => { + alert(data); + refetchCallback(); // 새로 데이터 가져오기 (콜백함수) + }, + onError: (error: Error) => { + console.error(error); + }, + }); +}; diff --git a/src/pages/guard/register/api/index.ts b/src/pages/guard/register/api/index.ts new file mode 100644 index 00000000..b65e5352 --- /dev/null +++ b/src/pages/guard/register/api/index.ts @@ -0,0 +1,8 @@ +export { + addSeniorInfo, + deleteSeniorInfo, + editSeniorInfo, +} from './senior-info.api'; + +export * from './hooks'; +export * from './types'; diff --git a/src/pages/guard/register/api/senior-info.api.ts b/src/pages/guard/register/api/senior-info.api.ts new file mode 100644 index 00000000..c2301e1e --- /dev/null +++ b/src/pages/guard/register/api/senior-info.api.ts @@ -0,0 +1,30 @@ +import { SeniorRegisterValues as SeniorRegisterRequest } from './types/senior-register.type'; +import { fetchInstance } from '@/shared/api/instance'; + +export const seniorInfoPath = () => '/api/guards/senior'; + +// 시니어 추가 +export const addSeniorInfo = async (seniorInfo: SeniorRegisterRequest) => { + const response = await fetchInstance.post(seniorInfoPath(), seniorInfo); + return response.data; +}; + +// 시니어 삭제 +export const deleteSeniorInfo = async (seniorId: number) => { + const response = await fetchInstance.delete( + seniorInfoPath() + `/${seniorId}` + ); + return response.data; +}; + +// 시니어 정보 수정 +export const editSeniorInfo = async ( + seniorId: number, + seniorInfo: SeniorRegisterRequest +) => { + const response = await fetchInstance.put( + seniorInfoPath() + `/${seniorId}`, + seniorInfo + ); + return response.data; +}; diff --git a/src/pages/guard/register/api/types/index.ts b/src/pages/guard/register/api/types/index.ts new file mode 100644 index 00000000..0c1223b8 --- /dev/null +++ b/src/pages/guard/register/api/types/index.ts @@ -0,0 +1 @@ +export type { SeniorRegisterValues } from './senior-register.type'; diff --git a/src/pages/guard/register/api/types/senior-register.type.ts b/src/pages/guard/register/api/types/senior-register.type.ts new file mode 100644 index 00000000..e67a5786 --- /dev/null +++ b/src/pages/guard/register/api/types/senior-register.type.ts @@ -0,0 +1,4 @@ +export type SeniorRegisterValues = { + seniorName: string; + seniorPhoneNumber: string; +}; diff --git a/src/pages/guard/register/components/index.ts b/src/pages/guard/register/components/index.ts index f70b7392..f1362dbd 100644 --- a/src/pages/guard/register/components/index.ts +++ b/src/pages/guard/register/components/index.ts @@ -1 +1,2 @@ export * from './senior-info'; +export * from './senior-register-box'; diff --git a/src/pages/guard/register/components/senior-info/senior-info-box/SeniorInfo.tsx b/src/pages/guard/register/components/senior-info/senior-info-box/SeniorInfo.tsx index c5b8adef..8d6ce92a 100644 --- a/src/pages/guard/register/components/senior-info/senior-info-box/SeniorInfo.tsx +++ b/src/pages/guard/register/components/senior-info/senior-info-box/SeniorInfo.tsx @@ -1,32 +1,125 @@ -import { Box, Flex, Text } from '@chakra-ui/react'; +import { useState } from 'react'; + +import { useDeleteSeniorInfo, useEditSeniorInfo } from '../../../api'; +import { formatPhoneNumber } from '@/shared'; +import { deleteIcon, editIcon } from '@/shared/assets'; +import { Box, Flex, Text, Image, Input } from '@chakra-ui/react'; import styled from '@emotion/styled'; type SeniorInfoType = { - title: string; - name: string; - phoneNumber: string; + seniorName: string; + seniorPhoneNumber: string; + seniorId: number; }; -const SeniorInfo = ({ senior }: { senior: SeniorInfoType }) => { +const SeniorInfo = ({ + senior, + refetch, +}: { + senior: SeniorInfoType; + refetch: () => void; +}) => { + const [isEditing, setIsEditing] = useState(false); + const [seniorName, setSeniorName] = useState(senior.seniorName); + const [seniorPhoneNumber, setSeniorPhoneNumber] = useState( + senior.seniorPhoneNumber + ); + + const deleteMutation = useDeleteSeniorInfo(refetch); + const editMutation = useEditSeniorInfo(refetch); + + const handleDelete = () => { + deleteMutation.mutate(senior.seniorId); + }; + + const handleEdit = () => { + editMutation.mutate({ + seniorId: senior.seniorId, + seniorInfo: { seniorName, seniorPhoneNumber }, + }); + setIsEditing(false); + }; + return ( - - - {senior.title} - - - 성함 - {senior.name} - - - 전화번호 - {senior.phoneNumber} - - + {isEditing ? ( + + + setSeniorName(e.target.value)} + placeholder='이름을 입력하세요' + size='sm' + bg='var(--color-white)' + border='1px solid var(--color-white)' + borderRadius='10px' + /> + setSeniorPhoneNumber(e.target.value)} + placeholder='전화번호를 입력하세요' + bg='var(--color-white)' + border='1px solid var(--color-white)' + borderRadius='10px' + size='sm' + /> + + + 저장 + + + ) : ( + + + + {senior.seniorName} + + + setIsEditing(true)} + /> + + + + + 전화번호 + {formatPhoneNumber(senior.seniorPhoneNumber)} + + + )} ); }; @@ -34,8 +127,8 @@ const SeniorInfo = ({ senior }: { senior: SeniorInfoType }) => { const SeniorInfoContainer = styled(Flex)` width: 100%; max-width: 330px; - height: 6rem; - min-height: 6rem; + height: 5rem; + min-height: 5rem; background-color: var(--color-secondary); border: 1px solid var(--color-secondary); border-radius: 10px; diff --git a/src/pages/guard/register/components/senior-register-box/SeniorFormField.tsx b/src/pages/guard/register/components/senior-register-box/SeniorFormField.tsx new file mode 100644 index 00000000..d439bf29 --- /dev/null +++ b/src/pages/guard/register/components/senior-register-box/SeniorFormField.tsx @@ -0,0 +1,53 @@ +import { UseFormRegister } from 'react-hook-form'; + +import { SeniorRegisterValues } from '../../api'; +import { Box, Input, Text } from '@chakra-ui/react'; +import styled from '@emotion/styled'; + +type Props = { + label: string; + placeholder: string; + error?: string; + registerProps: ReturnType>; +}; + +const SeniorFormField = ({ + label, + placeholder, + error, + registerProps, +}: Props) => { + return ( + + + + {error && {error}} + + ); +}; + +export default SeniorFormField; + +const FieldContainer = styled(Box)` + display: flex; + flex-direction: column; + width: 100%; +`; + +const Label = styled(Text)` + font-size: 18px; + font-weight: bold; + margin-bottom: 0.5rem; +`; + +const StyledInput = styled(Input)` + background-color: var(--color-white); + border: none; + font-size: 1rem; + margin-bottom: 0.5rem; +`; + +const ErrorMessage = styled(Text)` + color: red; + font-size: 14px; +`; diff --git a/src/pages/guard/register/components/senior-register-box/SeniorRegisterBox.tsx b/src/pages/guard/register/components/senior-register-box/SeniorRegisterBox.tsx new file mode 100644 index 00000000..18144347 --- /dev/null +++ b/src/pages/guard/register/components/senior-register-box/SeniorRegisterBox.tsx @@ -0,0 +1,97 @@ +import { useForm } from 'react-hook-form'; + +import { SeniorRegisterValues, useAddSeniorInfo } from '../../api'; +import SeniorFormField from './SeniorFormField'; +import { parsePhoneNumber } from '@/shared'; +import { Box } from '@chakra-ui/react'; +import styled from '@emotion/styled'; + +const SeniorRegisterBox = ({ refetch }: { refetch: () => void }) => { + const { + register, + handleSubmit, + formState: { errors }, + } = useForm(); + const { mutate: postSeniorInfo } = useAddSeniorInfo(refetch); + + const onSubmit = (data: SeniorRegisterValues) => { + const requestSeniorData = { + seniorName: data.seniorName, + seniorPhoneNumber: parsePhoneNumber(data.seniorPhoneNumber), + }; + postSeniorInfo(requestSeniorData); + }; + + return ( + + + + + + + + 시니어 등록 + + ); +}; + +export default SeniorRegisterBox; + +const RegisterBox = styled(Box)` + position: relative; + width: 100%; + height: auto; + left: 50%; + transform: translateX(-50%); + max-width: 370px; + display: flex; + flex-direction: column; + align-items: center; + background-color: var(--color-white-gray); + border: 1px solid var(--color-white-gray); + border-radius: 15px; +`; + +const InputBox = styled(Box)` + width: 300px; + margin: 0.5rem; +`; + +const StyledButton = styled.button` + width: 300px; + height: 40px; + background-color: #c69090; + color: #ffffff; + border: none; + border-radius: 5px; + cursor: pointer; + font-size: 16px; + transition: background-color 0.3s; + margin-bottom: 10px; + + &:hover { + background-color: #a67070; + } + + &:active { + transform: scale(0.98); + } +`; diff --git a/src/pages/guard/register/components/senior-register-box/index.ts b/src/pages/guard/register/components/senior-register-box/index.ts new file mode 100644 index 00000000..f12a913e --- /dev/null +++ b/src/pages/guard/register/components/senior-register-box/index.ts @@ -0,0 +1,2 @@ +export { default as SeniorRegisterBox } from './SeniorRegisterBox'; +export { default as SeniorFormField } from './SeniorFormField'; diff --git a/src/pages/guard/register/index.ts b/src/pages/guard/register/index.ts index e795c5b8..c2af5082 100644 --- a/src/pages/guard/register/index.ts +++ b/src/pages/guard/register/index.ts @@ -2,3 +2,4 @@ export { SeniorRegisterPage } from './SeniorRegisterPage'; export * from './components'; export * from './data'; +export * from './api'; diff --git a/src/pages/guard/register/types/index.tsx b/src/pages/guard/register/types/index.tsx deleted file mode 100644 index e69de29b..00000000 diff --git a/src/shared/assets/delete-icon.svg b/src/shared/assets/delete-icon.svg new file mode 100644 index 00000000..c153dad3 --- /dev/null +++ b/src/shared/assets/delete-icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/shared/assets/edit-icon.svg b/src/shared/assets/edit-icon.svg new file mode 100644 index 00000000..d4eb690d --- /dev/null +++ b/src/shared/assets/edit-icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/shared/assets/index.ts b/src/shared/assets/index.ts new file mode 100644 index 00000000..18e64c15 --- /dev/null +++ b/src/shared/assets/index.ts @@ -0,0 +1,2 @@ +export { default as deleteIcon } from './delete-icon.svg'; +export { default as editIcon } from './edit-icon.svg'; diff --git a/src/shared/components/features/header/Header.tsx b/src/shared/components/features/header/Header.tsx index 73f66e76..2c5e200a 100644 --- a/src/shared/components/features/header/Header.tsx +++ b/src/shared/components/features/header/Header.tsx @@ -44,6 +44,7 @@ const HeaderBox = styled.header` justify-content: center; align-items: center; background-color: var(--color-white); + z-index: 10; `; const Icon = styled(Image)` diff --git a/src/shared/components/features/mypage/point-log-box/PointLogBox.tsx b/src/shared/components/features/mypage/point-log-box/PointLogBox.tsx index 0e0e3141..6a70ada3 100644 --- a/src/shared/components/features/mypage/point-log-box/PointLogBox.tsx +++ b/src/shared/components/features/mypage/point-log-box/PointLogBox.tsx @@ -2,7 +2,7 @@ import { useState } from 'react'; import PointLogImg from '../../../../assets/point-log-icon.png'; import { getPointStatusLabel, useGetPointLogs } from '@/shared/hooks'; -import { Box, Image } from '@chakra-ui/react'; +import { Box, Image, Text } from '@chakra-ui/react'; import styled from '@emotion/styled'; const PointLogBox = () => { @@ -40,7 +40,25 @@ const PointLogBox = () => { {item.content} - {item.price.toLocaleString()} + + {(item.status === 'SPEND_COMPLETE' || + item.status === 'WITHDRAW_COMPLETE' + ? '-' + : item.status === 'EARN' || + item.status === 'CHARGE_COMPLETE' + ? '+' + : '') + item.price.toLocaleString()} + @@ -153,3 +171,8 @@ const DetailText = styled(Box)` font-size: 16px; font-weight: 600; `; + +const PriceText = styled(Text)` + font-size: 16px; + font-weight: 600; +`; diff --git a/src/shared/hooks/index.ts b/src/shared/hooks/index.ts index 86d45520..1d40b502 100644 --- a/src/shared/hooks/index.ts +++ b/src/shared/hooks/index.ts @@ -1,3 +1,4 @@ export { useIntersectionObserver } from './useIntersectionObserver'; export type { UseIntersectionObserverProps } from './useIntersectionObserver'; export * from './point'; +export * from './phone-number'; diff --git a/src/shared/index.ts b/src/shared/index.ts index 8ec826f3..634e7aa1 100644 --- a/src/shared/index.ts +++ b/src/shared/index.ts @@ -3,3 +3,4 @@ export * from './constants'; export * from './hooks'; export * from './provider'; export * from './types'; +export * from './utils'; diff --git a/src/shared/utils/index.ts b/src/shared/utils/index.ts index d2cfe48f..5be19105 100644 --- a/src/shared/utils/index.ts +++ b/src/shared/utils/index.ts @@ -1 +1,2 @@ export { handleCallbackError } from './handle-callback-error'; +export * from './phone-number'; diff --git a/src/shared/utils/phone-number/formatPhoneNumber.ts b/src/shared/utils/phone-number/formatPhoneNumber.ts new file mode 100644 index 00000000..33aba0c5 --- /dev/null +++ b/src/shared/utils/phone-number/formatPhoneNumber.ts @@ -0,0 +1,9 @@ +export const formatPhoneNumber = (phone: string): string => { + const match = phone.match(/(\d{3})(\d{4})(\d{4})/); + return match ? `${match[1]}-${match[2]}-${match[3]}` : phone; +}; + +export const parsePhoneNumber = (phone: string): string => { + const match = phone.match(/(\d{3})-(\d{4})-(\d{4})/); + return match ? `${match[1]}${match[2]}${match[3]}` : phone; +}; diff --git a/src/shared/utils/phone-number/index.ts b/src/shared/utils/phone-number/index.ts new file mode 100644 index 00000000..48b86920 --- /dev/null +++ b/src/shared/utils/phone-number/index.ts @@ -0,0 +1 @@ +export { formatPhoneNumber, parsePhoneNumber } from './formatPhoneNumber';