Skip to content

feat: 추천 결과 생성 대기 페이지 및 애니메이션 구현#118

Merged
RookieAND merged 5 commits intodevelopfrom
feature/doki-doki-stage-page
Feb 25, 2026
Merged

feat: 추천 결과 생성 대기 페이지 및 애니메이션 구현#118
RookieAND merged 5 commits intodevelopfrom
feature/doki-doki-stage-page

Conversation

@RookieAND
Copy link
Collaborator

🎯 PR 제목

feat: 추천 결과 생성 대기 페이지 및 애니메이션 구현

📑 작업 상세 내역

  • 기능 추가

    • 추천 결과 생성 대기 시 두구두구 애니메이션을 표시합니다
    • 젓가락 애니메이션을 개선합니다 (±18° 회전, 0.4s 속도)
  • 버그 수정

    • SSE 이벤트와 state update의 race condition을 해결합니다
    • 중복 결과 생성 요청을 방지합니다
    • navigation 전 데이터 준비 상태를 검증합니다
  • 리팩토링

    • ResultLoading* → ResultGenerating*으로 네이밍을 변경합니다
    • 상태 처리 로직을 switch-case로 개선합니다

🙏 리뷰 요청 사항

  • SSE 이벤트와 state update의 타이밍 처리가 올바르게 동작하는지 확인해주세요
  • 추천 결과 생성 대기 UX(두구두구 애니메이션)가 자연스러운지 검토해주세요
  • race condition 해결 방식(isProcessingRef 사용)이 적절한지 검토해주세요

📃 참고 자료

  • 관련 커밋:
    • b06d2f1 - 결과 완료 시에도 애니메이션 표시
    • cafea72 - 젓가락 애니메이션 각도 조정
    • a7e7d4b - switch-case 리팩토링
    • 18d7a12 - 추천 결과 생성 플로우 개선
    • feff116 - 네이밍 변경 및 애니메이션 개선

🖼️ 작업 결과물

  • 추천 결과 생성 대기 페이지 (ResultGeneratingPage)
    • 두구두구 텍스트 + 애니메이션 젓가락
    • 꼬르륵 말풍선 효과
  • 2.5초 드럼롤 애니메이션 후 결과 페이지 전환

@RookieAND RookieAND added the ✨ Feature 기능 개발 label Feb 25, 2026
@RookieAND RookieAND self-assigned this Feb 25, 2026
@RookieAND RookieAND requested a review from youngminss February 25, 2026 18:35
@RookieAND RookieAND changed the base branch from main to develop February 25, 2026 18:36
@github-actions github-actions bot added ✨ Feature 기능 개발 and removed ✨ Feature 기능 개발 labels Feb 25, 2026
RookieAND and others added 5 commits February 26, 2026 03:36
…ations

- Rename ResultLoadingPage to ResultGeneratingPage for clarity
- Rename ResultLoadingTooltip to ResultGeneratingSpeechBubble for accuracy
- Rename ResultLoadingIllustration to ResultGeneratingIllustration
- Increase chopstick rotation angles for more dynamic animation
- Speed up chopstick animation from 0.6s to 0.4s

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Add ResultGeneratingPage display during result generation
- Implement refetch before navigation to ensure data is ready
- Fix race condition where gathering-full event arrives before state update
- Add SSE event handler memoization in CompletePage
- Prevent duplicate result generation requests with status check
- Skip drumroll animation when result is already completed

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Replace if-else chain with switch-case for better readability
- Add early return for null/undefined status check
- Remove unnecessary optional chaining in switch block

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Increase rotation angle from ±8° to ±18° for wider spread
- Create more dynamic drumroll-like visual effect

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Display ResultGeneratingPage for 2.5s even when result is pre-generated
- Remove state cleanup before navigation to prevent view flickering
- Improve user experience consistency across different entry points

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@RookieAND RookieAND force-pushed the feature/doki-doki-stage-page branch from b06d2f1 to 0609415 Compare February 25, 2026 18:36
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @RookieAND, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

이 PR은 추천 결과 생성 대기 페이지와 애니메이션을 구현하여 사용자 경험을 개선하고, 기존 의견 수렴 플로우의 안정성을 강화합니다. SSE 이벤트 처리의 경쟁 조건을 해결하고 중복 요청을 방지하며, API 에러 발생 시 사용자에게 더 명확한 피드백과 적절한 페이지 전환을 제공하도록 전역 및 특정 페이지 에러 핸들링을 개선했습니다. 또한, 코드 가독성과 유지보수성을 높이기 위한 대규모 리팩토링과 UI/UX 개선이 포함되어 있습니다.

