-
Notifications
You must be signed in to change notification settings - Fork 3
Feat/128/edit crew api 연결 #129
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
Changes from 10 commits
a4642fe
8a65520
5cde8ef
6f5118d
8cc9bd6
9b8b2f8
0776981
f96de45
25be7cf
c58777e
062362b
cbe2519
60b6c03
9616d8b
5313fd4
be51721
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
| @@ -1,9 +1,53 @@ | ||||||||
| import { useQuery } from '@tanstack/react-query'; | ||||||||
| import { toast } from 'react-toastify'; | ||||||||
| import { useRouter } from 'next/navigation'; | ||||||||
| import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; | ||||||||
| import { createCrew, editCrew } from '@/src/_apis/crew/crew'; | ||||||||
| import { getCrewDetail } from '@/src/_apis/crew/crew-detail-apis'; | ||||||||
| import { CreateCrewRequestTypes, EditCrewRequestTypes } from '@/src/types/create-crew'; | ||||||||
|
|
||||||||
| export function useGetCrewDetailQuery(id: number) { | ||||||||
| return useQuery({ | ||||||||
| queryKey: ['crewDetail', id], | ||||||||
| queryFn: () => getCrewDetail(id), | ||||||||
| }); | ||||||||
| } | ||||||||
|
|
||||||||
| export function useCreateCrewQuery() { | ||||||||
| const router = useRouter(); | ||||||||
| const queryClient = useQueryClient(); | ||||||||
|
|
||||||||
| return useMutation({ | ||||||||
| mutationFn: (data: CreateCrewRequestTypes) => createCrew(data), | ||||||||
| onSuccess: (data) => { | ||||||||
| if (data === null || data === undefined) { | ||||||||
| return; | ||||||||
| } | ||||||||
| queryClient.invalidateQueries({ queryKey: ['crewLists', 'crewDetail'] }); | ||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 쿼리 무효화 시 올바른 사용법 적용
수정 제안: -queryClient.invalidateQueries({ queryKey: ['crewLists', 'crewDetail'] });
+queryClient.invalidateQueries(['crewLists']);
+queryClient.invalidateQueries(['crewDetail']);📝 Committable suggestion
Suggested change
|
||||||||
| toast.success('크루가 생성되었습니다.'); | ||||||||
| router.push(`/crew/detail/${data.crewId}`); | ||||||||
| }, | ||||||||
| onError: (error) => { | ||||||||
| toast.error(error.message); | ||||||||
| }, | ||||||||
| }); | ||||||||
| } | ||||||||
|
|
||||||||
| export function useEditCrewQuery(id: number) { | ||||||||
| const router = useRouter(); | ||||||||
| const queryClient = useQueryClient(); | ||||||||
|
|
||||||||
| return useMutation({ | ||||||||
| mutationFn: (data: EditCrewRequestTypes) => editCrew(id, data), | ||||||||
| onSuccess: () => { | ||||||||
| queryClient.invalidateQueries({ queryKey: ['crewDetail'] }); | ||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion 쿼리 무효화 시 정확한 키 사용 현재 수정 제안: -queryClient.invalidateQueries({ queryKey: ['crewDetail'] });
+queryClient.invalidateQueries(['crewDetail', id]);
|
||||||||
| toast.success('크루 정보가 수정되었습니다.'); | ||||||||
| if (router) { | ||||||||
| router.push(`/crew/detail/${id}`); | ||||||||
| } | ||||||||
| }, | ||||||||
| onError: (error) => { | ||||||||
| toast.error(error.message); | ||||||||
| }, | ||||||||
| retry: false, | ||||||||
| }); | ||||||||
| } | ||||||||
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
|
|
@@ -31,7 +31,7 @@ export default { | |||||||
| } as Meta<typeof CreateCrewForm>; | ||||||||
|
|
||||||||
| const Template: StoryFn<CreateCrewFormTypes> = function CreateCrewPageStory() { | ||||||||
| return <CreateCrewForm data={initialValue} />; | ||||||||
| return <CreateCrewForm data={initialValue} type="create" />; | ||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion 타입 안전성 개선이 필요합니다
다음과 같이 개선하는 것을 제안드립니다: - return <CreateCrewForm data={initialValue} type="create" />;
+ const formType = 'create' as const;
+ return <CreateCrewForm data={initialValue} type={formType} />;📝 Committable suggestion
Suggested change
|
||||||||
| }; | ||||||||
|
|
||||||||
| export const Default = Template.bind({}); | ||||||||
|
|
||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,6 +2,7 @@ | |
|
|
||
| import { useEffect, useState } from 'react'; | ||
| import { Controller, useForm, useWatch } from 'react-hook-form'; | ||
| import useFormPersist from 'react-hook-form-persist'; | ||
| import { useRouter } from 'next/navigation'; | ||
| import { NumberInput } from '@mantine/core'; | ||
| import categoryData from '@/src/data/category.json'; | ||
|
|
@@ -11,18 +12,19 @@ import DropDown from '@/src/components/common/input/drop-down'; | |
| import FileInputWrap from '@/src/components/common/input/file-input-wrap'; | ||
| import TextInput from '@/src/components/common/input/text-input'; | ||
| import Textarea from '@/src/components/common/input/textarea'; | ||
| import { CreateCrewFormTypes } from '@/src/types/create-crew'; | ||
| import { CreateCrewFormTypes, EditCrewResponseTypes } from '@/src/types/create-crew'; | ||
| import ImgCrewSampleUrls from '@/public/assets/images/crew-sample'; | ||
|
|
||
| export interface CreateCrewFormProps { | ||
| data: CreateCrewFormTypes; | ||
| type: 'create' | 'edit'; | ||
| data: CreateCrewFormTypes | EditCrewResponseTypes; | ||
|
Comment on lines
+20
to
+21
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion 타입 정의가 개선되었습니다.
다음과 같은 개선을 제안드립니다: export interface CreateCrewFormProps {
type: 'create' | 'edit';
data: CreateCrewFormTypes | EditCrewResponseTypes;
- isEdit?: boolean; // 제거
onEdit?: (data: CreateCrewFormTypes) => void;
onSubmit?: (data: CreateCrewFormTypes) => void;
}Also applies to: 27-27 |
||
| isEdit?: boolean; | ||
| onEdit?: (data: CreateCrewFormTypes) => void; | ||
| onSubmit?: (data: CreateCrewFormTypes) => void; | ||
| } | ||
|
|
||
| export default function CreateCrewForm({ | ||
| isEdit = false, | ||
| type, | ||
| onEdit = () => {}, | ||
| onSubmit = () => {}, | ||
| data, | ||
|
|
@@ -34,18 +36,26 @@ export default function CreateCrewForm({ | |
| setValue, | ||
| trigger, | ||
| clearErrors, | ||
| watch, | ||
| formState: { errors, isValid, isSubmitting }, | ||
| } = useForm<CreateCrewFormTypes>({ | ||
| defaultValues: data, | ||
| mode: 'onBlur', | ||
| }); | ||
|
|
||
| useFormPersist('createCrew', { | ||
| watch, | ||
| setValue, | ||
| storage: window.localStorage, | ||
| }); | ||
|
|
||
| const [categoryIndex, setCategoryIndex] = useState(0); | ||
| const [regionIndex, setRegionIndex] = useState(0); | ||
|
|
||
| const title = useWatch({ control, name: 'title' }); | ||
| const mainCategory = useWatch({ control, name: 'mainCategory' }); | ||
| const mainLocation = useWatch({ control, name: 'mainLocation' }); | ||
| const subLocation = useWatch({ control, name: 'subLocation' }); | ||
| const introduce = useWatch({ control, name: 'introduce' }); | ||
|
|
||
| const handleMainCategoryChange = (newValue: string | null) => { | ||
|
|
@@ -62,10 +72,13 @@ export default function CreateCrewForm({ | |
| useEffect(() => { | ||
| setCategoryIndex(categoryData.findIndex((category) => category.title.label === mainCategory)); | ||
| setRegionIndex(regionData.findIndex((region) => region.main.label === mainLocation)); | ||
| if (subLocation === '') { | ||
| setValue('subLocation', '전체'); | ||
| } | ||
| }, [mainCategory, mainLocation]); | ||
|
|
||
| return ( | ||
| <form onSubmit={isEdit ? handleSubmit(onEdit) : handleSubmit(onSubmit)}> | ||
| <form onSubmit={type === 'edit' ? handleSubmit(onEdit) : handleSubmit(onSubmit)}> | ||
| <div className="flex flex-col gap-6"> | ||
| <div className="flex flex-col gap-3"> | ||
| <div className="flex justify-between"> | ||
|
|
@@ -105,7 +118,6 @@ export default function CreateCrewForm({ | |
| )} | ||
| /> | ||
| </div> | ||
|
|
||
| <div className="flex flex-col gap-3"> | ||
| <label | ||
| htmlFor="crew-category" | ||
|
|
@@ -123,7 +135,7 @@ export default function CreateCrewForm({ | |
| {...field} | ||
| variant="default" | ||
| inWhere="form" | ||
| placeholder="메인 카테고리" | ||
| placeholder={type === 'edit' ? field.value : '메인 카테고리'} | ||
| data={categoryData.map((category) => category.title)} | ||
| className="flex-1" | ||
| onChange={(value) => { | ||
|
|
@@ -142,7 +154,7 @@ export default function CreateCrewForm({ | |
| {...field} | ||
| variant="default" | ||
| inWhere="form" | ||
| placeholder="세부 카테고리" | ||
| placeholder={type === 'edit' && field.value ? field.value : '세부 카테고리'} | ||
| data={categoryData[categoryIndex]?.items || []} | ||
| className="flex-1" | ||
| error={errors.subCategory?.message} | ||
|
|
@@ -176,7 +188,7 @@ export default function CreateCrewForm({ | |
| render={({ field }) => ( | ||
| <FileInputWrap | ||
| {...field} | ||
| isEdit={isEdit} | ||
| isEdit={type === 'edit'} | ||
| sample={ImgCrewSampleUrls} | ||
| onChange={(newValue) => { | ||
| field.onChange(newValue); | ||
|
|
@@ -205,7 +217,7 @@ export default function CreateCrewForm({ | |
| {...field} | ||
| variant="default" | ||
| inWhere="form" | ||
| placeholder="특별시/도" | ||
| placeholder={type === 'edit' ? field.value : '특별시/도'} | ||
| data={regionData.map((region) => region.main)} | ||
| className="flex-1" | ||
| onChange={(value) => { | ||
|
|
@@ -225,7 +237,7 @@ export default function CreateCrewForm({ | |
| {...field} | ||
| variant="default" | ||
| inWhere="form" | ||
| placeholder="시/군/구" | ||
| placeholder={type === 'edit' && field.value ? field.value : '시/군/구'} | ||
| data={regionData[regionIndex]?.areas || []} | ||
| className="flex-1" | ||
| error={errors.subLocation?.message} | ||
|
|
@@ -271,7 +283,7 @@ export default function CreateCrewForm({ | |
| 크루 소개 | ||
| </label> | ||
| <span> | ||
| <span className="text-blue-500">{introduce.length}</span>/100 | ||
| <span className="text-blue-500">{introduce?.length}</span>/100 | ||
| </span> | ||
| </div> | ||
| <Controller | ||
|
|
@@ -293,7 +305,7 @@ export default function CreateCrewForm({ | |
| disabled={!isValid || isSubmitting} | ||
| className="btn-filled h-11 flex-1 text-base font-medium disabled:bg-gray-200" | ||
| > | ||
| {isEdit ? '수정' : '확인'} | ||
| {type === 'create' ? '만들기' : '수정'} | ||
| </Button> | ||
| <Button | ||
| type="button" | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,58 +1,43 @@ | ||
| 'use client'; | ||
|
|
||
| import { toast } from 'react-toastify'; | ||
| import Image from 'next/image'; | ||
| import { useRouter } from 'next/navigation'; | ||
| import { Loader } from '@mantine/core'; | ||
| import { useMutation, useQueryClient } from '@tanstack/react-query'; | ||
| import { createCrew } from '@/src/_apis/crew/crew'; | ||
| import { getImageUrl } from '@/src/_apis/image/get-image-url'; | ||
| import CreateCrewForm from '@/src/app/(crew)/crew/_components/create-crew-form'; | ||
| import { useCreateCrewQuery } from '@/src/_queries/crew/crew-detail-queries'; | ||
| import CreateCrewForm from '@/src/app/(crew)/crew/create/_components/create-crew-form'; | ||
| import { CreateCrewFormTypes, CreateCrewRequestTypes } from '@/src/types/create-crew'; | ||
| import IcoCreateCrew from '@/public/assets/icons/ic-create-crew.svg'; | ||
|
|
||
| export default function CreateCrewPage() { | ||
| const router = useRouter(); | ||
| const savedInfo = JSON.parse(localStorage.getItem('createCrew') ?? ''); | ||
| const initialValue: CreateCrewFormTypes = { | ||
| title: '', | ||
| mainCategory: '', | ||
| subCategory: null, | ||
| imageUrl: null, | ||
| mainLocation: '', | ||
| subLocation: null, | ||
| totalCount: 4, | ||
| introduce: '', | ||
| title: savedInfo?.title, | ||
| mainCategory: savedInfo?.mainCategory, | ||
| subCategory: savedInfo?.subCategory, | ||
| imageUrl: savedInfo?.imageUrl, | ||
| mainLocation: savedInfo?.mainLocation, | ||
| subLocation: savedInfo?.subLocation, | ||
| totalCount: savedInfo?.totalCount, | ||
| introduce: savedInfo?.introduce, | ||
| }; | ||
| const queryClient = useQueryClient(); | ||
| const { isPending, mutate } = useMutation({ | ||
| mutationFn: (data: CreateCrewRequestTypes) => createCrew(data), | ||
| onSuccess: (data) => { | ||
| if (data === null || data === undefined) { | ||
| return; | ||
| } | ||
| queryClient.invalidateQueries({ queryKey: ['crewLists', 'crewDetail'] }); | ||
| router.push(`/crew/detail/${data.crewId}`); | ||
| }, | ||
| onError: (error) => { | ||
| toast.error(error.message); | ||
| }, | ||
| }); | ||
|
|
||
| const handleSubmit = async (data: CreateCrewFormTypes) => { | ||
| let newImageUrl = data.imageUrl as string; | ||
| if (data.imageUrl instanceof File) { | ||
| const imgResponse = await getImageUrl(data.imageUrl, 'CREW'); | ||
| const { isPending, mutate } = useCreateCrewQuery(); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Codebase verification 에러 처리가 누락되어 있습니다 useCreateCrewQuery 훅의 구현을 확인한 결과, onError 핸들러가 구현되어 있지 않습니다. 이는 다음과 같은 문제를 초래할 수 있습니다:
수정 제안:
🔗 Analysis chain에러 처리 검증이 필요합니다. useCreateCrewQuery 훅에서 에러 처리가 적절히 구현되어 있는지 확인이 필요합니다. 🏁 Scripts executedThe following scripts were executed for the analysis: Script: #!/bin/bash
# Description: Verify error handling implementation in useCreateCrewQuery hook
# Check error handling in the query hook
ast-grep --pattern 'export function useCreateCrewQuery() {
$$$
onError($$$) {
$$$
}
$$$
}'
Length of output: 328 Script: #!/bin/bash
# Let's first find the hook file
fd "useCreateCrewQuery" --type f
# Then examine its content
rg "useCreateCrewQuery" -A 10 -B 10
Length of output: 4026 |
||
|
|
||
| const handleSubmit = async (createdData: CreateCrewFormTypes) => { | ||
| let newImageUrl = createdData.imageUrl as string; | ||
| if (createdData.imageUrl instanceof File) { | ||
| const imgResponse = await getImageUrl(createdData.imageUrl, 'CREW'); | ||
| newImageUrl = imgResponse?.imageUrl as string; | ||
| } | ||
| const newData: CreateCrewRequestTypes = { | ||
| title: data.title, | ||
| mainCategory: data.mainCategory, | ||
| subCategory: data.subCategory ?? '', | ||
| title: createdData.title, | ||
| mainCategory: createdData.mainCategory, | ||
| subCategory: createdData.subCategory ?? '', | ||
| imageUrl: newImageUrl ?? '', | ||
| mainLocation: data.mainLocation, | ||
| subLocation: data.subLocation ?? '', | ||
| totalCount: data.totalCount, | ||
| introduce: data.introduce, | ||
| mainLocation: createdData.mainLocation, | ||
| subLocation: createdData.subLocation === '전체' ? '' : (createdData.subLocation ?? ''), | ||
| totalCount: createdData.totalCount, | ||
| introduce: createdData.introduce, | ||
| }; | ||
|
|
||
| mutate(newData); | ||
|
|
@@ -78,7 +63,7 @@ export default function CreateCrewPage() { | |
| </figure> | ||
| <h2 className="text-2xl font-bold text-gray-900 md:text-3.5xl">크루 만들기</h2> | ||
| </div> | ||
| <CreateCrewForm data={initialValue} onSubmit={handleSubmit} /> | ||
| <CreateCrewForm data={initialValue} onSubmit={handleSubmit} type="create" /> | ||
| </div> | ||
| ); | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
editCrew 함수의 구현을 개선해야 합니다.
다음과 같은 중요한 개선사항들이 필요합니다:
다음과 같이 수정하는 것을 제안드립니다:
🧰 Tools
🪛 Biome
[error] 40-40: The catch clause that only rethrows the original error is useless.
An unnecessary catch clause can be confusing.
Unsafe fix: Remove the try/catch clause.
(lint/complexity/noUselessCatch)