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
5 changes: 3 additions & 2 deletions src/feature/create-album/components/CreateComplete.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { CountdownTimer } from '@/global/components/CountdownTimer';
import LongButton from '@/global/components/LongButton';
import { Check } from 'lucide-react';
import { useRouter } from 'next/navigation';
import { useCallback } from 'react';

interface CreateCompleteProps {
albumId: string;
Expand All @@ -11,9 +12,9 @@ interface CreateCompleteProps {
export default function CreateComplete({ albumId }: CreateCompleteProps) {
const router = useRouter();

const handleClick = () => {
const handleClick = useCallback(() => {
router.push(`/album/upload/${albumId}`);
};
}, [router, albumId]);

return (
<div className='mt-[200px] flex flex-col items-center gap-4'>
Expand Down
61 changes: 49 additions & 12 deletions src/global/components/CountdownTimer.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
'use client';
import { AnimatePresence, motion } from 'framer-motion';
import { AnimatePresence, LazyMotion, domAnimation, m } from 'framer-motion';
import { useEffect, useState } from 'react';

interface TimeLeft {
Expand Down Expand Up @@ -47,7 +47,7 @@ function AnimatedNumber({ number, label }: AnimatedNumberProps) {
<div className='bg-element-gray-lighter flex h-[50px] w-[50px] flex-col items-center justify-center rounded-xl'>
<div className='typo-heading-md-medium text-text-subtle relative h-[30px] w-[30px] overflow-hidden'>
<AnimatePresence mode='wait'>
<motion.div
<m.div
key={formattedNumber}
initial={{ y: 20, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
Expand All @@ -56,7 +56,7 @@ function AnimatedNumber({ number, label }: AnimatedNumberProps) {
className='absolute top-0 right-0 left-0 text-center'
>
{formattedNumber}
</motion.div>
</m.div>
</AnimatePresence>
</div>
</div>
Expand Down Expand Up @@ -114,15 +114,52 @@ export function CountdownTimer({ albumId }: CountdownTimerProps) {
return () => clearInterval(timer);
}, [targetDate]);

// 레이아웃 시프트 방지를 위해 초기 렌더링 시에도 공간 확보
if (!targetDate) {
return (
<div className='flex items-end justify-center opacity-0'>
<div className='mx-2 flex flex-col items-center gap-2'>
<div className='bg-element-gray-lighter flex h-[50px] w-[50px] flex-col items-center justify-center rounded-xl'></div>
<span className='typo-caption-sm-medium text-text-subtler uppercase'>
Days
</span>
</div>
<span className='mb-9 text-2xl font-semibold text-[#D9D9D9]'>:</span>
<div className='mx-2 flex flex-col items-center gap-2'>
<div className='bg-element-gray-lighter flex h-[50px] w-[50px] flex-col items-center justify-center rounded-xl'></div>
<span className='typo-caption-sm-medium text-text-subtler uppercase'>
HOURS
</span>
</div>
<span className='mb-9 text-2xl font-semibold text-[#D9D9D9]'>:</span>
<div className='mx-2 flex flex-col items-center gap-2'>
<div className='bg-element-gray-lighter flex h-[50px] w-[50px] flex-col items-center justify-center rounded-xl'></div>
<span className='typo-caption-sm-medium text-text-subtler uppercase'>
Mins
</span>
</div>
<span className='mb-9 text-2xl font-semibold text-[#D9D9D9]'>:</span>
<div className='mx-2 flex flex-col items-center gap-2'>
<div className='bg-element-gray-lighter flex h-[50px] w-[50px] flex-col items-center justify-center rounded-xl'></div>
<span className='typo-caption-sm-medium text-text-subtler uppercase'>
Secs
</span>
</div>
</div>
);
}
Comment on lines +117 to +150
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

CLS(Cumulative Layout Shift)를 방지하기 위해 스켈레톤 UI를 추가하신 것은 아주 좋은 접근입니다. Lighthouse 점수 개선에 큰 도움이 될 것 같습니다.

다만 현재 스켈레톤 UI 코드가 Days, HOURS, Mins, Secs 각 단위마다 반복되어 중복이 발생하고 있습니다. 이로 인해 향후 타이머의 디자인이나 구조가 변경될 때 여러 곳을 수정해야 하는 유지보수성의 문제가 생길 수 있습니다.

이러한 중복을 줄이기 위해 스켈레톤 UI를 별도의 컴포넌트로 분리하는 것을 제안합니다. 예를 들어, AnimatedNumberSkeleton과 같은 컴포넌트를 만들 수 있습니다.

function AnimatedNumberSkeleton({ label }: { label: string }) {
  return (
    <div className='mx-2 flex flex-col items-center gap-2'>
      <div className='bg-element-gray-lighter flex h-[50px] w-[50px] flex-col items-center justify-center rounded-xl' />
      <span className='typo-caption-sm-medium text-text-subtler uppercase'>
        {label}
      </span>
    </div>
  );
}

이렇게 분리한 컴포넌트를 사용하면 if (!targetDate) 블록 내부 코드를 아래와 같이 더 간결하고 유지보수하기 쉽게 만들 수 있습니다.

if (!targetDate) {
  const labels = ['Days', 'HOURS', 'Mins', 'Secs'];
  return (
    <div className='flex items-end justify-center opacity-0'>
      {labels.map((label, index) => (
        <React.Fragment key={label}>
          <AnimatedNumberSkeleton label={label} />
          {index < labels.length - 1 && (
            <span className='mb-9 text-2xl font-semibold text-[#D9D9D9]'>:</span>
          )}
        </React.Fragment>
      ))}
    </div>
  );
}


return (
<div className='flex items-end justify-center'>
<AnimatedNumber number={timeLeft.days} label='Days' />
<span className='mb-9 text-2xl font-semibold text-[#D9D9D9]'>:</span>
<AnimatedNumber number={timeLeft.hours} label='HOURS' />
<span className='mb-9 text-2xl font-semibold text-[#D9D9D9]'>:</span>
<AnimatedNumber number={timeLeft.minutes} label='Mins' />
<span className='mb-9 text-2xl font-semibold text-[#D9D9D9]'>:</span>
<AnimatedNumber number={timeLeft.seconds} label='Secs' />
</div>
<LazyMotion features={domAnimation}>
<div className='flex items-end justify-center'>
<AnimatedNumber number={timeLeft.days} label='Days' />
<span className='mb-9 text-2xl font-semibold text-[#D9D9D9]'>:</span>
<AnimatedNumber number={timeLeft.hours} label='HOURS' />
<span className='mb-9 text-2xl font-semibold text-[#D9D9D9]'>:</span>
<AnimatedNumber number={timeLeft.minutes} label='Mins' />
<span className='mb-9 text-2xl font-semibold text-[#D9D9D9]'>:</span>
<AnimatedNumber number={timeLeft.seconds} label='Secs' />
</div>
</LazyMotion>
);
}
Loading