From 9449d26c41e2e9e6b51db035096c4cf22fe7a4c9 Mon Sep 17 00:00:00 2001 From: Yoo TaeSeung Date: Thu, 17 Apr 2025 23:51:52 +0900 Subject: [PATCH 01/22] =?UTF-8?q?feat(service):=20=EC=9D=B4=EB=AF=B8?= =?UTF-8?q?=EC=A7=80=20=ED=81=B4=EB=A6=BD=EB=B3=B4=EB=93=9C=20=EB=B3=B5?= =?UTF-8?q?=EC=82=AC=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/service/package.json | 1 + .../child-problem/[childProblemId]/page.tsx | 27 ++++++++++- .../[problemId]/main-problem/page.tsx | 27 ++++++++++- apps/service/src/utils/common/image.ts | 47 +++++++++++++++++++ apps/service/src/utils/common/index.ts | 1 + pnpm-lock.yaml | 3 ++ 6 files changed, 103 insertions(+), 3 deletions(-) create mode 100644 apps/service/src/utils/common/image.ts diff --git a/apps/service/package.json b/apps/service/package.json index df1c5302..89d91c37 100644 --- a/apps/service/package.json +++ b/apps/service/package.json @@ -20,6 +20,7 @@ "react": "^19", "react-dom": "^19", "react-hook-form": "^7.54.2", + "react-toastify": "^11.0.3", "swiper": "^11.2.5" }, "devDependencies": { diff --git a/apps/service/src/app/problem/solve/[publishId]/[problemId]/child-problem/[childProblemId]/page.tsx b/apps/service/src/app/problem/solve/[publishId]/[problemId]/child-problem/[childProblemId]/page.tsx index 644b2246..f46b4e31 100644 --- a/apps/service/src/app/problem/solve/[publishId]/[problemId]/child-problem/[childProblemId]/page.tsx +++ b/apps/service/src/app/problem/solve/[publishId]/[problemId]/child-problem/[childProblemId]/page.tsx @@ -3,7 +3,9 @@ import { useState } from 'react'; import { useParams, useRouter } from 'next/navigation'; import { SubmitHandler, useForm } from 'react-hook-form'; import Image from 'next/image'; +import { Slide, ToastContainer } from 'react-toastify'; +import { copyImageToClipboard } from '@utils'; import { useGetChildProblemById } from '@apis'; import { putChildProblemSubmit, putChildProblemSkip } from '@apis'; import { @@ -18,6 +20,7 @@ import { AnswerModalTemplate, Tag, ImageContainer, + CopyButton, } from '@components'; import { useInvalidate, useModal } from '@hooks'; import { components } from '@schema'; @@ -140,12 +143,30 @@ const Page = () => { onNext(); }; + const handleClickCopyImage = async () => { + if (!imageUrl) return; + await copyImageToClipboard(imageUrl); + }; + if (isLoading) { return <>; } return ( <> +
@@ -164,14 +185,18 @@ const Page = () => { )}
- + {`새끼 +
+ +
diff --git a/apps/service/src/app/problem/solve/[publishId]/[problemId]/main-problem/page.tsx b/apps/service/src/app/problem/solve/[publishId]/[problemId]/main-problem/page.tsx index a82d2c0e..d9e6e69c 100644 --- a/apps/service/src/app/problem/solve/[publishId]/[problemId]/main-problem/page.tsx +++ b/apps/service/src/app/problem/solve/[publishId]/[problemId]/main-problem/page.tsx @@ -3,6 +3,7 @@ import { useState } from 'react'; import { useParams, useRouter } from 'next/navigation'; import { SubmitHandler, useForm } from 'react-hook-form'; import Image from 'next/image'; +import { Slide, ToastContainer } from 'react-toastify'; import { useGetProblemById, putProblemSubmit } from '@apis'; import { @@ -16,11 +17,12 @@ import { NavigationFooter, TimeTag, ImageContainer, + CopyButton, } from '@components'; import { useInvalidate, useModal } from '@hooks'; import { ProblemStatus } from '@types'; import { useChildProblemContext } from '@/hooks/problem'; -import { trackEvent } from '@utils'; +import { copyImageToClipboard, trackEvent } from '@utils'; const statusLabel: Record = { CORRECT: '정답', @@ -111,12 +113,30 @@ const Page = () => { router.push(`/report/${publishId}/${problemId}/analysis`); }; + const handleClickCopyImage = async () => { + if (!imageUrl) return; + await copyImageToClipboard(imageUrl); + }; + if (isLoading) { return <>; } return ( <> +
@@ -136,7 +156,7 @@ const Page = () => { )}
- + {`메인 { height={200} priority /> +
+ +
{isDirect && ( diff --git a/apps/service/src/utils/common/image.ts b/apps/service/src/utils/common/image.ts new file mode 100644 index 00000000..ae8e14d7 --- /dev/null +++ b/apps/service/src/utils/common/image.ts @@ -0,0 +1,47 @@ +import { toast } from 'react-toastify'; + +/** + * 이미지를 클립보드에 복사하는 함수 + * @param imageUrl 복사할 이미지의 URL + * @returns Promise + */ +export const copyImageToClipboard = async (imageUrl: string): Promise => { + if (!imageUrl) return; + + try { + const img = document.createElement('img'); + img.crossOrigin = 'anonymous'; + img.src = imageUrl; + + img.onload = async () => { + const canvas = document.createElement('canvas'); + canvas.width = img.width; + canvas.height = img.height; + + const ctx = canvas.getContext('2d'); + ctx?.drawImage(img, 0, 0); + + canvas.toBlob(async (blob) => { + if (!blob) { + toast.error('이미지를 변환하는 데 실패했습니다.'); + return; + } + + try { + await navigator.clipboard.write([new ClipboardItem({ 'image/png': blob })]); + toast.success('이미지가 클립보드에 복사되었어요.'); + } catch (err) { + console.error('이미지 복사 실패:', err); + toast.error('이미지 복사에 실패했습니다.'); + } + }, 'image/png'); + }; + + img.onerror = () => { + toast.error('이미지를 불러오는 데 실패했습니다.'); + }; + } catch (error) { + console.error('이미지 처리 실패:', error); + toast.error('이미지 복사에 실패했습니다.'); + } +}; diff --git a/apps/service/src/utils/common/index.ts b/apps/service/src/utils/common/index.ts index 884efe34..3795e719 100644 --- a/apps/service/src/utils/common/index.ts +++ b/apps/service/src/utils/common/index.ts @@ -1,2 +1,3 @@ export * from './auth'; export * from './trackEvent'; +export * from './image'; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ea32efcb..58131003 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -195,6 +195,9 @@ importers: react-hook-form: specifier: ^7.54.2 version: 7.54.2(react@19.0.0) + react-toastify: + specifier: ^11.0.3 + version: 11.0.3(react-dom@19.0.0(react@19.0.0))(react@19.0.0) swiper: specifier: ^11.2.5 version: 11.2.5 From 6d557bb499e0691a83fdc64ca38058d43fdc812f Mon Sep 17 00:00:00 2001 From: Yoo TaeSeung Date: Sun, 20 Apr 2025 00:28:24 +0900 Subject: [PATCH 02/22] =?UTF-8?q?feat(service):=20BottomSheet=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=A0=9C=EC=9E=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/service/public/svg/ic-next-gray.svg | 3 + apps/service/src/assets/svg/IcNextGray.tsx | 17 ++++++ apps/service/src/assets/svg/index.ts | 1 + .../common/BottomSheet/BottomSheet.tsx | 42 ++++++++++++++ .../components/common/BottomSheet/index.ts | 11 ++++ .../templates/BaseBottomSheetTemplate.tsx | 56 +++++++++++++++++++ .../ChildAnswerCheckBottomSheetTemplate.tsx | 48 ++++++++++++++++ apps/service/src/components/common/index.ts | 1 + 8 files changed, 179 insertions(+) create mode 100644 apps/service/public/svg/ic-next-gray.svg create mode 100644 apps/service/src/assets/svg/IcNextGray.tsx create mode 100644 apps/service/src/components/common/BottomSheet/BottomSheet.tsx create mode 100644 apps/service/src/components/common/BottomSheet/index.ts create mode 100644 apps/service/src/components/common/BottomSheet/templates/BaseBottomSheetTemplate.tsx create mode 100644 apps/service/src/components/common/BottomSheet/templates/ChildAnswerCheckBottomSheetTemplate.tsx diff --git a/apps/service/public/svg/ic-next-gray.svg b/apps/service/public/svg/ic-next-gray.svg new file mode 100644 index 00000000..146cdbf1 --- /dev/null +++ b/apps/service/public/svg/ic-next-gray.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/service/src/assets/svg/IcNextGray.tsx b/apps/service/src/assets/svg/IcNextGray.tsx new file mode 100644 index 00000000..5f9eddc4 --- /dev/null +++ b/apps/service/src/assets/svg/IcNextGray.tsx @@ -0,0 +1,17 @@ +import type { SVGProps } from 'react'; +import { memo } from 'react'; +interface SVGRProps { + title?: string; + titleId?: string; +} +const SvgIcNextGray = ({ title, titleId, ...props }: SVGProps & SVGRProps) => ( + + {title ? {title} : null} + + +); +const Memo = memo(SvgIcNextGray); +export default Memo; diff --git a/apps/service/src/assets/svg/index.ts b/apps/service/src/assets/svg/index.ts index c18c0573..ed88684f 100644 --- a/apps/service/src/assets/svg/index.ts +++ b/apps/service/src/assets/svg/index.ts @@ -12,6 +12,7 @@ export { default as IcList } from './IcList'; export { default as IcMinusSmall } from './IcMinusSmall'; export { default as IcMinus } from './IcMinus'; export { default as IcNextBlack } from './IcNextBlack'; +export { default as IcNextGray } from './IcNextGray'; export { default as IcNext } from './IcNext'; export { default as IcNotice } from './IcNotice'; export { default as IcPrevBlack } from './IcPrevBlack'; diff --git a/apps/service/src/components/common/BottomSheet/BottomSheet.tsx b/apps/service/src/components/common/BottomSheet/BottomSheet.tsx new file mode 100644 index 00000000..a49dde95 --- /dev/null +++ b/apps/service/src/components/common/BottomSheet/BottomSheet.tsx @@ -0,0 +1,42 @@ +'use client'; +import { useEffect } from 'react'; +import { createPortal } from 'react-dom'; + +interface BottomSheetProps { + isOpen: boolean; + onClose: () => void; + children: React.ReactNode; +} + +const potalElement = + typeof window !== 'undefined' && (document.getElementById('modal') as HTMLElement); + +const BottomSheet = ({ isOpen, onClose, children = null }: BottomSheetProps) => { + useEffect(() => { + if (isOpen) { + document.body.style.overflow = 'hidden'; + } else { + document.body.style.overflow = ''; + } + + return () => { + document.body.style.overflow = ''; + }; + }, [isOpen]); + + if (!isOpen || !potalElement) return null; + + return createPortal( +
+
+
e.stopPropagation()}> + {children} +
+
, + potalElement + ); +}; + +export default BottomSheet; diff --git a/apps/service/src/components/common/BottomSheet/index.ts b/apps/service/src/components/common/BottomSheet/index.ts new file mode 100644 index 00000000..701759e5 --- /dev/null +++ b/apps/service/src/components/common/BottomSheet/index.ts @@ -0,0 +1,11 @@ +import BottomSheet from './BottomSheet'; +import BaseBottomSheetTemplate from './templates/BaseBottomSheetTemplate'; +import MainAnswerCheckBottomSheetTemplate from './templates/MainAnswerCheckBottomSheetTemplate'; +import ChildAnswerCheckBottomSheetTemplate from './templates/ChildAnswerCheckBottomSheetTemplate'; + +export { + BottomSheet, + BaseBottomSheetTemplate, + MainAnswerCheckBottomSheetTemplate, + ChildAnswerCheckBottomSheetTemplate, +}; diff --git a/apps/service/src/components/common/BottomSheet/templates/BaseBottomSheetTemplate.tsx b/apps/service/src/components/common/BottomSheet/templates/BaseBottomSheetTemplate.tsx new file mode 100644 index 00000000..413f9cbe --- /dev/null +++ b/apps/service/src/components/common/BottomSheet/templates/BaseBottomSheetTemplate.tsx @@ -0,0 +1,56 @@ +import { IcNextGray } from '@svg'; + +interface BottomSheetButtonProps { + variant?: 'default' | 'recommend'; + label: string; + onClick?: () => void; +} + +const BaseBottomSheetTemplate = ({ children }: { children: React.ReactNode }) => { + return ( +
+ {children} +
+ ); +}; + +const BottomSheetContent = ({ children }: { children: React.ReactNode }) => { + return ( +
+ {children} +
+ ); +}; + +const BottomSheetText = ({ text }: { text: string }) => { + return

{text}

; +}; + +const BottomSheetButtonSection = ({ children }: { children: React.ReactNode }) => { + return
{children}
; +}; + +const BottomSheetButton = ({ variant = 'default', label, onClick }: BottomSheetButtonProps) => { + return ( +
+
+

{label}

+ {variant === 'recommend' && ( +
+ 추천 +
+ )} +
+ +
+ ); +}; + +BaseBottomSheetTemplate.Content = BottomSheetContent; +BaseBottomSheetTemplate.Text = BottomSheetText; +BaseBottomSheetTemplate.ButtonSection = BottomSheetButtonSection; +BaseBottomSheetTemplate.Button = BottomSheetButton; + +export default BaseBottomSheetTemplate; diff --git a/apps/service/src/components/common/BottomSheet/templates/ChildAnswerCheckBottomSheetTemplate.tsx b/apps/service/src/components/common/BottomSheet/templates/ChildAnswerCheckBottomSheetTemplate.tsx new file mode 100644 index 00000000..be7e248a --- /dev/null +++ b/apps/service/src/components/common/BottomSheet/templates/ChildAnswerCheckBottomSheetTemplate.tsx @@ -0,0 +1,48 @@ +import { IcCorrect, IcIncorrect } from '@svg'; +import { components } from '@schema'; + +import BaseBottomSheetTemplate from './BaseBottomSheetTemplate'; + +type ChildProblemSubmitUpdateResponse = components['schemas']['ChildProblemSubmitUpdateResponse']; + +interface ChildAnswerCheckBottomSheetTemplateProps { + result: ChildProblemSubmitUpdateResponse | undefined; + onClose: () => void; + handleClickShowPointing?: () => void; + handleClickNext?: () => void; + handleClickShowAnswer?: () => void; +} + +const ChildAnswerCheckBottomSheetTemplate = ({ + result, + onClose, + handleClickShowPointing, + handleClickNext, + handleClickShowAnswer, +}: ChildAnswerCheckBottomSheetTemplateProps) => { + if (!result) return null; + + const { status } = result; + const isCorrect = status === 'CORRECT' || status === 'RETRY_CORRECT'; + + return ( + + + {isCorrect ? : } + + + + + + + + + + ); +}; + +export default ChildAnswerCheckBottomSheetTemplate; diff --git a/apps/service/src/components/common/index.ts b/apps/service/src/components/common/index.ts index 9c20a662..211a1378 100644 --- a/apps/service/src/components/common/index.ts +++ b/apps/service/src/components/common/index.ts @@ -13,6 +13,7 @@ import ImageContainer from './ImageContainer'; export * from './Buttons'; export * from './Inputs'; export * from './Modals'; +export * from './BottomSheet'; export { Tag, From e45f064bb0f26af1a801d57b7a73816a5005ea39 Mon Sep 17 00:00:00 2001 From: Yoo TaeSeung Date: Sun, 20 Apr 2025 01:25:40 +0900 Subject: [PATCH 03/22] =?UTF-8?q?feat(service):=20BottomSheet=20=EC=95=A0?= =?UTF-8?q?=EB=8B=88=EB=A9=94=EC=9D=B4=EC=85=98=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?=EB=B0=8F=20=EC=9D=BC=EB=B6=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../child-problem/[childProblemId]/page.tsx | 14 +++++- .../common/BottomSheet/BottomSheet.tsx | 49 +++++++++++++------ .../ChildAnswerCheckBottomSheetTemplate.tsx | 8 ++- 3 files changed, 52 insertions(+), 19 deletions(-) diff --git a/apps/service/src/app/problem/solve/[publishId]/[problemId]/child-problem/[childProblemId]/page.tsx b/apps/service/src/app/problem/solve/[publishId]/[problemId]/child-problem/[childProblemId]/page.tsx index f46b4e31..b6f75c93 100644 --- a/apps/service/src/app/problem/solve/[publishId]/[problemId]/child-problem/[childProblemId]/page.tsx +++ b/apps/service/src/app/problem/solve/[publishId]/[problemId]/child-problem/[childProblemId]/page.tsx @@ -21,6 +21,8 @@ import { Tag, ImageContainer, CopyButton, + BottomSheet, + ChildAnswerCheckBottomSheetTemplate, } from '@components'; import { useInvalidate, useModal } from '@hooks'; import { components } from '@schema'; @@ -229,7 +231,17 @@ const Page = () => { onClickNext={isSubmitted ? handleClickNext : handleClickFooterSkipButton} /> - + + {}} + handleClickNext={handleClickNextProblemButton} + handleClickShowAnswer={handleClickShowAnswer} + /> + + + { + const [shouldRender, setShouldRender] = useState(false); + const [isAnimating, setIsAnimating] = useState(false); + const [showBackdrop, setShowBackdrop] = useState(false); -const BottomSheet = ({ isOpen, onClose, children = null }: BottomSheetProps) => { useEffect(() => { if (isOpen) { + setShouldRender(true); document.body.style.overflow = 'hidden'; - } else { - document.body.style.overflow = ''; - } - return () => { - document.body.style.overflow = ''; - }; - }, [isOpen]); + requestAnimationFrame(() => { + requestAnimationFrame(() => { + setIsAnimating(true); + setShowBackdrop(true); + }); + }); + } else if (shouldRender) { + setIsAnimating(false); + setShowBackdrop(false); + + const timeout = setTimeout(() => { + setShouldRender(false); + document.body.style.overflow = ''; + }, 300); + + return () => clearTimeout(timeout); + } + }, [isOpen, shouldRender]); - if (!isOpen || !potalElement) return null; + if (!shouldRender || !portalElement) return null; return createPortal( -
-
+
+ {showBackdrop &&
}
e.stopPropagation()}> {children}
, - potalElement + portalElement ); }; diff --git a/apps/service/src/components/common/BottomSheet/templates/ChildAnswerCheckBottomSheetTemplate.tsx b/apps/service/src/components/common/BottomSheet/templates/ChildAnswerCheckBottomSheetTemplate.tsx index be7e248a..d90be10e 100644 --- a/apps/service/src/components/common/BottomSheet/templates/ChildAnswerCheckBottomSheetTemplate.tsx +++ b/apps/service/src/components/common/BottomSheet/templates/ChildAnswerCheckBottomSheetTemplate.tsx @@ -38,8 +38,12 @@ const ChildAnswerCheckBottomSheetTemplate = ({ onClick={handleClickShowPointing} /> - - + {!isCorrect && ( + <> + + + + )} ); From a2f5cbf3367b989693ec71f332a2ffbaab705a57 Mon Sep 17 00:00:00 2001 From: Yoo TaeSeung Date: Sun, 20 Apr 2025 01:52:07 +0900 Subject: [PATCH 04/22] =?UTF-8?q?feat(service):=20=EB=A9=94=EC=9D=B8=20?= =?UTF-8?q?=EB=AC=B8=EC=A0=9C=20=EC=B1=84=EC=A0=90=20=EB=AA=A8=EB=8B=AC=20?= =?UTF-8?q?->=20=EB=B0=94=ED=85=80=EC=8B=9C=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../child-problem/[childProblemId]/page.tsx | 8 ---- .../[problemId]/main-problem/page.tsx | 11 +++-- .../MainAnswerCheckBottomSheetTemplate.tsx | 41 +++++++++++++++++++ 3 files changed, 48 insertions(+), 12 deletions(-) create mode 100644 apps/service/src/components/common/BottomSheet/templates/MainAnswerCheckBottomSheetTemplate.tsx diff --git a/apps/service/src/app/problem/solve/[publishId]/[problemId]/child-problem/[childProblemId]/page.tsx b/apps/service/src/app/problem/solve/[publishId]/[problemId]/child-problem/[childProblemId]/page.tsx index b6f75c93..ae2e0c7b 100644 --- a/apps/service/src/app/problem/solve/[publishId]/[problemId]/child-problem/[childProblemId]/page.tsx +++ b/apps/service/src/app/problem/solve/[publishId]/[problemId]/child-problem/[childProblemId]/page.tsx @@ -241,14 +241,6 @@ const Page = () => { /> - - - { onClickNext={isSubmitted ? handleClickNext : undefined} /> - - + - + ); }; diff --git a/apps/service/src/components/common/BottomSheet/templates/MainAnswerCheckBottomSheetTemplate.tsx b/apps/service/src/components/common/BottomSheet/templates/MainAnswerCheckBottomSheetTemplate.tsx new file mode 100644 index 00000000..78e7737f --- /dev/null +++ b/apps/service/src/components/common/BottomSheet/templates/MainAnswerCheckBottomSheetTemplate.tsx @@ -0,0 +1,41 @@ +import { IcCorrect, IcIncorrect } from '@svg'; +import { ProblemStatus } from '@types'; + +import BaseBottomSheetTemplate from './BaseBottomSheetTemplate'; + +interface MainAnswerCheckModalTemplateProps { + result: ProblemStatus | undefined; + onClose: () => void; + handleClickStepSolve: () => void; + handleClickShowReport: () => void; +} + +const MainAnswerCheckModalTemplate = ({ + result, + onClose, + handleClickStepSolve, + handleClickShowReport, +}: MainAnswerCheckModalTemplateProps) => { + if (!result) return null; + const isCorrect = result === 'CORRECT' || result === 'RETRY_CORRECT'; + + return ( + + + {isCorrect ? : } + + + + + + {!isCorrect && } + + + ); +}; + +export default MainAnswerCheckModalTemplate; From c2f6b3d8fb65cd597eba08602f1b6858a1499f41 Mon Sep 17 00:00:00 2001 From: Yoo TaeSeung Date: Sun, 20 Apr 2025 01:57:29 +0900 Subject: [PATCH 05/22] =?UTF-8?q?chore(service):=20=EC=95=88=EC=93=B0?= =?UTF-8?q?=EB=8A=94=20Import=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../[problemId]/child-problem/[childProblemId]/page.tsx | 1 - .../problem/solve/[publishId]/[problemId]/main-problem/page.tsx | 2 -- 2 files changed, 3 deletions(-) diff --git a/apps/service/src/app/problem/solve/[publishId]/[problemId]/child-problem/[childProblemId]/page.tsx b/apps/service/src/app/problem/solve/[publishId]/[problemId]/child-problem/[childProblemId]/page.tsx index ae2e0c7b..ca91ca53 100644 --- a/apps/service/src/app/problem/solve/[publishId]/[problemId]/child-problem/[childProblemId]/page.tsx +++ b/apps/service/src/app/problem/solve/[publishId]/[problemId]/child-problem/[childProblemId]/page.tsx @@ -15,7 +15,6 @@ import { NavigationFooter, ProgressHeader, SmallButton, - ChildAnswerCheckModalTemplate, TwoButtonModalTemplate, AnswerModalTemplate, Tag, diff --git a/apps/service/src/app/problem/solve/[publishId]/[problemId]/main-problem/page.tsx b/apps/service/src/app/problem/solve/[publishId]/[problemId]/main-problem/page.tsx index f49d0c8c..7320e411 100644 --- a/apps/service/src/app/problem/solve/[publishId]/[problemId]/main-problem/page.tsx +++ b/apps/service/src/app/problem/solve/[publishId]/[problemId]/main-problem/page.tsx @@ -9,8 +9,6 @@ import { useGetProblemById, putProblemSubmit } from '@apis'; import { AnswerInput, Button, - MainAnswerCheckModalTemplate, - PortalModal, Tag, ProgressHeader, SmallButton, From 2be66beea18a820c1de44cd6646c445be937d5d4 Mon Sep 17 00:00:00 2001 From: Yoo TaeSeung Date: Sun, 20 Apr 2025 03:31:16 +0900 Subject: [PATCH 06/22] =?UTF-8?q?feat(service):=20=ED=86=A0=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EB=A9=94=EC=8B=9C=EC=A7=80=20=EC=8A=A4=ED=83=80?= =?UTF-8?q?=EC=9D=BC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../[problemId]/child-problem/[childProblemId]/page.tsx | 9 +++++++-- .../solve/[publishId]/[problemId]/main-problem/page.tsx | 9 +++++++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/apps/service/src/app/problem/solve/[publishId]/[problemId]/child-problem/[childProblemId]/page.tsx b/apps/service/src/app/problem/solve/[publishId]/[problemId]/child-problem/[childProblemId]/page.tsx index ca91ca53..e55ef95c 100644 --- a/apps/service/src/app/problem/solve/[publishId]/[problemId]/child-problem/[childProblemId]/page.tsx +++ b/apps/service/src/app/problem/solve/[publishId]/[problemId]/child-problem/[childProblemId]/page.tsx @@ -156,16 +156,21 @@ const Page = () => { return ( <> diff --git a/apps/service/src/app/problem/solve/[publishId]/[problemId]/main-problem/page.tsx b/apps/service/src/app/problem/solve/[publishId]/[problemId]/main-problem/page.tsx index 7320e411..48855f60 100644 --- a/apps/service/src/app/problem/solve/[publishId]/[problemId]/main-problem/page.tsx +++ b/apps/service/src/app/problem/solve/[publishId]/[problemId]/main-problem/page.tsx @@ -125,16 +125,21 @@ const Page = () => { return ( <> From 3a054d7a8f036d179e88893d9b7da29ef4304b2c Mon Sep 17 00:00:00 2001 From: Yoo TaeSeung Date: Sun, 20 Apr 2025 03:46:22 +0900 Subject: [PATCH 07/22] =?UTF-8?q?design(service):=20=EB=B3=B5=EC=82=AC?= =?UTF-8?q?=ED=95=98=EA=B8=B0=20=EB=B2=84=ED=8A=BC=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EC=9D=B8=ED=95=9C=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=83=81?= =?UTF-8?q?=ED=95=98=20=EC=97=AC=EB=B0=B1=20=EC=A1=B0=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/service/src/components/common/ImageContainer.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/service/src/components/common/ImageContainer.tsx b/apps/service/src/components/common/ImageContainer.tsx index fd36d6a3..f4deb905 100644 --- a/apps/service/src/components/common/ImageContainer.tsx +++ b/apps/service/src/components/common/ImageContainer.tsx @@ -6,7 +6,9 @@ interface ImageContainerProps { } const ImageContainer = ({ children, className = '' }: ImageContainerProps) => { - return
{children}
; + return ( +
{children}
+ ); }; export default ImageContainer; From 24ee8606ead527a2cd9253829688f75c7e34c810 Mon Sep 17 00:00:00 2001 From: Yoo TaeSeung Date: Sun, 20 Apr 2025 03:47:27 +0900 Subject: [PATCH 08/22] =?UTF-8?q?feat(service):=20=EC=8D=B8=EB=84=A4?= =?UTF-8?q?=EC=9D=BC=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=82=AD=EC=A0=9C(?= =?UTF-8?q?=EB=B0=94=EB=A1=9C=20=EB=A9=94=EC=9D=B8=EB=AC=B8=EC=A0=9C=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=EB=A1=9C)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/submit/postProblemSubmit.ts | 6 +- .../src/app/problem/list/[publishId]/page.tsx | 2 +- .../[problemId]/SolveButtonsClient.tsx | 2 +- .../solve/[publishId]/[problemId]/page.tsx | 244 ++++++++++++++++-- .../components/problem/ProblemStatusCard.tsx | 8 +- 5 files changed, 231 insertions(+), 31 deletions(-) diff --git a/apps/service/src/apis/controller/submit/postProblemSubmit.ts b/apps/service/src/apis/controller/submit/postProblemSubmit.ts index 2752cd74..2753eca8 100644 --- a/apps/service/src/apis/controller/submit/postProblemSubmit.ts +++ b/apps/service/src/apis/controller/submit/postProblemSubmit.ts @@ -1,10 +1,10 @@ import { client } from '@apis'; -const postProblemSubmit = async (publishId: string, problemId: string) => { +const postProblemSubmit = async (publishId: number, problemId: number) => { return await client.POST('/api/v1/client/problemSubmit', { body: { - publishId: Number(publishId), - problemId: Number(problemId), + publishId, + problemId, }, }); }; diff --git a/apps/service/src/app/problem/list/[publishId]/page.tsx b/apps/service/src/app/problem/list/[publishId]/page.tsx index 92a820bf..b62b9df7 100644 --- a/apps/service/src/app/problem/list/[publishId]/page.tsx +++ b/apps/service/src/app/problem/list/[publishId]/page.tsx @@ -25,7 +25,7 @@ const Page = () => { ); diff --git a/apps/service/src/app/problem/solve/[publishId]/[problemId]/SolveButtonsClient.tsx b/apps/service/src/app/problem/solve/[publishId]/[problemId]/SolveButtonsClient.tsx index b68c8ec6..19cf71d5 100644 --- a/apps/service/src/app/problem/solve/[publishId]/[problemId]/SolveButtonsClient.tsx +++ b/apps/service/src/app/problem/solve/[publishId]/[problemId]/SolveButtonsClient.tsx @@ -19,7 +19,7 @@ const SolveButtonsClient = ({ publishId, problemId }: SolveButtonsClientProps) = const handleClickDirect = async () => { trackEvent('problem_solve_direct_button_click'); - await postProblemSubmit(publishId, problemId); + await postProblemSubmit(Number(publishId), Number(problemId)); invalidateAll(); router.push(`/problem/solve/${publishId}/${problemId}/main-problem`); }; diff --git a/apps/service/src/app/problem/solve/[publishId]/[problemId]/page.tsx b/apps/service/src/app/problem/solve/[publishId]/[problemId]/page.tsx index 1b51fd1c..e0334d51 100644 --- a/apps/service/src/app/problem/solve/[publishId]/[problemId]/page.tsx +++ b/apps/service/src/app/problem/solve/[publishId]/[problemId]/page.tsx @@ -1,18 +1,124 @@ 'use client'; - -import { useParams } from 'next/navigation'; +import { useState } from 'react'; +import { useParams, useRouter } from 'next/navigation'; +import { SubmitHandler, useForm } from 'react-hook-form'; import Image from 'next/image'; +import { Slide, ToastContainer } from 'react-toastify'; + +import { useGetProblemById, putProblemSubmit, useGetChildData } from '@apis'; +import { + AnswerInput, + Button, + Tag, + ProgressHeader, + SmallButton, + NavigationFooter, + TimeTag, + ImageContainer, + CopyButton, + MainAnswerCheckBottomSheetTemplate, + BottomSheet, +} from '@components'; +import { useInvalidate, useModal } from '@hooks'; +import { ProblemStatus } from '@types'; +import { useChildProblemContext } from '@/hooks/problem'; +import { copyImageToClipboard, trackEvent } from '@utils'; -import { useGetProblemThumbnail } from '@apis'; -import { ImageContainer, ProgressHeader, TimeTag } from '@components'; +const statusLabel: Record = { + CORRECT: '정답', + INCORRECT: '오답', + RETRY_CORRECT: '정답', + NOT_STARTED: '시작전', +}; -import SolveButtonsClient from './SolveButtonsClient'; +const statusColor: Record = { + CORRECT: 'green', + INCORRECT: 'red', + RETRY_CORRECT: 'green', + NOT_STARTED: 'gray', +}; const Page = () => { const { publishId, problemId } = useParams<{ publishId: string; problemId: string }>(); + const router = useRouter(); + const { childProblemLength } = useChildProblemContext(); + const { invalidateAll } = useInvalidate(); + + const { isOpen, openModal, closeModal } = useModal(); + const [result, setResult] = useState(); + const { register, handleSubmit, watch } = useForm<{ answer: string }>(); + const selectedAnswer = watch('answer'); + + // apis + const { data, isLoading } = useGetProblemById(publishId, problemId); + const { data: childData } = useGetChildData(publishId, problemId); + const childProblemId = childData?.data?.childProblemIds[0]; + + const { + number, + imageUrl, + recommendedMinute, + recommendedSecond, + status, + childProblemStatuses = [], + answerType = 'MULTIPLE_CHOICE', + answer, + } = data?.data ?? {}; + + const isSolved = status === 'CORRECT' || status === 'RETRY_CORRECT'; + const isSubmitted = status === 'CORRECT' || status === 'RETRY_CORRECT' || status === 'INCORRECT'; + const isDirect = + childProblemStatuses.length > 0 && + childProblemStatuses[childProblemStatuses.length - 1] === 'NOT_STARTED'; + + const prevButtonLabel = + isDirect || childProblemLength === 0 + ? `메인 문제 ${number}번` + : `새끼 문제 ${number}-${childProblemLength}번`; + const nextButtonLabel = '해설 보기'; + + const handleSubmitAnswer: SubmitHandler<{ answer: string }> = async ({ answer }) => { + const { data } = await putProblemSubmit(publishId, problemId, answer); + const resultData = data?.data; + invalidateAll(); + + setResult(resultData); + if (resultData) { + openModal(); + } + }; - const { data, isLoading } = useGetProblemThumbnail(publishId, problemId); - const { number, imageUrl, recommendedMinute, recommendedSecond } = data?.data ?? {}; + const handleClickStepSolve = () => { + trackEvent('problem_main_solve_step_solve_button_click'); + router.push(`/problem/solve/${publishId}/${problemId}/child-problem/${childProblemId}`); + }; + + const handleClickPrev = () => { + trackEvent('problem_main_solve_footer_prev_button_click', { + buttonLabel: prevButtonLabel, + }); + router.back(); + }; + + const handleClickNext = () => { + trackEvent('problem_main_solve_footer_show_commentary_button_click'); + router.push(`/report/${publishId}/${problemId}/analysis`); + }; + + const handleClickSolveAgain = () => { + trackEvent('problem_main_solve_check_modal_solve_again_button_click'); + closeModal(); + }; + + const handleClickShowReport = () => { + trackEvent('problem_main_solve_check_modal_commentary_button_click'); + router.push(`/report/${publishId}/${problemId}/analysis`); + }; + + const handleClickCopyImage = async () => { + if (!imageUrl) return; + await copyImageToClipboard(imageUrl); + }; if (isLoading) { return <>; @@ -20,25 +126,115 @@ const Page = () => { return ( <> - -
-
-

메인 문제 {number}번

- + + +
+
+
+
+

메인 문제 {number}번

+ +
+ {isSolved && ( + + 정답 + + )} + {status === 'INCORRECT' && ( + + 오답 + + )} +
+ + {`메인 +
+ +
+
+ + {/* {isDirect && ( +
+ + 단계별로 풀어보기 + +
+ )} + + {!isDirect && childProblemStatuses.length > 0 && ( +
+

새끼 문제 정답

+
+ {childProblemStatuses.map((childProblemStatus, index) => ( +
+ + {number}-{index + 1}번 + + + {statusLabel[childProblemStatus]} + +
+ ))} +
+
+ )} */} +
+ +
+
+

정답 선택

+
+ + +
+
- - {`메인 - - -
+ + {/* */} + + + + ); }; diff --git a/apps/service/src/components/problem/ProblemStatusCard.tsx b/apps/service/src/components/problem/ProblemStatusCard.tsx index c649d498..f8e34a63 100644 --- a/apps/service/src/components/problem/ProblemStatusCard.tsx +++ b/apps/service/src/components/problem/ProblemStatusCard.tsx @@ -7,12 +7,13 @@ import { Button, StatusIcon, StatusTag } from '@components'; import { trackEvent } from '@utils'; import { components } from '@schema'; import { IcDown, IcUp } from '@svg'; +import { postProblemSubmit } from '@apis'; type ProblemFeedProgressesGetResponse = components['schemas']['ProblemFeedProgressesGetResponse']; interface ProblemStatusCardProps { mainProblemNumber: number; - publishId: string; + publishId: number; problemData: ProblemFeedProgressesGetResponse; } @@ -29,10 +30,13 @@ const ProblemStatusCard = ({ const isSolved = status === 'CORRECT' || status === 'RETRY_CORRECT' || status === 'INCORRECT'; - const handleClickSolveButton = () => { + const handleClickSolveButton = async () => { trackEvent('problem_list_card_solve_button_click', { problemId: problemId ?? '', }); + + if (!problemId) return; + await postProblemSubmit(publishId, problemId); router.push(`/problem/solve/${publishId}/${problemId}`); }; From 3bcf4f1c05bd2f5eb66cd051a6953f8de01eea93 Mon Sep 17 00:00:00 2001 From: Yoo TaeSeung Date: Sun, 20 Apr 2025 03:52:00 +0900 Subject: [PATCH 09/22] =?UTF-8?q?feat(service):=20=EB=AC=B8=EC=A0=9C=20?= =?UTF-8?q?=ED=92=80=EC=9D=B4=20=ED=94=8C=EB=A1=9C=EC=9A=B0=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=EC=97=90=20=EB=94=B0=EB=A5=B8=20=EC=A0=9C=EC=B6=9C=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20=EC=8B=9C=EC=A0=90=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../problem/solve/[publishId]/[problemId]/page.tsx | 11 +++++++++-- .../src/components/problem/ProblemStatusCard.tsx | 3 +++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/apps/service/src/app/problem/solve/[publishId]/[problemId]/page.tsx b/apps/service/src/app/problem/solve/[publishId]/[problemId]/page.tsx index e0334d51..dbaf9192 100644 --- a/apps/service/src/app/problem/solve/[publishId]/[problemId]/page.tsx +++ b/apps/service/src/app/problem/solve/[publishId]/[problemId]/page.tsx @@ -5,7 +5,12 @@ import { SubmitHandler, useForm } from 'react-hook-form'; import Image from 'next/image'; import { Slide, ToastContainer } from 'react-toastify'; -import { useGetProblemById, putProblemSubmit, useGetChildData } from '@apis'; +import { + useGetProblemById, + putProblemSubmit, + useGetChildData, + postChildProblemSubmit, +} from '@apis'; import { AnswerInput, Button, @@ -88,8 +93,10 @@ const Page = () => { } }; - const handleClickStepSolve = () => { + const handleClickStepSolve = async () => { trackEvent('problem_main_solve_step_solve_button_click'); + await postChildProblemSubmit(publishId, problemId); + invalidateAll(); router.push(`/problem/solve/${publishId}/${problemId}/child-problem/${childProblemId}`); }; diff --git a/apps/service/src/components/problem/ProblemStatusCard.tsx b/apps/service/src/components/problem/ProblemStatusCard.tsx index f8e34a63..d6d3b547 100644 --- a/apps/service/src/components/problem/ProblemStatusCard.tsx +++ b/apps/service/src/components/problem/ProblemStatusCard.tsx @@ -8,6 +8,7 @@ import { trackEvent } from '@utils'; import { components } from '@schema'; import { IcDown, IcUp } from '@svg'; import { postProblemSubmit } from '@apis'; +import { useInvalidate } from '@hooks'; type ProblemFeedProgressesGetResponse = components['schemas']['ProblemFeedProgressesGetResponse']; @@ -22,6 +23,7 @@ const ProblemStatusCard = ({ publishId, problemData, }: ProblemStatusCardProps) => { + const { invalidateAll } = useInvalidate(); const { problemId, status, childProblemStatuses } = problemData; const router = useRouter(); const [isOpen, setIsOpen] = useState( @@ -37,6 +39,7 @@ const ProblemStatusCard = ({ if (!problemId) return; await postProblemSubmit(publishId, problemId); + invalidateAll(); router.push(`/problem/solve/${publishId}/${problemId}`); }; From 8e3eadc8ec925825e0f6c2386a53ff5867850b4b Mon Sep 17 00:00:00 2001 From: Yoo TaeSeung Date: Sun, 20 Apr 2025 04:08:17 +0900 Subject: [PATCH 10/22] =?UTF-8?q?feat(service):=20=EB=AC=B8=EC=A0=9C=20?= =?UTF-8?q?=ED=92=80=EC=9D=B4=20=ED=94=8C=EB=A1=9C=EC=9A=B0=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=EC=97=90=20=EB=94=B0=EB=A5=B8=20=EC=83=88=EB=81=BC=20?= =?UTF-8?q?=EB=AC=B8=EC=A0=9C=20=ED=91=B8=ED=84=B0=20=EB=B2=84=ED=8A=BC=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../[problemId]/child-problem/[childProblemId]/page.tsx | 4 +--- apps/service/src/components/common/NavigationFooter.tsx | 4 ++-- apps/service/src/contexts/ProblemContext.tsx | 2 +- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/apps/service/src/app/problem/solve/[publishId]/[problemId]/child-problem/[childProblemId]/page.tsx b/apps/service/src/app/problem/solve/[publishId]/[problemId]/child-problem/[childProblemId]/page.tsx index e55ef95c..303228b7 100644 --- a/apps/service/src/app/problem/solve/[publishId]/[problemId]/child-problem/[childProblemId]/page.tsx +++ b/apps/service/src/app/problem/solve/[publishId]/[problemId]/child-problem/[childProblemId]/page.tsx @@ -68,9 +68,7 @@ const Page = () => { } = data?.data ?? {}; const prevButtonLabel = - childProblemNumber === 1 - ? `메인 문제 ${problemNumber}번` - : `새끼 문제 ${problemNumber}-${childProblemNumber - 1}번`; + childProblemNumber === 1 ? '' : `새끼 문제 ${problemNumber}-${childProblemNumber - 1}번`; const nextButtonLabel = childProblemNumber === childProblemLength diff --git a/apps/service/src/components/common/NavigationFooter.tsx b/apps/service/src/components/common/NavigationFooter.tsx index 7baf63da..41c61491 100644 --- a/apps/service/src/components/common/NavigationFooter.tsx +++ b/apps/service/src/components/common/NavigationFooter.tsx @@ -16,12 +16,12 @@ const NavigationFooter = ({ return (
- {prevLabel && onClickPrev && ( + {prevLabel && prevLabel !== '' && onClickPrev && ( )}
- {nextLabel && onClickNext && ( + {nextLabel && nextLabel !== '' && onClickNext && ( )}
diff --git a/apps/service/src/contexts/ProblemContext.tsx b/apps/service/src/contexts/ProblemContext.tsx index bad04eb4..9eae3a54 100644 --- a/apps/service/src/contexts/ProblemContext.tsx +++ b/apps/service/src/contexts/ProblemContext.tsx @@ -36,7 +36,7 @@ export const ProblemProvider = ({ children }: { children: React.ReactNode }) => const onNext = () => { if (step === childProblemIds.length - 1) { - router.push(`${baseUrl}/main-problem`); + router.push(baseUrl); } else if (step < childProblemIds.length - 1) { router.push(`${baseUrl}/child-problem/${childProblemIds[step + 1]}`); setStep(step + 1); From 3c9bdc761cffe990a6129969d6ba0569475c6749 Mon Sep 17 00:00:00 2001 From: Yoo TaeSeung Date: Sun, 20 Apr 2025 04:16:02 +0900 Subject: [PATCH 11/22] =?UTF-8?q?design(service):=20=ED=8F=AC=EC=9D=B8?= =?UTF-8?q?=ED=8C=85=20=EB=B2=84=ED=8A=BC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/service/public/svg/ic-next-small.svg | 3 +++ apps/service/src/assets/svg/IcNextSmall.tsx | 17 +++++++++++++++++ apps/service/src/assets/svg/index.ts | 1 + .../src/components/report/PrescriptionCard.tsx | 14 ++++++++++++-- 4 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 apps/service/public/svg/ic-next-small.svg create mode 100644 apps/service/src/assets/svg/IcNextSmall.tsx diff --git a/apps/service/public/svg/ic-next-small.svg b/apps/service/public/svg/ic-next-small.svg new file mode 100644 index 00000000..ed7f2cb2 --- /dev/null +++ b/apps/service/public/svg/ic-next-small.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/service/src/assets/svg/IcNextSmall.tsx b/apps/service/src/assets/svg/IcNextSmall.tsx new file mode 100644 index 00000000..5a1943ea --- /dev/null +++ b/apps/service/src/assets/svg/IcNextSmall.tsx @@ -0,0 +1,17 @@ +import type { SVGProps } from 'react'; +import { memo } from 'react'; +interface SVGRProps { + title?: string; + titleId?: string; +} +const SvgIcNextSmall = ({ title, titleId, ...props }: SVGProps & SVGRProps) => ( + + {title ? {title} : null} + + +); +const Memo = memo(SvgIcNextSmall); +export default Memo; diff --git a/apps/service/src/assets/svg/index.ts b/apps/service/src/assets/svg/index.ts index ed88684f..98acec29 100644 --- a/apps/service/src/assets/svg/index.ts +++ b/apps/service/src/assets/svg/index.ts @@ -13,6 +13,7 @@ export { default as IcMinusSmall } from './IcMinusSmall'; export { default as IcMinus } from './IcMinus'; export { default as IcNextBlack } from './IcNextBlack'; export { default as IcNextGray } from './IcNextGray'; +export { default as IcNextSmall } from './IcNextSmall'; export { default as IcNext } from './IcNext'; export { default as IcNotice } from './IcNotice'; export { default as IcPrevBlack } from './IcPrevBlack'; diff --git a/apps/service/src/components/report/PrescriptionCard.tsx b/apps/service/src/components/report/PrescriptionCard.tsx index daf83a57..c6fe1619 100644 --- a/apps/service/src/components/report/PrescriptionCard.tsx +++ b/apps/service/src/components/report/PrescriptionCard.tsx @@ -1,5 +1,12 @@ import { SmallButton } from '@components'; -import { IcStatusCorrect, IcStatusIncorrect, IcStatusNotStarted, IcStatusRetried } from '@svg'; +import { + IcNext, + IcNextSmall, + IcStatusCorrect, + IcStatusIncorrect, + IcStatusNotStarted, + IcStatusRetried, +} from '@svg'; type PrescriptionCardProps = { status?: 'CORRECT' | 'INCORRECT' | 'RETRY_CORRECT' | 'IN_PROGRESS' | 'NOT_STARTED'; @@ -28,7 +35,10 @@ const PrescriptionCard = ({ status = 'NOT_STARTED', title, onClick }: Prescripti {statusIcon(status)}

{title}

- 진단 받기 +
+ 포인팅 + +
); }; From f230009f1e9058aab317402e958a8ca79800c93f Mon Sep 17 00:00:00 2001 From: Yoo TaeSeung Date: Sun, 20 Apr 2025 04:16:21 +0900 Subject: [PATCH 12/22] =?UTF-8?q?chore(service):=20=EC=95=88=EC=93=B0?= =?UTF-8?q?=EB=8A=94=20import=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/service/src/components/report/PrescriptionCard.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/service/src/components/report/PrescriptionCard.tsx b/apps/service/src/components/report/PrescriptionCard.tsx index c6fe1619..0cfbd57b 100644 --- a/apps/service/src/components/report/PrescriptionCard.tsx +++ b/apps/service/src/components/report/PrescriptionCard.tsx @@ -1,6 +1,4 @@ -import { SmallButton } from '@components'; import { - IcNext, IcNextSmall, IcStatusCorrect, IcStatusIncorrect, From 0194d84d3378f67e46f243f9a991ede56123c5c0 Mon Sep 17 00:00:00 2001 From: Yoo TaeSeung Date: Sun, 20 Apr 2025 04:20:37 +0900 Subject: [PATCH 13/22] =?UTF-8?q?design(service):=20=ED=8F=AC=EC=9D=B8?= =?UTF-8?q?=ED=8C=85=20=EC=83=81=EC=84=B8=20=ED=8E=98=EC=9D=B4=EC=A7=80=20?= =?UTF-8?q?=ED=83=80=EC=9D=B4=ED=8B=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../[publishId]/[problemId]/prescription/detail/page.tsx | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/apps/service/src/app/report/[publishId]/[problemId]/prescription/detail/page.tsx b/apps/service/src/app/report/[publishId]/[problemId]/prescription/detail/page.tsx index d35d9569..a34e4565 100644 --- a/apps/service/src/app/report/[publishId]/[problemId]/prescription/detail/page.tsx +++ b/apps/service/src/app/report/[publishId]/[problemId]/prescription/detail/page.tsx @@ -3,7 +3,7 @@ import { useSearchParams } from 'next/navigation'; import Image from 'next/image'; -import { Header, ImageContainer } from '@components'; +import { Button, Header, ImageContainer } from '@components'; import { useReportContext } from '@/hooks/report'; const Page = () => { @@ -34,10 +34,9 @@ const Page = () => { return ( <> -
-
-

{title}

-
+
+
+
Date: Sun, 20 Apr 2025 15:34:52 +0900 Subject: [PATCH 14/22] =?UTF-8?q?fix(service):=20=ED=91=BC=20=EB=AC=B8?= =?UTF-8?q?=EC=A0=9C=20=EC=A0=95=EB=8B=B5=20=ED=91=9C=EC=8B=9C=EB=90=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../solve/[publishId]/[problemId]/page.tsx | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/apps/service/src/app/problem/solve/[publishId]/[problemId]/page.tsx b/apps/service/src/app/problem/solve/[publishId]/[problemId]/page.tsx index dbaf9192..a52d4370 100644 --- a/apps/service/src/app/problem/solve/[publishId]/[problemId]/page.tsx +++ b/apps/service/src/app/problem/solve/[publishId]/[problemId]/page.tsx @@ -1,5 +1,5 @@ 'use client'; -import { useState } from 'react'; +import { useEffect, useState } from 'react'; import { useParams, useRouter } from 'next/navigation'; import { SubmitHandler, useForm } from 'react-hook-form'; import Image from 'next/image'; @@ -51,7 +51,7 @@ const Page = () => { const { isOpen, openModal, closeModal } = useModal(); const [result, setResult] = useState(); - const { register, handleSubmit, watch } = useForm<{ answer: string }>(); + const { register, handleSubmit, watch, setValue } = useForm<{ answer: string }>(); const selectedAnswer = watch('answer'); // apis @@ -76,6 +76,8 @@ const Page = () => { childProblemStatuses.length > 0 && childProblemStatuses[childProblemStatuses.length - 1] === 'NOT_STARTED'; + const hasChildProblem = childProblemStatuses.length > 0; + const prevButtonLabel = isDirect || childProblemLength === 0 ? `메인 문제 ${number}번` @@ -101,9 +103,7 @@ const Page = () => { }; const handleClickPrev = () => { - trackEvent('problem_main_solve_footer_prev_button_click', { - buttonLabel: prevButtonLabel, - }); + trackEvent('problem_main_solve_footer_prev_button_click'); router.back(); }; @@ -127,6 +127,12 @@ const Page = () => { await copyImageToClipboard(imageUrl); }; + useEffect(() => { + if (isSolved && answer) { + setValue('answer', answer); + } + }, [isSolved, answer, setValue]); + if (isLoading) { return <>; } @@ -184,7 +190,7 @@ const Page = () => {
- {/* {isDirect && ( + {hasChildProblem && (
단계별로 풀어보기 @@ -192,7 +198,7 @@ const Page = () => {
)} - {!isDirect && childProblemStatuses.length > 0 && ( + {/* {!isDirect && childProblemStatuses.length > 0 && (

새끼 문제 정답

From a613f6bf6e959203188f0ed241b8eb626bd8eb95 Mon Sep 17 00:00:00 2001 From: Yoo TaeSeung Date: Sun, 20 Apr 2025 17:33:21 +0900 Subject: [PATCH 15/22] =?UTF-8?q?design(service):=20ProgressHeader=20?= =?UTF-8?q?=EB=B2=84=ED=8A=BC=20cursor:=20pointer=EB=A1=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/service/src/components/common/ProgressHeader.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/service/src/components/common/ProgressHeader.tsx b/apps/service/src/components/common/ProgressHeader.tsx index 563cd542..d0b5fa84 100644 --- a/apps/service/src/components/common/ProgressHeader.tsx +++ b/apps/service/src/components/common/ProgressHeader.tsx @@ -22,7 +22,12 @@ const ProgressHeader = ({ progress }: ProgressHeaderProps) => { return (
- +
{progress !== undefined && }
From 1322d83e86a855d598e72e8f8ec86e54f3627e20 Mon Sep 17 00:00:00 2001 From: Yoo TaeSeung Date: Sun, 20 Apr 2025 17:34:18 +0900 Subject: [PATCH 16/22] =?UTF-8?q?fix(service):=20=EB=AC=B8=EC=A0=9C=20?= =?UTF-8?q?=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=ED=99=94=EB=A9=B4=20=EC=B9=B4?= =?UTF-8?q?=EB=93=9C=20=EC=97=B4=EB=A6=BC=20=EC=83=81=ED=83=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/problem/ProblemStatusCard.tsx | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/apps/service/src/components/problem/ProblemStatusCard.tsx b/apps/service/src/components/problem/ProblemStatusCard.tsx index d6d3b547..3f5e0d87 100644 --- a/apps/service/src/components/problem/ProblemStatusCard.tsx +++ b/apps/service/src/components/problem/ProblemStatusCard.tsx @@ -1,7 +1,7 @@ 'use client'; import { useRouter } from 'next/navigation'; -import React, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; import { Button, StatusIcon, StatusTag } from '@components'; import { trackEvent } from '@utils'; @@ -30,7 +30,12 @@ const ProblemStatusCard = ({ !childProblemStatuses?.every((childStatus) => childStatus === 'NOT_STARTED') ); + useEffect(() => { + setIsOpen(!childProblemStatuses?.every((childStatus) => childStatus === 'NOT_STARTED')); + }, [childProblemStatuses]); + const isSolved = status === 'CORRECT' || status === 'RETRY_CORRECT' || status === 'INCORRECT'; + const hasChildProblem = childProblemStatuses && childProblemStatuses?.length > 0; const handleClickSolveButton = async () => { trackEvent('problem_list_card_solve_button_click', { @@ -57,12 +62,14 @@ const ProblemStatusCard = ({

메인 문제 {mainProblemNumber}번

-
setIsOpen((prev) => !prev)}> - {isOpen ? : } -
+ {hasChildProblem && ( +
setIsOpen((prev) => !prev)}> + {isOpen ? : } +
+ )}
- {isOpen && ( + {isOpen && hasChildProblem && (