refactor: 수정된 response 형식 반영

This commit is contained in:
jhynsoo 2024-09-17 18:25:21 +09:00
parent 503976b3ee
commit 6076c7cea1
21 changed files with 553 additions and 530 deletions

View File

@ -1,6 +1,6 @@
import axios, { AxiosError, AxiosResponse, InternalAxiosRequestConfig } from 'axios';
import useAuthStore from '@/stores/useAuthStore';
import { BaseResponse, CustomError, SuccessResponse, RefreshTokenResponseDTO } from '@/types';
import { BaseResponse, CustomError, SuccessResponse, RefreshTokenResponse } from '@/types';
const baseURL = import.meta.env.VITE_API_URL;
@ -60,11 +60,9 @@ api.interceptors.response.use(
isTokenRefreshing = true;
try {
const response: AxiosResponse<SuccessResponse<RefreshTokenResponseDTO>> = await api.post(
'/api/auth/reissue',
null,
{ withCredentials: true }
);
const response: AxiosResponse<SuccessResponse<RefreshTokenResponse>> = await api.post('/auth/reissue', null, {
withCredentials: true,
});
const newAccessToken = response.data.data?.accessToken;
if (!newAccessToken) {

View File

@ -32,17 +32,21 @@ export async function changeImageStatus(
memberId: number,
statusChangeRequest: ImageStatusChangeRequest
) {
return api.put(`/projects/${projectId}/folders/${folderId}/images/${imageId}/status`, statusChangeRequest, {
params: { memberId },
});
return api
.put(`/projects/${projectId}/folders/${folderId}/images/${imageId}/status`, statusChangeRequest, {
params: { memberId },
})
.then(({ data }) => data);
}
export async function uploadImageList(projectId: number, folderId: number, memberId: number, imageList: string[]) {
return api.post(
`/projects/${projectId}/folders/${folderId}/images`,
{ imageList },
{
params: { memberId },
}
);
return api
.post(
`/projects/${projectId}/folders/${folderId}/images`,
{ imageList },
{
params: { memberId },
}
)
.then(({ data }) => data);
}

View File

@ -2,17 +2,21 @@ import api from '@/api/axiosConfig';
import { LabelingRequest } from '@/types';
export async function saveImageLabels(projectId: number, imageId: number, memberId: number, data: LabelingRequest) {
return api.post(`/projects/${projectId}/label/image/${imageId}`, data, {
params: { memberId },
});
return api
.post(`/projects/${projectId}/label/image/${imageId}`, data, {
params: { memberId },
})
.then(({ data }) => data);
}
export async function runAutoLabel(projectId: number, memberId: number) {
return api.post(
`/projects/${projectId}/label/auto`,
{},
{
params: { memberId },
}
);
return api
.post(
`/projects/${projectId}/label/auto`,
{},
{
params: { memberId },
}
)
.then(({ data }) => data);
}

View File

@ -1,8 +1,26 @@
import api from '@/api/axiosConfig';
import { ProjectListResponse, ProjectResponse } from '@/types';
export async function getProjectList(
workspaceId: number,
memberId: number,
lastProjectId?: number,
limit: number = 10
) {
return api
.get<ProjectListResponse>(`/workspaces/${workspaceId}/projects`, {
params: {
memberId,
lastProjectId,
limit,
},
})
.then(({ data }) => data);
}
export async function getProject(projectId: number, memberId: number) {
return api
.get(`/projects/${projectId}`, {
.get<ProjectResponse>(`/projects/${projectId}`, {
params: { memberId },
})
.then(({ data }) => data);
@ -54,23 +72,6 @@ export async function removeProjectMember(projectId: number, memberId: number, t
.then(({ data }) => data);
}
export async function getAllProjects(
workspaceId: number,
memberId: number,
lastProjectId?: number,
limit: number = 10
) {
return api
.get(`/workspaces/${workspaceId}/projects`, {
params: {
memberId,
lastProjectId,
limit,
},
})
.then(({ data }) => data);
}
export async function createProject(
workspaceId: number,
memberId: number,

View File

@ -1,44 +1,58 @@
import api from '@/api/axiosConfig';
import { WorkspaceRequest } from '@/types';
import { WorkspaceListResponse, WorkspaceRequest, WorkspaceResponse } from '@/types';
export async function fetchWorkspaceList(memberId: number, lastWorkspaceId?: number, limit?: number) {
return api.get('/workspaces', {
params: { memberId, lastWorkspaceId, limit },
});
export async function getWorkspaceList(memberId: number, lastWorkspaceId?: number, limit?: number) {
return api
.get<WorkspaceListResponse>('/workspaces', {
params: { memberId, lastWorkspaceId, limit },
})
.then(({ data }) => data);
}
export async function fetchWorkspace(workspaceId: number, memberId: number) {
return api.get(`/workspaces/${workspaceId}`, {
params: { memberId },
});
export async function getWorkspace(workspaceId: number, memberId: number) {
return api
.get<WorkspaceResponse>(`/workspaces/${workspaceId}`, {
params: { memberId },
})
.then(({ data }) => data);
}
export async function updateWorkspace(workspaceId: number, memberId: number, data: WorkspaceRequest) {
return api.put(`/workspaces/${workspaceId}`, data, {
params: { memberId },
});
return api
.put(`/workspaces/${workspaceId}`, data, {
params: { memberId },
})
.then(({ data }) => data);
}
export async function deleteWorkspace(workspaceId: number, memberId: number) {
return api.delete(`/workspaces/${workspaceId}`, {
params: { memberId },
});
return api
.delete(`/workspaces/${workspaceId}`, {
params: { memberId },
})
.then(({ data }) => data);
}
export async function createWorkspace(memberId: number, data: WorkspaceRequest) {
return api.post('/workspaces', data, {
params: { memberId },
});
return api
.post('/workspaces', data, {
params: { memberId },
})
.then(({ data }) => data);
}
export async function addWorkspaceMember(workspaceId: number, memberId: number, newMemberId: number) {
return api.post(`/workspaces/${workspaceId}/members/${newMemberId}`, null, {
params: { memberId },
});
return api
.post(`/workspaces/${workspaceId}/members/${newMemberId}`, null, {
params: { memberId },
})
.then(({ data }) => data);
}
export async function removeWorkspaceMember(workspaceId: number, memberId: number, targetMemberId: number) {
return api.delete(`/workspaces/${workspaceId}/members/${targetMemberId}`, {
params: { memberId },
});
return api
.delete(`/workspaces/${workspaceId}/members/${targetMemberId}`, {
params: { memberId },
})
.then(({ data }) => data);
}

View File

@ -1,43 +1,26 @@
import { useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import useAuthStore from '@/stores/useAuthStore';
import { fetchProfileApi } from '@/api/authApi';
import useProfileQuery from '@/queries/useProfileQuery';
export default function OAuthCallback() {
const queryParams = new URLSearchParams(window.location.search);
const accessToken = queryParams.get('accessToken');
const navigate = useNavigate();
const setLoggedIn = useAuthStore((state) => state.setLoggedIn);
const setProfile = useAuthStore((state) => state.setProfile);
const { data: profile } = useProfileQuery();
useEffect(() => {
const queryParams = new URLSearchParams(window.location.search);
const accessToken = queryParams.get('accessToken');
if (accessToken) {
setLoggedIn(true, accessToken);
fetchProfileApi()
.then((data) => {
if (data?.isSuccess && data.data) {
const profileData = {
id: data.data.id,
nickname: data.data.nickname,
profileImage: data.data.profileImage,
};
setProfile(profileData);
navigate('/browse');
} else {
throw new Error('프로필 데이터를 가져올 수 없습니다.');
}
})
.catch((error) => {
alert('프로필을 가져오는 중 오류가 발생했습니다. 다시 로그인해주세요.');
console.error('프로필 가져오기 실패:', error);
navigate('/');
});
} else {
if (!accessToken) {
navigate('/');
return;
}
}, [navigate, setLoggedIn, setProfile]);
setLoggedIn(true, accessToken);
setProfile(profile);
navigate('/browse');
}, [accessToken, navigate, profile, setLoggedIn, setProfile]);
return <p> ...</p>;
}

View File

@ -2,10 +2,10 @@ import * as React from 'react';
import WorkSpaceCreateForm, { WorkSpaceCreateFormValues } from './WorkSpaceCreateForm';
import { Dialog, DialogContent, DialogHeader, DialogTrigger } from '../ui/dialogCustom';
import { Plus } from 'lucide-react';
import { WorkspaceRequestDTO } from '@/types';
import { WorkspaceRequest } from '@/types';
interface WorkSpaceCreateModalProps {
onSubmit: (data: WorkspaceRequestDTO) => void;
onSubmit: (data: WorkspaceRequest) => void;
}
export default function WorkSpaceCreateModal({ onSubmit }: WorkSpaceCreateModalProps) {
@ -15,7 +15,7 @@ export default function WorkSpaceCreateModal({ onSubmit }: WorkSpaceCreateModalP
const handleClose = () => setIsOpen(false);
const handleFormSubmit = (data: WorkSpaceCreateFormValues) => {
const formattedData: WorkspaceRequestDTO = {
const formattedData: WorkspaceRequest = {
title: data.workspaceName,
content: data.workspaceDescription || '',
};

View File

@ -1,18 +1,15 @@
import { Suspense, useEffect } from 'react';
import { NavLink, Outlet, useNavigate } from 'react-router-dom';
import Header from '../Header';
import { useGetAllWorkspaces, useCreateWorkspace } from '@/hooks/useWorkspaceHooks';
import useAuthStore from '@/stores/useAuthStore';
import WorkSpaceCreateModal from '../WorkSpaceCreateModal';
import { useQueryClient } from '@tanstack/react-query';
import { AxiosError } from 'axios';
import { WorkspaceRequestDTO, WorkspaceResponseDTO, CustomError } from '@/types';
import { WorkspaceRequest, WorkspaceResponse } from '@/types';
import useWorkspaceListQuery from '@/queries/useWorkspaceListQuery';
export default function WorkspaceBrowseLayout() {
const { profile, isLoggedIn } = useAuthStore();
const memberId = profile?.id ?? 0;
const navigate = useNavigate();
const queryClient = useQueryClient();
useEffect(() => {
if (!isLoggedIn || !memberId) {
@ -21,26 +18,27 @@ export default function WorkspaceBrowseLayout() {
}
}, [isLoggedIn, memberId, navigate]);
const { data: workspacesResponse, isLoading, isError } = useGetAllWorkspaces(memberId || 0);
const { data: workspacesResponse, isLoading, isError } = useWorkspaceListQuery(memberId ?? 0);
const workspaces = workspacesResponse?.data?.workspaceResponses || [];
const workspaces = workspacesResponse?.workspaceResponses ?? [];
const createWorkspace = useCreateWorkspace();
// const createWorkspace = useCreateWorkspace();
const handleCreateWorkspace = (data: WorkspaceRequestDTO) => {
const handleCreateWorkspace = (data: WorkspaceRequest) => {
if (!memberId) return;
createWorkspace.mutate(
{ memberId, data },
{
onSuccess: () => {
console.log('워크스페이스가 성공적으로 생성되었습니다.');
queryClient.invalidateQueries({ queryKey: ['workspaces'] });
},
onError: (error: AxiosError<CustomError>) => {
console.error('워크스페이스 생성 실패:', error.message);
},
}
);
console.log(data);
// createWorkspace.mutate(
// { memberId, data },
// {
// onSuccess: () => {
// console.log('워크스페이스가 성공적으로 생성되었습니다.');
// queryClient.invalidateQueries({ queryKey: ['workspaces'] });
// },
// onError: (error: AxiosError<CustomError>) => {
// console.error('워크스페이스 생성 실패:', error.message);
// },
// }
// );
};
if (isLoading) {
@ -62,7 +60,7 @@ export default function WorkspaceBrowseLayout() {
<WorkSpaceCreateModal onSubmit={handleCreateWorkspace} />
</div>
{workspaces.length > 0 ? (
workspaces.map((workspace: WorkspaceResponseDTO) => (
workspaces.map((workspace: WorkspaceResponse) => (
<NavLink
to={`/browse/${workspace.id}`}
key={workspace.id}
@ -76,11 +74,11 @@ export default function WorkspaceBrowseLayout() {
)}
</div>
<div className="flex w-[calc(100%-280px)] flex-col gap-24">
<Suspense fallback={<div></div>}>
<main className="grow">
<main className="grow">
<Suspense fallback={<div></div>}>
<Outlet />
</main>
</Suspense>
</Suspense>
</main>
</div>
</div>
</div>

View File

@ -1,15 +1,15 @@
import { useEffect, useState } from 'react';
import { useParams, Outlet } from 'react-router-dom';
import Header from '../Header';
import { Label, Project, DirectoryItem, FileItem } from '@/types';
import { Label, Project } from '@/types';
import { ResizablePanelGroup, ResizablePanel } from '../ui/resizable';
import WorkspaceSidebar from '../WorkspaceSidebar';
import WorkspaceLabelBar from '../WorkspaceLabelBar';
import { fetchFolderApi } from '@/api/folderApi';
import { getWorkspaceApi } from '@/api/workspaceApi';
import { getAllProjectsApi } from '@/api/projectApi';
import useAuthStore from '@/stores/useAuthStore';
import useCanvasStore from '@/stores/useCanvasStore';
import useFolderQuery from '@/queries/useFolderQuery';
import useWorkspaceQuery from '@/queries/useWorkspaceQuery';
import useProjectListQuery from '@/queries/useProjectListQuery';
const mockLabels: Label[] = [
{
@ -50,103 +50,136 @@ const mockLabels: Label[] = [
export default function WorkspaceLayout() {
const setLabels = useCanvasStore((state) => state.setLabels);
const { workspaceId, projectId } = useParams<{ workspaceId: string; projectId: string }>();
const params = useParams<{ workspaceId: string; projectId: string }>();
const workspaceId = Number(params.workspaceId);
const projectId = Number(params.projectId);
const [workspace, setWorkspace] = useState<{ name: string; projects: Project[] }>({
name: '',
projects: [],
});
const profile = useAuthStore((state) => state.profile);
const memberId = profile?.id || 0;
const { data: workspaceData } = useWorkspaceQuery(workspaceId, memberId);
const { data: projectListData } = useProjectListQuery(workspaceId, memberId);
const { data: folderData } = useFolderQuery(projectId, 0, memberId);
useEffect(() => {
const fetchWorkspaceData = async (workspaceId: number, memberId: number) => {
try {
const workspaceResponse = await getWorkspaceApi(workspaceId, memberId);
if (workspaceResponse.isSuccess) {
const workspaceTitle = workspaceResponse.data.title;
setWorkspace((prev) => ({
...prev,
name: workspaceTitle,
}));
fetchProjects(workspaceId, memberId);
}
} catch (error) {
console.error('워크스페이스 조회 실패:', error);
}
};
if (!workspaceData) return;
setWorkspace((prev) => ({
...prev,
name: workspaceData.title,
}));
}, [workspaceData]);
const fetchProjects = async (workspaceId: number, memberId: number) => {
try {
const projectResponse = await getAllProjectsApi(workspaceId, memberId);
if (projectResponse.isSuccess) {
const projects = await Promise.all(
projectResponse.data.workspaceResponses.map(async (project) => {
const children = await fetchFolderWithImages(project.id, memberId);
return {
id: project.id,
name: project.title,
type: capitalizeType(project.projectType),
children,
};
})
);
setWorkspace((prev) => ({
...prev,
projects: projects as Project[],
}));
}
} catch (error) {
console.error('프로젝트 목록 조회 실패:', error);
}
};
useEffect(() => {
if (!projectListData) return;
console.log(folderData);
const projects = projectListData.workspaceResponses.map(
(project): Project => ({
id: project.id,
name: project.title,
type: (project.projectType.charAt(0).toUpperCase() + project.projectType.slice(1)) as
| 'Classification'
| 'Detection'
| 'Segmentation',
children: [],
})
);
setWorkspace((prev) => ({
...prev,
projects,
}));
}, [projectListData]);
const fetchFolderWithImages = async (projectId: number, memberId: number): Promise<DirectoryItem[]> => {
try {
const folderResponse = await fetchFolderApi(projectId, 0, memberId);
if (folderResponse.isSuccess) {
const files: FileItem[] = folderResponse.data.images.map((image) => ({
id: image.id,
name: image.imageTitle,
url: image.imageUrl,
type: 'image',
status: image.status === 'COMPLETED' ? 'done' : 'idle',
}));
// useEffect(() => {
// const fetchWorkspaceData = async (workspaceId: number, memberId: number) => {
// try {
// const workspaceResponse = await getWorkspaceApi(workspaceId, memberId);
// if (workspaceResponse.isSuccess) {
// const workspaceTitle = workspaceResponse.data.title;
// setWorkspace((prev) => ({
// ...prev,
// name: workspaceTitle,
// }));
// fetchProjects(workspaceId, memberId);
// }
// } catch (error) {
// console.error('워크스페이스 조회 실패:', error);
// }
// };
return [
{
id: folderResponse.data.id,
name: folderResponse.data.title,
type: 'directory',
children: files,
},
];
}
return [];
} catch (error) {
console.error('폴더 및 이미지 조회 실패:', error);
return [];
}
};
// const fetchProjects = async (workspaceId: number, memberId: number) => {
// try {
// const projectResponse = await getAllProjectsApi(workspaceId, memberId);
// if (projectResponse.isSuccess) {
// const projects = await Promise.all(
// projectResponse.data.workspaceResponses.map(async (project) => {
// const children = await fetchFolderWithImages(project.id, memberId);
// return {
// id: project.id,
// name: project.title,
// type: capitalizeType(project.projectType),
// children,
// };
// })
// );
// setWorkspace((prev) => ({
// ...prev,
// projects: projects as Project[],
// }));
// }
// } catch (error) {
// console.error('프로젝트 목록 조회 실패:', error);
// }
// };
const capitalizeType = (
type: 'classification' | 'detection' | 'segmentation'
): 'Classification' | 'Detection' | 'Segmentation' => {
switch (type) {
case 'classification':
return 'Classification';
case 'detection':
return 'Detection';
case 'segmentation':
return 'Segmentation';
default:
throw new Error(`Unknown project type: ${type}`);
}
};
// const fetchFolderWithImages = async (projectId: number, memberId: number): Promise<DirectoryItem[]> => {
// try {
// const folderResponse = await fetchFolderApi(projectId, 0, memberId);
// if (folderResponse.isSuccess) {
// const files: FileItem[] = folderResponse.data.images.map((image) => ({
// id: image.id,
// name: image.imageTitle,
// url: image.imageUrl,
// type: 'image',
// status: image.status === 'COMPLETED' ? 'done' : 'idle',
// }));
if (workspaceId && memberId) {
fetchWorkspaceData(Number(workspaceId), memberId);
}
}, [workspaceId, projectId, memberId]);
// return [
// {
// id: folderResponse.data.id,
// name: folderResponse.data.title,
// type: 'directory',
// children: files,
// },
// ];
// }
// return [];
// } catch (error) {
// console.error('폴더 및 이미지 조회 실패:', error);
// return [];
// }
// };
// const capitalizeType = (
// type: 'classification' | 'detection' | 'segmentation'
// ): 'Classification' | 'Detection' | 'Segmentation' => {
// switch (type) {
// case 'classification':
// return 'Classification';
// case 'detection':
// return 'Detection';
// case 'segmentation':
// return 'Segmentation';
// default:
// throw new Error(`Unknown project type: ${type}`);
// }
// };
// if (workspaceId && memberId) {
// fetchWorkspaceData(Number(workspaceId), memberId);
// }
// }, [workspaceId, projectId, memberId]);
useEffect(() => {
setLabels(mockLabels);

View File

@ -1,114 +1,114 @@
import { useQuery, UseQueryResult, useMutation, UseMutationResult, useQueryClient } from '@tanstack/react-query';
import { AxiosError } from 'axios';
import {
getProjectApi,
updateProjectApi,
deleteProjectApi,
getAllProjectsApi,
createProjectApi,
addProjectMemberApi,
removeProjectMemberApi,
} from '@/api/projectApi';
import { BaseResponse, ProjectResponseDTO, ProjectListResponseDTO, CustomError } from '@/types';
// TODO: 훅 재설계
// import { useQuery, UseQueryResult, useMutation, UseMutationResult, useQueryClient } from '@tanstack/react-query';
// import { AxiosError } from 'axios';
// import {
// getProject,
// updateProject,
// deleteProject,
// getAllProjects,
// createProject,
// addProjectMember,
// removeProjectMember,
// } from '@/api/projectApi';
// import { BaseResponse, ProjectResponse, ProjectListResponse, CustomError } from '@/types';
export const useGetProject = (
projectId: number,
memberId: number
): UseQueryResult<BaseResponse<ProjectResponseDTO>, AxiosError<CustomError>> => {
return useQuery<BaseResponse<ProjectResponseDTO>, AxiosError<CustomError>>({
queryKey: ['project', projectId],
queryFn: () => getProjectApi(projectId, memberId),
});
};
// export const useGetProject = (
// projectId: number,
// memberId: number
// ): UseQueryResult<BaseResponse<ProjectResponse>, AxiosError<CustomError>> => {
// return useQuery<BaseResponse<ProjectResponse>, AxiosError<CustomError>>({
// queryKey: ['project', projectId],
// queryFn: () => getProject(projectId, memberId),
// });
// };
export const useUpdateProject = (): UseMutationResult<
BaseResponse<ProjectResponseDTO>,
AxiosError<CustomError>,
{
projectId: number;
memberId: number;
data: { title: string; projectType: 'classification' | 'detection' | 'segmentation' };
}
> => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ projectId, memberId, data }) => updateProjectApi(projectId, memberId, data),
onSuccess: (data) => {
queryClient.invalidateQueries({ queryKey: ['project', data.data.id] });
},
});
};
// export const useUpdateProject = (): UseMutationResult<
// BaseResponse<ProjectResponse>,
// AxiosError<CustomError>,
// {
// projectId: number;
// memberId: number;
// data: { title: string; projectType: 'classification' | 'detection' | 'segmentation' };
// }
// > => {
// const queryClient = useQueryClient();
// return useMutation({
// mutationFn: ({ projectId, memberId, data }) => updateProject(projectId, memberId, data),
// onSuccess: (data) => {
// queryClient.invalidateQueries({ queryKey: ['project', data.data.id] });
// },
// });
// };
export const useDeleteProject = (): UseMutationResult<
BaseResponse<null>,
AxiosError<CustomError>,
{ projectId: number; memberId: number }
> => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ projectId, memberId }) => deleteProjectApi(projectId, memberId),
onSuccess: (_, variables) => {
queryClient.invalidateQueries({ queryKey: ['project', variables.projectId] });
},
});
};
// export const useDeleteProject = (): UseMutationResult<
// BaseResponse<null>,
// AxiosError<CustomError>,
// { projectId: number; memberId: number }
// > => {
// const queryClient = useQueryClient();
// return useMutation({
// mutationFn: ({ projectId, memberId }) => deleteProject(projectId, memberId),
// onSuccess: (_, variables) => {
// queryClient.invalidateQueries({ queryKey: ['project', variables.projectId] });
// },
// });
// };
export const useGetAllProjects = (
workspaceId: number,
memberId: number,
options?: { enabled: boolean }
): UseQueryResult<BaseResponse<ProjectListResponseDTO>, AxiosError<CustomError>> => {
return useQuery<BaseResponse<ProjectListResponseDTO>, AxiosError<CustomError>>({
queryKey: ['projects', workspaceId],
queryFn: () => getAllProjectsApi(workspaceId, memberId),
enabled: options?.enabled,
});
};
// export const useGetAllProjects = (
// workspaceId: number,
// memberId: number,
// options?: { enabled: boolean }
// ): UseQueryResult<BaseResponse<ProjectListResponse>, AxiosError<CustomError>> => {
// return useQuery<BaseResponse<ProjectListResponse>, AxiosError<CustomError>>({
// queryKey: ['projects', workspaceId],
// queryFn: () => getAllProjects(workspaceId, memberId),
// enabled: options?.enabled,
// });
// };
export const useCreateProject = (): UseMutationResult<
BaseResponse<ProjectResponseDTO>,
AxiosError<CustomError>,
{
workspaceId: number;
memberId: number;
data: { title: string; projectType: 'classification' | 'detection' | 'segmentation' };
}
> => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ workspaceId, memberId, data }) => createProjectApi(workspaceId, memberId, data),
onSuccess: (_, variables) => {
queryClient.invalidateQueries({ queryKey: ['projects', variables.workspaceId] });
},
});
};
// export const useCreateProject = (): UseMutationResult<
// BaseResponse<ProjectResponse>,
// AxiosError<CustomError>,
// {
// workspaceId: number;
// memberId: number;
// data: { title: string; projectType: 'classification' | 'detection' | 'segmentation' };
// }
// > => {
// const queryClient = useQueryClient();
// return useMutation({
// mutationFn: ({ workspaceId, memberId, data }) => createProject(workspaceId, memberId, data),
// onSuccess: (_, variables) => {
// queryClient.invalidateQueries({ queryKey: ['projects', variables.workspaceId] });
// },
// });
// };
export const useAddProjectMember = (): UseMutationResult<
BaseResponse<null>,
AxiosError<CustomError>,
{ projectId: number; memberId: number; newMemberId: number; privilegeType: string }
> => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ projectId, memberId, newMemberId, privilegeType }) =>
addProjectMemberApi(projectId, memberId, newMemberId, privilegeType),
onSuccess: (_, variables) => {
queryClient.invalidateQueries({ queryKey: ['project', variables.projectId] });
},
});
};
// export const useAddProjectMember = (): UseMutationResult<
// BaseResponse<null>,
// AxiosError<CustomError>,
// { projectId: number; memberId: number; newMemberId: number; privilegeType: string }
// > => {
// const queryClient = useQueryClient();
// return useMutation({
// mutationFn: ({ projectId, memberId, newMemberId, privilegeType }) =>
// addProjectMember(projectId, memberId, newMemberId, privilegeType),
// onSuccess: (_, variables) => {
// queryClient.invalidateQueries({ queryKey: ['project', variables.projectId] });
// },
// });
// };
export const useRemoveProjectMember = (): UseMutationResult<
BaseResponse<null>,
AxiosError<CustomError>,
{ projectId: number; memberId: number; targetMemberId: number }
> => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ projectId, memberId, targetMemberId }) =>
removeProjectMemberApi(projectId, memberId, targetMemberId),
onSuccess: (_, variables) => {
queryClient.invalidateQueries({ queryKey: ['project', variables.projectId] });
},
});
};
// export const useRemoveProjectMember = (): UseMutationResult<
// BaseResponse<null>,
// AxiosError<CustomError>,
// { projectId: number; memberId: number; targetMemberId: number }
// > => {
// const queryClient = useQueryClient();
// return useMutation({
// mutationFn: ({ projectId, memberId, targetMemberId }) => removeProjectMember(projectId, memberId, targetMemberId),
// onSuccess: (_, variables) => {
// queryClient.invalidateQueries({ queryKey: ['project', variables.projectId] });
// },
// });
// };

View File

@ -1,104 +1,89 @@
import { useQuery, UseQueryResult, useMutation, UseMutationResult, useQueryClient } from '@tanstack/react-query';
import { AxiosError } from 'axios';
import {
getWorkspaceApi,
updateWorkspaceApi,
deleteWorkspaceApi,
getAllWorkspacesApi,
createWorkspaceApi,
addWorkspaceMemberApi,
removeWorkspaceMemberApi,
} from '@/api/workspaceApi';
import { BaseResponse, WorkspaceResponseDTO, WorkspaceListResponseDTO, CustomError } from '@/types';
// TODO: 훅 재설계
// import { useQuery } from '@tanstack/react-query';
// import { getWorkspace, getWorkspaceList } from '@/api/workspaceApi';
export const useGetWorkspace = (
workspaceId: number,
memberId: number
): UseQueryResult<BaseResponse<WorkspaceResponseDTO>, AxiosError<CustomError>> => {
return useQuery<BaseResponse<WorkspaceResponseDTO>, AxiosError<CustomError>>({
queryKey: ['workspace', workspaceId],
queryFn: () => getWorkspaceApi(workspaceId, memberId),
});
};
// export const useGetWorkspace = (workspaceId: number, memberId: number) => {
// return useQuery({
// queryKey: ['workspace', workspaceId],
// queryFn: () => getWorkspace(workspaceId, memberId),
// });
// };
export const useUpdateWorkspace = (): UseMutationResult<
BaseResponse<WorkspaceResponseDTO>,
AxiosError<CustomError>,
{ workspaceId: number; memberId: number; data: { title: string; content: string } }
> => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ workspaceId, memberId, data }) => updateWorkspaceApi(workspaceId, memberId, data),
onSuccess: (_, variables) => {
queryClient.invalidateQueries({ queryKey: ['workspace', variables.workspaceId] });
},
});
};
// export const useGetWorkspaceList = (memberId: number, lastWorkspaceId?: number, limit?: number) => {
// return useQuery({
// queryKey: ['workspaces'],
// queryFn: () => getWorkspaceList(memberId, lastWorkspaceId, limit),
// });
// };
export const useDeleteWorkspace = (): UseMutationResult<
BaseResponse<null>,
AxiosError<CustomError>,
{ workspaceId: number; memberId: number }
> => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ workspaceId, memberId }) => deleteWorkspaceApi(workspaceId, memberId),
onSuccess: (_, variables) => {
queryClient.invalidateQueries({ queryKey: ['workspace', variables.workspaceId] });
},
});
};
// TODO: 수정된 쿼리에 맞게 훅 수정
// export const useUpdateWorkspace = (): UseMutationResult<
// BaseResponse<WorkspaceResponse>,
// AxiosError<CustomError>,
// { workspaceId: number; memberId: number; data: { title: string; content: string } }
// > => {
// const queryClient = useQueryClient();
// return useMutation({
// mutationFn: ({ workspaceId, memberId, data }) => updateWorkspace(workspaceId, memberId, data),
// onSuccess: (_, variables) => {
// queryClient.invalidateQueries({ queryKey: ['workspace', variables.workspaceId] });
// },
// });
// };
export const useGetAllWorkspaces = (
memberId: number,
lastWorkspaceId?: number,
limit?: number
): UseQueryResult<BaseResponse<WorkspaceListResponseDTO>, AxiosError<CustomError>> => {
return useQuery<BaseResponse<WorkspaceListResponseDTO>, AxiosError<CustomError>>({
queryKey: ['workspaces'],
queryFn: () => getAllWorkspacesApi(memberId, lastWorkspaceId, limit),
});
};
// export const useDeleteWorkspace = (): UseMutationResult<
// BaseResponse<null>,
// AxiosError<CustomError>,
// { workspaceId: number; memberId: number }
// > => {
// const queryClient = useQueryClient();
// return useMutation({
// mutationFn: ({ workspaceId, memberId }) => deleteWorkspace(workspaceId, memberId),
// onSuccess: (_, variables) => {
// queryClient.invalidateQueries({ queryKey: ['workspace', variables.workspaceId] });
// },
// });
// };
export const useCreateWorkspace = (): UseMutationResult<
BaseResponse<WorkspaceResponseDTO>,
AxiosError<CustomError>,
{ memberId: number; data: { title: string; content: string } }
> => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ memberId, data }) => createWorkspaceApi(memberId, data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['workspaces'] });
},
});
};
// export const useCreateWorkspace = (): UseMutationResult<
// BaseResponse<WorkspaceResponse>,
// AxiosError<CustomError>,
// { memberId: number; data: { title: string; content: string } }
// > => {
// const queryClient = useQueryClient();
// return useMutation({
// mutationFn: ({ memberId, data }) => createWorkspace(memberId, data),
// onSuccess: () => {
// queryClient.invalidateQueries({ queryKey: ['workspaces'] });
// },
// });
// };
export const useAddWorkspaceMember = (): UseMutationResult<
BaseResponse<null>,
AxiosError<CustomError>,
{ workspaceId: number; memberId: number; newMemberId: number }
> => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ workspaceId, memberId, newMemberId }) => addWorkspaceMemberApi(workspaceId, memberId, newMemberId),
onSuccess: (_, variables) => {
queryClient.invalidateQueries({ queryKey: ['workspace', variables.workspaceId] });
},
});
};
// export const useAddWorkspaceMember = (): UseMutationResult<
// BaseResponse<null>,
// AxiosError<CustomError>,
// { workspaceId: number; memberId: number; newMemberId: number }
// > => {
// const queryClient = useQueryClient();
// return useMutation({
// mutationFn: ({ workspaceId, memberId, newMemberId }) => addWorkspaceMember(workspaceId, memberId, newMemberId),
// onSuccess: (_, variables) => {
// queryClient.invalidateQueries({ queryKey: ['workspace', variables.workspaceId] });
// },
// });
// };
export const useRemoveWorkspaceMember = (): UseMutationResult<
BaseResponse<null>,
AxiosError<CustomError>,
{ workspaceId: number; memberId: number; targetMemberId: number }
> => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ workspaceId, memberId, targetMemberId }) =>
removeWorkspaceMemberApi(workspaceId, memberId, targetMemberId),
onSuccess: (_, variables) => {
queryClient.invalidateQueries({ queryKey: ['workspace', variables.workspaceId] });
},
});
};
// export const useRemoveWorkspaceMember = (): UseMutationResult<
// BaseResponse<null>,
// AxiosError<CustomError>,
// { workspaceId: number; memberId: number; targetMemberId: number }
// > => {
// const queryClient = useQueryClient();
// return useMutation({
// mutationFn: ({ workspaceId, memberId, targetMemberId }) =>
// removeWorkspaceMember(workspaceId, memberId, targetMemberId),
// onSuccess: (_, variables) => {
// queryClient.invalidateQueries({ queryKey: ['workspace', variables.workspaceId] });
// },
// });
// };

