diff --git a/src/api/dto/comments.dto.ts b/src/api/dto/comments.dto.ts index 0b396998..de4f092c 100644 --- a/src/api/dto/comments.dto.ts +++ b/src/api/dto/comments.dto.ts @@ -1,48 +1,3 @@ -/** - * 답글작성 - */ -export interface CreateReplyCommentRequestDto { - commentId: string; -} - -/** - * 답글작성 response DTO - */ -export interface CreateReplyCommentResponseDto { - id: string; - content: string; - parentId: string; - userId: string; - createdAt: string; -} - -/** - * 답글 목록 조회 DTO - */ -export interface GetRepliesResponseDto { - comments: Array<{ - id: string; - content: string; - parentId: string | null; - userId: string; - createdAt: string; - }>; -} -/** - * 슬라이드 댓글 작성 - */ -export interface CreateCommentRequestDto { - slideId: string; -} -/** - * 슬라이드 댓글 작성 response DTO - */ -export interface CreateCommentResponseDto { - id: string; - content: string; - userId: string; - createdAt: string; -} /** * 댓글 작성자 정보 */ @@ -76,37 +31,86 @@ export interface GetSlideCommentsResponseDto { } /** - * 댓글 생성/수정 응답 + * 답글 목록 조회 응답 + */ +export interface GetReplyListResponseDto { + comments: CommentWithUserDto[]; + pagination: { + page: number; + limit: number; + total: number; + totalPages: number; + }; +} + +/** + * 슬라이드 댓글 생성 요청 */ -export interface CommentResponseDto { +export interface CreateCommentRequestDto { + content: string; +} + +/** + * 슬라이드 댓글 생성 응답 + */ +export interface CreateCommentResponseDto { commentId: string; content: string; - parentId?: string; userId: string; createdAt: string; } /** - * 답글 목록 조회 응답 + * 답글 생성 요청 + */ +export interface CreateReplyCommentRequestDto { + content: string; +} + +/** + * 답글 생성 응답 */ -export type GetReplyListResponseDto = CommentResponseDto[]; +export interface CreateReplyCommentResponseDto { + parentCommentId: string; + replyId: string; + content: string; + userId: string; + createdAt: string; +} + /** - * 댓글 수정 + * 댓글/답글 수정 응답 */ export interface UpdateCommentResponseDto { - commentId: string; + updatedTargetType: 'comment' | 'reply'; + commentId?: string; + replyId?: string; + parentCommentId?: string; content: string; userId: string; createdAt: string; + updatedAt: string; } + /** - * 댓글 및, 답글 삭제 + * 댓글/답글 삭제 요청 */ export interface DeleteCommentRequestDto { commentId: string; } + +/** + * 댓글/답글 삭제 응답 + */ +export interface DeleteCommentResponseDto { + deletedTargetType: 'comment' | 'reply'; + commentId?: string; + replyId?: string; + parentCommentId?: string; +} + /** - * 영상 타임스탬프 댓글 생성 + * 영상 타임스탬프 댓글 생성 요청 */ export interface CreateVideoCommentRequestDto { content: string; diff --git a/src/api/dto/index.ts b/src/api/dto/index.ts index ff2c0dde..d5cc323c 100644 --- a/src/api/dto/index.ts +++ b/src/api/dto/index.ts @@ -22,24 +22,41 @@ export type { } from './scripts.dto'; export type { ReadReactionCountDto, + ReadVideoReactionSummaryItemDto, + ReadVideoReactionTimelineResponseDto, + ReadVideoReactionTimelineMarkerDto, ToggleSlideReactionDto, ToggleSlideReactionResponseDto, ToggleVideoReactionDto, ToggleVideoReactionResponseDto, } from './reactions.dto'; export type { RestoreScriptRequestDto } from './analytics.dto'; -export type { UpdateProjectDto } from './presentations.dto'; +export type { + DeleteProjectResponseDto, + GetPresentationsRequestDto, + UpdateProjectRequestDto, + UpdateProjectResponseDto, +} from './presentations.dto'; // export type { UploadFileResponseDto } from './files.dto'; export type { ChunkUploadResponseDto, - CreateCommentDto, FinishVideoRequestDto, FinishVideoResponseDto, + GetProjectVideosResponseDto, + GetVideoDetailResponseDto, + GetVideoSlidesResponseDto, StartVideoRequestDto, StartVideoResponseDto, + VideoDetailDto, + VideoListItemDto, + VideoSlideTimelineItemDto, + VideoStatus, + VideoTimelineCommentDto, + VideoTimelineCommentUserDto, + VideoTimelineDto, + VideoTimelineReactionDto, } from './video.dto'; export type { - CommentResponseDto, CommentUserDto, CommentWithUserDto, CreateCommentRequestDto, @@ -48,7 +65,7 @@ export type { CreateReplyCommentResponseDto, CreateVideoCommentRequestDto, DeleteCommentRequestDto, - GetRepliesResponseDto, + DeleteCommentResponseDto, GetReplyListResponseDto, GetSlideCommentsResponseDto, UpdateCommentResponseDto, diff --git a/src/api/dto/presentations.dto.ts b/src/api/dto/presentations.dto.ts index 1819cbe4..a9cffbf8 100644 --- a/src/api/dto/presentations.dto.ts +++ b/src/api/dto/presentations.dto.ts @@ -1,6 +1,33 @@ /** * 프로젝트 제목 수정 요청 DTO */ -export interface UpdateProjectDto { +export interface UpdateProjectRequestDto { title?: string; } + +/** + * 프로젝트 제목 수정 응답 DTO + */ +export interface UpdateProjectResponseDto { + projectId: string; + title: string; + updatedAt: string; +} + +/** + * 프로젝트 목록 조회 요청 DTO + */ +export interface GetPresentationsRequestDto { + page?: number; + limit?: number; + search?: string; + maxDuration?: number; + sort?: string; +} + +/** + * 프로젝트 삭제 응답 DTO + */ +export interface DeleteProjectResponseDto { + message: string; +} diff --git a/src/api/dto/reactions.dto.ts b/src/api/dto/reactions.dto.ts index 5a2f81d6..35a369e2 100644 --- a/src/api/dto/reactions.dto.ts +++ b/src/api/dto/reactions.dto.ts @@ -26,6 +26,8 @@ export interface ToggleVideoReactionDto { * 영상 리액션 토글 응답 DTO */ export interface ToggleVideoReactionResponseDto { + reactionId: string; + videoId: string; active: boolean; } @@ -37,6 +39,31 @@ export interface ReadReactionCountDto { reactions: Record; } +/** + * 영상 리액션 구간 집계 항목 Dto + */ +export interface ReadVideoReactionSummaryItemDto { + emojiType: ReactionType; + count: number; +} + +/** + * 영상 리액션 타임라인 마커 Dto + */ +export interface ReadVideoReactionTimelineMarkerDto { + timestampMs: number; + emojiType: ReactionType; + count: number; +} + +/** + * 영상 리액션 타임라인 응답 Dto + */ +export interface ReadVideoReactionTimelineResponseDto { + intervalMs: number; + markers: ReadVideoReactionTimelineMarkerDto[]; +} + /** * 프로젝트 전체 슬라이드 리액션 집계 조회 Dto */ diff --git a/src/api/dto/video.dto.ts b/src/api/dto/video.dto.ts index 22f60a01..02ebccfb 100644 --- a/src/api/dto/video.dto.ts +++ b/src/api/dto/video.dto.ts @@ -1,11 +1,8 @@ -/** - * 영상 타임스탬프 댓글 생성 - */ -export interface CreateCommentDto { - content: string; - /** 답글인 경우 부모 댓글 ID */ - parentId?: string; -} +import type { ReactionType } from '@/types/script'; + +// ============================================================================ +// 영상 녹화 관련 DTO +// ============================================================================ /** * 영상 녹화 시작 요청 DTO @@ -19,7 +16,7 @@ export interface StartVideoRequestDto { * 영상 녹화 시작 응답 DTO (success 데이터) */ export interface StartVideoResponseDto { - videoId: number; + videoId: string; } /** @@ -51,3 +48,111 @@ export interface FinishVideoResponseDto { export interface ChunkUploadResponseDto { ok: boolean; } + +// ============================================================================ +// 영상 상세 조회 DTO — GET /videos/:videoId +// ============================================================================ + +export type VideoStatus = 'processing' | 'ready' | 'failed'; + +/** + * 영상 상세 정보 + */ +export interface VideoDetailDto { + videoId: string; + title: string; + status: VideoStatus; + durationSeconds: number; + width: number; + height: number; + fps: number; + hlsMasterUrl: string; + thumbnailUrl: string | null; + createdAt: string; +} + +/** + * 타임라인 리액션 항목 + */ +export interface VideoTimelineReactionDto { + timestampMs: number; + emojiType: ReactionType; + count: number; +} + +/** + * 타임라인 댓글 작성자 + */ +export interface VideoTimelineCommentUserDto { + userId: string; + name: string; +} + +/** + * 타임라인 댓글 항목 + */ +export interface VideoTimelineCommentDto { + commentId: string; + timestampMs: number; + content: string; + createdAt: string; + user: VideoTimelineCommentUserDto; +} + +/** + * 영상 타임라인 (리액션 + 댓글) + */ +export interface VideoTimelineDto { + reactions: VideoTimelineReactionDto[]; + comments: VideoTimelineCommentDto[]; +} + +/** + * GET /videos/:videoId 응답 DTO + */ +export interface GetVideoDetailResponseDto { + video: VideoDetailDto; + timeline: VideoTimelineDto; +} + +// ============================================================================ +// 영상 목록 조회 DTO — GET /presentations/:projectId/videos +// ============================================================================ + +/** + * 영상 목록 항목 + */ +export interface VideoListItemDto { + videoId: string; + title: string; + status: VideoStatus; + durationSeconds: number; + thumbnailUrl: string | null; + createdAt: string; +} + +/** + * GET /presentations/:projectId/videos 응답 DTO + */ +export interface GetProjectVideosResponseDto { + videos: VideoListItemDto[]; +} + +// ============================================================================ +// 영상-슬라이드 타임라인 DTO — GET /videos/:videoId/slides +// ============================================================================ + +/** + * 영상-슬라이드 타임라인 항목 + */ +export interface VideoSlideTimelineItemDto { + slideId: string; + timestampMs: number; +} + +/** + * GET /videos/:videoId/slides 응답 DTO + */ +export interface GetVideoSlidesResponseDto { + slides: VideoSlideTimelineItemDto[]; +} diff --git a/src/api/endpoints/comments.ts b/src/api/endpoints/comments.ts index 718d4fe2..c81d0ca9 100644 --- a/src/api/endpoints/comments.ts +++ b/src/api/endpoints/comments.ts @@ -4,9 +4,15 @@ */ import { apiClient } from '@/api/client'; import type { - CommentResponseDto, + CreateCommentRequestDto, + CreateCommentResponseDto, + CreateReplyCommentRequestDto, + CreateReplyCommentResponseDto, + DeleteCommentRequestDto, + DeleteCommentResponseDto, GetReplyListResponseDto, GetSlideCommentsResponseDto, + UpdateCommentResponseDto, } from '@/api/dto'; import type { ApiResponse } from '@/types/api'; @@ -45,9 +51,9 @@ export async function getSlideComments( */ export async function createSlideComment( slideId: string, - data: { content: string }, -): Promise { - const response = await apiClient.post>( + data: CreateCommentRequestDto, +): Promise { + const response = await apiClient.post>( `/slides/${slideId}/comments`, data, ); @@ -67,9 +73,9 @@ export async function createSlideComment( */ export async function createReply( commentId: string, - data: { content: string }, -): Promise { - const response = await apiClient.post>( + data: CreateReplyCommentRequestDto, +): Promise { + const response = await apiClient.post>( `/comments/${commentId}/replies`, data, ); @@ -86,9 +92,16 @@ export async function createReply( * @param commentId - 댓글 ID * @returns 답글 목록 */ -export async function getReplies(commentId: string): Promise { +export async function getReplies( + commentId: string, + page = 1, + limit = 20, +): Promise { const response = await apiClient.get>( `/comments/${commentId}/replies`, + { + params: { page, limit }, + }, ); if (response.data.resultType === 'SUCCESS') { @@ -107,8 +120,8 @@ export async function getReplies(commentId: string): Promise { - const response = await apiClient.patch>( +): Promise { + const response = await apiClient.patch>( `/comments/${commentId}`, data, ); @@ -122,12 +135,18 @@ export async function updateComment( /** * 댓글 삭제 * - * @param commentId - 댓글 ID + * @param data - 삭제 대상 정보 + * @returns 삭제 결과 */ -export async function deleteComment(commentId: string): Promise { - const response = await apiClient.delete>(`/comments/${commentId}`); +export async function deleteComment( + data: DeleteCommentRequestDto, +): Promise { + const response = await apiClient.delete>( + `/comments/${data.commentId}`, + ); - if (response.data.resultType === 'FAILURE') { - throw new Error(response.data.error.reason); + if (response.data.resultType === 'SUCCESS') { + return response.data.success; } + throw new Error(response.data.error.reason); } diff --git a/src/api/endpoints/presentations.ts b/src/api/endpoints/presentations.ts index 7556b0ee..ba46f252 100644 --- a/src/api/endpoints/presentations.ts +++ b/src/api/endpoints/presentations.ts @@ -6,24 +6,29 @@ * 이 함수들은 직접 호출하지 않고, hooks/queries에서 사용합니다. */ import { apiClient } from '@/api/client'; -import type { UpdateProjectDto } from '@/api/dto'; -import type { ApiResponse, ConversionStatusResponse } from '@/types/api'; import type { - Presentation, - PresentationListResponse, - ProjectUpdateResponse, -} from '@/types/presentation'; + DeleteProjectResponseDto, + GetPresentationsRequestDto, + UpdateProjectRequestDto, + UpdateProjectResponseDto, +} from '@/api/dto'; +import type { ApiResponse, ConversionStatusResponse } from '@/types/api'; +import type { Presentation, PresentationListResponse } from '@/types/presentation'; /** * 프로젝트 목록 조회 (GET) * * @returns Presentation[] */ -export async function getPresentations(): Promise { - const response = await apiClient.get>(`/presentations`); +export async function getPresentations( + params?: GetPresentationsRequestDto, +): Promise { + const response = await apiClient.get>(`/presentations`, { + params, + }); if (response.data.resultType === 'SUCCESS') { - return response.data.success.presentations; + return response.data.success; } throw new Error(response.data.error.reason); } @@ -48,13 +53,13 @@ export async function getPresentation(projectId: string): Promise * * @param projectId * @param data - 수정할 프로젝트 데이터 - * @returns ProjectUpdateResponse - 수정된 프로젝트 정보 + * @returns UpdateProjectResponseDto - 수정된 프로젝트 정보 */ export async function updatePresentation( projectId: string, - data: UpdateProjectDto, -): Promise { - const response = await apiClient.patch>( + data: UpdateProjectRequestDto, +): Promise { + const response = await apiClient.patch>( `/presentations/${projectId}`, data, ); @@ -70,12 +75,15 @@ export async function updatePresentation( * * @param projectId */ -export async function deletePresentation(projectId: string): Promise { - const response = await apiClient.delete>(`/presentations/${projectId}`); +export async function deletePresentation(projectId: string): Promise { + const response = await apiClient.delete>( + `/presentations/${projectId}`, + ); - if (response.data.resultType === 'FAILURE') { - throw new Error(response.data.error.reason); + if (response.data.resultType === 'SUCCESS') { + return response.data.success; } + throw new Error(response.data.error.reason); } /** diff --git a/src/api/endpoints/videoReactions.ts b/src/api/endpoints/videoReactions.ts index c13df6d4..0165d127 100644 --- a/src/api/endpoints/videoReactions.ts +++ b/src/api/endpoints/videoReactions.ts @@ -4,6 +4,8 @@ */ import { apiClient } from '@/api/client'; import type { + ReadVideoReactionSummaryItemDto, + ReadVideoReactionTimelineResponseDto, ToggleVideoReactionDto, ToggleVideoReactionResponseDto, } from '@/api/dto/reactions.dto'; @@ -32,3 +34,47 @@ export async function toggleVideoReaction( } throw new Error(response.error.reason); } + +/** + * 영상 리액션 (특정 시점) 조회 + * + * @param videoId - 영상 ID + * @param params - timestampMs (필수), windowMs (선택) + * @returns 이모지 타입별 집계 배열 + */ +export async function getVideoReactions( + videoId: string, + params: { timestampMs: number; windowMs?: number }, +): Promise { + const { data } = await apiClient.get>( + `/videos/${videoId}/reactions`, + { params }, + ); + + if (data.resultType === 'SUCCESS') { + return data.success; + } + throw new Error(data.error.reason); +} + +/** + * 영상 리액션 타임라인 조회 + * + * @param videoId - 영상 ID + * @param params - intervalMs (선택) + * @returns 타임라인 마커 + */ +export async function getVideoReactionTimeline( + videoId: string, + params?: { intervalMs?: number }, +): Promise { + const { data } = await apiClient.get>( + `/videos/${videoId}/reactions/timeline`, + { params }, + ); + + if (data.resultType === 'SUCCESS') { + return data.success; + } + throw new Error(data.error.reason); +} diff --git a/src/api/endpoints/videos.ts b/src/api/endpoints/videos.ts index 5b0dec13..2911c30c 100644 --- a/src/api/endpoints/videos.ts +++ b/src/api/endpoints/videos.ts @@ -1,27 +1,17 @@ import { apiClient } from '@/api/client'; import type { ChunkUploadResponseDto, - CommentResponseDto, + CreateCommentResponseDto, + CreateReplyCommentResponseDto, FinishVideoRequestDto, FinishVideoResponseDto, + GetVideoDetailResponseDto, + GetVideoSlidesResponseDto, StartVideoRequestDto, StartVideoResponseDto, } from '@/api/dto'; import type { ApiResponse } from '@/types/api'; -/** - * DTO → Model 변환: CommentResponseDto를 앱 내부용 Model로 변환 - */ -function commentDtoToModel(dto: CommentResponseDto): { - serverId: string; - content: string; -} { - return { - serverId: dto.commentId, - content: dto.content, - }; -} - // ============================================================================ // 레거시 videosApi 객체 (하위 호환성 유지) // useVideoUpload 등 기존 코드에서 사용 중 @@ -32,7 +22,7 @@ export const videosApi = { apiClient.post>('/videos/start', data), // POST /videos/{videoId}/chunks/{chunkIndex} - 청크 업로드 - uploadChunk: (videoId: number, chunkIndex: number, file: Blob) => { + uploadChunk: (videoId: string, chunkIndex: number, file: Blob) => { const formData = new FormData(); formData.append('file', file); @@ -48,14 +38,16 @@ export const videosApi = { }, // POST /videos/{videoId}/finish - 녹화 종료 및 영상 처리 시작 - finishVideo: (videoId: number, data: FinishVideoRequestDto) => + finishVideo: (videoId: string, data: FinishVideoRequestDto) => apiClient.post>(`/videos/${videoId}/finish`, data), // GET /videos/{videoId} - 영상 상세 조회 - getVideoDetail: (videoId: number) => apiClient.get(`/videos/${videoId}`), + getVideoDetail: (videoId: string) => + apiClient.get>(`/videos/${videoId}`), // GET /videos/{videoId}/slides - 슬라이드 타임라인 조회 - getVideoSlides: (videoId: number) => apiClient.get(`/videos/${videoId}/slides`), + getVideoSlides: (videoId: string) => + apiClient.get>(`/videos/${videoId}/slides`), }; // ============================================================================ @@ -71,17 +63,19 @@ export const videosApi = { * @returns Model - serverId와 content */ export async function createVideoComment( - videoId: number, + videoId: string, data: { content: string; timestampMs?: number }, ): Promise<{ serverId: string; content: string }> { - const response = await apiClient.post>( + const response = await apiClient.post>( `/videos/${videoId}/comments`, data, ); if (response.data.resultType === 'SUCCESS') { - // DTO → Model 변환 - return commentDtoToModel(response.data.success); + return { + serverId: response.data.success.commentId, + content: response.data.success.content, + }; } throw new Error(response.data.error.reason); } @@ -94,17 +88,19 @@ export async function createVideoComment( * @returns Model - serverId와 content */ export async function createCommentReply( - commentId: number, + commentId: string, data: { content: string }, ): Promise<{ serverId: string; content: string }> { - const response = await apiClient.post>( + const response = await apiClient.post>( `/comments/${commentId}/replies`, data, ); if (response.data.resultType === 'SUCCESS') { - // DTO → Model 변환 - return commentDtoToModel(response.data.success); + return { + serverId: response.data.success.replyId, + content: response.data.success.content, + }; } throw new Error(response.data.error.reason); } @@ -114,7 +110,7 @@ export async function createCommentReply( * * @param commentId - 댓글 ID */ -export async function deleteVideoComment(commentId: number): Promise { +export async function deleteVideoComment(commentId: string): Promise { const response = await apiClient.delete>(`/comments/${commentId}`); if (response.data.resultType === 'FAILURE') { diff --git a/src/api/queryClient.ts b/src/api/queryClient.ts index 3b8be482..56b9fdd7 100644 --- a/src/api/queryClient.ts +++ b/src/api/queryClient.ts @@ -68,7 +68,13 @@ export const queryKeys = { presentations: { all: ['presentations'] as const, lists: () => [...queryKeys.presentations.all, 'list'] as const, - list: () => [...queryKeys.presentations.lists()] as const, + list: (params?: { + page?: number; + limit?: number; + search?: string; + maxDuration?: number; + sort?: string; + }) => [...queryKeys.presentations.lists(), params ?? {}] as const, details: () => [...queryKeys.presentations.all, 'detail'] as const, detail: (projectId: string) => [...queryKeys.presentations.details(), projectId] as const, }, @@ -106,6 +112,13 @@ export const queryKeys = { summary: (slideId: string) => [...queryKeys.reactions.summaries(), slideId] as const, totals: () => [...queryKeys.reactions.all, 'total'] as const, total: (projectId: string) => [...queryKeys.reactions.totals(), projectId] as const, + video: { + all: (videoId: string) => [...queryKeys.reactions.all, 'video', videoId] as const, + window: (videoId: string, timestampMs: number, windowMs = 2000) => + [...queryKeys.reactions.video.all(videoId), 'window', timestampMs, windowMs] as const, + timeline: (videoId: string, intervalMs = 5000) => + [...queryKeys.reactions.video.all(videoId), 'timeline', intervalMs] as const, + }, }, } as const; diff --git a/src/components/comment/Comment.tsx b/src/components/comment/Comment.tsx index 88d045dd..7d9bc4fa 100644 --- a/src/components/comment/Comment.tsx +++ b/src/components/comment/Comment.tsx @@ -38,7 +38,7 @@ interface CommentProps { */ function Comment({ comment, isIndented = false, rootCommentId }: CommentProps) { // rootCommentId가 없으면 자기 자신이 최상위 댓글 - const resolvedRootId = rootCommentId ?? comment.id; + const resolvedRootId = rootCommentId ?? comment.commentId; const { replyingToId, replyDraft, @@ -46,13 +46,13 @@ function Comment({ comment, isIndented = false, rootCommentId }: CommentProps) { toggleReply, submitReply, cancelReply, + deleteComment, editingId, editDraft, setEditDraft, startEdit, cancelEdit, submitEdit, - deleteComment, goToRef, } = useCommentContext(); @@ -64,21 +64,21 @@ function Comment({ comment, isIndented = false, rootCommentId }: CommentProps) { const authorName = user?.name ?? '알 수 없음'; const authorProfileImage = user?.profileImage; - const isActive = replyingToId === comment.id; - const isEditing = editingId === comment.id; + const isActive = replyingToId === comment.commentId; + const isEditing = editingId === comment.commentId; const handleStartEdit = useCallback(() => { - if (editingId === comment.id) return; - startEdit(comment.id, comment.content); - }, [startEdit, editingId, comment.id, comment.content]); + if (editingId === comment.commentId) return; + startEdit(comment.commentId, comment.content); + }, [startEdit, editingId, comment.commentId, comment.content]); const handleSubmitEdit = useCallback(() => { - submitEdit(comment.id); - }, [submitEdit, comment.id]); + submitEdit(comment.commentId); + }, [submitEdit, comment.commentId]); const handleToggleReply = useCallback(() => { - toggleReply(comment.id); - }, [toggleReply, comment.id]); + toggleReply(comment.commentId); + }, [toggleReply, comment.commentId]); const handleSubmitReply = useCallback(() => { // 항상 최상위 부모 댓글 ID로 답글 제출 (서버는 root에만 답글 허용) @@ -86,8 +86,8 @@ function Comment({ comment, isIndented = false, rootCommentId }: CommentProps) { }, [submitReply, resolvedRootId]); const handleDelete = useCallback(() => { - deleteComment?.(comment.id); - }, [deleteComment, comment.id]); + deleteComment?.(comment.commentId); + }, [deleteComment, comment.commentId]); const handleGoToRef = useCallback(() => { if (comment.ref) { @@ -140,10 +140,8 @@ function Comment({ comment, isIndented = false, rootCommentId }: CommentProps) { onClick={handleStartEdit} aria-label="댓글 수정" className={clsx( - 'flex items-center gap-1 rounded text-caption-bold active:opacity-80 focus-visible:outline-2 focus-visible:outline-gray-400', - isEditing - ? 'text-gray-400' - : 'text-[#FFFFFF] hover:text-[rgba(255,255,255,0.8)]', + 'flex items-center gap-1 rounded text-caption-bold active:opacity-80 focus-visible:outline-2 focus-visible:outline-main', + isEditing ? 'text-gray-400' : 'text-black', )} > 수정 @@ -154,7 +152,7 @@ function Comment({ comment, isIndented = false, rootCommentId }: CommentProps) { type="button" onClick={handleDelete} aria-label="댓글 삭제" - className="flex items-center gap-1 rounded text-caption-bold text-error hover:text-red-400 active:opacity-80 focus-visible:outline-2 focus-visible:outline-error" + className="flex items-center gap-1 rounded text-caption-bold text-error active:opacity-80 focus-visible:outline-2 focus-visible:outline-error" > 삭제