-
Notifications
You must be signed in to change notification settings - Fork 0
[WIP] ReSummaryModal 디자인 수정 및 api 연결 #300
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
base: main
Are you sure you want to change the base?
Changes from all commits
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,36 @@ | ||
| import { safeFetch } from '@/hooks/util/server/safeFetch'; | ||
| import { SummaryData, SummaryRes } from '@/types/api/summaryApi'; | ||
|
|
||
| const API_URL = process.env.NEXT_PUBLIC_BASE_API_URL; | ||
| const API_TOKEN = process.env.NEXT_PUBLIC_API_TOKEN; | ||
|
|
||
| if (!API_URL) throw new Error('Missing environment variable: NEXT_PUBLIC_BASE_API_URL'); | ||
| if (!API_TOKEN) throw new Error('Missing environment variable: NEXT_PUBLIC_API_TOKEN'); | ||
|
|
||
| const SUMMARY_ENDPOINT = `${API_URL}/v1/links`; | ||
|
|
||
| type Params = { | ||
| id: number; | ||
| format?: 'CONCISE' | 'DETAILED'; | ||
| }; | ||
|
|
||
| export const fetchNewSummary = async (params: Params): Promise<SummaryData> => { | ||
| const url = new URL(`${SUMMARY_ENDPOINT}/${params.id}/summary`); | ||
| if (params.format) { | ||
| url.searchParams.set('format', params.format); | ||
| } | ||
|
|
||
| const body = await safeFetch<SummaryRes>(url.toString(), { | ||
| method: 'GET', | ||
| headers: { | ||
| 'Content-Type': 'application/json', | ||
| Authorization: `Bearer ${API_TOKEN}`, | ||
| }, | ||
| timeout: 15_000, | ||
| jsonContentTypeCheck: true, | ||
| }); | ||
| if (!body?.data || !body.success) { | ||
| throw new Error(body?.message ?? 'Invalid response structure'); | ||
| } | ||
| return body.data; | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| export default function NewSummary({ content }: { content: string }) { | ||
| return ( | ||
| <div className="flex flex-1 flex-col gap-2"> | ||
| <span className="font-label-sm">재생성 요약</span> | ||
| <div className="rounded-lg bg-white p-2"> | ||
| <div className="custom-scrollbar font-body-md h-45 overflow-auto pr-1">{content}</div> | ||
| </div> | ||
| </div> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,45 +1,46 @@ | ||
| import Button from '@/components/basics/Button/Button'; | ||
| // import Toast from '@/components/basics/Toast/Toast'; | ||
| import { useModalStore } from '@/stores/modalStore'; | ||
| import { showToast } from '@/stores/toastStore'; | ||
|
|
||
| interface Props { | ||
| disabled: boolean; | ||
| type: 'prev' | 'new'; | ||
| isWriting?: boolean; | ||
| // content: string; | ||
| onClick: () => void; | ||
| } | ||
|
|
||
| export default function PostReSummaryButton({ type, isWriting }: Props) { | ||
| export default function PostReSummaryButton({ type, disabled, onClick }: Props) { | ||
| const { close } = useModalStore(); | ||
| // const [toastVisible, setToastVisible] = useState(false); | ||
|
|
||
| const onClick = () => { | ||
| close(); | ||
| // setToastVisible(true); | ||
| const handleClick = () => { | ||
| onClick(); | ||
| if (type === 'prev') { | ||
| close(); | ||
| showToast({ | ||
| id: 'save-prev', | ||
| message: '기존 요약을 유지했습니다.', | ||
| variant: 'success', | ||
| }); | ||
| } else if (type === 'new') { | ||
| close(); | ||
| showToast({ | ||
| id: 'save-new', | ||
| message: '새로운 요약을 저장했습니다.', | ||
| variant: 'success', | ||
| }); | ||
| } | ||
| }; | ||
|
|
||
| // const handleToastClose = () => { | ||
| // setToastVisible(false); | ||
| // }; | ||
|
|
||
| return ( | ||
| <> | ||
| <Button | ||
| variant={type === 'prev' ? 'secondary' : 'primary'} | ||
| label={type === 'prev' ? '기존 요약 유지하기' : '새 요약 적용하기'} | ||
| disabled={isWriting} | ||
| label={type === 'prev' ? '기존 요약 유지하기' : '재생성된 요약 덮어쓰기'} | ||
| disabled={disabled} | ||
| className="w-full" | ||
| onClick={onClick} | ||
| onClick={handleClick} | ||
| /> | ||
|
|
||
| {/* {toastVisible && ( | ||
| <Toast | ||
| id="summary-toast" | ||
| message="요약을 적용했습니다." | ||
| variant="success" | ||
| duration={2000} | ||
| onClose={handleToastClose} | ||
| /> | ||
| )} */} | ||
| </> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| export default function PrevSummary({ content }: { content: string }) { | ||
| return ( | ||
| <div className="flex flex-1 flex-col gap-2"> | ||
| <span className="font-label-sm">기존 요약</span> | ||
| <div className="rounded-lg bg-white p-2"> | ||
| <div className="custom-scrollbar font-body-md h-45 overflow-auto pr-1">{content}</div> | ||
| </div> | ||
| </div> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,68 +1,75 @@ | ||||||||||||||||||||||||||||||||
| 'use client'; | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| import SVGIcon from '@/components/Icons/SVGIcon'; | ||||||||||||||||||||||||||||||||
| import Badge from '@/components/basics/Badge/Badge'; | ||||||||||||||||||||||||||||||||
| import IconButton from '@/components/basics/IconButton/IconButton'; | ||||||||||||||||||||||||||||||||
| import Modal from '@/components/basics/Modal/Modal'; | ||||||||||||||||||||||||||||||||
| import ProgressNotification from '@/components/basics/ProgressNotification/ProgressNotification'; | ||||||||||||||||||||||||||||||||
| import clsx from 'clsx'; | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| import NewSummary from './NewSummary'; | ||||||||||||||||||||||||||||||||
| import PostReSummaryButton from './PostReSummaryButton'; | ||||||||||||||||||||||||||||||||
| import PrevSummary from './PrevSummary'; | ||||||||||||||||||||||||||||||||
| import useReSummary from './hooks/useReSummary'; | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| const DIFF = | ||||||||||||||||||||||||||||||||
| '어쩌구 저쩌구 어쩌구 저쩌구 어쩌구저쩌구 어쩌구저쩌구 어쩌구저쩌구 어쩌구저쩌구 어쩌구저쩌구 어쩌구저쩌구 어쩌구저쩌구 어쩌구저쩌구 어쩌구저쩌구 어쩌구저쩌구 어쩌구저쩌구 어쩌구저쩌구 어쩌구저쩌구 어쩌구저쩌구 어쩌구저쩌구 어쩌구저쩌구 어쩌구저쩌구 어쩌구저쩌구 어쩌구저쩌구 어쩌구저쩌구 어쩌구저쩌구 어쩌구저쩌구 어쩌구저쩌구 어쩌구저쩌구 어쩌구저쩌구 어쩌구저쩌구'; | ||||||||||||||||||||||||||||||||
| const PREV = | ||||||||||||||||||||||||||||||||
| '기존 요약입니다. 기존 요약입니다. 기존 요약입니다. 기존 요약입니다. 기존 요약입니다. 기존 요약입니다. 기존 요약입니다. 기존 요약입니다. 기존 요약입니다. 기존 요약입니다. 기존 요약입니다. 기존 요약입니다. 기존 요약입니다. 기존 요약입니다. 기존 요약입니다. 기존 요약입니다. 기존 요약입니다. 기존 요약입니다. 기존 요약입니다. 기존 요약입니다. 기존 요약입니다. 기존 요약입니다. 기존 요약입니다. 기존 요약입니다. 기존 요약입니다. 기존 요약입니다. 기존 요약입니다. 기존 요약입니다. 기존 요약입니다. 기존 요약입니다. '; | ||||||||||||||||||||||||||||||||
| const NEW = | ||||||||||||||||||||||||||||||||
| '새로운 요약입니다. 새로운 요약입니다. 새로운 요약입니다. 새로운 요약입니다. 새로운 요약입니다. 새로운 요약입니다. 새로운 요약입니다. 새로운 요약입니다. 새로운 요약입니다. 새로운 요약입니다. 새로운 요약입니다. 새로운 요약입니다. 새로운 요약입니다. 새로운 요약입니다. 새로운 요약입니다. 새로운 요약입니다. 새로운 요약입니다. 새로운 요약입니다. 새로운 요약입니다. 새로운 요약입니다. 새로운 요약입니다. 새로운 요약입니다. 새로운 요약입니다. 새로운 요약입니다. 새로운 요약입니다. 새로운 요약입니다. 새로운 요약입니다. 새로운 요약입니다. 새로운 요약입니다. 새로운 요약입니다. 새로운 요약입니다. 새로운 요약입니다. 새로운 요약입니다. 새로운 요약입니다. '; | ||||||||||||||||||||||||||||||||
| '어쩌구저쩌구 어쩌구저쩌구 어쩌구저쩌구 어쩌구저쩌구 어쩌구저쩌구 어쩌구저쩌구 어쩌구저쩌구 어쩌구저쩌구 어쩌구저쩌구 어쩌구저쩌구 어쩌구저쩌구'; | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| // interface ReSummary { | ||||||||||||||||||||||||||||||||
| // id: string; | ||||||||||||||||||||||||||||||||
| // interface ReSummaryProps { | ||||||||||||||||||||||||||||||||
| // id: number; | ||||||||||||||||||||||||||||||||
| // } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| export default function ReSummaryModal() { | ||||||||||||||||||||||||||||||||
| const { loading, writing, error } = useReSummary(); | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| const prevContent = '이전요약이전요약'; | ||||||||||||||||||||||||||||||||
| const newContent = '새 요약 새 요약'; | ||||||||||||||||||||||||||||||||
| // TODO: api 연결(아직 api 작성 안함) | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||
| <Modal type="RE_SUMMARY" className="m-10 max-w-240 min-w-150"> | ||||||||||||||||||||||||||||||||
| <div> | ||||||||||||||||||||||||||||||||
| <Modal | ||||||||||||||||||||||||||||||||
| type="RE_SUMMARY" | ||||||||||||||||||||||||||||||||
| className={clsx('m-10 max-w-240 min-w-150', error && 'border-red500 border')} | ||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||
| <div className="p-2"> | ||||||||||||||||||||||||||||||||
| <span className="font-title-md">요약 비교</span> | ||||||||||||||||||||||||||||||||
| {loading && ( | ||||||||||||||||||||||||||||||||
| <div className="flex gap-2"> | ||||||||||||||||||||||||||||||||
| <span>SUMMARY COMPARE</span> | ||||||||||||||||||||||||||||||||
| <SVGIcon icon="IC_SumGenerate" /> | ||||||||||||||||||||||||||||||||
| <span>요약 재생성 중입니다...</span> | ||||||||||||||||||||||||||||||||
| <div className="text-gray500 flex gap-2"> | ||||||||||||||||||||||||||||||||
| <ProgressNotification animated={loading} /> | ||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||
| {error && ( | ||||||||||||||||||||||||||||||||
| <div className="text-red500 mb-64 flex items-center gap-2"> | ||||||||||||||||||||||||||||||||
| <span className="font-body-md">요약 재생성 중 문제가 발생했습니다.</span> | ||||||||||||||||||||||||||||||||
| <IconButton | ||||||||||||||||||||||||||||||||
| icon="IC_Regenerate" | ||||||||||||||||||||||||||||||||
| size="sm" | ||||||||||||||||||||||||||||||||
| variant="tertiary_subtle" | ||||||||||||||||||||||||||||||||
| ariaLabel="요약 재생성 재시도" | ||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||
|
Comment on lines
+43
to
+48
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 재시도 버튼에 onClick 핸들러가 없습니다.
🔎 수정 제안 <IconButton
icon="IC_Regenerate"
size="sm"
variant="tertiary_subtle"
ariaLabel="요약 재생성 재시도"
+ onClick={() => {
+ // TODO: 재시도 로직 구현
+ }}
/>📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||
| {error && <div>에러가 발생했습니다.</div>} | ||||||||||||||||||||||||||||||||
| {!loading && !error && ( | ||||||||||||||||||||||||||||||||
| <div className="flex flex-col gap-5"> | ||||||||||||||||||||||||||||||||
| <div className="flex flex-col gap-2"> | ||||||||||||||||||||||||||||||||
| <span className="font-title-md">요약 비교</span> | ||||||||||||||||||||||||||||||||
| <span className="relative flex gap-2 rounded-lg bg-white p-2"> | ||||||||||||||||||||||||||||||||
| <Badge label="WHAT'S CHANGED" className="h-fit" /> | ||||||||||||||||||||||||||||||||
| <span className="custom-scrollbar font-body-md max-h-20 w-full overflow-auto"> | ||||||||||||||||||||||||||||||||
| {DIFF} | ||||||||||||||||||||||||||||||||
| </span> | ||||||||||||||||||||||||||||||||
| <span className="relative flex items-center gap-2 rounded-lg bg-white p-2"> | ||||||||||||||||||||||||||||||||
| <Badge icon="IC_SumGenerate" label="변화 지점" className="h-fit" /> | ||||||||||||||||||||||||||||||||
| <span className="font-label-md w-full truncate">{DIFF}</span> | ||||||||||||||||||||||||||||||||
| </span> | ||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||
| <div className="flex gap-2"> | ||||||||||||||||||||||||||||||||
| <div className="flex flex-1 flex-col gap-2"> | ||||||||||||||||||||||||||||||||
| <span className="font-title-sm">기존 요약</span> | ||||||||||||||||||||||||||||||||
| <div className="rounded-lg bg-white p-2"> | ||||||||||||||||||||||||||||||||
| <div className="custom-scrollbar h-45 overflow-auto pr-1">{PREV}</div> | ||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||
| <div className="flex flex-1 flex-col gap-2"> | ||||||||||||||||||||||||||||||||
| <span className="font-title-sm">재생성 요약</span> | ||||||||||||||||||||||||||||||||
| <div className="rounded-lg bg-white p-2"> | ||||||||||||||||||||||||||||||||
| <div className="custom-scrollbar h-45 overflow-auto pr-1">{NEW}</div> | ||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||
| <div className="flex gap-2"> | ||||||||||||||||||||||||||||||||
| <PostReSummaryButton type="prev" isWriting={writing} /> | ||||||||||||||||||||||||||||||||
| <PostReSummaryButton type="new" isWriting={writing} /> | ||||||||||||||||||||||||||||||||
| <PrevSummary content={prevContent} /> | ||||||||||||||||||||||||||||||||
| <NewSummary content={newContent} /> | ||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||
| <div className="flex gap-2"> | ||||||||||||||||||||||||||||||||
| <PostReSummaryButton type="prev" disabled={writing} onClick={() => console.log('prev')} /> | ||||||||||||||||||||||||||||||||
| <PostReSummaryButton | ||||||||||||||||||||||||||||||||
| type="new" | ||||||||||||||||||||||||||||||||
| disabled={!!error || loading || writing} | ||||||||||||||||||||||||||||||||
| onClick={() => console.log('new')} | ||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||
| </Modal> | ||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| export type SummaryData = { | ||
| existingSummary: string; | ||
| newSummary: string; | ||
| comparison: string; | ||
| }; | ||
|
|
||
| export type SummaryRes = { | ||
| success: boolean; | ||
| status: string; | ||
| message: string; | ||
| data: SummaryData; | ||
| timestamp: string; | ||
| }; |
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.
모듈 로드 시점의 환경 변수 검증은 런타임 오류를 유발할 수 있습니다.
모듈이 import되는 시점에 환경 변수가 없으면 즉시 에러가 발생합니다. 테스트 환경이나 특정 빌드 시나리오에서 문제가 될 수 있습니다. 또한
NEXT_PUBLIC_API_TOKEN은 클라이언트에 노출되므로, 민감한 토큰인 경우 서버 사이드에서만 사용하는 것이 좋습니다.🔎 개선 제안
🤖 Prompt for AI Agents