Skip to content

Commit

Permalink
Merge pull request #198 from ppochaco/fix/166-form-input
Browse files Browse the repository at this point in the history
Week10/issue#166 react hook form을 사용해서 form 관리하기
  • Loading branch information
ppochaco authored Nov 11, 2024
2 parents 9404c93 + e6b1eb0 commit 43bac95
Show file tree
Hide file tree
Showing 36 changed files with 1,133 additions and 628 deletions.
29 changes: 29 additions & 0 deletions src/api/services/group/group.api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ export const useGroupInfo = (groupId: number) => {
})
}

export const useGroupInfoSuspense = (groupId: number) => {
return useSuspenseQuery({
queryKey: ['group', groupId],
queryFn: () => getGroupInfo(groupId),
})
}

type GroupResponse = PagingResponse<Omit<Group, 'groupDescription'>[]>

const getGroupPaging = async (params: PagingRequestParams) => {
Expand Down Expand Up @@ -159,3 +166,25 @@ export const approveGroupQuestion = async (
)
return response.data
}

export type ModifyGroupImgRequestBody = {
groupId: number
image: File
}

export const modifyGroupImg = async ({
groupId,
image,
}: ModifyGroupImgRequestBody) => {
const formData = new FormData()
formData.append('image', image)
const response = await authorizationInstance.patch(
`/api/group/modify/image/${groupId}`,
formData,
{
headers: { 'Content-Type': 'multipart/form-data' },
}
)

return response.data
}
13 changes: 10 additions & 3 deletions src/api/services/profile/my-page.api.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,28 @@
import { useQuery } from '@tanstack/react-query'
import { useQuery, useSuspenseQuery } from '@tanstack/react-query'

import { authorizationInstance, fetchInstance } from '@/api/instance'
import { MyPageItem, UserRankingItem } from '@/types'

export const getMyPage = async (userId: string) => {
export const getMyPage = async (userId: number) => {
const response = await fetchInstance.get<MyPageItem>(`/api/profile/${userId}`)

return response.data
}

export const useMyPage = (userId: string) => {
export const useMyPage = (userId: number) => {
return useQuery({
queryKey: ['myPage', userId],
queryFn: () => getMyPage(userId),
})
}

export const useMyPageSuspense = (userId: number) => {
return useSuspenseQuery({
queryKey: ['myPage', userId],
queryFn: () => getMyPage(userId),
})
}

type UploadProfileBgRequest = {
image: File
}
Expand Down
22 changes: 22 additions & 0 deletions src/components/AvatarLabel/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Avatar, HStack, StackProps, Text } from '@chakra-ui/react'

import { colors } from '@/styles/colors'

interface AvatarLabelProps extends StackProps {
avatarSrc?: string
label: string
}

export const AvatarLabel = ({ avatarSrc, label }: AvatarLabelProps) => {
return (
<HStack gap={1.5}>
<Avatar
width={7}
height={7}
src={avatarSrc}
border={`0.8px solid ${colors.black[300]}`}
/>
<Text>{label}</Text>
</HStack>
)
}
33 changes: 0 additions & 33 deletions src/components/AvatarLabelWithNavigate/index.tsx

This file was deleted.

1 change: 1 addition & 0 deletions src/components/Form/FormContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export const useFormField = () => {
name: fieldContext.name,
formItemId: `${id}-form-item`,
formDescriptionId: `${id}-form-item-description`,
formMessageId: `${id}-form-item-message`,
...fieldState,
}
}
38 changes: 35 additions & 3 deletions src/components/Form/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,17 @@ const FormItem = FormItemProvider

const FormControl = forwardRef<HTMLDivElement, BoxProps>(
({ ...props }, ref) => {
const { error, formItemId } = useFormField()
const { error, formItemId, formMessageId } = useFormField()

return <Box ref={ref} id={formItemId} aria-invalid={!!error} {...props} />
return (
<Box
ref={ref}
id={formItemId}
aria-describedby={`${formMessageId}`}
aria-invalid={!!error}
{...props}
/>
)
}
)
FormControl.displayName = 'FormControl'
Expand All @@ -37,4 +45,28 @@ const FormDescription = forwardRef<HTMLParagraphElement, TextProps>(
)
FormDescription.displayName = 'FormDescription'

export { Form, FormField, FormItem, FormControl, FormDescription }
const FormMessage = forwardRef<HTMLParagraphElement, TextProps>(
({ children, ...props }, ref) => {
const { error, formMessageId } = useFormField()
const body = error ? String(error?.message) : children

if (!body) {
return null
}

return (
<Text
ref={ref}
id={formMessageId}
color="red"
fontSize="small"
{...props}
>
{body}
</Text>
)
}
)
FormMessage.displayName = 'FormMessage'

export { Form, FormField, FormItem, FormControl, FormDescription, FormMessage }
2 changes: 1 addition & 1 deletion src/pages/CreateGroupPage/CreateGroupForm/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
FormField,
FormItem,
} from '@/components/Form'
import { CreateGroupFields } from '@/schema/create-group'
import { CreateGroupFields } from '@/schema/group'

