From 92d30a70b26078b1024b9d2f8e65337dfefcd5c9 Mon Sep 17 00:00:00 2001 From: kimsuyeon0916 Date: Mon, 15 Apr 2024 17:15:39 +0900 Subject: [PATCH 001/116] =?UTF-8?q?chore:=20#91=20=EC=9B=90=EA=B2=A9=20?= =?UTF-8?q?=EB=A0=88=ED=8F=AC=EC=A7=80=ED=86=A0=EB=A6=AC=20=EB=82=B4=20src?= =?UTF-8?q?/pages/recruit=20=EA=B4=80=EB=A0=A8=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=BA=90=EC=8B=9C=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../RecruitDetailPage.styled.ts | 415 ------------------ .../RecruitDetailPage/RecruitDetailPage.tsx | 196 --------- src/pages/recruit/RecruitDetailPage/data.ts | 38 -- .../steps/ApplyInfomation.styled.ts | 142 ------ .../steps/ApplyInfomation.tsx | 53 --- .../steps/ApplyInput.styled.ts | 174 -------- .../RecruitDetailPage/steps/ApplyInput.tsx | 103 ----- .../steps/ApplySubmit.styled.ts | 133 ------ .../RecruitDetailPage/steps/ApplySubmit.tsx | 60 --- .../recruit/RecruitPage/RecruitPage.styled.ts | 201 --------- src/pages/recruit/RecruitPage/RecruitPage.tsx | 116 ----- 11 files changed, 1631 deletions(-) delete mode 100644 src/pages/recruit/RecruitDetailPage/RecruitDetailPage.styled.ts delete mode 100644 src/pages/recruit/RecruitDetailPage/RecruitDetailPage.tsx delete mode 100644 src/pages/recruit/RecruitDetailPage/data.ts delete mode 100644 src/pages/recruit/RecruitDetailPage/steps/ApplyInfomation.styled.ts delete mode 100644 src/pages/recruit/RecruitDetailPage/steps/ApplyInfomation.tsx delete mode 100644 src/pages/recruit/RecruitDetailPage/steps/ApplyInput.styled.ts delete mode 100644 src/pages/recruit/RecruitDetailPage/steps/ApplyInput.tsx delete mode 100644 src/pages/recruit/RecruitDetailPage/steps/ApplySubmit.styled.ts delete mode 100644 src/pages/recruit/RecruitDetailPage/steps/ApplySubmit.tsx delete mode 100644 src/pages/recruit/RecruitPage/RecruitPage.styled.ts delete mode 100644 src/pages/recruit/RecruitPage/RecruitPage.tsx diff --git a/src/pages/recruit/RecruitDetailPage/RecruitDetailPage.styled.ts b/src/pages/recruit/RecruitDetailPage/RecruitDetailPage.styled.ts deleted file mode 100644 index 422b1163..00000000 --- a/src/pages/recruit/RecruitDetailPage/RecruitDetailPage.styled.ts +++ /dev/null @@ -1,415 +0,0 @@ -import styled from 'styled-components'; - -interface Props { - $isRound: boolean; - $color: string; -} - -const RecruitDetailPage = styled.div` - width: clamp(45%, 108rem, 75%); - margin: 0 auto; - margin-top: 3.38rem; - margin-bottom: 3.38rem; - - .container { - display: flex; - gap: 1.5rem; - - .container-left { - display: flex; - flex-direction: column; - gap: 1.5rem; - width: 71.4rem; - - .container-info { - /* height: 54rem; */ - border-radius: 0.75rem; - border: 1.5px solid #eeecff; - background: #fff; - padding: 2.25rem 3.45rem; - box-sizing: border-box; - - .container-info__title { - display: flex; - align-items: center; - gap: 1.27rem; - margin-top: 0.5rem; - - h1 { - margin-top: 0.2rem; - color: #000; - - font-size: 2.4rem; - font-style: normal; - font-weight: 400; - line-height: 3rem; - letter-spacing: 0.015rem; - } - } - - .container-info__writer { - display: flex; - align-items: center; - gap: 0.7rem; - margin-top: 1.37rem; - - .profile-img { - width: 3.3075rem; - height: 3.3075rem; - flex-shrink: 0; - border-radius: 50%; - - img { - width: 100%; - height: 100%; - border-radius: 50%; - } - } - - div:nth-child(2) { - color: var(--Light-Black, var(--text-color-2, #373f41)); - - font-size: 1.5rem; - font-style: normal; - font-weight: 400; - line-height: 1.35rem; /* 90% */ - letter-spacing: 0.015rem; - } - } - } - - .container-required__info { - margin-top: 3.6rem; - display: grid; - grid-template-columns: 1fr 2fr; - grid-row-gap: 1.5rem; - grid-column-gap: 8rem; - } - - .container-introduction { - margin-top: 5rem; - - h4 { - color: #000; - - font-size: 1.8rem; - font-style: normal; - font-weight: 400; - line-height: 1.8rem; /* 100% */ - letter-spacing: -0.036rem; - } - - p { - margin-top: 3rem; - color: var(--text-color-2, #373f41); - font-family: Pretendard; - font-size: 1.6rem; - font-style: normal; - font-weight: 400; - line-height: 150%; /* 2.7rem */ - letter-spacing: 0.015rem; - } - } - - .container-current { - flex-shrink: 0; - border-radius: 0.75rem; - border: 1.5px solid #eeecff; - background: #fff; - padding: 2.33rem 3.45rem; - - .container-current__title { - color: #000; - - font-size: 1.8rem; - font-style: normal; - font-weight: 400; - line-height: 1.8rem; /* 100% */ - letter-spacing: -0.036rem; - } - - .container-current__roles { - display: flex; - flex-direction: column; - justify-content: center; - gap: 1.43rem; - margin-top: 1.73rem; - - .container-current__roles--element { - display: flex; - justify-content: space-between; - align-items: center; - width: 45.75rem; - /* height: 7.8rem; */ - flex-shrink: 0; - border-radius: 0.75rem; - border: 0.75px solid #dcdcdc; - background: #f9f9f9; - padding: 1.35rem 2.03rem 0.75rem 2.03rem; - box-sizing: border-box; - - .roles-info { - display: flex; - flex-direction: column; - justify-content: flex-start; - gap: 0.8rem; - } - - .roles-info__role { - display: flex; - align-items: center; - gap: 1.25rem; - - .role { - color: #000; - - font-size: 1.65rem; - font-style: normal; - font-weight: 400; - line-height: 1.8rem; /* 109.091% */ - letter-spacing: 0.015rem; - } - - .members { - display: flex; - align-items: center; - - .member { - width: 2.55rem; - height: 2.55rem; - flex-shrink: 0; - cursor: pointer; - } - } - } - - .roles-info__spec { - display: flex; - gap: 0.6rem; - - .spec { - display: inline-flex; - width: 7.275rem; - height: 2.5rem; - padding: 0.75rem 1.125rem; - justify-content: center; - align-items: center; - gap: 0.75rem; - flex-shrink: 0; - color: var(--Light-Black, var(--text-color-2, #373f41)); - - font-size: 1.4rem; - font-style: normal; - font-weight: 400; - line-height: 1.35rem; /* 90% */ - letter-spacing: 0.015rem; - border-radius: 7.5rem; - border: 0.75px solid #000; - background: #fff; - } - } - } - } - } - } - - .container-right { - display: flex; - flex-direction: column; - gap: 1.5rem; - width: 34.8rem; - - .container-apply { - border-radius: 0.75rem; - border: 0.75px solid #dcdcdc; - background: #f9f9f9; - padding: 3.3rem 3rem 1.5rem 3rem; - } - - .container-recommend { - flex-shrink: 0; - border-radius: 0.75rem; - border: 0.75px solid #dcdcdc; - background: #f6f6f6; - display: flex; - flex-direction: column; - padding: 3.08rem 2.55rem 2.55rem 2.55rem; - gap: 1.5rem; - - .title { - color: #000; - - font-size: 1.8rem; - font-style: normal; - font-weight: 400; - line-height: 1.8rem; /* 100% */ - letter-spacing: -0.036rem; - } - - .content { - display: flex; - flex-direction: column; - width: 100%; - height: 18.375rem; - flex-shrink: 0; - border-radius: 0.75rem; - border: 1.5px solid var(--main-color, #ababab); - background: #f9f9f9; - padding: 1.35rem 1.5rem 1.8rem 1.5rem; - cursor: pointer; - - &:hover { - transition: 0.3s ease-in-out; - border: 1.5px solid var(--main-color, #5877fc); - } - - .content-tags { - display: flex; - justify-content: space-between; - - .tags { - display: flex; - gap: 0.6rem; - - div:nth-child(1) { - display: flex; - width: 4.05rem; - height: 2.4rem; - padding: 0.75rem; - justify-content: center; - align-items: center; - gap: 0.75rem; - border-radius: 0.6rem; - background: #e0e6ff; - color: #000; - - font-size: 1.2rem; - font-style: normal; - font-weight: 400; - line-height: 1.35rem; /* 112.5% */ - letter-spacing: 0.015rem; - } - - div:nth-child(2) { - display: flex; - width: 5.55rem; - height: 2.4rem; - padding: 0.75rem; - justify-content: center; - align-items: center; - gap: 0.75rem; - border-radius: 0.6rem; - background: #e3f5ff; - color: var(--Light-Black, var(--text-color-2, #373f41)); - - font-size: 1.1rem; - font-style: normal; - font-weight: 400; - line-height: 1.35rem; /* 112.5% */ - letter-spacing: 0.015rem; - } - } - } - - .content-title { - height: 4.8rem; - flex-shrink: 0; - margin-top: 1.65rem; - /* overflow: hidden; */ - color: var(--Light-Black, var(--text-color-2, #373f41)); - text-overflow: ellipsis; - /* white-space: nowrap; */ - - font-size: 1.65rem; - font-style: normal; - font-weight: 400; - line-height: 130%; /* 2.145rem */ - letter-spacing: 0.015rem; - } - - .content-info { - display: flex; - margin-top: 5rem; - justify-content: space-between; - - div { - color: var(--Light-Black, var(--text-color-2, #373f41)); - - font-size: 1.2rem; - font-style: normal; - font-weight: 400; - line-height: 1.35rem; /* 112.5% */ - letter-spacing: 0.015rem; - } - } - } - } - } - } - - .container-comments { - width: 100%; - height: 100%; - flex-shrink: 0; - border-radius: 0.75rem; - border: 1.5px solid #bcd7ff; - background: #f7faff; - margin-top: 1.5rem; - padding: 2rem 3.45rem; - - .container-comments__lists { - width: 100%; - display: flex; - flex-direction: column; - gap: 1.3rem; - margin-bottom: 2rem; - } - - .container-comments__title { - color: #373f41; - font-size: 1.8rem; - font-style: normal; - font-weight: 400; - line-height: 3rem; - letter-spacing: 0.015rem; - } - } -`; - -const RequiredInformationItem = styled.li` - display: flex; - column-gap: 2.25rem; - align-items: center; - .required-information__row { - display: flex; - column-gap: 1.2rem; - } -`; - -const RequiredInformationHead = styled.h3` - display: flex; - color: var(--text-color, #151515); - font-weight: 400; - font-size: 1.4rem; -`; - -const RequiredInformationSpan = styled.span` - display: flex; - justify-content: center; - align-items: center; - padding: 0.75rem; - height: 3.15rem; - font-size: 1.4rem; - font-weight: 400; - border-radius: ${props => (props.$isRound ? `7.5rem` : `0.6rem`)}; - background: ${props => props.$color}; -`; - -const S = { - RecruitDetailPage, - RequiredInformationItem, - RequiredInformationSpan, - RequiredInformationHead, -}; - -export default S; diff --git a/src/pages/recruit/RecruitDetailPage/RecruitDetailPage.tsx b/src/pages/recruit/RecruitDetailPage/RecruitDetailPage.tsx deleted file mode 100644 index 33bb1af7..00000000 --- a/src/pages/recruit/RecruitDetailPage/RecruitDetailPage.tsx +++ /dev/null @@ -1,196 +0,0 @@ -import React, { useState } from 'react'; -import S from './RecruitDetailPage.styled'; -import { - Tag, - ApplyInfomation, - ApplyInput, - ApplySubmit, - informationList, - role, - CONTENT, - Comment, - CommentInput, -} from '../../../components'; -import ColorMatching from '../../../utils/ColorMatching'; -import { useRecoilValue } from 'recoil'; -import { applyStepState } from '../../../atom'; -import { useNavigate } from 'react-router-dom'; -import { JsxElementComponentProps } from '../../../types'; -import { commentsData } from './data'; - -const stepLists: JsxElementComponentProps = { - 0: , - 1: , - 2: , -}; - -const RecruitDetailPage = () => { - const navigate = useNavigate(); - const [commentsList, setCommentsList] = useState(commentsData); - const [contents, setContents] = useState(''); - const isLogin = true; // 임시 코드 - const step = useRecoilValue(applyStepState); - - const isRound = (title: string) => { - const roundTitles = ['유형', '진행']; - - if (roundTitles.includes(title)) { - return false; - } - return true; - }; - - const addComment = () => { - if (contents !== '' && contents.trim() !== '') { - const newComment = { - id: commentsData.length.toString(), - username: 'yeom', - content: contents, - replies: [], - }; - setCommentsList([...commentsList, newComment]); - setContents(''); - } - }; - - const deleteComment = (id: string) => { - setCommentsList(prevComments => prevComments.filter(v => v.id !== id)); - }; - - const onKeyPress = (event: React.KeyboardEvent) => { - const target = event.currentTarget; - if (target.value.length !== 0 && event.key === 'Enter') { - event.preventDefault(); - addComment(); - } - }; - - const onChangeHandler = (event: React.ChangeEvent) => { - setContents(event.target.value); - }; - - const onClickInput = () => { - if (!isLogin) { - navigate('/signin'); - } - }; - - return ( - -
-
-
-
-
-

[커뮤니티 웹 서비스 프로젝트] 디자이너 모집

- -
-
- - - -
{'김민지'}
-
-
-
- {informationList.map((information, index) => ( - - {information.title} -
- {information.content.split(',').map((content, index) => ( - - {content} - - ))} -
-
- ))} -
-
-

구인 글

-

{CONTENT}

-
-
-
- 구인 현황 -
- {role.map((e, index) => ( -
-
-
-
- {e.role} ({e.current.length} / {e.max}) -
-
-
- {e.specs.map((spec, j) => ( -
- {spec} -
- ))} -
-
-
- ))} -
-
-
-
-
-
{stepLists[step]}
-
-
-
- 비슷한 구인 글 -
-
-
-
-
교외
-
프로젝트
-
-
-
- [반려 동물을 위한 앱 서비스] 프론트엔드/백엔드 개발자를 모집합니다. -
-
-
마감 7일 전
-
조회수 101회
-
-
-
-
-
-
- 댓글 -
    - {commentsList.map((comment, index) => { - return ( - deleteComment(comment.id)} - /> - ); - })} -
