-
Notifications
You must be signed in to change notification settings - Fork 1
hotfix: 조언 페이지 QA 사항 반영 #256
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
7b68e9a
2ff5bb1
0d00ac1
bbb3ecd
eb65635
6d0520c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| import { http, HttpResponse, delay } from 'msw'; | ||
| import { AdviceChatRequest, AdviceChatResponse } from '@/model/advice/types'; | ||
|
|
||
| // 조언 채팅 요청 핸들러 | ||
| export const requestAdviceChat = http.post('/advice/chat', async ({ request }) => { | ||
| // 2초 딜레이 | ||
| await delay(2000); | ||
|
|
||
| const body = (await request.json()) as AdviceChatRequest; | ||
|
|
||
| // 응답 스타일에 따른 메시지 예시 | ||
| const adviceMessages: Record<string, string> = { | ||
| BASIC: '목표를 달성하기 위해서는 꾸준한 노력이 필요해요. 오늘 하루도 화이팅!', | ||
| WARM: '정말 잘하고 계세요! 😊 당신의 노력이 분명히 좋은 결과로 이어질 거예요. 힘내세요!', | ||
| FACTUAL: `${body.week}주차 목표를 분석한 결과, 계획된 작업을 단계적으로 진행하는 것이 효율적입니다.`, | ||
| STRATEGIC: | ||
| '목표 달성을 위해 우선순위를 정하고 작은 성취를 쌓아가는 전략을 추천드려요. 구체적인 실행 계획을 세워보세요.', | ||
| }; | ||
|
|
||
| const grorongResponse = adviceMessages[body.adviceStyle] || '오늘도 목표를 향해 한 걸음 나아가세요!'; | ||
|
|
||
| const response: AdviceChatResponse = { | ||
| data: { | ||
| remainingCount: 5, // 남은 조언 횟수 | ||
| isGoalOnboardingCompleted: body.isGoalOnboardingCompleted ?? true, | ||
| conversations: [ | ||
| { | ||
| userMessage: body.userMessage, | ||
| grorongResponse, | ||
| timestamp: new Date().toISOString(), | ||
| }, | ||
| ], | ||
| }, | ||
| }; | ||
|
|
||
| return HttpResponse.json(response, { | ||
| status: 200, | ||
| headers: { | ||
| 'Access-Control-Allow-Origin': '*', | ||
| 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, PATCH, OPTIONS', | ||
| 'Access-Control-Allow-Headers': 'Content-Type, Authorization', | ||
| }, | ||
| }); | ||
| }); | ||
|
|
||
| // CORS preflight 요청 처리 | ||
| export const optionsHandler = http.options('*', () => { | ||
| return new HttpResponse(null, { | ||
| status: 200, | ||
| headers: { | ||
| 'Access-Control-Allow-Origin': '*', | ||
| 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, PATCH, OPTIONS', | ||
| 'Access-Control-Allow-Headers': 'Content-Type, Authorization', | ||
| }, | ||
| }); | ||
| }); | ||
|
|
||
| // 모든 Advice 핸들러들을 배열로 export | ||
| export const adviceHandlers = [optionsHandler, requestAdviceChat]; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| import { z } from 'zod'; | ||
|
|
||
| const AdviceFormSchema = z.object({ | ||
| week: z.number(), | ||
| goalId: z.string().min(1, '목표 ID는 필수값입니다'), | ||
| userMessage: z.string().min(1, '조언 요청 메시지는 최소 1자 이상이어야 합니다'), | ||
| adviceStyle: z.enum(['BASIC', 'WARM', 'FACTUAL', 'STRATEGIC']).default('BASIC'), | ||
| }); | ||
|
|
||
| export { AdviceFormSchema }; |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,15 +1,21 @@ | ||||||
| import { useEffect, useRef } from 'react'; | ||||||
| import { AdviceChat } from '@/model/advice/types'; | ||||||
| import { AdviceChatMessage } from './AdviceChatMessage'; | ||||||
| import { useAdviceChatMessages } from '../hooks/useAdviceChatMessages'; | ||||||
| import { useAdviceFormContext } from '../hooks/useAdviceFormContext'; | ||||||
|
|
||||||
| type AdviceChatRenderProps = { | ||||||
| adviceChat: AdviceChat | null; | ||||||
| isSendingRequest?: boolean; | ||||||
| }; | ||||||
|
|
||||||
| export const AdviceChatHistory = ({ adviceChat = null, isSendingRequest = false }: AdviceChatRenderProps) => { | ||||||
| export const AdviceChatHistory = ({ adviceChat = null }: { adviceChat: AdviceChat | null }) => { | ||||||
| const { isSendingRequest } = useAdviceFormContext(); | ||||||
| const { displayMessages, backgroundType, shouldShowOnboarding } = useAdviceChatMessages(adviceChat, isSendingRequest); | ||||||
|
|
||||||
| const scrollRef = useRef<HTMLElement>(null); | ||||||
|
||||||
| const scrollRef = useRef<HTMLElement>(null); | |
| const scrollRef = useRef<HTMLElement | null>(null); |
Copilot
AI
Jan 26, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pb-34.5 is not a default Tailwind spacing step (and there’s no custom spacing config in the repo), so this class will likely be ignored and the chat list may overlap the sticky form. Consider using an arbitrary value (e.g., pb-[138px]) or an existing spacing token.
| <section ref={scrollRef} className="px-5 pb-34.5 absolute inset-0 overflow-y-auto z-10 space-y-4"> | |
| <section ref={scrollRef} className="px-5 pb-[138px] absolute inset-0 overflow-y-auto z-10 space-y-4"> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| import { createContext } from 'react'; | ||
| import { UseMutateAsyncFunction } from '@tanstack/react-query'; | ||
| import { AdviceChat, AdviceChatRequest } from '@/model/advice/types'; | ||
|
|
||
| /** 폼 제출 함수, 제출 상태 컨텍스트 */ | ||
| type TAdviceFormContext = { | ||
| requestAdvice: UseMutateAsyncFunction<AdviceChat, Error, AdviceChatRequest, unknown>; | ||
| isSendingRequest: boolean; | ||
| remainingCount: number; | ||
| }; | ||
| export const AdviceFormContext = createContext<TAdviceFormContext | null>(null); | ||
|
|
||
| /** 조언 스타일 선택 시트 열림 상태, 열기/닫기 함수 컨텍스트 */ | ||
| type TAdviceStyleSelectContext = { | ||
| isSheetOpen: boolean; | ||
| openSheet: () => void; | ||
| closeSheet: () => void; | ||
| }; | ||
|
|
||
| export const AdviceStyleSelectContext = createContext<TAdviceStyleSelectContext | null>(null); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The mock returns
remainingCount: 5, but the UI copy and badge default imply a 1-day max of 3 (e.g., “1일 3개 입력만 가능”,maxAdviceCount = 3). Aligning the mockremainingCountwith the UI limit will avoid confusing Storybook/MSW runs.