interface CreateGroupFormProps {
form: UseFormReturn<CreateGroupFields>
Expand Down
2 changes: 1 addition & 1 deletion src/pages/CreateGroupPage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
} from '@/api/services/group/group.api'
import cookies from '@/assets/cookies.svg'
import { AlertModal } from '@/components/Modal/AlertModal'
import { CreateGroupFields, CreateGroupSchema } from '@/schema/create-group'
import { CreateGroupFields, CreateGroupSchema } from '@/schema/group'
import { Group } from '@/types'

import { CreateGroupForm } from './CreateGroupForm'
Expand Down
2 changes: 1 addition & 1 deletion src/pages/GroupMembersPage/MembersTable/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export default function MembersTable({
data: profile,
status: profileStatus,
isError: isProfileError,
} = useMyPage(myUserId.toString())
} = useMyPage(myUserId)

const {
data: memberList,
Expand Down
122 changes: 122 additions & 0 deletions src/pages/GroupPage/GroupProfile/ImgModify/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import { ChangeEvent, useState } from 'react'
import { useForm } from 'react-hook-form'
import { BiError } from 'react-icons/bi'

import { Box, Input, useDisclosure } from '@chakra-ui/react'
import { zodResolver } from '@hookform/resolvers/zod'
import { useMutation } from '@tanstack/react-query'

import { queryClient } from '@/api/instance'
import {
ModifyGroupImgRequestBody,
modifyGroupImg,
} from '@/api/services/group/group.api'
import { Form, FormControl, FormField, FormItem } from '@/components/Form'
import { AlertModal } from '@/components/Modal/AlertModal'
import { ModifyGroupImageFields, ModifyGroupImageSchema } from '@/schema/group'
import { Group, GroupRole } from '@/types'

type ImgModifyProps = {
gprofile: Group
role: GroupRole
}

export default function ImgModify({ role, gprofile }: ImgModifyProps) {
const form = useForm<ModifyGroupImageFields>({
resolver: zodResolver(ModifyGroupImageSchema),
mode: 'onSubmit',
defaultValues: {
groupId: gprofile.groupId,
image: new File([], ''),
},
})

const [errorMessage, setErrorMessage] = useState('')
const errorModal = useDisclosure()

const { mutate: uploadImage } = useMutation({
mutationFn: (data: ModifyGroupImgRequestBody) => modifyGroupImg(data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['group', gprofile.groupId] })
},
})

const handleFileChange = (e: ChangeEvent<HTMLInputElement>) => {
const file = e.target.files ? e.target.files[0] : null

if (file) {
form.setValue('image', file)
form.handleSubmit(
() => {
uploadImage(form.getValues())
},
(errors) => {
const errorMessages =
Object.values(errors).flatMap((error) => error.message)[0] || ''

setErrorMessage(errorMessages)
errorModal.onOpen()
}
)()
}
}

if (role === 'MEMBER') {
return (
<Box
width="70px"
height="70px"
sx={{ border: '0.8px solid', borderColor: 'black.300' }}
backgroundImage={`url('${gprofile.groupImageUrl}')`}
backgroundSize="cover"
backgroundPosition="center"
borderRadius="100%"
/>
)
}

return (
<Box
width="70px"
height="70px"
sx={{ border: '0.8px solid', borderColor: 'black.300' }}
_hover={{ opacity: 0.5 }}
cursor="pointer"
onClick={() => document.getElementById('fileInput')?.click()}
backgroundImage={`url('${gprofile.groupImageUrl}')`}
backgroundSize="cover"
backgroundPosition="center"
borderRadius="100%"
>
<Form {...form}>
<form>
<FormField
control={form.control}
name="image"
render={() => (
<FormItem>
<FormControl>
<Input
type="file"
id="fileInput"
accept=".jpg, .jpeg, .png"
display="none"
multiple={false}
onChange={handleFileChange}
/>
</FormControl>
</FormItem>
)}
/>
</form>
</Form>
<AlertModal
isOpen={errorModal.isOpen}
onClose={errorModal.onClose}
icon={<BiError />}
title={errorMessage}
description=""
/>
</Box>
)
}
24 changes: 24 additions & 0 deletions src/pages/GroupPage/GroupProfile/ModifySuccessModal/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { BiCheckCircle } from 'react-icons/bi'

import { AlertModal } from '@/components/Modal/AlertModal'
import { Modal } from '@/types'

interface ModifySuccessModalProps {
successModal: Modal
}

export const ModifySuccessModal = ({
successModal,
}: ModifySuccessModalProps) => {
return (
<AlertModal
isOpen={successModal.isOpen}
onClose={() => {
successModal.onClose()
}}
icon={<BiCheckCircle />}
title="그룹 이름과 소개를 수정하였습니다"
description=""
/>
)
}
Loading

0 comments on commit 43bac95

Please sign in to comment.