From 37682dd4d8d81c7a768b38b8c940f144630a8d0e Mon Sep 17 00:00:00 2001 From: minha Date: Fri, 26 Dec 2025 21:08:34 +0900 Subject: [PATCH 1/3] =?UTF-8?q?develop=EC=97=90=EC=84=9C=20ToastCopy=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EA=B0=80=EC=A0=B8=EC=98=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 7 +++ package.json | 1 + src/components/Modal/ModalTest.jsx | 72 ++++++++++++------------- src/contexts/Toast/ToastCopy.jsx | 24 +++++++++ src/contexts/Toast/ToastCopy.module.css | 15 ++++++ src/main.jsx | 6 ++- 6 files changed, 86 insertions(+), 39 deletions(-) create mode 100644 src/contexts/Toast/ToastCopy.jsx create mode 100644 src/contexts/Toast/ToastCopy.module.css diff --git a/package-lock.json b/package-lock.json index 9558ad4..840d0a3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "openmind", "version": "0.0.0", "dependencies": { + "pretendard": "^1.3.9", "react": "^19.2.0", "react-dom": "^19.2.0", "react-router": "^7.10.1", @@ -4286,6 +4287,12 @@ "node": ">= 0.8.0" } }, + "node_modules/pretendard": { + "version": "1.3.9", + "resolved": "https://registry.npmjs.org/pretendard/-/pretendard-1.3.9.tgz", + "integrity": "sha512-PaQAADyLY5v4kYFwkpSJHbSSYIkiriY/1xXw75TKoZ9UQQqeU+tvP05yTdZAWibiIYoo8ZKtRv8PM7w0IaywSw==", + "license": "OFL-1.1" + }, "node_modules/prettier": { "version": "3.7.4", "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.7.4.tgz", diff --git a/package.json b/package.json index 17a1f79..e465a1d 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "preview": "vite preview" }, "dependencies": { + "pretendard": "^1.3.9", "react": "^19.2.0", "react-dom": "^19.2.0", "react-router": "^7.10.1", diff --git a/src/components/Modal/ModalTest.jsx b/src/components/Modal/ModalTest.jsx index 5b780aa..00dcf95 100644 --- a/src/components/Modal/ModalTest.jsx +++ b/src/components/Modal/ModalTest.jsx @@ -1,50 +1,46 @@ -// src/pages/ModalTest.jsx import { useState } from 'react'; -import QuestionModal from './QuestionModal'; -import Button from '../../components/Button/Button'; +import Button from '@/components/Button/Button'; +import QuestionModal from '@/components/Modal/QuestionModal'; +/** + * Modal 테스트 페이지 + * + * UI가 잘 작동하는지 테스트 + * - 콘솔에서 질문 내용 확인 + */ export default function ModalTest() { const [isOpen, setIsOpen] = useState(false); - const handleOpenModal = () => setIsOpen(true); - const handleCloseModal = () => setIsOpen(false); + + // 테스트용 받는 사람 정보 + const recipient = { + name: '테스트용', + imageSource: + 'https://fastly.picsum.photos/id/505/200/200.jpg?hmac=c295sjTIAZ_9Gj-PENrzAbATNIiWPL1dmhIhWndYnyo', // 임시 이미지 + }; + const testSubjectId = 12629; // 실제 존재하는 ID Test + + const QuestionSuccess = newQuestion => { + console.log('질문 등록 성공!', newQuestion); + }; return ( -
- {/* // style={{ padding: '40px' }} */} -

모달 테스트 페이지

+
+

Modal 테스트

- +
+
- + {/* QuestionModal 테스트 버튼 */} + - {/* Box 버튼 - 기본 */} - - {/* Box 버튼 - 전체 너비 (외부 제어) */} - - {/* Floating 버튼 */} - - - {/* 비활성화 */} - - {/* 화살표 아이콘 */} - + {/* QuestionModal */} + setIsOpen(false)} + recipient={recipient} + subjectId={testSubjectId} + onSuccess={QuestionSuccess} + />
); } diff --git a/src/contexts/Toast/ToastCopy.jsx b/src/contexts/Toast/ToastCopy.jsx new file mode 100644 index 0000000..baa7a86 --- /dev/null +++ b/src/contexts/Toast/ToastCopy.jsx @@ -0,0 +1,24 @@ +import { createContext, useContext, useState } from 'react'; +import styles from './ToastCopy.module.css'; + +const ToastContext = createContext(null); + +export function ToastProvider({ children }) { + const [toast, setToast] = useState(null); + + const showToast = msg => { + setToast(msg); + setTimeout(() => setToast(null), 2000); + }; + + return ( + + {children} + {toast &&
{toast}
} +
+ ); +} + +export function useToast() { + return useContext(ToastContext); +} diff --git a/src/contexts/Toast/ToastCopy.module.css b/src/contexts/Toast/ToastCopy.module.css new file mode 100644 index 0000000..6d7e96d --- /dev/null +++ b/src/contexts/Toast/ToastCopy.module.css @@ -0,0 +1,15 @@ +.ToastText { + position: fixed; + bottom: 10%; + left: 50%; + z-index: 9999; + transform: translateX(-50%); + display: flex; + align-items: center; + justify-content: center; + border-radius: 8px; + padding: 12px 20px; + background-color: var(--gray-60); + color: var(--gray-10); + box-shadow: 0px 4px 4px 0px rgba(0, 0, 0, 0.25); +} diff --git a/src/main.jsx b/src/main.jsx index b6bdc60..a6bc97e 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -9,7 +9,7 @@ import List from './pages/List/ListPage.jsx'; import PostDetail from './pages/Post/PostDetail.jsx'; import Answer from './pages/Answer/index.jsx'; import Layout from './components/layout/Layout.jsx'; - +import ModalTest from './components/Modal/ModalTest.jsx'; import './index.css'; const router = createBrowserRouter([ @@ -25,6 +25,10 @@ const router = createBrowserRouter([ path: 'list', Component: List, }, + { + path: '/ModalTest', + element: , + }, { path: 'post', children: [ From fbc81bd8c2ac68a0a50132aa99b8ee0eb0139a66 Mon Sep 17 00:00:00 2001 From: minha Date: Sat, 27 Dec 2025 13:37:15 +0900 Subject: [PATCH 2/3] =?UTF-8?q?pull=20=ED=95=B4=EC=98=A4=EA=B8=B0=EC=A0=84?= =?UTF-8?q?=20=EC=A0=80=EC=9E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Modal/QuestionModal.jsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/components/Modal/QuestionModal.jsx b/src/components/Modal/QuestionModal.jsx index 5e21570..d8e2924 100644 --- a/src/components/Modal/QuestionModal.jsx +++ b/src/components/Modal/QuestionModal.jsx @@ -44,17 +44,14 @@ function QuestionModal({ isOpen, onClose, recipient, subjectId, onSuccess }) { // 성공 시 처리 setContent(''); // 입력창 비우기 onClose(); // 모달 닫기 - + alert('질문 등록 성공했습니다.!'); onSuccess(newQuestion); // 부모에게 새 질문 데이터 전달 console.log('onSuccess 성공내역 ', newQuestion); } catch (error) { // 실패 시 에러 처리 - console.error('질문 등록 실패:', error); alert('질문 등록에 실패했습니다. 다시 시도해주세요.'); } finally { - // 전송 종료 - alert('질문 등록 성공했습니다.!'); setIsSubmitting(false); // 버튼 보이게 } }; From b907a089d6872363d81066cc47970cffd0e8cc22 Mon Sep 17 00:00:00 2001 From: minha Date: Sat, 27 Dec 2025 15:12:29 +0900 Subject: [PATCH 3/3] =?UTF-8?q?feat:=20QuestionModal=20=EB=A6=AC=ED=8C=A9?= =?UTF-8?q?=ED=86=A0=EB=A7=81=20=EB=B0=8F=20Toast,=20Avatar=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Modal/QuestionModal.jsx | 44 ++++++++++--------------- src/components/Modal/QuestionTarget.jsx | 16 +++++---- 2 files changed, 26 insertions(+), 34 deletions(-) diff --git a/src/components/Modal/QuestionModal.jsx b/src/components/Modal/QuestionModal.jsx index d8e2924..209a4a5 100644 --- a/src/components/Modal/QuestionModal.jsx +++ b/src/components/Modal/QuestionModal.jsx @@ -1,33 +1,25 @@ import { useState } from 'react'; -import Modal from '../Modal/Modal'; -import Button from '../Button/Button'; -import TextArea from '../TextArea/TextArea'; -import QuestionTarget from './QuestionTarget'; -import ModalHeader from './ModalHeader'; -import { createQuestion } from '@/services/questionsApi'; +import Modal from '@/components/Modal/Modal'; +import Button from '@/components/Button/Button'; +import TextArea from '@/components/TextArea/TextArea'; +import QuestionTarget from '@/components/Modal/QuestionTarget'; +import ModalHeader from '@/components/Modal/ModalHeader'; +import { useToast } from '@/contexts/Toast/ToastCopy'; import styles from './QuestionModal.module.css'; /** - * 질문 작성 모달 컴포넌트 (API 연동 완료) - * - * 질문을 작성하고 서버에 전송 + * 질문 작성 모달 컴포넌트 * * @param {boolean} isOpen - 모달 열림/닫힘 (true/false) * @param {function} onClose - 모달 닫기 함수 * @param {object} recipient - 받는 사람 정보 { name, imageSource } - * @param {number} subjectId - 질문 받을 사람의 ID (서버 전송 시 필요) - * @param {function} onSuccess - 질문 등록 성공 시 실행할 함수 + * @param {function} onSuccess - 질문 제출 시 실행할 콜백 함수 (content를 인자로 받음) */ -function QuestionModal({ isOpen, onClose, recipient, subjectId, onSuccess }) { - // 질문 내용 상태 - // 사용자가 textarea에 입력하는 내용을 저장 +function QuestionModal({ isOpen, onClose, recipient, onSuccess }) { + // 입력 내용 상태 const [content, setContent] = useState(''); - - // 전송 중 상태 - // true: API 호출 중 (버튼 비활성화, 중복 클릭 방지) - // false: 대기 중 const [isSubmitting, setIsSubmitting] = useState(false); - + const { showToast } = useToast(); // 모달 닫았을 시 내용 삭제 const handleClose = () => { setContent(''); @@ -38,19 +30,17 @@ function QuestionModal({ isOpen, onClose, recipient, subjectId, onSuccess }) { setIsSubmitting(true); try { - // API 호출 - const newQuestion = await createQuestion(subjectId, { content }); + // 부모가 전달한 callback 실행 (content만 전달) + await onSuccess(content); // 성공 시 처리 - setContent(''); // 입력창 비우기 - onClose(); // 모달 닫기 - alert('질문 등록 성공했습니다.!'); - onSuccess(newQuestion); // 부모에게 새 질문 데이터 전달 - console.log('onSuccess 성공내역 ', newQuestion); + setContent(''); + onClose(); + showToast('질문이 등록되었습니다'); } catch (error) { // 실패 시 에러 처리 console.error('질문 등록 실패:', error); - alert('질문 등록에 실패했습니다. 다시 시도해주세요.'); + showToast('질문 등록에 실패했습니다'); } finally { setIsSubmitting(false); // 버튼 보이게 } diff --git a/src/components/Modal/QuestionTarget.jsx b/src/components/Modal/QuestionTarget.jsx index 4822ca2..4b3df1e 100644 --- a/src/components/Modal/QuestionTarget.jsx +++ b/src/components/Modal/QuestionTarget.jsx @@ -1,5 +1,5 @@ import styles from './QuestionModal.module.css'; - +import { Avatar } from '@/components/Avatar/Avatar'; /** * 질문 대상자 정보 컴포넌트 * @@ -13,12 +13,14 @@ function QuestionTarget({ target }) {
To. - {/* 프로필 이미지 */} - {`${target.name} + {/* Avatar 컴포넌트로 교체 */} + + {/* 이미지 로딩 시도 */} + + + {/* 이미지 로딩 실패 시 대체 UI (이름 첫 글자) */} + {target.name[0]} + {/* 받는 사람 이름 */} {target.name}