Skip to content

Commit 8a54b4e

Browse files
authored
Merge pull request #96 from SWMTheFirstTake/dev
상품 설명 부분 user input 추가
2 parents e7dcae0 + c282070 commit 8a54b4e

File tree

2 files changed

+65
-10
lines changed

2 files changed

+65
-10
lines changed

src/api/chatAPI.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,9 @@ export const getChatProduct = async (product: Product): Promise<APIResponse<Clos
9090
};
9191
};
9292

93-
export const getChatProductDescription = async (productId: string): Promise<APIResponse<string>> => {
94-
const response = await requestAPI<APIProductDescription>(`/api/chat/products/${productId}/description`, 'GET');
93+
export const getChatProductDescription = async (productId: string, question: string): Promise<APIResponse<string>> => {
94+
const url = `/api/chat/products/${productId}/description?user_input=${question}`;
95+
const response = await requestAPI<APIProductDescription>(url, 'GET');
9596
if (response.status === 'fail') {
9697
console.error(new Error(response.message));
9798
return response;

src/components/chat/modal/ClothModal.tsx

Lines changed: 62 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
'use client';
22

3-
import React, { useCallback, useState } from 'react';
3+
import React, { useCallback, useState, KeyboardEvent } from 'react';
44
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog';
55
import Image from 'next/image';
66
import { Button } from '@/components/ui/button';
7+
import { Textarea } from '@/components/ui/textarea';
78
import { ExternalLink, ShoppingBag, Loader2 } from 'lucide-react';
89
import { useAtom, useSetAtom } from 'jotai';
910
import { activeClothAtom, panelAtom } from '@/atoms/chatAtoms';
@@ -22,6 +23,8 @@ export default function ClothModal({ product, cloth, children }: ClothModalProps
2223
const setPanel = useSetAtom(panelAtom);
2324
const [productDescription, setProductDescription] = useState('');
2425
const [isLoadingDescription, setIsLoadingDescription] = useState(false);
26+
const [showQuestionInput, setShowQuestionInput] = useState(false);
27+
const [questionInput, setQuestionInput] = useState('');
2528

2629
// 스토리지 훅 사용
2730
const { closet, addClothToCloset } = useCloset();
@@ -64,6 +67,8 @@ export default function ClothModal({ product, cloth, children }: ClothModalProps
6467
setIsOpen(false);
6568
setProductDescription('');
6669
setIsLoadingDescription(false);
70+
setShowQuestionInput(false);
71+
setQuestionInput('');
6772
}
6873
},
6974
[product, cloth, setActiveCloth],
@@ -82,6 +87,16 @@ export default function ClothModal({ product, cloth, children }: ClothModalProps
8287
setIsOpen(false);
8388
}, [activeCloth, closet, addClothToCloset, setPanel]);
8489

90+
const handleGetProductDescription = useCallback(async () => {
91+
if (!activeCloth || activeCloth.id === 'user' || !questionInput.trim()) return;
92+
setIsLoadingDescription(true);
93+
const res = await getChatProductDescription(activeCloth.id, questionInput.trim());
94+
setProductDescription(
95+
res.status === 'success' ? (typeof res.data === 'string' ? res.data : '') : '',
96+
);
97+
setIsLoadingDescription(false);
98+
}, [activeCloth, questionInput]);
99+
85100
return (
86101
<Dialog open={isOpen} onOpenChange={handleOpenChange}>
87102
<DialogTrigger asChild>{children}</DialogTrigger>
@@ -151,18 +166,57 @@ export default function ClothModal({ product, cloth, children }: ClothModalProps
151166
<Button
152167
variant="outline"
153168
className="w-full h-12 text-lg"
154-
onClick={async () => {
169+
onClick={() => {
155170
if (!activeCloth || activeCloth.id === 'user') return;
156-
setIsLoadingDescription(true);
157-
const res = await getChatProductDescription(activeCloth.id);
158-
setProductDescription(
159-
res.status === 'success' ? (typeof res.data === 'string' ? res.data : '') : '',
160-
);
161-
setIsLoadingDescription(false);
171+
setShowQuestionInput(!showQuestionInput);
172+
if (showQuestionInput) {
173+
setQuestionInput('');
174+
}
162175
}}
163176
>
164177
상품 설명 보기
165178
</Button>
179+
{showQuestionInput && (
180+
<div className="mt-3 space-y-2">
181+
<Textarea
182+
value={questionInput}
183+
onChange={(e) => setQuestionInput(e.target.value)}
184+
placeholder="상품에 대해 궁금한 점에 대해 물어보세요!"
185+
onKeyDown={(e: KeyboardEvent<HTMLTextAreaElement>) => {
186+
const isEmpty = !questionInput.trim();
187+
const isComposing = e.nativeEvent.isComposing;
188+
189+
if (isComposing) {
190+
return;
191+
}
192+
193+
const isEnter = e.key === 'Enter' && !e.shiftKey;
194+
const submitOnEnter = isEnter && !isEmpty;
195+
196+
if (submitOnEnter) {
197+
e.preventDefault();
198+
handleGetProductDescription();
199+
}
200+
}}
201+
className="w-full min-h-[80px] resize-none"
202+
rows={3}
203+
/>
204+
<Button
205+
className="w-full bg-blue-600 hover:bg-blue-700 text-white"
206+
onClick={handleGetProductDescription}
207+
disabled={!questionInput.trim() || isLoadingDescription}
208+
>
209+
{isLoadingDescription ? (
210+
<>
211+
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
212+
설명을 불러오는 중...
213+
</>
214+
) : (
215+
'질문하기'
216+
)}
217+
</Button>
218+
</div>
219+
)}
166220
</div>
167221
{isLoadingDescription && (
168222
<div className="mt-4 p-4 bg-gray-50 dark:bg-gray-900 rounded-lg border border-gray-200 dark:border-gray-700 flex items-center gap-3">

0 commit comments

Comments
 (0)