Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat: 커피챗 오픈/수정 로직 구현 #1621

Merged
merged 30 commits into from
Nov 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
6602292
feat: 커피챗 오픈 뷰 헤더 구현
seojisoosoo Oct 16, 2024
9ae01a0
feat: 커피챗 입력 progress 박스 구현
seojisoosoo Oct 16, 2024
ce59d7d
Merge pull request #1594 from sopt-makers/feat/#1593-header
seojisoosoo Oct 17, 2024
8eaf085
feat: 커피챗 폼 디렉토리 분리
seojisoosoo Oct 17, 2024
7e6443f
feat: 커피챗 폼 구현
seojisoosoo Oct 19, 2024
ddb23be
chore: 인터페이스명 변경
seojisoosoo Oct 19, 2024
71483e5
fix: 빌드 에러 수정
seojisoosoo Oct 19, 2024
9893d12
fix: 스타일 린트 오류 해결
seojisoosoo Oct 19, 2024
c06fd1a
feat: 텍스트 필드 max height 설정 추가
seojisoosoo Oct 19, 2024
ada5dc1
refactor: 코드리뷰 반영
seojisoosoo Oct 19, 2024
ea6f4c9
chore: 불필요 주석 삭제
seojisoosoo Oct 19, 2024
e03c015
Merge pull request #1595 from sopt-makers/feat/#1593-progressbox
seojisoosoo Oct 21, 2024
a77e995
Merge pull request #1598 from sopt-makers/feat/#1593-forms-view
seojisoosoo Oct 21, 2024
22ff044
feat: 데스크탑 레이아웃 구현
seojisoosoo Oct 21, 2024
0e345b2
feat: 모바일 레이아웃 구현
seojisoosoo Oct 21, 2024
59ab759
chore: 컴포넌트 형식 변경
seojisoosoo Oct 21, 2024
ba83503
feat: 업로드 뷰 로직 구현
seojisoosoo Oct 24, 2024
7bef5c3
feat: submit 로직 구현
seojisoosoo Oct 25, 2024
a3959be
feat: 에러 발생한 input으로 포커싱 및 스크롤되도록 구현
seojisoosoo Oct 25, 2024
a403523
feat: 수정 페이지 로직 구현
seojisoosoo Oct 25, 2024
ba222d3
feat: progress 조건 내려주기
seojisoosoo Oct 25, 2024
ab07f82
chore: 불필요 코드 삭제
seojisoosoo Oct 25, 2024
d191359
feat: 커피챗 업로드 다이얼로그 구현
seojisoosoo Oct 25, 2024
021297e
feat: 커피챗 헤더 레이아웃에 추가
seojisoosoo Oct 25, 2024
ff6e6cf
chore: 색상 변경
seojisoosoo Oct 25, 2024
1a587ba
Merge pull request #1599 from sopt-makers/feat/#1593-layout
seojisoosoo Oct 25, 2024
d1539cf
chore: 데이터 변경
seojisoosoo Oct 25, 2024
920cfef
feat: 업로드 버튼 오픈, 수정 prop 받기
seojisoosoo Oct 25, 2024
c79355e
Merge pull request #1604 from sopt-makers/feat/#1593-logic
seojisoosoo Oct 30, 2024
10e8deb
Merge branch 'main' into feat/#1593
seojisoosoo Nov 3, 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
13 changes: 13 additions & 0 deletions src/api/endpoint/coffeechat/editCoffeechat.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { z } from 'zod';

import { createEndpoint } from '@/api/typedAxios';
import { CoffeechatFormContent } from '@/components/coffeechat/upload/CoffeechatForm/types';

export const editCoffeechat = createEndpoint({
request: (requestBody: CoffeechatFormContent) => ({
method: 'PUT',
url: 'api/v1/members/coffeechat/details',
data: requestBody,
}),
serverResponseScheme: z.unknown(),
});
13 changes: 13 additions & 0 deletions src/api/endpoint/coffeechat/uploadCoffeechat.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { z } from 'zod';

import { createEndpoint } from '@/api/typedAxios';
import { CoffeechatFormContent } from '@/components/coffeechat/upload/CoffeechatForm/types';