- -
-
- ); -}; - -export default RecruitDetailPage; diff --git a/src/pages/recruit/RecruitDetailPage/data.ts b/src/pages/recruit/RecruitDetailPage/data.ts deleted file mode 100644 index 667d8874..00000000 --- a/src/pages/recruit/RecruitDetailPage/data.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { Comment, JsxElementComponentProps } from '../../../types'; - -export const commentsData: Comment[] = [ - { - id: '0', - username: 'johny', - content: '이거 어때?', - replies: [ - { - id: '0-0', - username: 'lee', - content: '뭘 어때 걍 하셈', - }, - { - id: '0-1', - username: 'jun', - content: '조용히하셈', - }, - ], - }, - { - id: '1', - username: 'yeom', - content: '아니 근데 왜 나도 이거 지원하고 싶다', - replies: [ - { - id: '1-0', - username: 'lee', - content: '하셈', - }, - { - id: '1-1', - username: 'jun', - content: '바로 탈락하쥬?ㅋ', - }, - ], - }, -]; diff --git a/src/pages/recruit/RecruitDetailPage/steps/ApplyInfomation.styled.ts b/src/pages/recruit/RecruitDetailPage/steps/ApplyInfomation.styled.ts deleted file mode 100644 index 4e1effbb..00000000 --- a/src/pages/recruit/RecruitDetailPage/steps/ApplyInfomation.styled.ts +++ /dev/null @@ -1,142 +0,0 @@ -import styled from 'styled-components'; - -const ApplyInformation = styled.div` - .container-apply__member { - display: flex; - justify-content: flex-start; - align-items: center; - gap: 7rem; - - .type { - color: #000; - - font-size: 1.8rem; - font-style: normal; - font-weight: 400; - line-height: 1.8rem; /* 100% */ - letter-spacing: -0.036rem; - } - - .leader-info { - display: flex; - gap: 0.62rem; - margin-top: 2rem; - - .leader-info__icon { - width: 3.3075rem; - height: 3.3075rem; - flex-shrink: 0; - border-radius: 50%; - - img { - width: 100%; - height: 100%; - border-radius: 50%; - } - } - - .leader-info__user { - display: flex; - flex-direction: column; - justify-content: center; - gap: 0.3rem; - - span:nth-child(1) { - color: var(--Light-Black, var(--text-color-2, #373f41)); - - font-size: 1.5rem; - font-style: normal; - font-weight: 400; - line-height: 1.35rem; /* 90% */ - letter-spacing: 0.015rem; - } - - span:nth-child(2) { - display: flex; - gap: 1rem; - - .user-info { - color: #858585; - - font-size: 1.2rem; - font-style: normal; - font-weight: 400; - line-height: 1.35rem; - letter-spacing: 0.015rem; - } - } - } - } - } - - hr { - margin-top: 2.8rem; - } - - .container-apply__deadline { - margin-top: 2.51rem; - display: flex; - flex-direction: column; - gap: 0.75rem; - - span:nth-child(1) { - color: #000; - - font-size: 1.8rem; - font-style: normal; - font-weight: 400; - line-height: 1.8rem; /* 100% */ - letter-spacing: -0.036rem; - } - span:nth-child(2) { - color: var(--Light-Black, var(--text-color-2, #373f41)); - - font-size: 1.5rem; - font-style: normal; - font-weight: 400; - line-height: 1.35rem; /* 90% */ - letter-spacing: 0.015rem; - } - } - - .container-apply__buttons { - margin-top: 3.1rem; - display: flex; - flex-direction: column; - gap: 0.75rem; - align-items: center; - - button { - border: none; - outline: none; - display: flex; - width: 25.65rem; - height: 4.275rem; - padding: 0.75rem; - justify-content: center; - align-items: center; - gap: 0.75rem; - flex-shrink: 0; - border-radius: 7.5rem; - cursor: pointer; - - span { - margin-top: 0.1rem; - } - } - - button:nth-child(1) { - background-color: #fff; - border: 0.75px solid rgba(95, 92, 236, 0.76); - } - - button:nth-child(2) { - color: #fff; - background: linear-gradient(270deg, rgba(95, 92, 236, 0.76) -6.3%, #d85cec 101.52%); - } - } -`; - -const S = { ApplyInformation }; - -export default S; diff --git a/src/pages/recruit/RecruitDetailPage/steps/ApplyInfomation.tsx b/src/pages/recruit/RecruitDetailPage/steps/ApplyInfomation.tsx deleted file mode 100644 index cda6db64..00000000 --- a/src/pages/recruit/RecruitDetailPage/steps/ApplyInfomation.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import React, { useState } from 'react'; -import S from './ApplyInfomation.styled'; -import { useRecoilState } from 'recoil'; -import { applyStepState } from '../../../../atom'; -import { FaRegBookmark, FaBookmark } from 'react-icons/fa6'; - -const ApplyInfomation = () => { - const [step, setStep] = useRecoilState(applyStepState); - const [isBookmarked, setIsBookmarked] = useState(false); - const onClickStep = () => { - setStep(prev => prev + 1); - }; - const onClickBookmark = () => { - setIsBookmarked(prev => !prev); - }; - return ( - -
-
- 리더 -
-
- -
-
- 김민지 - - 응답률: 90% - 평점: 4.8 - -
-
-
-
-
-
- 🚨 마감일 - {'23.10.16(1일 남음)'} -
-
- - -
-
- ); -}; - -export default React.memo(ApplyInfomation); diff --git a/src/pages/recruit/RecruitDetailPage/steps/ApplyInput.styled.ts b/src/pages/recruit/RecruitDetailPage/steps/ApplyInput.styled.ts deleted file mode 100644 index ba8370a7..00000000 --- a/src/pages/recruit/RecruitDetailPage/steps/ApplyInput.styled.ts +++ /dev/null @@ -1,174 +0,0 @@ -import styled from 'styled-components'; - -const ApplyInput = styled.div` - .container-apply__form { - display: flex; - flex-direction: column; - gap: 1.8rem; - - .container-apply__form-title { - color: #000; - - font-size: 1.8rem; - font-style: normal; - font-weight: 400; - line-height: 1.8rem; /* 100% */ - letter-spacing: -0.036rem; - } - - .container-apply__form-my { - display: flex; - align-items: center; - gap: 0.85rem; - - span { - color: var(--Light-Black, var(--text-color-2, #373f41)); - - font-size: 1.5rem; - font-style: normal; - font-weight: 400; - line-height: 1.35rem; /* 90% */ - letter-spacing: 0.015rem; - } - } - - .container-apply__form-input { - display: flex; - position: relative; - flex-direction: column; - align-items: center; - margin-top: 1.75rem; - gap: 1.65rem; - - .container-apply__roles { - display: flex; - width: 100%; - height: 4rem; - padding: 0.45rem 1.35rem; - align-items: center; - border-radius: 0.75rem; - border: 0.05rem solid #614bf7; - background: #fff; - outline: none; - color: var(--Light-Black, var(--text-color-2, #373f41)); - - font-size: 1.3rem; - font-style: normal; - font-weight: 400; - line-height: 140%; /* 2.1rem */ - letter-spacing: 0.015rem; - cursor: pointer; - } - .dropdown { - position: absolute; - top: 5rem; - width: 100%; - z-index: 101; - background-color: #ffeaa7; - padding: 2rem 1.2rem; - border-radius: 1.2rem; - - ul { - display: flex; - flex-direction: column; - gap: 2rem; - - li { - font-size: 1.3rem; - cursor: pointer; - - &:hover { - transition: 0.2s; - color: #9a77ee; - } - } - } - } - - .container-apply__words { - display: flex; - width: 90%; - height: 3rem; - padding: 0.45rem 1.35rem; - align-items: center; - border-radius: 0.75rem; - border: 0.05rem solid #614bf7; - background: #fff; - outline: none; - } - } - - .container-apply__form-warn { - display: flex; - flex-direction: column; - padding: 1.35rem 1.45rem; - background-color: #fff; - width: 100%; - border-radius: 0.6rem; - - span:nth-child(1) { - color: #000; - - font-size: 1.5rem; - font-style: normal; - font-weight: 400; - line-height: 1.8rem; /* 120% */ - letter-spacing: -0.03rem; - } - span:nth-child(2) { - margin-top: 0.3rem; - color: #686868; - - font-size: 1.05rem; - font-style: normal; - font-weight: 400; - line-height: 1.8rem; /* 171.429% */ - letter-spacing: -0.021rem; - } - - .container-checkbox { - display: flex; - align-items: center; - margin-top: 0.82rem; - - label { - color: #000; - - font-size: 1.2rem; - font-style: normal; - font-weight: 400; - line-height: 1.8rem; /* 150% */ - letter-spacing: -0.024rem; - margin-top: 0.2rem; - margin-left: 0.4rem; - } - } - } - - .container-apply__form-button { - display: flex; - height: 4.275rem; - padding: 0.75rem; - justify-content: center; - align-items: center; - gap: 0.75rem; - flex-shrink: 0; - border-radius: 7.5rem; - } - - .disable { - background-color: #dfe6e9; - color: #000; - cursor: not-allowed; - } - - .able { - background: linear-gradient(270deg, rgba(95, 92, 236, 0.76) -6.3%, #d85cec 101.52%); - color: #fff; - } - } -`; - -const S = { ApplyInput }; - -export default S; diff --git a/src/pages/recruit/RecruitDetailPage/steps/ApplyInput.tsx b/src/pages/recruit/RecruitDetailPage/steps/ApplyInput.tsx deleted file mode 100644 index 8a8a55e3..00000000 --- a/src/pages/recruit/RecruitDetailPage/steps/ApplyInput.tsx +++ /dev/null @@ -1,103 +0,0 @@ -import React, { useState } from 'react'; -import { Icon } from '../../../../components'; -import S from './ApplyInput.styled'; -import { useRecoilState } from 'recoil'; -import { applyInfoState, applyStepState } from '../../../../atom'; - -const roles: string[] = ['프론트엔드 개발자', '백엔드 개발자', '디자이너', '기획자']; -interface Input { - message: string; - currentValue: string; -} - -const ApplyInput = () => { - const [step, setStep] = useRecoilState(applyStepState); - const [info, setInfo] = useRecoilState(applyInfoState); - const [isChecked, setIsChecked] = useState(false); - const [message, setMessage] = useState(''); - const [currentValue, setCurrentValue] = useState('역할 선택'); - const [inputValue, setInputValue] = useState({ - message: '', - currentValue: '역할 선택', - }); - const [openDropdown, setOpenDropdown] = useState(false); - const isValid = isChecked && inputValue.currentValue !== '역할 선택'; - - const onClickStep = () => { - setStep(prev => prev + 1); - setInfo({ role: inputValue.currentValue, message: inputValue.message }); - }; - - const onClickDropdown = () => { - setOpenDropdown(prev => !prev); - }; - - const onChangeInput = (event: React.ChangeEvent) => { - setInputValue({ ...inputValue, message: event.target.value }); - }; - - const onClickList = (event: React.MouseEvent) => { - const target = event.currentTarget; - setInputValue({ ...inputValue, currentValue: target.innerText }); - setOpenDropdown(false); - }; - - const onClickCancel = () => { - setStep(prev => prev - 1); - }; - - return ( - -
- 신청 정보 -
- - {'송유진'} -
-
-
- {inputValue.currentValue} -
- {openDropdown && ( -
-
    - {roles.map((element, index) => ( -
  • - {element} -
  • - ))} -
-
- )} - -
-
- 멤버들에게 내 정보 공개할 수 있나요? - 정보 공개 동의 시, 팀매칭에 유리합니다. -
- setIsChecked(prev => !prev)} /> - -
-
- - -
-
- ); -}; - -export default ApplyInput; diff --git a/src/pages/recruit/RecruitDetailPage/steps/ApplySubmit.styled.ts b/src/pages/recruit/RecruitDetailPage/steps/ApplySubmit.styled.ts deleted file mode 100644 index 690e0082..00000000 --- a/src/pages/recruit/RecruitDetailPage/steps/ApplySubmit.styled.ts +++ /dev/null @@ -1,133 +0,0 @@ -import styled from 'styled-components'; - -const ApplySubmit = styled.div` - .container-apply__form { - display: flex; - flex-direction: column; - gap: 1.8rem; - - .container-apply__form-title { - color: #000; - - font-size: 1.8rem; - font-style: normal; - font-weight: 400; - line-height: 1.8rem; /* 100% */ - letter-spacing: -0.036rem; - } - - .container-apply__form-short_info { - display: grid; - grid-template-columns: 1fr 1fr; - grid-template-rows: 1fr 1fr; - gap: 1.95rem 2.17rem; - } - - .container-apply__form-long_info { - margin-top: 0.3rem; - display: flex; - flex-direction: column; - gap: 1.8rem; - } - - .info { - display: flex; - align-items: center; - gap: 3.75rem; - } - - .email { - gap: 2.45rem; - } - - .info-subtitle { - color: var(--Light-Black, var(--text-color-2, #373f41)); - - font-size: 1.5rem; - font-style: normal; - font-weight: 400; - line-height: 140%; /* 2.1rem */ - letter-spacing: 0.015rem; - } - - .info-value { - color: var(--Light-Black, var(--text-color-2, #373f41)); - - font-size: 1.5rem; - font-style: normal; - font-weight: 400; - line-height: 1.35rem; /* 90% */ - letter-spacing: 0.015rem; - } - - .container-apply__form-warn { - width: 100%; - display: flex; - flex-direction: column; - align-items: center; - gap: 0.2rem; - border-radius: 0.6rem; - background: #fff; - padding: 1.72rem 0rem; - box-shadow: 0.5rem 0.5rem 1rem rgba(0, 0, 0, 0.1); - - span:nth-child(1) { - color: #000; - - font-size: 1.5rem; - font-style: normal; - font-weight: 400; - line-height: 1.8rem; /* 120% */ - letter-spacing: -0.03rem; - } - span:nth-child(2) { - color: #686868; - - font-size: 1.05rem; - font-style: normal; - font-weight: 400; - line-height: 1.8rem; /* 171.429% */ - letter-spacing: -0.021rem; - } - } - - .container-apply__form-buttons { - display: flex; - justify-content: center; - gap: 1.35rem; - - .btn { - display: flex; - width: 12.15rem; - height: 3.75rem; - padding: 0.75rem; - justify-content: center; - align-items: center; - gap: 0.75rem; - flex-shrink: 0; - border-radius: 0.6rem; - color: var(--Light-Black, var(--text-color-2, #373f41)); - - font-size: 1.5rem; - font-style: normal; - font-weight: 400; - line-height: 1.35rem; /* 90% */ - letter-spacing: 0.015rem; - } - - .back { - border: 0.75px solid var(--main-color, #5877fc); - background: #fff; - } - - .submit { - background: var(--main-color, #5877fc); - color: #fff; - } - } - } -`; - -const S = { ApplySubmit }; - -export default S; diff --git a/src/pages/recruit/RecruitDetailPage/steps/ApplySubmit.tsx b/src/pages/recruit/RecruitDetailPage/steps/ApplySubmit.tsx deleted file mode 100644 index 8e802ead..00000000 --- a/src/pages/recruit/RecruitDetailPage/steps/ApplySubmit.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import React from 'react'; -import S from './ApplySubmit.styled'; -import { useRecoilState } from 'recoil'; -import { applyStepState } from '../../../../atom'; - -const ApplySubmit = () => { - const [step, setStep] = useRecoilState(applyStepState); - const onClickStep = () => { - setStep(prev => prev - 1); - }; - return ( - -
- 신청 정보 -
-
- 이름 - {'송유진'} -
-
- 등급 - {'A0'} -
-
- 학교 - {'광운대학교'} -
-
- 학번 - {'18'} -
-
-
-
- 학과 - {'소프트웨어학부'} -
-
- 이메일 - {'jiminni_01@kw.ac.kr'} -
-
-
- 멤버들에게 위 정보가 공개됩니다. - 멤버들이 본인의 프로필을 열람할 수 있습니다. -
-
- - -
-
-
- ); -}; - -export default ApplySubmit; diff --git a/src/pages/recruit/RecruitPage/RecruitPage.styled.ts b/src/pages/recruit/RecruitPage/RecruitPage.styled.ts deleted file mode 100644 index 344d0517..00000000 --- a/src/pages/recruit/RecruitPage/RecruitPage.styled.ts +++ /dev/null @@ -1,201 +0,0 @@ -import styled from 'styled-components'; - -const RecruitPage = styled.div` - width: clamp(45%, 108rem, 75%); - margin: 0 auto; - - .container-filter_area { - display: flex; - margin-right: 2.6rem; - margin-top: 2.3rem; - .area { - display: flex; - width: 8.5rem; - height: 3.5rem; - padding: 0.75rem; - flex-direction: column; - justify-content: center; - align-items: center; - gap: 0.75rem; - flex-shrink: 0; - border-radius: 7.5rem; - background-color: #fcefaa; - color: #000; - - font-size: 1.5rem; - font-style: normal; - font-weight: 400; - line-height: 2.1rem; /* 100% */ - letter-spacing: 0.015rem; - cursor: pointer; - } - - .no { - background-color: #fff; - } - - .out { - background-color: #f3f5ff; - } - } - - .container-filter_menu { - display: flex; - gap: 1.65rem; - margin-top: 2.02rem; - } - .sep { - width: 0.3rem; - height: 3.225rem; - flex-shrink: 0; - background-color: #d9d9d9; - } - .dropdown-spec { - display: flex; - margin-left: 3rem; - gap: 1.65rem; - } - - hr { - margin-top: 2.32rem; - margin-bottom: 2.32rem; - background: #ababab; - height: 0.75px; - border: 0; - } - - .container-options { - display: flex; - justify-content: space-between; - align-items: center; - - .container-options__filters { - display: flex; - justify-content: flex-start; - align-items: center; - gap: 0.75rem; - - .filter { - display: flex; - width: 13.125rem; - height: 3.525rem; - padding: 0.75rem; - justify-content: center; - align-items: center; - gap: 0.75rem; - border-radius: 7.5rem; - background: var(--sub-color, #e0e6ff); - color: var(--Light-Black, var(--text-color-2, #373f41)); - - font-size: 1.5rem; - font-style: normal; - font-weight: 400; - line-height: 1.35rem; /* 90% */ - letter-spacing: 0.015rem; - cursor: pointer; - } - - .bookmark { - border-radius: 7.5rem; - background: #f7e8fb; - } - } - - .container-options__search { - display: inline-flex; - justify-content: flex-start; - padding: 0.8625rem 2rem 0.8625rem 1.2rem; - box-sizing: border-box; - height: 3.525rem; - align-items: center; - width: 25.175rem; - gap: 1.2rem; - border-radius: 7.5rem; - border: 0.75px solid #dcdcdc; - background: #f9f9f9; - color: var(--Light-Black, var(--text-color-2, #373f41)); - - font-size: 1.2rem; - font-style: normal; - font-weight: 400; - line-height: 1.35rem; /* 112.5% */ - letter-spacing: 0.015rem; - - svg { - display: flex; - align-items: center; - } - - input { - width: 20rem; - border: none; - outline: none; - font-size: 1.2rem; - background-color: transparent; - } - } - } - - .container-contents { - margin-top: 2rem; - display: flex; - flex-direction: column; - gap: 3.75rem; - margin-bottom: 5rem; - - .container-contents__row { - .container-subtitle { - display: flex; - justify-content: space-between; - align-items: center; - } - .subtitle { - color: var(--Light-Black, var(--text-color-2, #373f41)); - - font-size: 2rem; - font-style: normal; - font-weight: 500; - line-height: 4.2rem; - letter-spacing: 0.015rem; - } - - select { - border: none; - outline: none; - color: var(--Light-Black, var(--text-color-2, #373f41)); - - font-size: 1.3rem; - font-style: normal; - font-weight: 400; - line-height: 4.2rem; /* 280% */ - letter-spacing: 0.015rem; - } - } - .contents { - display: flex; - margin: 0 auto; - gap: 1.8rem; - } - - .container-contents__grid { - display: grid; - grid-template-columns: repeat(4, 1fr); - gap: 1.8rem 1.8rem; - } - } - - .container-pagination { - display: flex; - justify-content: center; - align-items: center; - - ul { - list-style: none; - display: flex; - } - } -`; - -const S = { RecruitPage }; - -export default S; diff --git a/src/pages/recruit/RecruitPage/RecruitPage.tsx b/src/pages/recruit/RecruitPage/RecruitPage.tsx deleted file mode 100644 index eaabeeb9..00000000 --- a/src/pages/recruit/RecruitPage/RecruitPage.tsx +++ /dev/null @@ -1,116 +0,0 @@ -import React, { useState } from 'react'; -import { Dropdown, Subtitle, RecruitCard, Pagination } from '../../../components'; -import S from './RecruitPage.styled'; -import { SearchIcon } from '../../../assets'; - -const START_PAGE_NUM = 1; - -const RecruitPage = () => { - const postsNum = 150; - const [currentPage, setCurrentPage] = useState(START_PAGE_NUM); - const [isFiltered, setIsFiltered] = useState({ - isInside: true, - isOutside: false, - }); - console.log(currentPage); - const onClickHandler = (event: React.MouseEvent) => { - const target = event.currentTarget; - if (target.innerText === '교내') { - setIsFiltered({ isInside: true, isOutside: false }); - } - if (target.innerText === '교외') { - setIsFiltered({ isInside: false, isOutside: true }); - } - }; - - return ( - -
-
-
- 교내 -
-
- 교외 -
-
-
- - -
- - -
-
-
-
-
-
-
-
☑️ 수업만 보기
-
-
-
- -
-
- -
-
-
-
-
-
-
👀 내가 관심 있을 만한 구인 글
-
- -
-
-
- - - - -
-
-
- 전체 구인 글 -
- - - - - -
-
-
-
-
- -
-
- ); -}; - -export default RecruitPage; From 481eaad094e7de2ebeb2252e064a0bc30caf309b Mon Sep 17 00:00:00 2001 From: kimsuyeon0916 Date: Mon, 15 Apr 2024 17:17:52 +0900 Subject: [PATCH 002/116] =?UTF-8?q?chore:=20#91=20src/pages/recruit=20?= =?UTF-8?q?=EB=82=B4=20=ED=8F=B4=EB=8D=94=20=EB=8C=80=EB=AC=B8=EC=9E=90=20?= =?UTF-8?q?->=20=EC=86=8C=EB=AC=B8=EC=9E=90=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/index.ts | 6 +- src/pages/index.ts | 4 +- .../RecruitDetailPage.styled.ts | 415 ++++++++++++++++++ .../recruitDetailPage/RecruitDetailPage.tsx | 196 +++++++++ src/pages/recruit/recruitDetailPage/data.ts | 38 ++ .../steps/ApplyInfomation.styled.ts | 142 ++++++ .../steps/ApplyInfomation.tsx | 53 +++ .../steps/ApplyInput.styled.ts | 174 ++++++++ .../recruitDetailPage/steps/ApplyInput.tsx | 103 +++++ .../steps/ApplySubmit.styled.ts | 133 ++++++ .../recruitDetailPage/steps/ApplySubmit.tsx | 60 +++ .../recruit/recruitPage/RecruitPage.styled.ts | 201 +++++++++ src/pages/recruit/recruitPage/RecruitPage.tsx | 116 +++++ 13 files changed, 1636 insertions(+), 5 deletions(-) create mode 100644 src/pages/recruit/recruitDetailPage/RecruitDetailPage.styled.ts create mode 100644 src/pages/recruit/recruitDetailPage/RecruitDetailPage.tsx create mode 100644 src/pages/recruit/recruitDetailPage/data.ts create mode 100644 src/pages/recruit/recruitDetailPage/steps/ApplyInfomation.styled.ts create mode 100644 src/pages/recruit/recruitDetailPage/steps/ApplyInfomation.tsx create mode 100644 src/pages/recruit/recruitDetailPage/steps/ApplyInput.styled.ts create mode 100644 src/pages/recruit/recruitDetailPage/steps/ApplyInput.tsx create mode 100644 src/pages/recruit/recruitDetailPage/steps/ApplySubmit.styled.ts create mode 100644 src/pages/recruit/recruitDetailPage/steps/ApplySubmit.tsx create mode 100644 src/pages/recruit/recruitPage/RecruitPage.styled.ts create mode 100644 src/pages/recruit/recruitPage/RecruitPage.tsx diff --git a/src/components/index.ts b/src/components/index.ts index deafeb6b..860f4f5f 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -79,9 +79,9 @@ import Dropdown from './dropdown/Dropdown'; import DateSelect from './dateSelect/DateSelect'; import DeadlineSelect from './dateSelect/DeadlineSelect'; import Icon from './meeteam/icon/Icon'; -import ApplyInfomation from '../pages/recruit/RecruitDetailPage/steps/ApplyInfomation'; -import ApplyInput from '../pages/recruit/RecruitDetailPage/steps/ApplyInput'; -import ApplySubmit from '../pages/recruit/RecruitDetailPage/steps/ApplySubmit'; +import ApplyInfomation from '../pages/recruit/recruitDetailPage/steps/ApplyInfomation'; +import ApplyInput from '../pages/recruit/recruitDetailPage/steps/ApplyInput'; +import ApplySubmit from '../pages/recruit/recruitDetailPage/steps/ApplySubmit'; import Content from './meeteam/Content'; import RecruitCard from './meeteam/card/RecruitCard'; import Pagination from './pagination/Pagination'; diff --git a/src/pages/index.ts b/src/pages/index.ts index d4efad69..0d72ae3c 100644 --- a/src/pages/index.ts +++ b/src/pages/index.ts @@ -1,5 +1,5 @@ import MainPage from './MainPage'; -import RecruitPage from './recruit/RecruitPage/RecruitPage'; +import RecruitPage from './recruit/recruitPage/RecruitPage'; import GalaryPage from './GalaryPage'; import RecruitCreatePage from './create/recruitCreatePage/RecruitCreatePage'; import OutputCreatePage from './create/outputCreatePage/OutputCreatePage'; @@ -13,7 +13,7 @@ import NicknameSettingPage from './account/signUp/nicknameSetting/NicknameSettin import type { Account, User } from './account/signUp/SignUpData'; import PassWordFindingPage from './account/passWordFindingPage/PassWordFindingPage'; import { PASSWORD_DATA } from './account/passWordFindingPage/PassWordData'; -import RecruitDetailPage from './recruit/RecruitDetailPage/RecruitDetailPage'; +import RecruitDetailPage from './recruit/recruitDetailPage/RecruitDetailPage'; import MyActivityManagePage from './activity/MyActivityManagePage'; import MyActivityInvited from './activity/MyActivityInvited'; import MyActivityWrapper from './activity/MyActivityWrapper'; diff --git a/src/pages/recruit/recruitDetailPage/RecruitDetailPage.styled.ts b/src/pages/recruit/recruitDetailPage/RecruitDetailPage.styled.ts new file mode 100644 index 00000000..422b1163 --- /dev/null +++ b/src/pages/recruit/recruitDetailPage/RecruitDetailPage.styled.ts @@ -0,0 +1,415 @@ +import styled from 'styled-components'; + +interface Props { + $isRound: boolean; + $color: string; +} + +const RecruitDetailPage = styled.div` + width: clamp(45%, 108rem, 75%); + margin: 0 auto; + margin-top: 3.38rem; + margin-bottom: 3.38rem; + + .container { + display: flex; + gap: 1.5rem; + + .container-left { + display: flex; + flex-direction: column; + gap: 1.5rem; + width: 71.4rem; + + .container-info { + /* height: 54rem; */ + border-radius: 0.75rem; + border: 1.5px solid #eeecff; + background: #fff; + padding: 2.25rem 3.45rem; + box-sizing: border-box; + + .container-info__title { + display: flex; + align-items: center; + gap: 1.27rem; + margin-top: 0.5rem; + + h1 { + margin-top: 0.2rem; + color: #000; + + font-size: 2.4rem; + font-style: normal; + font-weight: 400; + line-height: 3rem; + letter-spacing: 0.015rem; + } + } + + .container-info__writer { + display: flex; + align-items: center; + gap: 0.7rem; + margin-top: 1.37rem; + + .profile-img { + width: 3.3075rem; + height: 3.3075rem; + flex-shrink: 0; + border-radius: 50%; + + img { + width: 100%; + height: 100%; + border-radius: 50%; + } + } + + div:nth-child(2) { + color: var(--Light-Black, var(--text-color-2, #373f41)); + + font-size: 1.5rem; + font-style: normal; + font-weight: 400; + line-height: 1.35rem; /* 90% */ + letter-spacing: 0.015rem; + } + } + } + + .container-required__info { + margin-top: 3.6rem; + display: grid; + grid-template-columns: 1fr 2fr; + grid-row-gap: 1.5rem; + grid-column-gap: 8rem; + } + + .container-introduction { + margin-top: 5rem; + + h4 { + color: #000; + + font-size: 1.8rem; + font-style: normal; + font-weight: 400; + line-height: 1.8rem; /* 100% */ + letter-spacing: -0.036rem; + } + + p { + margin-top: 3rem; + color: var(--text-color-2, #373f41); + font-family: Pretendard; + font-size: 1.6rem; + font-style: normal; + font-weight: 400; + line-height: 150%; /* 2.7rem */ + letter-spacing: 0.015rem; + } + } + + .container-current { + flex-shrink: 0; + border-radius: 0.75rem; + border: 1.5px solid #eeecff; + background: #fff; + padding: 2.33rem 3.45rem; + + .container-current__title { + color: #000; + + font-size: 1.8rem; + font-style: normal; + font-weight: 400; + line-height: 1.8rem; /* 100% */ + letter-spacing: -0.036rem; + } + + .container-current__roles { + display: flex; + flex-direction: column; + justify-content: center; + gap: 1.43rem; + margin-top: 1.73rem; + + .container-current__roles--element { + display: flex; + justify-content: space-between; + align-items: center; + width: 45.75rem; + /* height: 7.8rem; */ + flex-shrink: 0; + border-radius: 0.75rem; + border: 0.75px solid #dcdcdc; + background: #f9f9f9; + padding: 1.35rem 2.03rem 0.75rem 2.03rem; + box-sizing: border-box; + + .roles-info { + display: flex; + flex-direction: column; + justify-content: flex-start; + gap: 0.8rem; + } + + .roles-info__role { + display: flex; + align-items: center; + gap: 1.25rem; + + .role { + color: #000; + + font-size: 1.65rem; + font-style: normal; + font-weight: 400; + line-height: 1.8rem; /* 109.091% */ + letter-spacing: 0.015rem; + } + + .members { + display: flex; + align-items: center; + + .member { + width: 2.55rem; + height: 2.55rem; + flex-shrink: 0; + cursor: pointer; + } + } + } + + .roles-info__spec { + display: flex; + gap: 0.6rem; + + .spec { + display: inline-flex; + width: 7.275rem; + height: 2.5rem; + padding: 0.75rem 1.125rem; + justify-content: center; + align-items: center; + gap: 0.75rem; + flex-shrink: 0; + color: var(--Light-Black, var(--text-color-2, #373f41)); + + font-size: 1.4rem; + font-style: normal; + font-weight: 400; + line-height: 1.35rem; /* 90% */ + letter-spacing: 0.015rem; + border-radius: 7.5rem; + border: 0.75px solid #000; + background: #fff; + } + } + } + } + } + } + + .container-right { + display: flex; + flex-direction: column; + gap: 1.5rem; + width: 34.8rem; + + .container-apply { + border-radius: 0.75rem; + border: 0.75px solid #dcdcdc; + background: #f9f9f9; + padding: 3.3rem 3rem 1.5rem 3rem; + } + + .container-recommend { + flex-shrink: 0; + border-radius: 0.75rem; + border: 0.75px solid #dcdcdc; + background: #f6f6f6; + display: flex; + flex-direction: column; + padding: 3.08rem 2.55rem 2.55rem 2.55rem; + gap: 1.5rem; + + .title { + color: #000; + + font-size: 1.8rem; + font-style: normal; + font-weight: 400; + line-height: 1.8rem; /* 100% */ + letter-spacing: -0.036rem; + } + + .content { + display: flex; + flex-direction: column; + width: 100%; + height: 18.375rem; + flex-shrink: 0; + border-radius: 0.75rem; + border: 1.5px solid var(--main-color, #ababab); + background: #f9f9f9; + padding: 1.35rem 1.5rem 1.8rem 1.5rem; + cursor: pointer; + + &:hover { + transition: 0.3s ease-in-out; + border: 1.5px solid var(--main-color, #5877fc); + } + + .content-tags { + display: flex; + justify-content: space-between; + + .tags { + display: flex; + gap: 0.6rem; + + div:nth-child(1) { + display: flex; + width: 4.05rem; + height: 2.4rem; + padding: 0.75rem; + justify-content: center; + align-items: center; + gap: 0.75rem; + border-radius: 0.6rem; + background: #e0e6ff; + color: #000; + + font-size: 1.2rem; + font-style: normal; + font-weight: 400; + line-height: 1.35rem; /* 112.5% */ + letter-spacing: 0.015rem; + } + + div:nth-child(2) { + display: flex; + width: 5.55rem; + height: 2.4rem; + padding: 0.75rem; + justify-content: center; + align-items: center; + gap: 0.75rem; + border-radius: 0.6rem; + background: #e3f5ff; + color: var(--Light-Black, var(--text-color-2, #373f41)); + + font-size: 1.1rem; + font-style: normal; + font-weight: 400; + line-height: 1.35rem; /* 112.5% */ + letter-spacing: 0.015rem; + } + } + } + + .content-title { + height: 4.8rem; + flex-shrink: 0; + margin-top: 1.65rem; + /* overflow: hidden; */ + color: var(--Light-Black, var(--text-color-2, #373f41)); + text-overflow: ellipsis; + /* white-space: nowrap; */ + + font-size: 1.65rem; + font-style: normal; + font-weight: 400; + line-height: 130%; /* 2.145rem */ + letter-spacing: 0.015rem; + } + + .content-info { + display: flex; + margin-top: 5rem; + justify-content: space-between; + + div { + color: var(--Light-Black, var(--text-color-2, #373f41)); + + font-size: 1.2rem; + font-style: normal; + font-weight: 400; + line-height: 1.35rem; /* 112.5% */ + letter-spacing: 0.015rem; + } + } + } + } + } + } + + .container-comments { + width: 100%; + height: 100%; + flex-shrink: 0; + border-radius: 0.75rem; + border: 1.5px solid #bcd7ff; + background: #f7faff; + margin-top: 1.5rem; + padding: 2rem 3.45rem; + + .container-comments__lists { + width: 100%; + display: flex; + flex-direction: column; + gap: 1.3rem; + margin-bottom: 2rem; + } + + .container-comments__title { + color: #373f41; + font-size: 1.8rem; + font-style: normal; + font-weight: 400; + line-height: 3rem; + letter-spacing: 0.015rem; + } + } +`; + +const RequiredInformationItem = styled.li` + display: flex; + column-gap: 2.25rem; + align-items: center; + .required-information__row { + display: flex; + column-gap: 1.2rem; + } +`; + +const RequiredInformationHead = styled.h3` + display: flex; + color: var(--text-color, #151515); + font-weight: 400; + font-size: 1.4rem; +`; + +const RequiredInformationSpan = styled.span` + display: flex; + justify-content: center; + align-items: center; + padding: 0.75rem; + height: 3.15rem; + font-size: 1.4rem; + font-weight: 400; + border-radius: ${props => (props.$isRound ? `7.5rem` : `0.6rem`)}; + background: ${props => props.$color}; +`; + +const S = { + RecruitDetailPage, + RequiredInformationItem, + RequiredInformationSpan, + RequiredInformationHead, +}; + +export default S; diff --git a/src/pages/recruit/recruitDetailPage/RecruitDetailPage.tsx b/src/pages/recruit/recruitDetailPage/RecruitDetailPage.tsx new file mode 100644 index 00000000..33bb1af7 --- /dev/null +++ b/src/pages/recruit/recruitDetailPage/RecruitDetailPage.tsx @@ -0,0 +1,196 @@ +import React, { useState } from 'react'; +import S from './RecruitDetailPage.styled'; +import { + Tag, + ApplyInfomation, + ApplyInput, + ApplySubmit, + informationList, + role, + CONTENT, + Comment, + CommentInput, +} from '../../../components'; +import ColorMatching from '../../../utils/ColorMatching'; +import { useRecoilValue } from 'recoil'; +import { applyStepState } from '../../../atom'; +import { useNavigate } from 'react-router-dom'; +import { JsxElementComponentProps } from '../../../types'; +import { commentsData } from './data'; + +const stepLists: JsxElementComponentProps = { + 0: , + 1: , + 2: , +}; + +const RecruitDetailPage = () => { + const navigate = useNavigate(); + const [commentsList, setCommentsList] = useState(commentsData); + const [contents, setContents] = useState(''); + const isLogin = true; // 임시 코드 + const step = useRecoilValue(applyStepState); + + const isRound = (title: string) => { + const roundTitles = ['유형', '진행']; + + if (roundTitles.includes(title)) { + return false; + } + return true; + }; + + const addComment = () => { + if (contents !== '' && contents.trim() !== '') { + const newComment = { + id: commentsData.length.toString(), + username: 'yeom', + content: contents, + replies: [], + }; + setCommentsList([...commentsList, newComment]); + setContents(''); + } + }; + + const deleteComment = (id: string) => { + setCommentsList(prevComments => prevComments.filter(v => v.id !== id)); + }; + + const onKeyPress = (event: React.KeyboardEvent) => { + const target = event.currentTarget; + if (target.value.length !== 0 && event.key === 'Enter') { + event.preventDefault(); + addComment(); + } + }; + + const onChangeHandler = (event: React.ChangeEvent) => { + setContents(event.target.value); + }; + + const onClickInput = () => { + if (!isLogin) { + navigate('/signin'); + } + }; + + return ( + +
+
+
+
+
+

[커뮤니티 웹 서비스 프로젝트] 디자이너 모집

+ +
+
+ + + +
{'김민지'}
+
+
+
+ {informationList.map((information, index) => ( + + {information.title} +
+ {information.content.split(',').map((content, index) => ( + + {content} + + ))} +
+
+ ))} +
+
+

구인 글

+

{CONTENT}

+
+
+
+ 구인 현황 +
+ {role.map((e, index) => ( +
+
+
+
+ {e.role} ({e.current.length} / {e.max}) +
+
+
+ {e.specs.map((spec, j) => ( +
+ {spec} +
+ ))} +
+
+
+ ))} +
+
+
+
+
+
{stepLists[step]}
+
+
+
+ 비슷한 구인 글 +
+
+
+
+
교외
+
프로젝트
+
+
+
+ [반려 동물을 위한 앱 서비스] 프론트엔드/백엔드 개발자를 모집합니다. +
+
+
마감 7일 전
+
조회수 101회
+
+
+
+
+
+
+ 댓글 +
    + {commentsList.map((comment, index) => { + return ( + deleteComment(comment.id)} + /> + ); + })} +