Highlights

  • 기능 추가: 추천 결과 생성 대기 페이지 및 애니메이션(두구두구, 젓가락, 꼬르륵 말풍선)이 구현되었습니다.
  • 버그 수정: SSE 이벤트와 상태 업데이트 간의 경쟁 조건이 해결되었고, 중복 결과 생성 요청 및 내비게이션 전 데이터 검증 문제가 수정되었습니다.
  • 리팩토링: 결과 로딩 관련 네이밍이 ResultLoading*에서 ResultGenerating*으로 변경되었고, 상태 처리 로직이 switch-case로 개선되었습니다. 타입 시스템이 enum에서 as const 패턴으로 전환되었으며, 의견 수렴 페이지 컴포넌트 구조가 개선되었습니다.
  • 에러 핸들링: 전역 에러 페이지 및 404 페이지가 추가되었고, API 에러 발생 시 적절한 페이지로 리다이렉트 또는 notFound 처리되도록 개선되었습니다.
  • UI/UX 개선: 인원 수 선택 시 시각적 피드백이 추가되었고, 결과 대기 페이지 내 공유 버튼 스타일이 수정되었으며, 음식 카테고리 일러스트레이터가 업데이트되었습니다.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • CHANGELOG.md
    • 2.0.0-beta.5 버전으로 업데이트되었습니다.
    • 모임 인원 초과 및 결과 생성 완료 시 Toast 커스텀 기능이 추가되었습니다.
    • 결과 대기 페이지 내 공유 버튼 스타일이 Tertiary로 수정되었습니다.
    • 음식 카테고리 일러스트레이터가 Figma 최신 시안으로 수정되었습니다.
    • PeopleIllustration 캐릭터 배치 및 구조가 개선되었습니다.
    • 1, 3순위를 선택했으나 2순위를 선택하지 않았을 경우 Validation을 막지 않았던 문제가 수정되었습니다.
    • OpinionForm 필드 이름이 변경되고 타입 시스템이 enum에서 as const 패턴으로 전환되었습니다.
    • opinion 페이지 컴포넌트 구조가 개선되고 로직이 통합되었습니다.
