pages/CreateBungae.js
pages/EditBungae.js
components/CreateBungae/
CreateBungaeForm.js
: <CreateBungaePage>
및 <EditBungaePage>
가 공통으로 갖는 자식 컴포넌트로 공통된 데이터 및 마크업을 포함MeetingLocation.js
: <CreateBungaeForm>
내의 모임 장소 박스, css 코드량이 많아서 따로 분리폼과 다루는 데이터가 동일한 작성 페이지와 수정 페이지를 하나의 공통 컴포넌트를 사용함으로써 코드가 중복되지 않도록 코드를 만들었다. 또한 분리할 수 있는 상태 및 함수는 분리해서 @hooks 폴더 내에 커스텀 훅 또는 @util 폴더에서 함수로 만든 다음 컴포넌트에서 import 해서 사용함으로서 코드의 가독성 및 재사용성을 높인다.
<CreateBungaeForm>
폼 컴포넌트를 자식 컴포넌트로 사용할 때, 번개 상세 페이지의 객체 데이터를 받는 props(bungaeDetail)의 유무에 따라 작성/수정 페이지의 state 초기값이 다르게 들어오도록 구별한다.
pages/CreateBungae.js
import CreateBungaeForm from "../components/CreateBungae/CreateBungaeForm";
import HeadingPageContent from "../components/PageContent/HeadingPageContent";
import RootPageContent from "../components/PageContent/RootPageContent";
function CreateBungaePage() {
return (
<RootPageContent maxWidth="md">
<HeadingPageContent heading="번개 모임 만들기">
<CreateBungaeForm />
</HeadingPageContent>
</RootPageContent>
);
}
export default CreateBungaePage;
pages/EditBungae.js
import { dummyBungaeDetail } from "../@constants/dummy";
import CreateBungaeForm from "../components/CreateBungae/CreateBungaeForm";
import HeadingPageContent from "../components/PageContent/HeadingPageContent";
import RootPageContent from "../components/PageContent/RootPageContent";
function EditBungaePage() {
return (
<RootPageContent maxWidth="md">
<HeadingPageContent heading="번개 모임 만들기">
<CreateBungaeForm bungaeDetail={dummyBungaeDetail} />
</HeadingPageContent>
</RootPageContent>
);
}
export default EditBungaePage;
components/CreateBungae/CreateBungaeForm.js
import { useState } from "react";
import styled from "styled-components";
import MeetingLocation from "./MeetingLocation";
import { numberOptionList, timeOptionList } from "../../@constants/dropdown";
import useDropdown from "../../@hooks/useDropdown";
import useInput from "../../@hooks/useInput";
import { getInitialBungaeState } from "../../@utils/bungaeInfo";
import Button from "../UI/Button";
import Dropdown from "../UI/Dropdown";
import InputWithLabel from "../UI/InputWithLabel";
import Textarea from "../UI/Textarea";
const StyledMarginWrapper = styled.div`
width: 100%;
margin-bottom: 20px;
`;
const StyledDropdownContainer = styled(StyledMarginWrapper)`
display: flex;
gap: 20px;
`;
const StyledNarrowMarginWrapper = styled(StyledMarginWrapper)`
margin-bottom: 14px;
`;
const StyledButtonContainer = styled.div`
align-self: flex-end;
display: flex;
gap: 10px;
`;
function CreateBungaeForm({ bungaeDetail, onSubmit }) {
// bungaeDetail에 따라 state 초기값으로 세팅될 값을 반환하는 함수 (getInitialBungaeState)
const {
initialNumberOfRecruits,
initialMeetingTime,
initialMeetingLocation,
initialOpenChat,
initialIntroduction
} = getInitialBungaeState(bungaeDetail);
// 드롭다운 상태 및 로직을 관리하는 커스텀 훅 (useDropdown)
const {
ref: numberDropdownRef,
isOpen: numberDropdownIsOpen,
selected: selectedNumberOption,
onToggle: onToggleNumberDropdown,
onSelect: onSelectNumberOption
} = useDropdown(initialNumberOfRecruits);
const {
ref: timeDropdownRef,
isOpen: timeDropdownIsOpen,
selected: selectedTimeOption,
onToggle: onToggleTimeDropdown,
onSelect: onSelectTimeOption
} = useDropdown(initialMeetingTime);
const [meetingLocation] = useState(initialMeetingLocation);
// input value 및 change 이벤트를 관리하는 커스텀 훅 (useInput)
const { value: openChat, onChange: onChangeOpenChat } =
useInput(initialOpenChat);
const { value: introduction, onChange: onChangeIntroduction } =
useInput(initialIntroduction);
const bungaeSubmitHandler = () => {
// 서버로 post(작성) 혹은 patch(수정) 요청
// onSubmit();
// 후에 detailPage로 이동
};
return (
<>
<StyledDropdownContainer>
<Dropdown
label="모집 인원"
ref={numberDropdownRef}
isOpen={numberDropdownIsOpen}
selected={selectedNumberOption}
onToggle={onToggleNumberDropdown}
onSelect={onSelectNumberOption}
options={numberOptionList}
/>
<Dropdown
label="모임 시간"
ref={timeDropdownRef}
isOpen={timeDropdownIsOpen}
selected={selectedTimeOption}
onToggle={onToggleTimeDropdown}
onSelect={onSelectTimeOption}
options={timeOptionList}
/>
</StyledDropdownContainer>
<StyledMarginWrapper>
<MeetingLocation meetingLocation={meetingLocation} />
</StyledMarginWrapper>
<StyledMarginWrapper>
<InputWithLabel
label="카카오톡 오픈채팅"
id="kakao-link"
placeholder="오픈 카톡방 링크"
fontSize="base"
height="46px"
value={openChat}
onChange={onChangeOpenChat}
/>
</StyledMarginWrapper>
<StyledNarrowMarginWrapper>
<InputWithLabel
label="제목"
id="create-bungae-title"
name="title"
placeholder="제목을 입력해주세요"
fontSize="base"
height="46px"
value={introduction.title}
onChange={onChangeIntroduction}
/>
</StyledNarrowMarginWrapper>
<StyledNarrowMarginWrapper>
<Textarea
name="description"
placeholder="번개 모임에 대해 소개해주세요"
height="340px"
value={introduction.description}
onChange={onChangeIntroduction}
/>
</StyledNarrowMarginWrapper>
<StyledButtonContainer>
<Button background="white" outline>
취소
</Button>
<Button
background="mainViolet"
color="white"
onClick={bungaeSubmitHandler}
>
번개 등록
</Button>
</StyledButtonContainer>
</>
);
}
export default CreateBungaeForm;
bungaeDetail 더미 데이터 구조
export const dummyBungaeDetail = {
id: 1,
owner: {
id: 1,
email: "test@test.com",
emoji: "😶🌫️", // 필요
nickname: "닉네임입니다"
},
title: "오늘 7시 성수역 클라이밍 하실 분 계신가요!!...",
description: "설명",
location: {
country: "한국",
city: "서울",
state: "성동구",
street: "성수일로",
zipCode: "1234",
detail: "303호"
},
openChat: "http://localhost:3000/", // 필요
createdAt: "2023-05-17T00:00:00",
meetingAt: "2023-05-17T18:00:00",
numberOfParticipants: 2,
numberOfRecruits: 4
};
@utils/bungaeInfo
, getInitialBungaeState
함수
export const getMeetingTime = (meetingAt) => {
const meetingDate = new Date(meetingAt);
const hours = String(meetingDate.getHours()).padStart(2, "0");
const minutes = String(meetingDate.getMinutes()).padStart(2, "0");
return `${hours}:${minutes}`;
};
export const getInitialBungaeState = (bungaeDetail) => {
let initialNumberOfRecruits = { name: "1명 ~ 10명", value: null };
let initialMeetingTime = { name: "00:30 ~ 23:30", value: null };
let initialMeetingLocation = null;
let initialOpenChat = "";
let initialIntroduction = { title: "", description: "" };
if (bungaeDetail) {
initialNumberOfRecruits = numberOptionList.find(
({ value }) => value === bungaeDetail.numberOfRecruits
);
initialMeetingTime = timeOptionList.find(
({ value }) => value === getMeetingTime(bungaeDetail.meetingAt)
);
initialMeetingLocation = `${bungaeDetail.location.city} ${bungaeDetail.location.state} ${bungaeDetail.location.street} ${bungaeDetail.location.zipCode} ${bungaeDetail.location.detail}`;
initialOpenChat = bungaeDetail.openChat;
initialIntroduction = {
title: bungaeDetail.title,
description: bungaeDetail.description
};
}
return {
initialNumberOfRecruits,
initialMeetingTime,
initialMeetingLocation,
initialOpenChat,
initialIntroduction
};
};
/@constants/dropdown
, numberOptionList
, timeOptionList
export const numberOptionList = [
{ name: "1명", value: 1 },
{ name: "2명", value: 2 },
{ name: "3명", value: 3 },
{ name: "4명", value: 4 },
{ name: "5명", value: 5 },
{ name: "6명", value: 6 },
{ name: "7명", value: 7 },
{ name: "8명", value: 8 },
{ name: "9명", value: 9 },
{ name: "10명", value: 10 }
];
export const timeOptionList = [
{ name: "00:30", value: "00:30" },
{ name: "01:00", value: "01:00" },
{ name: "01:30", value: "01:30" },
{ name: "02:00", value: "02:00" },
{ name: "02:30", value: "02:30" },
{ name: "03:00", value: "03:00" },
{ name: "03:30", value: "03:30" },
{ name: "04:00", value: "04:00" },
{ name: "04:30", value: "04:30" },
{ name: "05:00", value: "05:00" },
{ name: "05:30", value: "05:30" },
{ name: "06:00", value: "06:00" },
{ name: "06:30", value: "06:30" },
{ name: "07:00", value: "07:00" },
{ name: "07:30", value: "07:30" },
{ name: "08:00", value: "08:00" },
{ name: "08:30", value: "08:30" },
{ name: "09:00", value: "09:00" },
{ name: "09:30", value: "09:30" },
{ name: "10:00", value: "10:00" },
{ name: "10:30", value: "10:30" },
{ name: "11:00", value: "11:00" },
{ name: "11:30", value: "11:30" },
{ name: "12:00", value: "12:00" },
{ name: "12:30", value: "12:30" },
{ name: "13:00", value: "13:00" },
{ name: "13:30", value: "13:30" },
{ name: "14:00", value: "14:00" },
{ name: "14:30", value: "14:30" },
{ name: "15:00", value: "15:00" },
{ name: "15:30", value: "15:30" },
{ name: "16:00", value: "16:00" },
{ name: "16:30", value: "16:30" },
{ name: "17:00", value: "17:00" },
{ name: "17:30", value: "17:30" },
{ name: "18:00", value: "18:00" },
{ name: "18:30", value: "18:30" },
{ name: "19:00", value: "19:00" },
{ name: "19:30", value: "19:30" },
{ name: "20:00", value: "20:00" },
{ name: "20:30", value: "20:30" },
{ name: "21:00", value: "21:00" },
{ name: "21:30", value: "21:30" },
{ name: "22:00", value: "22:00" },
{ name: "22:30", value: "22:30" },
{ name: "23:00", value: "23:00" },
{ name: "23:30", value: "23:30" }
];