+ +
+
+ ); +}; + +export default RecruitDetailPage; diff --git a/src/pages/recruit/recruitDetailPage/data.ts b/src/pages/recruit/recruitDetailPage/data.ts new file mode 100644 index 00000000..667d8874 --- /dev/null +++ b/src/pages/recruit/recruitDetailPage/data.ts @@ -0,0 +1,38 @@ +import { Comment, JsxElementComponentProps } from '../../../types'; + +export const commentsData: Comment[] = [ + { + id: '0', + username: 'johny', + content: '이거 어때?', + replies: [ + { + id: '0-0', + username: 'lee', + content: '뭘 어때 걍 하셈', + }, + { + id: '0-1', + username: 'jun', + content: '조용히하셈', + }, + ], + }, + { + id: '1', + username: 'yeom', + content: '아니 근데 왜 나도 이거 지원하고 싶다', + replies: [ + { + id: '1-0', + username: 'lee', + content: '하셈', + }, + { + id: '1-1', + username: 'jun', + content: '바로 탈락하쥬?ㅋ', + }, + ], + }, +]; diff --git a/src/pages/recruit/recruitDetailPage/steps/ApplyInfomation.styled.ts b/src/pages/recruit/recruitDetailPage/steps/ApplyInfomation.styled.ts new file mode 100644 index 00000000..4e1effbb --- /dev/null +++ b/src/pages/recruit/recruitDetailPage/steps/ApplyInfomation.styled.ts @@ -0,0 +1,142 @@ +import styled from 'styled-components'; + +const ApplyInformation = styled.div` + .container-apply__member { + display: flex; + justify-content: flex-start; + align-items: center; + gap: 7rem; + + .type { + color: #000; + + font-size: 1.8rem; + font-style: normal; + font-weight: 400; + line-height: 1.8rem; /* 100% */ + letter-spacing: -0.036rem; + } + + .leader-info { + display: flex; + gap: 0.62rem; + margin-top: 2rem; + + .leader-info__icon { + width: 3.3075rem; + height: 3.3075rem; + flex-shrink: 0; + border-radius: 50%; + + img { + width: 100%; + height: 100%; + border-radius: 50%; + } + } + + .leader-info__user { + display: flex; + flex-direction: column; + justify-content: center; + gap: 0.3rem; + + span:nth-child(1) { + color: var(--Light-Black, var(--text-color-2, #373f41)); + + font-size: 1.5rem; + font-style: normal; + font-weight: 400; + line-height: 1.35rem; /* 90% */ + letter-spacing: 0.015rem; + } + + span:nth-child(2) { + display: flex; + gap: 1rem; + + .user-info { + color: #858585; + + font-size: 1.2rem; + font-style: normal; + font-weight: 400; + line-height: 1.35rem; + letter-spacing: 0.015rem; + } + } + } + } + } + + hr { + margin-top: 2.8rem; + } + + .container-apply__deadline { + margin-top: 2.51rem; + display: flex; + flex-direction: column; + gap: 0.75rem; + + span:nth-child(1) { + color: #000; + + font-size: 1.8rem; + font-style: normal; + font-weight: 400; + line-height: 1.8rem; /* 100% */ + letter-spacing: -0.036rem; + } + span:nth-child(2) { + color: var(--Light-Black, var(--text-color-2, #373f41)); + + font-size: 1.5rem; + font-style: normal; + font-weight: 400; + line-height: 1.35rem; /* 90% */ + letter-spacing: 0.015rem; + } + } + + .container-apply__buttons { + margin-top: 3.1rem; + display: flex; + flex-direction: column; + gap: 0.75rem; + align-items: center; + + button { + border: none; + outline: none; + display: flex; + width: 25.65rem; + height: 4.275rem; + padding: 0.75rem; + justify-content: center; + align-items: center; + gap: 0.75rem; + flex-shrink: 0; + border-radius: 7.5rem; + cursor: pointer; + + span { + margin-top: 0.1rem; + } + } + + button:nth-child(1) { + background-color: #fff; + border: 0.75px solid rgba(95, 92, 236, 0.76); + } + + button:nth-child(2) { + color: #fff; + background: linear-gradient(270deg, rgba(95, 92, 236, 0.76) -6.3%, #d85cec 101.52%); + } + } +`; + +const S = { ApplyInformation }; + +export default S; diff --git a/src/pages/recruit/recruitDetailPage/steps/ApplyInfomation.tsx b/src/pages/recruit/recruitDetailPage/steps/ApplyInfomation.tsx new file mode 100644 index 00000000..cda6db64 --- /dev/null +++ b/src/pages/recruit/recruitDetailPage/steps/ApplyInfomation.tsx @@ -0,0 +1,53 @@ +import React, { useState } from 'react'; +import S from './ApplyInfomation.styled'; +import { useRecoilState } from 'recoil'; +import { applyStepState } from '../../../../atom'; +import { FaRegBookmark, FaBookmark } from 'react-icons/fa6'; + +const ApplyInfomation = () => { + const [step, setStep] = useRecoilState(applyStepState); + const [isBookmarked, setIsBookmarked] = useState(false); + const onClickStep = () => { + setStep(prev => prev + 1); + }; + const onClickBookmark = () => { + setIsBookmarked(prev => !prev); + }; + return ( + +
+
+ 리더 +
+
+ +
+
+ 김민지 + + 응답률: 90% + 평점: 4.8 + +
+
+
+
+
+
+ 🚨 마감일 + {'23.10.16(1일 남음)'} +
+
+ + +
+
+ ); +}; + +export default React.memo(ApplyInfomation); diff --git a/src/pages/recruit/recruitDetailPage/steps/ApplyInput.styled.ts b/src/pages/recruit/recruitDetailPage/steps/ApplyInput.styled.ts new file mode 100644 index 00000000..ba8370a7 --- /dev/null +++ b/src/pages/recruit/recruitDetailPage/steps/ApplyInput.styled.ts @@ -0,0 +1,174 @@ +import styled from 'styled-components'; + +const ApplyInput = styled.div` + .container-apply__form { + display: flex; + flex-direction: column; + gap: 1.8rem; + + .container-apply__form-title { + color: #000; + + font-size: 1.8rem; + font-style: normal; + font-weight: 400; + line-height: 1.8rem; /* 100% */ + letter-spacing: -0.036rem; + } + + .container-apply__form-my { + display: flex; + align-items: center; + gap: 0.85rem; + + span { + color: var(--Light-Black, var(--text-color-2, #373f41)); + + font-size: 1.5rem; + font-style: normal; + font-weight: 400; + line-height: 1.35rem; /* 90% */ + letter-spacing: 0.015rem; + } + } + + .container-apply__form-input { + display: flex; + position: relative; + flex-direction: column; + align-items: center; + margin-top: 1.75rem; + gap: 1.65rem; + + .container-apply__roles { + display: flex; + width: 100%; + height: 4rem; + padding: 0.45rem 1.35rem; + align-items: center; + border-radius: 0.75rem; + border: 0.05rem solid #614bf7; + background: #fff; + outline: none; + color: var(--Light-Black, var(--text-color-2, #373f41)); + + font-size: 1.3rem; + font-style: normal; + font-weight: 400; + line-height: 140%; /* 2.1rem */ + letter-spacing: 0.015rem; + cursor: pointer; + } + .dropdown { + position: absolute; + top: 5rem; + width: 100%; + z-index: 101; + background-color: #ffeaa7; + padding: 2rem 1.2rem; + border-radius: 1.2rem; + + ul { + display: flex; + flex-direction: column; + gap: 2rem; + + li { + font-size: 1.3rem; + cursor: pointer; + + &:hover { + transition: 0.2s; + color: #9a77ee; + } + } + } + } + + .container-apply__words { + display: flex; + width: 90%; + height: 3rem; + padding: 0.45rem 1.35rem; + align-items: center; + border-radius: 0.75rem; + border: 0.05rem solid #614bf7; + background: #fff; + outline: none; + } + } + + .container-apply__form-warn { + display: flex; + flex-direction: column; + padding: 1.35rem 1.45rem; + background-color: #fff; + width: 100%; + border-radius: 0.6rem; + + span:nth-child(1) { + color: #000; + + font-size: 1.5rem; + font-style: normal; + font-weight: 400; + line-height: 1.8rem; /* 120% */ + letter-spacing: -0.03rem; + } + span:nth-child(2) { + margin-top: 0.3rem; + color: #686868; + + font-size: 1.05rem; + font-style: normal; + font-weight: 400; + line-height: 1.8rem; /* 171.429% */ + letter-spacing: -0.021rem; + } + + .container-checkbox { + display: flex; + align-items: center; + margin-top: 0.82rem; + + label { + color: #000; + + font-size: 1.2rem; + font-style: normal; + font-weight: 400; + line-height: 1.8rem; /* 150% */ + letter-spacing: -0.024rem; + margin-top: 0.2rem; + margin-left: 0.4rem; + } + } + } + + .container-apply__form-button { + display: flex; + height: 4.275rem; + padding: 0.75rem; + justify-content: center; + align-items: center; + gap: 0.75rem; + flex-shrink: 0; + border-radius: 7.5rem; + } + + .disable { + background-color: #dfe6e9; + color: #000; + cursor: not-allowed; + } + + .able { + background: linear-gradient(270deg, rgba(95, 92, 236, 0.76) -6.3%, #d85cec 101.52%); + color: #fff; + } + } +`; + +const S = { ApplyInput }; + +export default S; diff --git a/src/pages/recruit/recruitDetailPage/steps/ApplyInput.tsx b/src/pages/recruit/recruitDetailPage/steps/ApplyInput.tsx new file mode 100644 index 00000000..8a8a55e3 --- /dev/null +++ b/src/pages/recruit/recruitDetailPage/steps/ApplyInput.tsx @@ -0,0 +1,103 @@ +import React, { useState } from 'react'; +import { Icon } from '../../../../components'; +import S from './ApplyInput.styled'; +import { useRecoilState } from 'recoil'; +import { applyInfoState, applyStepState } from '../../../../atom'; + +const roles: string[] = ['프론트엔드 개발자', '백엔드 개발자', '디자이너', '기획자']; +interface Input { + message: string; + currentValue: string; +} + +const ApplyInput = () => { + const [step, setStep] = useRecoilState(applyStepState); + const [info, setInfo] = useRecoilState(applyInfoState); + const [isChecked, setIsChecked] = useState(false); + const [message, setMessage] = useState(''); + const [currentValue, setCurrentValue] = useState('역할 선택'); + const [inputValue, setInputValue] = useState({ + message: '', + currentValue: '역할 선택', + }); + const [openDropdown, setOpenDropdown] = useState(false); + const isValid = isChecked && inputValue.currentValue !== '역할 선택'; + + const onClickStep = () => { + setStep(prev => prev + 1); + setInfo({ role: inputValue.currentValue, message: inputValue.message }); + }; + + const onClickDropdown = () => { + setOpenDropdown(prev => !prev); + }; + + const onChangeInput = (event: React.ChangeEvent) => { + setInputValue({ ...inputValue, message: event.target.value }); + }; + + const onClickList = (event: React.MouseEvent) => { + const target = event.currentTarget; + setInputValue({ ...inputValue, currentValue: target.innerText }); + setOpenDropdown(false); + }; + + const onClickCancel = () => { + setStep(prev => prev - 1); + }; + + return ( + +
+ 신청 정보 +
+ + {'송유진'} +
+
+
+ {inputValue.currentValue} +
+ {openDropdown && ( +
+
    + {roles.map((element, index) => ( +
  • + {element} +
  • + ))} +
+
+ )} + +
+
+ 멤버들에게 내 정보 공개할 수 있나요? + 정보 공개 동의 시, 팀매칭에 유리합니다. +
+ setIsChecked(prev => !prev)} /> + +
+
+ + +
+
+ ); +}; + +export default ApplyInput; diff --git a/src/pages/recruit/recruitDetailPage/steps/ApplySubmit.styled.ts b/src/pages/recruit/recruitDetailPage/steps/ApplySubmit.styled.ts new file mode 100644 index 00000000..690e0082 --- /dev/null +++ b/src/pages/recruit/recruitDetailPage/steps/ApplySubmit.styled.ts @@ -0,0 +1,133 @@ +import styled from 'styled-components'; + +const ApplySubmit = styled.div` + .container-apply__form { + display: flex; + flex-direction: column; + gap: 1.8rem; + + .container-apply__form-title { + color: #000; + + font-size: 1.8rem; + font-style: normal; + font-weight: 400; + line-height: 1.8rem; /* 100% */ + letter-spacing: -0.036rem; + } + + .container-apply__form-short_info { + display: grid; + grid-template-columns: 1fr 1fr; + grid-template-rows: 1fr 1fr; + gap: 1.95rem 2.17rem; + } + + .container-apply__form-long_info { + margin-top: 0.3rem; + display: flex; + flex-direction: column; + gap: 1.8rem; + } + + .info { + display: flex; + align-items: center; + gap: 3.75rem; + } + + .email { + gap: 2.45rem; + } + + .info-subtitle { + color: var(--Light-Black, var(--text-color-2, #373f41)); + + font-size: 1.5rem; + font-style: normal; + font-weight: 400; + line-height: 140%; /* 2.1rem */ + letter-spacing: 0.015rem; + } + + .info-value { + color: var(--Light-Black, var(--text-color-2, #373f41)); + + font-size: 1.5rem; + font-style: normal; + font-weight: 400; + line-height: 1.35rem; /* 90% */ + letter-spacing: 0.015rem; + } + + .container-apply__form-warn { + width: 100%; + display: flex; + flex-direction: column; + align-items: center; + gap: 0.2rem; + border-radius: 0.6rem; + background: #fff; + padding: 1.72rem 0rem; + box-shadow: 0.5rem 0.5rem 1rem rgba(0, 0, 0, 0.1); + + span:nth-child(1) { + color: #000; + + font-size: 1.5rem; + font-style: normal; + font-weight: 400; + line-height: 1.8rem; /* 120% */ + letter-spacing: -0.03rem; + } + span:nth-child(2) { + color: #686868; + + font-size: 1.05rem; + font-style: normal; + font-weight: 400; + line-height: 1.8rem; /* 171.429% */ + letter-spacing: -0.021rem; + } + } + + .container-apply__form-buttons { + display: flex; + justify-content: center; + gap: 1.35rem; + + .btn { + display: flex; + width: 12.15rem; + height: 3.75rem; + padding: 0.75rem; + justify-content: center; + align-items: center; + gap: 0.75rem; + flex-shrink: 0; + border-radius: 0.6rem; + color: var(--Light-Black, var(--text-color-2, #373f41)); + + font-size: 1.5rem; + font-style: normal; + font-weight: 400; + line-height: 1.35rem; /* 90% */ + letter-spacing: 0.015rem; + } + + .back { + border: 0.75px solid var(--main-color, #5877fc); + background: #fff; + } + + .submit { + background: var(--main-color, #5877fc); + color: #fff; + } + } + } +`; + +const S = { ApplySubmit }; + +export default S; diff --git a/src/pages/recruit/recruitDetailPage/steps/ApplySubmit.tsx b/src/pages/recruit/recruitDetailPage/steps/ApplySubmit.tsx new file mode 100644 index 00000000..8e802ead --- /dev/null +++ b/src/pages/recruit/recruitDetailPage/steps/ApplySubmit.tsx @@ -0,0 +1,60 @@ +import React from 'react'; +import S from './ApplySubmit.styled'; +import { useRecoilState } from 'recoil'; +import { applyStepState } from '../../../../atom'; + +const ApplySubmit = () => { + const [step, setStep] = useRecoilState(applyStepState); + const onClickStep = () => { + setStep(prev => prev - 1); + }; + return ( + +
+ 신청 정보 +
+
+ 이름 + {'송유진'} +
+
+ 등급 + {'A0'} +
+
+ 학교 + {'광운대학교'} +
+
+ 학번 + {'18'} +
+
+
+
+ 학과 + {'소프트웨어학부'} +
+
+ 이메일 + {'jiminni_01@kw.ac.kr'} +
+
+
+ 멤버들에게 위 정보가 공개됩니다. + 멤버들이 본인의 프로필을 열람할 수 있습니다. +
+
+ + +
+
+
+ ); +}; + +export default ApplySubmit; diff --git a/src/pages/recruit/recruitPage/RecruitPage.styled.ts b/src/pages/recruit/recruitPage/RecruitPage.styled.ts new file mode 100644 index 00000000..344d0517 --- /dev/null +++ b/src/pages/recruit/recruitPage/RecruitPage.styled.ts @@ -0,0 +1,201 @@ +import styled from 'styled-components'; + +const RecruitPage = styled.div` + width: clamp(45%, 108rem, 75%); + margin: 0 auto; + + .container-filter_area { + display: flex; + margin-right: 2.6rem; + margin-top: 2.3rem; + .area { + display: flex; + width: 8.5rem; + height: 3.5rem; + padding: 0.75rem; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 0.75rem; + flex-shrink: 0; + border-radius: 7.5rem; + background-color: #fcefaa; + color: #000; + + font-size: 1.5rem; + font-style: normal; + font-weight: 400; + line-height: 2.1rem; /* 100% */ + letter-spacing: 0.015rem; + cursor: pointer; + } + + .no { + background-color: #fff; + } + + .out { + background-color: #f3f5ff; + } + } + + .container-filter_menu { + display: flex; + gap: 1.65rem; + margin-top: 2.02rem; + } + .sep { + width: 0.3rem; + height: 3.225rem; + flex-shrink: 0; + background-color: #d9d9d9; + } + .dropdown-spec { + display: flex; + margin-left: 3rem; + gap: 1.65rem; + } + + hr { + margin-top: 2.32rem; + margin-bottom: 2.32rem; + background: #ababab; + height: 0.75px; + border: 0; + } + + .container-options { + display: flex; + justify-content: space-between; + align-items: center; + + .container-options__filters { + display: flex; + justify-content: flex-start; + align-items: center; + gap: 0.75rem; + + .filter { + display: flex; + width: 13.125rem; + height: 3.525rem; + padding: 0.75rem; + justify-content: center; + align-items: center; + gap: 0.75rem; + border-radius: 7.5rem; + background: var(--sub-color, #e0e6ff); + color: var(--Light-Black, var(--text-color-2, #373f41)); + + font-size: 1.5rem; + font-style: normal; + font-weight: 400; + line-height: 1.35rem; /* 90% */ + letter-spacing: 0.015rem; + cursor: pointer; + } + + .bookmark { + border-radius: 7.5rem; + background: #f7e8fb; + } + } + + .container-options__search { + display: inline-flex; + justify-content: flex-start; + padding: 0.8625rem 2rem 0.8625rem 1.2rem; + box-sizing: border-box; + height: 3.525rem; + align-items: center; + width: 25.175rem; + gap: 1.2rem; + border-radius: 7.5rem; + border: 0.75px solid #dcdcdc; + background: #f9f9f9; + color: var(--Light-Black, var(--text-color-2, #373f41)); + + font-size: 1.2rem; + font-style: normal; + font-weight: 400; + line-height: 1.35rem; /* 112.5% */ + letter-spacing: 0.015rem; + + svg { + display: flex; + align-items: center; + } + + input { + width: 20rem; + border: none; + outline: none; + font-size: 1.2rem; + background-color: transparent; + } + } + } + + .container-contents { + margin-top: 2rem; + display: flex; + flex-direction: column; + gap: 3.75rem; + margin-bottom: 5rem; + + .container-contents__row { + .container-subtitle { + display: flex; + justify-content: space-between; + align-items: center; + } + .subtitle { + color: var(--Light-Black, var(--text-color-2, #373f41)); + + font-size: 2rem; + font-style: normal; + font-weight: 500; + line-height: 4.2rem; + letter-spacing: 0.015rem; + } + + select { + border: none; + outline: none; + color: var(--Light-Black, var(--text-color-2, #373f41)); + + font-size: 1.3rem; + font-style: normal; + font-weight: 400; + line-height: 4.2rem; /* 280% */ + letter-spacing: 0.015rem; + } + } + .contents { + display: flex; + margin: 0 auto; + gap: 1.8rem; + } + + .container-contents__grid { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 1.8rem 1.8rem; + } + } + + .container-pagination { + display: flex; + justify-content: center; + align-items: center; + + ul { + list-style: none; + display: flex; + } + } +`; + +const S = { RecruitPage }; + +export default S; diff --git a/src/pages/recruit/recruitPage/RecruitPage.tsx b/src/pages/recruit/recruitPage/RecruitPage.tsx new file mode 100644 index 00000000..eaabeeb9 --- /dev/null +++ b/src/pages/recruit/recruitPage/RecruitPage.tsx @@ -0,0 +1,116 @@ +import React, { useState } from 'react'; +import { Dropdown, Subtitle, RecruitCard, Pagination } from '../../../components'; +import S from './RecruitPage.styled'; +import { SearchIcon } from '../../../assets'; + +const START_PAGE_NUM = 1; + +const RecruitPage = () => { + const postsNum = 150; + const [currentPage, setCurrentPage] = useState(START_PAGE_NUM); + const [isFiltered, setIsFiltered] = useState({ + isInside: true, + isOutside: false, + }); + console.log(currentPage); + const onClickHandler = (event: React.MouseEvent) => { + const target = event.currentTarget; + if (target.innerText === '교내') { + setIsFiltered({ isInside: true, isOutside: false }); + } + if (target.innerText === '교외') { + setIsFiltered({ isInside: false, isOutside: true }); + } + }; + + return ( + +
+
+
+ 교내 +
+
+ 교외 +
+
+
+ + +
+ + +
+
+
+
+
+
+
+
☑️ 수업만 보기
+
+
+
+ +
+
+ +
+
+
+
+
+
+
👀 내가 관심 있을 만한 구인 글
+
+ +
+
+
+ + + + +
+
+
+ 전체 구인 글 +
+ + + + + +
+
+
+
+
+ +
+
+ ); +}; + +export default RecruitPage; From 22623d6ffb69f47bfda3713aeb35f0f2d512d9f5 Mon Sep 17 00:00:00 2001 From: kimsuyeon0916 Date: Mon, 15 Apr 2024 20:18:03 +0900 Subject: [PATCH 003/116] =?UTF-8?q?style:=20#91=20=EC=9E=85=EB=A0=A5?= =?UTF-8?q?=ED=8F=BC=20=EA=B3=B5=ED=86=B5=20=EC=BB=B4=ED=8F=AC=EB=84=8C?= =?UTF-8?q?=ED=8A=B8=20=EB=82=B4=20=EA=B8=80=EC=9E=90=EC=88=98=20=EC=B9=B4?= =?UTF-8?q?=EC=9A=B4=ED=8C=85=20=EC=8A=A4=ED=83=80=EC=9D=BC=EB=A7=81=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/input/Input.styled.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/input/Input.styled.ts b/src/components/input/Input.styled.ts index 5e40dc07..8e5191a1 100644 --- a/src/components/input/Input.styled.ts +++ b/src/components/input/Input.styled.ts @@ -41,8 +41,9 @@ const InputContainer = styled.div` flex-direction: column; span { - margin-top: 0.4rem; - margin-left: auto; + position: absolute; + top: 5.4rem; + right: 0; color: var(--State-unactive, #8e8e8e); } From 99e41e95f72eaa37f39b3bec6ace3d445d41b485 Mon Sep 17 00:00:00 2001 From: kimsuyeon0916 Date: Mon, 15 Apr 2024 20:19:26 +0900 Subject: [PATCH 004/116] =?UTF-8?q?feat:=20#91=20=ED=8F=AC=ED=8A=B8?= =?UTF-8?q?=ED=8F=B4=EB=A6=AC=EC=98=A4=20=EC=83=81=EC=84=B8=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=ED=8E=B8=EC=A7=91=20=EB=B2=84=ED=8A=BC=20?= =?UTF-8?q?=ED=81=B4=EB=A6=AD=20=EC=8B=9C=EC=9D=98=20=ED=8F=AC=ED=8A=B8?= =?UTF-8?q?=ED=8F=B4=EB=A6=AC=EC=98=A4=20=ED=8E=B8=EC=A7=91=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20url=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/portfolio/details/PortfolioDetailsPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/portfolio/details/PortfolioDetailsPage.tsx b/src/pages/portfolio/details/PortfolioDetailsPage.tsx index 29739dc4..d3a22926 100644 --- a/src/pages/portfolio/details/PortfolioDetailsPage.tsx +++ b/src/pages/portfolio/details/PortfolioDetailsPage.tsx @@ -30,7 +30,7 @@ const PortfolioDetailsPage = () => { navigate(`/portfolio/${portfolioId}/edit`)} + handleClick={() => navigate(`/portfolio/edit/${portfolioId}`)} /> )} From 45cd61aa20693878c43f26644c64ba07bad77124 Mon Sep 17 00:00:00 2001 From: kimsuyeon0916 Date: Mon, 15 Apr 2024 20:31:05 +0900 Subject: [PATCH 005/116] =?UTF-8?q?style:=20#91=20=ED=8F=BC=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EB=B2=84=ED=8A=BC=20=EA=B3=B5=ED=86=B5=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=8A=A4=ED=83=80=EC=9D=BC?= =?UTF-8?q?=EB=A7=81=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 이전: padding: 1.7rem 0; - 이후: padding-bottom: 2.7rem; --- src/components/button/Button.styled.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/button/Button.styled.ts b/src/components/button/Button.styled.ts index bf0aa8b5..cd391779 100644 --- a/src/components/button/Button.styled.ts +++ b/src/components/button/Button.styled.ts @@ -61,7 +61,7 @@ const FormButtonLayout = styled.button` flex-direction: row; column-gap: 0.8rem; width: 100%; - padding: 1.7rem 0; + padding-bottom: 2.7rem; align-items: center; color: var(--Text-textColor2, var(--text-color-2, #373f41)); cursor: pointer; From 7f1644ecfde49939ff83a5c4f6cfef3299225eaf Mon Sep 17 00:00:00 2001 From: kimsuyeon0916 Date: Tue, 16 Apr 2024 03:54:42 +0900 Subject: [PATCH 006/116] =?UTF-8?q?feat:=20#91=20=ED=94=84=EB=A1=9C?= =?UTF-8?q?=ED=95=84=20=ED=8E=B8=EC=A7=91=20=ED=8E=98=EC=9D=B4=EC=A7=80=20?= =?UTF-8?q?=EB=82=B4,=20=EB=A7=81=ED=81=AC=20=EB=AC=B4=ED=95=9C=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20=EB=B0=A9=EC=A7=80=20=EB=B0=8F=20css=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/profile/edit/ProfileEditPage.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pages/profile/edit/ProfileEditPage.tsx b/src/pages/profile/edit/ProfileEditPage.tsx index c4235cf2..799fd131 100644 --- a/src/pages/profile/edit/ProfileEditPage.tsx +++ b/src/pages/profile/edit/ProfileEditPage.tsx @@ -183,7 +183,7 @@ const ProfileEditPage = () => { }); const addLink = (index: number) => { - if (index === -1 || getValues(`links.${index}.url`)) { + if (index === -1 || getValues(`links.0.url`)) { prependLink({ description: 'Link', url: '' }); } }; @@ -429,8 +429,8 @@ const ProfileEditPage = () => { 수상/활동 {DESCRIPTION.awards} + addAward(awards.length - 1)} /> - addAward(awards.length - 1)} /> {awards?.map((award, index) => ( @@ -463,8 +463,8 @@ const ProfileEditPage = () => { 링크 {DESCRIPTION.links} + addLink(links.length - 1)} /> - addLink(links.length - 1)} /> {links?.map((link, index) => ( Date: Tue, 16 Apr 2024 04:01:00 +0900 Subject: [PATCH 007/116] =?UTF-8?q?feat:=20#91=20=ED=8F=AC=ED=8A=B8?= =?UTF-8?q?=ED=8F=B4=EB=A6=AC=EC=98=A4=20=EA=B4=80=EB=A0=A8=20=EC=9C=A0?= =?UTF-8?q?=ED=9A=A8=EC=84=B1=20=EA=B2=80=EC=82=AC=20=EC=83=81=EC=88=98?= =?UTF-8?q?=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- validation.ts | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/validation.ts b/validation.ts index 34d3fe86..b9754097 100644 --- a/validation.ts +++ b/validation.ts @@ -21,6 +21,33 @@ export const INPUT_VALIDATION = { introduction: { maxLength: 20, }, + portfolioImage: { + required: '포트폴리오를 대표할 이미지를 업로드해주세요', + }, + portfolioTitle: { + required: '포트폴리오 제목을 작성해주세요', + }, + portfolioDescription: { + required: '포트폴리오 한줄 소개를 작성해주세요', + }, + field: { + required: '분야를 선택해주세요', + }, + role: { + required: '분야를 선택해주세요', + }, + startDate: { + required: '진행 시작일을 설정해주세요', + }, + endDate: { + required: '진행 마감일을 설정해주세요', + }, + proceedType: { + required: '진행방식을 설정해주세요', + }, + skill: { + required: '스킬을 추가해주세요', + }, }; export const TEXTAREA_VALIDATION = { From abd7730b9ee668377277d9b23422663c73573d19 Mon Sep 17 00:00:00 2001 From: kimsuyeon0916 Date: Tue, 16 Apr 2024 04:22:38 +0900 Subject: [PATCH 008/116] =?UTF-8?q?feat:=20#91=20=ED=8F=AC=ED=8A=B8?= =?UTF-8?q?=ED=8F=B4=EB=A6=AC=EC=98=A4=20=ED=8E=B8=EC=A7=91=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20UI=20=EC=83=81=EC=88=98=20=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=84=B0=20=EC=A0=9C=EC=9E=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/index.ts | 2 + src/pages/portfolio/edit/portfolioEditData.ts | 68 +++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 src/pages/portfolio/edit/portfolioEditData.ts diff --git a/src/pages/index.ts b/src/pages/index.ts index 0d72ae3c..80ad2c75 100644 --- a/src/pages/index.ts +++ b/src/pages/index.ts @@ -26,6 +26,7 @@ import ProfileEditPage from './profile/edit/ProfileEditPage'; import PROFILE_EDIT_DATA from './profile/edit/ProfileEditData'; import { portfolioData } from './portfolio/portfolioData'; import PortfolioDetailsPage from './portfolio/details/PortfolioDetailsPage'; +import PORTFOLIO_EDIT_DATA from './portfolio/edit/portfolioEditData'; export { MainPage, @@ -57,4 +58,5 @@ export { PROFILE_EDIT_DATA, portfolioData, PortfolioDetailsPage, + PORTFOLIO_EDIT_DATA, }; diff --git a/src/pages/portfolio/edit/portfolioEditData.ts b/src/pages/portfolio/edit/portfolioEditData.ts new file mode 100644 index 00000000..a58e4570 --- /dev/null +++ b/src/pages/portfolio/edit/portfolioEditData.ts @@ -0,0 +1,68 @@ +import { ArrowBottom, ArrowTop, Search } from '../../../assets'; +import { INPUT_VALIDATION } from '../../../../validation'; + +const title = { + label: '포트폴리오 제목', + type: 'text', + placeholder: '20자 이내로 제목을 작성해주세요', + name: 'title', + validation: INPUT_VALIDATION.portfolioTitle, + maxLength: 20, +}; + +const description = { + label: '포트폴리오 한줄 소개', + type: 'text', + placeholder: '20자 이내로 한줄 소개를 작성해주세요', + name: 'description', + validation: INPUT_VALIDATION.portfolioDescription, + maxLength: 20, +}; + +const field = { + label: '분야', + type: 'text', + placeholder: '분야', + name: 'field', + validation: INPUT_VALIDATION.field, + icon: { + default: ArrowBottom, + focus: ArrowTop, + arrow: 'right', + }, +}; + +const role = { + label: '역할', + type: 'text', + placeholder: '역할', + name: 'role', + validation: INPUT_VALIDATION.role, + icon: { + default: ArrowBottom, + focus: ArrowTop, + arrow: 'right', + }, +}; + +const skills = { + label: '스킬', + type: 'text', + placeholder: '보유 스킬을 검색해주세요', + name: 'skills', + validation: INPUT_VALIDATION.skill, + icon: { + default: Search, + arrow: 'right', + }, +}; + +const PORTFOLIO_EDIT_DATA = { + title, + description, + field, + role, + skills, +}; + +export default PORTFOLIO_EDIT_DATA; From 4798722096c0ef94da66b10bedb7ff55fad2d538 Mon Sep 17 00:00:00 2001 From: kimsuyeon0916 Date: Tue, 16 Apr 2024 15:53:55 +0900 Subject: [PATCH 009/116] =?UTF-8?q?feat:=20#91=20react-quill=20modules=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - font, image 설정 불가능하도록 설정 --- src/utils/editorModule.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/editorModule.ts b/src/utils/editorModule.ts index c5cc3d04..0c91b7b7 100644 --- a/src/utils/editorModule.ts +++ b/src/utils/editorModule.ts @@ -2,7 +2,7 @@ export const modules = { toolbar: { container: [ [{ header: [1, 2, 3, 4, 5, 6, false] }], - [{ font: [] }], + // [{ font: [] }], [{ align: [] }], ['bold', 'italic', 'underline', 'strike', 'blockquote'], [{ list: 'ordered' }, { list: 'bullet' }, 'link'], @@ -49,7 +49,7 @@ export const modules = { }, { background: [] }, ], - ['image'], + // ['image'], ['clean'], ], }, From 9136770c4f18d83628b2b9f3e282e5c8944a44f9 Mon Sep 17 00:00:00 2001 From: kimsuyeon0916 Date: Tue, 16 Apr 2024 16:31:51 +0900 Subject: [PATCH 010/116] =?UTF-8?q?style:=20#91=20=EC=9E=85=EB=A0=A5?= =?UTF-8?q?=ED=8F=BC=20=EA=B3=B5=ED=86=B5=20=EC=BB=B4=ED=8F=AC=EB=84=8C?= =?UTF-8?q?=ED=8A=B8=20=EB=82=B4=20=EB=9D=BC=EB=B2=A8=20=EC=8A=A4=ED=83=80?= =?UTF-8?q?=EC=9D=BC=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/input/Input.styled.ts | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/components/input/Input.styled.ts b/src/components/input/Input.styled.ts index 8e5191a1..6de2f7f7 100644 --- a/src/components/input/Input.styled.ts +++ b/src/components/input/Input.styled.ts @@ -8,7 +8,7 @@ interface InputStyle { invalid?: boolean; } -const InputLabel = styled.label<{ $width?: string }>` +const InputLayout = styled.label<{ $width?: string }>` min-width: 0; display: flex; flex-direction: column; @@ -24,8 +24,6 @@ const InputLabel = styled.label<{ $width?: string }>` letter-spacing: 0.0032rem; h6 { - margin-bottom: 0.8rem; - /* Body/body2/semibold */ font-size: 1.4rem; font-style: normal; @@ -35,6 +33,17 @@ const InputLabel = styled.label<{ $width?: string }>` } `; +const InputLabel = styled.h6<{ $required?: boolean }>` + margin-bottom: 0.8rem; + + ${props => + props.$required && + `&:: after { + content: ' *'; + color: #f85858; + }`} +`; + const InputContainer = styled.div` position: relative; display: flex; @@ -106,6 +115,6 @@ const Input = styled.input` } `; -const S = { InputLabel, InputContainer, Input }; +const S = { InputLayout, InputLabel, InputContainer, Input }; export default S; From 1a838beb6ec97b411c6c69d8f01c2921e1178762 Mon Sep 17 00:00:00 2001 From: kimsuyeon0916 Date: Tue, 16 Apr 2024 16:32:34 +0900 Subject: [PATCH 011/116] =?UTF-8?q?feat:=20#91=20=EC=9E=85=EB=A0=A5?= =?UTF-8?q?=ED=8F=BC=20=EA=B3=B5=ED=86=B5=20=EC=BB=B4=ED=8F=AC=EB=84=8C?= =?UTF-8?q?=ED=8A=B8=20=EB=82=B4=20=EB=9D=BC=EB=B2=A8=20props=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/input/Input.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/components/input/Input.tsx b/src/components/input/Input.tsx index 1143ccf8..07d12240 100644 --- a/src/components/input/Input.tsx +++ b/src/components/input/Input.tsx @@ -54,8 +54,10 @@ const Input = ({ const { ref, ...rest } = register(name as Path, validation); return ( - - {label &&
{label}
} + + {label && ( + {label} + )} ({ )} -
+ ); }; From 40c4fe7ed115c1578518745e7deaed5a006c15bc Mon Sep 17 00:00:00 2001 From: kimsuyeon0916 Date: Tue, 16 Apr 2024 22:23:55 +0900 Subject: [PATCH 012/116] =?UTF-8?q?feat:=20#91=20=EC=82=AC=EC=9D=B4?= =?UTF-8?q?=ED=81=B4=20=EC=95=84=EC=9D=B4=EC=BD=98=20svg=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/assets/Refresh.svg | 3 +++ src/assets/index.ts | 2 ++ 2 files changed, 5 insertions(+) create mode 100644 src/assets/Refresh.svg diff --git a/src/assets/Refresh.svg b/src/assets/Refresh.svg new file mode 100644 index 00000000..9f20dcb9 --- /dev/null +++ b/src/assets/Refresh.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/index.ts b/src/assets/index.ts index 640b7556..06e6b3e8 100644 --- a/src/assets/index.ts +++ b/src/assets/index.ts @@ -34,6 +34,7 @@ import Test1 from './Test1.png'; import Test2 from './Test2.png'; import Test3 from './Test3.png'; import Test4 from './Test4.png'; +import Refresh from './Refresh.svg'; export { Exit, @@ -72,4 +73,5 @@ export { Test2, Test3, Test4, + Refresh, }; From cd6453395281db7471fde69471409195260772ea Mon Sep 17 00:00:00 2001 From: kimsuyeon0916 Date: Tue, 16 Apr 2024 22:29:40 +0900 Subject: [PATCH 013/116] =?UTF-8?q?style:=20#91=20=EA=B8=B0=EB=B3=B8/?= =?UTF-8?q?=EC=A3=BC=EC=9A=94=20=EB=B2=84=ED=8A=BC=20=EA=B3=B5=ED=86=B5=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=8A=A4=ED=83=80?= =?UTF-8?q?=EC=9D=BC=EB=A7=81=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/button/Button.styled.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/button/Button.styled.ts b/src/components/button/Button.styled.ts index cd391779..9d888073 100644 --- a/src/components/button/Button.styled.ts +++ b/src/components/button/Button.styled.ts @@ -24,6 +24,7 @@ const DefaultButtonLayout = styled.button<{ $small?: boolean }>` padding: ${props => (props.$small ? '1.2rem 2rem' : '1.2rem 3.2rem')}; justify-content: center; align-items: center; + column-gap: 0.75rem; border: 1px solid var(--ButtonColors-Default-outline-defaultLine, #e3e3e3); border-radius: 0.6rem; color: var(--text-color-2, #373f41); From fa98886fca4d59739bfb7a81119a055e6d240ace Mon Sep 17 00:00:00 2001 From: kimsuyeon0916 Date: Tue, 16 Apr 2024 22:31:31 +0900 Subject: [PATCH 014/116] =?UTF-8?q?feat:=20#91=20=EA=B8=B0=EB=B3=B8/?= =?UTF-8?q?=EC=A3=BC=EC=9A=94=20=EB=B2=84=ED=8A=BC=20=EA=B3=B5=ED=86=B5=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EB=82=B4=20=EC=95=84?= =?UTF-8?q?=EC=9D=B4=EC=BD=98=20=EC=B6=94=EA=B0=80=20=EA=B0=80=EB=8A=A5?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20=EB=A1=9C=EC=A7=81=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/button/DefaultBtn.tsx | 4 +++- src/components/button/PrimaryBtn.tsx | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/components/button/DefaultBtn.tsx b/src/components/button/DefaultBtn.tsx index a6462550..0c669445 100644 --- a/src/components/button/DefaultBtn.tsx +++ b/src/components/button/DefaultBtn.tsx @@ -4,13 +4,15 @@ import S from './Button.styled'; interface Button { type: 'button' | 'submit'; title: string; + icon?: string; small?: boolean; handleClick?: React.MouseEventHandler; } -const DefaultBtn = ({ type, title, small, handleClick }: Button) => { +const DefaultBtn = ({ type, title, icon, small, handleClick }: Button) => { return ( + {icon && 아이콘} {title} ); diff --git a/src/components/button/PrimaryBtn.tsx b/src/components/button/PrimaryBtn.tsx index 3d31545d..9f95e9b8 100644 --- a/src/components/button/PrimaryBtn.tsx +++ b/src/components/button/PrimaryBtn.tsx @@ -4,13 +4,15 @@ import S from './Button.styled'; interface Button { type: 'button' | 'submit'; title: string; + icon?: string; small?: boolean; handleClick?: React.MouseEventHandler; } -const PrimaryBtn = ({ type, title, small, handleClick }: Button) => { +const PrimaryBtn = ({ type, title, icon, small, handleClick }: Button) => { return ( + {icon && 아이콘} {title} ); From 7b8eb93af20cda0ca52ef1fcc1994bf7a8821d11 Mon Sep 17 00:00:00 2001 From: kimsuyeon0916 Date: Thu, 18 Apr 2024 14:52:30 +0900 Subject: [PATCH 015/116] =?UTF-8?q?feat:=20#91=20imageNameListState,=20ima?= =?UTF-8?q?geSrcListState,=20binaryImageListState=20atom=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/atom.tsx | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/atom.tsx b/src/atom.tsx index 0074ff75..d09d7149 100644 --- a/src/atom.tsx +++ b/src/atom.tsx @@ -127,3 +127,18 @@ export const imageNameState = atom({ key: 'imageNameState', default: '', }); + +export const imageNameListState = atom({ + key: 'imageNameListState', + default: [], +}); + +export const imageSrcListState = atom({ + key: 'imageSrcListState', + default: [], +}); + +export const binaryImageListState = atom({ + key: 'binaryImageListState', + default: [], +}); From bb8417cf478d010702e6185c9c7b2af21783e80a Mon Sep 17 00:00:00 2001 From: kimsuyeon0916 Date: Thu, 18 Apr 2024 14:53:26 +0900 Subject: [PATCH 016/116] =?UTF-8?q?style:=20#91=20=ED=8F=AC=ED=8A=B8?= =?UTF-8?q?=ED=8F=B4=EB=A6=AC=EC=98=A4=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=EC=97=85=EB=A1=9C=EB=93=9C=20=EA=B3=B5=ED=86=B5=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=8A=A4=ED=83=80=EC=9D=BC?= =?UTF-8?q?=EB=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../\bimage/PortfolioImageUpload.styled.ts" | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 "src/components/portfolio/\bimage/PortfolioImageUpload.styled.ts" diff --git "a/src/components/portfolio/\bimage/PortfolioImageUpload.styled.ts" "b/src/components/portfolio/\bimage/PortfolioImageUpload.styled.ts" new file mode 100644 index 00000000..53d19307 --- /dev/null +++ "b/src/components/portfolio/\bimage/PortfolioImageUpload.styled.ts" @@ -0,0 +1,76 @@ +import styled from 'styled-components'; + +const PortfolioImageUploadLayout = styled.article` + display: flex; + flex-direction: column; +`; + +const PortfolioImageGrid = styled.div` + display: grid; + grid-template-columns: repeat(auto-fill, minmax(24.4rem, 1fr)); + grid-auto-rows: minmax(13.7rem, auto); + row-gap: 1.6rem; + column-gap: 1.6rem; +`; + +const PortfolioImageUpload = styled.div` + position: relative; + display: flex; + align-items: center; + justify-content: center; + border-radius: 1rem; + border: 0.1rem solid var(--Form-border-default, #8e8e8e); + background: var(--Grayscale-200, #f6f6f6); + color: var(--Text-textColor2, var(--text-color-2, #373f41)); + cursor: pointer; + aspect-ratio: 183 / 103; // 포트폴리오 비율 + + /* Body/body1/semibold */ + font-size: 1.6rem; + font-style: normal; + font-weight: 600; + line-height: 1.9rem; + letter-spacing: 0.0032rem; + + small { + color: #8e8e8e; + + /* Text/t2 */ + font-size: 1.2rem; + font-style: normal; + font-weight: 500; + line-height: 1.4rem; + letter-spacing: 0.0024rem; + } +`; + +const PortfolioImageUploadRow = styled.div` + display: flex; + flex-direction: row; + column-gap: 0.6rem; +`; + +const PortfolioImageUploadColumn = styled.div` + display: flex; + align-items: center; + flex-direction: column; + row-gap: 0.8rem; +`; + +const PortfolioImageInput = styled.input` + position: absolute; + width: 100%; + height: 100%; + display: none; +`; + +const S = { + PortfolioImageUploadLayout, + PortfolioImageGrid, + PortfolioImageUpload, + PortfolioImageUploadRow, + PortfolioImageUploadColumn, + PortfolioImageInput, +}; + +export default S; From 6b949840945e5ce33027124c16c42e171508fa42 Mon Sep 17 00:00:00 2001 From: kimsuyeon0916 Date: Thu, 18 Apr 2024 14:54:55 +0900 Subject: [PATCH 017/116] =?UTF-8?q?feat:=20#91=20=ED=8F=AC=ED=8A=B8?= =?UTF-8?q?=ED=8F=B4=EB=A6=AC=EC=98=A4=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=EC=97=85=EB=A1=9C=EB=93=9C=20=EA=B3=B5=ED=86=B5=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 --- src/components/index.ts | 2 + .../\bimage/PortfolioImageUpload.tsx" | 89 +++++++++++++++++++ 2 files changed, 91 insertions(+) create mode 100644 "src/components/portfolio/\bimage/PortfolioImageUpload.tsx" diff --git a/src/components/index.ts b/src/components/index.ts index 860f4f5f..b1e56586 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -116,6 +116,7 @@ import LinkDetails from './link/details/LinkDetails'; import PortfolioInformation from './portfolio/information/PortfolioInformation'; import PortfolioList from './portfolio/list/PortfolioList'; import ImageCarousel from './carousel/ImageCarousel'; +import PortfolioImageUpload from './portfolio/\bimage/PortfolioImageUpload'; export { Header, @@ -227,4 +228,5 @@ export { PortfolioInformation, PortfolioList, ImageCarousel, + PortfolioImageUpload, }; diff --git "a/src/components/portfolio/\bimage/PortfolioImageUpload.tsx" "b/src/components/portfolio/\bimage/PortfolioImageUpload.tsx" new file mode 100644 index 00000000..31858997 --- /dev/null +++ "b/src/components/portfolio/\bimage/PortfolioImageUpload.tsx" @@ -0,0 +1,89 @@ +import React, { useRef, useState } from 'react'; +import S from './PortfolioImageUpload.styled'; +import { Plus } from '../../../assets'; +import { useRecoilState } from 'recoil'; +import { binaryImageListState, imageNameListState, imageSrcListState } from '../../../atom'; +import PortfolioCard from '../card/PortfolioCard'; + +const MAX_IMAGE_SIZE_BYTES = 30 * 1024 * 1024; // 30MB +const MAX_IMAGE_COUNT = 15; + +const PortfolioImageUpload = (portfolioId?: { portfolioId?: string }) => { + const inputRef = useRef(null); + const addImageList = () => { + inputRef.current?.click(); + }; + + // portfolioId 존재하는 경우에 presignedUrl API 호출 + + const [imageNameList, setImageNameList] = useRecoilState(imageNameListState); // 추후에 nameList 받아와서 초기화 + const [imageSrcList, setImageSrcList] = useRecoilState(imageSrcListState); // 추후에 urlList 받아와서 초기화 + const [binaryImageList, setBinaryImageList] = useRecoilState(binaryImageListState); // 추후에 binaryList 받아와서 초기화 + + const changeImageList = (event: React.BaseSyntheticEvent) => { + const uploadImageList = event.target?.files; + // 이미지 개수 제한 + for (let i = 0; i < uploadImageList.length && imageNameList.length + i < MAX_IMAGE_COUNT; i++) { + // 중복된 이름 제한 + if ( + imageNameList.find(imageName => imageName === uploadImageList[i].name) || + [...uploadImageList].find( + (image, index) => index !== i && image.name === uploadImageList[i].name + ) + ) { + continue; + } + // 이미지 용량 제한 + if (uploadImageList[i].size > MAX_IMAGE_SIZE_BYTES) { + continue; + } + setImageNameList(prev => [...prev, uploadImageList[i].name]); + + const urlReader = new FileReader(); + urlReader.readAsDataURL(uploadImageList[i]); + urlReader.onload = () => setImageSrcList(prev => [...prev, urlReader.result] as string[]); + + const binaryReader = new FileReader(); + binaryReader.readAsArrayBuffer(uploadImageList[i]); + binaryReader.onload = () => + setBinaryImageList(prev => [...prev, binaryReader.result] as ArrayBuffer[]); + } + }; + + // 이미지 압축 및 리사이징 적용 + + return ( + + + {[...imageSrcList].map((imageSrc, index) => ( + + ))} + {/* 이미지 추가 버튼 */} + + + + 포트폴리오 이미지 추가 + 이미지 추가 (최대 15장) + + (1920px X 1080px) + + + + + + ); +}; + +export default PortfolioImageUpload; From b3b2c53ba4cb27009c214adefa61bd1c55915a62 Mon Sep 17 00:00:00 2001 From: kimsuyeon0916 Date: Thu, 18 Apr 2024 14:59:08 +0900 Subject: [PATCH 018/116] =?UTF-8?q?style:=20#91=20=ED=8F=AC=ED=8A=B8?= =?UTF-8?q?=ED=8F=B4=EB=A6=AC=EC=98=A4=20=EC=B9=B4=EB=93=9C=20=EA=B3=B5?= =?UTF-8?q?=ED=86=B5=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=8A=A4?= =?UTF-8?q?=ED=83=80=EC=9D=BC=EB=A7=81=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 편집 시, 테두리 스타일링 추가 --- src/components/portfolio/card/PortfolioCard.styled.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/portfolio/card/PortfolioCard.styled.ts b/src/components/portfolio/card/PortfolioCard.styled.ts index d2a4cfca..97ec7739 100644 --- a/src/components/portfolio/card/PortfolioCard.styled.ts +++ b/src/components/portfolio/card/PortfolioCard.styled.ts @@ -8,10 +8,11 @@ const PortfolioCardLayout = styled.article` cursor: pointer; `; -const PortfolioCardBox = styled.div<{ $url?: string }>` +const PortfolioCardBox = styled.div<{ $isEditable?: boolean }>` position: relative; - border-radius: 0.75rem; + border-radius: 1rem; + border: ${props => props.$isEditable && '0.1rem solid var(--Form-border-default, #e3e3e3)'}; overflow: hidden; aspect-ratio: 183 / 103; // 포트폴리오 비율 From f9a6f96dd714e4b873875089c88a043ef2e69a53 Mon Sep 17 00:00:00 2001 From: kimsuyeon0916 Date: Thu, 18 Apr 2024 15:15:51 +0900 Subject: [PATCH 019/116] =?UTF-8?q?style:=20#91=20=ED=8F=AC=ED=8A=B8?= =?UTF-8?q?=ED=8F=B4=EB=A6=AC=EC=98=A4=20=EC=B9=B4=EB=93=9C=20=EA=B3=B5?= =?UTF-8?q?=ED=86=B5=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EB=82=B4=20?= =?UTF-8?q?=20=EB=A9=94=EC=9D=B8=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20=ED=83=9C?= =?UTF-8?q?=EA=B7=B8=20=EC=8A=A4=ED=83=80=EC=9D=BC=EB=A7=81=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/portfolio/card/PortfolioCard.styled.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/components/portfolio/card/PortfolioCard.styled.ts b/src/components/portfolio/card/PortfolioCard.styled.ts index 97ec7739..3489b29d 100644 --- a/src/components/portfolio/card/PortfolioCard.styled.ts +++ b/src/components/portfolio/card/PortfolioCard.styled.ts @@ -50,7 +50,7 @@ const PortfolioTagRow = styled.div` column-gap: 0.8rem; `; -const PortfolioCardTag = styled.span<{ $color: string }>` +const PortfolioCardTag = styled.span<{ $color?: string }>` align-items: center; display: flex; @@ -67,6 +67,13 @@ const PortfolioCardTag = styled.span<{ $color: string }>` font-weight: 500; line-height: 1.4rem; /* 116.667% */ letter-spacing: 0.0024rem; + + &.main-image-tag { + padding: 0.6rem 0.8rem; + border-radius: 1.5rem; + border: 1px solid var(--Purplescale-200, #e0e6ff); + background: var(--Grayscale-000, #fff); + } `; const PortfolioCardButton = styled.button<{ $checked?: boolean }>` From a65c75ce6a0166ced2ec4a999f86026219d5261a Mon Sep 17 00:00:00 2001 From: kimsuyeon0916 Date: Thu, 18 Apr 2024 15:18:22 +0900 Subject: [PATCH 020/116] =?UTF-8?q?feat:=20#91=20=ED=8F=AC=ED=8A=B8?= =?UTF-8?q?=ED=8F=B4=EB=A6=AC=EC=98=A4=20=EC=B9=B4=EB=93=9C=20=EA=B3=B5?= =?UTF-8?q?=ED=86=B5=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=ED=8C=8C?= =?UTF-8?q?=EB=9D=BC=EB=AF=B8=ED=84=B0=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - isMainImage 옵셔널 파라미터 추가 - id, title, field, role을 옵셔널 파라미터로 변경 --- .../portfolio/card/PortfolioCard.tsx | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/components/portfolio/card/PortfolioCard.tsx b/src/components/portfolio/card/PortfolioCard.tsx index 529afd99..7bd215c9 100644 --- a/src/components/portfolio/card/PortfolioCard.tsx +++ b/src/components/portfolio/card/PortfolioCard.tsx @@ -4,11 +4,12 @@ import { useNavigate } from 'react-router'; import { DefaultPortfolioImage } from '../../../assets'; interface PortfolioCard { - id: string; - title: string; + id?: string; + title?: string; mainImageUrl?: string; - field: string; - role: string; + field?: string; + role?: string; + isMainImage?: boolean; isEditable?: boolean; clickNumber?: number; handleClick?: (id: string) => void; @@ -20,6 +21,7 @@ const PortfolioCard = ({ mainImageUrl, field, role, + isMainImage, isEditable, clickNumber, handleClick, @@ -28,28 +30,29 @@ const PortfolioCard = ({ return ( (isEditable ? handleClick?.(id) : navigate(`/portfolio/${id}`))} + onClick={() => (isEditable ? () => id && handleClick?.(id) : navigate(`/portfolio/${id}`))} > - + - {field} - {role} + {field && {field}} + {role && {role}} + {isMainImage && 메인} {isEditable && ( handleClick?.(id)} + onClick={() => id && handleClick?.(id)} > {clickNumber !== 0 && clickNumber} )} - {title} + {title && {title}} ); }; From d51f95bd090f82f1089bc15c7136788d48738135 Mon Sep 17 00:00:00 2001 From: kimsuyeon0916 Date: Thu, 18 Apr 2024 15:21:39 +0900 Subject: [PATCH 021/116] =?UTF-8?q?feat:=20#91=20=ED=8F=AC=ED=8A=B8?= =?UTF-8?q?=ED=8F=B4=EB=A6=AC=EC=98=A4=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=EC=97=85=EB=A1=9C=EB=93=9C=20=EA=B3=B5=ED=86=B5=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EB=82=B4=20PortfolioCard=20isMai?= =?UTF-8?q?nImage=20=ED=94=84=EB=A1=9C=ED=8D=BC=ED=8B=B0=20=EC=A0=84?= =?UTF-8?q?=EB=8B=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- "src/components/portfolio/\bimage/PortfolioImageUpload.tsx" | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git "a/src/components/portfolio/\bimage/PortfolioImageUpload.tsx" "b/src/components/portfolio/\bimage/PortfolioImageUpload.tsx" index 31858997..dc7810e2 100644 --- "a/src/components/portfolio/\bimage/PortfolioImageUpload.tsx" +++ "b/src/components/portfolio/\bimage/PortfolioImageUpload.tsx" @@ -58,7 +58,7 @@ const PortfolioImageUpload = (portfolioId?: { portfolioId?: string }) => { {[...imageSrcList].map((imageSrc, index) => ( Date: Thu, 18 Apr 2024 15:24:32 +0900 Subject: [PATCH 022/116] =?UTF-8?q?feat:=20#91=20MuiDatepicker=20=EA=B3=B5?= =?UTF-8?q?=ED=86=B5=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EB=82=B4=20?= =?UTF-8?q?DatePicker=20value=20=ED=94=84=EB=A1=9C=ED=8D=BC=ED=8B=B0=20?= =?UTF-8?q?=EC=A0=84=EB=8B=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/muiDatepicker/MuiDatepicker.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/muiDatepicker/MuiDatepicker.tsx b/src/components/muiDatepicker/MuiDatepicker.tsx index 4fbcfce5..37c59806 100644 --- a/src/components/muiDatepicker/MuiDatepicker.tsx +++ b/src/components/muiDatepicker/MuiDatepicker.tsx @@ -19,8 +19,9 @@ const MuiDatepicker = ({ name, control }: Date) => { ( + render={({ field: { onChange, ref, value } }) => ( Date: Thu, 18 Apr 2024 15:25:18 +0900 Subject: [PATCH 023/116] =?UTF-8?q?fix:=20#91=20=ED=8F=AC=ED=8A=B8?= =?UTF-8?q?=ED=8F=B4=EB=A6=AC=EC=98=A4=20=EC=A0=95=EB=B3=B4=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EB=82=B4=20startDate,=20endDate?= =?UTF-8?q?=20undefined=20=ED=83=80=EC=9E=85=20=EC=97=90=EB=9F=AC=20?= =?UTF-8?q?=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/portfolio/information/PortfolioInformation.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/portfolio/information/PortfolioInformation.tsx b/src/components/portfolio/information/PortfolioInformation.tsx index 34763a70..a4ab933d 100644 --- a/src/components/portfolio/information/PortfolioInformation.tsx +++ b/src/components/portfolio/information/PortfolioInformation.tsx @@ -34,8 +34,8 @@ const PortfolioInformation = ({
진행기간
- {format(new Date(startDate), 'yy년 MM월 dd일')} -{' '} - {format(new Date(endDate), 'yy년 MM월 dd일')} + {startDate && format(new Date(startDate), 'yy년 MM월 dd일')} -{' '} + {endDate && format(new Date(endDate), 'yy년 MM월 dd일')}
진행방식
From bb7d58766bbf56ec277306595ba6ea95de03d19e Mon Sep 17 00:00:00 2001 From: kimsuyeon_0916 Date: Sat, 20 Apr 2024 15:48:52 +0900 Subject: [PATCH 024/116] =?UTF-8?q?style:=20#91=20=ED=8F=AC=ED=8A=B8?= =?UTF-8?q?=ED=8F=B4=EB=A6=AC=EC=98=A4=20=ED=8E=B8=EC=A7=91=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EC=8A=A4=ED=83=80=EC=9D=BC=EB=A7=81=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../portfolio/edit/PortfolioEdit.styled.ts | 201 ++++++++++++++++++ 1 file changed, 201 insertions(+) create mode 100644 src/pages/portfolio/edit/PortfolioEdit.styled.ts diff --git a/src/pages/portfolio/edit/PortfolioEdit.styled.ts b/src/pages/portfolio/edit/PortfolioEdit.styled.ts new file mode 100644 index 00000000..249ea2b4 --- /dev/null +++ b/src/pages/portfolio/edit/PortfolioEdit.styled.ts @@ -0,0 +1,201 @@ +import styled from 'styled-components'; +import ReactQuill from 'react-quill'; + +interface ProfileBoxStyle { + $gap?: string; + $width?: string; +} + +const PortfolioEditLayout = styled.form` + display: flex; + flex-direction: column; + margin: 0 auto; + margin-bottom: 15rem; + width: clamp(45%, 96rem, 75%); // width: 96rem; + + color: var(--Light-Black, #373f41); + + /* Body/body1/medium */ + font-size: 1.6rem; + font-style: normal; + font-weight: 600; + line-height: 1.9rem; + letter-spacing: 0.0032rem; + + h2 { + color: var(--Text-textColor1, #151515); + + /* Headline/h2 */ + font-size: 2.4rem; + font-weight: 700; + line-height: 2.9rem; /* 120.833% */ + letter-spacing: 0.0048rem; + } + + h4 { + /* Headline/h4 */ + font-size: 1.8rem; + font-style: normal; + font-weight: 600; + line-height: 2.1rem; /* 116.667% */ + letter-spacing: 0.0036rem; + } + + h6 { + /* Body/body2/semibold */ + font-size: 1.4rem; + font-style: normal; + font-weight: 600; + line-height: 1.7rem; /* 121.429% */ + letter-spacing: 0.0028rem; + } + + /* 수평선 */ + hr { + all: unset; + margin-top: 4rem; + height: 0.075rem; + background: #e3e3e3; + } +`; + +const PortfolioEditColumn = styled.div` + display: flex; + flex-direction: column; + row-gap: ${props => props.$gap}; + ${props => (props.$width ? `width: ${props.$width}` : `flex: 1;`)}; +`; + +const PortfolioEditRow = styled.div` + display: flex; + flex-direction: row; + column-gap: ${props => props.$gap}; + ${props => (props.$width ? `width: ${props.$width}` : `flex: 1;`)}; + + /* 반응형 대비 */ + flex-wrap: wrap; + row-gap: ${props => props.$gap}; +`; + +const PortfolioEditHeader = styled.header` + display: flex; + flex-direction: column; + margin-top: 8rem; + + /* 반응형 대비 */ + flex-wrap: wrap; + column-gap: 2.4rem; + + h2 { + margin-bottom: 1.2rem; + } + + hr { + margin-top: 2rem; + background: #000000; + } +`; + +const PortfolioEditTitle = styled.h4` + display: flex; +`; + +const PortfolioEditLabel = styled.h6<{ $required?: boolean }>` + margin-bottom: 0.8rem; + + ${props => + props.$required && + `&:: after { + content: ' *'; + color: #f85858; + }`} +`; + +const PortfolioEditArticle = styled.article` + display: flex; + flex-direction: row; + justify-content: space-between; + white-space: pre-wrap; // 줄바꿈 + + /* 반응형 대비 */ + flex-wrap: wrap; + row-gap: 2.4rem; +`; + +const PortfolioEditor = styled(ReactQuill)` + display: flex; + flex-direction: column; + min-height: 27.1rem; + border-radius: 0.75rem; + border: 0.1rem solid #e3e3e3; + overflow: hidden; + + &:hover, + focus { + border-color: var(--Form-border-focus, #5877fc); + } + + .ql-toolbar { + border: 0; + border-bottom: 0.1rem solid #e3e3e3; + } + + .ql-container { + display: flex; + border: 0; + color: var(--Light-Black, #373f41); + white-space: pre-wrap; // 줄바꿈 + + /* 스크롤바 스타일링 */ + overflow-y: auto; + &::-webkit-scrollbar { + width: 1.8rem; + } + &::-webkit-scrollbar-thumb { + background-color: #e3e3e3; + border-radius: 1rem; + background-clip: padding-box; + border: 0.5rem solid transparent; + } + + /* Body/body1/medium */ + font-family: Pretendard; + font-size: 1.6rem; + font-style: normal; + font-weight: 500; + line-height: 1.9rem; + letter-spacing: 0.0032rem; + } + + .ql-editor { + width: 100%; + padding: 1.5rem 1.7rem; + } + + &.quill > .ql-container > .ql-editor.ql-blank::before { + display: flex; + color: #8e8e8e; + } +`; + +const PortfolioEditButtonBox = styled.div` + display: flex; + flex-direction: row; + column-gap: 1.6rem; + margin-top: 12rem; + margin-left: auto; +`; + +const S = { + PortfolioEditLayout, + PortfolioEditColumn, + PortfolioEditRow, + PortfolioEditHeader, + PortfolioEditTitle, + PortfolioEditLabel, + PortfolioEditArticle, + PortfolioEditor, + PortfolioEditButtonBox, +}; + +export default S; From b6879ed8f48c6dcc42afd2184814a0a88f46e898 Mon Sep 17 00:00:00 2001 From: kimsuyeon_0916 Date: Sat, 20 Apr 2024 15:50:23 +0900 Subject: [PATCH 025/116] =?UTF-8?q?feat:=20#91=20=ED=8F=AC=ED=8A=B8?= =?UTF-8?q?=ED=8F=B4=EB=A6=AC=EC=98=A4=20=EC=83=81=EC=84=B8=20=EB=82=B4?= =?UTF-8?q?=EC=9A=A9=20=EC=9C=A0=ED=9A=A8=EC=84=B1=20=EA=B2=80=EC=82=AC=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- validation.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/validation.ts b/validation.ts index b9754097..47cc2ea2 100644 --- a/validation.ts +++ b/validation.ts @@ -48,6 +48,9 @@ export const INPUT_VALIDATION = { skill: { required: '스킬을 추가해주세요', }, + content: { + required: true, + }, }; export const TEXTAREA_VALIDATION = { From c77cfb865ffad74a789e40971137fc5894cec115 Mon Sep 17 00:00:00 2001 From: kimsuyeon_0916 Date: Sat, 20 Apr 2024 15:52:11 +0900 Subject: [PATCH 026/116] =?UTF-8?q?feat:=20#91=20=ED=8F=AC=ED=8A=B8?= =?UTF-8?q?=ED=8F=B4=EB=A6=AC=EC=98=A4=20=ED=8E=B8=EC=A7=91=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20UI=20=EC=83=81=EC=84=B8=20=EB=82=B4?= =?UTF-8?q?=EC=9A=A9=20=EC=83=81=EC=88=98=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/portfolio/edit/portfolioEditData.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/pages/portfolio/edit/portfolioEditData.ts b/src/pages/portfolio/edit/portfolioEditData.ts index a58e4570..a3fac951 100644 --- a/src/pages/portfolio/edit/portfolioEditData.ts +++ b/src/pages/portfolio/edit/portfolioEditData.ts @@ -57,12 +57,21 @@ const skills = { }, }; +const content = { + label: '상세 내용', + type: 'text', + placeholder: `나의 경험 및 경력 및 맡게 되는 역할을 작성해주세요.\n\n﹒재직시 전문적으로 담당한 업무나, 별도로 진행하신 팀 프로젝트가 있으시다면 적어주세요.\n(ex. 재직중인 회사에서, 사업기획 및 PM을 담당했습니다. 사람간의 일정조율 , 요구사항 조절에 자신이 있습니다.\n이와 별개로 총 10명정도의 규모의 팀에서 부팀장으로 역할을 담당하였고, 출시까지 한 경험이 있습니다.)\n\n﹒이 프로젝트에서 나(리더) 역할을 적어주세요.\n(ex. 전체 프로덕트의 기획 및 프로젝트 매니징을 담당하게 됩니다. 다만 한분이 더 같이 들어오셔서, 논의를 같이 했으면 좋겠습니다.)`, + name: 'content', + validation: INPUT_VALIDATION.content, +}; + const PORTFOLIO_EDIT_DATA = { title, description, field, role, skills, + content, }; export default PORTFOLIO_EDIT_DATA; From afb74670b587dbe1fba439380e8f1874a82cd6a7 Mon Sep 17 00:00:00 2001 From: kimsuyeon_0916 Date: Sat, 20 Apr 2024 15:52:33 +0900 Subject: [PATCH 027/116] =?UTF-8?q?feat:=20#91=20=ED=8F=AC=ED=8A=B8?= =?UTF-8?q?=ED=8F=B4=EB=A6=AC=EC=98=A4=20=ED=8E=B8=EC=A7=91=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EC=A0=9C=EC=9E=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/index.ts | 2 + .../portfolio/edit/PortfolioEditPage.tsx | 303 ++++++++++++++++++ 2 files changed, 305 insertions(+) create mode 100644 src/pages/portfolio/edit/PortfolioEditPage.tsx diff --git a/src/pages/index.ts b/src/pages/index.ts index 80ad2c75..14e14b1c 100644 --- a/src/pages/index.ts +++ b/src/pages/index.ts @@ -27,6 +27,7 @@ import PROFILE_EDIT_DATA from './profile/edit/ProfileEditData'; import { portfolioData } from './portfolio/portfolioData'; import PortfolioDetailsPage from './portfolio/details/PortfolioDetailsPage'; import PORTFOLIO_EDIT_DATA from './portfolio/edit/portfolioEditData'; +import PortfolioEditPage from './portfolio/edit/PortfolioEditPage'; export { MainPage, @@ -59,4 +60,5 @@ export { portfolioData, PortfolioDetailsPage, PORTFOLIO_EDIT_DATA, + PortfolioEditPage, }; diff --git a/src/pages/portfolio/edit/PortfolioEditPage.tsx b/src/pages/portfolio/edit/PortfolioEditPage.tsx new file mode 100644 index 00000000..2dad9915 --- /dev/null +++ b/src/pages/portfolio/edit/PortfolioEditPage.tsx @@ -0,0 +1,303 @@ +import React, { useEffect, useRef, useState } from 'react'; +import S from './PortfolioEdit.styled'; +import { DevTool } from '@hookform/devtools'; +import { + Input, + ComboBox, + Radio, + SkillTag, + AddFormBtn, + LinkForm, + DefaultBtn, + PrimaryBtn, + MuiDatepicker, + PortfolioImageUpload, +} from '../../../components'; +import { useParams, useNavigate } from 'react-router-dom'; +import { useForm, useFieldArray } from 'react-hook-form'; +import { Link, Skill } from '../../../types'; +import { useDebounce, useReadPortfolio, useReadRoleList, useReadSkillList } from '../../../hooks'; +import PORTFOLIO_EDIT_DATA from './portfolioEditData'; +import { modules, formats } from '../../../utils'; +import { Refresh } from '../../../assets'; +import type ReactQuill from 'react-quill'; + +interface FormValues { + title?: string; + description?: string; + field?: string; + role?: string; + startDate?: string; + endDate?: string; + skills?: string | null; + content?: string; + links?: Link[]; +} + +const LABEL = { + image: `최소 한 장 이상의 이미지가 업로드 되어야하며 첫번 째 이미지가 메인 이미지가 됩니다.\n단, 한 장당 30MB 이하로 최대 15장까지 업로드 가능합니다.`, + content: `진행 했던 내용을 자유롭게 작성해주세요.`, +}; +const PROCEED_TYPE = ['오프라인', '온라인', '상관없음']; + +const PortfolioEditPage = () => { + const { portfolioId } = useParams() as { portfolioId: string }; // undefined 인 경우(생성하는 경우) 로직 필요 + const navigate = useNavigate(); + + const { data: portfolio, isSuccess } = useReadPortfolio(portfolioId); + // 작성자가 아닌 경우, 편집 방지(상세페이지로 이동) + useEffect(() => { + if (isSuccess) { + portfolioId && !portfolio?.isWriter && navigate(`/portfolio/${portfolioId}`); + } + }, [isSuccess]); + + const { register, formState, handleSubmit, control, watch, getValues, setValue, trigger } = + useForm({ + mode: 'onChange', + values: { + ...portfolio, + skills: null, + }, + resetOptions: { + keepDirtyValues: true, + keepErrors: true, + }, + }); + + // 분야 + const fields = [{ id: '1', name: '개발' }]; + + // 역할 + const role = useDebounce(watch('role')) as string; + const { data: roles } = useReadRoleList(role); + + // 진행 방식 + const [proceedType, setProceedType] = useState(portfolio?.proceedType); + const handleRadioClick = (id: string) => { + setProceedType(id); + }; + + // 스킬 + const skill = useDebounce(watch('skills')) as string; + const { data: skills } = useReadSkillList(skill); + + const [skillList, setSkillList] = useState(portfolio?.skills ? portfolio?.skills : []); + + const addSkill = () => { + const newSkill = { id: sessionStorage.skills, name: getValues('skills') } as Skill; + if (getValues('skills')?.length === 0) return; + if (!skillList.find(skill => newSkill.name === skill.name)) { + setSkillList(prev => [...prev, newSkill]); + } + setValue('skills', ''); + }; + + const deleteSkill = (skillName: string) => { + setSkillList(() => skillList.filter(skill => skill.name !== skillName)); + }; + + // 링크 + const { + fields: links, + prepend: prependLink, + remove: removeLink, + } = useFieldArray({ + name: 'links', + control: control, + }); + + const addLink = (index: number) => { + if (index === -1 || getValues(`links.0.url`)) { + prependLink({ description: 'Link', url: '' }); + } + }; + + // 상세 내용 + const quillRef = useRef(null); + const { ref, ...rest } = register('content', PORTFOLIO_EDIT_DATA.content.validation); + + const handleChangeEditor = value => { + setValue('content', value); + }; + + useEffect(() => { + if (isSuccess) { + setProceedType(portfolio?.proceedType); + setSkillList(portfolio?.skills ? portfolio?.skills : []); + } + }, [isSuccess]); + + return ( + <> + + + +

