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

[Feature] - 여행기 & 여행 계획 등록/수정 페이지 리팩터링 #520

Merged
merged 18 commits into from
Oct 14, 2024
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
ab97029
refactor(useTravelogueDays): 여행기 등록 페이지 default export로 변경
jinyoung234 Oct 9, 2024
49a501a
refactor(useTravelogueForm): 여행기 form에 대한 책임을 useTravelogueForm으로 분리
jinyoung234 Oct 9, 2024
1e6eec1
refactor(useMultiImageUpload): 다중 이미지 업로드 로직에 대한 리팩터링
jinyoung234 Oct 9, 2024
3ddfe8d
refactor(TravelogueRegisterPage): 페이지 리팩터링
jinyoung234 Oct 9, 2024
d23a62d
refactor(ThumbnailUpload): fileRef와 click 핸들러를 내부에서 관리하는 것으로 변경
jinyoung234 Oct 10, 2024
a5a7ec8
refactor: 인증된 사용자가 아닌 경우 redirect 시키는 hook 분리
jinyoung234 Oct 10, 2024
7745cd3
refactor: useToggle 훅을 통해 open, close 하도록 변경
jinyoung234 Oct 10, 2024
5d0a22f
refactor(travelTransform): TravelTransformPlace의 타입을 유틸리티 타입으로 개선
jinyoung234 Oct 10, 2024
cf84ef5
refactor(TravelogueRegisterPage): useTravelogueForm 형태 변경
jinyoung234 Oct 10, 2024
ee784f0
refactor(TravelogueEditPage): 여행기 수정 페이지 리팩터링
jinyoung234 Oct 10, 2024
c8ebee0
refactor(TravelPlanRegisterPage): 여행 계획 등록에 대한 책임 분리
jinyoung234 Oct 10, 2024
9045bc3
refactor(TravelPlanRegisterPage): 여행 계획 수정 페이지 리팩터링
jinyoung234 Oct 10, 2024
b44f89f
refactor: useTravelPlanFormState, useTravelogueFormState 폴더구조 변경
jinyoung234 Oct 10, 2024
52dca65
fix(useTravelogueFormState): ci 문제 해결
jinyoung234 Oct 10, 2024
6b28065
refactor: on prefix를 handle prefix로 변경
jinyoung234 Oct 13, 2024
5defe1c
refactor(ThumbnailUpload): 불필요한 제네릭 제거
jinyoung234 Oct 13, 2024
02fc25f
refactor(travelTransform): import 시 type 추가
jinyoung234 Oct 13, 2024
6e85fd3
refactor(ThumbnailUpload): 불필요한 props 개행 제거
jinyoung234 Oct 13, 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: 1 addition & 1 deletion frontend/__tests__/travelPlanRegisterPage.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { renderHook, waitFor } from "@testing-library/react";

import { TravelPlanPlace } from "@type/domain/travelPlan";

import { useTravelPlanDays } from "@hooks/pages/useTravelPlanDays";
import { useTravelPlanDays } from "@hooks/pages/useTravelPlanFormState/useTravelPlanDays";
import useCalendar from "@hooks/useCalendar";

