Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
3 changes: 2 additions & 1 deletion src/components/common/Tab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { type ComponentPropsWithRef } from 'react';
import { cn } from '@/utils/cn';

const tabVariants = cva(
'flex cursor-pointer items-center justify-center text-[18px] leading-normal font-bold text-text-primary',
'flex cursor-pointer items-center justify-center text-[18px] leading-normal font-bold text-text-primary transition-[color,border-color] duration-150 motion-reduce:transition-none',
{
variants: {
variant: {
Expand All @@ -18,6 +18,7 @@ const tabVariants = cva(
compoundVariants: [
{ variant: 'filled', active: true, className: 'bg-primary-400 text-white' },
{ variant: 'underline', active: true, className: 'border-primary-400' },
{ variant: 'underline', active: false, className: 'text-text-tertiary' },
],
defaultVariants: {
variant: 'filled',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,26 +25,26 @@ export default function RecommendationCompletion({
return (
<section
ref={ref}
className={cn('flex flex-col items-center gap-6 text-center', className)}
className={cn('flex flex-col items-center gap-5 text-center', className)}
{...props}
>
<div className="flex flex-col items-center gap-2">
<h1 className="text-heading-large font-bold text-text-primary">
<div className="flex flex-col items-center gap-5">
<h1 className="text-display-medium leading-[1.2] font-bold text-text-primary">
오늘의 레터를 다 보셨어요.
</h1>
<p className="text-body-large font-medium text-text-secondary">
<p className="text-heading-medium font-medium text-text-secondary">
저장 {savedCount}건 · 관심 없음 {dismissedCount}건 · 열람 {viewedCount}건
</p>
</div>
<div className="flex items-center gap-3">
<Button className="w-45" onClick={onViewSaved}>
<div className="flex items-center gap-15">
<Button className="w-62.5" onClick={onViewSaved}>
저장 목록 보기
</Button>
<Button variant="outline" className="w-45" onClick={onExplore}>
<Button variant="outline" className="w-62.5" onClick={onExplore}>
탐색 둘러보기
</Button>
</div>
<p className="text-body-small font-medium text-text-tertiary">{nextLetterNotice}</p>
<p className="text-heading-small font-medium text-text-tertiary">{nextLetterNotice}</p>
</section>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export type RecommendationScreen =
| 'deck'
| 'news'
| 'complete'
| 'revisit'
| 'archive'
| 'empty-candidates'
| 'empty-signup'
| 'empty-before-send';
Expand Down Expand Up @@ -168,21 +168,25 @@ export default function RecommendationFlowContent({
dismissedCount={dismissedCount}
viewedCount={viewedCount}
nextLetterNotice="내일 오전 7시 30분경, 다음 레터가 도착해요."
onViewSaved={() => navigate('/recommendations/revisit')}
onViewSaved={() => navigate('/recommendations/archive')}
onExplore={() => navigate('/explore')}
/>
</ScreenLayout>
);
}

if (screen === 'revisit') {
if (screen === 'archive') {
const displayIndex = Math.min(cardIndex, Math.max(filteredLetters.length - 1, 0));
const letter = filteredLetters[displayIndex];
return (
<ScreenLayout>
<RecommendationLetterCarousel
key={activeStatus}
className="gap-5"
current={filteredLetters.length === 0 ? 0 : displayIndex + 1}
total={filteredLetters.length}
contentKey={displayIndex}
enableStackTransition
Comment thread
coderabbitai[bot] marked this conversation as resolved.
onPrev={() => setCardIndex(Math.max(displayIndex - 1, 0))}
onNext={() => setCardIndex(Math.min(displayIndex + 1, filteredLetters.length - 1))}
prevDisabled={displayIndex === 0}
Expand All @@ -198,20 +202,22 @@ export default function RecommendationFlowContent({
/>
}
>
{letter ? (
<RecommendationLetterCard
{...letter}
onExpand={() => navigate(`/jobs/${letter.id}`)}
onSave={() => setStatus(letter.id, 'saved')}
onDismiss={() => setStatus(letter.id, 'dismissed')}
/>
) : (
<div className="flex w-full max-w-130 flex-col items-center gap-2 rounded-sm border border-gray-200 bg-white p-10 text-center">
<p className="text-body-large font-medium text-text-secondary">
{EMPTY_MESSAGE[activeStatus]}
</p>
</div>
)}
<div key={activeStatus} className="page-content-enter">
{letter ? (
<RecommendationLetterCard
{...letter}
onExpand={() => navigate(`/jobs/${letter.id}`)}
onSave={() => setStatus(letter.id, 'saved')}
onDismiss={() => setStatus(letter.id, 'dismissed')}
/>
) : (
<div className="flex min-h-142 w-190 items-center justify-center rounded-md border border-gray-200 bg-white p-6 text-center">
<p className="text-heading-small font-medium text-text-secondary">
{EMPTY_MESSAGE[activeStatus]}
</p>
</div>
)}
</div>
</RecommendationLetterCarousel>
</ScreenLayout>
);
Expand Down Expand Up @@ -262,10 +268,10 @@ export default function RecommendationFlowContent({
description={notice.description}
footNote={notice.footNote}
>
<Button className="w-[414px]" onClick={handlePrimaryAction}>
<Button className="w-103.5" onClick={handlePrimaryAction}>
{notice.primary}
</Button>
<Button className="w-[414px]" variant="outline" onClick={() => navigate('/explore')}>
<Button className="w-103.5" variant="outline" onClick={() => navigate('/explore')}>
탐색 둘러보기
</Button>
</RecommendationNoticePanel>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,11 @@ export default function RecommendationLetterCard({
<div className={cn('relative w-190', className)}>
<div
aria-hidden="true"
className="absolute inset-0 translate-x-6 rounded-md border border-gray-200 bg-white"
className="absolute inset-0 translate-x-10.5 rounded-md border border-gray-200 bg-white"
/>
<div
aria-hidden="true"
className="absolute inset-0 translate-x-3 rounded-md border border-gray-200 bg-white"
className="absolute inset-0 translate-x-5.25 rounded-md border border-gray-200 bg-white"
/>
<article
ref={ref}
Expand Down Expand Up @@ -86,7 +86,7 @@ export default function RecommendationLetterCard({

<div className="flex w-full flex-col gap-1 rounded-sm bg-gray-200 p-4">
<p className="text-label-small font-bold text-text-tertiary">{noteTitle}</p>
<p className="min-h-[42px] text-body-small leading-normal font-medium text-text-secondary">
<p className="min-h-10.5 text-body-small leading-normal font-medium text-text-secondary">
{note}
</p>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export default function RecommendationLetterCarousel({
enableStackTransition = false,
className,
}: RecommendationLetterCarouselProps) {
const hasItems = total > 0;
const [activeContent, setActiveContent] = useState({ key: contentKey, node: children });
const activeContentRef = useRef(activeContent);
const previousCurrentRef = useRef(current);
Expand Down Expand Up @@ -80,16 +81,14 @@ export default function RecommendationLetterCarousel({
};

return (
<div className={cn('flex flex-col items-center gap-4', className)}>
<div aria-busy={isAnimating} className={cn('flex flex-col items-center gap-4', className)}>
{filterSlot}
<div className="flex items-center gap-16">
<CarouselArrow
direction="left"
onClick={handlePrev}
disabled={prevDisabled || isAnimating}
/>
{hasItems && (
<CarouselArrow direction="left" onClick={handlePrev} disabled={prevDisabled} />
)}
{enableStackTransition ? (
<div className="relative flex min-h-[524px] w-196 items-center overflow-x-clip">
<div className="relative flex min-h-[524px] w-[802px] translate-x-[21px] items-center overflow-x-clip">
{leavingContent && (
<div
aria-hidden="true"
Expand Down Expand Up @@ -123,22 +122,29 @@ export default function RecommendationLetterCarousel({
) : (
children
)}
<CarouselArrow
direction="right"
onClick={handleNext}
disabled={nextDisabled || isAnimating}
/>
</div>
<div className="flex items-center gap-2">
<CarouselIndicator variant="number" current={current} total={total} />
<CarouselIndicator variant="dot" total={total} activeIndex={current - 1} />
{hasItems && (
<CarouselArrow direction="right" onClick={handleNext} disabled={nextDisabled} />

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

카드 전환 애니메이션이 진행되는 동안에도 화살표 입력이 가능하더라고요. 이 경우 연속으로 클릭하면 애니메이션이 끝나기 전에 handlePrev/handleNext가 다시 호출돼 전환이 꼬일 수 있을 것 같습니다.
의도적으로 제외하신 게 아니라면 || isAnimating 조건 다시 넣어주시면 좋을 것 같습니다

@KyeongJooni KyeongJooni Jul 26, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

이미 handlePrev/handleNext에서 isAnimating에서 가드하고 있어 애니메이션 중 추가 전환은 실행되지 않도록 작성했습니다! 시각적으로만 반영이 안되게 해놨습니다~

)}
</div>
<p
aria-hidden={!footNote}
className="min-h-6 text-body-medium font-medium text-text-tertiary"
>
{footNote}
</p>
{hasItems ? (
<>
<div className="flex items-center gap-2">
<CarouselIndicator variant="number" current={current} total={total} />
<CarouselIndicator variant="dot" total={total} activeIndex={current - 1} />
</div>
<p
aria-hidden={!footNote}
className="min-h-6 text-heading-medium font-medium text-text-tertiary"
>
{footNote}
</p>
</>
) : (
<>
<div aria-hidden="true" className="h-10" />
<div aria-hidden="true" className="h-9" />
</>
)}
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,12 @@ export default function RecommendationStatusTabs({
className,
}: RecommendationStatusTabsProps) {
return (
<div className={cn('inline-flex items-center gap-3', className)}>
<div
className={cn(
'flex w-190 items-center justify-center gap-2.5 overflow-hidden rounded-xs p-2.5',
className,
)}
>
{tabs.map((label, index) => {
const active = index === activeIndex;

Expand All @@ -24,10 +29,10 @@ export default function RecommendationStatusTabs({
type="button"
onClick={() => onChange?.(index)}
className={cn(
'flex h-12 items-center justify-center rounded-full px-6 text-[18px] font-bold whitespace-nowrap',
'flex h-10 items-center justify-center rounded-full border border-transparent px-3 text-label-large font-medium whitespace-nowrap transition-[background-color,border-color] duration-150 motion-reduce:transition-none',
active
? 'bg-primary-600 text-gray-900'
: 'border border-gray-200 bg-white text-text-primary hover:bg-gray-50',
: 'border-gray-200 bg-white text-text-primary hover:bg-gray-50',
)}
>
{label}
Expand Down
35 changes: 35 additions & 0 deletions src/features/savedJobs/SavedJobsEmptyState.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import Button from '@/components/common/Button';
import { ResultIcon } from '@/components/feedback';

interface SavedJobsEmptyStateProps {
title: string;
description: string;
onBrowseRecommendations: () => void;
onExplore: () => void;
}

export default function SavedJobsEmptyState({
title,
description,
onBrowseRecommendations,
onExplore,
}: SavedJobsEmptyStateProps) {
return (
<div className="flex min-h-150 w-full max-w-285 self-center flex-col items-center justify-center gap-5">
<ResultIcon variant="warning" />
<p className="text-center text-heading-medium font-semibold text-text-primary">{title}</p>
<p className="text-center text-label-medium font-medium text-text-secondary">{description}</p>
<div className="w-174 border-t border-gray-200" />
<Button className="h-14 w-62.5" onClick={onBrowseRecommendations}>
오늘의 추천 보기
</Button>
<button
type="button"
className="cursor-pointer text-label-large font-medium text-text-tertiary underline decoration-from-font [text-underline-position:from-font]"
onClick={onExplore}
>
탐색 둘러보기
</button>
</div>
);
}
Loading
Loading