Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
ab29621
refactor: 타입 맞추기 (#111)
AndyH0ng Feb 2, 2026
e0566ff
fix: API 경로 수정 (#111)
AndyH0ng Feb 2, 2026
e7fd61a
fix: Presentation API 연결 (#111)
AndyH0ng Feb 2, 2026
b5ec59b
fix: /presentations API 연결 (#111)
AndyH0ng Feb 2, 2026
4a8a219
Merge branch 'develop' into refactor/setting-type-111
AndyH0ng Feb 2, 2026
ff9abe7
feat: /comments API 연결 (#111)
AndyH0ng Feb 2, 2026
d8bbbe1
Merge branch 'refactor/setting-type-111' of https://github.com/TTORAN…
AndyH0ng Feb 2, 2026
295b1ae
feat: 구글 로그인 연동 (#111)
AndyH0ng Feb 2, 2026
6523ec7
feat: 네이버 카카오 로그인 연동 (#111)
AndyH0ng Feb 2, 2026
a026d8f
feat: 로그인 콜백 및 라우팅 (#111)
AndyH0ng Feb 2, 2026
778b328
design: 제목 수정 팝오버 사용성 개선 (#111)
AndyH0ng Feb 2, 2026
59b5a03
fix: 빌드 오류 해결 (#111)
AndyH0ng Feb 2, 2026
06f29fb
refactor: api 연동 구조 변경 (#111)
kimyw1018 Feb 3, 2026
44f486e
refactor: 빌드에러 수정(#111)
kimyw1018 Feb 4, 2026
5e75db5
fix: 무한루프 해결(#111)
kimyw1018 Feb 4, 2026
eed8303
fix: SocialLoginSuccessResponseDto로 업데이트 (#111)
AndyH0ng Feb 4, 2026
05bda5c
refactor: Script 타입을 DTO로 통합 (#111)
AndyH0ng Feb 4, 2026
3ecaf2e
refactor: Slide 타입을 DTO로 통합 (#111)
AndyH0ng Feb 4, 2026
0d632a5
refactor: Comment 타입을 DTO로 통합 (#111)
AndyH0ng Feb 4, 2026
435a0d5
fix: DTO export 누락 및 import 오류 수정 (#111)
AndyH0ng Feb 4, 2026
7e65f9c
Merge branch 'refactor/setting-type-111' of https://github.com/TTORAN…
kimyw1018 Feb 4, 2026
e27ed4a
refactor: Video DTO 구조 통일 (#111)
AndyH0ng Feb 4, 2026
d1a545b
Merge branch 'refactor/setting-type-111' of https://github.com/TTORAN…
AndyH0ng Feb 4, 2026
e633cfb
Merge branch 'develop' into refactor/setting-type-111
AndyH0ng Feb 4, 2026
73dd156
fix: 빌드 오류 2/3 해결 (#111)
AndyH0ng Feb 4, 2026
411b6ee
fix: Recording props 수정(#111)
kimyw1018 Feb 4, 2026
02119e3
fix: 클로드 리뷰 반영 (#111)
kimyw1018 Feb 4, 2026
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
26 changes: 18 additions & 8 deletions src/api/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,21 @@ import { useAuthStore } from '@/stores/authStore';
export const API_TIMEOUT_MS = 10000;

/**
* API 에러 응답 타입
* API 에러 응답 타입 (FAILURE 응답의 error 필드)
*/
export interface ApiError {
message: string;
code?: string;
status?: number;
export interface ApiErrorResponse {
errorCode: string;
reason: string;
data?: unknown;
}

/**
* API FAILURE 응답 구조
*/
export interface ApiFailureResponse {
resultType: 'FAILURE';
error: ApiErrorResponse;
success: null;
}

/**
Expand Down Expand Up @@ -69,14 +78,15 @@ apiClient.interceptors.request.use(
*/
apiClient.interceptors.response.use(
(response) => response,
(error: AxiosError<ApiError>) => {
(error: AxiosError<ApiFailureResponse>) => {
const status = error.response?.status;
const message = error.response?.data?.message || '알 수 없는 오류가 발생했습니다';
const errorData = error.response?.data?.error;
const reason = errorData?.reason || '알 수 없는 오류가 발생했습니다';

// [하이브리드 전략]
// 시스템 에러 (401 인증, 500 서버 장애)는 Axios가 즉시 처리
if (status === 401 || (status && status >= 500)) {
handleApiError(status, message);
handleApiError(status, reason);
// 다운스트림(React Query 등)에서 중복 처리하지 않도록 플래그 설정
error.isHandled = true;
}
Expand Down
10 changes: 10 additions & 0 deletions src/api/dto/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/**
* @file index.ts
* @description DTO 배럴 export
*/

export type { CreateProjectDto, UpdateProjectDto } from './projects.dto';
export type { CreateSlideDto, UpdateSlideDto } from './slides.dto';
export type { UpdateScriptDto, RestoreScriptDto } from './scripts.dto';
export type { CreateOpinionDto } from './opinions.dto';
export type { ToggleSlideReactionDto } from './reactions.dto';
8 changes: 8 additions & 0 deletions src/api/dto/opinions.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/**
* 의견(댓글) 생성 요청 DTO
*/
export interface CreateOpinionDto {
content: string;
/** 답글인 경우 부모 의견 ID */
parentId?: string;
}
14 changes: 14 additions & 0 deletions src/api/dto/projects.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* 프로젝트 수정 요청 DTO
*/
export interface UpdateProjectDto {
title: string;
}

/**
* 프로젝트 생성 요청 DTO
*/
export interface CreateProjectDto {
title: string;
uploadFileId: string;
}
8 changes: 8 additions & 0 deletions src/api/dto/reactions.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import type { ReactionType } from '@/types/script';

/**
* 슬라이드 리액션 토글 요청 DTO
*/
export interface ToggleSlideReactionDto {
type: ReactionType;
}
13 changes: 13 additions & 0 deletions src/api/dto/scripts.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/**
* 대본 저장 요청 DTO
*/
export interface UpdateScriptDto {
script: string;
}

/**
* 대본 복원 요청 DTO
*/
export interface RestoreScriptDto {
version: number;
}
14 changes: 14 additions & 0 deletions src/api/dto/slides.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* 슬라이드 제목 수정 요청 DTO
*/
export interface UpdateSlideDto {
title?: string;
}

/**
* 슬라이드 생성 요청 DTO
*/
export interface CreateSlideDto {
title: string;
script?: string;
}
27 changes: 17 additions & 10 deletions src/api/endpoints/opinions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,15 @@
* @description 의견(댓글) 관련 API 엔드포인트
*/
import { apiClient } from '@/api';
import type { CreateOpinionDto } from '@/api/dto';
import type { ApiResponse } from '@/types/api';
import type { Comment } from '@/types/comment';

/**
* 의견 생성 요청 타입
* 의견 생성 요청 타입 (하위 호환성)
* @deprecated CreateOpinionDto 사용 권장
*/
export interface CreateOpinionRequest {
content: string;
/** 답글인 경우 부모 의견 ID */
parentId?: string;
}
export type CreateOpinionRequest = CreateOpinionDto;

/**
* 의견 추가
Expand All @@ -21,9 +20,13 @@ export interface CreateOpinionRequest {
* @param data - 의견 데이터
* @returns 생성된 의견
*/
export async function createOpinion(slideId: string, data: CreateOpinionRequest): Promise<Comment> {
const response = await apiClient.post<Comment>(`/slides/${slideId}/opinions`, data);
return response.data;
export async function createOpinion(slideId: string, data: CreateOpinionDto): Promise<Comment> {
const response = await apiClient.post<ApiResponse<Comment>>(`/slides/${slideId}/opinions`, data);

if (response.data.resultType === 'SUCCESS') {
return response.data.success;
}
throw new Error(response.data.error.reason);
}

/**
Expand All @@ -32,5 +35,9 @@ export async function createOpinion(slideId: string, data: CreateOpinionRequest)
* @param opinionId - 삭제할 의견 ID
*/
export async function deleteOpinion(opinionId: string): Promise<void> {
await apiClient.delete(`/opinions/${opinionId}`);
const response = await apiClient.delete<ApiResponse<null>>(`/opinions/${opinionId}`);

if (response.data.resultType === 'FAILURE') {
throw new Error(response.data.error.reason);
}
}
118 changes: 80 additions & 38 deletions src/api/endpoints/presentations.ts
Original file line number Diff line number Diff line change
@@ -1,76 +1,118 @@
/**
* @file projects.ts
* @file presentations.ts
* @description 프로젝트 관련 API 엔드포인트
*
* 서버와 통신하는 함수들을 정의합니다.
* 이 함수들은 직접 호출하지 않고, hooks/queries에서 사용합니다.
*
* 위에 interface로 받는 타입 정의 해주고
* 아래에서 endpoint 맞춰주고
*/
import type { Presentation } from '@/types/presentation';

import { apiClient } from '../client';
import { apiClient } from '@/api';
import type { UpdateProjectDto } from '@/api/dto';
import type { ApiResponse, ConversionStatusResponse } from '@/types/api';
import type {
CreatePresentationRequest,
CreatePresentationSuccess,
Presentation,
PresentationListResponse,
ProjectUpdateResponse,
} from '@/types/presentation';

/**
* 프로젝트 목록 조회
* 프로젝트 목록 조회 (GET)
*
* 각 프로젝트는 id를 포함하며, 수정/삭제 시 이 id를 사용함.
* @returns 프로젝트 배열
* @returns Presentation[]
*/
export async function getPresentations(): Promise<Presentation[]> {
const response = await apiClient.get<Presentation[]>(`/presentations`);
return response.data;
const response = await apiClient.get<ApiResponse<PresentationListResponse>>(`/presentations`);

if (response.data.resultType === 'SUCCESS') {
return response.data.success.presentations;
}
throw new Error(response.data.error.reason);
}

/**
* 프로젝트 상세 조회
*
* @param projectId - 프로젝트 ID
* @param projectId
* @returns Presentation
*/
export async function getPresentation(): Promise<Presentation> {
const response = await apiClient.get<Presentation>(`/presentations`);
return response.data;
}
export async function getPresentation(projectId: string): Promise<Presentation> {
const response = await apiClient.get<ApiResponse<Presentation>>(`/presentations/${projectId}`);

/**
* 프로젝트 수정 요청 타입
*/
export interface UpdatePresentationRequest {
title?: string;
if (response.data.resultType === 'SUCCESS') {
return response.data.success;
}
throw new Error(response.data.error.reason);
}

/**
* 프로젝트 수정
* 프로젝트 제목 수정 (PATCH)
*
* @param projectId - 수정할 프로젝트 ID
* @param data - 수정할 데이터
* @returns 수정된 프로젝트
* @param projectId
* @param data - 수정할 프로젝트 데이터
* @returns ProjectUpdateResponse - 수정된 프로젝트 정보
*/
export async function updatePresentation(
projectId: string,
data: UpdatePresentationRequest,
): Promise<Presentation> {
const response = await apiClient.patch<Presentation>(`/presentations/${projectId}`, data);
return response.data;
data: UpdateProjectDto,
): Promise<ProjectUpdateResponse> {
const response = await apiClient.patch<ApiResponse<ProjectUpdateResponse>>(
`/presentations/${projectId}`,
data,
);

if (response.data.resultType === 'SUCCESS') {
return response.data.success;
}
throw new Error(response.data.error.reason);
}

/**
* 프로젝트 생성
* 프로젝트 생성 (POST)
*
* @param data - 생성할 프로젝트 데이터
* @returns 생성된 프로젝트
* @returns CreatePresentationSuccess - 생성된 프로젝트 정보
*/
export async function createPresentation(data: { title: string }): Promise<Presentation> {
const response = await apiClient.post<Presentation>(`/presentations`, data);
return response.data;
export async function createPresentation(
data: CreatePresentationRequest,
): Promise<CreatePresentationSuccess> {
const response = await apiClient.post<ApiResponse<CreatePresentationSuccess>>(
`/presentations`,
data,
);

if (response.data.resultType === 'SUCCESS') {
return response.data.success;
}
throw new Error(response.data.error.reason);
}

/**
* 프로젝트 삭제
* 프로젝트 삭제 (DELETE)
*
* @param projectId - 삭제할 프로젝트 ID
* @param projectId
*/
export async function deletePresentation(projectId: string): Promise<void> {
await apiClient.delete(`/presentations/${projectId}`);
const response = await apiClient.delete<ApiResponse<null>>(`/presentations/${projectId}`);

if (response.data.resultType === 'FAILURE') {
throw new Error(response.data.error.reason);
}
}

/**
* 프로젝트 파일 변환 상태 조회 (GET)
*
* @param projectId - 프로젝트 ID
* @returns ConversionStatusResponse - 변환 상태 정보
*/
export async function getConversionStatus(projectId: string): Promise<ConversionStatusResponse> {
const response = await apiClient.get<ApiResponse<ConversionStatusResponse>>(
`/presentations/${projectId}/conversion-status`,
);

if (response.data.resultType === 'SUCCESS') {
return response.data.success;
}
throw new Error(response.data.error.reason);
}
41 changes: 33 additions & 8 deletions src/api/endpoints/reactions.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,36 @@
/**
* @file reactions.ts
* @description 슬라이드 리액션 관련 API 엔드포인트
*/
import { apiClient } from '@/api';
import type { Reaction, ReactionType } from '@/types/script';
import type { ToggleSlideReactionDto } from '@/api/dto';
import type { ApiResponse } from '@/types/api';
import type { Reaction } from '@/types/script';

export interface ToggleReactionRequest {
type: ReactionType;
}
/**
* 리액션 토글 요청 타입 (하위 호환성)
* @deprecated ToggleSlideReactionDto 사용 권장
*/
export type ToggleReactionRequest = ToggleSlideReactionDto;

/**
* 슬라이드 리액션 토글
*
* @param slideId - 슬라이드 ID
* @param data - 리액션 데이터
* @returns 업데이트된 리액션 배열
*/
export async function toggleReaction(
slideId: string,
data: ToggleSlideReactionDto,
): Promise<Reaction[]> {
const response = await apiClient.post<ApiResponse<Reaction[]>>(
`/slides/${slideId}/reactions`,
data,
);

export const toggleReaction = async (slideId: string, data: ToggleReactionRequest) => {
const { data: response } = await apiClient.post<Reaction[]>(`/slides/${slideId}/reactions`, data);
return response;
};
if (response.data.resultType === 'SUCCESS') {
return response.data.success;
}
throw new Error(response.data.error.reason);
}
Loading
Loading