Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

fix: 6차 QA 피드백 적용 #206

Merged
merged 16 commits into from
May 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
d9d1c8d
fix: #202 포트폴리오 작성/편집 시 스킬, 링크 10개 초과 및 스킬 중복 시 모달 화면 중앙 오버레이
kimsuyeon0916 May 23, 2024
ca514fe
fix: #202 프로필 편집 시 스킬, 링크, 수상/활동 10개 초과 및 스킬 중복 시 모달 화면 중앙 오버레이
kimsuyeon0916 May 23, 2024
d86dc5e
fix: #202 포트폴리오 작성/편집 폼 제출 실패했을 때도 버튼 비활성화 되는 이슈 해결
kimsuyeon0916 May 23, 2024
50da47f
fix: #202 프로필 편집 실패했을 때도 프로필 상세 페이지로 리다이렉션 되는 이슈 해결
kimsuyeon0916 May 23, 2024
3ed8f8c
fix: #202 링크 유효성 검사 UI(유효한 링크 형식) 적용
kimsuyeon0916 May 23, 2024
021b45d
fix: #202 프로필 편집 페이지 폼 제출 중에 버튼 비활성화 적용
kimsuyeon0916 May 23, 2024
17e44b9
fix: #202 StrictMode 제거 후 새로고침 시 포트폴리오 상세 내용 안보이는 이슈 해결
kimsuyeon0916 May 23, 2024
c168b9d
fix: #202 필수정보 미입력하고 폼 제출시, 필수정보 입력 모달 화면 중앙 오버레이
kimsuyeon0916 May 28, 2024
4786bcb
chore: #202 MuiDatepicker 내 any 타입 제거
kimsuyeon0916 May 28, 2024
45cf23a
fix: #202 이미지 파일 이름이 중복되는 경우와 상관없이 이미지 추가하도록 로직 변경
kimsuyeon0916 May 28, 2024
5f374eb
style: #202 포트폴리오 필수정보 모달 반응형 스타일링 적용
kimsuyeon0916 May 29, 2024
5a261f2
fix: #202 포트폴리오 필수정보 모달 컴포넌트 내 타입 에러 해결
kimsuyeon0916 May 29, 2024
b53c5e5
style: #202 모달 공통 컴포넌트 기본 width 설정
kimsuyeon0916 May 30, 2024
e41b333
fix: #202 로그인 페이지 내 이용약관 설명 제거
kimsuyeon0916 May 30, 2024
56a4ada
fix: #202 프로필 편집 폼 제출 시, 변경이 반영 안되는 이슈 해결
kimsuyeon0916 May 31, 2024
44e9d00
Merge branch 'release-1.0' into feature/#202_apply_6th_QA_issues
kimsuyeon0916 May 31, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ import PostingDelete from './recruit/recruitDetail/modal/postingDelete/PostingDe
import Modal from './modal/Modal';
import WarnRoleDelete from './recruit/recruitDetail/modal/warnRoleDelete/WarnRoleDelete';
import Footer from './footer/Footer';
import PortfolioModal from './portfolio/modal/PortfolioModal';
import WarnRoleCount from './recruit/create/modal/WarnRoleCount';

