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
2 changes: 1 addition & 1 deletion src/components/ui/scrollable-date-picker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ export function ScrollableDatePicker({
return (
<div className='relative mx-auto flex h-[236px] w-full max-w-sm items-center justify-center overflow-hidden'>
{/* Selection Indicator */}
<div className='bg-element-gray-light pointer-events-none absolute inset-x-4 top-1/2 z-0 h-8 -translate-y-1/2 rounded-[6px]' />
<div className='bg-element-gray-light pointer-events-none absolute inset-x-0 top-1/2 z-0 h-8 -translate-y-1/2 rounded-[6px]' />

{/* Columns Container */}
<div className='z-10 flex h-[236px] w-full justify-center px-6'>
Expand Down
15 changes: 7 additions & 8 deletions src/feature/create-album/components/CreateInputList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,13 +75,12 @@ export default function CreateInputList({
onErrorChange?.(error !== '' || eventNameError !== '');
};

// 로컬 시간대 기준으로 어제 날짜를 YYYY-MM-DD로 계산
const yesterdayDate = new Date();
yesterdayDate.setDate(yesterdayDate.getDate() - 1);
const yyyy = yesterdayDate.getFullYear();
const mm = String(yesterdayDate.getMonth() + 1).padStart(2, '0'); // 0-11이므로 +1
const dd = String(yesterdayDate.getDate()).padStart(2, '0');
const yesterday = `${yyyy}-${mm}-${dd}`;
// 로컬 시간대 기준으로 오늘 날짜를 YYYY-MM-DD로 계산
const todayDate = new Date();
const yyyy = todayDate.getFullYear();
const mm = String(todayDate.getMonth() + 1).padStart(2, '0'); // 0-11이므로 +1
const dd = String(todayDate.getDate()).padStart(2, '0');
const today = `${yyyy}-${mm}-${dd}`;
Comment on lines +79 to +83
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

date-fns 라이브러리가 프로젝트에 이미 사용되고 있으니, 날짜 형식을 직접 만드는 대신 format 함수를 사용하면 코드를 더 간결하고 일관성 있게 유지할 수 있습니다. DateXInput 컴포넌트에서도 date-fns를 사용하고 있습니다.

제안된 코드를 적용하려면 파일 상단에 import { format } from 'date-fns';를 추가해야 합니다.

Suggested change
const todayDate = new Date();
const yyyy = todayDate.getFullYear();
const mm = String(todayDate.getMonth() + 1).padStart(2, '0'); // 0-11이므로 +1
const dd = String(todayDate.getDate()).padStart(2, '0');
const today = `${yyyy}-${mm}-${dd}`;
const today = format(new Date(), 'yyyy-MM-dd');

Copy link
Contributor Author

Choose a reason for hiding this comment

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

반영


return (
<div className='flex flex-col gap-6 px-4'>
Expand All @@ -98,7 +97,7 @@ export default function CreateInputList({
value={eventDate}
onChange={onEventDateChange}
placeholder='YYYY-MM-DD'
max={yesterday}
max={today}
/>
<XInput
label='참여 인원'
Expand Down
79 changes: 61 additions & 18 deletions src/feature/photo-detail/components/FooterPhotoDetail.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
'use client';
import { EP } from '@/global/api/ep';
import BottomSheetModal from '@/global/components/modal/BottomSheetModal';
import ConfirmModal from '@/global/components/modal/ConfirmModal';
import Toast from '@/global/components/toast/Toast';
import { downloadFile } from '@/global/utils/downloadFile';
import { getDeviceType } from '@/global/utils/getDeviceType';
import { shareImage } from '@/global/utils/image/shareImage';
import { useQueryClient } from '@tanstack/react-query';
import { Download, Heart, Info } from 'lucide-react';
import { Download, Heart, Info, Trash2 } from 'lucide-react';
import { useState } from 'react';
import { useDeleteAlbumPhotoMutation } from '../hooks/useDeleteAlbumPhotoMutation';
import { usePhotoDetailQuery } from '../hooks/usePhotoDetailQuery';
import { usePhotoDownloadMutation } from '../hooks/usePhotoDownloadMutation';
import { usePhotoLikedMutation } from '../hooks/usePhotoLikedMutation';
import { usePhotoUnlikedMutation } from '../hooks/usePhotoUnlikedMutation';
Expand Down Expand Up @@ -35,11 +38,36 @@ export default function FooterPhotoDetail({
const queryClient = useQueryClient();
const [isDownloading, setIsDownloading] = useState(false);
const [isPhotoInfoOpen, setIsPhotoInfoOpen] = useState(false);

const { data: photoDetail } = usePhotoDetailQuery({
albumId,
photoId,
});

console.log(
'🗑️ FooterPhotoDetail - canDelete:',
photoDetail?.canDelete,
'photoId:',
photoId,
);
Comment on lines +47 to +52
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

디버깅을 위해 추가된 console.log 구문이 남아있습니다. 프로덕션 코드에 포함되지 않도록 삭제하는 것이 좋습니다.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

반영


const { mutateAsync: mutateAsyncLike, isPending: isLiking } =
usePhotoLikedMutation();
const { mutateAsync: mutateAsyncUnlike, isPending: isUnliking } =
usePhotoUnlikedMutation();
const { mutateAsync: mutateAsyncDownload } = usePhotoDownloadMutation();
const { mutateAsync: mutateAsyncDelete } = useDeleteAlbumPhotoMutation();

const handleDelete = async (): Promise<void> => {
try {
await mutateAsyncDelete({ albumId, photoId });
queryClient.invalidateQueries({ queryKey: [EP.album.photos(albumId)] });
setIsPhotoInfoOpen(false);
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

handleDelete 함수 내의 setIsPhotoInfoOpen(false) 호출은 리팩토링 과정에서 남은 코드로 보입니다. 현재 삭제 버튼은 사진 정보 모달(BottomSheetModal) 외부에서 트리거되므로, 이 코드는 더 이상 필요하지 않습니다. 코드의 명확성을 위해 삭제하는 것이 좋겠습니다.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

반영

} catch (e) {
console.error(e);
Toast.alert('사진 삭제에 실패했습니다.');
}
};

const handleDeepToggle = async (): Promise<void> => {
try {
Expand Down Expand Up @@ -105,23 +133,6 @@ export default function FooterPhotoDetail({

return (
<section className='mx-10 flex shrink-0 justify-around py-5'>
<BottomSheetModal
title={'사진 정보'}
open={isPhotoInfoOpen}
onOpenChange={setIsPhotoInfoOpen}
trigger={
<button className='flex w-12 justify-center'>
<Info width={24} height={24} color='white' />
</button>
}
>
<SectionPhotoData
albumId={albumId}
photoId={photoId}
onAfterDelete={() => setIsPhotoInfoOpen(false)}
/>
</BottomSheetModal>

<button
type='button'
onClick={handleDownload}
Expand All @@ -135,6 +146,22 @@ export default function FooterPhotoDetail({
color={`${isRecentlyDownloaded ? 'var(--color-neutral-400)' : 'white'}`}
/>
</button>
<BottomSheetModal
title={'사진 정보'}
open={isPhotoInfoOpen}
onOpenChange={setIsPhotoInfoOpen}
trigger={
<button className='flex w-12 justify-center'>
<Info width={24} height={24} color='white' />
</button>
}
>
<SectionPhotoData
name={photoDetail?.name}
captureTime={photoDetail?.captureTime}
createdAt={photoDetail?.createdAt}
/>
</BottomSheetModal>

<div className='typo-body-lg-semibold flex w-12 justify-center gap-1'>
<button type='button' onClick={handleDeepToggle}>
Expand Down Expand Up @@ -165,6 +192,22 @@ export default function FooterPhotoDetail({
<ListPhotoLikers albumId={albumId} photoId={photoId} />
</BottomSheetModal>
</div>

{photoDetail?.canDelete && (
<ConfirmModal
title='사진을 삭제할까요?'
description='지운 사진은 다시 복구할 수 없어요.'
cancelText='취소'
confirmText='삭제하기'
confirmClassName='text-text-basic-inverse bg-button-accent-fill active:bg-button-accent-pressed active:text-basic-inverse'
onConfirm={handleDelete}
trigger={
<button className='flex w-12 justify-center'>
<Trash2 width={24} height={24} color='white' />
</button>
}
/>
)}
</section>
);
}
66 changes: 11 additions & 55 deletions src/feature/photo-detail/components/SectionPhotoData.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,7 @@
import { EP } from '@/global/api/ep';
import ConfirmModal from '@/global/components/modal/ConfirmModal';
import { useQueryClient } from '@tanstack/react-query';
import { useDeleteAlbumPhotoMutation } from '../hooks/useDeleteAlbumPhotoMutation';
import { usePhotoDetailQuery } from '../hooks/usePhotoDetailQuery';

interface SectionPhotoDataProps {
albumId: string;
photoId: number;
onAfterDelete?: () => void;
name?: string;
captureTime?: string;
createdAt?: string;
}

// 촬영 시각: 사진 EXIF 시간 그대로 표시 (타임존 변환 안 함)
Expand Down Expand Up @@ -48,68 +42,30 @@ const formatKoreanDateTime = (isoString?: string): string => {
};

export default function SectionPhotoData({
albumId,
photoId,
onAfterDelete,
name,
captureTime,
createdAt,
}: SectionPhotoDataProps) {
const queryClient = useQueryClient();
const { data, isPending, isError } = usePhotoDetailQuery({
albumId,
photoId,
});
const { mutateAsync } = useDeleteAlbumPhotoMutation();

if (isPending) return null;
if (isError) return null;

const handleDeleteClick = async () => {
try {
await mutateAsync({ albumId, photoId });
queryClient.invalidateQueries({ queryKey: [EP.album.photos(albumId)] });
} finally {
onAfterDelete?.();
}
};

return (
<section className='typo-body-lg-medium flex flex-col gap-6 rounded-3xl bg-white py-2'>
<dl className='flex flex-col gap-4 px-2'>
<section className='typo-body-lg-medium flex flex-col gap-4 rounded-3xl bg-white px-2 py-2'>
<dl className='flex flex-col gap-4'>
<div className='flex items-center justify-between'>
<dt className='text-text-subtle w-1/3'>업로드한 사람</dt>
<dd className='text-text-subtler flex-1'>{data?.name}</dd>
<dd className='text-text-subtler flex-1'>{name}</dd>
</div>
<div className='flex items-center justify-between'>
<dt className='text-text-subtle w-1/3'>촬영 시각</dt>
<dd className='text-text-subtler flex-1'>
{formatCaptureTime(data?.captureTime)}
{formatCaptureTime(captureTime)}
</dd>
</div>
<div className='flex items-center justify-between'>
<dt className='text-text-subtle w-1/3'>업로드 시각</dt>
<dd className='text-text-subtler flex-1'>
{formatKoreanDateTime(data?.createdAt)}
{formatKoreanDateTime(createdAt)}
</dd>
</div>
</dl>

{data?.canDelete && (
<ConfirmModal
title='사진을 삭제할까요?'
description='지운 사진은 다시 복구할 수 없어요.'
cancelText='취소'
confirmText='삭제하기'
confirmClassName='text-text-basic-inverse bg-button-accent-fill active:bg-button-accent-pressed active:text-basic-inverse'
onConfirm={handleDeleteClick}
trigger={
<button
type='button'
className='bg-element-gray-lighter typo-body-1xl-semibold text-text-error w-full rounded-[8px] py-4'
>
사진 삭제하기
</button>
}
/>
)}
</section>
);
}
2 changes: 1 addition & 1 deletion src/global/api/ep.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ export interface PreviewPhotoInfoSchema { "photoId": number; "imageUrl": string;
export interface AuthExchangeResponseSchema { "accessToken": string; "refreshToken": string; "isOnboarded": boolean; "userId": number; "name": string; "email": string; }
export interface CommonResponseAuthExchangeResponseSchema { "isSuccess"?: boolean; "code"?: number; "message"?: string; "result"?: AuthExchangeResponseSchema; }
export interface CommonResponsePhotoPageResponseSchema { "isSuccess"?: boolean; "code"?: number; "message"?: string; "result"?: PhotoPageResponseSchema; }
export interface PhotoListResponseSchema { "name"?: string; "photoId": number; "profileImage": string; "imageUrl"?: string; "thumbnailUrl": string; "likeCnt": number; "isLiked": boolean; "isDownloaded": boolean; "isRecentlyDownloaded": boolean; }
export interface PhotoListResponseSchema { "name"?: string; "photoId": number; "profileImage": string; "imageUrl"?: string; "thumbnailUrl": string; "likeCnt": number; "isLiked": boolean; "isDownloaded": boolean; "isRecentlyDownloaded": boolean; "canDelete"?: boolean; }
export interface PhotoPageResponseSchema { "responses": PhotoListResponseSchema[]; "listSize": number; "isFirst": boolean; "isLast": boolean; "hasNext": boolean; }
export interface CommonResponsePhotoDetailResponseSchema { "isSuccess"?: boolean; "code"?: number; "message"?: string; "result"?: PhotoDetailResponseSchema; }
export interface PhotoDetailResponseSchema { "name": string; "profileImage": string; "photoId": number; "imageUrl": string; "thumbnailUrl": string; "likesCnt": number; "isLiked": boolean; "isDownloaded": boolean; "isRecentlyDownloaded": boolean; "canDelete"?: boolean; "captureTime"?: string; "createdAt"?: string; }
Expand Down
2 changes: 1 addition & 1 deletion src/global/components/modal/ConfirmModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ export default function ConfirmModal({
<AlertDialog>
<AlertDialogTrigger asChild>{trigger}</AlertDialogTrigger>

<AlertDialogContent className='rounded-[20px]!'>
<AlertDialogContent className='rounded-[20px]! border-none'>
<AlertDialogHeader className='pb-6'>
<AlertDialogTitle className='typo-heading-sm-semibold text-text-basic pt-4'>
{title}
Expand Down
Loading