import { createTravelPlanRegisterHook } from "./utils/createTravelPlanRegisterHook";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,7 @@ const inputContents = {
/>
),
thumbnailUploader: (
<ThumbnailUpload
id="thumbnail-upload-1"
previewUrls={[]}
fileInputRef={React.createRef()}
onChangeImage={() => {}}
onClickButton={() => {}}
/>
<ThumbnailUpload id="thumbnail-upload-1" previewUrls={[]} onChangeImage={() => {}} />
),
calendar: <Input placeholder="시작일을 입력해주세요" />,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,13 @@ const meta = {
export default meta;
type Story = StoryObj<typeof meta>;
const mockOnChangeImage = () => {};
const mockOnClickButton = () => {};

export const Default: Story = {
args: {
id: "1",
previewUrls: [],
fileInputRef: React.createRef<HTMLInputElement>(),

onChangeImage: mockOnChangeImage,
onClickButton: mockOnClickButton,
},
};
export const WithImage: Story = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { InputHTMLAttributes, useState } from "react";
import { InputHTMLAttributes, useRef, useState } from "react";

import { PRIMITIVE_COLORS } from "@styles/tokens";

Expand All @@ -11,22 +11,25 @@ import * as S from "./ThumbnailUpload.styled";
interface ThumbnailUploadProps extends InputHTMLAttributes<HTMLInputElement> {
id?: string;
previewUrls: string[];
fileInputRef: React.RefObject<HTMLInputElement>;

onChangeImage: (e: React.ChangeEvent<HTMLInputElement>) => void;
onClickButton: () => void;

onDeleteButton?: () => void;
}

const ThumbnailUpload = ({
id,
previewUrls,
fileInputRef,
onChangeImage,
onClickButton,
onDeleteButton,
}: ThumbnailUploadProps) => {
const [isShowEditButton, setIsShowEditButton] = useState<boolean>(false);
const [isLoading, setIsLoading] = useState<boolean>(true);
const thumbnailFileInputRef = useRef<HTMLInputElement>(null);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

개인적으로 궁금한건데 boolean을 기입하지 않아도 충분히 타입 추론이 될텐데 넣어주는 이유가 있나요? 저는 잘 안 넣는 편인데 단지 지니의 생각이 궁금해서 질문을 남겨봅니다

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

제가 작업했던 부분이 아니라 boolean이 적혀있는지도 몰랐네요,,,, 빼도 동일하게 동작해서 빼놓을게요!


const handleButtonClick = () => {
thumbnailFileInputRef.current?.click();
};

const handleMouseOver = () => {
if (!isLoading) setIsShowEditButton(true);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

handleMouseOver함수와 handleMouseLeave 함수가 거의 똑같은데,

const handleMouseEvents = (isOver: boolean) => () => {
  if (!isLoading) setIsShowEditButton(isOver);
};

이런 리팩토링은 어떨까요?!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아하 제가 작업했던 부분은 아니라 리팩터링 사항에 반영해놓을게요 :)

Expand Down Expand Up @@ -56,7 +59,7 @@ const ThumbnailUpload = ({
const HiddenInput = (
<S.ThumbnailUploadHiddenInput
id={id}
ref={fileInputRef}
ref={thumbnailFileInputRef}
type="file"
accept="image/*"
onChange={handleChangeImage}
Expand All @@ -79,7 +82,11 @@ const ThumbnailUpload = ({

{!image ? (
<>
<S.ThumbnailUploadButton type="button" onClick={onClickButton} aria-label="이미지 업로드">
<S.ThumbnailUploadButton
type="button"
onClick={handleButtonClick}
aria-label="이미지 업로드"
>
<PictureIcon />
<p>썸네일 업로드</p>
</S.ThumbnailUploadButton>
Expand All @@ -91,7 +98,7 @@ const ThumbnailUpload = ({
onMouseLeave={handleMouseLeave}
>
{isShowEditButton && (
<S.ThumbnailUploadEditButton onClick={onClickButton}>
<S.ThumbnailUploadEditButton onClick={handleButtonClick}>
썸네일 수정하기
</S.ThumbnailUploadEditButton>
)}
Expand Down
141 changes: 41 additions & 100 deletions frontend/src/components/pages/travelPlanEdit/TravelPlanEditPage.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
import { useEffect, useState } from "react";
import { useNavigate, useParams } from "react-router-dom";

import { useGetTravelPlan } from "@queries/useGetTravelPlan";
import { usePutTravelPlan } from "@queries/usePutTravelPlan";
import { useNavigate } from "react-router-dom";

import {
Accordion,
Expand All @@ -17,12 +13,13 @@ import {
TextField,
} from "@components/common";
import Calendar from "@components/common/Calendar/Calendar";
import useTravelPlanEdit from "@components/pages/travelPlanEdit/hooks/useTravelPlanEdit";
import { useTravelPlanInitialization } from "@components/pages/travelPlanEdit/hooks/useTravelPlanInitialization";
import TravelPlanDayAccordion from "@components/pages/travelPlanRegister/TravelPlanDayAccordion/TravelPlanDayAccordion";

import { useTravelPlanDays } from "@hooks/pages/useTravelPlanDays";
import useLeadingDebounce from "@hooks/useLeadingDebounce";
import useTravelPlanFormState from "@hooks/pages/useTravelPlanFormState/useTravelPlanFormState";
import useToggle from "@hooks/useToggle";

import { DEBOUNCED_TIME } from "@constants/debouncedTime";
import { ERROR_MESSAGE_MAP } from "@constants/errorMessage";
import { FORM_VALIDATIONS_MAP } from "@constants/formValidation";
import { ROUTE_PATHS_MAP } from "@constants/route";
Expand All @@ -32,106 +29,50 @@ import { extractUTCDate } from "@utils/extractUTCDate";
import * as S from "./TravelPlanEditPage.styled";

const TravelPlanEditPage = () => {
const navigate = useNavigate();

const { id = "" } = useParams();

const { data, status, error, isLoading } = useGetTravelPlan(id);

const [title, setTitle] = useState("");

const [startDate, setStartDate] = useState<Date | null>(null);
const [isOpen, handleOpenBottomSheet, handleCloseBottomSheet] = useToggle();
const [isShowCalendar, handleOpenCalendar, handleCloseCalendar] = useToggle();

const {
travelPlanDays,
state: { title, startDate, travelPlanDays },
handler: {
onChangeTravelPlanDays,
onChangeTitle,
onSelectStartDate,
onInitializeStartDate,
onAddDay,
onAddPlace,
onDeleteDay,
onDeletePlace,
onAddPlaceTodo,
onDeletePlaceTodo,
onChangeContent,
},
} = useTravelPlanFormState([]);

const { status, error, isLoading } = useTravelPlanInitialization({
onChangeTitle,
onChangeTravelPlanDays,
onAddDay,
onAddPlace,
onDeleteDay,
onDeletePlace,
onAddPlaceTodo,
onDeletePlaceTodo,
onChangeContent,
} = useTravelPlanDays([]);

const handleChangeTitle = (e: React.ChangeEvent<HTMLInputElement>) => {
const title = e.target.value.slice(
FORM_VALIDATIONS_MAP.title.minLength,
FORM_VALIDATIONS_MAP.title.maxLength,
);
setTitle(title);
};

const [isOpen, setIsOpen] = useState(false);

const handleOpenBottomSheet = () => {
setIsOpen(true);
};

const handleCloseBottomSheet = () => {
setIsOpen(false);
};
onInitializeStartDate,
});

const {
mutate: mutateTravelPlanEdit,
isPaused,
isPending: isPuttingTravelPlanPending,
} = usePutTravelPlan();

const handleEditTravelPlan = () => {
const formattedStartDate = extractUTCDate(startDate);

mutateTravelPlanEdit(
{
travelPlan: { title, startDate: formattedStartDate, days: travelPlanDays },
id: Number(id),
},
{
onSuccess: () => {
handleCloseBottomSheet();
navigate(ROUTE_PATHS_MAP.travelPlan(id));
},
},
);
};

const debouncedEditTravelPlan = useLeadingDebounce(() => handleEditTravelPlan(), DEBOUNCED_TIME);

const handleConfirmBottomSheet = () => {
if (isPaused) alert(ERROR_MESSAGE_MAP.network);
debouncedEditTravelPlan();
};

const [isShowCalendar, setIsShowCalendar] = useState(false);

const handleInputClick = () => {
setIsShowCalendar(true);
};

const handleSelectDate = (date: Date) => {
setStartDate(date);
setIsShowCalendar(false);
};

useEffect(() => {
if (data) {
setTitle(data.title);
setStartDate(new Date(data.startDate));
onChangeTravelPlanDays(data.days);
}
}, [data, onChangeTravelPlanDays]);
const { onEditTravelPlan, isPuttingTravelPlanPending } = useTravelPlanEdit(
{ title, startDate: extractUTCDate(startDate), days: travelPlanDays },
handleCloseBottomSheet,
);

const navigate = useNavigate();

if (status === "error") {
const errorMessage =
error.message === ERROR_MESSAGE_MAP.api.travelPlanOnlyWriter
error?.message === ERROR_MESSAGE_MAP.api.travelPlanOnlyWriter
? ERROR_MESSAGE_MAP.api.travelPlanEditOnlyWriter
: error.message;
: error?.message;

alert(errorMessage);
navigate(ROUTE_PATHS_MAP.back);
}

if (isLoading) <>Loadinggㅋㅋㅋㅋ</>;
if (isLoading) <>Loading...</>;

return (
<>
Expand All @@ -145,7 +86,7 @@ const TravelPlanEditPage = () => {
value={title}
maxLength={FORM_VALIDATIONS_MAP.title.maxLength}
placeholder="여행 계획 제목을 입력해주세요"
onChange={handleChangeTitle}
onChange={(event) => onChangeTitle(event.target.value)}
/>
<CharacterCount
count={title.length}
Expand All @@ -165,14 +106,14 @@ const TravelPlanEditPage = () => {
<Input
id={id}
value={startDate ? startDate.toLocaleDateString().slice(0, -1) : ""}
onClick={handleInputClick}
onClick={handleOpenCalendar}
readOnly
placeholder="시작일을 입력해주세요"
/>
{isShowCalendar && (
<Calendar
onSelectDate={handleSelectDate}
onClose={() => setIsShowCalendar((prev) => !prev)}
onSelectDate={(date) => onSelectStartDate(date, handleCloseCalendar)}
onClose={handleCloseCalendar}
/>
)}
</>
Expand Down Expand Up @@ -231,7 +172,7 @@ const TravelPlanEditPage = () => {
mainText="여행 계획을 수정할까요?"
subText="수정한 후에도 다시 여행 계획을 변경할 수 있어요."
onClose={handleCloseBottomSheet}
onConfirm={handleConfirmBottomSheet}
onConfirm={onEditTravelPlan}
/>
</>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { useNavigate, useParams } from "react-router-dom";

import { TravelPlanPayload } from "@type/domain/travelPlan";

import { usePutTravelPlan } from "@queries/usePutTravelPlan";

import useLeadingDebounce from "@hooks/useLeadingDebounce";

import { DEBOUNCED_TIME } from "@constants/debouncedTime";
import { ERROR_MESSAGE_MAP } from "@constants/errorMessage";
import { ROUTE_PATHS_MAP } from "@constants/route";

const useTravelPlanEdit = (payload: TravelPlanPayload, handleCloseBottomSheet: () => void) => {
const {
mutate: mutateTravelPlanEdit,
isPaused,
isPending: isPuttingTravelPlanPending,
} = usePutTravelPlan();

const navigate = useNavigate();

const { id = "" } = useParams();

const handleEditTravelPlan = () => {
mutateTravelPlanEdit(
{
travelPlan: payload,
id: Number(id),
},
{
onSuccess: () => {
handleCloseBottomSheet();
navigate(ROUTE_PATHS_MAP.travelPlan(id));
},
},
);
};

const onEditTravelPlan = useLeadingDebounce(() => {
if (isPaused) alert(ERROR_MESSAGE_MAP.network);
handleEditTravelPlan();
}, DEBOUNCED_TIME);

return { onEditTravelPlan, isPuttingTravelPlanPending };
};

export default useTravelPlanEdit;
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { useEffect } from "react";
import { useParams } from "react-router-dom";

import { useGetTravelPlan } from "@queries/useGetTravelPlan";

import useTravelPlanFormState from "@hooks/pages/useTravelPlanFormState/useTravelPlanFormState";

type UseTravelPlanInitialization = Pick<
ReturnType<typeof useTravelPlanFormState>["handler"],
"onChangeTitle" | "onInitializeStartDate" | "onChangeTravelPlanDays"
>;

export const useTravelPlanInitialization = ({
onChangeTitle,
onInitializeStartDate,
onChangeTravelPlanDays,
}: UseTravelPlanInitialization) => {
const { id = "" } = useParams();

const { data, status, error, isLoading } = useGetTravelPlan(id);
useEffect(() => {
if (data) {
onChangeTitle(data.title);
onInitializeStartDate(data.startDate);
onChangeTravelPlanDays(data.days);
}
}, [data, onChangeTitle, onChangeTravelPlanDays, onInitializeStartDate]);

return { status, error, isLoading };
};
Loading
Loading