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
99 changes: 79 additions & 20 deletions src/components/ui/scrollable-date-picker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -128,52 +128,111 @@ export function ScrollableDatePicker({
}: ScrollableDatePickerProps) {
const currentDate = value || new Date();

const selectedYear = currentDate.getFullYear();
const selectedMonth = currentDate.getMonth() + 1;
const selectedDay = currentDate.getDate();

// Generate years
const currentYear = new Date().getFullYear();
const minYear = minDate?.getFullYear() || 2020;
const maxYear = maxDate?.getFullYear() || currentYear + 50;

const clamp = (value: number, min: number, max: number) =>
Math.min(Math.max(value, min), max);

const getDaysInMonth = (year: number, month: number) => {
return new Date(year, month, 0).getDate();
};
Comment on lines +136 to +141
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

clampgetDaysInMonth 함수는 컴포넌트의 상태나 props에 의존하지 않는 순수 함수입니다. 컴포넌트 외부로 옮겨서 매 렌더링마다 함수가 재생성되는 것을 방지하는 것이 좋습니다. 이는 코드 가독성을 높이고 약간의 성능 개선 효과를 가져올 수 있습니다.


const getMinMonthForYear = (year: number) => {
if (minDate && year === minYear) {
return minDate.getMonth() + 1;
}
return 1;
};

const getMaxMonthForYear = (year: number) => {
if (maxDate && year === maxYear) {
return maxDate.getMonth() + 1;
}
return 12;
};

const getMinDayForMonth = (year: number, month: number) => {
if (minDate && year === minYear && month === minDate.getMonth() + 1) {
return minDate.getDate();
}
return 1;
};

const getMaxDayForMonth = (year: number, month: number) => {
const daysInMonth = getDaysInMonth(year, month);
if (maxDate && year === maxYear && month === maxDate.getMonth() + 1) {
return Math.min(daysInMonth, maxDate.getDate());
}
return daysInMonth;
};
Comment on lines +143 to +170
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

이 헬퍼 함수들은 minDate, maxDate와 같은 props에 의존합니다. React.useCallback으로 메모이제이션하여 매 렌더링마다 함수가 재생성되는 것을 방지하면 성능을 최적화할 수 있습니다. 이전 제안에 따라 getDaysInMonth를 컴포넌트 외부로 옮기면 getMaxDayForMonth의 메모이제이션이 더 효과적입니다.

  const getMinMonthForYear = React.useCallback((year: number) => {
    if (minDate && year === minYear) {
      return minDate.getMonth() + 1;
    }
    return 1;
  }, [minDate, minYear]);

  const getMaxMonthForYear = React.useCallback((year: number) => {
    if (maxDate && year === maxYear) {
      return maxDate.getMonth() + 1;
    }
    return 12;
  }, [maxDate, maxYear]);

  const getMinDayForMonth = React.useCallback((year: number, month: number) => {
    if (minDate && year === minYear && month === minDate.getMonth() + 1) {
      return minDate.getDate();
    }
    return 1;
  }, [minDate, minYear]);

  const getMaxDayForMonth = React.useCallback((year: number, month: number) => {
    const daysInMonth = getDaysInMonth(year, month);
    if (maxDate && year === maxYear && month === maxDate.getMonth() + 1) {
      return Math.min(daysInMonth, maxDate.getDate());
    }
    return daysInMonth;
  }, [maxDate, maxYear, getDaysInMonth]);


const selectedYear = clamp(currentDate.getFullYear(), minYear, maxYear);
const minMonthForYear = getMinMonthForYear(selectedYear);
const maxMonthForYear = getMaxMonthForYear(selectedYear);
const selectedMonth = clamp(
currentDate.getMonth() + 1,
minMonthForYear,
maxMonthForYear,
);
const minDayForMonth = getMinDayForMonth(selectedYear, selectedMonth);
const maxDayForMonth = getMaxDayForMonth(selectedYear, selectedMonth);
const selectedDay = clamp(
currentDate.getDate(),
minDayForMonth,
maxDayForMonth,
);

const years = React.useMemo(
() => Array.from({ length: maxYear - minYear + 1 }, (_, i) => minYear + i),
[minYear, maxYear],
);

const months = React.useMemo(
() => Array.from({ length: 12 }, (_, i) => i + 1),
[],
() =>
Array.from(
{ length: maxMonthForYear - minMonthForYear + 1 },
(_, i) => minMonthForYear + i,
),
[minMonthForYear, maxMonthForYear],
);

const getDaysInMonth = (year: number, month: number) => {
return new Date(year, month, 0).getDate();
};

const days = React.useMemo(
() =>
Array.from(
{ length: getDaysInMonth(selectedYear, selectedMonth) },
(_, i) => i + 1,
{ length: maxDayForMonth - minDayForMonth + 1 },
(_, i) => minDayForMonth + i,
),
[selectedYear, selectedMonth],
[minDayForMonth, maxDayForMonth],
);

const handleYearChange = (year: number) => {
const daysInNewMonth = getDaysInMonth(year, selectedMonth);
const newDay = Math.min(selectedDay, daysInNewMonth);
onChange(new Date(year, selectedMonth - 1, newDay));
const minMonth = getMinMonthForYear(year);
const maxMonth = getMaxMonthForYear(year);
const newMonth = clamp(selectedMonth, minMonth, maxMonth);
const minDay = getMinDayForMonth(year, newMonth);
const maxDay = getMaxDayForMonth(year, newMonth);
const newDay = clamp(selectedDay, minDay, maxDay);
onChange(new Date(year, newMonth - 1, newDay));
};

const handleMonthChange = (month: number) => {
const daysInNewMonth = getDaysInMonth(selectedYear, month);
const newDay = Math.min(selectedDay, daysInNewMonth);
onChange(new Date(selectedYear, month - 1, newDay));
const minMonth = getMinMonthForYear(selectedYear);
const maxMonth = getMaxMonthForYear(selectedYear);
const newMonth = clamp(month, minMonth, maxMonth);
const minDay = getMinDayForMonth(selectedYear, newMonth);
const maxDay = getMaxDayForMonth(selectedYear, newMonth);
const newDay = clamp(selectedDay, minDay, maxDay);
onChange(new Date(selectedYear, newMonth - 1, newDay));
};

const handleDayChange = (day: number) => {
onChange(new Date(selectedYear, selectedMonth - 1, day));
const minDay = getMinDayForMonth(selectedYear, selectedMonth);
const maxDay = getMaxDayForMonth(selectedYear, selectedMonth);
const newDay = clamp(day, minDay, maxDay);
onChange(new Date(selectedYear, selectedMonth - 1, newDay));
};

return (
Expand Down
2 changes: 2 additions & 0 deletions src/feature/album/4cut/components/ScreenAlbum4Cut.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ export default function ScreenAlbum4Cut({ albumId }: ScreenAlbum4CutProps) {
}

try {
setIsDownloading(true);
await showCaptureNode();

const fileName = data?.title
Expand All @@ -136,6 +137,7 @@ export default function ScreenAlbum4Cut({ albumId }: ScreenAlbum4CutProps) {
Toast.alert('이미지를 생성하지 못했습니다. 다시 시도해주세요.');
} finally {
setIsCaptureVisible(false);
setIsDownloading(false);
}
};

Expand Down
10 changes: 10 additions & 0 deletions src/feature/album/detail/sidebar/components/AlbumParticipants.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { useGetAlbumInform } from '@/feature/upload/hooks/useGetAlbumInform';
import BottomSheetModal from '@/global/components/modal/BottomSheetModal';
import { GA_EVENTS } from '@/global/constants/gaEvents';
import { trackGaEvent } from '@/global/utils/trackGaEvent';
import { Plus } from 'lucide-react';
import { useState } from 'react';
import BottomSheetContentShare from './BottomSheetContentShare';
Expand All @@ -24,6 +26,13 @@ export default function AlbumParticipants({ albumId }: AlbumParticipantsProps) {

const isMaker = data.myRole === 'MAKER';

const handleClickInvite = () => {
trackGaEvent(GA_EVENTS.click_invite, {
album_id: albumId,
access_type: data?.myRole === 'MAKER' ? 'creator' : 'member',
});
};

return (
<section className='bg-background-white rounded-[12px] px-5 pt-5 pb-7'>
<div className='mb-3.5 flex items-center justify-between gap-3'>
Expand Down Expand Up @@ -53,6 +62,7 @@ export default function AlbumParticipants({ albumId }: AlbumParticipantsProps) {
<button
type='button'
className='flex w-full items-center gap-3 py-2'
onClick={handleClickInvite}
>
<div className='flex h-9 w-9 items-center justify-center rounded-full bg-[#F1F2F3]'>
<Plus width={20} height={20} color='#000' />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,16 @@ import { useGetAlbumInform } from '@/feature/upload/hooks/useGetAlbumInform';
import { HEADER_HEIGHT } from '@/global/components/header/CustomHeader';
import ConfirmModal from '@/global/components/modal/ConfirmModal';
import Toast from '@/global/components/toast/Toast';
import { GA_EVENTS } from '@/global/constants/gaEvents';
import { convertUnicodeToEmoji } from '@/global/utils/convertEmoji';
import {
formatExpirationTime,
getIsExpired,
} from '@/global/utils/time/formatExpirationTime';
import { trackGaEvent } from '@/global/utils/trackGaEvent';
import { X } from 'lucide-react';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
import { useEffect, useState } from 'react';
import { useAlbumExitMutation } from '../hooks/useAlbumExitMutation';
import AlbumParticipants from './AlbumParticipants';

Expand All @@ -35,6 +37,15 @@ export default function ScreenAlbumSidebar({
const { mutateAsync } = useAlbumExitMutation();
const [isClosing, setIsClosing] = useState(false);

useEffect(() => {
if (isOpen) {
trackGaEvent(GA_EVENTS.view_albumsidebar, {
album_id: albumId,
access_type: informData?.myRole === 'MAKER' ? 'creator' : 'member',
});
}
}, [isOpen]);
Comment on lines +40 to +47
Copy link
Contributor

Choose a reason for hiding this comment

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

high

useEffect의 의존성 배열에 albumIdinformData가 누락되었습니다. trackGaEvent 콜백 함수가 이 값들을 사용하므로, 의존성 배열에 추가하여 isOpen 상태가 변경될 때 뿐만 아니라 albumIdinformData가 변경될 때도 이벤트가 올바르게 추적되도록 해야 합니다.

Suggested change
useEffect(() => {
if (isOpen) {
trackGaEvent(GA_EVENTS.view_albumsidebar, {
album_id: albumId,
access_type: informData?.myRole === 'MAKER' ? 'creator' : 'member',
});
}
}, [isOpen]);
useEffect(() => {
if (isOpen) {
trackGaEvent(GA_EVENTS.view_albumsidebar, {
album_id: albumId,
access_type: informData?.myRole === 'MAKER' ? 'creator' : 'member',
});
}
}, [isOpen, albumId, informData]);


if (!isOpen && !isClosing) return null;
if (isPending) return null;
if (isError) return null;
Expand Down
2 changes: 1 addition & 1 deletion src/global/components/DateXInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ export default function DateXInput({
/>
</div>
<LongButton
text='시작하기'
text='확인'
noFixed={true}
className='mb-5'
onClick={handleConfirm}
Expand Down
19 changes: 16 additions & 3 deletions src/global/components/Spinner.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,21 @@
export default function Spinner() {
interface SpinnerProps {
color?: string; // ex) '#3B82F6', 'rgb(59,130,246)', 'var(--primary)'
size?: number; // px 단위
}

export default function Spinner({
color = '#ffffff', // 기본값 (tailwind primary-400 정도)
size = 64,
}: SpinnerProps) {
Comment on lines +6 to +9
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

스피너의 기본 크기가 32px (h-8 w-8)에서 64px로, 기본 색상이 primary-400에서 흰색 (#ffffff)으로 변경되었습니다. 이 변경 사항이 의도된 것인지 확인이 필요합니다. 만약 의도된 변경이 아니라면 이전 값으로 되돌리는 것을 고려해 보세요. 또한, 7번 줄의 주석 // 기본값 (tailwind primary-400 정도)은 현재 기본값인 #ffffff과 일치하지 않아 혼란을 줄 수 있으므로 수정이 필요합니다.

return (
<div className='flex h-full w-full items-center justify-center py-10'>
<svg
className='text-primary-400 h-8 w-8 animate-spin'
className='animate-spin'
style={{
color,
width: size,
height: size,
}}
xmlns='http://www.w3.org/2000/svg'
fill='none'
viewBox='0 0 24 24'
Expand All @@ -11,7 +24,7 @@ export default function Spinner() {
className='opacity-75'
fill='currentColor'
d='M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z'
></path>
/>
</svg>
</div>
);
Expand Down
Loading