Ignored Files
  • Ignored by pattern: .gemini/** (1)
    • .gemini/styleguide.md
  • Ignored by pattern: .github/workflows/** (2)
    • .github/workflows/development-deploy.yml
    • .github/workflows/pr-auto-labeler.yml
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

추천 결과 대기 페이지 및 애니메이션을 구현하고, 결과 생성 플로우를 리팩토링하는 PR입니다. 전반적으로 변경 사항이 잘 구조화되어 있지만, 훅의 상태 관리 및 스타일 가이드 위반과 관련된 몇 가지 문제를 발견했습니다.

특히 useProceedRecommendResult 훅과 PendingPage의 SSE 이벤트 핸들러에서 stale closure로 인해 발생할 수 있는 경쟁 상태 및 잘못된 타이머 로직이 있습니다. 또한 새로 추가된 컴포넌트에서 저장소 스타일 가이드를 위반하는 하드코딩된 색상과 정의되지 않은 타이포그래피 토큰 사용 사례를 여러 곳에서 발견했습니다. 자세한 수정 제안은 각 리뷰 코멘트를 확인해주세요.

Comment on lines +40 to +61
const onResultComplete = useCallback(async () => {
if (!isProcessingRef.current) {
return;
}

isPollingRef.current = true;
const remaining = Math.max(0, NAVIGATION_DELAY);

const poll = async () => {
try {
const response = await getRecommendResult(accessKey);
const { status } = response.data;
setTimeout(async () => {
const { data: latestResult } = await fetchRecommendResult();

switch (status) {
case RecommendationResultStatus.COMPLETED:
isPollingRef.current = false;
setManualPollingTrigger(false);
queryClient.setQueryData(
recommendResultKeys.detail(accessKey),
response,
);
router.push(`/gathering/${accessKey}/opinion/result`);
break;

case RecommendationResultStatus.PENDING:
timeoutRef.current = setTimeout(() => {
poll();
}, 1000);
break;

case RecommendationResultStatus.FAILED:
default:
isPollingRef.current = false;
setManualPollingTrigger(false);
toast.warning(
"추천 결과 생성에 실패했습니다. 다시 시도해주세요.",
);
break;
}
} catch {
isPollingRef.current = false;
setManualPollingTrigger(false);
toast.warning("추천 결과 조회에 실패했습니다.");
if (latestResult?.status === RecommendationResultStatus.COMPLETED) {
router.push(`/gathering/${accessKey}/opinion/result`);
return;
}
};

poll();

return () => {
isPollingRef.current = false;
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
};
}, [shouldPoll, accessKey, router, queryClient]);
isProcessingRef.current = false;
setProceedState({ status: PROCEED_STATUS.IDLE });
toast.warning(
"추천 결과가 아직 준비되지 않았습니다. 잠시 후 다시 시도해주세요.",
);
}, remaining);
}, [accessKey, router, fetchRecommendResult]);
Copy link
Contributor

Choose a reason for hiding this comment

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

high

현재 onResultComplete 함수에서 NAVIGATION_DELAY를 그대로 사용하여 항상 2.5초를 기다리도록 되어 있습니다. 이로 인해 proceed 함수에서 기록한 startTime이 무시되고, 애니메이션이 시작된 시점부터 최소 2.5초를 보장하려는 의도와 다르게 동작할 수 있습니다.

useServerSentEvent 훅의 구현상 onResultComplete 콜백은 stale closure가 될 수 있으므로, startTimeuseRef로 관리하여 항상 최신 값을 참조하도록 수정하는 것이 좋습니다. proceed 함수 내에서 setProceedState를 호출할 때 startTimeRef.current도 함께 업데이트해야 합니다.

수정 제안:

  1. 훅 최상단에 const startTimeRef = useRef(0);를 추가합니다.
  2. proceed 함수 내에서 setProceedState를 호출하는 모든 위치에 startTimeRef.current = Date.now();을 추가합니다.
  3. 아래와 같이 onResultComplete 함수를 수정합니다.
 	const onResultComplete = useCallback(async () => {
 		if (!isProcessingRef.current) {
 			return;
 		}
 
 		const elapsedTime = Date.now() - startTimeRef.current;
 		const remaining = Math.max(0, NAVIGATION_DELAY - elapsedTime);
 
 		setTimeout(async () => {
 			const { data: latestResult } = await fetchRecommendResult();
 
 			if (latestResult?.status === RecommendationResultStatus.COMPLETED) {
 				router.push(`/gathering/${accessKey}/opinion/result`);
 				return;
 			}
 
 			isProcessingRef.current = false;
 			setProceedState({ status: PROCEED_STATUS.IDLE });
 			toast.warning(
 				"추천 결과가 아직 준비되지 않았습니다. 잠시 후 다시 시도해주세요.",
 			);
 		}, remaining);
 	}, [accessKey, router, fetchRecommendResult]);

Comment on lines +21 to +146
fill="url(#paint0_linear_1865_35373)"
/>

{/* Bowl Bottom */}
<path
d="M256.889 93.3672C256.465 101.288 252.257 108.624 245.374 114.819C232.954 125.995 211.717 133.388 187.5 133.388C163.283 133.388 142.046 125.995 129.626 114.819C122.747 108.624 118.535 101.288 118.111 93.3672C118.042 93.1832 118 93.0699 118 93.0699V111.365C118 150.787 149.114 182.749 187.5 182.749C225.886 182.749 257 150.791 257 111.365V93.0699C257 93.0699 256.958 93.1879 256.889 93.3672Z"
fill="url(#paint1_linear_1865_35373)"
/>

{/* Bowl Top - Inner */}
<path
d="M187.5 56.4092C206.312 56.4092 223.212 60.7837 235.315 67.7188C247.493 74.6962 254.327 83.9303 254.327 93.5566C254.327 103.183 247.493 112.418 235.315 119.396C223.212 126.331 206.312 130.705 187.5 130.705C168.688 130.705 151.788 126.331 139.685 119.396C127.507 112.418 120.673 103.183 120.673 93.5566C120.673 83.9303 127.507 74.6962 139.685 67.7188C151.788 60.7837 168.688 56.4092 187.5 56.4092Z"
fill="url(#paint2_linear_1865_35373)"
stroke="#7FC3FF"
strokeWidth="5.34615"
strokeLinecap="round"
/>

{/* Bowl Top - Outer */}
<path
d="M187.5 56.4092C206.312 56.4092 223.212 60.7837 235.315 67.7188C247.493 74.6962 254.327 83.9303 254.327 93.5566C254.327 103.183 247.493 112.418 235.315 119.396C223.212 126.331 206.312 130.705 187.5 130.705C168.688 130.705 151.788 126.331 139.685 119.396C127.507 112.418 120.673 103.183 120.673 93.5566C120.673 83.9303 127.507 74.6962 139.685 67.7188C151.788 60.7837 168.688 56.4092 187.5 56.4092Z"
stroke="#ADD9FF"
strokeWidth="5.34615"
strokeLinecap="round"
/>

