Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,16 @@ function App() {
<TooltipProvider>
<AppInitializer>
<AppRouter />
<Toaster position="top-right" reverseOrder={false} />
<Toaster
position="top-right"
reverseOrder={false}
toastOptions={{
style: {
maxWidth: '400px',
width: 'fit-content',
},
}}
/>
</AppInitializer>
</TooltipProvider>
</QueryClientProvider>
Expand Down
24 changes: 16 additions & 8 deletions src/app/AppInitializer.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import axios from 'axios';
import * as Sentry from '@sentry/react';
import { useEffect, useRef, type ReactNode } from 'react';
import { ROUTE_PATH } from '@/app/routes/routePaths';
import SplashScreen from '@/pages/SplashScreen';
import { ApiError } from '@/shared/error/types/apiError.types';
import { fetchRefreshToken } from '@/features/auth/api/authApi';
import { useAuthStore } from '@/features/auth/store/useAuthStore';
import { useMyInfoQuery } from '@/features/settings/hooks/useMyInfoQuery';
import SplashScreen from '@/pages/SplashScreen';
import type { User } from '@/features/user/types/userTypes';

interface AppInitializerProps {
Expand All @@ -13,15 +14,24 @@ interface AppInitializerProps {

const AppInitializer = ({ children }: AppInitializerProps) => {
const { setAuth, clearAuth, isInitializing, setIsInitializing } = useAuthStore();
const hasInit = useRef(false);
const { refetch: refetchUser } = useMyInfoQuery();

const hasInit = useRef(false);

useEffect(() => {
if (hasInit.current) return;
hasInit.current = true;

const pathname = window.location.pathname;
const publicPaths = ['/', '/login', '/error', '/auth/callback', '/alarm/permission'];

const publicPaths = [
ROUTE_PATH.MAIN,
ROUTE_PATH.LOGIN,
ROUTE_PATH.ERROR,
ROUTE_PATH.CALLBACK,
ROUTE_PATH.ALARM_SETUP_MOBILE,
];

const isPublic = publicPaths.includes(pathname);

if (isPublic) {
Expand All @@ -46,7 +56,7 @@ const AppInitializer = ({ children }: AppInitializerProps) => {
setAuth({ user });
}
} catch (error) {
if (axios.isAxiosError(error) && error.response?.status === 401) {
if (error instanceof ApiError && error.status === 401) {
console.error('리프레시 토큰 만료 또는 사용자 정보 조회 실패:', error);
clearAuth();
} else {
Expand All @@ -65,9 +75,7 @@ const AppInitializer = ({ children }: AppInitializerProps) => {
init();
}, [setAuth, clearAuth, setIsInitializing, refetchUser]);

if (isInitializing) {
return <SplashScreen />;
}
if (isInitializing) return <SplashScreen />;

return <>{children}</>;
};
Expand Down
6 changes: 3 additions & 3 deletions src/app/layout/AppLayout.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { SidebarProvider } from '@/shared/components/shadcn/sidebar';
import AppSidebar from '@/widgets/AppSidebar';
import { Outlet } from 'react-router-dom';
import { useRef } from 'react';
import { Outlet } from 'react-router-dom';
import AppSidebar from '@/widgets/AppSidebar';
import { SidebarProvider } from '@/shared/components/shadcn/sidebar';
import FloatingSidebarTrigger from '@/features/sidebar/components/FloatingSidebarTrigger';

const AppLayout = () => {
Expand Down
13 changes: 13 additions & 0 deletions src/app/layout/RootLayout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Outlet } from 'react-router-dom';
import ModalRenderer from '@/shared/components/ui/modal/ModalRenderer';

const RootLayout = () => {
return (
<>
<Outlet />
<ModalRenderer />
</>
);
};

export default RootLayout;
34 changes: 19 additions & 15 deletions src/app/routes/Router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import ProtectedRoute from '@/app/routes/ProtectedRoute';
import RootFallback from '@/app/error/root-error/RootFallback';
import PageErrorBoundary from '@/app/error/page-error/PageErrorBoundary';
import AppLayout from '@/app/layout/AppLayout';
import RootLayout from '@/app/layout/RootLayout';
import LandingPage from '@/pages/LandingPage';
import LoginPage from '@/pages/LoginPage';
import MyTaskPage from '@/pages/MyTaskPage';
Expand All @@ -15,7 +16,6 @@ import AvatarPickerPage from '@/pages/AvatarSettingsPage';
import KakaoCallbackPage from '@/pages/KakaoCallbackPage';
import AlarmSetupPage from '@/pages/AlarmSetupPage';
import AlarmSetupMobilePage from '@/pages/AlarmSetupMobilePage';
import ModalRenderer from '@/shared/components/ui/modal/ModalRenderer';
import BoardSection from '@/features/board/components/BoardSection';
import MemoSection from '@/features/memo/components/MemoSection';
import FileSection from '@/features/file/components/FileSection';
Expand Down Expand Up @@ -65,28 +65,32 @@ const withProtected = (element: React.ReactNode) => (
);

export const router = createBrowserRouter([
...PUBLIC_ROUTES,

...PROTECTED_ROUTES_NO_LAYOUT.map((route) => ({
...route,
element: withProtected(route.element),
})),

{
path: '/',
element: <AppLayout />,
element: <RootLayout />,
errorElement: <RootFallback />,
children: PROTECTED_ROUTES.map((route) => ({
...route,
element: withProtected(route.element),
})),
children: [
...PUBLIC_ROUTES,

Comment on lines 68 to +73
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

확인했습니다!😊

...PROTECTED_ROUTES_NO_LAYOUT.map((route) => ({
...route,
element: withProtected(route.element),
})),

{
path: '/',
element: <AppLayout />,
children: PROTECTED_ROUTES.map((route) => ({
...route,
element: withProtected(route.element),
})),
},
],
},
]);

export const AppRouter = () => {
return (
<>
<ModalRenderer />
<RouterProvider router={router} />
</>
);
Expand Down
3 changes: 2 additions & 1 deletion src/features/ai-transform/api/aiTransformApi.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import api from '@/shared/api/axiosInstance';
import { api } from '@/shared/api/axiosInstance';

import type {
AiTransformRequest,
AiTransformResponse,
Expand Down
2 changes: 1 addition & 1 deletion src/features/auth/api/authApi.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import api, { apiPublic } from '@/shared/api/axiosInstance';
import { api, apiPublic } from '@/shared/api/axiosInstance';
import type {
KakaoLoginRequest,
KakaoLoginResponse,
Expand Down
2 changes: 1 addition & 1 deletion src/features/comment/api/commentApi.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import api from '@/shared/api/axiosInstance';
import { api } from '@/shared/api/axiosInstance';
import type { CommentType } from '@/features/comment/types/commentTypes';
import type { FileInfo } from '@/features/task-detail/types/taskDetailType';
import type { PersonaType } from '@/features/comment/constants/personaConstants';
Expand Down
2 changes: 0 additions & 2 deletions src/features/comment/hooks/useCreateCommentMutation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { v4 as uuidv4 } from 'uuid';
import type { CommentType } from '@/features/comment/types/commentTypes';
import { useAuthStore } from '@/features/auth/store/useAuthStore';
import { COMMENT_QUERY_KEYS } from '@/features/comment/api/commentQueryKey';
import toast from 'react-hot-toast';

// 댓글 생성
export const useCreateCommentMutation = (projectId: string, taskId: string) => {
Expand Down Expand Up @@ -86,7 +85,6 @@ export const useCreateCommentMutation = (projectId: string, taskId: string) => {

onError: (_err, _vars, context) => {
if (!context) return;
toast.error('댓글 등록에 실패했습니다');
queryClient.setQueryData<CommentType[]>(context.queryKey, context.previous ?? []);
},
});
Expand Down
6 changes: 0 additions & 6 deletions src/features/comment/hooks/useUpdateCommentMutation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { useMutation, useQueryClient } from '@tanstack/react-query';
import { commentApi } from '@/features/comment/api/commentApi';
import type { CommentType } from '@/features/comment/types/commentTypes';
import { COMMENT_QUERY_KEYS } from '@/features/comment/api/commentQueryKey';
import toast from 'react-hot-toast';
import { AxiosError } from 'axios';

export const useUpdateCommentMutation = (projectId: string, taskId: string) => {
Expand Down Expand Up @@ -38,15 +37,10 @@ export const useUpdateCommentMutation = (projectId: string, taskId: string) => {
return { previousComments };
},

onSuccess: () => {
toast.success('댓글이 수정되었습니다.');
},

onError: (_error, _, context) => {
if (context?.previousComments) {
queryClient.setQueryData(queryKey, context.previousComments);
}
toast.error('댓글 수정에 실패했습니다.');
},

onSettled: () => {
Expand Down
2 changes: 1 addition & 1 deletion src/features/file/api/fileApi.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { FileSummaryResponse } from '@/features/file/types/fileApiTypes';
import api from '@/shared/api/axiosInstance';
import { api } from '@/shared/api/axiosInstance';

export const fileApi = {
// 프로젝트 파일 목록 조회
Expand Down
2 changes: 1 addition & 1 deletion src/features/file/api/fileDownloadApi.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import api from '@/shared/api/axiosInstance';
import { api } from '@/shared/api/axiosInstance';
import type { FileDownloadUrlResponse } from '@/features/file/types/fileApiTypes';

export const fetchFileDownloadUrl = async (fileId: string): Promise<FileDownloadUrlResponse> => {
Expand Down
2 changes: 1 addition & 1 deletion src/features/memo/api/memoApi.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Memo } from '@/features/memo/types/memoTypes';
import api from '@/shared/api/axiosInstance';
import { api } from '@/shared/api/axiosInstance';

export const memoApi = {
// 전체 메모 목록 조회
Expand Down
2 changes: 1 addition & 1 deletion src/features/notifications/api/notificationsApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type {
NotificationCountsResponse,
NotificationsResponse,
} from '@/features/notifications/types/NotificationsType';
import api from '@/shared/api/axiosInstance';
import { api } from '@/shared/api/axiosInstance';

export const notificationsApi = {
fetchNotifications: async (cursor?: string, limit = 6): Promise<NotificationsResponse> => {
Expand Down
2 changes: 1 addition & 1 deletion src/features/project/api/projectApi.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { Project, ProjectBoostingScores } from '@/features/project/types/projectTypes';
import type { Member } from '@/features/user/types/userTypes';
import api from '@/shared/api/axiosInstance';
import { api } from '@/shared/api/axiosInstance';

export const projectApi = {
// 참여 프로젝트 목록 조회
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,11 @@ interface ProjectCreateModalProps {

const ProjectCreateModalContent = ({ onConfirm, onJoinClick }: ProjectCreateModalProps) => {
const [projectName, setProjectName] = useState('');
const { resetModal, setLoading, backModal, stack } = useModalStore();
const { resetModal, backModal, stack } = useModalStore();
const isLoading = stack[stack.length - 1]?.isLoading ?? false;

const handleConfirm = async () => {
setLoading(true);
try {
await onConfirm(projectName);
resetModal();
} catch (error) {
console.error('프로젝트 생성 실패 :', error);
} finally {
setLoading(false);
}
await onConfirm(projectName);
};

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { ROUTE_PATH } from '@/app/routes/routePaths';
import { useModal } from '@/shared/hooks/useModal';
import { Button } from '@/shared/components/shadcn/button';
import MovingBoo from '@/shared/components/ui/MovingBoo';
import { ApiError } from '@/shared/error/types/apiError.types';
import { getErrorMessage } from '@/shared/error/utils/error.utils';
import { useProjectStore } from '@/features/project/store/useProjectStore';
import { useDeleteProjectMutation } from '@/features/project/hooks/mutation/useDeleteProjectMutation';
import ProjectDeleteRotatingText from '@/features/project/components/ProjectDeleteModal/ProjectDeleteRotatingText';
Expand All @@ -19,7 +21,10 @@ const ProjectDeleteModalContent = () => {
toast.success('프로젝트가 삭제되었습니다.');
navigate(ROUTE_PATH.MY_TASK);
},
onError: () => toast.error('프로젝트 삭제를 실패했습니다.'),
onError: (error) => {
if (error instanceof ApiError) toast.error(getErrorMessage(error));
else toast.error('프로젝트 삭제를 실패했어요.');
},
});

if (!projectData) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import toast from 'react-hot-toast';
import { Copy, RefreshCw } from 'lucide-react';
import { useEffect, useState } from 'react';
import { Button } from '@/shared/components/shadcn/button';
import { DialogFooter } from '@/shared/components/shadcn/dialog';
import useModalStore from '@/shared/store/useModalStore';
import toast from 'react-hot-toast';
import { Copy, RefreshCw } from 'lucide-react';
import { useJoinCode } from '@/features/project/hooks/domain/useJoinCode';
import { ApiError } from '@/shared/error/types/apiError.types';
import { getErrorMessage } from '@/shared/error/utils/error.utils';
import { formatSecondsToHHMMSS, getRemainingSeconds } from '@/shared/utils/dateUtils';
import { useJoinCode } from '@/features/project/hooks/domain/useJoinCode';
import { useCreateJoinCodeMutation } from '@/features/project/hooks/mutation/useCreateJoinCodeMutation';

interface ProjectJoinCodeViewModalContentProps {
Expand Down Expand Up @@ -51,18 +53,17 @@ const ProjectJoinCodeViewModalContent = ({ projectId }: ProjectJoinCodeViewModal
loadJoinCode();
toast.success('새 참여 코드가 발급되었습니다!');
},
onError: (err) => {
toast.error('참여 코드 재발급에 실패했습니다.');
console.error('참여 코드 재발급 실패:', err);
onError: (error) => {
if (error instanceof ApiError) toast.error(getErrorMessage(error));
else toast.error('참여 코드 재발급을 실패했어요.');
},
onSettled: () => {
setIsCreating(false);
},
});
} catch (err) {
} catch {
setIsCreating(false);
toast.error('참여 코드 재발급 중 에러가 발생했습니다.');
console.error(err);
toast.error('예상치 못한 오류가 발생했습니다.');
}
};

Expand Down
29 changes: 13 additions & 16 deletions src/features/project/hooks/domain/useJoinCode.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useState, useEffect, useCallback } from 'react';
import { isAxiosError } from 'axios';
import { projectMembershipApi } from '@/features/project/api/projectMembershipApi';
import { ERROR } from '@/shared/constants/errorTypes';
import { ERROR } from '@/shared/error/constants/error.constants';
import { ApiError } from '@/shared/error/types/apiError.types';

export const useJoinCode = (projectId: string) => {
const [joinCode, setJoinCode] = useState<string | null>(null);
Expand All @@ -13,24 +13,21 @@ export const useJoinCode = (projectId: string) => {
setLoading(true);

try {
const codeInfo = await projectMembershipApi
.fetchJoinCode(projectId)
.catch(async (error: unknown) => {
if (isAxiosError(error)) {
if (
error.response?.data?.type === ERROR.JOIN_CODE.NOT_FOUND.type ||
error.response?.status === 400
) {
return projectMembershipApi.createJoinCode(projectId);
}
const codeInfo = await projectMembershipApi.fetchJoinCode(projectId).catch(async (error) => {
if (error instanceof ApiError) {
const isNotFound = error.type === ERROR.JOIN_CODE.NOT_FOUND.type;
const isBadRequest = error.status === 400;

if (isNotFound || isBadRequest) {
return projectMembershipApi.createJoinCode(projectId);
}
throw error;
});
}
throw error;
});

setJoinCode(codeInfo.joinCode);
setExpiresAt(codeInfo.expiresAt);
} catch (err) {
console.error('참여 코드 조회/생성 실패:', err);
} catch {
setJoinCode(null);
setExpiresAt(null);
} finally {
Expand Down
Loading
Loading