export {
Expand Down Expand Up @@ -296,5 +297,6 @@ export {
PostingDelete,
Modal,
WarnRoleDelete,
PortfolioModal,
WarnRoleCount,
};
1 change: 1 addition & 0 deletions src/components/modal/Modal.styled.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const ModalContainer = styled.div`
flex-direction: column;
align-items: center;
padding: 1.8rem 2rem;
width: clamp(10%, 30rem, 50%);
border-radius: 1rem;
border: 0.1rem solid var(--box_stroke, #e3e3e3);
background: var(--Grayscale-100, #f8fafb);
Expand Down
10 changes: 6 additions & 4 deletions src/components/modal/Modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import { DefaultBtn, PrimaryBtn } from '../index';
interface Modal {
title: string;
content: string;
defaultBtn: Button;
primaryBtn: Button;
defaultBtn?: Button;
}

interface Button {
Expand All @@ -21,9 +21,11 @@ const Modal = ({ title, content, defaultBtn, primaryBtn }: Modal) => {
<S.ModalTitle>{title}</S.ModalTitle>
<S.ModalContent>{content}</S.ModalContent>
<S.ModalRow $gap='1.6rem'>
<div>
<DefaultBtn type='button' {...defaultBtn} />
</div>
{defaultBtn && (
<div>
<DefaultBtn type='button' {...defaultBtn} />
</div>
)}
<div>
<PrimaryBtn type='button' {...primaryBtn} />
</div>
Expand Down
2 changes: 1 addition & 1 deletion src/components/muiDatepicker/MuiDatepicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const MuiDatepicker = ({ handleChange, defaultValue, value, inputRef, invalid }:
slots={{
popper: S.StyledPopper,
textField: S.StyledTextField,
layout: S.StyledPickersLayout as React.JSXElementConstructor<any>,
layout: S.StyledPickersLayout as React.JSXElementConstructor<unknown>,
openPickerIcon: CalendarIcon,
}}
slotProps={{
Expand Down
44 changes: 41 additions & 3 deletions src/components/portfolio/image/upload/PortfolioImageUpload.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,13 +93,42 @@ const PortfolioImageUpload = ({
const changeImageList = (event: React.BaseSyntheticEvent) => {
const imageList = event.target?.files;
for (let i = 0; i < imageList.length && uploadImageList.length + i < MAX_IMAGE_COUNT; i++) {
if (imageList[i].size > MAX_IMAGE_SIZE_BYTES) {
continue;
}
if (
uploadImageList.find(image => image.fileName === imageList[i].name) ||
[...imageList].find((image, index) => index !== i && image.name === imageList[i].name)
) {
continue;
}
if (imageList[i].size > MAX_IMAGE_SIZE_BYTES) {
let uniqueFileName = imageList[i].name;
let idx = 1;
while (
uploadImageList.find(image => image.fileName === uniqueFileName) ||
[...imageList].find((image, index) => index !== i && image.name === uniqueFileName)
) {
const { fileNameWithoutExt, fileExt } = splitExt(imageList[i].name);
uniqueFileName = `${fileNameWithoutExt}(${idx})${fileExt}`;
idx++;
}

const newFile = new File([imageList[i]], uniqueFileName, { type: imageList[i].type });
const urlReader = new FileReader();
urlReader.readAsDataURL(newFile);
urlReader.onload = () => {
const uploadImage = {
fileName: newFile.name,
url: urlReader.result,
file: newFile,
} as Image;
setUploadImageList(prev => [...prev, uploadImage]);
i === 0 &&
uploadImageList.length === 0 &&
setValue('mainImage', uploadImage, {
shouldValidate: true,
shouldDirty: true,
shouldTouch: true,
});
};
continue;
}

Expand All @@ -123,6 +152,15 @@ const PortfolioImageUpload = ({
}
};

const splitExt = (fileName: string) => {
const lastDotIndex = fileName.lastIndexOf('.');

const fileNameWithoutExt = lastDotIndex !== -1 ? fileName.substring(0, lastDotIndex) : fileName;
const fileExt = lastDotIndex !== -1 ? fileName.substring(lastDotIndex) : '';

return { fileNameWithoutExt, fileExt };
};

return (
<S.PortfolioImageUploadLayout>
<S.PortfolioImageGrid>
Expand Down
99 changes: 99 additions & 0 deletions src/components/portfolio/modal/PortfolioModal.styled.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import styled from 'styled-components';

const PortfolioModalLayout = 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 PortfolioModalContainer = styled.div`
display: flex;
flex-direction: column;
align-items: center;
padding: 2.8rem 4rem;
width: clamp(30%, 58rem, 75%);
border-radius: 1rem;
border: 0.1rem solid var(--box_stroke, #e3e3e3);
background: var(--Grayscale-100, #f8fafb);
color: var(--text-color-2, #373f41);

/* Body/body2/medium */
font-size: 1.4rem;
font-weight: 500;
line-height: 1.6rem; /* 114.286% */
letter-spacing: 0.0028rem;
`;

const PortfolioModalTitle = styled.span`
display: flex;
margin-bottom: 2.8rem;
color: var(--text-color, #151515);

/* Headline/h3 */
font-size: 2rem;
font-weight: 600;
line-height: 2.4rem; /* 120% */
letter-spacing: 0.004rem;
`;

const PortfolioModalContent = styled.div`
display: flex;
margin-bottom: 2.8rem;
font-weight: 600;
`;

const PortfolioContentList = styled.ul`
display: flex;
flex-direction: column;
width: 100%;
margin-bottom: 4rem;
border-top: 0.1rem solid var(--Border-boxStroke, #d3d3d3);
`;

const PortfolioContentItem = styled.li`
display: flex;
flex-direction: row;
align-items: center;
border-bottom: 0.1rem solid var(--Border-boxStroke, #d3d3d3);
background: var(--Grayscale-200, #f6f6f6);
`;

const PortfolioContentItemTitle = styled.h6`
display: flex;
padding: 0.86rem 1.59rem;
background: var(--Grayscale-200, #f6f6f6);
color: var(--text-color, #151515);
`;

const PortfolioContentItemInfo = styled.div`
flex: 1;
display: flex;
padding: 0.86rem 1.59rem;
flex-direction: row;
column-gap: 1rem;
border-left: 0.1rem solid var(--Border-boxStroke, #d3d3d3);
background: var(--Form-fill-others, #fff);

/* 반응형 대비 */
flex-wrap: wrap;
row-gap: 1rem;
`;

const S = {
PortfolioModalLayout,
PortfolioModalContainer,
PortfolioModalTitle,
PortfolioModalContent,
PortfolioContentList,
PortfolioContentItem,
PortfolioContentItemTitle,
PortfolioContentItemInfo,
};

export default S;
72 changes: 72 additions & 0 deletions src/components/portfolio/modal/PortfolioModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import React from 'react';
import S from './PortfolioModal.styled';
import { FormState, FieldValues } from 'react-hook-form';
import { PrimaryBtn } from '../..';

interface PortfolioModal {
formState: FormState<FieldValues>;
handleClick: () => void;
}

const PortfolioModal = ({ formState, handleClick }: PortfolioModal) => {
const defaultInformationErrorList = [
!!formState?.errors['title'],
!!formState?.errors['description'],
!!formState?.errors['field'],
!!formState?.errors['role'],
!!formState?.errors['startDate'] || formState?.errors['endDate'],
!!formState?.errors['proceedType'],
];

const defaultInformationList = ['제목', '한줄소개', '분야', '역할', '진행기간', '진행방식'];

const defaultInformationErrorMessageList = defaultInformationList.filter(
(_, index) => defaultInformationErrorList[index]
);

return (
<S.PortfolioModalLayout>
<S.PortfolioModalContainer>
<S.PortfolioModalTitle>필수정보를 입력해주세요</S.PortfolioModalTitle>
<S.PortfolioModalContent>
아래 미작성된 항목을 입력해 포트폴리오 작성을 완료해주세요
</S.PortfolioModalContent>
<S.PortfolioContentList>
<S.PortfolioContentItem>
{formState?.errors['mainImage'] && (
<>
<S.PortfolioContentItemTitle>슬라이드</S.PortfolioContentItemTitle>
<S.PortfolioContentItemInfo>최소 1장의 이미지</S.PortfolioContentItemInfo>
</>
)}
</S.PortfolioContentItem>
<S.PortfolioContentItem>
{defaultInformationErrorList.includes(true) && (
<>
<S.PortfolioContentItemTitle>기본정보</S.PortfolioContentItemTitle>
<S.PortfolioContentItemInfo>
{defaultInformationErrorMessageList.map(message => (
<span key={message}>{message}</span>
))}
</S.PortfolioContentItemInfo>
</>
)}
</S.PortfolioContentItem>
<S.PortfolioContentItem>
{formState?.errors['content'] && (
<>
<S.PortfolioContentItemTitle>상세내용</S.PortfolioContentItemTitle>
<S.PortfolioContentItemInfo>내용 미입력</S.PortfolioContentItemInfo>
</>
)}
</S.PortfolioContentItem>
</S.PortfolioContentList>
<div>
<PrimaryBtn type='button' title='확인' handleClick={handleClick} />
</div>
</S.PortfolioModalContainer>
</S.PortfolioModalLayout>
);
};

export default PortfolioModal;
5 changes: 5 additions & 0 deletions src/constant/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const REGEXP = {
phone: /^\d{3}-\d{4}-\d{4}$/,
email: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/,
gpa: /^[0-9]+(\.[0-9]+)?$/,
url: /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_+.~#()?&/=]*)/,
};

export const INPUT_VALIDATION = {
Expand Down Expand Up @@ -105,6 +106,10 @@ export const INPUT_VALIDATION = {
},
url: {
required: 'url을 입력해주세요',
pattern: {
value: REGEXP.url,
message: 'url 형식에 맞게 입력해주세요',
},
},
};

Expand Down
12 changes: 8 additions & 4 deletions src/hooks/useProfile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,19 @@ export const useUpdateProfile = ({
onSuccess,
userId,
}: {
onSuccess: () => void;
onSuccess: (data: string) => void;
userId: string;
}) => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: updateProfile,
onSuccess: async () => {
await queryClient.invalidateQueries({ queryKey: profileKeys.readProfile(userId) });
onSuccess?.();
onSuccess: async data => {
if (data) {
await queryClient.invalidateQueries({ queryKey: profileKeys.readProfile(userId) });
queryClient.invalidateQueries({ queryKey: ['readInfinitePortfolioList', 12] });
queryClient.invalidateQueries({ queryKey: ['readPaginationPortfolioList', 16] });
onSuccess?.(data);
}
},
});
};
7 changes: 0 additions & 7 deletions src/pages/account/signIn/SignInPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,6 @@ const SignInPage = () => {
<div>
<NaverLogin />
</div>
<div className='sign-in_tos'>
<small>
소셜 계정 로그인으로 가입 시{' '}
<span className='sign-in_tos-marker'>이용약관, 개인정보처리방침, 전자금융거래약관</span>
에 동의함으로 처리됩니다
</small>
</div>
</article>
</S.SignInPageLayout>
);
Expand Down
Loading