diff --git a/README.md b/README.md index 60b18ae..c5d3be2 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,10 @@ + + + +
-

OpenMind

+ +
@@ -16,6 +21,18 @@ CRUD 기능을 구현한 **Open Mind**로 다른 사람들과 익명으로 소통해보세요! + + +
+"이번 기회로 질문을 거리낌 없이 할 수 있었던 것 같아요!😊" +
+"익명으로 얘기할 때 더 편하게 질문과 답변을 한 것 같아요!👍" +
+ +## 🖐️ 배포 서비스 + +[클릭해서 접속하세요!](https://team6openmind.netlify.app/) + ## 📁 디렉토리 구조 ``` @@ -25,7 +42,6 @@ CRUD 기능을 구현한 **Open Mind**로 다른 사람들과 익명으로 소 ┃ ┣ 📂images ┃ ┗ 📂styles ┣ 📂components - ┣ 📂config ┣ 📂pages ┣ 📂routes ┣ 📂utils @@ -35,7 +51,6 @@ CRUD 기능을 구현한 **Open Mind**로 다른 사람들과 익명으로 소 - `📂api` 비동기 리퀘스트 관련 파일 - `📂assets` image 혹은 styles 관련 파일 - `📂components` 컴포넌트 관련 파일 -- `📂config` 상수 관련 파일 - `📂hooks` 커스텀 훅 관련 파일 - `📂pages` 페이지 컴포넌트 파일 - `📂routes` 라우팅 설정 파일 @@ -44,6 +59,93 @@ CRUD 기능을 구현한 **Open Mind**로 다른 사람들과 익명으로 소 ## 💻 기능 소개 +
+ + + + + + + + + + +
+ 메인 페이지
+ +

로그인

+
+ 질문 목록 페이지
+ +

페이지네이션 및 피드 이동

+
+ 피드 페이지
+ +

질문 등록

+
+ 피드 페이지 & 답변 페이지
+ +

무한 스크롤 로딩

+
+ 피드 페이지 & 답변 페이지
+ +

URL 복사

+
+ 피드 페이지 & 답변 페이지
+ +

카카오톡 공유

+
+ 피드 페이지 & 답변 페이지
+ +

페이스북 공유

+
+
+ +
+
+ +
+ + + + + + + + + + +
+ 답변 페이지
+ +

답변 등록

+
+ 답변 페이지
+ +

답변 수정

+
+ 답변 페이지
+ +

답변 삭제

+
+ 답변 페이지
+ +

답변 거절

+
+ 답변 페이지
+ +

질문 개별 삭제

+
+ 답변 페이지
+ +

피드 삭제

+
+ 오류 페이지
+ +

페이지 리디렉션

+
+
+ @@ -108,3 +210,6 @@ CRUD 기능을 구현한 **Open Mind**로 다른 사람들과 익명으로 소 + + + diff --git a/package-lock.json b/package-lock.json index 658c509..f2bf485 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,8 +9,11 @@ "version": "0.1.0", "dependencies": { "cra-template": "1.2.0", + "date-fns": "^4.1.0", + "prop-types": "^15.8.1", "react": "^19.0.0", "react-dom": "^19.0.0", + "react-responsive": "^10.0.0", "react-router-dom": "^7.0.2", "react-scripts": "5.0.1", "sass": "^1.82.0" @@ -5963,6 +5966,11 @@ "node": ">=10" } }, + "node_modules/css-mediaquery": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/css-mediaquery/-/css-mediaquery-0.1.2.tgz", + "integrity": "sha512-COtn4EROW5dBGlE/4PiKnh6rZpAPxDeFLaEEwt4i10jpDMFt2EhQGS79QmmrO+iKCHv0PU/HrOWEhijFd1x99Q==" + }, "node_modules/css-minimizer-webpack-plugin": { "version": "3.4.1", "resolved": "https://registry.npmjs.org/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-3.4.1.tgz", @@ -6311,6 +6319,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/date-fns": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", + "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, "node_modules/debug": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", @@ -8919,6 +8936,11 @@ "url": "https://github.com/sponsors/typicode" } }, + "node_modules/hyphenate-style-name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.1.0.tgz", + "integrity": "sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw==" + }, "node_modules/iconv-lite": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", @@ -11319,6 +11341,14 @@ "tmpl": "1.0.5" } }, + "node_modules/matchmediaquery": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/matchmediaquery/-/matchmediaquery-0.4.2.tgz", + "integrity": "sha512-wrZpoT50ehYOudhDjt/YvUJc6eUzcdFPdmbizfgvswCKNHD1/OBOHYJpHie+HXpu6bSkEGieFMYk6VuutaiRfA==", + "dependencies": { + "css-mediaquery": "^0.1.2" + } + }, "node_modules/mdn-data": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz", @@ -13810,6 +13840,23 @@ "node": ">=0.10.0" } }, + "node_modules/react-responsive": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/react-responsive/-/react-responsive-10.0.0.tgz", + "integrity": "sha512-N6/UiRLGQyGUqrarhBZmrSmHi2FXSD++N5VbSKsBBvWfG0ZV7asvUBluSv5lSzdMyEVjzZ6Y8DL4OHABiztDOg==", + "dependencies": { + "hyphenate-style-name": "^1.0.0", + "matchmediaquery": "^0.4.2", + "prop-types": "^15.6.1", + "shallow-equal": "^3.1.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, "node_modules/react-router": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.0.2.tgz", @@ -14795,6 +14842,11 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, + "node_modules/shallow-equal": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/shallow-equal/-/shallow-equal-3.1.0.tgz", + "integrity": "sha512-pfVOw8QZIXpMbhBWvzBISicvToTiM5WBF1EeAUZDDSb5Dt29yl4AYbyywbJFSEsRUMr7gJaxqCdr4L3tQf9wVg==" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", diff --git a/package.json b/package.json index 8638ca9..c15b1aa 100644 --- a/package.json +++ b/package.json @@ -4,8 +4,11 @@ "private": true, "dependencies": { "cra-template": "1.2.0", + "date-fns": "^4.1.0", + "prop-types": "^15.8.1", "react": "^19.0.0", "react-dom": "^19.0.0", + "react-responsive": "^10.0.0", "react-router-dom": "^7.0.2", "react-scripts": "5.0.1", "sass": "^1.82.0" diff --git a/public/index.html b/public/index.html index 36e752c..635ea69 100644 --- a/public/index.html +++ b/public/index.html @@ -16,8 +16,13 @@ href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.min.css" rel="stylesheet" /> + + + +
+ diff --git a/src/assets/images/icons/Messages.svg b/src/assets/images/icons/Messages.svg new file mode 100644 index 0000000..6368f81 --- /dev/null +++ b/src/assets/images/icons/Messages.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/images/icons/ic_Arrow-dash-right.svg b/src/assets/images/icons/ic_Arrow-dash-right.svg index e352a87..dcc3351 100644 --- a/src/assets/images/icons/ic_Arrow-dash-right.svg +++ b/src/assets/images/icons/ic_Arrow-dash-right.svg @@ -1,4 +1,4 @@ - - - + + + diff --git a/src/assets/images/icons/ic_Arrow-down.svg b/src/assets/images/icons/ic_Arrow-down.svg index c30473b..47bb2df 100644 --- a/src/assets/images/icons/ic_Arrow-down.svg +++ b/src/assets/images/icons/ic_Arrow-down.svg @@ -1,3 +1,3 @@ - - + + diff --git a/src/assets/images/icons/ic_Arrow-left.svg b/src/assets/images/icons/ic_Arrow-left.svg index 289a8cb..de0f239 100644 --- a/src/assets/images/icons/ic_Arrow-left.svg +++ b/src/assets/images/icons/ic_Arrow-left.svg @@ -1,3 +1,3 @@ - - + + diff --git a/src/assets/images/icons/ic_Arrow-right.svg b/src/assets/images/icons/ic_Arrow-right.svg index 4740487..2df2b59 100644 --- a/src/assets/images/icons/ic_Arrow-right.svg +++ b/src/assets/images/icons/ic_Arrow-right.svg @@ -1,3 +1,3 @@ - - + + diff --git a/src/assets/images/icons/ic_Close.svg b/src/assets/images/icons/ic_Close.svg index 0e83e57..ed3121c 100644 --- a/src/assets/images/icons/ic_Close.svg +++ b/src/assets/images/icons/ic_Close.svg @@ -1,4 +1,4 @@ - - - + + + diff --git a/src/assets/images/icons/ic_Edit.svg b/src/assets/images/icons/ic_Edit.svg index 5f59a14..c920b1e 100644 --- a/src/assets/images/icons/ic_Edit.svg +++ b/src/assets/images/icons/ic_Edit.svg @@ -1,4 +1,4 @@ - - - + + + diff --git a/src/assets/images/icons/ic_Messages.svg b/src/assets/images/icons/ic_Messages.svg index 6368f81..e8b5577 100644 --- a/src/assets/images/icons/ic_Messages.svg +++ b/src/assets/images/icons/ic_Messages.svg @@ -1,4 +1,4 @@ - - - + + + diff --git a/src/assets/images/icons/ic_Person.svg b/src/assets/images/icons/ic_Person.svg index f2022b5..2cca199 100644 --- a/src/assets/images/icons/ic_Person.svg +++ b/src/assets/images/icons/ic_Person.svg @@ -1,4 +1,4 @@ - - + + diff --git a/src/assets/images/icons/ic_Rejection.svg b/src/assets/images/icons/ic_Rejection.svg index b5a36d1..cbf7a5e 100644 --- a/src/assets/images/icons/ic_Rejection.svg +++ b/src/assets/images/icons/ic_Rejection.svg @@ -1,4 +1,4 @@ - - - + + + diff --git a/src/assets/images/icons/thumbs-down.svg b/src/assets/images/icons/thumbs-down.svg new file mode 100644 index 0000000..09e87a4 --- /dev/null +++ b/src/assets/images/icons/thumbs-down.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/images/icons/thumbs-up.svg b/src/assets/images/icons/thumbs-up.svg new file mode 100644 index 0000000..8833fd6 --- /dev/null +++ b/src/assets/images/icons/thumbs-up.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/images/img_Banner.svg b/src/assets/images/img_Banner.svg index e8e0bf7..43e3c25 100644 --- a/src/assets/images/img_Banner.svg +++ b/src/assets/images/img_Banner.svg @@ -1,11 +1,9 @@ - - - - + + - - + + - + diff --git a/src/assets/images/img_Header.svg b/src/assets/images/img_Header.svg new file mode 100644 index 0000000..b23cda0 --- /dev/null +++ b/src/assets/images/img_Header.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/assets/images/img_QuestionBox.svg b/src/assets/images/img_QuestionBox.svg new file mode 100644 index 0000000..23cf698 --- /dev/null +++ b/src/assets/images/img_QuestionBox.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/assets/images/img_QusetionBox.svg b/src/assets/images/img_QusetionBox.svg new file mode 100644 index 0000000..23cf698 --- /dev/null +++ b/src/assets/images/img_QusetionBox.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/components/AnswerEditForm/index.jsx b/src/components/AnswerEditForm/index.jsx new file mode 100644 index 0000000..8025ba5 --- /dev/null +++ b/src/components/AnswerEditForm/index.jsx @@ -0,0 +1,119 @@ +import PropTypes from 'prop-types'; +import { useState } from 'react'; +import { putAnswer } from 'api/answers'; +import { ReactComponent as Close } from 'assets/images/icons/ic_Close.svg'; +import ConfirmModal from 'components/UI/Modals/ConfirmModal'; + +const AnswerEditForm = ({ answer, name, imageSource, id, setEditId, setQuestionList, setIsKebabLoading, setIsToast }) => { + AnswerEditForm.propTypes = { + answer: PropTypes.shape({ + id: PropTypes.number.isRequired, + content: PropTypes.string.isRequired, + isRejected: PropTypes.bool, + createdAt: PropTypes.string.isRequired, + }), + name: PropTypes.string.isRequired, + imageSource: PropTypes.string, + id: PropTypes.number.isRequired, + setIsKebabLoading: PropTypes.func.isRequired, + setIsToast: PropTypes.func.isRequired, + setEditId: PropTypes.number.isRequired, + setQuestionList: PropTypes.func.isRequired, + }; + + AnswerEditForm.defaultProps = { + imageSource: 'https://fastly.picsum.photos/id/772/200/200.jpg?hmac=9euSj4JHTPr7uT5QWVmeNJ8JaqAXY8XmJnYfr_DfBJc', + }; + + const [textareaValue, setTextareaValue] = useState(answer.content === null || answer.content === 'reject' ? '' : answer.content); + const [isLoading, setIsLoading] = useState(false); + const [isValid, setIsValid] = useState(false); + const [showModal, setShowModal] = useState(false); + + const handleTextareaChange = (event) => { + const text = event.target.value; + setTextareaValue(text); + const isFormValid = text.trim() !== answer.content.trim() && text !== '' && text !== 'reject'; + setIsValid(isFormValid); + }; + + const handleAnswerPatch = async (e) => { + e.preventDefault(); + try { + setIsKebabLoading(true); + setIsLoading(true); + const result = await putAnswer(answer.id, { + content: textareaValue, + isRejected: false, + }); + setIsToast('수정'); + setQuestionList((prevQuestions) => + prevQuestions.map((question) => { + if (question.id === id) { + return { ...question, answer: result }; + } + return question; + }), + ); + } catch (err) { + // eslint-disable-next-line + console.error(err); + } finally { + setIsKebabLoading(false); + setIsLoading(false); + setEditId(null); + setTimeout(() => { + setIsToast(null); + }, 3000); + } + }; + + const onCancelClick = () => { + setShowModal(true); + }; + + const handleModalCancel = () => { + setShowModal(false); + }; + + const handleModalConfirm = () => { + setShowModal(false); + setEditId(null); + }; + + const renderProfileImg = () => {`${name}의; + + const renderAnswerForm = () => ( +
+