포트폴리오 작성

+

+ 작성하신 포트폴리오는 프로필을 통해 보여집니다. 진행했던 내용을 자유롭게 작성해보세요! +

+
+
+ + + + 슬라이드 이미지 + + + {LABEL.image} + + + + + +
+
+ + + + 기본 정보 + + {/* 포트폴리오 제목 */} + + {/* 포트폴리오 한줄 소개 */} + + + {/* 분야 */} + + {/* 역할 */} + + + {/* 진행기간 */} + + 진행기간 + + + + + + {/* 진행방식 */} + + 진행방식 + + {PROCEED_TYPE.map(type => ( + +
+ {type} +
+
+ ))} +
+
+ {/* 스킬 */} + + + + {skillList?.map(({ ...props }, index) => ( + + ))} + + +
+
+
+
+ + + + 상세 내용 + + {LABEL.content} + { + ref(e); + if (quillRef) quillRef.current = e; + }} + value={portfolio?.content} + onChange={handleChangeEditor} + modules={modules} + formats={formats} + {...PORTFOLIO_EDIT_DATA.content} + /> + + +
+
+ + + + 링크 + + addLink(links.length - 1)} /> + + {links?.map((link, index) => ( + + ))} + + + +
+
+ + + navigate(`/portfolio/${portfolioId}`)} + /> + + +
+
+ + + ); +}; + +export default PortfolioEditPage; From 41e15139fa7efe5056ba2fbfc024cb6e576b3be9 Mon Sep 17 00:00:00 2001 From: kimsuyeon_0916 Date: Sat, 20 Apr 2024 15:52:54 +0900 Subject: [PATCH 028/116] =?UTF-8?q?feat:=20#91=20=ED=8F=AC=ED=8A=B8?= =?UTF-8?q?=ED=8F=B4=EB=A6=AC=EC=98=A4=20=ED=8E=B8=EC=A7=91=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EB=9D=BC=EC=9A=B0=ED=8C=85=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main.tsx b/src/main.tsx index 5151306a..9e9afcb5 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -22,6 +22,7 @@ import { ProfileDetailsPage, ProfileEditPage, PortfolioDetailsPage, + PortfolioEditPage, } from './pages/index.ts'; import './globalStyle.css'; @@ -104,6 +105,10 @@ const router = createBrowserRouter([ path: 'portfolio/:portfolioId?', element: , }, + { + path: 'portfolio/edit/:portfolioId?', + element: , // 생성 및 편집 + }, ], }, ]); From 6a71d0934f951a645cdf54500e58f2591b71fbae Mon Sep 17 00:00:00 2001 From: kimsuyeon_0916 Date: Wed, 24 Apr 2024 04:29:26 +0900 Subject: [PATCH 029/116] =?UTF-8?q?refactor:=20#91=20=EC=9C=A0=ED=9A=A8?= =?UTF-8?q?=EC=84=B1=20=EA=B2=80=EC=82=AC=20=EC=83=81=EC=88=98=ED=99=94=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EC=9C=84=EC=B9=98=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/constant/index.ts | 3 +++ validation.ts => src/constant/validation.ts | 0 src/pages/account/signUp/SignUpData.ts | 2 +- src/pages/index.ts | 2 -- src/pages/portfolio/edit/portfolioEditData.ts | 2 +- src/pages/profile/edit/ProfileEditData.ts | 2 +- 6 files changed, 6 insertions(+), 5 deletions(-) create mode 100644 src/constant/index.ts rename validation.ts => src/constant/validation.ts (100%) diff --git a/src/constant/index.ts b/src/constant/index.ts new file mode 100644 index 00000000..840b485a --- /dev/null +++ b/src/constant/index.ts @@ -0,0 +1,3 @@ +import { INPUT_VALIDATION, TEXTAREA_VALIDATION } from './validation'; + +export { INPUT_VALIDATION, TEXTAREA_VALIDATION }; diff --git a/validation.ts b/src/constant/validation.ts similarity index 100% rename from validation.ts rename to src/constant/validation.ts diff --git a/src/pages/account/signUp/SignUpData.ts b/src/pages/account/signUp/SignUpData.ts index 65fb6a94..a09e8860 100644 --- a/src/pages/account/signUp/SignUpData.ts +++ b/src/pages/account/signUp/SignUpData.ts @@ -1,4 +1,4 @@ -import { INPUT_VALIDATION } from '../..'; +import { INPUT_VALIDATION } from '../../../constant'; export interface Account { [key: string]: string; diff --git a/src/pages/index.ts b/src/pages/index.ts index 14e14b1c..fd5fa314 100644 --- a/src/pages/index.ts +++ b/src/pages/index.ts @@ -7,7 +7,6 @@ import SignInPage from './account/signIn/SignInPage'; import SignUpPage from './account/signUp/SignUpPage'; import SchoolCertificationPage from './account/schoolCertification/SchoolCertificationPage'; import { SCHOOL_CERTIFICATION_DATA } from './account/schoolCertification/SchoolCertificationData'; -import { INPUT_VALIDATION } from '../../validation'; import { SIGN_UP_DATA } from './account/signUp/SignUpData'; import NicknameSettingPage from './account/signUp/nicknameSetting/NicknameSettingPage'; import type { Account, User } from './account/signUp/SignUpData'; @@ -40,7 +39,6 @@ export { SchoolCertificationPage, SCHOOL_CERTIFICATION_DATA, User, - INPUT_VALIDATION, SIGN_UP_DATA, NicknameSettingPage, Account, diff --git a/src/pages/portfolio/edit/portfolioEditData.ts b/src/pages/portfolio/edit/portfolioEditData.ts index a3fac951..1d398f0a 100644 --- a/src/pages/portfolio/edit/portfolioEditData.ts +++ b/src/pages/portfolio/edit/portfolioEditData.ts @@ -1,5 +1,5 @@ import { ArrowBottom, ArrowTop, Search } from '../../../assets'; -import { INPUT_VALIDATION } from '../../../../validation'; +import { INPUT_VALIDATION } from '../../../constant'; const title = { label: '포트폴리오 제목', diff --git a/src/pages/profile/edit/ProfileEditData.ts b/src/pages/profile/edit/ProfileEditData.ts index 21085658..dde8130b 100644 --- a/src/pages/profile/edit/ProfileEditData.ts +++ b/src/pages/profile/edit/ProfileEditData.ts @@ -9,7 +9,7 @@ import { GrayCalendar, BlackCalendar, } from '../../../assets'; -import { INPUT_VALIDATION, TEXTAREA_VALIDATION } from '../../../../validation'; +import { INPUT_VALIDATION, TEXTAREA_VALIDATION } from '../../../constant'; const nickname = { type: 'text', From 657b2d78f4775568e977c00ff8bc636e410f3f8d Mon Sep 17 00:00:00 2001 From: kimsuyeon_0916 Date: Wed, 24 Apr 2024 11:02:55 +0900 Subject: [PATCH 030/116] =?UTF-8?q?fix:=20#91=20=ED=8F=AC=ED=8A=B8?= =?UTF-8?q?=ED=8F=B4=EB=A6=AC=EC=98=A4=20=ED=8E=B8=EC=A7=91=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20handleChangeEditor=20=ED=83=80=EC=9E=85=20?= =?UTF-8?q?=EC=97=90=EB=9F=AC=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/portfolio/edit/PortfolioEditPage.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/portfolio/edit/PortfolioEditPage.tsx b/src/pages/portfolio/edit/PortfolioEditPage.tsx index 2dad9915..5b545f50 100644 --- a/src/pages/portfolio/edit/PortfolioEditPage.tsx +++ b/src/pages/portfolio/edit/PortfolioEditPage.tsx @@ -115,9 +115,9 @@ const PortfolioEditPage = () => { // 상세 내용 const quillRef = useRef(null); - const { ref, ...rest } = register('content', PORTFOLIO_EDIT_DATA.content.validation); + const { ref } = register('content', PORTFOLIO_EDIT_DATA.content.validation); - const handleChangeEditor = value => { + const handleChangeEditor = (value: string) => { setValue('content', value); }; From 5db361f2d6b2612bc9d0181fc8d29902a5dfd00c Mon Sep 17 00:00:00 2001 From: kimsuyeon_0916 Date: Wed, 24 Apr 2024 11:37:34 +0900 Subject: [PATCH 031/116] =?UTF-8?q?feat:=20#91=20=ED=8F=AC=ED=8A=B8?= =?UTF-8?q?=ED=8F=B4=EB=A6=AC=EC=98=A4=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=EC=97=85=EB=A1=9C=EB=93=9C=20=EC=BB=B4=ED=8F=AC=EB=84=8C?= =?UTF-8?q?=ED=8A=B8=20=ED=8F=B4=EB=8D=94=20=EC=9C=84=EC=B9=98=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/index.ts | 2 +- .../portfolio/image/upload/PortfolioImageUpload.styled.ts | 0 .../portfolio/image/upload/PortfolioImageUpload.tsx | 8 ++++---- 3 files changed, 5 insertions(+), 5 deletions(-) rename "src/components/portfolio/\bimage/PortfolioImageUpload.styled.ts" => src/components/portfolio/image/upload/PortfolioImageUpload.styled.ts (100%) rename "src/components/portfolio/\bimage/PortfolioImageUpload.tsx" => src/components/portfolio/image/upload/PortfolioImageUpload.tsx (94%) diff --git a/src/components/index.ts b/src/components/index.ts index b1e56586..e820b521 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -116,7 +116,7 @@ import LinkDetails from './link/details/LinkDetails'; import PortfolioInformation from './portfolio/information/PortfolioInformation'; import PortfolioList from './portfolio/list/PortfolioList'; import ImageCarousel from './carousel/ImageCarousel'; -import PortfolioImageUpload from './portfolio/\bimage/PortfolioImageUpload'; +import PortfolioImageUpload from './portfolio/image/upload/PortfolioImageUpload'; export { Header, diff --git "a/src/components/portfolio/\bimage/PortfolioImageUpload.styled.ts" b/src/components/portfolio/image/upload/PortfolioImageUpload.styled.ts similarity index 100% rename from "src/components/portfolio/\bimage/PortfolioImageUpload.styled.ts" rename to src/components/portfolio/image/upload/PortfolioImageUpload.styled.ts diff --git "a/src/components/portfolio/\bimage/PortfolioImageUpload.tsx" b/src/components/portfolio/image/upload/PortfolioImageUpload.tsx similarity index 94% rename from "src/components/portfolio/\bimage/PortfolioImageUpload.tsx" rename to src/components/portfolio/image/upload/PortfolioImageUpload.tsx index dc7810e2..36ecee45 100644 --- "a/src/components/portfolio/\bimage/PortfolioImageUpload.tsx" +++ b/src/components/portfolio/image/upload/PortfolioImageUpload.tsx @@ -1,9 +1,9 @@ -import React, { useRef, useState } from 'react'; +import React, { useRef } from 'react'; import S from './PortfolioImageUpload.styled'; -import { Plus } from '../../../assets'; +import { Plus } from '../../../../assets'; import { useRecoilState } from 'recoil'; -import { binaryImageListState, imageNameListState, imageSrcListState } from '../../../atom'; -import PortfolioCard from '../card/PortfolioCard'; +import { binaryImageListState, imageNameListState, imageSrcListState } from '../../../../atom'; +import PortfolioCard from '../../card/PortfolioCard'; const MAX_IMAGE_SIZE_BYTES = 30 * 1024 * 1024; // 30MB const MAX_IMAGE_COUNT = 15; From 394089736b2832f7468879bd925037fcb80d3d16 Mon Sep 17 00:00:00 2001 From: kimsuyeon_0916 Date: Wed, 24 Apr 2024 11:38:01 +0900 Subject: [PATCH 032/116] =?UTF-8?q?feat:=20#91=20react-beautiful-dnd=20?= =?UTF-8?q?=ED=8C=A8=ED=82=A4=EC=A7=80=20=EC=84=A4=EC=B9=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 118 +++++++++++++++++++++++++++++++++++++++++++++- package.json | 2 + 2 files changed, 119 insertions(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index 4bf022fd..1166ce8e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "embla-carousel-react": "^8.0.1", "framer-motion": "^11.0.2", "react": "^18.2.0", + "react-beautiful-dnd": "^13.1.1", "react-datepicker": "^4.21.0", "react-dom": "^18.2.0", "react-hook-form": "^7.49.3", @@ -35,6 +36,7 @@ "@tanstack/react-query-devtools": "^5.24.8", "@types/node": "^20.11.6", "@types/react": "^18.2.15", + "@types/react-beautiful-dnd": "^13.1.8", "@types/react-datepicker": "^4.19.3", "@types/react-dom": "^18.2.7", "@typescript-eslint/eslint-plugin": "^6.10.0", @@ -2042,6 +2044,15 @@ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==" }, + "node_modules/@types/hoist-non-react-statics": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.5.tgz", + "integrity": "sha512-SbcrWzkKBw2cdwRTwQAswfpB9g9LJWfjtUeW/jvNwbhC8cpmmNYVePa+ncbUe0rGTQ7G3Ff6mYUN2VMfLVr+Sg==", + "dependencies": { + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0" + } + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -2092,6 +2103,15 @@ "csstype": "^3.0.2" } }, + "node_modules/@types/react-beautiful-dnd": { + "version": "13.1.8", + "resolved": "https://registry.npmjs.org/@types/react-beautiful-dnd/-/react-beautiful-dnd-13.1.8.tgz", + "integrity": "sha512-E3TyFsro9pQuK4r8S/OL6G99eq7p8v29sX0PM7oT8Z+PJfZvSQTx4zTQbUJ+QZXioAF0e7TGBEcA1XhYhCweyQ==", + "dev": true, + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/react-datepicker": { "version": "4.19.3", "resolved": "https://registry.npmjs.org/@types/react-datepicker/-/react-datepicker-4.19.3.tgz", @@ -2129,6 +2149,17 @@ "@types/react": "*" } }, + "node_modules/@types/react-redux": { + "version": "7.1.33", + "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.33.tgz", + "integrity": "sha512-NF8m5AjWCkert+fosDsN3hAlHzpjSiXlVy9EgQEmLoBhaNXbmyeGs/aj5dQzKuF+/q+S7JQagorGDW8pJ28Hmg==", + "dependencies": { + "@types/hoist-non-react-statics": "^3.3.0", + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0", + "redux": "^4.0.0" + } + }, "node_modules/@types/react-transition-group": { "version": "4.4.10", "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.10.tgz", @@ -2891,6 +2922,14 @@ "node": ">= 8" } }, + "node_modules/css-box-model": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz", + "integrity": "sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==", + "dependencies": { + "tiny-invariant": "^1.0.6" + } + }, "node_modules/css-color-keywords": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", @@ -4128,7 +4167,6 @@ "version": "3.3.2", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", - "devOptional": true, "dependencies": { "react-is": "^16.7.0" } @@ -4803,6 +4841,11 @@ "node": ">=10" } }, + "node_modules/memoize-one": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", + "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==" + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -5384,6 +5427,11 @@ "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.1.2.tgz", "integrity": "sha512-KaJUt+M9t1qaIteSvjc6P3RbMdXsNhK61GRftR6SNxqmhthcd9MGIi4T+o0jD8LUSpSnSKXE20nLtJ3fOHxQig==" }, + "node_modules/raf-schd": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/raf-schd/-/raf-schd-4.0.3.tgz", + "integrity": "sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==" + }, "node_modules/react": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", @@ -5395,6 +5443,24 @@ "node": ">=0.10.0" } }, + "node_modules/react-beautiful-dnd": { + "version": "13.1.1", + "resolved": "https://registry.npmjs.org/react-beautiful-dnd/-/react-beautiful-dnd-13.1.1.tgz", + "integrity": "sha512-0Lvs4tq2VcrEjEgDXHjT98r+63drkKEgqyxdA7qD3mvKwga6a5SscbdLPO2IExotU1jW8L0Ksdl0Cj2AF67nPQ==", + "dependencies": { + "@babel/runtime": "^7.9.2", + "css-box-model": "^1.2.0", + "memoize-one": "^5.1.1", + "raf-schd": "^4.0.2", + "react-redux": "^7.2.0", + "redux": "^4.0.4", + "use-memo-one": "^1.1.1" + }, + "peerDependencies": { + "react": "^16.8.5 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.5 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-datepicker": { "version": "4.21.0", "resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-4.21.0.tgz", @@ -5514,6 +5580,35 @@ "react-dom": "^16 || ^17 || ^18" } }, + "node_modules/react-redux": { + "version": "7.2.9", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.9.tgz", + "integrity": "sha512-Gx4L3uM182jEEayZfRbI/G11ZpYdNAnBs70lFVMNdHJI76XYtR+7m0MN+eAs7UHBPhWXcnFPaS+9owSCJQHNpQ==", + "dependencies": { + "@babel/runtime": "^7.15.4", + "@types/react-redux": "^7.1.20", + "hoist-non-react-statics": "^3.3.2", + "loose-envify": "^1.4.0", + "prop-types": "^15.7.2", + "react-is": "^17.0.2" + }, + "peerDependencies": { + "react": "^16.8.3 || ^17 || ^18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, + "node_modules/react-redux/node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" + }, "node_modules/react-router": { "version": "6.18.0", "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.18.0.tgz", @@ -5595,6 +5690,14 @@ "recoil": "^0.7.2" } }, + "node_modules/redux": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", + "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==", + "dependencies": { + "@babel/runtime": "^7.9.2" + } + }, "node_modules/reflect.getprototypeof": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.4.tgz", @@ -6175,6 +6278,11 @@ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==" + }, "node_modules/titleize": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/titleize/-/titleize-3.0.0.tgz", @@ -6411,6 +6519,14 @@ "react": ">=16.13" } }, + "node_modules/use-memo-one": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/use-memo-one/-/use-memo-one-1.1.3.tgz", + "integrity": "sha512-g66/K7ZQGYrI6dy8GLpVcMsBp4s17xNkYJVSMvTEevGy3nDxHOfE6z8BVE22+5G5x7t3+bhzrlTDB7ObrEE0cQ==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/uuid": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", diff --git a/package.json b/package.json index deb13ff6..1b84cb76 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "embla-carousel-react": "^8.0.1", "framer-motion": "^11.0.2", "react": "^18.2.0", + "react-beautiful-dnd": "^13.1.1", "react-datepicker": "^4.21.0", "react-dom": "^18.2.0", "react-hook-form": "^7.49.3", @@ -38,6 +39,7 @@ "@tanstack/react-query-devtools": "^5.24.8", "@types/node": "^20.11.6", "@types/react": "^18.2.15", + "@types/react-beautiful-dnd": "^13.1.8", "@types/react-datepicker": "^4.19.3", "@types/react-dom": "^18.2.7", "@typescript-eslint/eslint-plugin": "^6.10.0", From 44e55a755facea40d6225ff1a3cae27c2f9a8f06 Mon Sep 17 00:00:00 2001 From: kimsuyeon_0916 Date: Wed, 24 Apr 2024 11:56:34 +0900 Subject: [PATCH 033/116] =?UTF-8?q?refactor:=20#91=20=ED=8F=AC=ED=8A=B8?= =?UTF-8?q?=ED=8F=B4=EB=A6=AC=EC=98=A4=20=ED=8E=B8=EC=A7=91=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EC=8A=A4=ED=83=80=EC=9D=BC=EB=93=9C=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EB=82=B4=20=ED=83=80?= =?UTF-8?q?=EC=9E=85=EB=AA=85=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ProfileBoxStyle -> PortfolioBoxStyle --- src/pages/portfolio/edit/PortfolioEdit.styled.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pages/portfolio/edit/PortfolioEdit.styled.ts b/src/pages/portfolio/edit/PortfolioEdit.styled.ts index 249ea2b4..583a6a39 100644 --- a/src/pages/portfolio/edit/PortfolioEdit.styled.ts +++ b/src/pages/portfolio/edit/PortfolioEdit.styled.ts @@ -1,7 +1,7 @@ import styled from 'styled-components'; import ReactQuill from 'react-quill'; -interface ProfileBoxStyle { +interface PortfolioBoxStyle { $gap?: string; $width?: string; } @@ -59,14 +59,14 @@ const PortfolioEditLayout = styled.form` } `; -const PortfolioEditColumn = styled.div` +const PortfolioEditColumn = styled.div` display: flex; flex-direction: column; row-gap: ${props => props.$gap}; ${props => (props.$width ? `width: ${props.$width}` : `flex: 1;`)}; `; -const PortfolioEditRow = styled.div` +const PortfolioEditRow = styled.div` display: flex; flex-direction: row; column-gap: ${props => props.$gap}; From 69cb13a4af255347ea7912fa808e0fd687c5c184 Mon Sep 17 00:00:00 2001 From: kimsuyeon_0916 Date: Wed, 24 Apr 2024 17:53:14 +0900 Subject: [PATCH 034/116] =?UTF-8?q?feat:=20#91=20uploadImageListState=20at?= =?UTF-8?q?om=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/atom.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/atom.tsx b/src/atom.tsx index d09d7149..4c76116f 100644 --- a/src/atom.tsx +++ b/src/atom.tsx @@ -1,6 +1,6 @@ import { atom } from 'recoil'; import { SessionStorageEffect, simpleDate } from './utils'; -import { User, InputState } from './types'; +import { User, InputState, Image } from './types'; import { LocalStorageEffect } from './utils'; import { Account } from './pages'; @@ -142,3 +142,8 @@ export const binaryImageListState = atom({ key: 'binaryImageListState', default: [], }); + +export const uploadImageListState = atom({ + key: 'uploadImageListState', + default: [], +}); From 94b773b2ef5c11da6b34571cc9d4205f01db1824 Mon Sep 17 00:00:00 2001 From: kimsuyeon_0916 Date: Wed, 24 Apr 2024 17:54:33 +0900 Subject: [PATCH 035/116] =?UTF-8?q?feat:=20#91=20Image=20=ED=83=80?= =?UTF-8?q?=EC=9E=85=20=EB=82=B4=20url=20=ED=94=84=EB=A1=9C=ED=8D=BC?= =?UTF-8?q?=ED=8B=B0=EB=A5=BC=20=EC=84=A0=ED=83=9D=EC=A0=81=20=ED=94=84?= =?UTF-8?q?=EB=A1=9C=ED=8D=BC=ED=8B=B0=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/types/image.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/types/image.ts b/src/types/image.ts index dfe07986..66add035 100644 --- a/src/types/image.ts +++ b/src/types/image.ts @@ -1,4 +1,5 @@ export interface Image { fileName: string; - url: string; + url?: string; + binary?: ArrayBuffer; } From f76ffaf21747b8be10b1ec8e419add3e3356a64c Mon Sep 17 00:00:00 2001 From: kimsuyeon_0916 Date: Wed, 24 Apr 2024 17:56:20 +0900 Subject: [PATCH 036/116] =?UTF-8?q?feat:=20#91=20=ED=8F=AC=ED=8A=B8?= =?UTF-8?q?=ED=8F=B4=EB=A6=AC=EC=98=A4=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=EC=97=85=EB=A1=9C=EB=93=9C=20=EC=BB=B4=ED=8F=AC=EB=84=8C?= =?UTF-8?q?=ED=8A=B8=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=97=85=EB=A1=9C?= =?UTF-8?q?=EB=93=9C=20=EB=A1=9C=EC=A7=81=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 이미지 파일 이름, 이미지 파일 주소, 이미지 바이터리 정보를 객체에 저장하고 상태 변경 --- .../image/upload/PortfolioImageUpload.tsx | 36 +++++++++++++------ 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/src/components/portfolio/image/upload/PortfolioImageUpload.tsx b/src/components/portfolio/image/upload/PortfolioImageUpload.tsx index 36ecee45..59f88917 100644 --- a/src/components/portfolio/image/upload/PortfolioImageUpload.tsx +++ b/src/components/portfolio/image/upload/PortfolioImageUpload.tsx @@ -2,8 +2,14 @@ import React, { useRef } from 'react'; import S from './PortfolioImageUpload.styled'; import { Plus } from '../../../../assets'; import { useRecoilState } from 'recoil'; -import { binaryImageListState, imageNameListState, imageSrcListState } from '../../../../atom'; +import { + binaryImageListState, + imageNameListState, + imageSrcListState, + uploadImageListState, +} from '../../../../atom'; import PortfolioCard from '../../card/PortfolioCard'; +import { Image } from '../../../../types'; const MAX_IMAGE_SIZE_BYTES = 30 * 1024 * 1024; // 30MB const MAX_IMAGE_COUNT = 15; @@ -19,12 +25,11 @@ const PortfolioImageUpload = (portfolioId?: { portfolioId?: string }) => { const [imageNameList, setImageNameList] = useRecoilState(imageNameListState); // 추후에 nameList 받아와서 초기화 const [imageSrcList, setImageSrcList] = useRecoilState(imageSrcListState); // 추후에 urlList 받아와서 초기화 const [binaryImageList, setBinaryImageList] = useRecoilState(binaryImageListState); // 추후에 binaryList 받아와서 초기화 + const [uploadImageList, setUploadImageList] = useRecoilState(uploadImageListState); // 추후에 받아온 정보 reduce로 조합해서 초기화 const changeImageList = (event: React.BaseSyntheticEvent) => { const uploadImageList = event.target?.files; - // 이미지 개수 제한 for (let i = 0; i < uploadImageList.length && imageNameList.length + i < MAX_IMAGE_COUNT; i++) { - // 중복된 이름 제한 if ( imageNameList.find(imageName => imageName === uploadImageList[i].name) || [...uploadImageList].find( @@ -33,20 +38,31 @@ const PortfolioImageUpload = (portfolioId?: { portfolioId?: string }) => { ) { continue; } - // 이미지 용량 제한 if (uploadImageList[i].size > MAX_IMAGE_SIZE_BYTES) { continue; } + setImageNameList(prev => [...prev, uploadImageList[i].name]); - const urlReader = new FileReader(); - urlReader.readAsDataURL(uploadImageList[i]); - urlReader.onload = () => setImageSrcList(prev => [...prev, urlReader.result] as string[]); + let uploadImage: Image = { + fileName: uploadImageList[i].name, + }; const binaryReader = new FileReader(); binaryReader.readAsArrayBuffer(uploadImageList[i]); - binaryReader.onload = () => + binaryReader.onload = () => { setBinaryImageList(prev => [...prev, binaryReader.result] as ArrayBuffer[]); + uploadImage = { ...uploadImage, binary: binaryReader.result as ArrayBuffer }; + }; + + const urlReader = new FileReader(); + urlReader.readAsDataURL(uploadImageList[i]); + urlReader.onload = () => { + setImageSrcList(prev => [...prev, urlReader.result] as string[]); + uploadImage = { ...uploadImage, url: urlReader.result as string }; + + setUploadImageList(prev => [...prev, uploadImage]); + }; } }; @@ -55,11 +71,11 @@ const PortfolioImageUpload = (portfolioId?: { portfolioId?: string }) => { return ( - {[...imageSrcList].map((imageSrc, index) => ( + {[...uploadImageList].map((uploadImage, index) => ( From 13b29a2626c94f55bb6510676d0e0cfa851a3901 Mon Sep 17 00:00:00 2001 From: kimsuyeon_0916 Date: Wed, 24 Apr 2024 22:34:20 +0900 Subject: [PATCH 037/116] =?UTF-8?q?feat:=20#91=20=ED=96=84=EB=B2=84?= =?UTF-8?q?=EA=B1=B0=20=EB=A9=94=EB=89=B4=20=EC=95=84=EC=9D=B4=EC=BD=98=20?= =?UTF-8?q?svg=20=ED=8C=8C=EC=9D=BC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/assets/HamburgerMenuIcon.svg | 3 +++ src/assets/index.ts | 2 ++ 2 files changed, 5 insertions(+) create mode 100644 src/assets/HamburgerMenuIcon.svg diff --git a/src/assets/HamburgerMenuIcon.svg b/src/assets/HamburgerMenuIcon.svg new file mode 100644 index 00000000..c72b3272 --- /dev/null +++ b/src/assets/HamburgerMenuIcon.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/index.ts b/src/assets/index.ts index 06e6b3e8..27481f7b 100644 --- a/src/assets/index.ts +++ b/src/assets/index.ts @@ -35,6 +35,7 @@ import Test2 from './Test2.png'; import Test3 from './Test3.png'; import Test4 from './Test4.png'; import Refresh from './Refresh.svg'; +import HambergerMenuIcon from './HamburgerMenuIcon.svg'; export { Exit, @@ -74,4 +75,5 @@ export { Test3, Test4, Refresh, + HambergerMenuIcon, }; From 75ec51590f83c9792c7cf922fdd042cf93147177 Mon Sep 17 00:00:00 2001 From: kimsuyeon_0916 Date: Wed, 24 Apr 2024 23:55:34 +0900 Subject: [PATCH 038/116] =?UTF-8?q?feat:=20#91=20index.html=20=EB=82=B4,?= =?UTF-8?q?=20root=20=EC=9A=94=EC=86=8C=EC=99=80=20=EA=B0=99=EC=9D=80=20?= =?UTF-8?q?=EC=9C=84=EC=B9=98=EC=9D=98=20=EC=83=88=EB=A1=9C=EC=9A=B4=20mod?= =?UTF-8?q?al=20DOM=20=EA=B0=9D=EC=B2=B4=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- index.html | 1 + 1 file changed, 1 insertion(+) diff --git a/index.html b/index.html index 2da5a088..530e9974 100644 --- a/index.html +++ b/index.html @@ -10,6 +10,7 @@
+ From 2ed7c17cd3761b3f003639cee223b0102d754799 Mon Sep 17 00:00:00 2001 From: kimsuyeon_0916 Date: Wed, 24 Apr 2024 23:58:02 +0900 Subject: [PATCH 039/116] =?UTF-8?q?feat:=20#91=20modal=20DOM=20=EC=9A=94?= =?UTF-8?q?=EC=86=8C=EC=97=90=20=EB=A0=8C=EB=8D=94=EB=A7=81=20=EA=B0=80?= =?UTF-8?q?=EB=8A=A5=ED=95=9C=20ModalPortal=20=EA=B3=B5=ED=86=B5=20?= =?UTF-8?q?=EC=BB=B4=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 --- src/components/index.ts | 2 ++ src/components/modal/ModalPortal.tsx | 14 ++++++++++++++ 2 files changed, 16 insertions(+) create mode 100644 src/components/modal/ModalPortal.tsx diff --git a/src/components/index.ts b/src/components/index.ts index e820b521..38a14bc3 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -117,6 +117,7 @@ import PortfolioInformation from './portfolio/information/PortfolioInformation'; import PortfolioList from './portfolio/list/PortfolioList'; import ImageCarousel from './carousel/ImageCarousel'; import PortfolioImageUpload from './portfolio/image/upload/PortfolioImageUpload'; +import ModalPortal from './modal/ModalPortal'; export { Header, @@ -229,4 +230,5 @@ export { PortfolioList, ImageCarousel, PortfolioImageUpload, + ModalPortal, }; diff --git a/src/components/modal/ModalPortal.tsx b/src/components/modal/ModalPortal.tsx new file mode 100644 index 00000000..0fbd6c1b --- /dev/null +++ b/src/components/modal/ModalPortal.tsx @@ -0,0 +1,14 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; + +interface PortalProps { + children: React.ReactNode; +} + +const ModalPortal = ({ children }: PortalProps) => { + const modalRoot = document.getElementById('modal') as HTMLElement; + console.log(modalRoot); + return ReactDOM.createPortal(children, modalRoot); +}; + +export default ModalPortal; From 7f13cb0293c36459ce3d1e74bc84b4a9322e2feb Mon Sep 17 00:00:00 2001 From: kimsuyeon_0916 Date: Thu, 25 Apr 2024 00:03:22 +0900 Subject: [PATCH 040/116] =?UTF-8?q?style:=20#91=20=ED=8F=AC=ED=8A=B8?= =?UTF-8?q?=ED=8F=B4=EB=A6=AC=EC=98=A4=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=EB=AA=A8=EB=8B=AC=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20?= =?UTF-8?q?=EC=8A=A4=ED=83=80=EC=9D=BC=EB=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../image/modal/PortfolioImageModal.styled.ts | 145 ++++++++++++++++++ 1 file changed, 145 insertions(+) create mode 100644 src/components/portfolio/image/modal/PortfolioImageModal.styled.ts diff --git a/src/components/portfolio/image/modal/PortfolioImageModal.styled.ts b/src/components/portfolio/image/modal/PortfolioImageModal.styled.ts new file mode 100644 index 00000000..c12b8544 --- /dev/null +++ b/src/components/portfolio/image/modal/PortfolioImageModal.styled.ts @@ -0,0 +1,145 @@ +import styled from 'styled-components'; + +const PortfolioImageModalLayout = styled.div` + position: fixed; + left: 0; + top: 0; + display: flex; + align-items: center; + justify-content: center; + width: 100%; + height: 100%; + background: rgba(21, 21, 21, 0.4); +`; + +const PortfolioImageModalContainer = styled.div` + display: flex; + flex-direction: column; + align-items: center; + padding: 4rem; + width: clamp(30%, 52rem, 75%); + border-radius: 1rem; + border: 0.1rem solid var(--box_stroke, #e3e3e3); + background: var(--Grayscale-100, #f8fafb); + color: var(--Text-textColor2, var(--text-color-2, #373f41)); + + /* Body/body1/medium */ + font-size: 1.6rem; + font-weight: 500; + line-height: 1.9rem; + letter-spacing: 0.0032rem; + + h2 { + color: var(--Text-textColor1, #151515); + + /* Headline/h2 */ + font-size: 2.4rem; + font-weight: 700; + line-height: 2.9rem; /* 120.833% */ + letter-spacing: 0.0048rem; + } +`; + +const PortfolioImageModalHeader = styled.header` + display: flex; + flex-direction: column; + width: 100%; + + h2 { + margin-bottom: 2rem; + } +`; + +const PortfolioImageList = styled.ul` + flex: 1; + display: flex; + flex-direction: column; + row-gap: 0.4rem; + margin-bottom: 2.8rem; + max-height: 38rem; + + /* 스크롤바 스타일링 */ + overflow-y: auto; + &::-webkit-scrollbar { + width: 1.8rem; + } + &::-webkit-scrollbar-thumb { + background-color: #e3e3e3; + border-radius: 1rem; + background-clip: padding-box; + border: 0.5rem solid transparent; + } +`; + +const PortfolioImageItem = styled.li` + display: flex; + flex-direction: row; + min-height: 9.2rem; + align-items: center; + border-radius: 1rem; + border: 0.1rem solid var(--box_stroke, #e3e3e3); + background: var(--Form-fill-others, #fff); +`; + +const PortfolioImageListIcon = styled.span` + display: flex; + padding: 1.6rem; + align-items: center; + height: 100%; + border-right: 0.1rem solid var(--box_stroke, #e3e3e3); +`; + +const PortfolioImageWrapper = styled.div` + display: flex; + width: 10rem; + flex-direction: row; + margin: 1.6rem; + align-items: center; +`; + +const PortfolioImageModalRow = styled.div` + flex: 1; + display: flex; + flex-direction: row; + column-gap: 1.6rem; + align-items: center; +`; + +const PortfolioImageModalColumn = styled.div` + display: flex; + flex-direction: column; + width: 100%; +`; + +const PortfolioImageNumberIcon = styled.span` + box-sizing: border-box; + display: flex; + justify-content: center; + align-items: center; + margin: 0 1.6rem; + width: 2.8rem; + height: 2.8rem; + border-radius: 10rem; + background: var(--main-color, #5877fc); + color: var(--ButtonColors-Primary-outline-default, #fff); + + font-size: 1.6rem; + font-weight: 600; + line-height: 2.4rem; + letter-spacing: 0.0032rem; +`; + +const S = { + PortfolioImageModalLayout, + PortfolioImageModalContainer, + PortfolioImageModalHeader, + PortfolioImageList, + PortfolioImageItem, + PortfolioImageListIcon, + PortfolioImageWrapper, + PortfolioImageModalRow, + PortfolioImageModalColumn, + PortfolioImageNumberIcon, +}; + +export default S; From 91d354af42a9610a9db516c5566271be9c5cf13b Mon Sep 17 00:00:00 2001 From: kimsuyeon_0916 Date: Thu, 25 Apr 2024 00:16:08 +0900 Subject: [PATCH 041/116] =?UTF-8?q?feat:=20#91=20react-beautiful-dnd=20?= =?UTF-8?q?=EA=B8=B0=EB=B0=98=20=ED=8F=AC=ED=8A=B8=ED=8F=B4=EB=A6=AC?= =?UTF-8?q?=EC=98=A4=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EB=AA=A8=EB=8B=AC=20?= =?UTF-8?q?=EC=BB=B4=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 - 이미지 drag and drop 기능 --- src/components/index.ts | 2 + .../image/modal/PortfolioImageModal.tsx | 101 ++++++++++++++++++ 2 files changed, 103 insertions(+) create mode 100644 src/components/portfolio/image/modal/PortfolioImageModal.tsx diff --git a/src/components/index.ts b/src/components/index.ts index 38a14bc3..73de5af0 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -118,6 +118,7 @@ import PortfolioList from './portfolio/list/PortfolioList'; import ImageCarousel from './carousel/ImageCarousel'; import PortfolioImageUpload from './portfolio/image/upload/PortfolioImageUpload'; import ModalPortal from './modal/ModalPortal'; +import PortfolioImageModal from './portfolio/image/modal/PortfolioImageModal'; export { Header, @@ -231,4 +232,5 @@ export { ImageCarousel, PortfolioImageUpload, ModalPortal, + PortfolioImageModal, }; diff --git a/src/components/portfolio/image/modal/PortfolioImageModal.tsx b/src/components/portfolio/image/modal/PortfolioImageModal.tsx new file mode 100644 index 00000000..de11fae8 --- /dev/null +++ b/src/components/portfolio/image/modal/PortfolioImageModal.tsx @@ -0,0 +1,101 @@ +import React, { useState, useEffect } from 'react'; +import S from './PortfolioImageModal.styled'; +import { DragDropContext, Draggable, Droppable, DropResult } from 'react-beautiful-dnd'; +import { PortfolioCard, PrimaryBtn, DefaultBtn } from '../../..'; +import { useRecoilState } from 'recoil'; +import { uploadImageListState } from '../../../../atom'; +import { HambergerMenuIcon } from '../../../../assets'; + +const PortfolioImageModal = ({ onClose }: { onClose: () => void }) => { + const [uploadImageList, setUploadImageList] = useRecoilState(uploadImageListState); + const [changeImageList, setChangeImageList] = useState(uploadImageList); + + const orderImageList = () => { + setUploadImageList(changeImageList); + onClose(); + }; + + const onDragEnd = ({ source, destination }: DropResult) => { + if (!destination) return; + + const _items = [...changeImageList]; + const [targetItem] = _items.splice(source.index, 1); + _items.splice(destination.index, 0, targetItem); + + setChangeImageList(_items); + }; + + // --- requestAnimationFrame 초기화 + const [enabled, setEnabled] = useState(false); + + useEffect(() => { + const animation = requestAnimationFrame(() => setEnabled(true)); + + return () => { + cancelAnimationFrame(animation); + setEnabled(false); + }; + }, []); + + if (!enabled) { + return null; + } + // --- requestAnimationFrame 초기화 END + + return ( + + + +

