diff --git a/src/pages/Notification/hooks/queries/usePollingNotification.ts b/src/pages/Notification/hooks/queries/usePollingNotification.ts index 4b18c654..36cf1b6a 100644 --- a/src/pages/Notification/hooks/queries/usePollingNotification.ts +++ b/src/pages/Notification/hooks/queries/usePollingNotification.ts @@ -1,65 +1,17 @@ +import { useSuspenseQuery } from '@tanstack/react-query'; import { http } from '../../../../api/http'; import { NotificationDTO } from '../../types'; export const NOTIFICATION_LIST_QUERY_KEY = 'NOTIFICATION_LIST_QUERY_KEY'; -const _getNotificationList = () => http.get('/somewhere'); +const _getNotificationList = () => http.get('/notifications'); export const usePollingNotification = () => { - // const { data: notifications, ...rest } = useSuspenseQuery({ - // queryKey: [NOTIFICATION_LIST_QUERY_KEY], - // queryFn: () => _getNotificationList(), - // refetchInterval: 3600, - // }); - - const notifications = tempData; - const rest = {}; + const { data: notifications, ...rest } = useSuspenseQuery({ + queryKey: [NOTIFICATION_LIST_QUERY_KEY], + queryFn: () => _getNotificationList(), + refetchInterval: 60000, + }); return { notifications, ...rest }; }; - -const tempData = [ - { - id: 1, - feedId: 39, - title: '글 제목입니다. 두 줄짜리 제목으로 확장합니다. 두 줄짜리 제목으로 확장합니다.', - createAt: '2022.11.18', - badges: [ - '테스트-테스트', - '매우긴놈매우긴놈매우긴놈', - '매우', - '테스트-테스트', - '테스트-테스트', - '테스트-테스트', - '테스트-테스트', - ], - }, - { - id: 2, - feedId: 39, - title: '글 제목입니다. 줄짜리 제목으로 확장합니다.', - createAt: '2022.11.18', - badges: ['테스트-테스트', '테스트', '테스트', '테스트', '테스트테스트'], - }, - { - id: 3, - feedId: 39, - title: '글 제목입니다. 두 줄짜리 제목으로 확장합니다. 두 줄짜리 제목으로 확장합니다.', - createAt: '2022.11.18', - badges: ['첫번쨰가매우길다면?첫번쨰가매우길다면길다면?', '테스트'], - }, - { - id: 4, - feedId: 39, - title: '글 제목입니다. 두 줄짜리 제목으로 확장합니다. 두 줄짜리 제목으로 확장합니다.', - createAt: '2022.11.18', - badges: ['매우긴놈매우긴놈매우긴놈매우긴놈', '테스트-테스트', '테스트-테스트', '테스트-테스트'], - }, - { - id: 5, - feedId: 39, - title: '글 제목입니다. 두 줄짜리 제목으로 확장합니다. 두 줄짜리 제목으로 확장합니다.', - createAt: '2022.11.18', - badges: ['테스트', '테스트', '테스트', '테스트', '테스트', 's테스트', 's테스트', 's테스트'], - }, -]; diff --git a/src/pages/ProfileEdit/ProfileEdit.page.tsx b/src/pages/ProfileEdit/ProfileEdit.page.tsx index 8c2ae89e..eb871fcb 100644 --- a/src/pages/ProfileEdit/ProfileEdit.page.tsx +++ b/src/pages/ProfileEdit/ProfileEdit.page.tsx @@ -2,7 +2,6 @@ import styled from '@emotion/styled'; import { Box, Button, - CheckboxContainer, Dropdown, Field, Flex, @@ -14,45 +13,31 @@ import { Tag, Text, theme, - useCheckbox, useDropdown, useField, } from 'concept-be-design-system'; -import { FormEvent } from 'react'; +import { useNavigate } from 'react-router-dom'; import { ReactComponent as SVGToolTip24 } from '../../../public/assets/tool_tip_24.svg'; import { NICKNAME_REG_EXP } from '../../constants/index.ts'; import useAlert from '../../hooks/useAlert.tsx'; import Back from '../../layouts/Back.tsx'; -import { getUserId } from '../Profile/utils/getUserId.ts'; import useCheckDuplicateNickname from '../SignUp/hooks/useCheckDuplicateNickname.ts'; import useDefaultProfileImage from '../SignUp/hooks/useDefaultProfileImage.ts'; import useSetDetailSkills from '../SignUp/hooks/useSetDetailSkills.ts'; +import { generateQueryString } from '../SignUp/utils/manageQueryString.ts'; import useProfileEditQuery from './hooks/useProfileEditQuery.ts'; -import usePutProfileMutation from './hooks/usePutProfileMutation.ts'; import { DropdownValue, FieldValue } from './types'; -interface CheckboxValue { - goal: CheckboxOption[]; -} - -interface CheckboxOption { - id: number; - name: string; - checked: boolean; -} - const ProfileEdit = () => { + const navigate = useNavigate(); const openAlert = useAlert(); - const { mainSkills, detailSkills, skillLevels, regions, purposes, my } = useProfileEditQuery(); + const { mainSkills, detailSkills, skillLevels, regions, my } = useProfileEditQuery(); const { fieldValue, fieldErrorValue, setFieldErrorValue, onChangeField } = useField({ nickname: my.nickname ?? '', company: my.workingPlace ?? '', intro: my.introduction ?? '', }); - const { checkboxValue, selectedCheckboxId, onChangeCheckbox } = useCheckbox({ - goal: my.joinPurposes ?? purposes, - }); const { dropdownValue, onResetDropdown, onClickDropdown } = useDropdown({ mainSkill: my.mainSkill ?? '', skillDepthOne: '', @@ -72,8 +57,6 @@ const ProfileEdit = () => { defaultProfileImage: PNGDefaultProfileInfo100, }); - const { putProfile } = usePutProfileMutation(getUserId(), fieldValue.nickname); - useCheckDuplicateNickname({ nickname: fieldValue.nickname, setFieldErrorValue }); const validateInput = () => { @@ -89,39 +72,24 @@ const ProfileEdit = () => { ]; }; - const onSubmit = (e: FormEvent) => { - e.preventDefault(); - - if (!fieldValue.nickname) { - openAlert({ content: '닉네임을 입력해 주세요.' }); - return; - } - - if (!dropdownValue.mainSkill) { - openAlert({ content: '대표 스킬을 선택해 주세요.' }); - return; - } + const formData = { + nickname: fieldValue.nickname, + mainSkillId: mainSkills.find(({ name }) => dropdownValue.mainSkill === name)?.id || 0, + profileImageUrl: profileImageUrl === PNGDefaultProfileInfo100 ? null : my.profileImageUrl, + skills: selectedSkillDepths.map(({ id, name }) => ({ skillId: id, level: name.split(', ')[1] })), + livingPlaceId: regions.find((place) => place.name === dropdownValue.region)?.id || 1, + workingPlace: fieldValue.company, + introduction: fieldValue.intro, + }; - if (selectedSkillDepths.length === 0) { - openAlert({ content: '세부 스킬을 하나 이상 선택해 주세요.' }); - return; - } + const onClickNextStep = (e: React.MouseEvent) => { + e.preventDefault(); - if (selectedCheckboxId.goal.length === 0) { - openAlert({ content: '가입 목적을 하나 이상 선택해 주세요.' }); - return; - } + if (!fieldValue.nickname) return openAlert({ content: '닉네임은 필수 값입니다.' }); + if (!dropdownValue.mainSkill) return openAlert({ content: '대표 스킬은 필수 값입니다.' }); + if (selectedSkillDepths.length === 0) return openAlert({ content: '세부 스킬은 최소 한 개 이상 선택해주세요.' }); - putProfile({ - nickname: fieldValue.nickname, - mainSkillId: mainSkills.find(({ name }) => dropdownValue.mainSkill === name)?.id || 0, - profileImageUrl: profileImageUrl === PNGDefaultProfileInfo100 ? null : my.profileImageUrl, - skills: selectedSkillDepths.map(({ id, name }) => ({ skillId: id, level: name.split(', ')[1] })), - joinPurposes: selectedCheckboxId.goal, - livingPlaceId: regions.find((place) => place.name === dropdownValue.region)?.id || 1, - workingPlace: fieldValue.company, - introduction: fieldValue.intro, - }); + navigate(`/profile-edit-match?${generateQueryString(formData)}`); }; return ( @@ -140,7 +108,7 @@ const ProfileEdit = () => { - + { - - - - - - 지역 @@ -367,7 +322,7 @@ const ProfileEdit = () => { - + diff --git a/src/pages/ProfileEdit/ProfileEditMatch.page.tsx b/src/pages/ProfileEdit/ProfileEditMatch.page.tsx new file mode 100644 index 00000000..8a68ef30 --- /dev/null +++ b/src/pages/ProfileEdit/ProfileEditMatch.page.tsx @@ -0,0 +1,298 @@ +import styled from '@emotion/styled'; +import { + Box, + Button, + CheckboxContainer, + Flex, + Header, + RadioContainer, + Spacer, + SVGRadioCheck24, + SVGRadioUncheck24, + Tag, + Text, + theme, + useCheckbox, + useRadio, +} from 'concept-be-design-system'; +import { FormEvent, useState } from 'react'; +import useAlert from '../../hooks/useAlert'; +import { useMemberInfoQuery } from '../Profile/hooks/queries/useMemberInfoQuery'; +import { getUserId } from '../Profile/utils/getUserId'; +import { SVGBellCircle } from '../SignUp/assets/SVGBellCircle'; +import { useNotificationSettingsMutation } from '../SignUp/hooks/useNotificationSettingsMutation'; +import { WORKING_PLACE_MAP } from '../SignUp/SignUpMatch.page'; +import { WorkingPlaceType } from '../SignUp/types'; +import { parseQueryString } from '../SignUp/utils/manageQueryString'; +import { + Sheet_Left, + Sheet_leftItem, + Sheet_radioDiv, + Sheet_right, + TwoDepthBottomSheet, +} from '../Write/components/TwoDepthBottomSheet'; +import { useWritingInfoQuery } from '../Write/hooks/queries/useWritingInfoQuery'; +import { Info } from '../Write/types'; +import { get2DepthCountsBy1DepthBranches } from '../Write/utils/get2DepthCountBy1DepthBranches'; +import usePutProfileMutation from './hooks/usePutProfileMutation'; + +interface QueryStringProps { + nickname: string; + mainSkillId: number; + skills: { + skillId: number; + level: string; + }[]; + profileImageUrl: string | null; + livingPlaceId: number; + workingPlace: string; + introduction: string; +} +interface CheckboxValue { + goal: CheckboxOption[]; +} + +interface CheckboxOption { + id: number; + name: string; + checked: boolean; +} + +export default function ProfileEditMatchPage() { + const openAlert = useAlert(); + + const [isOpenBranchBottomSheet, setIsOpenBranchBottomSheet] = useState(false); + const [prevFormData, _] = useState(parseQueryString); + + const my = useMemberInfoQuery(getUserId()); + const { branches, purposes, cooperationWays } = useWritingInfoQuery(); + + const { putProfile } = usePutProfileMutation(getUserId(), prevFormData.nickname); + const { postNotificationSettings } = useNotificationSettingsMutation(); + + const { checkboxValue, selectedCheckboxId, onChangeCheckbox } = useCheckbox({ + goal: purposes.map((purpose) => ({ + ...purpose, + checked: my.joinPurposes.includes(purpose.name), + })), + }); + const { radioValue, selectedRadioName, onChangeRadio } = useRadio({ + cooperationWays, + }); + + const [selectedBranch1Depth, setSelectedBranch1Depth] = useState(branches[0].name); + const [selectedBranchResponses, setSelectedBranchResponses] = useState([]); + + const branchBottomSheetLeftItems = branches.map((item) => item.name); + const branchBottomSheetRightItems = branches.find((item) => item.name === selectedBranch1Depth)?.branchResponses; + + const onSubmit = (e: FormEvent) => { + e.preventDefault(); + + if (selectedCheckboxId.goal.length === 0) { + openAlert({ content: '가입 목적을 하나 이상 선택해 주세요.' }); + return; + } + + if (!selectedBranchResponses.length) { + openAlert({ content: '분야를 1개 이상 선택해 주세요.' }); + return; + } + + if (!selectedRadioName.cooperationWays) { + openAlert({ content: '협업 방식을 선택해 주세요.' }); + return; + } + + putProfile( + { + ...prevFormData, + joinPurposes: selectedCheckboxId.goal, + }, + { + onSuccess: () => { + postNotificationSettings({ + purposeIds: selectedCheckboxId.goal, + branchIds: selectedBranchResponses.map((item) => item.id), + cooperationWay: WORKING_PLACE_MAP[ + selectedRadioName.cooperationWays as keyof typeof WORKING_PLACE_MAP + ] as WorkingPlaceType, + }); + }, + }, + ); + }; + + const onClickBranch = (selected: Info) => { + if (selectedBranchResponses.length >= 10) { + openAlert({ content: '최대 10개까지 선택할 수 있습니다.' }); + return; + } + + setSelectedBranchResponses((prev) => + selectedBranchResponses.includes(selected) ? prev.filter((item) => item.id !== selected.id) : [...prev, selected], + ); + }; + + const onDeleteBranch = (id: number) => { + setSelectedBranchResponses((prev) => prev.filter((item) => item.id !== id)); + }; + + return ( + +
+ + + + + + 프로젝트 매칭 수정 + + + + + +
+ + + + + + + + + 참여하고자 하는 프로젝트 조건을 등록해주세요. + + + 딱 맞는 모집 공고가 뜨면 알려드릴게요! + + + + + + + + + + + onChangeRadio(e, 'cooperationWays')} + gap="large" + required + /> + + + + + + + 분야 + + +
{ + setIsOpenBranchBottomSheet(true); + }} + > + + + + 추가하기 + + +
+
+ + setIsOpenBranchBottomSheet(false)} + > + + {branchBottomSheetLeftItems.map((item: any) => { + return ( + setSelectedBranch1Depth(item)} + checked={selectedBranch1Depth === item} + > + + {item} + + + + {get2DepthCountsBy1DepthBranches(selectedBranchResponses, branches)[item]} + + + ); + })} + + + {branchBottomSheetRightItems?.map((item: any) => { + return ( + onClickBranch(item)}> + + {item.name} + + {selectedBranchResponses.includes(item) ? : } + + ); + })} + + +
+ + + + {selectedBranchResponses.map((item) => { + return ( + onDeleteBranch(item.id)} style={{ borderRadius: 100 }}> + {item.name} + + ); + })} + + + +
+ + + + +
+
+ ); +} + +const MainWrapper = styled.form` + background-color: ${theme.color.c1}; + height: 100%; +`; + +const TeamLabelBox = styled.div` + display: flex; + flex-wrap: wrap; + gap: 8px; +`; diff --git a/src/pages/ProfileEdit/types/index.ts b/src/pages/ProfileEdit/types/index.ts index 446136ed..3892af3c 100644 --- a/src/pages/ProfileEdit/types/index.ts +++ b/src/pages/ProfileEdit/types/index.ts @@ -13,10 +13,9 @@ export interface PutSignUp { mainSkillId: number; profileImageUrl: string | null; skills: Omit[]; - joinPurposes: number[]; livingPlaceId: number; - workingPlace: string | null; introduction: string | null; + joinPurposes: number[]; } export interface ProfileSkill { diff --git a/src/pages/SignUp/SignUp.page.tsx b/src/pages/SignUp/SignUp.page.tsx index 4d612c36..29f5afd8 100644 --- a/src/pages/SignUp/SignUp.page.tsx +++ b/src/pages/SignUp/SignUp.page.tsx @@ -71,6 +71,7 @@ const SignUpPage = () => { oauthServerType: memberInfo?.oauthServerType || '', }; + // TODO: QA 이후 복원 // useValidateUserInfo(memberInfo); useCheckDuplicateNickname({ nickname: fieldValue.nickname, setFieldErrorValue }); diff --git a/src/pages/SignUp/SignUpMatch.page.tsx b/src/pages/SignUp/SignUpMatch.page.tsx index 36d157bc..50c253ce 100644 --- a/src/pages/SignUp/SignUpMatch.page.tsx +++ b/src/pages/SignUp/SignUpMatch.page.tsx @@ -7,9 +7,9 @@ import { Header, RadioContainer, Spacer, - SVGGoldBell, SVGRadioCheck24, SVGRadioUncheck24, + Tag, Text, theme, useCheckbox, @@ -27,8 +27,12 @@ import { TwoDepthBottomSheet, } from '../Write/components/TwoDepthBottomSheet'; import { useWritingInfoQuery } from '../Write/hooks/queries/useWritingInfoQuery'; -import { get2DepthCountsBy1Depth } from '../Write/utils/get2DepthCountsBy1Depth'; +import { Info } from '../Write/types'; +import { get2DepthCountsBy1DepthBranches } from '../Write/utils/get2DepthCountBy1DepthBranches'; +import { SVGBellCircle } from './assets/SVGBellCircle'; +import { useNotificationSettingsMutation } from './hooks/useNotificationSettingsMutation'; import useSignUpMutation from './hooks/useSignUpMutation'; +import { WorkingPlaceType } from './types'; import { parseQueryString } from './utils/manageQueryString'; interface QueryStringProps { @@ -54,6 +58,12 @@ interface CheckboxOption { checked: boolean; } +export const WORKING_PLACE_MAP = { + 상관없음: 'NO_MATTER', + 온라인: 'ONLINE', + 오프라인: 'OFFLINE', +} as const; + const SignUpMatchPage = () => { const openAlert = useAlert(); const { state: memberInfo }: { state: OauthMemberInfo | null } = useLocation(); @@ -61,32 +71,80 @@ const SignUpMatchPage = () => { const [isOpenBranchBottomSheet, setIsOpenBranchBottomSheet] = useState(false); const [prevFormData, _] = useState(parseQueryString); - const { branches, purposes, recruitmentPlaces, cooperationWays, skillCategoryResponses } = useWritingInfoQuery(); + const { branches, purposes, cooperationWays } = useWritingInfoQuery(); const { postSignUp } = useSignUpMutation(); + const { postNotificationSettings } = useNotificationSettingsMutation(); const { checkboxValue, selectedCheckboxId, onChangeCheckbox } = useCheckbox({ - goal: purposes, + goal: purposes.map((purpose) => ({ checked: false, ...purpose })), }); const { radioValue, selectedRadioName, onChangeRadio } = useRadio({ cooperationWays, }); - const branchBottomSheetLeftItems = [] as any; - const branchBottomSheetRightItems = [] as any; + const [selectedBranch1Depth, setSelectedBranch1Depth] = useState(branches[0].name); + const [selectedBranchResponses, setSelectedBranchResponses] = useState([]); + + const branchBottomSheetLeftItems = branches.map((item) => item.name); + const branchBottomSheetRightItems = branches.find((item) => item.name === selectedBranch1Depth)?.branchResponses; + // TODO: QA 이후 복원 // useValidateUserInfo(memberInfo); + const onClickBranch = (selected: Info) => { + if (selectedBranchResponses.length >= 10) { + openAlert({ content: '최대 10개까지 선택할 수 있습니다.' }); + return; + } + + setSelectedBranchResponses((prev) => + selectedBranchResponses.includes(selected) ? prev.filter((item) => item.id !== selected.id) : [...prev, selected], + ); + }; + + const onDeleteBranch = (id: number) => { + setSelectedBranchResponses((prev) => prev.filter((item) => item.id !== id)); + }; + const onSubmit = (e: FormEvent) => { e.preventDefault(); if (!memberInfo) return openAlert({ content: '유저 정보가 없습니다. 잘못된 접근 방법입니다.' }); - postSignUp({ - ...memberInfo, - ...prevFormData, - joinPurposes: selectedCheckboxId.goal, - }); + if (!selectedCheckboxId.goal.length) { + openAlert({ content: '가입 목적을 1개 이상 선택해 주세요.' }); + return; + } + + if (!selectedBranchResponses.length) { + openAlert({ content: '분야를 1개 이상 선택해 주세요.' }); + return; + } + + if (!selectedRadioName.cooperationWays) { + openAlert({ content: '협업 방식을 선택해 주세요.' }); + return; + } + + postSignUp( + { + ...memberInfo, + ...prevFormData, + joinPurposes: selectedCheckboxId.goal, + }, + { + onSuccess: () => { + postNotificationSettings({ + purposeIds: selectedCheckboxId.goal, + branchIds: selectedBranchResponses.map((item) => item.id), + cooperationWay: WORKING_PLACE_MAP[ + selectedRadioName.cooperationWays as keyof typeof WORKING_PLACE_MAP + ] as WorkingPlaceType, + }); + }, + }, + ); }; return ( @@ -105,11 +163,11 @@ const SignUpMatchPage = () => { - + - + 참여하고자 하는 프로젝트 조건을 등록해주세요. @@ -148,13 +206,14 @@ const SignUpMatchPage = () => { required /> - + - + 분야 +
{ setIsOpenBranchBottomSheet(true); @@ -185,28 +244,28 @@ const SignUpMatchPage = () => { return ( setSelectedTeamRecruitment1Depth(item)} - checked={selectedTeamRecruitment1Depth === item} + onClick={() => setSelectedBranch1Depth(item)} + checked={selectedBranch1Depth === item} > - + {item} - - {get2DepthCountsBy1Depth(selectedSkillResponses, skillCategoryResponses)[item]} + + {get2DepthCountsBy1DepthBranches(selectedBranchResponses, branches)[item]} ); })} - {branchBottomSheetRightItems.map((item: any) => { + {branchBottomSheetRightItems?.map((item: any) => { return ( - onClickTeamRecruitment(item)}> + onClickBranch(item)}> {item.name} - {selectedSkillResponses.includes(item) ? : } + {selectedBranchResponses.includes(item) ? : } ); })} @@ -214,20 +273,37 @@ const SignUpMatchPage = () => { + + + {selectedBranchResponses.map((item) => { + return ( + onDeleteBranch(item.id)} style={{ borderRadius: 100 }}> + {item.name} + + ); + })} + + - + ); }; +export default SignUpMatchPage; + const MainWrapper = styled.form` background-color: ${theme.color.c1}; height: 100%; `; -export default SignUpMatchPage; +const TeamLabelBox = styled.div` + display: flex; + flex-wrap: wrap; + gap: 8px; +`; diff --git a/src/pages/SignUp/assets/SVGBellCircle.tsx b/src/pages/SignUp/assets/SVGBellCircle.tsx new file mode 100644 index 00000000..7bb06ac7 --- /dev/null +++ b/src/pages/SignUp/assets/SVGBellCircle.tsx @@ -0,0 +1,33 @@ +export const SVGBellCircle = () => { + return ( + + + + + + + + + + + + + + + + + + ); +}; diff --git a/src/pages/SignUp/assets/bell_circle.svg b/src/pages/SignUp/assets/bell_circle.svg new file mode 100644 index 00000000..55173dae --- /dev/null +++ b/src/pages/SignUp/assets/bell_circle.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/src/pages/SignUp/hooks/useNotificationSettingsMutation.ts b/src/pages/SignUp/hooks/useNotificationSettingsMutation.ts new file mode 100644 index 00000000..40a76195 --- /dev/null +++ b/src/pages/SignUp/hooks/useNotificationSettingsMutation.ts @@ -0,0 +1,30 @@ +import { useMutation } from '@tanstack/react-query'; +import { http } from '../../../api/http'; +import useAlert from '../../../hooks/useAlert'; +import { WorkingPlaceType } from '../types'; + +interface Payload { + purposeIds: number[]; + branchIds: number[]; + cooperationWay: WorkingPlaceType; +} + +const _postNotificationSettings = async (payload: Payload) => { + await http.post('/notification-setting', payload); +}; + +export const useNotificationSettingsMutation = () => { + const openAlert = useAlert(); + + const { mutate: postNotificationSettings } = useMutation({ + mutationFn: (payload: Payload) => _postNotificationSettings(payload), + onSuccess: () => { + openAlert({ content: '프로필 설정이 완료되었습니다.' }); + }, + onError: () => { + openAlert({ content: '프로필 설정에 실패하였습니다.' }); + }, + }); + + return { postNotificationSettings }; +}; diff --git a/src/pages/SignUp/types/index.ts b/src/pages/SignUp/types/index.ts index 81e44dd6..1f05ff40 100644 --- a/src/pages/SignUp/types/index.ts +++ b/src/pages/SignUp/types/index.ts @@ -1,3 +1,5 @@ +export type WorkingPlaceType = 'NO_MATTER' | 'ONLINE' | 'OFFLINE'; + export interface DropdownValue { mainSkill: string; skillDepthOne: string; @@ -16,13 +18,12 @@ export interface PostSignUp { skillId: number; level: string; }[]; - joinPurposes: number[]; livingPlaceId: number; - workingPlace: string | null; introduction: string | null; email: string; oauthId: string; oauthServerType: string; + joinPurposes: number[]; } export interface Skill { diff --git a/src/pages/SignUp/utils/manageQueryString.ts b/src/pages/SignUp/utils/manageQueryString.ts index 431e0cce..2378c226 100644 --- a/src/pages/SignUp/utils/manageQueryString.ts +++ b/src/pages/SignUp/utils/manageQueryString.ts @@ -14,11 +14,17 @@ export const parseQueryString = >() => { const searchParams = new URLSearchParams(window.location.search); const searchParamsObject = Object.fromEntries(searchParams.entries()); - Object.keys(searchParamsObject).forEach((key) => { - if (key === 'skills') { - searchParamsObject[key] = JSON.parse(searchParamsObject[key]); - } - }); + const parsedObject = Object.entries(searchParamsObject).reduce( + (acc, [key, value]) => { + try { + acc[key] = JSON.parse(value); + } catch { + acc[key] = value; + } + return acc; + }, + {} as Record, + ); - return Object.fromEntries(searchParams.entries()) as T; + return parsedObject as T; }; diff --git a/src/pages/Write/Write.page.tsx b/src/pages/Write/Write.page.tsx index 902250d6..9b9a7fbf 100644 --- a/src/pages/Write/Write.page.tsx +++ b/src/pages/Write/Write.page.tsx @@ -5,12 +5,11 @@ import { Divider, Flex, RadioContainer, - SVGCancel, SVGRadioCheck24, SVGRadioUncheck24, Spacer, + Tag, Text, - theme, useCheckbox, useDropdown, useRadio, @@ -32,7 +31,8 @@ import { import { usePostIdeasMutation } from './hooks/mutations/usePostIdeasMutation'; import { useWritingInfoQuery } from './hooks/queries/useWritingInfoQuery'; import { Info, PostIdeasRequest } from './types'; -import { get2DepthCountsBy1Depth } from './utils/get2DepthCountsBy1Depth'; +import { get2DepthCountsBy1DepthBranches } from './utils/get2DepthCountBy1DepthBranches'; +import { get2DepthCountsBy1DepthSkills } from './utils/get2DepthCountsBy1DepthSkills'; const WritePage = () => { const openAlert = useAlert(); @@ -45,11 +45,12 @@ const WritePage = () => { const [isOpenBranchBottomSheet, setIsOpenBranchBottomSheet] = useState(false); const [selectedTeamRecruitment1Depth, setSelectedTeamRecruitment1Depth] = useState(skillCategoryResponses[0].name); const [selectedSkillResponses, setSelectedSkillResponses] = useState([]); + const [selectedBranch1Depth, setSelectedBranch1Depth] = useState(branches[0].name); + const [selectedBranchResponses, setSelectedBranchResponses] = useState([]); const [images, setImages] = useState([]); const { checkboxValue, selectedCheckboxId, onChangeCheckbox } = useCheckbox({ - branches, - purposes, + purposes: purposes.map((purpose) => ({ checked: false, ...purpose })), }); const { radioValue, selectedRadioName, onChangeRadio } = useRadio({ cooperationWays, @@ -58,17 +59,15 @@ const WritePage = () => { recruitmentPlace: '', }); - const branchBottomSheetLeftItems = [] as any; - const branchBottomSheetRightItems = [] as any; const teamMateBottomSheetLeftItems = skillCategoryResponses.map((item) => item.name); const teamMateBottomSheetRightItems = skillCategoryResponses.find( (item) => item.name === selectedTeamRecruitment1Depth, )?.skillResponses; + const branchBottomSheetLeftItems = branches.map((item) => item.name); + const branchBottomSheetRightItems = branches.find((item) => item.name === selectedBranch1Depth)?.branchResponses; const canSubmit = - selectedCheckboxId.branches.length > 0 && - selectedCheckboxId.purposes.length > 0 && - !!selectedRadioName.cooperationWays; + selectedBranchResponses.length > 0 && selectedCheckboxId.purposes.length > 0 && !!selectedRadioName.cooperationWays; if (!teamMateBottomSheetRightItems) { console.error('sheetRightItems is null'); @@ -76,7 +75,6 @@ const WritePage = () => { } const writeIdea = () => { - // TODO: 글쓰기 필수 조건 누락 시 토스트 띄워주기 (alert -> toast) if (!title) { openAlert({ content: '제목을 입력해 주세요.' }); return; @@ -85,7 +83,7 @@ const WritePage = () => { openAlert({ content: '본문 내용을 10자 이상 입력해 주세요.' }); return; } - if (!selectedCheckboxId.branches.length) { + if (!selectedBranchResponses.length) { openAlert({ content: '분야를 1개 이상 선택해 주세요.' }); return; } @@ -104,7 +102,7 @@ const WritePage = () => { introduce, recruitmentPlaceId: recruitmentPlaces.find((place) => place.name === dropdownValue.recruitmentPlace)?.id || 1, cooperationWay: selectedRadioName.cooperationWays, - branchIds: selectedCheckboxId.branches, + branchIds: selectedBranchResponses.map((branchResponse) => branchResponse.id), purposeIds: selectedCheckboxId.purposes, skillCategoryIds: selectedSkillResponses.map((selectedSkillResponse) => selectedSkillResponse.id), }; @@ -143,12 +141,27 @@ const WritePage = () => { setSelectedSkillResponses((prev) => prev.filter((item) => item.id !== id)); }; + const onClickBranch = (selected: Info) => { + if (selectedBranchResponses.length >= 10) { + openAlert({ content: '최대 10개까지 선택할 수 있습니다.' }); + return; + } + + setSelectedBranchResponses((prev) => + selectedBranchResponses.includes(selected) ? prev.filter((item) => item.id !== selected.id) : [...prev, selected], + ); + }; + + const onDeleteBranch = (id: number) => { + setSelectedBranchResponses((prev) => prev.filter((item) => item.id !== id)); + }; + // 글 작성중인지 여부: 뒤로가기 시 경고창 띄우기 const isWritingActive = title !== '' || introduce !== '' || selectedSkillResponses.length > 0 || - selectedCheckboxId.branches.length > 0 || + selectedBranchResponses.length > 0 || selectedCheckboxId.purposes.length > 0 || selectedRadioName.cooperationWays !== '상관없음' || !!dropdownValue.recruitmentPlace; @@ -177,7 +190,7 @@ const WritePage = () => { - + 분야
{
+ + + {selectedBranchResponses.map((item) => { + return ( + onDeleteBranch(item.id)} style={{ borderRadius: 100 }}> + {item.name} + + ); + })} + + { return ( setSelectedTeamRecruitment1Depth(item)} - checked={selectedTeamRecruitment1Depth === item} + onClick={() => setSelectedBranch1Depth(item)} + checked={selectedBranch1Depth === item} > - + {item} - - {get2DepthCountsBy1Depth(selectedSkillResponses, skillCategoryResponses)[item]} + + {get2DepthCountsBy1DepthBranches(selectedBranchResponses, branches)[item]} ); })} - {branchBottomSheetRightItems.map((item: any) => { + {branchBottomSheetRightItems?.map((item: any) => { return ( - onClickTeamRecruitment(item)}> + onClickBranch(item)}> {item.name} - {selectedSkillResponses.includes(item) ? : } + {selectedBranchResponses.includes(item) ? : } ); })} @@ -294,10 +318,9 @@ const WritePage = () => { {selectedSkillResponses.map((item) => { return ( - + onDeleteTeamRecruitment(item.id)} style={{ borderRadius: 100 }}> {item.name} - onDeleteTeamRecruitment(item.id)} cursor="pointer" /> - + ); })} @@ -324,7 +347,7 @@ const WritePage = () => { - {get2DepthCountsBy1Depth(selectedSkillResponses, skillCategoryResponses)[item]} + {get2DepthCountsBy1DepthSkills(selectedSkillResponses, skillCategoryResponses)[item]} ); @@ -366,20 +389,3 @@ const TeamLabelBox = styled.div` flex-wrap: wrap; gap: 8px; `; - -const TeamLabel = styled.label` - display: flex; - justify-content: center; - align-items: center; - padding: 11px 16px 12px; - height: 40px; - box-sizing: border-box; - border: 1px solid ${theme.color.l2}; - border-radius: 6px; - background-color: ${theme.color.c1}; - color: ${theme.color.w1}; - font-size: 14px; - font-weight: 500; - width: fit-content; - gap: 14px; -`; diff --git a/src/pages/Write/hooks/queries/temp/tempBranches.ts b/src/pages/Write/hooks/queries/temp/tempBranches.ts new file mode 100644 index 00000000..6b8cc094 --- /dev/null +++ b/src/pages/Write/hooks/queries/temp/tempBranches.ts @@ -0,0 +1,166 @@ +export const BRANCH_DTO = [ + { + id: 1, + name: '기획', + skillResponses: [ + { + id: 7, + name: 'IT기획', + }, + { + id: 8, + name: '게임기획', + }, + { + id: 9, + name: '제품기획', + }, + { + id: 10, + name: '사업기획', + }, + ], + }, + { + id: 2, + name: '디자인', + skillResponses: [ + { + id: 11, + name: 'UX/UI', + }, + { + id: 12, + name: '게임디자인', + }, + { + id: 13, + name: '캐릭터디자인', + }, + { + id: 14, + name: '그림/일러스트', + }, + { + id: 15, + name: '제품디자인', + }, + { + id: 16, + name: '시각디자인', + }, + { + id: 17, + name: '패션디자인', + }, + { + id: 18, + name: '출판디자인', + }, + ], + }, + { + id: 3, + name: '개발', + skillResponses: [ + { + id: 19, + name: 'BE', + }, + { + id: 20, + name: 'FE', + }, + { + id: 21, + name: 'AOS', + }, + { + id: 22, + name: 'iOS', + }, + { + id: 23, + name: '퍼블리싱', + }, + { + id: 24, + name: '서버개발', + }, + { + id: 25, + name: '게임개발', + }, + ], + }, + { + id: 4, + name: '데이터', + skillResponses: [ + { + id: 26, + name: '데이터엔지니어', + }, + { + id: 27, + name: '데이터분석', + }, + ], + }, + { + id: 5, + name: '마케팅/영업', + skillResponses: [ + { + id: 28, + name: '퍼포먼스마케팅', + }, + { + id: 29, + name: '콘텐츠마케팅', + }, + { + id: 30, + name: '광고/크리에이티브', + }, + { + id: 31, + name: '브랜딩', + }, + { + id: 32, + name: '세일즈', + }, + ], + }, + { + id: 6, + name: '미디어', + skillResponses: [ + { + id: 33, + name: 'PD', + }, + { + id: 34, + name: '작가', + }, + { + id: 35, + name: '음악', + }, + { + id: 36, + name: '영상촬영', + }, + { + id: 37, + name: '영상편집', + }, + { + id: 38, + name: '사진', + }, + ], + }, +]; diff --git a/src/pages/Write/hooks/queries/useWritingInfoQuery.ts b/src/pages/Write/hooks/queries/useWritingInfoQuery.ts index a580b7b6..87dbe88e 100644 --- a/src/pages/Write/hooks/queries/useWritingInfoQuery.ts +++ b/src/pages/Write/hooks/queries/useWritingInfoQuery.ts @@ -3,14 +3,18 @@ import { useSuspenseQuery } from '@tanstack/react-query'; import { http } from '../../../../api/http'; import { Idea } from '../../types'; -const cooperations = [ +const COOPERATIONS = [ { id: 1, name: '상관없음' }, { id: 2, name: '온라인' }, { id: 3, name: '오프라인' }, ]; +export const COOPERATION_OPTIONS = COOPERATIONS.map((properties) => + properties.id === 1 ? { checked: true, ...properties } : { checked: false, ...properties }, +); + const getWritingInfo = async () => { - return http.get('/ideas/writing'); + return http.get('/writing'); }; export const useWritingInfoQuery = () => { @@ -18,27 +22,24 @@ export const useWritingInfoQuery = () => { queryKey: ['writingInfo'], queryFn: getWritingInfo, select: (data) => { - const branches = data.branches.map((properties) => ({ checked: false, ...properties })); - const purposes = data.purposes.map((properties) => ({ checked: false, ...properties })); - const cooperationWays = cooperations.map((properties) => - properties.id === 1 ? { checked: true, ...properties } : { checked: false, ...properties }, - ); + const purposes = data.purposesResponses.map((properties) => ({ checked: false, ...properties })); + const cooperationWays = COOPERATION_OPTIONS; return { ...data, - branches, purposes, cooperationWays, }; }, }); - const { branches, purposes, regions: recruitmentPlaces, cooperationWays, skillCategoryResponses } = writingInfo; + const { branchesResponses, purposesResponses, regionsResponses, cooperationWays, skillCategoryResponses } = + writingInfo; return { - branches, - purposes, - recruitmentPlaces, + branches: branchesResponses, + purposes: purposesResponses, + recruitmentPlaces: regionsResponses, cooperationWays, skillCategoryResponses, ...rest, diff --git a/src/pages/Write/types/index.ts b/src/pages/Write/types/index.ts index 9f3f0455..6300963d 100644 --- a/src/pages/Write/types/index.ts +++ b/src/pages/Write/types/index.ts @@ -1,3 +1,5 @@ +import { WorkingPlaceType } from '../../SignUp/types'; + // 글쓰기 요청 body 타입 export interface PostIdeasRequest extends FormData { request: { @@ -18,20 +20,27 @@ export type Info = { name: string; }; -export type Idea = { - branches: Info[]; // 분야 - purposes: Info[]; // 목적 - regions: Info[]; // 팀원 모집 지역 - skillCategoryResponses: { - // 팀원 모집 종류 - id: number; - name: string; // 기획 - skillResponses: Info[]; // IT기획, 게임기획, 제품기획, 사업기획 - }[]; +export type WrappedSkillInfo = { + id: number; + name: string; + skillResponses: Info[]; }; -export type CooperationWay = { +export type WrappedBranchInfo = { id: number; name: string; - checked: boolean; + branchResponses: Info[]; +}; + +export type Idea = { + purposesResponses: Info[]; + regionsResponses: Info[]; + branchesResponses: WrappedBranchInfo[]; + skillCategoryResponses: WrappedSkillInfo[]; +}; + +export type NotificationDTO = { + cooperationWays: WorkingPlaceType[]; + purposes: Info[]; + branches: WrappedBranchInfo[]; }; diff --git a/src/pages/Write/utils/get2DepthCountBy1DepthBranches.ts b/src/pages/Write/utils/get2DepthCountBy1DepthBranches.ts new file mode 100644 index 00000000..408b7af3 --- /dev/null +++ b/src/pages/Write/utils/get2DepthCountBy1DepthBranches.ts @@ -0,0 +1,21 @@ +import { Idea, Info } from '../types'; + +// 선택된 항목을 기반으로 카테고리(1depth)별 선택된 스킬(2depth) 수를 계산하는 함수 +export const get2DepthCountsBy1DepthBranches = ( + selectedSkillResponses: Info[], + teamRecruitmentCategories: Idea['branches'], +) => { + const categoryCount: Record = {}; + const selectedIds = selectedSkillResponses.map((item) => item.id); + + // 모든 카테고리를 순회 + teamRecruitmentCategories.forEach((category) => { + const count = category.branchResponses.filter((item) => selectedIds.includes(item.id)).length; + + if (count > 0) { + categoryCount[category.name] = count; + } + }); + + return categoryCount; +}; diff --git a/src/pages/Write/utils/get2DepthCountsBy1Depth.ts b/src/pages/Write/utils/get2DepthCountsBy1DepthSkills.ts similarity index 74% rename from src/pages/Write/utils/get2DepthCountsBy1Depth.ts rename to src/pages/Write/utils/get2DepthCountsBy1DepthSkills.ts index 96023487..3972ad0b 100644 --- a/src/pages/Write/utils/get2DepthCountsBy1Depth.ts +++ b/src/pages/Write/utils/get2DepthCountsBy1DepthSkills.ts @@ -1,22 +1,17 @@ import { Idea, Info } from '../types'; // 선택된 항목을 기반으로 카테고리(1depth)별 선택된 스킬(2depth) 수를 계산하는 함수 -export const get2DepthCountsBy1Depth = ( +export const get2DepthCountsBy1DepthSkills = ( selectedSkillResponses: Info[], teamRecruitmentCategories: Idea['skillCategoryResponses'], ) => { - // 결과를 저장할 객체 const categoryCount: Record = {}; - - // 선택된 항목의 id 배열 const selectedIds = selectedSkillResponses.map((item) => item.id); // 모든 카테고리를 순회 teamRecruitmentCategories.forEach((category) => { - // 현재 카테고리에 선택된 항목의 수를 세기 const count = category.skillResponses.filter((item) => selectedIds.includes(item.id)).length; - // 선택된 항목이 있으면 결과 객체에 추가 if (count > 0) { categoryCount[category.name] = count; } diff --git a/src/pages/WriteEdit/WriteEdit.page.tsx b/src/pages/WriteEdit/WriteEdit.page.tsx index 8050474d..9d885ea7 100644 --- a/src/pages/WriteEdit/WriteEdit.page.tsx +++ b/src/pages/WriteEdit/WriteEdit.page.tsx @@ -12,6 +12,7 @@ import { SVGRadioCheck24, SVGRadioUncheck24, Spacer, + Tag, Text, theme, useCheckbox, @@ -22,6 +23,8 @@ import { useState } from 'react'; import { useLocation } from 'react-router-dom'; import useAlert from '../../hooks/useAlert'; +import { TwoDepthBottomSheet } from '../Write/components/TwoDepthBottomSheet'; +import { get2DepthCountsBy1DepthBranches } from '../Write/utils/get2DepthCountBy1DepthBranches'; import Header from './components/Header'; import RecruitmentPlaceSection from './components/RecruitmentPlaceSection'; import TitleAndIntroduceSection from './components/TitleAndIntroduceSection'; @@ -38,8 +41,14 @@ const WriteEditPage = () => { const location = useLocation(); const { putIdea } = usePutIdea(); - const { ideaDetail, branches, purposes, recruitmentPlaces, cooperationWays, skillCategoryResponses } = - useWritingEditInfoQuery(Number(location.state.ideaId)); + const { + ideaDetail, + branchesResponses, + purposesResponses, + recruitmentPlaces, + cooperationWays, + skillCategoryResponses, + } = useWritingEditInfoQuery(Number(location.state.ideaId)); const [title, setTitle] = useState(ideaDetail.title); const [introduce, setIntroduce] = useState(ideaDetail.introduce); @@ -53,9 +62,17 @@ const WriteEditPage = () => { .filter((item) => ideaDetail.skillCategories.includes(item.name)), ); + const [isOpenBranchBottomSheet, setIsOpenBranchBottomSheet] = useState(false); + const [selectedBranch1Depth, setSelectedBranch1Depth] = useState(branchesResponses[0].name); + const [selectedBranchResponses, setSelectedBranchResponses] = useState( + branchesResponses + .map((item) => item.branchResponses) + .flat() + .filter((item) => ideaDetail.branchList.includes(item.name)), + ); + const { checkboxValue, selectedCheckboxId, onChangeCheckbox } = useCheckbox({ - branches, - purposes, + purposes: purposesResponses.map((item) => ({ checked: ideaDetail.purposeList.includes(item.name), ...item })), }); const { radioValue, selectedRadioName, onChangeRadio } = useRadio({ cooperationWays, @@ -68,10 +85,12 @@ const WriteEditPage = () => { const sheetRightItems = skillCategoryResponses.find((item) => item.name === selectedTeamRecruitment1Depth) ?.skillResponses; + const branchBottomSheetLeftItems = branchesResponses.map((item) => item.name); + const branchBottomSheetRightItems = branchesResponses.find((item) => item.name === selectedBranch1Depth) + ?.branchResponses; + const canSubmit = - selectedCheckboxId.branches.length > 0 && - selectedCheckboxId.purposes.length > 0 && - !!selectedRadioName.cooperationWays; + selectedBranchResponses.length > 0 && selectedCheckboxId.purposes.length > 0 && !!selectedRadioName.cooperationWays; if (!sheetRightItems) { console.error('sheetRightItems is null'); @@ -88,7 +107,7 @@ const WriteEditPage = () => { openAlert({ content: '본문 내용을 10자 이상 입력해 주세요.' }); return; } - if (!selectedCheckboxId.branches.length) { + if (!selectedBranchResponses.length) { openAlert({ content: '분야를 1개 이상 선택해 주세요.' }); return; } @@ -111,7 +130,7 @@ const WriteEditPage = () => { introduce, recruitmentPlaceId: recruitmentPlaces.find((place) => place.name === dropdownValue.recruitmentPlace)?.id || 1, cooperationWay: selectedRadioName.cooperationWays, - branchIds: selectedCheckboxId.branches, + branchIds: selectedBranchResponses.map((branch) => branch.id), purposeIds: selectedCheckboxId.purposes, skillCategoryIds: selectedSkillResponses.map((selectedSkillResponse) => selectedSkillResponse.id), imageIds: alreadyUploadedImages.map((image) => image.id), @@ -164,6 +183,21 @@ const WriteEditPage = () => { setImages(deletedImages); }; + const onClickBranch = (selected: Info) => { + if (selectedBranchResponses.length >= 10) { + openAlert({ content: '최대 10개까지 선택할 수 있습니다.' }); + return; + } + + setSelectedBranchResponses((prev) => + selectedBranchResponses.includes(selected) ? prev.filter((item) => item.id !== selected.id) : [...prev, selected], + ); + }; + + const onDeleteBranch = (id: number) => { + setSelectedBranchResponses((prev) => prev.filter((item) => item.id !== id)); + }; + return (
@@ -184,14 +218,80 @@ const WriteEditPage = () => { - + + + 분야 + +
{ + setIsOpenBranchBottomSheet(true); + }} + > + + + + 추가하기 + + +
+
+ + + + {selectedBranchResponses.map((item) => { + return ( + onDeleteBranch(item.id)} style={{ borderRadius: 100 }}> + {item.name} + + ); + })} + + + setIsOpenBranchBottomSheet(false)} + > + + {branchBottomSheetLeftItems.map((item: any) => { + return ( + setSelectedBranch1Depth(item)} + checked={selectedBranch1Depth === item} + > + + {item} + + + + {get2DepthCountsBy1DepthBranches(selectedBranchResponses, branchesResponses)[item]} + + + ); + })} + + + {branchBottomSheetRightItems?.map((item: any) => { + return ( + onClickBranch(item)}> + + {item.name} + + {selectedBranchResponses.includes(item) ? : } + + ); + })} + +
+ { - return http.get('/ideas/writing'); + return http.get('/writing'); }; export const useWritingEditInfoQuery = (ideaId: number) => { @@ -20,12 +28,12 @@ export const useWritingEditInfoQuery = (ideaId: number) => { queryKey: ['writingInfo'], queryFn: getWritingInfo, select: (data) => { - const branches = data.branches.map((properties) => + const branches = data.branchesResponses.map((properties) => ideaDetail.branchList.includes(properties.name) ? { checked: true, ...properties } : { checked: false, ...properties }, ); - const purposes = data.purposes.map((properties) => + const purposes = data.purposesResponses.map((properties) => ideaDetail.purposeList.includes(properties.name) ? { checked: true, ...properties } : { checked: false, ...properties }, @@ -45,7 +53,16 @@ export const useWritingEditInfoQuery = (ideaId: number) => { }, }); - const { branches, purposes, regions: recruitmentPlaces, cooperationWays, skillCategoryResponses } = writingInfo; + const { branchesResponses, purposesResponses, regionsResponses, cooperationWays, skillCategoryResponses } = + writingInfo; - return { ideaDetail, branches, purposes, recruitmentPlaces, cooperationWays, skillCategoryResponses, ...rest }; + return { + ideaDetail, + branchesResponses, + purposesResponses, + recruitmentPlaces: regionsResponses, + cooperationWays, + skillCategoryResponses, + ...rest, + }; }; diff --git a/src/pages/components/NewIdeaCard/compound/Content/Content.tsx b/src/pages/components/NewIdeaCard/compound/Content/Content.tsx index 578c798f..6a556c52 100644 --- a/src/pages/components/NewIdeaCard/compound/Content/Content.tsx +++ b/src/pages/components/NewIdeaCard/compound/Content/Content.tsx @@ -81,7 +81,7 @@ const Content = ({ onClick }: Props) => {
- {branches.map((branch) => ( + {identifyAbbreviationBadge(branches, 13).map((branch) => ( {branch} @@ -97,7 +97,7 @@ const Content = ({ onClick }: Props) => { - {identifyAbbreviationBadge(skillCategories, 15).map((category) => ( + {identifyAbbreviationBadge(skillCategories, 13).map((category) => ( {category} diff --git a/src/router.tsx b/src/router.tsx index 2970f7cb..b37e4f4a 100644 --- a/src/router.tsx +++ b/src/router.tsx @@ -20,6 +20,7 @@ const SignUpPage = lazy(() => import('./pages/SignUp/SignUp.page')); const NeedAuth = lazy(() => import('./pages/NeedAuth')); const SignUpMatchPage = lazy(() => import('./pages/SignUp/SignUpMatch.page')); const NotificationPage = lazy(() => import('./pages/Notification/Notification.page')); +const ProfileEditMatchPage = lazy(() => import('./pages/ProfileEdit/ProfileEditMatch.page')); interface RouteElement { path: string; @@ -81,6 +82,10 @@ const routes: RouteElement[] = [ path: '/profile-edit', element: withAsyncBoundary(), }, + { + path: '/profile-edit-match', + element: withAsyncBoundary(), + }, { path: '/profile/:id/more', element: ,