-
Notifications
You must be signed in to change notification settings - Fork 3
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
FAQ 페이지 CRUD 적용 #228
base: main
Are you sure you want to change the base?
FAQ 페이지 CRUD 적용 #228
Conversation
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
Walkthrough이 풀 리퀘스트는 FAQ(자주 묻는 질문) 관리 기능을 구현하기 위한 포괄적인 변경 사항을 포함하고 있습니다. 주요 변경 사항은 FAQ 데이터를 가져오고, 생성하고, 업데이트하고, 삭제할 수 있는 API 엔드포인트와 관련 훅, 컴포넌트를 추가하는 것입니다. 이를 통해 관리자가 FAQ 콘텐츠를 동적으로 관리할 수 있는 인터페이스를 제공합니다. Changes
Assessment against linked issues
Suggested labels
Suggested reviewers
✨ Finishing Touches
Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media? 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
Documentation and Community
|
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.
Actionable comments posted: 7
🧹 Nitpick comments (8)
src/hooks/api/faq/useAllFaq.ts (2)
7-9
: 에러 처리 개선이 필요합니다.콘솔 로그만으로는 사용자에게 에러 상황을 알리기 어렵습니다. 토스트 메시지나 에러 상태를 상위 컴포넌트에 전달하는 방식으로 개선하는 것이 좋겠습니다.
4-11
: 캐싱 전략과 재시도 로직 추가를 고려해보세요.데이터 갱신이 빈번하지 않은 FAQ의 특성을 고려하여, 적절한 캐싱 전략을 설정하면 성능을 개선할 수 있습니다. 또한 네트워크 오류 시 자동 재시도 로직을 추가하면 좋겠습니다.
export const useAllFaq = (token: string) => { return useQuery(['allFaqs'], () => getAllFaq(token), { enabled: !!token, + staleTime: 5 * 60 * 1000, // 5분 + cacheTime: 30 * 60 * 1000, // 30분 + retry: 3, onError: (error) => { console.error('FAQ 가져오기 실패', error); }, }); };src/types/faq.ts (1)
1-27
: 타입에 유효성 검사 제약 조건 추가를 고려해보세요.문자열 길이 제한이나 필수값 표시 등의 제약 조건을 추가하면 타입 안정성을 더욱 높일 수 있습니다.
export type FAQItem = { - question: string; - reply: string; + question: string & { minLength: 5; maxLength: 200 }; + reply: string & { minLength: 10; maxLength: 1000 }; };src/hooks/api/faq/useDeleteFaq.ts (1)
24-26
: 에러 처리 개선이 필요합니다.에러 메시지를 콘솔에만 출력하는 것보다 사용자에게 표시하는 것이 좋습니다.
onError: (error) => { - console.error('FAQ 삭제 실패:', error); + return error.response?.data?.message || 'FAQ 삭제에 실패했습니다.'; },src/components/faq/FAQActions.tsx (1)
20-45
: 버튼 스타일 중복 코드를 개선할 수 있습니다.취소, 저장하기, 수정하기 버튼에서 공통 스타일이 중복되어 있습니다. 이를 공통 컴포넌트로 추출하면 코드 재사용성이 향상됩니다.
+const ButtonBase = `h-10 rounded-lg px-4.5 py-2 text-sm font-bold`; +const CancelButton = `${ButtonBase} bg-gray-100 text-gray-500 hover:bg-gray-200`; +const SaveButton = `${ButtonBase} text-white bg-blue-500 hover:bg-blue-600`; +const EditButton = `${ButtonBase} bg-blue-100 text-blue-500 hover:bg-blue-200`; <button onClick={() => setIsEditing(false)} - className="ml-3 h-10 rounded-lg bg-gray-100 px-4.5 py-2 text-sm font-bold text-gray-500 hover:bg-gray-200" + className={`ml-3 ${CancelButton}`} >src/pages/admin/FAQ/index.tsx (1)
16-26
: FAQ 관리 로직을 커스텀 훅으로 분리하는 것이 좋습니다.상태 관리와 FAQ 추가 로직을 커스텀 훅으로 분리하면 코드의 재사용성과 테스트 용이성이 향상됩니다.
+// useFAQManagement.ts +export function useFAQManagement() { + const [isEditing, setIsEditing] = useState(false); + const [newFAQs, setNewFAQs] = useState<{ question: string; reply: string }[]>([]); + + const addFAQ = () => { + setNewFAQs([ + ...newFAQs, + { question: '질문을 입력해주세요', reply: '답변을 입력해주세요' }, + ]); + }; + + return { isEditing, setIsEditing, newFAQs, setNewFAQs, addFAQ }; +}src/components/faq/FAQList.tsx (2)
35-36
: 토큰 유효성 검사 추가 필요토큰을 쿠키에서 추출할 때 유효성 검사가 누락되어 있습니다.
다음과 같이 토큰 유효성 검사를 추가해주세요:
const [cookies] = useCookies(['token', 'role']); const { token } = cookies; + if (!token) { + toast.error('인증 정보가 없습니다.'); + return null; + }
78-91
: 접근성 속성 추가 필요입력 필드에 접근성 속성이 누락되어 있어 스크린 리더 사용자의 접근성이 제한됩니다.
다음과 같이 접근성 속성을 추가해주세요:
<input className="w-11/12" placeholder={item.question} value={item.question} + aria-label="FAQ 질문" + id={`faq-question-${index}`} onChange={(e) => setNewFAQs((prev) => prev.map((faq, i) => i === index ? { ...faq, question: e.target.value } : faq, ), ) } /> ... <textarea className="w-full resize-none rounded-md border border-gray-200 bg-gray-50 p-2 focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="답변을 입력해주세요" value={item.reply} rows={3} + aria-label="FAQ 답변" + id={`faq-reply-${index}`} onChange={(e) => setNewFAQs((prev) => prev.map((faq, i) => i === index ? { ...faq, reply: e.target.value } : faq, ), ) } />Also applies to: 102-114
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (11)
src/apis/index.ts
(2 hunks)src/components/faq/FAQActions.tsx
(1 hunks)src/components/faq/FAQList.tsx
(1 hunks)src/components/ui/accordion.tsx
(1 hunks)src/constants/qna.ts
(0 hunks)src/hooks/api/faq/useAllFaq.ts
(1 hunks)src/hooks/api/faq/useCreateFaq.ts
(1 hunks)src/hooks/api/faq/useDeleteFaq.ts
(1 hunks)src/hooks/api/faq/usePatchFaq.ts
(1 hunks)src/pages/admin/FAQ/index.tsx
(1 hunks)src/types/faq.ts
(1 hunks)
💤 Files with no reviewable changes (1)
- src/constants/qna.ts
🔇 Additional comments (2)
src/components/ui/accordion.tsx (1)
23-44
: 구현이 깔끔하고 잘 되어있습니다.
- isArrow prop의 기본값 설정이 적절합니다.
- 조건부 렌더링이 명확하게 구현되어 있습니다.
src/apis/index.ts (1)
643-689
: 구현이 잘 되었습니다!FAQ 관련 API 함수들이 잘 구현되어 있습니다:
- 적절한 타입 정의
- 일관된 토큰 처리
- 에러 처리 구현
export const useCreateFaq = () => { | ||
return useMutation(createFaq, { | ||
onSuccess: () => { | ||
console.log('FAQ 생성성공'); | ||
}, | ||
onError: (error) => { | ||
console.error('FAQ 생성실패', error); | ||
}, | ||
}); | ||
}; |
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.
🛠️ Refactor suggestion
쿼리 무효화 및 사용자 피드백 개선이 필요합니다.
FAQ 생성 후 자동으로 목록을 갱신하고 사용자에게 적절한 피드백을 제공하도록 개선이 필요합니다.
+import { useQueryClient } from '@tanstack/react-query';
+import { toast } from 'react-toastify';
export const useCreateFaq = () => {
+ const queryClient = useQueryClient();
return useMutation(createFaq, {
onSuccess: () => {
- console.log('FAQ 생성성공');
+ queryClient.invalidateQueries(['allFaqs']);
+ toast.success('FAQ가 성공적으로 생성되었습니다.');
},
onError: (error) => {
- console.error('FAQ 생성실패', error);
+ toast.error('FAQ 생성에 실패했습니다. 다시 시도해주세요.');
},
});
};
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
export const useCreateFaq = () => { | |
return useMutation(createFaq, { | |
onSuccess: () => { | |
console.log('FAQ 생성성공'); | |
}, | |
onError: (error) => { | |
console.error('FAQ 생성실패', error); | |
}, | |
}); | |
}; | |
import { useQueryClient } from '@tanstack/react-query'; | |
import { toast } from 'react-toastify'; | |
export const useCreateFaq = () => { | |
const queryClient = useQueryClient(); | |
return useMutation(createFaq, { | |
onSuccess: () => { | |
queryClient.invalidateQueries(['allFaqs']); | |
toast.success('FAQ가 성공적으로 생성되었습니다.'); | |
}, | |
onError: (error) => { | |
toast.error('FAQ 생성에 실패했습니다. 다시 시도해주세요.'); | |
}, | |
}); | |
}; |
export const usePatchFaq = () => { | ||
return useMutation(updateFaq, { | ||
onSuccess: () => { | ||
console.log('FAQ 수정 성공'); | ||
}, | ||
onError: (error) => { | ||
console.error('FAQ 수정 실패', error); | ||
}, | ||
}); | ||
}; |
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.
🛠️ Refactor suggestion
쿼리 무효화 및 에러 처리 개선이 필요합니다.
FAQ 수정 후 목록 갱신과 사용자 피드백 처리가 필요합니다.
+import { useQueryClient } from '@tanstack/react-query';
+import { toast } from 'react-toastify';
export const usePatchFaq = () => {
+ const queryClient = useQueryClient();
return useMutation(updateFaq, {
onSuccess: () => {
- console.log('FAQ 수정 성공');
+ queryClient.invalidateQueries(['allFaqs']);
+ toast.success('FAQ가 성공적으로 수정되었습니다.');
},
onError: (error) => {
- console.error('FAQ 수정 실패', error);
+ toast.error('FAQ 수정에 실패했습니다. 다시 시도해주세요.');
},
});
};
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
export const usePatchFaq = () => { | |
return useMutation(updateFaq, { | |
onSuccess: () => { | |
console.log('FAQ 수정 성공'); | |
}, | |
onError: (error) => { | |
console.error('FAQ 수정 실패', error); | |
}, | |
}); | |
}; | |
import { useQueryClient } from '@tanstack/react-query'; | |
import { toast } from 'react-toastify'; | |
export const usePatchFaq = () => { | |
const queryClient = useQueryClient(); | |
return useMutation(updateFaq, { | |
onSuccess: () => { | |
queryClient.invalidateQueries(['allFaqs']); | |
toast.success('FAQ가 성공적으로 수정되었습니다.'); | |
}, | |
onError: (error) => { | |
toast.error('FAQ 수정에 실패했습니다. 다시 시도해주세요.'); | |
}, | |
}); | |
}; |
export type CreateFaq = { | ||
token: string; | ||
faqData: object; | ||
}; |
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.
CreateFaq 타입의 faqData를 구체화해야 합니다.
object
타입은 너무 일반적이며 타입 안정성을 보장하지 않습니다. FAQItem 타입을 재사용하는 것이 좋겠습니다.
export type CreateFaq = {
token: string;
- faqData: object;
+ faqData: FAQItem;
};
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
export type CreateFaq = { | |
token: string; | |
faqData: object; | |
}; | |
export type CreateFaq = { | |
token: string; | |
faqData: FAQItem; | |
}; |
<div className="flex items-end justify-between"> | ||
<Heading>FAQ 관리</Heading> | ||
{isEditing ? ( | ||
<div> | ||
<button | ||
onClick={() => setIsEditing(false)} | ||
className="ml-3 h-10 rounded-lg bg-gray-100 px-4.5 py-2 text-sm font-bold text-gray-500 hover:bg-gray-200" | ||
> | ||
취소 | ||
</button> | ||
<button | ||
onClick={saveFAQ} | ||
className={`ml-3 h-10 rounded-lg px-4.5 py-2 text-sm font-bold text-white | ||
${ | ||
isSaving | ||
? 'cursor-not-allowed bg-gray-500' | ||
: 'bg-blue-500 hover:bg-blue-600' | ||
}`} | ||
disabled={isSaving} | ||
> | ||
저장하기 | ||
</button> | ||
</div> | ||
) : ( | ||
<button | ||
onClick={() => setIsEditing(true)} | ||
className="ml-3 h-10 rounded-lg bg-blue-100 px-4.5 py-2 text-sm font-bold text-blue-500 hover:bg-blue-200" | ||
> | ||
수정하기 | ||
</button> | ||
)} |
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.
🛠️ Refactor suggestion
버튼 관련 코드를 FAQActions 컴포넌트로 이동하는 것이 좋습니다.
현재 버튼 관련 코드가 FAQActions 컴포넌트와 중복되어 있습니다. 이를 FAQActions 컴포넌트로 이동하여 코드 중복을 제거하고 재사용성을 높일 수 있습니다.
const saveFAQ = async () => { | ||
if (newFAQs.length === 0) return; | ||
|
||
try { | ||
await Promise.all( | ||
newFAQs.map((faq) => | ||
createFaq( | ||
{ token, ...faq }, | ||
{ | ||
onSuccess: () => { | ||
refetch(); | ||
setNewFAQs([]); | ||
toast.success('FAQ가 성공적으로 저장되었습니다'); | ||
}, | ||
}, | ||
), | ||
), | ||
); | ||
} catch (error) { | ||
toast.error('FAQ 저장에 실패하였습니다'); | ||
} | ||
}; |
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.
🛠️ Refactor suggestion
saveFAQ 함수의 에러 처리를 개선해야 합니다.
현재 구현에서는 일반적인 에러 메시지만 표시됩니다. 구체적인 에러 정보를 제공하면 사용자가 문제를 더 잘 이해할 수 있습니다.
} catch (error) {
- toast.error('FAQ 저장에 실패하였습니다');
+ if (error.response?.status === 400) {
+ toast.error('입력된 내용을 확인해주세요');
+ } else if (error.response?.status === 401) {
+ toast.error('권한이 없습니다');
+ } else {
+ toast.error('FAQ 저장에 실패하였습니다. 잠시 후 다시 시도해주세요');
+ }
}
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
const saveFAQ = async () => { | |
if (newFAQs.length === 0) return; | |
try { | |
await Promise.all( | |
newFAQs.map((faq) => | |
createFaq( | |
{ token, ...faq }, | |
{ | |
onSuccess: () => { | |
refetch(); | |
setNewFAQs([]); | |
toast.success('FAQ가 성공적으로 저장되었습니다'); | |
}, | |
}, | |
), | |
), | |
); | |
} catch (error) { | |
toast.error('FAQ 저장에 실패하였습니다'); | |
} | |
}; | |
const saveFAQ = async () => { | |
if (newFAQs.length === 0) return; | |
try { | |
await Promise.all( | |
newFAQs.map((faq) => | |
createFaq( | |
{ token, ...faq }, | |
{ | |
onSuccess: () => { | |
refetch(); | |
setNewFAQs([]); | |
toast.success('FAQ가 성공적으로 저장되었습니다'); | |
}, | |
}, | |
), | |
), | |
); | |
} catch (error) { | |
if (error.response?.status === 400) { | |
toast.error('입력된 내용을 확인해주세요'); | |
} else if (error.response?.status === 401) { | |
toast.error('권한이 없습니다'); | |
} else { | |
toast.error('FAQ 저장에 실패하였습니다. 잠시 후 다시 시도해주세요'); | |
} | |
} | |
}; |
const isClickedDeleteButton = (questionId?: number) => { | ||
if (!questionId || !token) { | ||
toast.error('인증 정보가 없습니다.'); | ||
return; | ||
} | ||
|
||
deleteFaq( | ||
{ questionId, token }, | ||
{ | ||
onSuccess: () => { | ||
refetch(); | ||
toast.success('FAQ가 삭제되었습니다.'); | ||
}, | ||
onError: () => { | ||
toast.error('FAQ 삭제에 실패했습니다.'); | ||
}, | ||
}, | ||
); | ||
}; |
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.
🛠️ Refactor suggestion
삭제 작업 전 확인 대화상자 추가 필요
FAQ 삭제 시 사용자 확인 없이 바로 삭제가 진행됩니다. 실수로 인한 삭제를 방지하기 위해 확인 절차가 필요합니다.
다음과 같이 확인 대화상자를 추가해주세요:
const isClickedDeleteButton = (questionId?: number) => {
if (!questionId || !token) {
toast.error('인증 정보가 없습니다.');
return;
}
+ if (!window.confirm('정말로 이 FAQ를 삭제하시겠습니까?')) {
+ return;
+ }
deleteFaq(
{ questionId, token },
{
onSuccess: () => {
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
const isClickedDeleteButton = (questionId?: number) => { | |
if (!questionId || !token) { | |
toast.error('인증 정보가 없습니다.'); | |
return; | |
} | |
deleteFaq( | |
{ questionId, token }, | |
{ | |
onSuccess: () => { | |
refetch(); | |
toast.success('FAQ가 삭제되었습니다.'); | |
}, | |
onError: () => { | |
toast.error('FAQ 삭제에 실패했습니다.'); | |
}, | |
}, | |
); | |
}; | |
const isClickedDeleteButton = (questionId?: number) => { | |
if (!questionId || !token) { | |
toast.error('인증 정보가 없습니다.'); | |
return; | |
} | |
if (!window.confirm('정말로 이 FAQ를 삭제하시겠습니까?')) { | |
return; | |
} | |
deleteFaq( | |
{ questionId, token }, | |
{ | |
onSuccess: () => { | |
refetch(); | |
toast.success('FAQ가 삭제되었습니다.'); | |
}, | |
onError: () => { | |
toast.error('FAQ 삭제에 실패했습니다.'); | |
}, | |
}, | |
); | |
}; |
interface FAQListProps { | ||
FAQ: any; | ||
newFAQs: FAQItem[]; | ||
setNewFAQs: React.Dispatch<React.SetStateAction<FAQItem[]>>; | ||
isEditing: boolean; | ||
refetch: () => void; | ||
} |
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.
🛠️ Refactor suggestion
FAQ 타입 안전성 개선 필요
FAQ
prop의 타입이 any
로 지정되어 있어 타입 안전성이 보장되지 않습니다.
다음과 같이 명시적인 타입을 지정해주세요:
interface FAQListProps {
- FAQ: any;
+ FAQ: { data: FAQItem[] } | undefined;
newFAQs: FAQItem[];
setNewFAQs: React.Dispatch<React.SetStateAction<FAQItem[]>>;
isEditing: boolean;
refetch: () => void;
}
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
interface FAQListProps { | |
FAQ: any; | |
newFAQs: FAQItem[]; | |
setNewFAQs: React.Dispatch<React.SetStateAction<FAQItem[]>>; | |
isEditing: boolean; | |
refetch: () => void; | |
} | |
interface FAQListProps { | |
FAQ: { data: FAQItem[] } | undefined; | |
newFAQs: FAQItem[]; | |
setNewFAQs: React.Dispatch<React.SetStateAction<FAQItem[]>>; | |
isEditing: boolean; | |
refetch: () => void; | |
} |
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.
수고 많으셨어요 👍
아래 리스트는 확인이 필요해 보여요 🙇♂️
- 수정 기능
- 수정 취소 -> 수정버튼 재클릭시, 이전 수정사항이 남아있는 부분(초기화 동작x)
- 입력이 활성화되지 않는 부분
- 일반 유저 화면에서는 데이터가 노출 되고 있지 않는 부분

- 빌드 실패
const formData = new URLSearchParams(); | ||
formData.append('question', question); | ||
formData.append('reply', reply); | ||
|
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.
- searchParams을 사용하신 이유가 궁금해요!
- 네이밍은
formData
이네요?!
const addFAQ = () => { | ||
setNewFAQs([ | ||
...newFAQs, | ||
{ question: '질문을 입력해주세요', reply: '답변을 입력해주세요' }, |
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.
|
||
const { mutate: deleteFaq, isLoading } = useDeleteFaq(); | ||
|
||
const isClickedDeleteButton = (questionId?: number) => { |
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.
const { mutate: deleteFaq, isLoading } = useDeleteFaq(); | ||
|
||
const isClickedDeleteButton = (questionId?: number) => { | ||
if (!questionId || !token) { |
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.
token이 없는 경우 login 페이지로 이동시키는 로직이 api에 rejectedResponse
에 포함되어져있어요. 따로 대응할 필요가 없어서 참고해주세용
// src/api/index.ts
// interceptors로 일괄처리 적용
api.interceptors.response.use(fulfilledResponse, rejectedResponse);
// 401 경우 login화면 전환 로직 (expirationToken이 rejectedResponse하위로직으로 포함되어져있어요)
function expirationToken(error: AxiosError<ErrorType>) {
removeToken();
window.location.href = '/login';
toast.error(error.response?.data?.message ?? `로그인 시간이 만료되었어요.`);
export const useCreateFaq = () => { | ||
return useMutation(createFaq, { | ||
onSuccess: () => { | ||
console.log('FAQ 생성성공'); |
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.
console은 개발 편의를 위해서 생성하신거라고 생각해요. console을 제거하고 toast를 여기에 띄우는 것은 어떨까요?
console.log('FAQ 생성성공'); | |
toast.success('FAQ가 성공적으로 저장되었습니다'); |
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.
생성하신 FAQActions
component가 사용되고 있는 곳이 없고 중복코드인 것으로 보여요.
혹시 적용을 누락하신 것일까요??
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.
수고하셨습니다!
@@ -638,3 +639,51 @@ function rejectedResponse(error: AxiosError<ErrorType>) { | |||
} | |||
|
|||
api.interceptors.response.use(fulfilledResponse, rejectedResponse); | |||
|
|||
export async function getAllFaq( |
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.
명칭은 hooks에서는 Faq를 페이지,컴포넌트에서는 FAQ 요렇게 두가지를 사용하고 있는 것 같은데 둘 중 하나로 통일 시켜도 좋을 것 같습니다~!
@@ -0,0 +1,60 @@ | |||
interface FAQActionsProps { |
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.
현재 type
으로 컨벤션을 맞춰 진행하고 있었어요! 확인부탁드립니닷
|
||
export const useAllFaq = (token: string) => { | ||
return useQuery(['allFaqs'], () => getAllFaq(token), { | ||
enabled: !!token, |
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.
FAQ는 일반 동아리원/총동아리연합회/동아리 회장님들 모두 사용 확인이 가능하기때문에,token
이 없을 경우 FAQ를 확인하지 못하는 경우는 없을 것 같아요~!
const queryClient = useQueryClient(); | ||
|
||
return useMutation( | ||
async ({ questionId, token }) => await deleteFaq({ questionId, token }), // ✅ async 함수 전달 |
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.
주석은 지워두는게 좋을 것 같습니다~!
addFAQ: () => void; | ||
} | ||
|
||
export default function FAQActions({ |
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.
이미 /admin/FAQ
index.tsx 파일 내에 해당 내용이 포함되어 있는 것 같아요! 추후 삭제될 예정일까요??
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.
개인적으로 분리 안 했을 때 코드가 더 직관적이라면 굳이 나눌 필요는 없을 것 같다고 생각합니다!
} from '@/components/ui/accordion'; | ||
import { useDeleteFaq } from '@/hooks/api/faq/useDeleteFaq'; | ||
|
||
interface FAQItem { |
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.
다시 선언할 필요없이 types/faq.ts
에서 사용중인 타입 가져다 써도 좋을 것 같아요~!
{ questionId, token }, | ||
{ | ||
onSuccess: () => { | ||
refetch(); | ||
toast.success('FAQ가 삭제되었습니다.'); | ||
}, | ||
onError: () => { | ||
toast.error('FAQ 삭제에 실패했습니다.'); | ||
}, |
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.
삭제 로직은 훅 내부에서 처리하는게 좋을 것 같아요! 혹시 refetch
를 사용한 이유가 있을까요??
async ({ questionId, token }) => await deleteFaq({ questionId, token }), // ✅ async 함수 전달 | ||
{ | ||
onSuccess: () => { | ||
queryClient.invalidateQueries({ queryKey: ['faqs'] }); |
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.
삭제와 전체 조회의 queryKey
가 다른 이유가 궁금해요!
const { mutate: createFaq, isLoading: isSaving } = useCreateFaq(); | ||
|
||
const [isEditing, setIsEditing] = useState(false); | ||
const [newFAQs, setNewFAQs] = useState<{ question: string; reply: string }[]>( |
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.
변경 가능할 것 같아요~!
const [newFAQs, setNewFAQs] = useState<{ question: string; reply: string }[]>( | |
const [newFAQs, setNewFAQs] = useState<FAQItem[]> |
}; | ||
|
||
const saveFAQ = async () => { | ||
if (newFAQs.length === 0) return; |
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.
요것도 좋지만, newFaqs.length
가 0 일 경우 저장하기 버튼을 disable 처리하는게 사용자 경험 상 더 좋을 것 같아요~
|
🔥 연관 이슈
🚀 작업 내용
저장버튼을 누르면 기존 FAQ를 지우거나 새로운 FAQ를 작성할 수 있도록 했습니다
🤔 고민했던 내용
기존에 내용을 수정하도록 해야될지 고민을 했었는데 수정버튼이 따로 있기에 한번에 추가와 수정을 같이 하는방향으로 결정하였습니다
그리고 새로운 데이터를 바로 반영하는 방식으로 refetch를 사용하였습니다
💬 리뷰 중점사항
Summary by CodeRabbit
새로운 기능
버그 수정
리팩터링