슬라이드 순서 변경

+
+ + + {provided => ( + + + {[...changeImageList].map(({ fileName, url }, index) => ( + + {provided => ( + + + 햄버거메뉴아이콘 + + + + + + {fileName} + + {index + 1} + + )} + + ))} + + {provided.placeholder} + + )} + + + +
+ +
+
+ +
+
+
+
+ ); +}; + +export default PortfolioImageModal; From c33bf3de440b0075c62f63b559ed26ccaf9e3903 Mon Sep 17 00:00:00 2001 From: kimsuyeon_0916 Date: Thu, 25 Apr 2024 00:17:02 +0900 Subject: [PATCH 042/116] =?UTF-8?q?feat:=20#91=20=ED=8F=AC=ED=8A=B8?= =?UTF-8?q?=ED=8F=B4=EB=A6=AC=EC=98=A4=20=ED=8E=B8=EC=A7=91=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EB=82=B4,=20=ED=8F=AC=ED=8A=B8=ED=8F=B4?= =?UTF-8?q?=EB=A6=AC=EC=98=A4=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EB=AA=A8?= =?UTF-8?q?=EB=8B=AC=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=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 - 모달 오버레이 시, 스크롤 이벤트 방지 --- .../portfolio/edit/PortfolioEditPage.tsx | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/pages/portfolio/edit/PortfolioEditPage.tsx b/src/pages/portfolio/edit/PortfolioEditPage.tsx index 5b545f50..f1bdc09c 100644 --- a/src/pages/portfolio/edit/PortfolioEditPage.tsx +++ b/src/pages/portfolio/edit/PortfolioEditPage.tsx @@ -12,13 +12,15 @@ import { PrimaryBtn, MuiDatepicker, PortfolioImageUpload, + PortfolioImageModal, + ModalPortal, } from '../../../components'; import { useParams, useNavigate } from 'react-router-dom'; import { useForm, useFieldArray } from 'react-hook-form'; import { Link, Skill } from '../../../types'; import { useDebounce, useReadPortfolio, useReadRoleList, useReadSkillList } from '../../../hooks'; import PORTFOLIO_EDIT_DATA from './portfolioEditData'; -import { modules, formats } from '../../../utils'; +import { modules, formats, fixModalBackground } from '../../../utils'; import { Refresh } from '../../../assets'; import type ReactQuill from 'react-quill'; @@ -65,6 +67,13 @@ const PortfolioEditPage = () => { }, }); + // 이미지 순서 + const [modalOpen, setModalOpen] = useState(false); + + useEffect(() => { + fixModalBackground(modalOpen); + }, [modalOpen]); + // 분야 const fields = [{ id: '1', name: '개발' }]; @@ -146,7 +155,17 @@ const PortfolioEditPage = () => { {LABEL.image} - + setModalOpen(true)} + /> + {modalOpen && ( + + setModalOpen(false)} /> + + )} From 00a0337c3a5d7a143d69f34f5f0361bcef1e53a9 Mon Sep 17 00:00:00 2001 From: kimsuyeon_0916 Date: Thu, 25 Apr 2024 01:15:15 +0900 Subject: [PATCH 043/116] =?UTF-8?q?style:=20#91=20=ED=8F=AC=ED=8A=B8?= =?UTF-8?q?=ED=8F=B4=EB=A6=AC=EC=98=A4=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=EB=AA=A8=EB=8B=AC=20=EC=8A=A4=ED=83=80=EC=9D=BC=EB=A7=81=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../portfolio/image/modal/PortfolioImageModal.styled.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/portfolio/image/modal/PortfolioImageModal.styled.ts b/src/components/portfolio/image/modal/PortfolioImageModal.styled.ts index c12b8544..bdb20138 100644 --- a/src/components/portfolio/image/modal/PortfolioImageModal.styled.ts +++ b/src/components/portfolio/image/modal/PortfolioImageModal.styled.ts @@ -74,7 +74,7 @@ const PortfolioImageList = styled.ul` const PortfolioImageItem = styled.li` display: flex; flex-direction: row; - min-height: 9.2rem; + height: 9.2rem; align-items: center; border-radius: 1rem; border: 0.1rem solid var(--box_stroke, #e3e3e3); From f7108a0c1dceb802182b7bf3f4d005ee4cd3f649 Mon Sep 17 00:00:00 2001 From: kimsuyeon_0916 Date: Thu, 25 Apr 2024 01:25:56 +0900 Subject: [PATCH 044/116] =?UTF-8?q?style:=20#91=20=ED=8F=AC=ED=8A=B8?= =?UTF-8?q?=ED=8F=B4=EB=A6=AC=EC=98=A4=20=EC=B9=B4=EB=93=9C=20=EB=82=B4=20?= =?UTF-8?q?=EB=B2=84=ED=8A=BC=20=EC=8A=A4=ED=83=80=EC=9D=BC=EB=93=9C=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=EB=AA=85=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - PortfolioCardButton -> PortfolioCardNumberButton --- src/components/portfolio/card/PortfolioCard.styled.ts | 4 ++-- src/components/portfolio/card/PortfolioCard.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/portfolio/card/PortfolioCard.styled.ts b/src/components/portfolio/card/PortfolioCard.styled.ts index 3489b29d..2c2b11fd 100644 --- a/src/components/portfolio/card/PortfolioCard.styled.ts +++ b/src/components/portfolio/card/PortfolioCard.styled.ts @@ -76,7 +76,7 @@ const PortfolioCardTag = styled.span<{ $color?: string }>` } `; -const PortfolioCardButton = styled.button<{ $checked?: boolean }>` +const PortfolioCardNumberButton = styled.button<{ $checked?: boolean }>` position: absolute; bottom: 1rem; right: 1rem; @@ -106,7 +106,7 @@ const S = { PortfolioCardTitle, PortfolioTagRow, PortfolioCardTag, - PortfolioCardButton, + PortfolioCardNumberButton, }; export default S; diff --git a/src/components/portfolio/card/PortfolioCard.tsx b/src/components/portfolio/card/PortfolioCard.tsx index 7bd215c9..f360dddd 100644 --- a/src/components/portfolio/card/PortfolioCard.tsx +++ b/src/components/portfolio/card/PortfolioCard.tsx @@ -43,13 +43,13 @@ const PortfolioCard = ({ {isMainImage && 메인} {isEditable && ( - id && handleClick?.(id)} > {clickNumber !== 0 && clickNumber} - + )} {title && {title}} From 2c4e5acd057d787aab5f135da04c75414545a519 Mon Sep 17 00:00:00 2001 From: kimsuyeon_0916 Date: Thu, 25 Apr 2024 01:42:39 +0900 Subject: [PATCH 045/116] =?UTF-8?q?feat:=20#91=20=EC=97=B0=ED=95=84=20?= =?UTF-8?q?=EC=95=84=EC=9D=B4=EC=BD=98=20svg=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/assets/Pencil.svg | 3 +++ src/assets/index.ts | 2 ++ 2 files changed, 5 insertions(+) create mode 100644 src/assets/Pencil.svg diff --git a/src/assets/Pencil.svg b/src/assets/Pencil.svg new file mode 100644 index 00000000..48ad1227 --- /dev/null +++ b/src/assets/Pencil.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/index.ts b/src/assets/index.ts index 27481f7b..ba7b5549 100644 --- a/src/assets/index.ts +++ b/src/assets/index.ts @@ -36,6 +36,7 @@ import Test3 from './Test3.png'; import Test4 from './Test4.png'; import Refresh from './Refresh.svg'; import HambergerMenuIcon from './HamburgerMenuIcon.svg'; +import Pencil from './Pencil.svg'; export { Exit, @@ -76,4 +77,5 @@ export { Test4, Refresh, HambergerMenuIcon, + Pencil, }; From 7240af54fc1ed420e9b1e58ef087dfde8be746cd Mon Sep 17 00:00:00 2001 From: kimsuyeon_0916 Date: Thu, 25 Apr 2024 02:52:13 +0900 Subject: [PATCH 046/116] =?UTF-8?q?feat:=20#91=20=EC=98=B5=EC=85=98=20?= =?UTF-8?q?=EB=AA=A9=EB=A1=9D=20=EA=B3=B5=ED=86=B5=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20$style=20=ED=8C=8C=EB=9D=BC=EB=AF=B8?= =?UTF-8?q?=ED=84=B0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/optionList/OptionList.tsx | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/components/optionList/OptionList.tsx b/src/components/optionList/OptionList.tsx index 6822a0d3..39b86641 100644 --- a/src/components/optionList/OptionList.tsx +++ b/src/components/optionList/OptionList.tsx @@ -7,15 +7,22 @@ interface Option { } interface OptionListProps { - label?: T; name: T; handleOptionClick: (...rest: T[]) => void; optionList: Option[]; + label?: T; + $style?: T; } -const OptionList = ({ label, name, handleOptionClick, optionList }: OptionListProps) => { +const OptionList = ({ + label, + name, + handleOptionClick, + optionList, + ...props +}: OptionListProps) => { return ( - + {optionList.map(({ name: optionName, id }) => ( Date: Thu, 25 Apr 2024 02:53:36 +0900 Subject: [PATCH 047/116] =?UTF-8?q?style:=20#91=20=EC=98=B5=EC=85=98=20?= =?UTF-8?q?=EB=AA=A9=EB=A1=9D=20=EA=B3=B5=ED=86=B5=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20=EC=8A=A4=ED=83=80=EC=9D=BC=EB=A7=81=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/optionList/OptionList.styled.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/components/optionList/OptionList.styled.ts b/src/components/optionList/OptionList.styled.ts index 123e9fc0..738cd21a 100644 --- a/src/components/optionList/OptionList.styled.ts +++ b/src/components/optionList/OptionList.styled.ts @@ -1,10 +1,16 @@ import styled from 'styled-components'; -const OptionList = styled.ul<{ $label?: string }>` +interface OptionStyle { + $label?: string; + $style?: string; +} + +const OptionList = styled.ul` position: absolute; width: 100%; z-index: 5; top: ${props => (props.$label ? '7.9rem' : '5.4rem')}; + ${props => props.$style} display: flex; flex-direction: column; From 718e4647ed368a6b580a613970bf53a2471c1f1a Mon Sep 17 00:00:00 2001 From: kimsuyeon_0916 Date: Thu, 25 Apr 2024 02:59:20 +0900 Subject: [PATCH 048/116] =?UTF-8?q?style:=20#91=20=ED=8F=AC=ED=8A=B8?= =?UTF-8?q?=ED=8F=B4=EB=A6=AC=EC=98=A4=20=EC=B9=B4=EB=93=9C=20=EA=B3=B5?= =?UTF-8?q?=ED=86=B5=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EB=82=B4=20?= =?UTF-8?q?=EC=9D=B4=EB=AF=B8=EC=A7=80=20=ED=8E=B8=EC=A7=91=20=EB=B2=84?= =?UTF-8?q?=ED=8A=BC=20=EC=8A=A4=ED=83=80=EC=9D=BC=EB=A7=81=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../portfolio/card/PortfolioCard.styled.ts | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/src/components/portfolio/card/PortfolioCard.styled.ts b/src/components/portfolio/card/PortfolioCard.styled.ts index 2c2b11fd..fcbec834 100644 --- a/src/components/portfolio/card/PortfolioCard.styled.ts +++ b/src/components/portfolio/card/PortfolioCard.styled.ts @@ -1,11 +1,29 @@ import styled from 'styled-components'; -const PortfolioCardLayout = styled.article` +interface PortfolioCardStyle { + $open?: boolean; +} + +const PortfolioCardLayout = styled.article` + position: relative; display: flex; flex-direction: column; row-gap: 0.8rem; cursor: pointer; + ${props => + !props.$open && + ` + button:last-of-type { + display: none; + } + + &:hover { + button:last-of-type { + display: flex; + } + } + `} `; const PortfolioCardBox = styled.div<{ $isEditable?: boolean }>` @@ -16,6 +34,10 @@ const PortfolioCardBox = styled.div<{ $isEditable?: boolean }>` overflow: hidden; aspect-ratio: 183 / 103; // 포트폴리오 비율 + + button:first-of-type { + display: flex; + } `; const PortfolioCardImage = styled.img` @@ -99,6 +121,11 @@ const PortfolioCardNumberButton = styled.button<{ $checked?: boolean }>` letter-spacing: 0.0032rem; `; +const PortfolioCardIconButton = styled(PortfolioCardNumberButton)` + top: -1rem; + left: -1rem; +`; + const S = { PortfolioCardLayout, PortfolioCardBox, @@ -107,6 +134,7 @@ const S = { PortfolioTagRow, PortfolioCardTag, PortfolioCardNumberButton, + PortfolioCardIconButton, }; export default S; From cb936d9a9997a9dbb77f02f04e032b84b9597ec3 Mon Sep 17 00:00:00 2001 From: kimsuyeon_0916 Date: Thu, 25 Apr 2024 03:02:09 +0900 Subject: [PATCH 049/116] =?UTF-8?q?fix:=20#91=20=EC=98=B5=EC=85=98=20?= =?UTF-8?q?=EB=AA=A9=EB=A1=9D=20=EC=8A=A4=ED=83=80=EC=9D=BC=EB=93=9C=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20props=20=EC=97=90?= =?UTF-8?q?=EB=9F=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - label -> $label 프로퍼티 오타 수정 --- src/components/optionList/OptionList.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/optionList/OptionList.tsx b/src/components/optionList/OptionList.tsx index 39b86641..a8b06baf 100644 --- a/src/components/optionList/OptionList.tsx +++ b/src/components/optionList/OptionList.tsx @@ -22,7 +22,7 @@ const OptionList = ({ ...props }: OptionListProps) => { return ( - + {optionList.map(({ name: optionName, id }) => ( Date: Thu, 25 Apr 2024 04:37:15 +0900 Subject: [PATCH 050/116] =?UTF-8?q?feat:=20#91=20=ED=8F=AC=ED=8A=B8?= =?UTF-8?q?=ED=8F=B4=EB=A6=AC=EC=98=A4=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=EC=97=85=EB=A1=9C=EB=93=9C=20=EC=BB=B4=ED=8F=AC=EB=84=8C?= =?UTF-8?q?=ED=8A=B8=20=EB=A1=9C=EC=A7=81=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 포트폴리오 카드 공통 컴포넌트에 isImageEditable 프로퍼티 지정 --- .../portfolio/image/upload/PortfolioImageUpload.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/portfolio/image/upload/PortfolioImageUpload.tsx b/src/components/portfolio/image/upload/PortfolioImageUpload.tsx index 59f88917..771f8c46 100644 --- a/src/components/portfolio/image/upload/PortfolioImageUpload.tsx +++ b/src/components/portfolio/image/upload/PortfolioImageUpload.tsx @@ -8,7 +8,7 @@ import { imageSrcListState, uploadImageListState, } from '../../../../atom'; -import PortfolioCard from '../../card/PortfolioCard'; +import { PortfolioCard } from '../../..'; import { Image } from '../../../../types'; const MAX_IMAGE_SIZE_BYTES = 30 * 1024 * 1024; // 30MB @@ -73,10 +73,11 @@ const PortfolioImageUpload = (portfolioId?: { portfolioId?: string }) => { {[...uploadImageList].map((uploadImage, index) => ( ))} From 5048d1fb70dfa982593bacfed5eca34d51e6ad07 Mon Sep 17 00:00:00 2001 From: kimsuyeon_0916 Date: Thu, 25 Apr 2024 04:55:30 +0900 Subject: [PATCH 051/116] =?UTF-8?q?refactor:=20#91=20=ED=8F=AC=ED=8A=B8?= =?UTF-8?q?=ED=8F=B4=EB=A6=AC=EC=98=A4=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=EC=97=85=EB=A1=9C=EB=93=9C=20=EC=BB=B4=ED=8F=AC=EB=84=8C?= =?UTF-8?q?=ED=8A=B8=20=EB=A1=9C=EC=A7=81=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 불필요한 변수 제거 --- .../image/upload/PortfolioImageUpload.tsx | 32 ++++++------------- 1 file changed, 9 insertions(+), 23 deletions(-) diff --git a/src/components/portfolio/image/upload/PortfolioImageUpload.tsx b/src/components/portfolio/image/upload/PortfolioImageUpload.tsx index 771f8c46..80c9cf35 100644 --- a/src/components/portfolio/image/upload/PortfolioImageUpload.tsx +++ b/src/components/portfolio/image/upload/PortfolioImageUpload.tsx @@ -2,12 +2,7 @@ import React, { useRef } from 'react'; import S from './PortfolioImageUpload.styled'; import { Plus } from '../../../../assets'; import { useRecoilState } from 'recoil'; -import { - binaryImageListState, - imageNameListState, - imageSrcListState, - uploadImageListState, -} from '../../../../atom'; +import { uploadImageListState } from '../../../../atom'; import { PortfolioCard } from '../../..'; import { Image } from '../../../../types'; @@ -22,43 +17,34 @@ const PortfolioImageUpload = (portfolioId?: { portfolioId?: string }) => { // portfolioId 존재하는 경우에 presignedUrl API 호출 - const [imageNameList, setImageNameList] = useRecoilState(imageNameListState); // 추후에 nameList 받아와서 초기화 - const [imageSrcList, setImageSrcList] = useRecoilState(imageSrcListState); // 추후에 urlList 받아와서 초기화 - const [binaryImageList, setBinaryImageList] = useRecoilState(binaryImageListState); // 추후에 binaryList 받아와서 초기화 const [uploadImageList, setUploadImageList] = useRecoilState(uploadImageListState); // 추후에 받아온 정보 reduce로 조합해서 초기화 const changeImageList = (event: React.BaseSyntheticEvent) => { - const uploadImageList = event.target?.files; - for (let i = 0; i < uploadImageList.length && imageNameList.length + i < MAX_IMAGE_COUNT; i++) { + const imageList = event.target?.files; + for (let i = 0; i < imageList.length && uploadImageList.length + i < MAX_IMAGE_COUNT; i++) { if ( - imageNameList.find(imageName => imageName === uploadImageList[i].name) || - [...uploadImageList].find( - (image, index) => index !== i && image.name === uploadImageList[i].name - ) + uploadImageList.find(image => image.fileName === imageList[i].name) || + [...imageList].find((image, index) => index !== i && image.name === imageList[i].name) ) { continue; } - if (uploadImageList[i].size > MAX_IMAGE_SIZE_BYTES) { + if (imageList[i].size > MAX_IMAGE_SIZE_BYTES) { continue; } - setImageNameList(prev => [...prev, uploadImageList[i].name]); - let uploadImage: Image = { - fileName: uploadImageList[i].name, + fileName: imageList[i].name, }; const binaryReader = new FileReader(); - binaryReader.readAsArrayBuffer(uploadImageList[i]); + binaryReader.readAsArrayBuffer(imageList[i]); binaryReader.onload = () => { - setBinaryImageList(prev => [...prev, binaryReader.result] as ArrayBuffer[]); uploadImage = { ...uploadImage, binary: binaryReader.result as ArrayBuffer }; }; const urlReader = new FileReader(); - urlReader.readAsDataURL(uploadImageList[i]); + urlReader.readAsDataURL(imageList[i]); urlReader.onload = () => { - setImageSrcList(prev => [...prev, urlReader.result] as string[]); uploadImage = { ...uploadImage, url: urlReader.result as string }; setUploadImageList(prev => [...prev, uploadImage]); From fe15df59fdd55a3dc39b101443be213b1172a277 Mon Sep 17 00:00:00 2001 From: kimsuyeon_0916 Date: Thu, 25 Apr 2024 05:22:24 +0900 Subject: [PATCH 052/116] =?UTF-8?q?style:=20#91=20=ED=8F=AC=ED=8A=B8?= =?UTF-8?q?=ED=8F=B4=EB=A6=AC=EC=98=A4=20=EC=B9=B4=EB=93=9C=20=EA=B3=B5?= =?UTF-8?q?=ED=86=B5=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=8A=A4?= =?UTF-8?q?=ED=83=80=EC=9D=BC=EB=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 이미지 변경 시, 이미지 업로드 스타일링 --- src/components/portfolio/card/PortfolioCard.styled.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/components/portfolio/card/PortfolioCard.styled.ts b/src/components/portfolio/card/PortfolioCard.styled.ts index fcbec834..470c146c 100644 --- a/src/components/portfolio/card/PortfolioCard.styled.ts +++ b/src/components/portfolio/card/PortfolioCard.styled.ts @@ -126,6 +126,11 @@ const PortfolioCardIconButton = styled(PortfolioCardNumberButton)` left: -1rem; `; +const PortfolioImageInput = styled.input` + position: absolute; + display: none; +`; + const S = { PortfolioCardLayout, PortfolioCardBox, @@ -135,6 +140,7 @@ const S = { PortfolioCardTag, PortfolioCardNumberButton, PortfolioCardIconButton, + PortfolioImageInput, }; export default S; From 8156a3d863a67d91405536d6ddd3409e4968c58c Mon Sep 17 00:00:00 2001 From: kimsuyeon_0916 Date: Thu, 25 Apr 2024 05:25:21 +0900 Subject: [PATCH 053/116] =?UTF-8?q?feat:=20#91=20=ED=8F=AC=ED=8A=B8?= =?UTF-8?q?=ED=8F=B4=EB=A6=AC=EC=98=A4=20=EC=B9=B4=EB=93=9C=20=EA=B3=B5?= =?UTF-8?q?=ED=86=B5=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 이미지 편집 버튼 hover시 렌더링 - 클릭 시, 옵션 목록 렌더링 - 이미지 변경 및 삭제 --- .../portfolio/card/PortfolioCard.tsx | 97 ++++++++++++++++++- 1 file changed, 95 insertions(+), 2 deletions(-) diff --git a/src/components/portfolio/card/PortfolioCard.tsx b/src/components/portfolio/card/PortfolioCard.tsx index f360dddd..aef17062 100644 --- a/src/components/portfolio/card/PortfolioCard.tsx +++ b/src/components/portfolio/card/PortfolioCard.tsx @@ -1,7 +1,11 @@ -import React from 'react'; +import React, { useState, useRef, useEffect } from 'react'; import S from './PortfolioCard.styled'; import { useNavigate } from 'react-router'; -import { DefaultPortfolioImage } from '../../../assets'; +import { DefaultPortfolioImage, Pencil } from '../../../assets'; +import { OptionList } from '../..'; +import { useRecoilState } from 'recoil'; +import { uploadImageListState } from '../../../atom'; +import { Image } from '../../../types'; interface PortfolioCard { id?: string; @@ -11,10 +15,14 @@ interface PortfolioCard { role?: string; isMainImage?: boolean; isEditable?: boolean; + isImageEditable?: boolean; clickNumber?: number; handleClick?: (id: string) => void; } +const MAX_IMAGE_SIZE_BYTES = 30 * 1024 * 1024; // 30MB +const imageEditOptionList = [{ name: '이미지 변경' }, { name: '이미지 삭제' }]; + const PortfolioCard = ({ id, title, @@ -23,13 +31,77 @@ const PortfolioCard = ({ role, isMainImage, isEditable, + isImageEditable, clickNumber, handleClick, }: PortfolioCard) => { const navigate = useNavigate(); + const [isOpen, setIsOpen] = useState(false); + const buttonRef = useRef(null); + + useEffect(() => { + const handleOutsideClick = (e: MouseEvent) => { + const target = e.target as HTMLDivElement; + if (isOpen && buttonRef.current && !buttonRef.current.contains(target)) { + setIsOpen(false); + } + }; + + document.addEventListener('click', handleOutsideClick); + return () => document.removeEventListener('click', handleOutsideClick); + }, [isOpen]); + + const inputRef = useRef(null); + const [uploadImageList, setUploadImageList] = useRecoilState(uploadImageListState); + + const changeImage = (event: React.BaseSyntheticEvent) => { + const image = event.target?.files[0]; + if ( + uploadImageList.find(({ fileName }) => fileName === image.name) || + image.size > MAX_IMAGE_SIZE_BYTES + ) { + return; + } + + let uploadImage: Image = { + fileName: image.name, + }; + + const binaryReader = new FileReader(); + binaryReader.readAsArrayBuffer(image); + binaryReader.onload = () => { + uploadImage = { ...uploadImage, binary: binaryReader.result as ArrayBuffer }; + }; + + const urlReader = new FileReader(); + urlReader.readAsDataURL(image); + urlReader.onload = () => { + uploadImage = { ...uploadImage, url: urlReader.result as string }; + + const imageList = [...uploadImageList]; + imageList.splice((clickNumber as number) - 1, 1, uploadImage); + setUploadImageList(imageList); + }; + }; + + const deleteImage = () => { + const imageList = [...uploadImageList]; + imageList.splice((clickNumber as number) - 1, 1); + setUploadImageList(imageList); + }; + + const handleOptionClick = (name: string, optionName: string) => { + if (optionName === '이미지 변경') { + inputRef.current?.click(); + } else if (optionName === '이미지 삭제') { + deleteImage(); + } + }; + return ( (isEditable ? () => id && handleClick?.(id) : navigate(`/portfolio/${id}`))} > @@ -53,6 +125,27 @@ const PortfolioCard = ({ )} {title && {title}} + {isImageEditable && ( + <> + setIsOpen(true)} + > + 연필아이콘 + + {isOpen && ( + + )} + + )} + ); }; From 4b2c1a2dd225906b2682ffdc70cd4a72b1da43d8 Mon Sep 17 00:00:00 2001 From: kimsuyeon_0916 Date: Thu, 25 Apr 2024 09:06:36 +0900 Subject: [PATCH 054/116] =?UTF-8?q?feat:=20#91=20=ED=94=84=EB=A1=9C?= =?UTF-8?q?=ED=95=84=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EA=B3=B5=ED=86=B5=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EB=82=B4=20=ED=94=84?= =?UTF-8?q?=EB=A1=9C=ED=95=84=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=97=85?= =?UTF-8?q?=EB=A1=9C=EB=93=9C=20=EB=A1=9C=EC=A7=81=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/profile/image/ProfileImage.tsx | 32 ++++++++++++------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/src/components/profile/image/ProfileImage.tsx b/src/components/profile/image/ProfileImage.tsx index 4e03853d..df0f3b0d 100644 --- a/src/components/profile/image/ProfileImage.tsx +++ b/src/components/profile/image/ProfileImage.tsx @@ -2,8 +2,7 @@ import React, { useRef, useState } from 'react'; import S from './ProfileImage.styled'; import { AddProfile, DefaultProfileImage } from '../../../assets'; import { useNavigate } from 'react-router'; -import { useSetRecoilState } from 'recoil'; -import { imageNameState } from '../../../atom'; +import { Image } from '../../../types'; interface ProfileImage { isEditable?: boolean; @@ -23,26 +22,37 @@ const ProfileImage = ({ isEditable, userId, size, url }: ProfileImage) => { inputRef.current?.click(); }; - const [imageSrc, setImageSrc] = useState(url ? url : null); - const setImageNameState = useSetRecoilState(imageNameState); + const [uploadImage, setUploadImage] = useState({ fileName: '프로필사진', url: url }); const changeImage = (event: React.BaseSyntheticEvent) => { - const uploadImage = event.target?.files[0]; - setImageNameState(uploadImage.name); + const image = event.target?.files[0]; - const reader = new FileReader(); - reader.readAsDataURL(uploadImage); - reader.onload = () => setImageSrc(reader.result as string); + let newImage: Image = { + fileName: image.name, + }; + + const binaryReader = new FileReader(); + binaryReader.readAsArrayBuffer(image); + binaryReader.onload = () => { + newImage = { ...newImage, binary: binaryReader.result as ArrayBuffer }; + }; + + const urlReader = new FileReader(); + urlReader.readAsDataURL(image); + urlReader.onload = () => { + newImage = { ...newImage, url: urlReader.result as string }; + setUploadImage(newImage); + }; }; return ( - + {isEditable && ( <> From 0579dbafcca724f90379c2526eaa00ef38c149a5 Mon Sep 17 00:00:00 2001 From: kimsuyeon_0916 Date: Thu, 25 Apr 2024 12:11:24 +0900 Subject: [PATCH 055/116] =?UTF-8?q?feat:=20#91=20=ED=8F=AC=ED=8A=B8?= =?UTF-8?q?=ED=8F=B4=EB=A6=AC=EC=98=A4=20=EB=93=B1=EB=A1=9D=20=EC=97=94?= =?UTF-8?q?=EB=93=9C=ED=8F=AC=EC=9D=B8=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/service/endPoint.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/service/endPoint.ts b/src/service/endPoint.ts index 6a27fe88..2153a756 100644 --- a/src/service/endPoint.ts +++ b/src/service/endPoint.ts @@ -24,5 +24,6 @@ export const EndPoint = { /* portfolio */ PORTFOLIO: { read: (portfolioId: string) => `/portfolio/${portfolioId}`, + create: '/portfolio', }, }; From 530700745b1e04b06800bb14ee3860b3351768da Mon Sep 17 00:00:00 2001 From: kimsuyeon_0916 Date: Thu, 25 Apr 2024 12:12:11 +0900 Subject: [PATCH 056/116] =?UTF-8?q?feat:=20#91=20PortfolioPayload=20?= =?UTF-8?q?=ED=83=80=EC=9E=85=20=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/types/index.ts | 3 ++- src/types/payload.ts | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/types/index.ts b/src/types/index.ts index 0efd3d5d..021caebf 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,4 +1,4 @@ -import type { SignUpPayload, UpdateProfilePayload } from './payload'; +import type { SignUpPayload, UpdateProfilePayload, PortfolioPayload } from './payload'; import type { UserReponse, University, Department } from './response'; import type { CustomInstance } from './api'; import type { TitleInfo, OptionList, Option, Role, InputRoleForm, InputState } from './information'; @@ -35,4 +35,5 @@ export type { Search, PortfolioDetails, Image, + PortfolioPayload, }; diff --git a/src/types/payload.ts b/src/types/payload.ts index d3f1e551..507aa5e1 100644 --- a/src/types/payload.ts +++ b/src/types/payload.ts @@ -1,4 +1,4 @@ -import { Award, Link } from './index'; +import { Award, Link, PortfolioDetails } from './index'; export interface SignUpPayload { platformType?: string; @@ -31,3 +31,5 @@ export interface UpdateProfilePayload { links?: Link[]; portfolios?: string[]; } + +export type PortfolioPayload = Omit; From f7e0a94d223f9a002f46e4dc9749b90ad11468d3 Mon Sep 17 00:00:00 2001 From: kimsuyeon_0916 Date: Thu, 25 Apr 2024 12:12:40 +0900 Subject: [PATCH 057/116] =?UTF-8?q?feat:=20#91=20=ED=8F=AC=ED=8A=B8?= =?UTF-8?q?=ED=8F=B4=EB=A6=AC=EC=98=A4=20=EB=93=B1=EB=A1=9D=20API=20?= =?UTF-8?q?=ED=95=A8=EC=88=98=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/service/index.ts | 3 ++- src/service/portfolio/portfolio.ts | 15 ++++++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/service/index.ts b/src/service/index.ts index efc150aa..e2a8fd6e 100644 --- a/src/service/index.ts +++ b/src/service/index.ts @@ -10,7 +10,7 @@ import { } from './auth/auth'; import { readProfile, updateProfile } from './user/Profile'; import { readSkillList, readRoleList } from './search/search'; -import { readPortfolio } from './portfolio/portfolio'; +import { readPortfolio, createPortfolio } from './portfolio/portfolio'; export { EndPoint, @@ -27,4 +27,5 @@ export { readSkillList, readRoleList, readPortfolio, + createPortfolio, }; diff --git a/src/service/portfolio/portfolio.ts b/src/service/portfolio/portfolio.ts index 1a244563..7ddc26b9 100644 --- a/src/service/portfolio/portfolio.ts +++ b/src/service/portfolio/portfolio.ts @@ -1,4 +1,4 @@ -import { PortfolioDetails } from '../../types'; +import { PortfolioDetails, PortfolioPayload } from '../../types'; import { axiosAuthInstance } from '../axiosInstance'; import { EndPoint } from '../endPoint'; @@ -14,3 +14,16 @@ export const readPortfolio = async (portfolioId: string) => { return null; } }; + +export const createPortfolio = async (portfolio: PortfolioPayload) => { + try { + const response = await axiosAuthInstance.put(EndPoint.PORTFOLIO.create, { + ...portfolio, + }); + + return response; + } catch (error) { + console.error(error); + return null; + } +}; From 2ead7ed6ea2a71de8dde2dde22af3a6ce591c242 Mon Sep 17 00:00:00 2001 From: kimsuyeon_0916 Date: Thu, 25 Apr 2024 12:13:01 +0900 Subject: [PATCH 058/116] =?UTF-8?q?feat:=20#91=20=ED=8F=AC=ED=8A=B8?= =?UTF-8?q?=ED=8F=B4=EB=A6=AC=EC=98=A4=20=EB=93=B1=EB=A1=9D=20API=20?= =?UTF-8?q?=ED=98=B8=EC=B6=9C=20=EC=BB=A4=EC=8A=A4=ED=85=80=20=ED=9B=85=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/index.ts | 3 ++- src/hooks/usePortfolio.ts | 16 ++++++++++++++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/hooks/index.ts b/src/hooks/index.ts index 4b0a3aba..c6996e22 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -10,7 +10,7 @@ import { useReadProfile, useUpdateProfile } from './useProfile'; import useDebounce from './useDebounce'; import useValid from './useValid'; import { useReadSkillList, useReadRoleList } from './useSearch'; -import { useReadPortfolio } from './usePortfolio'; +import { useReadPortfolio, useCreatePortfolio } from './usePortfolio'; export { useCheckExist, @@ -26,4 +26,5 @@ export { useReadSkillList, useReadRoleList, useReadPortfolio, + useCreatePortfolio, }; diff --git a/src/hooks/usePortfolio.ts b/src/hooks/usePortfolio.ts index c9bf2eba..706250ad 100644 --- a/src/hooks/usePortfolio.ts +++ b/src/hooks/usePortfolio.ts @@ -1,5 +1,5 @@ -import { useQuery } from '@tanstack/react-query'; -import { readPortfolio } from '../service'; +import { useQuery, useMutation } from '@tanstack/react-query'; +import { createPortfolio, readPortfolio } from '../service'; const portfolioKeys = { readPortfolio: (portfolioId: string) => ['readPortfolio', portfolioId], @@ -14,3 +14,15 @@ export const useReadPortfolio = (portfolioId: string) => { queryFn: () => readPortfolio(portfolioId), }); }; + +/** + * @description 포트폴리오 등록 API를 호출하는 hook입니다. + */ +export const useCreatePortfolio = ({ onSuccess }: { onSuccess: () => void }) => { + return useMutation({ + mutationFn: createPortfolio, + onSuccess: () => { + onSuccess?.(); + }, + }); +}; From f1ce6a30b45c6c6b2935ef335caaa35f46a3e7cb Mon Sep 17 00:00:00 2001 From: kimsuyeon_0916 Date: Thu, 25 Apr 2024 12:16:54 +0900 Subject: [PATCH 059/116] =?UTF-8?q?feat:=20#91=20=ED=8F=AC=ED=8A=B8?= =?UTF-8?q?=ED=8F=B4=EB=A6=AC=EC=98=A4=20=ED=8E=B8=EC=A7=91=20=EC=97=94?= =?UTF-8?q?=EB=93=9C=ED=8F=AC=EC=9D=B8=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/service/endPoint.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/service/endPoint.ts b/src/service/endPoint.ts index 2153a756..4fd5a135 100644 --- a/src/service/endPoint.ts +++ b/src/service/endPoint.ts @@ -25,5 +25,6 @@ export const EndPoint = { PORTFOLIO: { read: (portfolioId: string) => `/portfolio/${portfolioId}`, create: '/portfolio', + update: (portfolioId: string) => `/portfolio/${portfolioId}`, }, }; From 051ad50870038f0f7c431a30ee62b8f6982b7555 Mon Sep 17 00:00:00 2001 From: kimsuyeon_0916 Date: Thu, 25 Apr 2024 12:24:21 +0900 Subject: [PATCH 060/116] =?UTF-8?q?feat:=20#91=20=ED=8F=AC=ED=8A=B8?= =?UTF-8?q?=ED=8F=B4=EB=A6=AC=EC=98=A4=20=ED=8E=B8=EC=A7=91=20API=20?= =?UTF-8?q?=ED=95=A8=EC=88=98=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/service/index.ts | 3 ++- src/service/portfolio/portfolio.ts | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/service/index.ts b/src/service/index.ts index e2a8fd6e..b49fc523 100644 --- a/src/service/index.ts +++ b/src/service/index.ts @@ -10,7 +10,7 @@ import { } from './auth/auth'; import { readProfile, updateProfile } from './user/Profile'; import { readSkillList, readRoleList } from './search/search'; -import { readPortfolio, createPortfolio } from './portfolio/portfolio'; +import { readPortfolio, createPortfolio, updatePortfolio } from './portfolio/portfolio'; export { EndPoint, @@ -28,4 +28,5 @@ export { readRoleList, readPortfolio, createPortfolio, + updatePortfolio, }; diff --git a/src/service/portfolio/portfolio.ts b/src/service/portfolio/portfolio.ts index 7ddc26b9..2f199fe8 100644 --- a/src/service/portfolio/portfolio.ts +++ b/src/service/portfolio/portfolio.ts @@ -27,3 +27,22 @@ export const createPortfolio = async (portfolio: PortfolioPayload) => { return null; } }; + +export const updatePortfolio = async ({ + portfolioId, + portfolio, +}: { + portfolioId: string; + portfolio: PortfolioPayload; +}) => { + try { + const response = await axiosAuthInstance.put(EndPoint.PORTFOLIO.update(portfolioId), { + ...portfolio, + }); + + return response; + } catch (error) { + console.error(error); + return null; + } +}; From 4ae2d8793f9b571a0f96feb8aef49d901b371f95 Mon Sep 17 00:00:00 2001 From: kimsuyeon_0916 Date: Thu, 25 Apr 2024 12:25:26 +0900 Subject: [PATCH 061/116] =?UTF-8?q?feat:=20#91=20=ED=8F=AC=ED=8A=B8?= =?UTF-8?q?=ED=8F=B4=EB=A6=AC=EC=98=A4=20=ED=8E=B8=EC=A7=91=20API=20?= =?UTF-8?q?=ED=98=B8=EC=B6=9C=20=EC=BB=A4=EC=8A=A4=ED=85=80=20=ED=9B=85=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/index.ts | 3 ++- src/hooks/usePortfolio.ts | 14 +++++++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/hooks/index.ts b/src/hooks/index.ts index c6996e22..15366784 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -10,7 +10,7 @@ import { useReadProfile, useUpdateProfile } from './useProfile'; import useDebounce from './useDebounce'; import useValid from './useValid'; import { useReadSkillList, useReadRoleList } from './useSearch'; -import { useReadPortfolio, useCreatePortfolio } from './usePortfolio'; +import { useReadPortfolio, useCreatePortfolio, useUpdatePortfolio } from './usePortfolio'; export { useCheckExist, @@ -27,4 +27,5 @@ export { useReadRoleList, useReadPortfolio, useCreatePortfolio, + useUpdatePortfolio, }; diff --git a/src/hooks/usePortfolio.ts b/src/hooks/usePortfolio.ts index 706250ad..1034dc5a 100644 --- a/src/hooks/usePortfolio.ts +++ b/src/hooks/usePortfolio.ts @@ -1,5 +1,5 @@ import { useQuery, useMutation } from '@tanstack/react-query'; -import { createPortfolio, readPortfolio } from '../service'; +import { createPortfolio, readPortfolio, updatePortfolio } from '../service'; const portfolioKeys = { readPortfolio: (portfolioId: string) => ['readPortfolio', portfolioId], @@ -26,3 +26,15 @@ export const useCreatePortfolio = ({ onSuccess }: { onSuccess: () => void }) => }, }); }; + +/** + * @description 포트폴리오 편집 API를 호출하는 hook입니다. + */ +export const useUpdatePortfolio = ({ onSuccess }: { onSuccess: () => void }) => { + return useMutation({ + mutationFn: updatePortfolio, + onSuccess: () => { + onSuccess?.(); + }, + }); +}; From fd70f94d1a189241128bd2bb72654e060da5b8a8 Mon Sep 17 00:00:00 2001 From: kimsuyeon_0916 Date: Thu, 25 Apr 2024 12:26:22 +0900 Subject: [PATCH 062/116] =?UTF-8?q?fix:=20#91=20=ED=8F=AC=ED=8A=B8?= =?UTF-8?q?=ED=8F=B4=EB=A6=AC=EC=98=A4=20=EB=93=B1=EB=A1=9D=20API=20http?= =?UTF-8?q?=20=EB=A9=94=EC=84=9C=EB=93=9C=20put=20->=20post=20=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/service/portfolio/portfolio.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/service/portfolio/portfolio.ts b/src/service/portfolio/portfolio.ts index 2f199fe8..235e79ad 100644 --- a/src/service/portfolio/portfolio.ts +++ b/src/service/portfolio/portfolio.ts @@ -17,7 +17,7 @@ export const readPortfolio = async (portfolioId: string) => { export const createPortfolio = async (portfolio: PortfolioPayload) => { try { - const response = await axiosAuthInstance.put(EndPoint.PORTFOLIO.create, { + const response = await axiosAuthInstance.post(EndPoint.PORTFOLIO.create, { ...portfolio, }); From b51bc8321917314a204fac92b10911e01b2b19ba Mon Sep 17 00:00:00 2001 From: kimsuyeon_0916 Date: Thu, 25 Apr 2024 13:34:18 +0900 Subject: [PATCH 063/116] =?UTF-8?q?fix:=20#91=20=ED=8F=AC=ED=8A=B8?= =?UTF-8?q?=ED=8F=B4=EB=A6=AC=EC=98=A4=20=EC=8A=A4=ED=82=AC=20=EC=9C=A0?= =?UTF-8?q?=ED=9A=A8=EC=84=B1=20=EA=B2=80=EC=82=AC=20=EC=83=81=EC=88=98=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 이유: 스킬이 여러개 있어도, 입력폼 값이 없을 때마다 유효성 검사 에러 발생 및 폼 제출 불가 --- src/constant/validation.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/constant/validation.ts b/src/constant/validation.ts index 47cc2ea2..51397596 100644 --- a/src/constant/validation.ts +++ b/src/constant/validation.ts @@ -45,9 +45,6 @@ export const INPUT_VALIDATION = { proceedType: { required: '진행방식을 설정해주세요', }, - skill: { - required: '스킬을 추가해주세요', - }, content: { required: true, }, From 37133e1cb21a6abb4b5e4cd5e1008d4c148580a1 Mon Sep 17 00:00:00 2001 From: kimsuyeon_0916 Date: Thu, 25 Apr 2024 13:34:39 +0900 Subject: [PATCH 064/116] =?UTF-8?q?feat:=20#91=20PortfolioPayload=20?= =?UTF-8?q?=ED=83=80=EC=9E=85=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/types/payload.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/types/payload.ts b/src/types/payload.ts index 507aa5e1..af15c086 100644 --- a/src/types/payload.ts +++ b/src/types/payload.ts @@ -32,4 +32,9 @@ export interface UpdateProfilePayload { portfolios?: string[]; } -export type PortfolioPayload = Omit; +export interface PortfolioPayload + extends Omit { + skills: string[]; + mainImageFileName: string; + zipFileName: string; +} From 9d48cf4550742b0d19908b8b1c8e7d72763900af Mon Sep 17 00:00:00 2001 From: kimsuyeon_0916 Date: Thu, 25 Apr 2024 15:23:41 +0900 Subject: [PATCH 065/116] =?UTF-8?q?feat:=20#91=20=ED=8F=AC=ED=8A=B8?= =?UTF-8?q?=ED=8F=B4=EB=A6=AC=EC=98=A4=20=EB=93=B1=EB=A1=9D=20API=20?= =?UTF-8?q?=EB=B0=98=ED=99=98=20=ED=83=80=EC=9E=85=20=EC=A7=80=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/service/portfolio/portfolio.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/service/portfolio/portfolio.ts b/src/service/portfolio/portfolio.ts index 235e79ad..b4322c33 100644 --- a/src/service/portfolio/portfolio.ts +++ b/src/service/portfolio/portfolio.ts @@ -17,7 +17,7 @@ export const readPortfolio = async (portfolioId: string) => { export const createPortfolio = async (portfolio: PortfolioPayload) => { try { - const response = await axiosAuthInstance.post(EndPoint.PORTFOLIO.create, { + const response = await axiosAuthInstance.post(EndPoint.PORTFOLIO.create, { ...portfolio, }); From 0f7b32635e691c4ab6802cb726ebbe684df07ee7 Mon Sep 17 00:00:00 2001 From: kimsuyeon_0916 Date: Thu, 25 Apr 2024 15:24:23 +0900 Subject: [PATCH 066/116] =?UTF-8?q?feat:=20#91=20=ED=8F=AC=ED=8A=B8?= =?UTF-8?q?=ED=8F=B4=EB=A6=AC=EC=98=A4=20=EB=93=B1=EB=A1=9D/=ED=8E=B8?= =?UTF-8?q?=EC=A7=91=20API=20=ED=98=B8=EC=B6=9C=20=EC=BB=A4=EC=8A=A4?= =?UTF-8?q?=ED=85=80=20=ED=9B=85=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/usePortfolio.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/hooks/usePortfolio.ts b/src/hooks/usePortfolio.ts index 1034dc5a..24835925 100644 --- a/src/hooks/usePortfolio.ts +++ b/src/hooks/usePortfolio.ts @@ -18,11 +18,14 @@ export const useReadPortfolio = (portfolioId: string) => { /** * @description 포트폴리오 등록 API를 호출하는 hook입니다. */ -export const useCreatePortfolio = ({ onSuccess }: { onSuccess: () => void }) => { +export const useCreatePortfolio = ({ onSuccess }: { onSuccess: (data: string) => void }) => { return useMutation({ mutationFn: createPortfolio, - onSuccess: () => { - onSuccess?.(); + onSuccess: data => { + if (data) { + onSuccess?.(data); + sessionStorage.clear(); + } }, }); }; @@ -35,6 +38,7 @@ export const useUpdatePortfolio = ({ onSuccess }: { onSuccess: () => void }) => mutationFn: updatePortfolio, onSuccess: () => { onSuccess?.(); + sessionStorage.clear(); }, }); }; From 129608eb52c20a1c6bf200e01b2a17cc0cbbce8c Mon Sep 17 00:00:00 2001 From: kimsuyeon_0916 Date: Thu, 25 Apr 2024 15:25:24 +0900 Subject: [PATCH 067/116] =?UTF-8?q?feat:=20#91=20=ED=8F=AC=ED=8A=B8?= =?UTF-8?q?=ED=8F=B4=EB=A6=AC=EC=98=A4=20=EB=93=B1=EB=A1=9D/=ED=8E=B8?= =?UTF-8?q?=EC=A7=91=20fetch=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../portfolio/edit/PortfolioEditPage.tsx | 89 ++++++++++++++++--- 1 file changed, 76 insertions(+), 13 deletions(-) diff --git a/src/pages/portfolio/edit/PortfolioEditPage.tsx b/src/pages/portfolio/edit/PortfolioEditPage.tsx index f1bdc09c..b3ee9944 100644 --- a/src/pages/portfolio/edit/PortfolioEditPage.tsx +++ b/src/pages/portfolio/edit/PortfolioEditPage.tsx @@ -16,13 +16,22 @@ import { ModalPortal, } from '../../../components'; import { useParams, useNavigate } from 'react-router-dom'; -import { useForm, useFieldArray } from 'react-hook-form'; -import { Link, Skill } from '../../../types'; -import { useDebounce, useReadPortfolio, useReadRoleList, useReadSkillList } from '../../../hooks'; +import { useForm, useFieldArray, SubmitHandler } from 'react-hook-form'; +import { Link, PortfolioPayload, Skill } from '../../../types'; +import { + useCreatePortfolio, + useDebounce, + useReadPortfolio, + useReadRoleList, + useReadSkillList, + useUpdatePortfolio, +} from '../../../hooks'; import PORTFOLIO_EDIT_DATA from './portfolioEditData'; import { modules, formats, fixModalBackground } from '../../../utils'; import { Refresh } from '../../../assets'; import type ReactQuill from 'react-quill'; +import { useRecoilValue } from 'recoil'; +import { uploadImageListState } from '../../../atom'; interface FormValues { title?: string; @@ -46,15 +55,15 @@ const PortfolioEditPage = () => { const { portfolioId } = useParams() as { portfolioId: string }; // undefined 인 경우(생성하는 경우) 로직 필요 const navigate = useNavigate(); - const { data: portfolio, isSuccess } = useReadPortfolio(portfolioId); + const { data: portfolio, isSuccess: isSuccessReadPortfolio } = useReadPortfolio(portfolioId); // 작성자가 아닌 경우, 편집 방지(상세페이지로 이동) useEffect(() => { - if (isSuccess) { + if (isSuccessReadPortfolio) { portfolioId && !portfolio?.isWriter && navigate(`/portfolio/${portfolioId}`); } - }, [isSuccess]); + }, [isSuccessReadPortfolio]); - const { register, formState, handleSubmit, control, watch, getValues, setValue, trigger } = + const { register, formState, handleSubmit, control, watch, getValues, setValue } = useForm({ mode: 'onChange', values: { @@ -67,7 +76,48 @@ const PortfolioEditPage = () => { }, }); - // 이미지 순서 + const createPortfolioInSuccess = (newPortfolioId: string) => { + navigate(`/portfolio/${newPortfolioId}`); + }; + const updatePortfolioInSuccess = () => { + navigate(`/portfolio/${portfolioId}`); + }; + + const { mutate: createPortfolio } = useCreatePortfolio({ + onSuccess: createPortfolioInSuccess, + }); + const { mutate: updatePortfolio } = useUpdatePortfolio({ + onSuccess: updatePortfolioInSuccess, + }); + + const uploadImageList = useRecoilValue(uploadImageListState); + + const submitHandler: SubmitHandler = data => { + // 이미지 압축 + // presignedUrl 을 이용해서 S3에 이미지 업로드 + + const portfolio = { + ...data, + mainImageFileName: uploadImageList[0].fileName, + zipFileName: 'test.zip', // 추후 변경해야 함 + fileOrder: uploadImageList.map(image => image.fileName), + field: sessionStorage.field, + role: sessionStorage.role, + proceedType: proceedType, + skills: skillList.map(skill => skill.id), + } as PortfolioPayload; + + if (portfolioId) { + updatePortfolio({ + portfolioId: portfolioId, + portfolio: portfolio, + }); + } else { + createPortfolio({ ...portfolio }); + } + }; + + // 이미지 순서 변경 모달 const [modalOpen, setModalOpen] = useState(false); useEffect(() => { @@ -131,15 +181,22 @@ const PortfolioEditPage = () => { }; useEffect(() => { - if (isSuccess) { + if (isSuccessReadPortfolio) { setProceedType(portfolio?.proceedType); setSkillList(portfolio?.skills ? portfolio?.skills : []); } - }, [isSuccess]); + }, [isSuccessReadPortfolio]); + + const checkKeyDown = (e: React.KeyboardEvent) => { + if (e.key === 'Enter') e.preventDefault(); + }; return ( <> - + checkKeyDown(e)} + >