{/* Left Chopstick */}
<motion.g
style={{ originX: "162px", originY: "186px" }}
animate={{
rotate: [0, -18, 0],
}}
transition={{
duration: 0.4,
repeat: Infinity,
ease: "easeInOut",
}}
>
<path
d="M177.478 87.8644C178.537 88.1337 179.184 89.2029 178.931 90.2658L155.766 187.59L148.29 185.688L174.223 89.073C174.507 88.0161 175.587 87.3834 176.648 87.6532L177.478 87.8644Z"
fill="url(#paint3_linear_1865_35373)"
/>
<path
d="M153.921 195.343C153.663 196.43 152.565 197.094 151.483 196.818L147.662 195.847C146.581 195.572 145.934 194.467 146.223 193.39L148.291 185.688L155.767 187.59L153.921 195.343Z"
fill="#1F2933"
/>
</motion.g>

{/* Right Chopstick */}
<motion.g
style={{ originX: "219px", originY: "202px" }}
animate={{
rotate: [0, 18, 0],
}}
transition={{
duration: 0.4,
repeat: Infinity,
ease: "easeInOut",
}}
>
<path
d="M200.325 103.101C199.252 103.305 198.54 104.333 198.728 105.409L215.876 203.971L223.455 202.532L203.5 104.507C203.281 103.435 202.242 102.737 201.167 102.941L200.325 103.101Z"
fill="url(#paint4_linear_1865_35373)"
/>
<path
d="M217.242 211.823C217.433 212.923 218.489 213.654 219.586 213.445L223.459 212.71C224.554 212.502 225.268 211.438 225.045 210.346L223.455 202.532L215.876 203.971L217.242 211.823Z"
fill="#1F2933"
/>
</motion.g>

<defs>
<linearGradient
id="paint0_linear_1865_35373"
x1="188"
y1="171.5"
x2="187.5"
y2="204.264"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#E5E7EB" />
<stop offset="1" stopColor="#F3F4F6" />
</linearGradient>
<linearGradient
id="paint1_linear_1865_35373"
x1="187.5"
y1="93.0699"
x2="187.5"
y2="182.749"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#66B9FF" />
<stop offset="1" stopColor="#ADD9FF" />
</linearGradient>
<linearGradient
id="paint2_linear_1865_35373"
x1="187.5"
y1="53.7363"
x2="187.5"
y2="133.378"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#7FC3FF" />
<stop offset="1" stopColor="#53B7FF" />
</linearGradient>
<linearGradient
id="paint3_linear_1865_35373"
x1="177.06"
y1="87.7582"
x2="149.452"
y2="196.302"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#FF5A3C" />
<stop offset="1" stopColor="#FF7F6B" />
</linearGradient>
<linearGradient
id="paint4_linear_1865_35373"
x1="200.748"
y1="103.021"
x2="221.644"
y2="213.054"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#FF5A3C" />
<stop offset="1" stopColor="#FF7F6B" />
</linearGradient>
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

SVG 내부에 fill, stroke, stopColor 등으로 색상이 하드코딩되어 있습니다. 스타일 가이드(라인 315-317, 489)에서는 하드코딩된 색상 값 대신 디자인 토큰 사용을 권장합니다.

SVG를 컴포넌트로 다루고 있으므로, 색상 값을 props로 받거나 CSS 변수를 활용하여 동적으로 주입하는 방식을 고려해볼 수 있습니다. 예를 들어, fill="var(--color-primary)"와 같이 사용할 수 있습니다. 이렇게 하면 디자인 시스템과의 일관성을 유지하고 향후 색상 변경에 유연하게 대처할 수 있습니다.

References
  1. 스타일링 시 하드코딩된 값을 사용하는 대신 항상 디자인 토큰을 사용해야 합니다. (link)

@RookieAND RookieAND merged commit a720a76 into develop Feb 25, 2026
8 checks passed
github-actions bot pushed a commit that referenced this pull request Feb 26, 2026
## [2.0.0-beta.7](v2.0.0-beta.6...v2.0.0-beta.7) (2026-02-26)

### Features