export const uploadCoffeechat = createEndpoint({
request: (reqeustBody: CoffeechatFormContent) => ({
method: 'POST',
url: 'api/v1/members/coffeechat/details',
data: reqeustBody,
}),
serverResponseScheme: z.unknown(),
});
5 changes: 5 additions & 0 deletions src/components/coffeechat/mediaQuery.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const COFFEECHAT_TABLET_MAX_WIDTH = 1260;
export const COFFEECHAT_MOBILE_MAX_WIDTH = 430;

export const COFFEECHAT_TABLET_MEDIA_QUERY = `screen and (max-width: ${COFFEECHAT_TABLET_MAX_WIDTH}px)`;
export const COFFEECHAT_MOBILE_MEDIA_QUERY = `screen and (max-width: ${COFFEECHAT_MOBILE_MAX_WIDTH}px)`;
113 changes: 113 additions & 0 deletions src/components/coffeechat/page/CoffeechatUploadPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { yupResolver } from '@hookform/resolvers/yup';
import { FieldValues, FormProvider, useForm } from 'react-hook-form';

import DesktopCoffeechatUploadLayout from '@/components/coffeechat/page/layout/DesktopCoffeechatUploadLayout';
import MobileCoffeechatUploadLayout from '@/components/coffeechat/page/layout/MobileCoffeechatUploadLayout';
import CoffeechatForm from '@/components/coffeechat/upload/CoffeechatForm';
import { coffeeChatchema } from '@/components/coffeechat/upload/CoffeechatForm/schema';
import SubmitDialog from '@/components/coffeechat/upload/CoffeechatForm/SubmitDialog';
import { CoffeechatFormContent } from '@/components/coffeechat/upload/CoffeechatForm/types';
import UploadButton from '@/components/coffeechat/upload/CoffeechatForm/UploadButton';
import ProgressBox from '@/components/coffeechat/upload/ProgressBox';
import UploadHeader from '@/components/coffeechat/upload/UploadHeader';
import useModalState from '@/components/common/Modal/useModalState';
import Responsive from '@/components/common/Responsive';
interface CoffeechatUploadPageProps {
uploadType: '오픈' | '수정';
form: CoffeechatFormContent;
onSubmit: <T extends FieldValues>(values: T) => void;
}