포트폴리오 작성

@@ -167,7 +224,7 @@ const PortfolioEditPage = () => { )} - +

@@ -178,10 +235,16 @@ const PortfolioEditPage = () => { 기본 정보 {/* 포트폴리오 제목 */} - + {/* 포트폴리오 한줄 소개 */} From 9cda79c0f03e6ba9cafb9a8086690122788e2737 Mon Sep 17 00:00:00 2001 From: kimsuyeon_0916 Date: Thu, 25 Apr 2024 15:35:17 +0900 Subject: [PATCH 068/116] =?UTF-8?q?fix:=20#91=20=EB=B9=8C=EB=93=9C=20undef?= =?UTF-8?q?ined=20=ED=83=80=EC=9E=85=20=EC=97=90=EB=9F=AC=20=ED=95=B4?= =?UTF-8?q?=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/portfolio/edit/portfolioEditData.ts | 1 - src/pages/profile/edit/ProfileEditPage.tsx | 2 +- src/pages/profile/userData.ts | 140 ------------------ 3 files changed, 1 insertion(+), 142 deletions(-) diff --git a/src/pages/portfolio/edit/portfolioEditData.ts b/src/pages/portfolio/edit/portfolioEditData.ts index 1d398f0a..45118563 100644 --- a/src/pages/portfolio/edit/portfolioEditData.ts +++ b/src/pages/portfolio/edit/portfolioEditData.ts @@ -50,7 +50,6 @@ const skills = { type: 'text', placeholder: '보유 스킬을 검색해주세요', name: 'skills', - validation: INPUT_VALIDATION.skill, icon: { default: Search, arrow: 'right', diff --git a/src/pages/profile/edit/ProfileEditPage.tsx b/src/pages/profile/edit/ProfileEditPage.tsx index 799fd131..ff0032d4 100644 --- a/src/pages/profile/edit/ProfileEditPage.tsx +++ b/src/pages/profile/edit/ProfileEditPage.tsx @@ -80,7 +80,7 @@ const ProfileEditPage = () => { }); const submitHandler: SubmitHandler = data => { - const { imageUrl, interest, ...updateData } = data; + const { interest, ...updateData } = data; mutate({ ...updateData, imageFileName: profileImageName, diff --git a/src/pages/profile/userData.ts b/src/pages/profile/userData.ts index 46301cb9..2a213996 100644 --- a/src/pages/profile/userData.ts +++ b/src/pages/profile/userData.ts @@ -55,36 +55,16 @@ export const userData: User = { id: '1', mainImageUrl: '', title: '[Meeteam] 대학생 포트폴리오 기반 구인 플랫폼', - description: '', - startDate: '2023년 11월 02일', - endDate: '2023년 03월 15일', field: '개발', role: '프론트엔드', - skills: [ - { id: '1', name: 'Node.js' }, - { id: '1', name: 'Figma' }, - { id: '1', name: 'TypeScript' }, - { id: '1', name: 'React' }, - { id: '1', name: 'NextJs' }, - ], pinned: true, pinOrder: '1', }, { id: '2', title: '[Meeteam] 대학생 포트폴리오 기반 구인 플랫폼', - description: '', - startDate: '2023년 11월 02일', - endDate: '2023년 03월 15일', field: '개발', role: '프론트엔드', - skills: [ - { id: '1', name: 'Node.js' }, - { id: '1', name: 'Figma' }, - { id: '1', name: 'TypeScript' }, - { id: '1', name: 'React' }, - { id: '1', name: 'NextJs' }, - ], pinned: true, pinOrder: '2', }, @@ -92,18 +72,8 @@ export const userData: User = { id: '3', mainImageUrl: '', title: '[Meeteam] 대학생 포트폴리오 기반 구인 플랫폼', - description: '', - startDate: '2023년 11월 02일', - endDate: '2023년 03월 15일', field: '개발', role: '프론트엔드', - skills: [ - { id: '1', name: 'Node.js' }, - { id: '1', name: 'Figma' }, - { id: '1', name: 'TypeScript' }, - { id: '1', name: 'React' }, - { id: '1', name: 'NextJs' }, - ], pinned: true, pinOrder: '3', }, @@ -111,18 +81,8 @@ export const userData: User = { id: '4', mainImageUrl: '', title: '[Meeteam] 대학생 포트폴리오 기반 구인 플랫폼', - description: '', - startDate: '2023년 11월 02일', - endDate: '2023년 03월 15일', field: '개발', role: '프론트엔드', - skills: [ - { id: '1', name: 'Node.js' }, - { id: '1', name: 'Figma' }, - { id: '1', name: 'TypeScript' }, - { id: '1', name: 'React' }, - { id: '1', name: 'NextJs' }, - ], pinned: false, pinOrder: '0', }, @@ -130,18 +90,8 @@ export const userData: User = { id: '5', mainImageUrl: '', title: '[Meeteam] 대학생 포트폴리오 기반 구인 플랫폼', - description: '', - startDate: '2023년 11월 02일', - endDate: '2023년 03월 15일', field: '개발', role: '프론트엔드', - skills: [ - { id: '1', name: 'Node.js' }, - { id: '1', name: 'Figma' }, - { id: '1', name: 'TypeScript' }, - { id: '1', name: 'React' }, - { id: '1', name: 'NextJs' }, - ], pinned: true, pinOrder: '4', }, @@ -149,18 +99,8 @@ export const userData: User = { id: '6', mainImageUrl: '', title: '[Meeteam] 대학생 포트폴리오 기반 구인 플랫폼', - description: '', - startDate: '2023년 11월 02일', - endDate: '2023년 03월 15일', field: '개발', role: '프론트엔드', - skills: [ - { id: '1', name: 'Node.js' }, - { id: '1', name: 'Figma' }, - { id: '1', name: 'TypeScript' }, - { id: '1', name: 'React' }, - { id: '1', name: 'NextJs' }, - ], pinned: false, pinOrder: '0', }, @@ -168,18 +108,8 @@ export const userData: User = { id: '7', mainImageUrl: '', title: '[Meeteam] 대학생 포트폴리오 기반 구인 플랫폼', - description: '', - startDate: '2023년 11월 02일', - endDate: '2023년 03월 15일', field: '개발', role: '프론트엔드', - skills: [ - { id: '1', name: 'Node.js' }, - { id: '1', name: 'Figma' }, - { id: '1', name: 'TypeScript' }, - { id: '1', name: 'React' }, - { id: '1', name: 'NextJs' }, - ], pinned: false, pinOrder: '0', }, @@ -187,18 +117,8 @@ export const userData: User = { id: '8', mainImageUrl: '', title: '[Meeteam] 대학생 포트폴리오 기반 구인 플랫폼', - description: '', - startDate: '2023년 11월 02일', - endDate: '2023년 03월 15일', field: '개발', role: '프론트엔드', - skills: [ - { id: '1', name: 'Node.js' }, - { id: '1', name: 'Figma' }, - { id: '1', name: 'TypeScript' }, - { id: '1', name: 'React' }, - { id: '1', name: 'NextJs' }, - ], pinned: true, pinOrder: '5', }, @@ -206,18 +126,8 @@ export const userData: User = { id: '9', mainImageUrl: '', title: '[Meeteam] 대학생 포트폴리오 기반 구인 플랫폼', - description: '', - startDate: '2023년 11월 02일', - endDate: '2023년 03월 15일', field: '개발', role: '프론트엔드', - skills: [ - { id: '1', name: 'Node.js' }, - { id: '1', name: 'Figma' }, - { id: '1', name: 'TypeScript' }, - { id: '1', name: 'React' }, - { id: '1', name: 'NextJs' }, - ], pinned: true, pinOrder: '6', }, @@ -225,18 +135,8 @@ export const userData: User = { id: '10', mainImageUrl: '', title: '[Meeteam] 대학생 포트폴리오 기반 구인 플랫폼', - description: '', - startDate: '2023년 11월 02일', - endDate: '2023년 03월 15일', field: '개발', role: '프론트엔드', - skills: [ - { id: '1', name: 'Node.js' }, - { id: '1', name: 'Figma' }, - { id: '1', name: 'TypeScript' }, - { id: '1', name: 'React' }, - { id: '1', name: 'NextJs' }, - ], pinned: false, pinOrder: '0', }, @@ -244,18 +144,8 @@ export const userData: User = { id: '11', mainImageUrl: '', title: '[Meeteam] 대학생 포트폴리오 기반 구인 플랫폼', - description: '', - startDate: '2023년 11월 02일', - endDate: '2023년 03월 15일', field: '개발', role: '프론트엔드', - skills: [ - { id: '1', name: 'Node.js' }, - { id: '1', name: 'Figma' }, - { id: '1', name: 'TypeScript' }, - { id: '1', name: 'React' }, - { id: '1', name: 'NextJs' }, - ], pinned: false, pinOrder: '0', }, @@ -263,18 +153,8 @@ export const userData: User = { id: '12', mainImageUrl: '', title: '[Meeteam] 대학생 포트폴리오 기반 구인 플랫폼', - description: '', - startDate: '2023년 11월 02일', - endDate: '2023년 03월 15일', field: '개발', role: '프론트엔드', - skills: [ - { id: '1', name: 'Node.js' }, - { id: '1', name: 'Figma' }, - { id: '1', name: 'TypeScript' }, - { id: '1', name: 'React' }, - { id: '1', name: 'NextJs' }, - ], pinned: false, pinOrder: '0', }, @@ -282,18 +162,8 @@ export const userData: User = { id: '13', mainImageUrl: '', title: '[Meeteam] 대학생 포트폴리오 기반 구인 플랫폼', - description: '', - startDate: '2023년 11월 02일', - endDate: '2023년 03월 15일', field: '개발', role: '프론트엔드', - skills: [ - { id: '1', name: 'Node.js' }, - { id: '1', name: 'Figma' }, - { id: '1', name: 'TypeScript' }, - { id: '1', name: 'React' }, - { id: '1', name: 'NextJs' }, - ], pinned: false, pinOrder: '0', }, @@ -301,18 +171,8 @@ export const userData: User = { id: '14', mainImageUrl: '', title: '[Meeteam] 대학생 포트폴리오 기반 구인 플랫폼', - description: '', - startDate: '2023년 11월 02일', - endDate: '2023년 03월 15일', field: '개발', role: '프론트엔드', - skills: [ - { id: '1', name: 'Node.js' }, - { id: '1', name: 'Figma' }, - { id: '1', name: 'TypeScript' }, - { id: '1', name: 'React' }, - { id: '1', name: 'NextJs' }, - ], pinned: false, pinOrder: '0', }, From e72bd4f172786d8608bc652867e6cb7a19dfca0a Mon Sep 17 00:00:00 2001 From: kimsuyeon_0916 Date: Thu, 25 Apr 2024 16:26:32 +0900 Subject: [PATCH 069/116] =?UTF-8?q?feat:=20#96=20=EB=8B=A8=EC=9D=BC=20?= =?UTF-8?q?=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=97=85=EB=A1=9C=EB=93=9C=20?= =?UTF-8?q?=EC=9C=84=ED=95=9C=20presignedURL=20=EB=B0=9C=EA=B8=89=20?= =?UTF-8?q?=EC=97=94=EB=93=9C=ED=8F=AC=EC=9D=B8=ED=8A=B8=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/service/endPoint.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/service/endPoint.ts b/src/service/endPoint.ts index 4fd5a135..b86a0800 100644 --- a/src/service/endPoint.ts +++ b/src/service/endPoint.ts @@ -27,4 +27,9 @@ export const EndPoint = { create: '/portfolio', update: (portfolioId: string) => `/portfolio/${portfolioId}`, }, + + /* presignedURL */ + UPLOAD_IMAGE: { + profile: '/profile/pre-signed-url', + }, }; From cbb7e8bb3c337f91f839778e3d1f434a6dba8eee Mon Sep 17 00:00:00 2001 From: kimsuyeon_0916 Date: Thu, 25 Apr 2024 16:26:45 +0900 Subject: [PATCH 070/116] =?UTF-8?q?feat:=20#96=20=EB=8B=A4=EC=A4=91=20?= =?UTF-8?q?=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=97=85=EB=A1=9C=EB=93=9C=20?= =?UTF-8?q?=EC=9C=84=ED=95=9C=20presignedURL=20=EB=B0=9C=EA=B8=89=20?= =?UTF-8?q?=EC=97=94=EB=93=9C=ED=8F=AC=EC=9D=B8=ED=8A=B8=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/service/endPoint.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/service/endPoint.ts b/src/service/endPoint.ts index b86a0800..984cdf10 100644 --- a/src/service/endPoint.ts +++ b/src/service/endPoint.ts @@ -31,5 +31,6 @@ export const EndPoint = { /* presignedURL */ UPLOAD_IMAGE: { profile: '/profile/pre-signed-url', + portfolio: '/portfolio/pre-signed-url', }, }; From e6249812feb3fb241563b34534522ff87850f232 Mon Sep 17 00:00:00 2001 From: kimsuyeon_0916 Date: Thu, 25 Apr 2024 16:35:50 +0900 Subject: [PATCH 071/116] =?UTF-8?q?feat:=20#96=20ImageResponse=20=ED=83=80?= =?UTF-8?q?=EC=9E=85=20=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/types/index.ts | 3 ++- src/types/response.ts | 6 ++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/types/index.ts b/src/types/index.ts index 021caebf..27cf55f2 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,5 +1,5 @@ import type { SignUpPayload, UpdateProfilePayload, PortfolioPayload } from './payload'; -import type { UserReponse, University, Department } from './response'; +import type { UserReponse, University, Department, ImageResponse } from './response'; import type { CustomInstance } from './api'; import type { TitleInfo, OptionList, Option, Role, InputRoleForm, InputState } from './information'; import type { Comment, CommentInputFunctions } from './comment'; @@ -36,4 +36,5 @@ export type { PortfolioDetails, Image, PortfolioPayload, + ImageResponse, }; diff --git a/src/types/response.ts b/src/types/response.ts index 2347ebc6..0a5ffcdc 100644 --- a/src/types/response.ts +++ b/src/types/response.ts @@ -18,3 +18,9 @@ export interface Department { departmentId: string; departmentName: string; } + +export interface ImageResponse { + serviceType: string; + fileName: string; + url: string; +} From f2ce18794f7b01e84e04c06ff4a8e76c0cbdd4a9 Mon Sep 17 00:00:00 2001 From: kimsuyeon_0916 Date: Thu, 25 Apr 2024 18:03:55 +0900 Subject: [PATCH 072/116] =?UTF-8?q?feat:=20#96=20=EB=8B=A8=EC=9D=BC=20?= =?UTF-8?q?=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=97=85=EB=A1=9C=EB=93=9C=20?= =?UTF-8?q?=EC=9C=84=ED=95=9C=20presignedURL=20=EC=A1=B0=ED=9A=8C=20API=20?= =?UTF-8?q?=ED=95=A8=EC=88=98=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/service/image/image.ts | 18 ++++++++++++++++++ src/service/index.ts | 2 ++ 2 files changed, 20 insertions(+) create mode 100644 src/service/image/image.ts diff --git a/src/service/image/image.ts b/src/service/image/image.ts new file mode 100644 index 00000000..ecc32ab4 --- /dev/null +++ b/src/service/image/image.ts @@ -0,0 +1,18 @@ +import { ImageResponse } from '../../types'; +import { axiosAuthInstance } from '../axiosInstance'; +import { EndPoint } from '../endPoint'; + +export const readImagePresignedUrl = async (fileName: string) => { + try { + const response = await axiosAuthInstance.get(EndPoint.UPLOAD_IMAGE.profile, { + params: { + 'file-name': fileName, + }, + }); + + return response; + } catch (error) { + console.error(error); + return null; + } +}; diff --git a/src/service/index.ts b/src/service/index.ts index b49fc523..40372a64 100644 --- a/src/service/index.ts +++ b/src/service/index.ts @@ -11,6 +11,7 @@ import { import { readProfile, updateProfile } from './user/Profile'; import { readSkillList, readRoleList } from './search/search'; import { readPortfolio, createPortfolio, updatePortfolio } from './portfolio/portfolio'; +import { readImagePresignedUrl } from './image/image'; export { EndPoint, @@ -29,4 +30,5 @@ export { readPortfolio, createPortfolio, updatePortfolio, + readImagePresignedUrl, }; From 765595db18240d1c06404a6fa82149718376ff8a Mon Sep 17 00:00:00 2001 From: kimsuyeon_0916 Date: Thu, 25 Apr 2024 18:04:10 +0900 Subject: [PATCH 073/116] =?UTF-8?q?feat:=20#96=20=EB=8B=A4=EC=A4=91=20?= =?UTF-8?q?=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=97=85=EB=A1=9C=EB=93=9C=20?= =?UTF-8?q?=EC=9C=84=ED=95=9C=20presignedURL=20=EC=A1=B0=ED=9A=8C=20API=20?= =?UTF-8?q?=ED=95=A8=EC=88=98=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/service/image/image.ts | 22 ++++++++++++++++++++++ src/service/index.ts | 3 ++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/service/image/image.ts b/src/service/image/image.ts index ecc32ab4..088e3675 100644 --- a/src/service/image/image.ts +++ b/src/service/image/image.ts @@ -16,3 +16,25 @@ export const readImagePresignedUrl = async (fileName: string) => { return null; } }; + +export const readImageListPresignedUrl = async ({ + fileName, + portfolioId, +}: { + fileName: string; + portfolioId?: string; +}) => { + try { + const response = await axiosAuthInstance.get(EndPoint.UPLOAD_IMAGE.portfolio, { + params: { + 'file-name': fileName, + portfolio: portfolioId, + }, + }); + + return response; + } catch (error) { + console.error(error); + return null; + } +}; diff --git a/src/service/index.ts b/src/service/index.ts index 40372a64..e14f734c 100644 --- a/src/service/index.ts +++ b/src/service/index.ts @@ -11,7 +11,7 @@ import { import { readProfile, updateProfile } from './user/Profile'; import { readSkillList, readRoleList } from './search/search'; import { readPortfolio, createPortfolio, updatePortfolio } from './portfolio/portfolio'; -import { readImagePresignedUrl } from './image/image'; +import { readImagePresignedUrl, readImageListPresignedUrl } from './image/image'; export { EndPoint, @@ -31,4 +31,5 @@ export { createPortfolio, updatePortfolio, readImagePresignedUrl, + readImageListPresignedUrl, }; From d201cb15b5bbab94989bab7e6cecf88a7413761c Mon Sep 17 00:00:00 2001 From: kimsuyeon_0916 Date: Thu, 25 Apr 2024 18:05:28 +0900 Subject: [PATCH 074/116] =?UTF-8?q?feat:=20#96=20=EB=8B=A8=EC=9D=BC=20?= =?UTF-8?q?=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=97=85=EB=A1=9C=EB=93=9C=20?= =?UTF-8?q?=EC=9C=84=ED=95=9C=20presignedURL=20=EC=A1=B0=ED=9A=8C=20API=20?= =?UTF-8?q?=ED=95=A8=EC=88=98=20=ED=98=B8=EC=B6=9C=20=EC=BB=A4=EC=8A=A4?= =?UTF-8?q?=ED=85=80=20=ED=9B=85=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/index.ts | 2 ++ src/hooks/useImage.ts | 16 ++++++++++++++++ 2 files changed, 18 insertions(+) create mode 100644 src/hooks/useImage.ts diff --git a/src/hooks/index.ts b/src/hooks/index.ts index 15366784..7b41d14c 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -11,6 +11,7 @@ import useDebounce from './useDebounce'; import useValid from './useValid'; import { useReadSkillList, useReadRoleList } from './useSearch'; import { useReadPortfolio, useCreatePortfolio, useUpdatePortfolio } from './usePortfolio'; +import { useReadImagePresignedUrl } from './useImage'; export { useCheckExist, @@ -28,4 +29,5 @@ export { useReadPortfolio, useCreatePortfolio, useUpdatePortfolio, + useReadImagePresignedUrl, }; diff --git a/src/hooks/useImage.ts b/src/hooks/useImage.ts new file mode 100644 index 00000000..b5d5e38e --- /dev/null +++ b/src/hooks/useImage.ts @@ -0,0 +1,16 @@ +import { useQuery } from '@tanstack/react-query'; +import { readImagePresignedUrl } from '../service'; + +const imageKeys = { + readImagePresignedUrl: (fileName: string) => ['readImagePresignedUrl', fileName], +}; + +/** + * @description 단일 이미지 업로드를 위한 presignedURL 조회 API를 호출하는 hook입니다. + */ +export const useReadImagePresignedUrl = (fileName: string) => { + return useQuery({ + queryKey: imageKeys.readImagePresignedUrl(fileName), + queryFn: () => readImagePresignedUrl(fileName), + }); +}; From e1a3131761ddbd4319125be51bf355e0ce02e513 Mon Sep 17 00:00:00 2001 From: kimsuyeon_0916 Date: Thu, 25 Apr 2024 18:05:41 +0900 Subject: [PATCH 075/116] =?UTF-8?q?feat:=20#96=20=EB=8B=A4=EC=A4=91=20?= =?UTF-8?q?=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=97=85=EB=A1=9C=EB=93=9C=20?= =?UTF-8?q?=EC=9C=84=ED=95=9C=20presignedURL=20=EC=A1=B0=ED=9A=8C=20API=20?= =?UTF-8?q?=ED=95=A8=EC=88=98=20=ED=98=B8=EC=B6=9C=20=EC=BB=A4=EC=8A=A4?= =?UTF-8?q?=ED=85=80=20=ED=9B=85=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/index.ts | 3 ++- src/hooks/useImage.ts | 25 ++++++++++++++++++++++++- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/hooks/index.ts b/src/hooks/index.ts index 7b41d14c..f7338612 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -11,7 +11,7 @@ import useDebounce from './useDebounce'; import useValid from './useValid'; import { useReadSkillList, useReadRoleList } from './useSearch'; import { useReadPortfolio, useCreatePortfolio, useUpdatePortfolio } from './usePortfolio'; -import { useReadImagePresignedUrl } from './useImage'; +import { useReadImagePresignedUrl, useReadImageListPresignedUrl } from './useImage'; export { useCheckExist, @@ -30,4 +30,5 @@ export { useCreatePortfolio, useUpdatePortfolio, useReadImagePresignedUrl, + useReadImageListPresignedUrl, }; diff --git a/src/hooks/useImage.ts b/src/hooks/useImage.ts index b5d5e38e..a144b759 100644 --- a/src/hooks/useImage.ts +++ b/src/hooks/useImage.ts @@ -1,8 +1,15 @@ import { useQuery } from '@tanstack/react-query'; -import { readImagePresignedUrl } from '../service'; +import { readImagePresignedUrl, readImageListPresignedUrl } from '../service'; const imageKeys = { readImagePresignedUrl: (fileName: string) => ['readImagePresignedUrl', fileName], + readImageListPresignedUrl: ({ + fileName, + portfolioId, + }: { + fileName: string; + portfolioId?: string; + }) => ['readImageListPresignedUrl', fileName, portfolioId], }; /** @@ -14,3 +21,19 @@ export const useReadImagePresignedUrl = (fileName: string) => { queryFn: () => readImagePresignedUrl(fileName), }); }; + +/** + * @description 다중 이미지 업로드를 위한 presignedURL 조회 API를 호출하는 hook입니다. + */ +export const useReadImageListPresignedUrl = ({ + fileName, + portfolioId, +}: { + fileName: string; + portfolioId?: string; +}) => { + return useQuery({ + queryKey: imageKeys.readImageListPresignedUrl({ fileName, portfolioId }), + queryFn: () => readImageListPresignedUrl({ fileName, portfolioId }), + }); +}; From d1d137fec4b8e489d6e66b05108b92e761eb8833 Mon Sep 17 00:00:00 2001 From: kimsuyeon_0916 Date: Thu, 25 Apr 2024 20:46:25 +0900 Subject: [PATCH 076/116] =?UTF-8?q?feat:=20#96=20presignedUrl=20=EC=9D=B4?= =?UTF-8?q?=EC=9A=A9=ED=95=98=EC=97=AC=20S3=EC=97=90=20=EC=9D=B4=EB=AF=B8?= =?UTF-8?q?=EC=A7=80=20=EC=97=85=EB=A1=9C=EB=93=9C=20API=20=ED=95=A8?= =?UTF-8?q?=EC=88=98=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/service/image/image.ts | 30 +++++++++++++++++++++++++++++- src/service/index.ts | 3 ++- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/service/image/image.ts b/src/service/image/image.ts index 088e3675..5647f0b6 100644 --- a/src/service/image/image.ts +++ b/src/service/image/image.ts @@ -1,7 +1,12 @@ import { ImageResponse } from '../../types'; -import { axiosAuthInstance } from '../axiosInstance'; +import { axiosAuthInstance, axiosInstance } from '../axiosInstance'; import { EndPoint } from '../endPoint'; +interface UploadImageFile { + presignedUrl: string; + imageFile: ArrayBuffer; +} + export const readImagePresignedUrl = async (fileName: string) => { try { const response = await axiosAuthInstance.get(EndPoint.UPLOAD_IMAGE.profile, { @@ -38,3 +43,26 @@ export const readImageListPresignedUrl = async ({ return null; } }; + +const axiosConfig = { + headers: { + 'Content-Type': 'application/octet-stream', + }, +}; + +export const uploadImageFile = async ({ presignedUrl, imageFile }: UploadImageFile) => { + try { + const response = await axiosInstance.put( + presignedUrl, + { ...imageFile }, + { + ...axiosConfig, + } + ); + + return response; + } catch (error) { + console.error(error); + return null; + } +}; diff --git a/src/service/index.ts b/src/service/index.ts index e14f734c..fd831154 100644 --- a/src/service/index.ts +++ b/src/service/index.ts @@ -11,7 +11,7 @@ import { import { readProfile, updateProfile } from './user/Profile'; import { readSkillList, readRoleList } from './search/search'; import { readPortfolio, createPortfolio, updatePortfolio } from './portfolio/portfolio'; -import { readImagePresignedUrl, readImageListPresignedUrl } from './image/image'; +import { readImagePresignedUrl, readImageListPresignedUrl, uploadImageFile } from './image/image'; export { EndPoint, @@ -32,4 +32,5 @@ export { updatePortfolio, readImagePresignedUrl, readImageListPresignedUrl, + uploadImageFile, }; From b563125b6681f800f7ccd66795caf76e8d7ec41a Mon Sep 17 00:00:00 2001 From: kimsuyeon_0916 Date: Thu, 25 Apr 2024 21:00:44 +0900 Subject: [PATCH 077/116] =?UTF-8?q?feat:=20#96=20presignedUrl=20=EC=9D=B4?= =?UTF-8?q?=EC=9A=A9=ED=95=98=EC=97=AC=20S3=EC=97=90=20=EC=9D=B4=EB=AF=B8?= =?UTF-8?q?=EC=A7=80=20=EC=97=85=EB=A1=9C=EB=93=9C=20API=20=ED=95=A8?= =?UTF-8?q?=EC=88=98=20=ED=98=B8=EC=B6=9C=20=EC=BB=A4=EC=8A=A4=ED=85=80=20?= =?UTF-8?q?=ED=9B=85=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/index.ts | 7 ++++++- src/hooks/useImage.ts | 19 +++++++++++++++++-- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/hooks/index.ts b/src/hooks/index.ts index f7338612..3992c361 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -11,7 +11,11 @@ import useDebounce from './useDebounce'; import useValid from './useValid'; import { useReadSkillList, useReadRoleList } from './useSearch'; import { useReadPortfolio, useCreatePortfolio, useUpdatePortfolio } from './usePortfolio'; -import { useReadImagePresignedUrl, useReadImageListPresignedUrl } from './useImage'; +import { + useReadImagePresignedUrl, + useReadImageListPresignedUrl, + useUploadImageFile, +} from './useImage'; export { useCheckExist, @@ -31,4 +35,5 @@ export { useUpdatePortfolio, useReadImagePresignedUrl, useReadImageListPresignedUrl, + useUploadImageFile, }; diff --git a/src/hooks/useImage.ts b/src/hooks/useImage.ts index a144b759..b2953daa 100644 --- a/src/hooks/useImage.ts +++ b/src/hooks/useImage.ts @@ -1,5 +1,5 @@ -import { useQuery } from '@tanstack/react-query'; -import { readImagePresignedUrl, readImageListPresignedUrl } from '../service'; +import { useQuery, useMutation } from '@tanstack/react-query'; +import { readImagePresignedUrl, readImageListPresignedUrl, uploadImageFile } from '../service'; const imageKeys = { readImagePresignedUrl: (fileName: string) => ['readImagePresignedUrl', fileName], @@ -10,6 +10,7 @@ const imageKeys = { fileName: string; portfolioId?: string; }) => ['readImageListPresignedUrl', fileName, portfolioId], + uploadImageFile: ['useUploadImageFile'], }; /** @@ -19,6 +20,7 @@ export const useReadImagePresignedUrl = (fileName: string) => { return useQuery({ queryKey: imageKeys.readImagePresignedUrl(fileName), queryFn: () => readImagePresignedUrl(fileName), + enabled: false, }); }; @@ -35,5 +37,18 @@ export const useReadImageListPresignedUrl = ({ return useQuery({ queryKey: imageKeys.readImageListPresignedUrl({ fileName, portfolioId }), queryFn: () => readImageListPresignedUrl({ fileName, portfolioId }), + enabled: false, + }); +}; + +/** + * @description S3 에 이미지 바이너리 파일 업로드 API를 호출하는 hook입니다. + */ +export const useUploadImageFile = ({ onSuccess }: { onSuccess: () => void }) => { + return useMutation({ + mutationFn: uploadImageFile, + onSuccess: () => { + onSuccess?.(); + }, }); }; From 37fe1d10a6a9dcc5ccda4d946064807c650b34bc Mon Sep 17 00:00:00 2001 From: kimsuyeon_0916 Date: Fri, 26 Apr 2024 23:03:43 +0900 Subject: [PATCH 078/116] =?UTF-8?q?feat:=20#96=20uploadImageState=20atom?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/atom.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/atom.tsx b/src/atom.tsx index 4c76116f..5e77cb0c 100644 --- a/src/atom.tsx +++ b/src/atom.tsx @@ -147,3 +147,8 @@ export const uploadImageListState = atom({ key: 'uploadImageListState', default: [], }); + +export const uploadImageState = atom({ + key: 'uploadImageState', + default: null, +}); From 03a02ae0985ec7663dd3e8780a3d5bfc73b835c0 Mon Sep 17 00:00:00 2001 From: kimsuyeon_0916 Date: Fri, 26 Apr 2024 23:04:09 +0900 Subject: [PATCH 079/116] =?UTF-8?q?feat:=20#96=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=ED=95=98=EC=A7=80=20=EC=95=8A=EB=8A=94=20atom=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/atom.tsx | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/src/atom.tsx b/src/atom.tsx index 5e77cb0c..d6ab0ab3 100644 --- a/src/atom.tsx +++ b/src/atom.tsx @@ -123,26 +123,6 @@ export const searchPageState = atom({ default: false, }); -export const imageNameState = atom({ - key: 'imageNameState', - default: '', -}); - -export const imageNameListState = atom({ - key: 'imageNameListState', - default: [], -}); - -export const imageSrcListState = atom({ - key: 'imageSrcListState', - default: [], -}); - -export const binaryImageListState = atom({ - key: 'binaryImageListState', - default: [], -}); - export const uploadImageListState = atom({ key: 'uploadImageListState', default: [], From 424548f7e76262327ac3dff853e9dd5cd89e2b25 Mon Sep 17 00:00:00 2001 From: kimsuyeon_0916 Date: Sat, 27 Apr 2024 00:15:27 +0900 Subject: [PATCH 080/116] =?UTF-8?q?feat:=20#96=20Image=20=ED=83=80?= =?UTF-8?q?=EC=9E=85=20=EB=82=B4=20=20binary=20->=20file=20=ED=94=84?= =?UTF-8?q?=EB=A1=9C=ED=8D=BC=ED=8B=B0=20=EB=B0=8F=20=ED=83=80=EC=9E=85=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/types/image.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/image.ts b/src/types/image.ts index 66add035..0772d30e 100644 --- a/src/types/image.ts +++ b/src/types/image.ts @@ -1,5 +1,5 @@ export interface Image { fileName: string; url?: string; - binary?: ArrayBuffer; + file?: File; } From 4c8f76d13c1c8fa4503d1cc9ded4e436b7a67f5d Mon Sep 17 00:00:00 2001 From: kimsuyeon_0916 Date: Sat, 27 Apr 2024 00:17:36 +0900 Subject: [PATCH 081/116] =?UTF-8?q?fix:=20#96=20=ED=94=84=EB=A1=9C?= =?UTF-8?q?=ED=95=84=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=97=85=EB=A1=9C?= =?UTF-8?q?=EB=93=9C=20=EB=A1=9C=EC=A7=81=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 별도의 이미지 바이너리화 제거 --- src/components/profile/image/ProfileImage.tsx | 29 +++++++++---------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/src/components/profile/image/ProfileImage.tsx b/src/components/profile/image/ProfileImage.tsx index df0f3b0d..bd228443 100644 --- a/src/components/profile/image/ProfileImage.tsx +++ b/src/components/profile/image/ProfileImage.tsx @@ -1,8 +1,10 @@ -import React, { useRef, useState } from 'react'; +import React, { useEffect, useRef } from 'react'; import S from './ProfileImage.styled'; import { AddProfile, DefaultProfileImage } from '../../../assets'; import { useNavigate } from 'react-router'; import { Image } from '../../../types'; +import { useRecoilState } from 'recoil'; +import { uploadImageState } from '../../../atom'; interface ProfileImage { isEditable?: boolean; @@ -17,30 +19,27 @@ const ProfileImage = ({ isEditable, userId, size, url }: ProfileImage) => { navigate(`/profile/${userId}`); }; + const [uploadImage, setUploadImage] = useRecoilState(uploadImageState); + useEffect(() => { + setUploadImage({ fileName: '프로필사진', url: url }); // 초기화 + }, []); + const inputRef = useRef(null); + const addImage = () => { inputRef.current?.click(); }; - const [uploadImage, setUploadImage] = useState({ fileName: '프로필사진', url: url }); - const changeImage = (event: React.BaseSyntheticEvent) => { const image = event.target?.files[0]; - - let newImage: Image = { - fileName: image.name, - }; - - const binaryReader = new FileReader(); - binaryReader.readAsArrayBuffer(image); - binaryReader.onload = () => { - newImage = { ...newImage, binary: binaryReader.result as ArrayBuffer }; - }; - const urlReader = new FileReader(); urlReader.readAsDataURL(image); urlReader.onload = () => { - newImage = { ...newImage, url: urlReader.result as string }; + const newImage = { + fileName: image.name, + url: urlReader.result, + file: image, + } as Image; setUploadImage(newImage); }; }; From d9db65d5d8bab3ed6e74d34d8fe8f5b871a84bf5 Mon Sep 17 00:00:00 2001 From: kimsuyeon_0916 Date: Sat, 27 Apr 2024 00:26:32 +0900 Subject: [PATCH 082/116] =?UTF-8?q?fix:=20#96=20presignedUrl=20=EC=9D=B4?= =?UTF-8?q?=EC=9A=A9=ED=95=98=EC=97=AC=20S3=EC=97=90=20=EC=9D=B4=EB=AF=B8?= =?UTF-8?q?=EC=A7=80=20=EC=97=85=EB=A1=9C=EB=93=9C=20API=20=ED=95=A8?= =?UTF-8?q?=EC=88=98=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 이유: 이미지 파일을 {}로 한번 감싸고 전송 --- src/service/image/image.ts | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/src/service/image/image.ts b/src/service/image/image.ts index 5647f0b6..49cf4215 100644 --- a/src/service/image/image.ts +++ b/src/service/image/image.ts @@ -4,7 +4,7 @@ import { EndPoint } from '../endPoint'; interface UploadImageFile { presignedUrl: string; - imageFile: ArrayBuffer; + imageFile: File; } export const readImagePresignedUrl = async (fileName: string) => { @@ -44,21 +44,13 @@ export const readImageListPresignedUrl = async ({ } }; -const axiosConfig = { - headers: { - 'Content-Type': 'application/octet-stream', - }, -}; - export const uploadImageFile = async ({ presignedUrl, imageFile }: UploadImageFile) => { try { - const response = await axiosInstance.put( - presignedUrl, - { ...imageFile }, - { - ...axiosConfig, - } - ); + const response = await axiosInstance.put(presignedUrl, imageFile, { + headers: { + 'Content-Type': imageFile.type, + }, + }); return response; } catch (error) { From de07fb49024e6d14289988cd79a472ce166516cd Mon Sep 17 00:00:00 2001 From: kimsuyeon_0916 Date: Sat, 27 Apr 2024 00:42:00 +0900 Subject: [PATCH 083/116] =?UTF-8?q?feat:=20#96=20=EB=8B=A8=EC=9D=BC=20?= =?UTF-8?q?=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=97=85=EB=A1=9C=EB=93=9C=20pre?= =?UTF-8?q?signedURL=20=EC=A1=B0=ED=9A=8C=20=EB=B0=8F=20S3=EC=97=90=20?= =?UTF-8?q?=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=97=85=EB=A1=9C=EB=93=9C=20fet?= =?UTF-8?q?ch?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/profile/edit/ProfileEditPage.tsx | 532 +++++++++++---------- 1 file changed, 288 insertions(+), 244 deletions(-) diff --git a/src/pages/profile/edit/ProfileEditPage.tsx b/src/pages/profile/edit/ProfileEditPage.tsx index ff0032d4..e3c86ddc 100644 --- a/src/pages/profile/edit/ProfileEditPage.tsx +++ b/src/pages/profile/edit/ProfileEditPage.tsx @@ -20,7 +20,7 @@ import { AddFormBtn, } from '../../../components'; import { useRecoilValue } from 'recoil'; -import { imageNameState, userState } from '../../../atom'; +import { uploadImageState, userState } from '../../../atom'; import { Skill, Portfolio, Award, Link } from '../../../types'; import { useDebounce, @@ -28,6 +28,8 @@ import { useUpdateProfile, useReadRoleList, useReadSkillList, + useUploadImageFile, + useReadImagePresignedUrl, } from '../../../hooks'; import { useNavigate } from 'react-router-dom'; @@ -66,8 +68,6 @@ const gpaList = [{ name: '4.5' }, { name: '4.3' }]; const ProfileEditPage = () => { const userId = useRecoilValue(userState)?.userId as string; const { data: user, isSuccess } = useReadProfile(userId); // 새로고침 시, 렌더링 지연 및 콘솔 에러 - // const user = useRecoilValue(userState); - const profileImageName = useRecoilValue(imageNameState); const navigate = useNavigate(); @@ -75,15 +75,23 @@ const ProfileEditPage = () => { return navigate(`/profile/${userId}`); }; - const { mutate } = useUpdateProfile({ + const { mutate: updateProfile } = useUpdateProfile({ onSuccess: updateProfileInSuccess, }); - const submitHandler: SubmitHandler = data => { - const { interest, ...updateData } = data; - mutate({ - ...updateData, - imageFileName: profileImageName, + // 이미지 업로드 + const profileImage = useRecoilValue(uploadImageState); + const { + data: imageResponse, + refetch: readImagePresignedUrl, + isSuccess: isSuccessReadUrl, + } = useReadImagePresignedUrl(profileImage?.fileName as string); + + const uploadImageFileInSuccess = () => { + const formData = getValues(); + updateProfile({ + ...formData, + imageFileName: imageResponse?.fileName, isUserNamePublic: isUserNamePublic, interestId: sessionStorage.interest, isPhonePublic: isPhonePublic, @@ -93,8 +101,40 @@ const ProfileEditPage = () => { skills: skillList.map(skill => skill.id), portfolios: pinnedPortfolioList, }); - sessionStorage.removeItem('interest'); - sessionStorage.removeItem('skill'); + sessionStorage.clear(); + }; + + const { mutate: uploadImageFile } = useUploadImageFile({ + onSuccess: uploadImageFileInSuccess, + }); + + useEffect(() => { + if (isSuccessReadUrl && imageResponse) { + uploadImageFile({ + presignedUrl: imageResponse.url, + imageFile: profileImage?.file as File, + }); + } + }, [isSuccessReadUrl]); + + const submitHandler: SubmitHandler = data => { + if (user?.imageUrl !== profileImage?.url) { + // 이미지를 처음 업로드 및 변경하는 경우에만 S3에 업로드(기존!==지금) + readImagePresignedUrl(); // presignedUrl 발급 + } else { + updateProfile({ + ...data, + isUserNamePublic: isUserNamePublic, + interestId: sessionStorage.interest, + isPhonePublic: isPhonePublic, + isUniversityMain: isUniversityMain, + isUniversityEmailPublic: isUniversityEmailPublic, + isSubEmailPublic: isSubEmailPublic, + skills: skillList.map(skill => skill.id), + portfolios: pinnedPortfolioList, + }); + sessionStorage.clear(); + } }; const { register, formState, handleSubmit, control, watch, getValues, setValue } = @@ -248,272 +288,276 @@ const ProfileEditPage = () => { }; return ( - <> - checkKeyDown(e)}> - - - - - - + isSuccess && ( + <> + checkKeyDown(e)}> + + + + - - - - - - - - - - - 자기 소개 -