* opinion 랜딩 페이지 UI 업데이트 (로고·lottie·footer) ([#120](#120)) ([2817c50](2817c50))
* 추천 결과 생성 대기 페이지 및 애니메이션 구현 ([#118](#118)) ([a720a76](a720a76))
github-actions bot pushed a commit that referenced this pull request Feb 27, 2026
## [2.0.0](v1.5.1...v2.0.0) (2026-02-27)

### ⚠ BREAKING CHANGES

* OpinionForm field names changed

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* refactor: update schema field names and exports

- foodCategorySchema → categorySchema
- dislikedFoodSchema → dislikedCategoriesSchema
- preferredMenusSchema → preferredCategoriesSchema
- Update opinionFormSchema with new field names
- Replace hardcoded "ANY" with CATEGORY.ANY
* Schema field names changed

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* refactor: rename component files to use Category naming

- FoodCard → CategoryCard
- FoodCategoryCarousel → CategoryCarousel
- DislikedFoodButton → DislikedCategoryButton

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* refactor: update component export names

- FoodCard → CategoryCard
- FoodCategoryCarousel → CategoryCarousel
- DislikedFoodButton → DislikedCategoryButton
- Update component function names to match new file names
- Internal logic unchanged (will be updated in next PR)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* refactor: update import references to use new constant names

- Update all components to use new constant names:
  * RANKS → RANK_LIST
  * RANK_LABELS → RANK_LABEL
  * FOOD_CATEGORIES → CATEGORY_LIST
  * FOOD_CATEGORY_LABEL → CATEGORY_LABEL
  * FOOD_CATEGORY_VALUES → CATEGORY_VALUES
  * dislikedFoodSchema → dislikedCategoriesSchema
  * preferredMenusSchema → preferredCategoriesSchema

- Update type imports:
  * FoodCategory → Category

- Update form field references:
  * dislikedFoods → dislikedCategories
  * preferredMenus → preferredCategories

- Fix component imports after file renames:
  * FoodCategoryCarousel → CategoryCarousel
  * DislikedFoodButton → DislikedCategoryButton

This ensures the build succeeds after type system changes.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* style: run prettier format

- Format code according to project prettier rules
- No logic changes, only formatting

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

### Features

* 404, 500 에러 페이지 추가 ([#107](#107)) ([e9e5772](e9e5772))
* opinion 랜딩 페이지 UI 업데이트 (로고·lottie·footer) ([#120](#120)) ([2817c50](2817c50))
* SSE Event Registry 시스템 구현 및 마이그레이션 ([#119](#119)) ([178c79c](178c79c))
* SSE 기반 실시간 모임 현황 업데이트 구현 ([#87](#87)) ([0d220e0](0d220e0))
* 결과 페이지 - 투표 결과 섹션 구현 ([#106](#106)) ([c633225](c633225))
* 과반수 이상 의견 제출 시 추천 결과 생성 관련 API, Hook 추가 ([#103](#103)) ([5b31eec](5b31eec)), closes [#105](#105)
* 인원 수 선택 시 시각적 피드백 추가 ([e9f6de6](e9f6de6))
* 추천 결과 API response 타입 업데이트 ([#100](#100)) ([1d046ab](1d046ab)), closes [#101](#101) [#102](#102)
* 추천 결과 생성 대기 페이지 및 애니메이션 구현 ([#118](#118)) ([a720a76](a720a76))
* 취향 요약 카드 추가 ([#104](#104)) ([816fa96](816fa96))

### Bug Fixes

* 1, 3순위를 선택했으나 2순위를 선택하지 않았을 경우 Validation 을 막지 않았던 문제 수정 ([#113](#113)) ([22e58e8](22e58e8))
* GitHub Actions 워크플로우 개선 및 스타일 가이드 업데이트 ([#91](#91)) ([e6c009e](e6c009e))
* 결과 대기 페이지 내 공유 버튼 스타일을 Tertiary 로 수정 ([#115](#115)) ([a63ddd1](a63ddd1))
* 결과 페이지 - 상단 취향 요약 섹션, 하단 투표 결과 선호 카테고리 노출 순서 로직 수정 ([#117](#117)) ([f5c4a1c](f5c4a1c))
* 모임 인원 초과 및 결과 생성 완료 시 Toast 커스텀 기능 추가 ([#116](#116)) ([6088935](6088935))
* 음식 카테고리 일러스트레이터를 Figma 내 최신 시안으로 수정 ([#114](#114)) ([18f9cc3](18f9cc3))

### Code Refactoring

* opinion 페이지 컴포넌트 구조 개선 및 로직 통합 ([#109](#109)) ([a76f75f](a76f75f))
* PeopleIllustration 캐릭터 배치 및 구조 개선 ([#112](#112)) ([ae95b72](ae95b72))
* ProgressBar 컴포넌트 UI 개선 및 코드 최적화 ([#122](#122)) ([c73de1d](c73de1d))
* SSE 이벤트를 recommend-result-created로 변경 ([#121](#121)) ([222e613](222e613))
* 타입 시스템 리팩토링 - enum을 as const 패턴으로 전환 ([#110](#110)) ([d427378](d427378)), closes [#111](#111)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨ Feature 기능 개발

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant