-
Notifications
You must be signed in to change notification settings - Fork 4
Refector/글로벌에러바운더리 #163
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
The head ref may contain hidden characters: "refector/\uAE00\uB85C\uBC8C\uC5D0\uB7EC\uBC14\uC6B4\uB354\uB9AC"
Refector/글로벌에러바운더리 #163
Changes from all commits
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 | ||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -9,34 +9,26 @@ import { Task } from '@/types/tasks.types'; | |||||||||||||||||||||||||||||||||||||
| import { axiosInstance } from './_axiosInstance'; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| export const getGroup = async (id: number) => { | ||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||
| const response = await axiosInstance<GetGroupResponse>({ | ||||||||||||||||||||||||||||||||||||||
| method: 'GET', | ||||||||||||||||||||||||||||||||||||||
| url: `groups/${id}`, | ||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||
| return response.data; | ||||||||||||||||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||||||||||||||||
| throw new Error('팀 정보를 가져오는 데 실패했습니다.'); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| const response = await axiosInstance<GetGroupResponse>({ | ||||||||||||||||||||||||||||||||||||||
| method: 'GET', | ||||||||||||||||||||||||||||||||||||||
| url: `groups/${id}`, | ||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||
| return response.data; | ||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| export const postGroup = async ({ name, image }: PostGroupRequest) => { | ||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||
| const response = await axiosInstance.post<GetGroupResponse>( | ||||||||||||||||||||||||||||||||||||||
| 'groups', | ||||||||||||||||||||||||||||||||||||||
| { name, image }, | ||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||
| headers: { | ||||||||||||||||||||||||||||||||||||||
| 'Content-Type': 'application/json', | ||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||
| const body = response.data; | ||||||||||||||||||||||||||||||||||||||
| const response = await axiosInstance.post<GetGroupResponse>( | ||||||||||||||||||||||||||||||||||||||
| 'groups', | ||||||||||||||||||||||||||||||||||||||
| { name, image }, | ||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||
| headers: { | ||||||||||||||||||||||||||||||||||||||
| 'Content-Type': 'application/json', | ||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||
| const body = response.data; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| return body; | ||||||||||||||||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||||||||||||||||
| throw new Error('팀 생성하는 데 실패했습니다.'); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| return body; | ||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| export const patchGroup = async ({ id, name, image }: UpdateGroupRequest) => { | ||||||||||||||||||||||||||||||||||||||
|
|
@@ -63,22 +55,18 @@ export async function postInviteGroup({ | |||||||||||||||||||||||||||||||||||||
| userEmail, | ||||||||||||||||||||||||||||||||||||||
| token, | ||||||||||||||||||||||||||||||||||||||
| }: InviteGroupRequest) { | ||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||
| const response = await axiosInstance.post<string>( | ||||||||||||||||||||||||||||||||||||||
| 'groups/accept-invitation', | ||||||||||||||||||||||||||||||||||||||
| { userEmail, token }, | ||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||
| headers: { | ||||||||||||||||||||||||||||||||||||||
| 'Content-Type': 'application/json', | ||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||
| const body = response.data; | ||||||||||||||||||||||||||||||||||||||
| const response = await axiosInstance.post<string>( | ||||||||||||||||||||||||||||||||||||||
| 'groups/accept-invitation', | ||||||||||||||||||||||||||||||||||||||
| { userEmail, token }, | ||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||
| headers: { | ||||||||||||||||||||||||||||||||||||||
| 'Content-Type': 'application/json', | ||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||
| const body = response.data; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| return body; | ||||||||||||||||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||||||||||||||||
| throw new Error('그룹 참여에 실패했습니다.'); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| return body; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| export const getInviteGroup = async (id: number) => { | ||||||||||||||||||||||||||||||||||||||
|
|
@@ -90,44 +78,32 @@ export const getInviteGroup = async (id: number) => { | |||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| export const postTaskList = async (groupId: number, name: string) => { | ||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||
| const response = await axiosInstance<TaskList>({ | ||||||||||||||||||||||||||||||||||||||
| method: 'POST', | ||||||||||||||||||||||||||||||||||||||
| url: `/groups/${groupId}/task-lists`, | ||||||||||||||||||||||||||||||||||||||
| data: { name }, | ||||||||||||||||||||||||||||||||||||||
| headers: { | ||||||||||||||||||||||||||||||||||||||
| 'Content-Type': 'application/json', | ||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||
| return response.data; | ||||||||||||||||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||||||||||||||||
| throw new Error('할 일 목록를 생성하는 데 실패했습니다.'); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| const response = await axiosInstance<TaskList>({ | ||||||||||||||||||||||||||||||||||||||
| method: 'POST', | ||||||||||||||||||||||||||||||||||||||
| url: `/groups/${groupId}/task-lists`, | ||||||||||||||||||||||||||||||||||||||
| data: { name }, | ||||||||||||||||||||||||||||||||||||||
| headers: { | ||||||||||||||||||||||||||||||||||||||
| 'Content-Type': 'application/json', | ||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||
| return response.data; | ||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| export async function getTasks(id: number, date: string) { | ||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||
| const response = await axiosInstance<Task[]>({ | ||||||||||||||||||||||||||||||||||||||
| method: 'GET', | ||||||||||||||||||||||||||||||||||||||
| url: `groups/${id}/tasks`, | ||||||||||||||||||||||||||||||||||||||
| data: { date }, | ||||||||||||||||||||||||||||||||||||||
| headers: { | ||||||||||||||||||||||||||||||||||||||
| 'Content-Type': 'application/json', | ||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||
| return response.data; | ||||||||||||||||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||||||||||||||||
| throw new Error('할 일을 불러오는 데 실패했습니다.'); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| const response = await axiosInstance<Task[]>({ | ||||||||||||||||||||||||||||||||||||||
| method: 'GET', | ||||||||||||||||||||||||||||||||||||||
| url: `groups/${id}/tasks`, | ||||||||||||||||||||||||||||||||||||||
| data: { date }, | ||||||||||||||||||||||||||||||||||||||
| headers: { | ||||||||||||||||||||||||||||||||||||||
| 'Content-Type': 'application/json', | ||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||
| return response.data; | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+93
to
+101
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. Verify data parameter usage in getTasks The Consider refactoring to use query parameters: export async function getTasks(id: number, date: string) {
const response = await axiosInstance<Task[]>({
method: 'GET',
url: `groups/${id}/tasks`,
- data: { date },
+ params: { date },
headers: {
'Content-Type': 'application/json',
},
});
return response.data;
}📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| export async function deleteMember(groupId: number, memberUserId: number) { | ||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||
| await axiosInstance({ | ||||||||||||||||||||||||||||||||||||||
| method: 'DELETE', | ||||||||||||||||||||||||||||||||||||||
| url: `groups/${groupId}/member/${memberUserId}`, | ||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||||||||||||||||
| throw new Error('멤버 삭제에 실패했습니다.'); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| await axiosInstance({ | ||||||||||||||||||||||||||||||||||||||
| method: 'DELETE', | ||||||||||||||||||||||||||||||||||||||
| url: `groups/${groupId}/member/${memberUserId}`, | ||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
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.
|
||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,32 +2,24 @@ import { TaskList } from '@/types/tasklist.types'; | |
| import { axiosInstance } from './_axiosInstance'; | ||
|
|
||
| export const postTaskList = async (groupId: number, name: string) => { | ||
| try { | ||
| const response = await axiosInstance<TaskList>({ | ||
| method: 'POST', | ||
| url: `/groups/${groupId}/task-lists`, | ||
| data: { name }, | ||
| headers: { | ||
| 'Content-Type': 'application/json', | ||
| }, | ||
| }); | ||
| return response.data; | ||
| } catch (error) { | ||
| throw new Error('할 일 목록를 생성하는 데 실패했습니다.'); | ||
| } | ||
| const response = await axiosInstance<TaskList>({ | ||
| method: 'POST', | ||
| url: `/groups/${groupId}/task-lists`, | ||
| data: { name }, | ||
| headers: { | ||
| 'Content-Type': 'application/json', | ||
| }, | ||
| }); | ||
| return response.data; | ||
|
Comment on lines
+5
to
+13
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 Add type validation and retry configuration. While removing local error handling is good for centralization, consider these improvements: +import { AxiosError } from 'axios';
export const postTaskList = async (groupId: number, name: string) => {
const response = await axiosInstance<TaskList>({
method: 'POST',
url: `/groups/${groupId}/task-lists`,
data: { name },
headers: {
'Content-Type': 'application/json',
},
+ // Implement retry behavior mentioned in PR objectives
+ retry: 0,
+ retryDelay: 1000,
});
+
+ // Validate response type
+ if (!isTaskList(response.data)) {
+ throw new Error('Invalid response format');
+ }
return response.data;
};
+// Type guard
+function isTaskList(data: unknown): data is TaskList {
+ return (
+ typeof data === 'object' &&
+ data !== null &&
+ 'id' in data &&
+ 'name' in data
+ );
+}
|
||
| }; | ||
|
|
||
| export const deleteTaskList = async (groupId: number, taskListId: number) => { | ||
| try { | ||
| const response = await axiosInstance({ | ||
| method: 'DELETE', | ||
| url: `/groups/${groupId}/task-lists/${taskListId}`, | ||
| headers: { | ||
| 'Content-Type': 'application/json', | ||
| }, | ||
| }); | ||
| return response; | ||
| } catch (error) { | ||
| throw new Error(''); | ||
| } | ||
| const response = await axiosInstance({ | ||
| method: 'DELETE', | ||
| url: `/groups/${groupId}/task-lists/${taskListId}`, | ||
| headers: { | ||
| 'Content-Type': 'application/json', | ||
| }, | ||
| }); | ||
| return response; | ||
|
Comment on lines
+17
to
+24
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 Apply consistent error handling pattern. Apply the same retry configuration and type validation pattern to deleteTaskList. export const deleteTaskList = async (groupId: number, taskListId: number) => {
const response = await axiosInstance({
method: 'DELETE',
url: `/groups/${groupId}/task-lists/${taskListId}`,
headers: {
'Content-Type': 'application/json',
},
+ retry: 0,
+ retryDelay: 1000,
});
+
+ // Validate response status
+ if (response.status !== 204) {
+ throw new Error('Unexpected response status');
+ }
return response;
};
|
||
| }; | ||
|
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.
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -41,7 +41,7 @@ function TaskItem({ taskList, taskListColor, isMember }: TaskItemProps) { | |
|
|
||
| const handleTaskClick = (e: React.MouseEvent) => { | ||
| setSelectedTaskList(taskList); | ||
| router.push(`/${taskList.groupId}/tasks`); | ||
| router.push(`/teams/${taskList.groupId}/tasks`); | ||
| e.stopPropagation(); | ||
| }; | ||
|
|
||
|
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. 이 코드 패치에서 우리가 변경한 부분은
Comment on lines
42
to
47
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 Add loading states and integrate with error boundary The task-related mutations (handleTaskClick, handleTaskDelete) should be integrated with the global error boundary and include loading states for better user experience. Consider adding loading states and ensuring mutations are configured to work with the error boundary: const handleTaskClick = (e: React.MouseEvent) => {
+ // Show loading state
setSelectedTaskList(taskList);
router.push(`/teams/${taskList.groupId}/tasks`);
e.stopPropagation();
};
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,11 +12,11 @@ function Badge(props: BadgeProps): JSX.Element { | |
| const no = count === 0 && left === 0; | ||
| const badgeClass = `flex items-center gap-1 rounded-2xl | ||
| bg-primary pb-1 pl-2 pr-2 pt-1 shadow-md`; | ||
| const ongoingSrc = 'icons/Progress_ongoing.svg'; | ||
| const checkSrc = 'icons/Progress_done.svg'; | ||
| const bestSrc = 'icons/Best.svg'; | ||
| const doneSrc = 'icons/Check_lightGreen.svg'; | ||
| const xSrc = 'icons/X.svg'; | ||
| const ongoingSrc = '../icons/Progress_ongoing.svg'; | ||
| const checkSrc = '../icons/Progress_done.svg'; | ||
| const bestSrc = '../icons/Best.svg'; | ||
| const doneSrc = '../icons/Check_lightGreen.svg'; | ||
| const xSrc = '../icons/X.svg'; | ||
|
|
||
| let imageSrc; | ||
| let altText; | ||
|
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. 이 코드 패치는 파일 경로를 수정하는 것으로 보입니다. 이동한 파일이 있는 디렉토리 경로를 고려하여 상대 경로를 사용하고 있습니다. 이러한 변경은 존재하는 파일을 참조할 때 필요합니다. 이 코드 패치는 안전한 변경으로 보입니다. 단, 이동한 파일이나 경로가 다시 변경될 수 있으므로 이후에도 업데이트가 필요할 수 있습니다. |
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| import { QueryErrorResetBoundary } from '@tanstack/react-query'; | ||
| import { ErrorBoundary } from 'react-error-boundary'; | ||
| import ErrorFallback from './ErrorFallback'; | ||
|
|
||
| export default function GlobalBoundary({ | ||
| children, | ||
| }: { | ||
| children: React.ReactNode; | ||
| }) { | ||
| return ( | ||
| <QueryErrorResetBoundary> | ||
| {({ reset }) => ( | ||
| <ErrorBoundary onReset={reset} FallbackComponent={ErrorFallback}> | ||
| {children} | ||
| </ErrorBoundary> | ||
| )} | ||
| </QueryErrorResetBoundary> | ||
| ); | ||
| } | ||
|
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. 해당 코드 패치는 QueryErrorResetBoundary와 ErrorBoundary를 사용하여 전역 오류 처리를 담당하는 GlobalBoundary 컴포넌트를 정의하고 있습니다. 코드의 주요 기능은 자식 컴포넌트(children)를 감싸고 있으며 오류가 발생할 경우 ErrorFallback 컴포넌트를 표시하는 것입니다. 개선 제안:
버그 위험:
Comment on lines
+5
to
+19
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 Consider enhancing error boundary configuration. While the basic setup is good, consider these improvements for more robust error handling:
export default function GlobalBoundary({
children,
}: {
children: React.ReactNode;
}) {
return (
<QueryErrorResetBoundary>
{({ reset }) => (
- <ErrorBoundary onReset={reset} FallbackComponent={ErrorFallback}>
+ <ErrorBoundary
+ onReset={reset}
+ FallbackComponent={ErrorFallback}
+ onError={(error) => {
+ // Log errors to your error reporting service
+ console.error('Error caught by boundary:', error);
+ }}
+ fallbackRender={({ error, resetErrorBoundary }) => (
+ <ErrorFallback
+ error={error}
+ resetErrorBoundary={resetErrorBoundary}
+ // Pass error type based on classification
+ errorType={classifyError(error)}
+ />
+ )}
+ >
{children}
</ErrorBoundary>
)}
</QueryErrorResetBoundary>
);
}
+// Add error classification utility
+function classifyError(error: unknown) {
+ if (axios.isAxiosError(error)) {
+ switch (error.response?.status) {
+ case 401: return 'unauthorized';
+ case 404: return 'not_found';
+ default: return 'api_error';
+ }
+ }
+ return 'unknown';
+}
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,62 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import Button, { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ButtonBackgroundColor, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ButtonBorderColor, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ButtonPadding, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ButtonStyle, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| TextColor, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| TextSize, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } from '@/components/common/Button/Button'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import axios from 'axios'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { useRouter } from 'next/router'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { useState } from 'react'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { FallbackProps } from 'react-error-boundary'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export default function ErrorFallback({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| error, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| resetErrorBoundary, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }: FallbackProps) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const [redirected, setRedirected] = useState(false); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const router = useRouter(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| axios.isAxiosError(error) && | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| error.response?.status === 401 && | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| !redirected | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| router.push('/signin'); // 로그인 페이지로 리다이렉트 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setRedirected(true); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+21
to
+29
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. Prevent potential memory leaks and improve redirect handling. The current implementation has potential issues with cleanup and redirect handling. Consider wrapping the redirect logic in useEffect: - if (
- axios.isAxiosError(error) &&
- error.response?.status === 401 &&
- !redirected
- ) {
- router.push('/signin');
- setRedirected(true);
- return;
- }
+ useEffect(() => {
+ let mounted = true;
+ if (
+ axios.isAxiosError(error) &&
+ error.response?.status === 401 &&
+ !redirected
+ ) {
+ router.push('/signin').then(() => {
+ if (mounted) {
+ setRedirected(true);
+ }
+ });
+ }
+ return () => {
+ mounted = false;
+ };
+ }, [error, redirected, router]);📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className="flex h-screen flex-col items-center justify-center"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className="flex flex-col items-center"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <h1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| className="relative mt-[50px] text-[10rem] text-brand-primary | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| mob:text-[6rem]" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <span className="relative inline-block animate-bounce404-1">E</span> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <span className="relative inline-block animate-bounce404-2">R</span> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <span className="relative inline-block animate-bounce404-1">R</span> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <span className="relative inline-block animate-bounce404-2">O</span> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <span className="relative inline-block animate-bounce404-3">R</span> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </h1> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+31
to
+43
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 Enhance accessibility for error display. The animated error text might not be accessible to screen readers and could be distracting for some users. Consider these accessibility improvements: - <div className="flex h-screen flex-col items-center justify-center">
+ <div
+ role="alert"
+ aria-live="assertive"
+ className="flex h-screen flex-col items-center justify-center"
+ >
<div className="flex flex-col items-center">
- <h1
+ <h1
+ aria-label="Error"
className="relative mt-[50px] text-[10rem] text-brand-primary
mob:text-[6rem]"
>📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <p className="mb-10 text-2xl mob:text-xl-semibold"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {redirected ? '로그인 후 시도해주세요!' : error.response?.data.message} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </p> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+44
to
+46
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. Improve error message handling. The current implementation might display raw API error messages or fail when the response structure is different. Consider adding error message fallbacks and sanitization: - <p className="mb-10 text-2xl mob:text-xl-semibold">
- {redirected ? '로그인 후 시도해주세요!' : error.response?.data.message}
- </p>
+ <p className="mb-10 text-2xl mob:text-xl-semibold">
+ {redirected ? '로그인 후 시도해주세요!' : (
+ error.response?.data.message ||
+ '일시적인 오류가 발생했습니다. 다시 시도해 주세요.'
+ )}
+ </p>📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Button | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| textSize={TextSize.Large} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| buttonStyle={ButtonStyle.Box} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| textColor={TextColor.White} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| buttonBackgroundColor={ButtonBackgroundColor.None} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| buttonBorderColor={ButtonBorderColor.LightGray} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| buttonPadding={ButtonPadding.Large} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| className="border-2" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onClick={() => resetErrorBoundary()} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {redirected ? '로그인하기' : '재시도하기'} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </Button> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
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. 가장 먼저 확인해야 할 사항은 코드에서 사용된 외부 라이브러리 및 컴포넌트의 import 부분입니다. 코드 상단의 import 구문을 살펴보면 Button 컴포넌트 등 다양한 요소들이 import 되어 있습니다. 이 부분은 올바르게 불러오고 있는지 확인할 필요가 있습니다. 또한, axios와 같은 외부 라이브러리를 사용할 때 오류 처리 부분에 대해서도 주목해야 합니다. 현재 코드에서는 axios를 통해 에러 발생 시 401 상태코드에 대한 처리가 있는데, 이 부분이 올바로 동작하는지 확인이 필요합니다. 그리고 페이지 레이아웃 및 디자인에 대한 부분도 코드 리뷰에서 중요한 부분입니다. 현재 코드에서는 에러 발생 시 보여지는 화면의 디자인 및 레이아웃이 구현되어 있습니다. 이 부분이 사용자에게 적절한 정보와 시각적 효과를 전달하는지 확인이 필요합니다. 마지막으로, 버튼 클릭 시 동작하는 함수인 resetErrorBoundary()에 대한 처리도 신중하게 검토해야 합니다. 이 함수가 예상대로 동작하여 사용자 경험에 영향을 미치지 않는지 확인이 필요합니다. 이 외에도 코드 내에 개선할 점이나 버그 가능성이 있는 부분을 발견하거나 추가적인 피드백이 있다면 언제든지 공유해주세요. 함께 논의하고 보완하는 데 도움이 될 것입니다. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -35,7 +35,7 @@ export function HeaderMenu({ | |
| className="flex items-center justify-center" | ||
| > | ||
| <Link | ||
| href={`/${membership.groupId}/editteam`} | ||
| href={`/teams/${membership.groupId}/editteam`} | ||
| className="p-4 hover:bg-tertiary" | ||
| > | ||
| 수정하기 | ||
|
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. 이 코드 패치의 주된 변경점은 이 변경으로 인해 두 가지 주요 사항을 확인할 필요가 있습니다. 첫 번째로, 모든 관련된 경로와 링크들이 제대로 수정되었는지 확인해야 합니다. 두 번째로, 개선 제안으로는 |
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -108,7 +108,7 @@ export default function HeaderNav() { | |
| {memberships && | ||
| memberships.map((m) => ( | ||
| <li key={m.groupId} className="relative flex items-center"> | ||
| <Link href={`/${m.groupId}`}> | ||
| <Link href={`/teams/${m.groupId}`}> | ||
| <Menu.Trigger | ||
| className="flex" | ||
| menuId={GROUP_MENU} | ||
|
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. Link 컴포넌트의 href 속성은 변경되었습니다. 이 변경된 코드는 "/teams" 경로로 이동하도록 되어 있습니다. 해당 변경은 제대로 동작할 것으로 예상됩니다. 코드 리뷰에서 잠재적 오류나 개선점이 발견되지는 않았습니다. |
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -32,7 +32,7 @@ export const MembershipItem = memo( | |
| <ImageIcon width={32} height={32} /> | ||
| )} | ||
| <Link | ||
| href={`/${groupId}`} | ||
| href={`/teams/${groupId}`} | ||
| type="button" | ||
| className={cn([ | ||
| 'w-36 overflow-hidden text-ellipsis whitespace-nowrap', | ||
|
|
@@ -64,7 +64,7 @@ export const MembershipItem = memo( | |
| className="flex items-center justify-center" | ||
| > | ||
| <Link | ||
| href={`/${groupId}/editteam`} | ||
| href={`/teams/${groupId}/editteam`} | ||
| className="p-4 hover:bg-tertiary" | ||
| > | ||
| 수정하기 | ||
|
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. 변경된 코드 패치는 Link 요소에서 href 속성값이 변경되었습니다. 개선 제안:
|
||
|
|
||
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.
이 코드 패치를 살펴보겠습니다.
이상입니다. 부족한 점이 있거나 추가로 도움이 필요하시면 말씀해 주세요. 감사합니다!