Skip to content

Refactor/all page #67

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

Merged
merged 12 commits into from
Dec 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions src/components/AnswerEditForm/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@ 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/ConfirmModal';
import ConfirmModal from 'components/UI/Modals/ConfirmModal';

// eslint-disable-next-line
const AnswerEditForm = ({ answer, name, imageSource, id, setEditId, setQuestionList, setIsKebabLoading, setIsToast }) => {
AnswerEditForm.propTypes = {
answer: PropTypes.shape({
Expand All @@ -18,13 +17,14 @@ const AnswerEditForm = ({ answer, name, imageSource, id, setEditId, setQuestionL
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',
};

// 초기값 설정 시 answer.content가 null일 경우 빈 문자열로 처리
const [textareaValue, setTextareaValue] = useState(answer.content === null || answer.content === 'reject' ? '' : answer.content);
const [isLoading, setIsLoading] = useState(false);
const [isValid, setIsValid] = useState(false);
Expand All @@ -43,8 +43,8 @@ const AnswerEditForm = ({ answer, name, imageSource, id, setEditId, setQuestionL
setIsKebabLoading(true);
setIsLoading(true);
const result = await putAnswer(answer.id, {
content: textareaValue, // textareaValue에서 내용을 가져옵니다.
isRejected: false, // 필요하다면 다른 데이터도 추가 가능합니다.
content: textareaValue,
isRejected: false,
});
setIsToast('수정');
setQuestionList((prevQuestions) =>
Expand All @@ -56,7 +56,8 @@ const AnswerEditForm = ({ answer, name, imageSource, id, setEditId, setQuestionL
}),
);
} catch (err) {
// handle error here (e.g., show error message)
// eslint-disable-next-line
console.error(err);
} finally {
setIsKebabLoading(false);
setIsLoading(false);
Expand Down
2 changes: 1 addition & 1 deletion src/components/App.jsx → src/components/App/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import 'assets/styles/index.scss';

import { AppProvider } from 'components/Context';
import ModalPortal from 'utils/portal';
import Modal from 'components/Modals';
import Modal from 'components/UI/Modals';

const App = () => (
<AppProvider>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ const Header = ({ imageSource, name }) => {
handleToastUrlCopyLoad();
};

// ↓ URL 복사 알림 컴포넌트
const ToastUrlCopy = () => (
<div className='fixed bottom-[100px] md:bottom-[60px] left-1/2 w-[167px] h-[42px] px-5 py-3 bg-black rounded-lg text-sm/[18px] font-medium text-white -translate-x-1/2 shadow-2pt animate-slide-up-fade-urlCopy'>
URL이 복사되었습니다
Expand All @@ -51,13 +50,13 @@ const Header = ({ imageSource, name }) => {
<header className={`w-[calc(100vw - ${scrollBarWidth}px)] bg-white`}>
<div className='flex justify-center relative'>
<div className='w-screen overflow-hidden flex justify-center'>
<object className='min-w-[906px] md:min-w-[1200px] h-100' data={headerImg} aria-labelledby='Header_Image' type='image/svg+xml' />
<object className='min-w-[906px] md:min-w-[1200px] h-60' data={headerImg} aria-labelledby='Header_Image' type='image/svg+xml' />
</div>
<div className='flex flex-col items-center absolute gap-3 top-[40px] md:top-[50px]'>
<Link to='/'>
<img className='max-w-[124px] md:max-w-[170px]' src={logo} alt='Logo' />
<img className='w-[124px] h-16 md:w-[170px] md:h-12' src={logo} alt='Logo' />
</Link>
{imageSource && <img className='rounded-full max-w-[104px] max-h-[104px] md:max-w-[136px] md:max-h-[136px]' src={imageSource} alt='Profile_Img' />}
{imageSource && <img className='rounded-full w-[104px] h-[104px] md:w-[136px] md:h-[136px]' src={imageSource} alt='Profile_Img' />}
<div className='font-normal text-2xl/[30px] md:text-[32px]/[40px]'>{name}</div>
<div className='flex gap-3'>
<button className='flex justify-center items-center w-10 h-10 rounded-full bg-brown-40' type='button' onClick={handleCopyUrl}>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import PropTypes from 'prop-types';
import { useState } from 'react';
import { useLocation } from 'react-router-dom';
import AnswerStatus from 'components/AnswerStatus';
import QuestionContent from 'components/QuestionContent';
import AnswerContent from 'components/AnswerContent';
import CountFavorite from 'components/CountFavorite';
import Kebab from 'components/Kebab';
import AnswerStatus from 'components/Common/AnswerStatus';
import QuestionContent from 'components/Common/QuestionContent';
import AnswerContent from 'components/Common/AnswerContent';
import CountFavorite from 'components/Common/CountFavorite';
import Kebab from 'components/UI/Kebab';
import questionBoxImg from 'assets/images/img_QuestionBox.svg';
import AnswerEditForm from 'components/AnswerEditForm';

Expand Down Expand Up @@ -91,13 +91,10 @@ const QnAList = ({ name, imageSource, questionList, setQuestionList, onDeleteQue
/>
)}
</div>
<QuestionContent
createdAt={question.createdAt}
content={question.content || ''} // content가 null일 경우 기본 값 제공
/>
<QuestionContent createdAt={question.createdAt} content={question.content || ''} />
{question.answer && question.answer.id === editId ? (
<AnswerEditForm
answer={question.answer || { content: '' }} // answer가 없으면 빈 객체를 넘겨줌
answer={question.answer || { content: '' }}
name={name}
imageSource={imageSource}
id={question.id}
Expand Down
5 changes: 0 additions & 5 deletions src/components/Context/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,6 @@ import PropTypes from 'prop-types';
const AppContext = createContext();

const AppProvider = ({ children }) => {
/**
* children prop 유효성 검사
* PropType.node : 해당 prop 값이 렌더링 가능한 값이어야 됨
* isRequired : children prop은 필수임
*/
AppProvider.propTypes = {
children: PropTypes.node.isRequired,
};
Expand Down
37 changes: 0 additions & 37 deletions src/components/ToastSuccess/index.jsx

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { ReactComponent as Close } from 'assets/images/icons/ic_Close.svg';
import PropTypes from 'prop-types';
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import ConfirmModal from 'components/ConfirmModal'; // Import the modal component
import ConfirmModal from 'components/UI/Modals/ConfirmModal';

const AnswerDelete = ({ id, answerId, onAnswerDeleted, onKebabClick, setIsKebabLoading, setIsToast, editId, setEditId }) => {
AnswerDelete.propTypes = {
Expand All @@ -23,18 +23,18 @@ const AnswerDelete = ({ id, answerId, onAnswerDeleted, onKebabClick, setIsKebabL
const [showModal, setShowModal] = useState(false);

const handleDelete = async () => {
setShowModal(true); // Show the modal when delete is clicked
setShowModal(true);
};

const handleModalCancel = () => {
onKebabClick(id);
setShowModal(false); // Close the modal if canceled
setShowModal(false);
};

const handleModalConfirm = async () => {
onKebabClick(id);
setIsKebabLoading(true);
setShowModal(false); // Close the modal
setShowModal(false);
setIsDeleting(true);

try {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import PropTypes from 'prop-types';
import { ReactComponent as Edit } from 'assets/images/icons/ic_Edit.svg';

// eslint-disable-next-line
const AnswerEdit = ({ id, editId, setEditId, answerId, onKebabClick }) => {
AnswerEdit.propTypes = {
id: PropTypes.number.isRequired,
editId: PropTypes.number.isRequired,
setEditId: PropTypes.func.isRequired,
onKebabClick: PropTypes.func.isRequired,
answerId: PropTypes.number.isRequired,
};

const handleEdit = () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import PropTypes from 'prop-types';
import { useState } from 'react';
import { deleteQuestion } from 'api/questions';
import { ReactComponent as Close } from 'assets/images/icons/ic_Close.svg';
import ConfirmModal from 'components/ConfirmModal';
import ConfirmModal from 'components/UI/Modals/ConfirmModal';

const QuestionDelete = ({ id, onDeleteQuestion, onKebabClick, setIsKebabLoading, setIsToast, editId, setEditId }) => {
QuestionDelete.propTypes = {
Expand All @@ -21,16 +21,16 @@ const QuestionDelete = ({ id, onDeleteQuestion, onKebabClick, setIsKebabLoading,
const [showModal, setShowModal] = useState(false);

const handleDelete = async () => {
setShowModal(true); // Show the modal when delete is clicked
setShowModal(true);
};

const handleModalCancel = () => {
onKebabClick(id);
setShowModal(false); // Close the modal if canceled
setShowModal(false);
};

const handleModalConfirm = async () => {
setShowModal(false); // Close the moda
setShowModal(false);

try {
setIsKebabLoading(true);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import PropTypes from 'prop-types';
import { useEffect, useRef, useState } from 'react';
import QuestionDelete from 'components/QuestionDelete';
import AnswerRejection from 'components/AnswerRejection';
import QuestionDelete from 'components/UI/Kebab/QuestionDelete';
import AnswerRejection from 'components/UI/Kebab/AnswerRejection';
import kebab from 'assets/images/icons/ic_Kebab.svg';
import AnswerDelete from 'components/AnswerDelete';
import AnswerEdit from 'components/AnswerEdit';
import ConfirmModal from 'components/ConfirmModal';
import AnswerDelete from 'components/UI/Kebab/AnswerDelete';
import AnswerEdit from 'components/UI/Kebab/AnswerEdit';
import ConfirmModal from 'components/UI/Modals/ConfirmModal';

const Kebab = ({ id, isAnswer, isKebabOpen, onKebabClick, onDeleteQuestion, onAnswerDeleted, setQuestionList, editId, setEditId, answerId, isKebabLoading, setIsKebabLoading, setIsToast }) => {
Kebab.propTypes = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,24 @@ const ConfirmModal = ({ isOpen, onConfirm, onCancel, message }) => {

const modalRef = useRef(null);

// Close the modal if the user clicks outside of it
useEffect(() => {
const handleClickOutside = (event) => {
if (modalRef.current && !modalRef.current.contains(event.target)) {
onCancel(); // Close the modal if clicked outside
onCancel();
}
};

if (isOpen) {
const scrollBarWidth = window.innerWidth - document.documentElement.clientWidth;
document.body.style.overflow = 'hidden';
document.body.style.paddingRight = `${scrollBarWidth}px`;
document.addEventListener('mousedown', handleClickOutside);
}

return () => {
document.removeEventListener('mousedown', handleClickOutside);
document.body.style.overflow = '';
document.body.style.paddingRight = 0;
};
}, [isOpen, onCancel]);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,17 @@ const Modal = () => {
closeModal();
setIsContent(false);
};
// Form 제출 이벤트

const postFormHandler = async (e) => {
e.preventDefault();
setPostObject(await postQuestion(profile.id, { content: content.current.value.trim() }));
exitModal();
};
// 실제 이벤트 발생 지점과 버블링 지점에서 target이 일치하면 모달 닫기

const clickOutsideModal = ({ target, currentTarget }) => {
if (target === currentTarget) exitModal();
};
// TextArea 유효성 검사

const changeContentHandler = () => {
if (content.current.value.trim() !== '') setIsContent(true);
else setIsContent(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,10 @@ const Pagination = ({ data }) => {

const btnHoverAnimation = 'hover:transform hover:translate-y-[-5px] transition-transform duration-300 ease-in-out';

// 페이지네이션의 시작 번호 계산 (ex.두번째 페이지네이션 : 6)
const startNum = Math.floor((activeNum - 1) / 5) * 5 + 1;
// 페이지네이션 번호 버튼 배열

const pages = Array.from({ length: Math.min(5, Math.ceil(count / limit - startNum) + 1) }, (_, i) => startNum + i);

// 페이지 번호 선택에 따른 상태 변경 함수
const pageSelect = useCallback(
(pageNum) => {
setActiveNum(pageNum);
Expand All @@ -36,7 +34,6 @@ const Pagination = ({ data }) => {
[limit, setOffset, setLimit],
);

// 페이지네이션 이동 버튼 유효성 검사
const checkArrowBtn = useCallback(() => {
const divActiveNum = Math.floor((activeNum - 1) / 5);
const divTotalPageNum = Math.floor((Math.ceil(count / limit) - 1) / 5);
Expand Down Expand Up @@ -79,21 +76,27 @@ const Pagination = ({ data }) => {
}, [pageWidth, count, setLimit, setOffset, activeNum]);

return (
<div className='flex gap-6 justify-center items-center font-actor my-8 md:mt-20 md:mb-16'>
<button type='button' onClick={prevClick} disabled={disabledArrowLeft} className={disabledArrowLeft ? '' : btnHoverAnimation}>
<IconLeftArrow alt='왼쪽 화살표' className='fill-gray-40' />
</button>
{pages.map((value) => {
const activeColor = activeNum === value ? 'font-semibold text-brown-40' : 'text-gray-40';
return (
<button type='button' key={value} onClick={() => pageSelect(value)} className={`text-xl ${activeColor} ${btnHoverAnimation}`}>
{value}
<div className='flex justify-center items-center relative font-actor my-8 md:mt-20 md:mb-16'>
{pages.length === 0 ? (
<div className='absolute top-0 bottom-0 w-[300px] h-[28px] border rounded-2xl flex justify-center items-center bg-gray-20 z-10' />
) : (
<>
<button type='button' onClick={prevClick} disabled={disabledArrowLeft} className={`px-3 ${disabledArrowLeft ? '' : btnHoverAnimation}`}>
<IconLeftArrow alt='왼쪽 화살표' className='fill-gray-40' />
</button>
{pages.map((value) => {
const activeColor = activeNum === value ? 'font-semibold text-brown-40' : 'text-gray-40';
return (
<button type='button' key={value} onClick={() => pageSelect(value)} className={`px-3 text-xl ${activeColor} ${btnHoverAnimation}`}>
{value}
</button>
);
})}
<button type='button' onClick={nextClick} disabled={disabledArrowRight} className={`px-3 ${disabledArrowRight ? '' : btnHoverAnimation}`}>
<IconRightArrow alt='오른쪽 화살표' className='fill-gray-40' />
</button>
);
})}
<button type='button' onClick={nextClick} disabled={disabledArrowRight} className={disabledArrowRight ? '' : btnHoverAnimation}>
<IconRightArrow alt='오른쪽 화살표' className='fill-gray-40' />
</button>
</>
)}
</div>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,12 @@ const SortDropDown = ({ changeSort }) => {
changeSort(target.textContent);
};

// 외부 클릭 시 드롭다운을 닫는 함수
const handleClickOutside = (event) => {
if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
setIsOpen(false);
}
};

// 컴포넌트 마운트 시 이벤트 리스너 추가
useEffect(() => {
document.addEventListener('click', handleClickOutside);
return () => {
Expand Down
Loading
Loading