View File

@ -1,21 +1,21 @@
import { http, HttpResponse } from 'msw';
import {
BaseResponse,
ProjectResponseDTO,
FolderResponseDTO,
ImageResponseDTO,
WorkspaceResponseDTO,
MemberResponseDTO,
RefreshTokenResponseDTO,
AutoLabelingResponseDTO,
ProjectListResponseDTO,
ProjectResponse,
FolderResponse,
ImageResponse,
WorkspaceResponse,
MemberResponse,
RefreshTokenResponse,
AutoLabelingResponse,
ProjectListResponse,
} from '@/types';
export const handlers = [
// Auth Handlers
http.post('/api/auth/reissue', () => {
// 토큰 재발급 핸들러
const response: BaseResponse<RefreshTokenResponseDTO> = {
const response: BaseResponse<RefreshTokenResponse> = {
status: 200,
code: 0,
message: '토큰 재발급 성공',
@ -28,7 +28,7 @@ export const handlers = [
http.get('/api/auth/profile', () => {
// 사용자 프로필 핸들러
const response: BaseResponse<MemberResponseDTO> = {
const response: BaseResponse<MemberResponse> = {
status: 200,
code: 0,
message: '사용자 정보 가져오기 성공',
@ -47,7 +47,7 @@ export const handlers = [
http.get('/api/workspaces/:workspaceId', ({ params }) => {
// 워크스페이스 조회 핸들러
const { workspaceId } = params;
const response: BaseResponse<WorkspaceResponseDTO> = {
const response: BaseResponse<WorkspaceResponse> = {
status: 200,
code: 0,
message: '워크스페이스 조회 성공',
@ -68,7 +68,7 @@ export const handlers = [
http.put('/api/workspaces/:workspaceId', ({ params }) => {
// 워크스페이스 수정 핸들러
const { workspaceId } = params;
const response: BaseResponse<WorkspaceResponseDTO> = {
const response: BaseResponse<WorkspaceResponse> = {
status: 200,
code: 0,
message: '워크스페이스 수정 성공',
@ -102,7 +102,7 @@ export const handlers = [
http.get('/api/workspaces', () => {
// 워크스페이스 목록 조회 핸들러
const response: BaseResponse<{ workspaceResponses: WorkspaceResponseDTO[] }> = {
const response: BaseResponse<{ workspaceResponses: WorkspaceResponse[] }> = {
status: 200,
code: 0,
message: '워크스페이스 목록 조회 성공',
@ -128,7 +128,7 @@ export const handlers = [
http.get('/api/projects/:projectId', ({ params }) => {
// 프로젝트 조회 핸들러
const { projectId } = params;
const response: BaseResponse<ProjectResponseDTO> = {
const response: BaseResponse<ProjectResponse> = {
status: 200,
code: 0,
message: '프로젝트 조회 성공',
@ -148,7 +148,7 @@ export const handlers = [
http.post('/api/workspaces/:workspaceId/projects', () => {
// 프로젝트 생성 핸들러
const response: BaseResponse<ProjectResponseDTO> = {
const response: BaseResponse<ProjectResponse> = {
status: 200,
code: 0,
message: '프로젝트 생성 성공',
@ -169,7 +169,7 @@ export const handlers = [
http.put('/api/projects/:projectId', ({ params }) => {
// 프로젝트 수정 핸들러
const { projectId } = params;
const response: BaseResponse<ProjectResponseDTO> = {
const response: BaseResponse<ProjectResponse> = {
status: 200,
code: 0,
message: '프로젝트 수정 성공',
@ -204,7 +204,7 @@ export const handlers = [
http.get('/api/projects/:projectId/folders/:folderId', ({ params }) => {
const { folderId } = params;
const response: BaseResponse<FolderResponseDTO> = {
const response: BaseResponse<FolderResponse> = {
status: 200,
code: 0,
message: '폴더 조회 성공',
@ -241,7 +241,7 @@ export const handlers = [
field: 'id',
code: '1001',
message: 'ID format issue',
objectName: 'FolderResponseDTO',
objectName: 'FolderResponse',
},
],
isSuccess: true,
@ -261,7 +261,7 @@ export const handlers = [
const limit = parseInt(url.searchParams.get('limit') || '10', 10);
// 프로젝트 데이터 예시 생성
const projects: ProjectResponseDTO[] = Array.from({ length: limit }, (_, index) => ({
const projects: ProjectResponse[] = Array.from({ length: limit }, (_, index) => ({
id: lastProjectId + index + 1,
title: `프로젝트 ${lastProjectId + index + 1}`,
workspaceId,
@ -274,7 +274,7 @@ export const handlers = [
}));
// 응답 생성
const response: BaseResponse<ProjectListResponseDTO> = {
const response: BaseResponse<ProjectListResponse> = {
status: 200,
code: 0,
message: '프로젝트 목록 조회 성공',
@ -290,7 +290,7 @@ export const handlers = [
http.post('/api/projects/:projectId/folders', () => {
// 폴더 생성 핸들러
const response: BaseResponse<FolderResponseDTO> = {
const response: BaseResponse<FolderResponse> = {
status: 200,
code: 0,
message: '폴더 생성 성공',
@ -310,7 +310,7 @@ export const handlers = [
http.get('/api/projects/:projectId/folders/:folderId/images/:imageId', ({ params }) => {
// 이미지 조회 핸들러
const { imageId } = params;
const response: BaseResponse<ImageResponseDTO> = {
const response: BaseResponse<ImageResponse> = {
status: 200,
code: 0,
message: '이미지 조회 성공',
@ -434,8 +434,8 @@ export const handlers = [
// "imageDepth": 4
// }`;
// AutoLabelingResponseDTO 예시
const response: BaseResponse<AutoLabelingResponseDTO> = {
// AutoLabelingResponse 예시
const response: BaseResponse<AutoLabelingResponse> = {
status: 200,
code: 0,
message: '프로젝트 오토 레이블링 성공',

View File

@ -1,45 +1,27 @@
import { useRef } from 'react';
import { useNavigate } from 'react-router-dom';
import { Link } from 'react-router-dom';
import GoogleLogo from '@/assets/icons/web_neutral_rd_ctn@1x.png';
import useAuthStore from '@/stores/useAuthStore';
import { Button } from '@/components/ui/button';
import { fetchProfileApi, reissueTokenApi } from '@/api/authApi';
import { SuccessResponse, MemberResponseDTO, CustomError } from '@/types';
import { AxiosError } from 'axios';
import { getProfile, reissueToken } from '@/api/authApi';
const BASE_URL = import.meta.env.VITE_API_URL;
export default function Home() {
const navigate = useNavigate();
const { isLoggedIn, accessToken, setLoggedIn, profile, setProfile } = useAuthStore();
const hasFetchedProfile = useRef(false);
if (!isLoggedIn && !profile && !hasFetchedProfile.current && accessToken) {
setLoggedIn(true, accessToken);
fetchProfileApi()
.then((data: SuccessResponse<MemberResponseDTO>) => {
if (data?.isSuccess && data.data) {
setProfile(data.data);
hasFetchedProfile.current = true;
}
})
.catch((error: AxiosError<CustomError>) => {
alert('프로필을 가져오는 중 오류가 발생했습니다. 다시 시도해주세요.');
console.error('프로필 가져오기 실패:', error?.response?.data?.message || '알 수 없는 오류');
});
getProfile().then((data) => {
setProfile(data);
hasFetchedProfile.current = true;
});
}
const handleGoogleSignIn = () => {
window.location.href = `${BASE_URL}/api/login/oauth2/authorization/google`;
};
const handleStart = () => {
navigate('/browse');
};
const handleReissueToken = async () => {
try {
const response = await reissueTokenApi();
const response = await reissueToken();
console.log('토큰 재발급 성공:', response);
alert('토큰 재발급 성공! 새로운 액세스 토큰을 콘솔에서 확인하세요.');
} catch (error) {
@ -72,8 +54,9 @@ export default function Home() {
</div>
{!isLoggedIn ? (
<button
onClick={handleGoogleSignIn}
<Link
to={`${BASE_URL}/login/oauth2/authorization/google`}
// onClick={handleGoogleSignIn}
className="mb-4 transition hover:opacity-90 focus:outline-none focus:ring-2 focus:ring-gray-300 active:opacity-80"
>
<img
@ -81,15 +64,15 @@ export default function Home() {
alt="Sign in with Google"
className="h-auto w-full"
/>
</button>
</Link>
) : (
<>
<Button
asChild
variant="outlinePrimary"
size="lg"
onClick={handleStart}
>
<Link to="/browse"></Link>
</Button>
<Button
variant="outlinePrimary"

View File

@ -1,52 +1,48 @@
import { useParams } from 'react-router-dom';
import { useNavigate, useParams } from 'react-router-dom';
import ProjectCard from '@/components/ProjectCard';
import { Smile } from 'lucide-react';
import ProjectCreateModal from '../ProjectCreateModal';
import { useGetAllProjects, useCreateProject } from '@/hooks/useProjectHooks';
import ProjectCreateModal from '../components/ProjectCreateModal';
import useAuthStore from '@/stores/useAuthStore';
import { ProjectResponseDTO } from '@/types';
import { ProjectResponse } from '@/types';
import useProjectListQuery from '@/queries/useProjectListQuery';
import { webPath } from '@/router';
export default function WorkspaceBrowseDetail() {
const { workspaceId } = useParams<{ workspaceId: string }>();
const numericWorkspaceId = Number(workspaceId);
const params = useParams<{ workspaceId: string }>();
const workspaceId = Number(params.workspaceId);
const { profile } = useAuthStore();
const memberId = profile?.id ?? 0;
const navigate = useNavigate();
// const createProject = useCreateProject();
const {
data: projectsResponse,
isLoading,
isError,
refetch,
} = useGetAllProjects(numericWorkspaceId, memberId, {
enabled: !isNaN(numericWorkspaceId),
});
const createProject = useCreateProject();
const { data: projectsResponse, isError } = useProjectListQuery(workspaceId, memberId);
const handleCreateProject = (data: { title: string; labelType: 'classification' | 'detection' | 'segmentation' }) => {
createProject.mutate(
{
workspaceId: numericWorkspaceId,
memberId,
data: { title: data.title, projectType: data.labelType },
},
{
onSuccess: () => {
console.log('프로젝트가 성공적으로 생성되었습니다.');
refetch();
},
onError: (error) => {
console.error('프로젝트 생성 실패:', error);
const errorMessage = error?.response?.data?.message || error.message || '알 수 없는 오류';
console.error('프로젝트 생성 실패:', errorMessage);
},
}
);
console.log(data);
// createProject.mutate(
// {
// workspaceId: workspaceId,
// memberId,
// data: { title: data.title, projectType: data.labelType },
// },
// {
// onSuccess: () => {
// console.log('프로젝트가 성공적으로 생성되었습니다.');
// refetch();
// },
// onError: (error) => {
// console.error('프로젝트 생성 실패:', error);
// const errorMessage = error?.response?.data?.message || error.message || '알 수 없는 오류';
// console.error('프로젝트 생성 실패:', errorMessage);
// },
// }
// );
};
const projects: ProjectResponseDTO[] = projectsResponse?.data?.workspaceResponses ?? [];
// TODO: 반환 형식 반영 projectsResponse?.workspaceResponses => projectResponse
const projects: ProjectResponse[] = projectsResponse?.workspaceResponses ?? [];
if (isNaN(numericWorkspaceId)) {
if (isNaN(workspaceId)) {
return (
<div className="flex h-full w-full flex-col items-center justify-center">
<div className="flex flex-col items-center">
@ -60,10 +56,6 @@ export default function WorkspaceBrowseDetail() {
);
}
if (isLoading) {
return <p>Loading projects...</p>;
}
if (isError || !workspaceId) {
return (
<div className="flex h-full w-full flex-col items-center justify-center">
@ -95,13 +87,13 @@ export default function WorkspaceBrowseDetail() {
</div>
{projects.length > 0 ? (
<div className="flex flex-wrap gap-6">
{projects.map((project: ProjectResponseDTO) => (
{projects.map((project: ProjectResponse) => (
<ProjectCard
key={project.id}
title={project.title}
description={project.projectType}
onClick={() => {
console.log('project id : ' + project.id);
navigate(`${webPath.workspace()}/${workspaceId}/project/${project.id}`);
}}
/>
))}

View File

@ -0,0 +1,7 @@
export default function WorkspaceBrowseIndex() {
return (
<div className="body-strong flex h-full w-full items-center justify-center text-gray-400">
</div>
);
}

View File

@ -0,0 +1,9 @@
import { getProjectList } from '@/api/projectApi';
import { useSuspenseQuery } from '@tanstack/react-query';
export default function useProjectListQuery(workspaceId: number, memberId: number) {
return useSuspenseQuery({
queryKey: ['projects', workspaceId, memberId],
queryFn: () => getProjectList(workspaceId, memberId),
});
}

View File

@ -1,9 +1,9 @@
import { fetchWorkspaceList } from '@/api/workspaceApi';
import { getWorkspaceList } from '@/api/workspaceApi';
import { useSuspenseQuery } from '@tanstack/react-query';
export default function useWorkspaceListQuery(memberId: number, lastWorkspaceId?: number, limit?: number) {
return useSuspenseQuery({
queryKey: ['workspaceList'],
queryFn: () => fetchWorkspaceList(memberId, lastWorkspaceId, limit),
queryFn: () => getWorkspaceList(memberId, lastWorkspaceId, limit),
});
}

View File

@ -1,9 +1,9 @@
import { fetchWorkspace } from '@/api/workspaceApi';
import { getWorkspace } from '@/api/workspaceApi';
import { useSuspenseQuery } from '@tanstack/react-query';
export default function useWorkspaceQuery(workspaceId: number, memberId: number) {
return useSuspenseQuery({
queryKey: ['workspace', workspaceId, memberId],
queryFn: () => fetchWorkspace(workspaceId, memberId),
queryFn: () => getWorkspace(workspaceId, memberId),
});
}

View File

@ -1,7 +1,7 @@
import PageLayout from '@/components/PageLayout';
import ImageCanvas from '@/components/ImageCanvas';
import Home from '@/components/Home';
import WorkspaceBrowseDetail from '@/components/WorkspaceBrowseDetail';
import Home from '@/pages/Home';
import WorkspaceBrowseDetail from '@/pages/WorkspaceBrowseDetail';
import WorkspaceBrowseLayout from '@/components/WorkspaceBrowseLayout';
import WorkspaceLayout from '@/components/WorkspaceLayout';
import AdminLayout from '@/components/AdminLayout';
@ -10,6 +10,8 @@ import AdminMemberManage from '@/components/AdminMemberManage';
import OAuthCallback from '@/components/OAuthCallback';
import { createBrowserRouter } from 'react-router-dom';
import { Navigate } from 'react-router-dom';
import { Suspense } from 'react';
import WorkspaceBrowseIndex from '@/pages/WorkspaceBrowseIndex';
export const webPath = {
home: () => '/',
@ -33,12 +35,17 @@ const router = createBrowserRouter([
],
},
{
// FIXME: index에서 오류나지 않게 수정
path: webPath.browse(),
element: <WorkspaceBrowseLayout />,
element: (
<Suspense fallback={<div></div>}>
<WorkspaceBrowseLayout />
</Suspense>
),
children: [
{
index: true,
element: <WorkspaceBrowseDetail />,
element: <WorkspaceBrowseIndex />,
},
{
path: ':workspaceId',
@ -47,8 +54,13 @@ const router = createBrowserRouter([
],
},
{
// FIXME: index에서 오류나지 않게 수정
path: `${webPath.workspace()}/:workspaceId`,
element: <WorkspaceLayout />,
element: (
<Suspense fallback={<div></div>}>
<WorkspaceLayout />
</Suspense>
),
children: [
{
index: true,

View File

@ -1,13 +1,13 @@
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import { MemberResponseDTO } from '@/types';
import { MemberResponse } from '@/types';
interface AuthState {
isLoggedIn: boolean;
accessToken: string;
profile: MemberResponseDTO | null;
profile: MemberResponse | null;
setLoggedIn: (status: boolean, token: string) => void;
setProfile: (profile: MemberResponseDTO) => void;
setProfile: (profile: MemberResponse) => void;
clearAuth: () => void;
}
@ -17,8 +17,8 @@ const useAuthStore = create<AuthState>()(
isLoggedIn: false,
accessToken: '',
profile: null,
setLoggedIn: (status, token) => set({ isLoggedIn: status, accessToken: token }),
setProfile: (profile) => set({ profile }),
setLoggedIn: (status: boolean, token: string) => set({ isLoggedIn: status, accessToken: token }),
setProfile: (profile: MemberResponse) => set({ profile }),
clearAuth: () => set({ isLoggedIn: false, accessToken: '', profile: null }),
}),
{

View File

@ -1,11 +1,11 @@
import { create } from 'zustand';
import { FolderResponseDTO } from '@/types';
import { FolderResponse } from '@/types';
interface FolderState {
folder: FolderResponseDTO | null;
folder: FolderResponse | null;
loading: boolean;
error: string | null;
setFolder: (folder: FolderResponseDTO | null) => void;
setFolder: (folder: FolderResponse | null) => void;
setLoading: (loading: boolean) => void;
setError: (error: string | null) => void;
}