-
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 5 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
| 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 |
|---|---|---|
| @@ -1,18 +1,14 @@ | ||
| '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 { useCreateCrewQuery } from '@/src/_queries/crew/crew-detail-queries'; | ||
| import CreateCrewForm from '@/src/app/(crew)/crew/_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 initialValue: CreateCrewFormTypes = { | ||
| title: '', | ||
| mainCategory: '', | ||
|
|
@@ -23,36 +19,24 @@ export default function CreateCrewPage() { | |
| totalCount: 4, | ||
| 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 ?? '', | ||
| totalCount: createdData.totalCount, | ||
| introduce: createdData.introduce, | ||
| }; | ||
|
|
||
|
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. 이미지 업로드 실패에 대한 에러 처리가 필요합니다. getImageUrl 호출 시 발생할 수 있는 에러에 대한 처리가 없습니다. 이미지 업로드 실패 시 사용자에게 적절한 피드백을 제공해야 합니다. 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;
+ try {
+ const imgResponse = await getImageUrl(createdData.imageUrl, 'CREW');
+ if (!imgResponse?.imageUrl) {
+ throw new Error('이미지 URL을 받아오지 못했습니다.');
+ }
+ newImageUrl = imgResponse.imageUrl;
+ } catch (error) {
+ console.error('이미지 업로드 실패:', error);
+ // TODO: 에러 처리 로직 추가 (예: 토스트 메시지 표시)
+ return;
+ }
}
|
||
| mutate(newData); | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -2,34 +2,46 @@ | |||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| import Image from 'next/image'; | ||||||||||||||||||||||||
| import { useParams } from 'next/navigation'; | ||||||||||||||||||||||||
| import { useGetCrewDetailQuery } from '@/src/_queries/crew/crew-detail-queries'; | ||||||||||||||||||||||||
| import { Loader } from '@mantine/core'; | ||||||||||||||||||||||||
| import { getImageUrl } from '@/src/_apis/image/get-image-url'; | ||||||||||||||||||||||||
| import { useEditCrewQuery, useGetCrewDetailQuery } from '@/src/_queries/crew/crew-detail-queries'; | ||||||||||||||||||||||||
| import CreateCrewForm from '@/src/app/(crew)/crew/_components/create-crew-form'; | ||||||||||||||||||||||||
| import { CreateCrewFormTypes } from '@/src/types/create-crew'; | ||||||||||||||||||||||||
| import { CreateCrewFormTypes, EditCrewRequestTypes } from '@/src/types/create-crew'; | ||||||||||||||||||||||||
| import IcoCreateCrew from '@/public/assets/icons/ic-create-crew.svg'; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| export default function EditCrewPage() { | ||||||||||||||||||||||||
| const { id } = useParams(); | ||||||||||||||||||||||||
| const { data, isLoading, error } = useGetCrewDetailQuery(Number(id)); | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| const { isPending, isSuccess, mutate } = useEditCrewQuery(Number(id)); | ||||||||||||||||||||||||
| if (data === undefined) return null; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| // TODO : 테스트중 | ||||||||||||||||||||||||
| const initialValue: CreateCrewFormTypes = { | ||||||||||||||||||||||||
| title: '', | ||||||||||||||||||||||||
| mainCategory: '', | ||||||||||||||||||||||||
| subCategory: '', | ||||||||||||||||||||||||
| imageUrl: | ||||||||||||||||||||||||
| 'https://crewcrew.s3.ap-northeast-2.amazonaws.com/crew/0e05d971-15a8-4a32-bf03-80d12cae392e', | ||||||||||||||||||||||||
| mainLocation: '', | ||||||||||||||||||||||||
| subLocation: '', | ||||||||||||||||||||||||
| totalCount: 4, | ||||||||||||||||||||||||
| introduce: '', | ||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||
| const handleEdit = async (editedData: CreateCrewFormTypes) => { | ||||||||||||||||||||||||
| let newImageUrl = editedData.imageUrl as string; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| const handleEdit = () => { | ||||||||||||||||||||||||
| // TODO : PATCH API 연결 | ||||||||||||||||||||||||
| if (editedData.imageUrl instanceof File && newImageUrl !== data.imageUrl) { | ||||||||||||||||||||||||
| const imgResponse = await getImageUrl(editedData.imageUrl, 'CREW'); | ||||||||||||||||||||||||
| newImageUrl = imgResponse?.imageUrl as string; | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
| if (editedData.imageUrl instanceof File && newImageUrl !== data.imageUrl) { | |
| const imgResponse = await getImageUrl(editedData.imageUrl, 'CREW'); | |
| newImageUrl = imgResponse?.imageUrl as string; | |
| } | |
| if (editedData.imageUrl instanceof File && newImageUrl !== data.imageUrl) { | |
| const imgResponse = await getImageUrl(editedData.imageUrl, 'CREW'); | |
| if (!imgResponse?.imageUrl) { | |
| throw new Error('이미지 업로드에 실패했습니다.'); | |
| } | |
| newImageUrl = imgResponse?.imageUrl as string; | |
| } |
Outdated
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.
💡 Codebase verification
타입 캐스팅이 안전하지 않습니다.
CreateCrewFormProps 인터페이스에 따르면 data prop은 CreateCrewFormTypes | EditCrewResponseTypes 타입을 받도록 정의되어 있는데, EditCrewRequestTypes로 캐스팅하는 것은 타입 안전성을 해칠 수 있습니다.
src/app/(crew)/crew/_components/create-crew-form/index.tsx에서 정의된 인터페이스와 일치하도록 타입을 수정해야 합니다.- 타입 캐스팅 대신
EditCrewResponseTypes타입으로 데이터를 변환하는 로직을 추가하거나, 컴포넌트의 타입 정의를 수정해야 합니다.
🔗 Analysis chain
타입 캐스팅의 안전성 검증이 필요합니다.
data를 EditCrewRequestTypes로 캐스팅하기 전에 타입 가드를 사용하여 안전성을 보장하는 것이 좋습니다.
다음 스크립트로 CreateCrewForm 컴포넌트의 props 타입을 확인해보세요:
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# CreateCrewForm 컴포넌트의 props 타입 정의 확인
ast-grep --pattern 'interface $interface {
$$$
data?: $type;
$$$
}'
# EditCrewRequestTypes 타입 정의 확인
ast-grep --pattern 'type EditCrewRequestTypes = {
$$$
}'
Length of output: 136
Script:
#!/bin/bash
# Search for CreateCrewForm component props interface
rg -A 10 "interface.*CreateCrewFormProps"
# Search for EditCrewRequestTypes type definition
rg -A 10 "type EditCrewRequestTypes"
# Search for CreateCrewForm component implementation
ast-grep --pattern 'const CreateCrewForm = ($props: $type) => {
$$$
}'
Length of output: 1122
Script:
#!/bin/bash
# Search for EditCrewResponseTypes and CreateCrewFormTypes definitions
rg -A 5 "type (EditCrewResponseTypes|CreateCrewFormTypes)"
# Search for actual usage of data prop in CreateCrewForm
ast-grep --pattern 'const { data, $$ } = $props'
# Search for type validation or guards
rg -A 3 "data as|is.*data"
Length of output: 12144
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -6,22 +6,22 @@ import { useInfiniteScroll } from '@/src/hooks/use-infinite-scroll'; | |||||||||||||||||||||||||
| import CrewCardList from '@/src/components/common/crew-list/crew-card-list'; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| export default function MyCrewJoinedPage() { | ||||||||||||||||||||||||||
| const { data, status, ref, isFetchingNextPage } = useInfiniteScroll( | ||||||||||||||||||||||||||
| const { data, isLoading, error, ref, isFetchingNextPage } = useInfiniteScroll( | ||||||||||||||||||||||||||
| useGetMyCrewJoinedQuery({ | ||||||||||||||||||||||||||
| pageable: { page: 0, size: 6, sort: ['createdAt,desc'] }, | ||||||||||||||||||||||||||
| }), | ||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||
| <div> | ||||||||||||||||||||||||||
| <CrewCardList inWhere="my-crew" data={data ?? { pages: [], pageParams: [] }} /> | ||||||||||||||||||||||||||
| {status === 'pending' || isFetchingNextPage ? ( | ||||||||||||||||||||||||||
| {isLoading || isFetchingNextPage ? ( | ||||||||||||||||||||||||||
| <div className="flex justify-center py-10"> | ||||||||||||||||||||||||||
| <Loader size="sm" /> | ||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||
| ) : ( | ||||||||||||||||||||||||||
| <div ref={ref} className="h-[1px]" /> | ||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||
| {status === 'error' && <p className="py-10 text-center">에러가 발생했습니다.</p>} | ||||||||||||||||||||||||||
| {error && <p className="py-10 text-center">에러가 발생했습니다.</p>} | ||||||||||||||||||||||||||
|
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 에러 처리를 더 상세하게 개선하면 좋겠습니다. 현재는 일반적인 에러 메시지만 표시되고 있습니다. 사용자에게 더 구체적인 정보와 해결 방법을 제공하면 좋을 것 같습니다. 다음과 같이 개선해보세요: - {error && <p className="py-10 text-center">에러가 발생했습니다.</p>}
+ {error && (
+ <div className="py-10 text-center">
+ <p className="text-red-500 mb-2">데이터를 불러오는 중 문제가 발생했습니다.</p>
+ <button
+ onClick={() => window.location.reload()}
+ className="text-blue-500 underline"
+ >
+ 다시 시도하기
+ </button>
+ </div>
+ )}📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -35,7 +35,7 @@ export default function HomePage() { | |
| } | ||
| }; | ||
|
|
||
| const { data, status, isFetchingNextPage, ref } = useInfiniteScroll( | ||
| const { data, isLoading, error, isFetchingNextPage, ref } = useInfiniteScroll( | ||
| useGetCrewListQuery({ | ||
| condition: { | ||
| keyword: search, | ||
|
|
@@ -133,14 +133,14 @@ export default function HomePage() { | |
| </div> | ||
| <div className="mt-8 px-3 md:px-8 lg:px-11.5"> | ||
| {data && <CrewCardList data={data} />} | ||
| {status === 'pending' || isFetchingNextPage ? ( | ||
| {isLoading || isFetchingNextPage ? ( | ||
| <div className="flex justify-center py-10"> | ||
| <Loader size="sm" /> | ||
| </div> | ||
| ) : ( | ||
| <div ref={ref} className="h-[1px]" /> | ||
| )} | ||
| {status === 'error' && <p className="py-10 text-center">에러가 발생했습니다.</p>} | ||
| {error && <p className="py-10 text-center">에러가 발생했습니다.</p>} | ||
|
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 에러 처리를 더 자세하게 개선해 주세요. 현재 에러 메시지가 너무 단순합니다. 사용자에게 더 구체적인 정보와 문제 해결 방법을 제공하면 좋겠습니다. - {error && <p className="py-10 text-center">에러가 발생했습니다.</p>}
+ {error && (
+ <div className="flex flex-col items-center py-10 text-center">
+ <p className="mb-2 text-red-500">데이터를 불러오는 중 문제가 발생했습니다.</p>
+ <p className="mb-4 text-sm text-gray-600">잠시 후 다시 시도해 주세요.</p>
+ <Button
+ onClick={() => window.location.reload()}
+ className="btn-filled px-4 py-2 text-sm"
+ >
+ 새로고침
+ </Button>
+ </div>
+ )}
|
||
| </div> | ||
| </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)