export default function CoffeechatUploadPage({ uploadType, form, onSubmit }: CoffeechatUploadPageProps) {
const methods = useForm<CoffeechatFormContent>({ resolver: yupResolver(coffeeChatchema), defaultValues: form });
const { handleSubmit, setFocus, watch } = methods;
const { isOpen, onClose, onOpen } = useModalState();

const findFirstErrorFieldInOrder = (errors: FieldValues): string | null => {
const fieldOrder = [
'memberInfo.career',
'memberInfo.introduction',
'coffeeChatInfo.sections',
'coffeeChatInfo.bio',
'coffeeChatInfo.topicTypes',
'coffeeChatInfo.topic',
'coffeeChatInfo.meetingType',
'coffeeChatInfo.guideline',
];

for (const fieldPath of fieldOrder) {
const keys = fieldPath.split('.');
let currentError = errors;

for (const key of keys) {
currentError = currentError[key];
if (!currentError) break;
}

if (currentError?.message) {
return fieldPath;
}
}

return null;
};

const onError = (errors: FieldValues) => {
const firstErrorField = findFirstErrorFieldInOrder(errors);

if (firstErrorField) {
setFocus(firstErrorField as keyof CoffeechatFormContent);
const errorElement = document.querySelector(`[name="${firstErrorField}"]`);

if (errorElement) {
errorElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
}
};

return (
<FormProvider {...methods}>
<form onSubmit={handleSubmit(onOpen, onError)}>
<SubmitDialog isOpen={isOpen} onClose={onClose} onSubmit={onSubmit} />
<>
<Responsive only='desktop'>
<DesktopCoffeechatUploadLayout
main={
<>
<UploadHeader uploadType={uploadType} />
<CoffeechatForm />
</>
}
aside={
<ProgressBox
uploadType={uploadType}
myInfoInprogress={!!(watch('memberInfo.career') && watch('memberInfo.introduction'))}
coffeechatInfoInprogress={
!!(
watch('coffeeChatInfo.sections') &&
watch('coffeeChatInfo.bio') &&
watch('coffeeChatInfo.topicTypes') &&
watch('coffeeChatInfo.topic')
)
}
/>
}
submitButton={<UploadButton uploadType={uploadType} />}
/>
</Responsive>
<Responsive only='mobile'>
<MobileCoffeechatUploadLayout
main={
<>
<UploadHeader uploadType={uploadType} />
<CoffeechatForm />
</>
}
submitButton={<UploadButton uploadType={uploadType} />}
/>
</Responsive>
</>
</form>
</FormProvider>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { COFFEECHAT_TABLET_MEDIA_QUERY } from '@/components/coffeechat/mediaQuery';
import styled from '@emotion/styled';
import { ReactNode } from 'react';

interface DesktopCoffeechatUploadLayoutProps {
main: ReactNode;
aside: ReactNode;
submitButton: ReactNode;
}

export default function DesktopCoffeechatUploadLayout({
main,
aside,
submitButton,
}: DesktopCoffeechatUploadLayoutProps) {
return (
<Layout>
<Main>
<Body>{main}</Body>
<Footer>{submitButton}</Footer>
</Main>
<Aside>{aside}</Aside>
</Layout>
);
}

const Footer = styled.footer`
margin: 62px 0 50px;
`;

const Layout = styled.div`
display: flex;
gap: 30px;
justify-content: center;
margin: 0 30px;
margin-top: 48px;
margin-bottom: 180px;

@media ${COFFEECHAT_TABLET_MEDIA_QUERY} {
margin-top: 0;
}
`;

const Body = styled.section`
display: flex;
flex-direction: column;
`;

const Main = styled.main`
display: flex;
flex-direction: column;
align-items: flex-end;
margin: 0 40px;
`;

const Aside = styled.aside``;
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { COFFEECHAT_MOBILE_MEDIA_QUERY } from '@/components/coffeechat/mediaQuery';
import { MOBILE_MEDIA_QUERY } from '@/styles/mediaQuery';
import styled from '@emotion/styled';
import { ReactNode } from 'react';

interface MobileCoffeechatUploadLayoutProps {
main: ReactNode;
submitButton: ReactNode;
}

export default function MobileCoffeechatUploadLayout({ main, submitButton }: MobileCoffeechatUploadLayoutProps) {
return (
<Layout>
<>{main}</>
<Footer>{submitButton}</Footer>
</Layout>
);
}

const Layout = styled.div`
display: flex;
flex-direction: column;
margin: 0 20px;
margin-bottom: 180px;

@media ${MOBILE_MEDIA_QUERY} {
margin-top: 48px;
}

@media ${COFFEECHAT_MOBILE_MEDIA_QUERY} {
margin-top: 0;
}
`;

const Footer = styled.footer`
margin: 40px 0 56px;
width: 100%;
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import styled from '@emotion/styled';
import { Chip } from '@sopt-makers/ui';
import { Controller, useFormContext } from 'react-hook-form';

import { CoffeechatFormContent, CoffeechatFormPaths } from '@/components/coffeechat/upload/CoffeechatForm/types';
import FormItem from '@/components/common/form/FormItem';
import Responsive from '@/components/common/Responsive';

interface ChipFieldProps {
field: CoffeechatFormPaths;
errorMessage: string;
chipList: readonly string[];
isSingleSelect?: boolean;
}

export default function ChipField({ field, errorMessage, chipList, isSingleSelect = false }: ChipFieldProps) {
const { control } = useFormContext<CoffeechatFormContent>();

return (
<FormItem errorMessage={errorMessage}>
<Controller
name={field}
control={control}
render={({ field }) => (
<ChipsWrapper {...field}>
{chipList.map((chip) => (
<div
key={chip}
onClick={() => {
if (isSingleSelect) {
field.onChange([chip]);
} else {
const newValue =
Array.isArray(field.value) && field.value.includes(chip)
? field.value.filter((item) => item !== chip) // MEMO: 이미 선택된 경우 제거
: [...(Array.isArray(field.value) ? field.value : []), chip]; // MEMO: 복수 선택 모드일 경우 추가

field.onChange(newValue); // MEMO: 배열 업데이트
}
}}
>
<Responsive only='desktop'>
<Chip size='sm' active={Array.isArray(field.value) && field.value.includes(chip)}>
{chip}
</Chip>
</Responsive>
<Responsive only='mobile'>
<Chip size='md' active={Array.isArray(field.value) && field.value.includes(chip)}>
{chip}
</Chip>
</Responsive>
</div>
))}
</ChipsWrapper>
)}
/>
</FormItem>
);
}

const ChipsWrapper = styled.div`
display: flex;
flex-wrap: wrap;
gap: 10px;
`;
Loading
Loading