Skip to content

Commit

Permalink
Merge pull request #154 from SCBJ-7/feature/#75-refactor-room-detail-…
Browse files Browse the repository at this point in the history
…page

[#75] 상품 상세 페이지 리팩터링
  • Loading branch information
im-na0 authored Jan 24, 2024
2 parents 481860d + a41b4d4 commit c10bdb7
Show file tree
Hide file tree
Showing 21 changed files with 151 additions and 84 deletions.
8 changes: 6 additions & 2 deletions src/apis/fetchRoom.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { axiosInstance } from "@apis/axiosInstance";
import { END_POINTS } from "@constants/api";
import type { ResponseData } from "@type/responseType";
import type { RoomData } from "@type/room";

export const getRoom = async (roomId: string) => {
const { data } = await axiosInstance.get(END_POINTS.ROOM(roomId));
export const getRoom = async (roomId: string): Promise<RoomData> => {
const { data } = await axiosInstance.get<ResponseData<RoomData>>(
END_POINTS.ROOM(roomId),
);
return data.data;
};
2 changes: 0 additions & 2 deletions src/components/carousel/Carousel.style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,9 @@ import styled, { css } from "styled-components";

export const CarouselContainer = styled.div<{
$height: number;
$width: number;
}>`
position: relative;
width: ${(props) => `${props.$width}px`};
min-height: ${(props) => `${props.$height}px`};
height: ${(props) => `${props.$height}px`};
Expand Down
4 changes: 1 addition & 3 deletions src/components/carousel/Carousel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import * as S from "./Carousel.style.ts";

interface CarouselProps {
images: string[];
width?: number;
height?: number;
arrows?: boolean;
infinite?: boolean;
Expand All @@ -14,7 +13,6 @@ interface CarouselProps {

const Carousel = ({
height = 300,
width = 300,
images,
arrows = true,
infinite = false,
Expand All @@ -41,7 +39,7 @@ const Carousel = ({
});

return (
<S.CarouselContainer $height={height} $width={width}>
<S.CarouselContainer $height={height}>
<S.SliderWrapper>
<S.SliderContainer
ref={sliderRef}
Expand Down
10 changes: 5 additions & 5 deletions src/components/checkbox/Checkbox.style.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import styled, { DefaultTheme } from "styled-components";
import styled, { DefaultTheme, css } from "styled-components";

export interface CheckboxStyleProps {
size?: "sm" | "md" | "lg";
Expand Down Expand Up @@ -117,11 +117,11 @@ export const StyledCheckbox = styled.span<CheckboxStyleProps>`
`;

const labelStyles = {
title: (theme: DefaultTheme) => `
title: (theme: DefaultTheme) => css`
color: ${theme.color.greyScale1};
}
`,
caption: (theme: DefaultTheme) => `
caption: (theme: DefaultTheme) => css`
color: ${theme.color.greyScale3};
}
`,
Expand All @@ -140,8 +140,6 @@ export const LabelText = styled.span.withConfig({
${({ theme }) => theme.typo.caption1}
${({ variant, theme }) => variant && labelStyles[variant](theme)};
margin-inline-start: 0.5rem;
user-select: none;
Expand All @@ -150,4 +148,6 @@ export const LabelText = styled.span.withConfig({
text-underline-offset: 2px;
color: inherit;
}
${({ variant, theme }) => variant && labelStyles[variant](theme)};
`;
2 changes: 1 addition & 1 deletion src/components/toast/Toast.style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export const ToastContainer = styled(motion.div)<{ $isError: boolean }>`
justify-content: center;
align-items: center;
position: absolute;
position: fixed;
left: 0;
right: 0;
top: 80px;
Expand Down
5 changes: 4 additions & 1 deletion src/hooks/api/mutation/useValidateEmailMutation.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { postValidateEmail } from "@apis/fetchLogin";
import { useMutation } from "@tanstack/react-query";
import { isAxiosError } from "axios";

export const useValidateEmailMutation = () => {
const validateEmailMutation = useMutation({
mutationFn: ({ email }: { email: string }) => postValidateEmail(email),
throwOnError: true,
throwOnError: (error) => {
return !(isAxiosError(error) && error.response);
},
});

return validateEmailMutation;
Expand Down
28 changes: 28 additions & 0 deletions src/hooks/api/useRoomQuery.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { useSuspenseQuery } from "@tanstack/react-query";
import { getRoom } from "@apis/fetchRoom";
import { calculateDiscount } from "@utils/calculator";

import type { RoomData } from "@type/room";
import type { AxiosError } from "axios";

interface RoomQueryData {
rawData: RoomData;
discountRate: string;
}

export const useRoomQuery = (roomId: string) => {
return useSuspenseQuery<RoomData, AxiosError, RoomQueryData>({
queryKey: ["room", roomId],
queryFn: () => getRoom(roomId),
select: (data) => {
const discountRate = calculateDiscount(
data.originalPrice,
data.sellingPrice,
);
return {
rawData: data,
discountRate,
};
},
});
};
7 changes: 4 additions & 3 deletions src/hooks/common/useIsVisible.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,21 @@ const useIsVisible = (props: UseIsVisibleProps): UseIsVisibleReturnType => {

useEffect(() => {
const observer = new IntersectionObserver(([entry]) => {
setIsVisible(entry.isIntersecting);
if (entry.isIntersecting !== isVisible) {
setIsVisible(entry.isIntersecting);
}
}, options);

if (visibleRef) {
observer.observe(visibleRef);
return;
}

return () => {
if (visibleRef) {
observer.unobserve(visibleRef);
}
};
}, [options, visibleRef]);
}, [options, visibleRef, isVisible]);

const setRefCallback: RefCallback<HTMLDivElement> = (node) => {
setVisibleRef(node);
Expand Down
21 changes: 8 additions & 13 deletions src/main.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import React, { Suspense } from "react";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
import ReactDOM from "react-dom/client";
import { RouterProvider } from "react-router-dom";
import { ThemeProvider } from "styled-components";
import { worker } from "./mocks/broswer.ts";
import { worker } from "./mocks/broswer";
import { router } from "./routes/router";
import { GlobalStyle } from "./styles/globalStyle";
import { theme } from "./styles/theme";
Expand All @@ -15,15 +14,11 @@ if (process.env.NODE_ENV === "development") {
}

ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode>
<QueryClientProvider client={queryClient}>
<ThemeProvider theme={theme}>
<GlobalStyle />
<Suspense fallback={<div>{/* Global Loading... */}</div>}>
<RouterProvider router={router} />
</Suspense>
</ThemeProvider>
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
</React.StrictMode>,
<QueryClientProvider client={queryClient}>
<ThemeProvider theme={theme}>
<GlobalStyle />
<RouterProvider router={router} />
</ThemeProvider>
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>,
);
3 changes: 2 additions & 1 deletion src/mocks/data/dummyRoomDetail.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@
},
"hotelAddress": "서울특별시 강남구 테헤란로 99길 9",
"hotelInfoUrl": "https://place-site.yanolja.com/places/3001615",
"saleStatus": false
"saleStatus": false,
"isSeller": true
},
"message": "상품 조회에 성공했습니다."
}
10 changes: 5 additions & 5 deletions src/pages/paymentPage/Payment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,17 @@ import PaymentMethodSection from "@/pages/paymentPage/components/paymentMethodSe
import TermsAgreementSection from "@/pages/paymentPage/components/termsAgreementSection/TermsAgreementSection";
import UserInfoSection from "@/pages/paymentPage/components/userInfoSection/UserInfoSection";
import { usePaymentQuery } from "@hooks/api/query/usePaymentQuery";
import { useSearchParams } from "react-router-dom";
import { useParams } from "react-router-dom";
import PaymentButton from "./components/paymentButton/PaymentButton";

import { FormProvider, useForm } from "react-hook-form";
import Caption from "@components/caption/Caption";

const Payment = () => {
const [searchParams] = useSearchParams();
const product = searchParams.get("product") ?? "";
const { productId } = useParams();
if (!productId) throw Error("존재하지 않는 productId 입니다.");

const { data } = usePaymentQuery(product);
const { data } = usePaymentQuery(productId);

const methods = useForm({
mode: "onChange",
Expand Down Expand Up @@ -47,7 +47,7 @@ const Payment = () => {
</Caption>
</S.CaptionWrapper>
<S.BottomWrapper>
<PaymentButton productId={product} payment={data} />
<PaymentButton productId={productId} price={data.salePrice} />
</S.BottomWrapper>
</FormProvider>
</S.PaymentContainer>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,13 @@ import { useFormContext } from "react-hook-form";
import { usePaymentMutation } from "@hooks/api/mutation/usePaymentMutation";

import * as S from "./PaymentButton.style";
import type { PaymentData } from "@type/payment";

interface PaymentButtonProps {
productId: string;
payment: Pick<PaymentData, "salePrice">;
price: number;
}

const PaymentButton = ({ productId, payment }: PaymentButtonProps) => {
const PaymentButton = ({ productId, price }: PaymentButtonProps) => {
const {
handleSubmit,
getValues,
Expand Down Expand Up @@ -55,7 +54,7 @@ const PaymentButton = ({ productId, payment }: PaymentButtonProps) => {
data-disabled={isValid ? null : ""}
aria-label="결제하기"
>
{payment.salePrice.toLocaleString("ko-KR")}원 결제하기
{price.toLocaleString("ko-KR")}원 결제하기
</S.Button>
);
};
Expand Down
10 changes: 5 additions & 5 deletions src/pages/paymentSuccessPage/PaymentSuccess.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useSearchParams } from "react-router-dom";
import { useParams } from "react-router-dom";
import { usePurchaseDetailQuery } from "@hooks/api/query/usePurchaseQuery";

import PaymentSuccessInfo from "@pages/paymentSuccessPage/components/PaymentSuccessInfo/PaymentSuccessInfo";
Expand All @@ -9,10 +9,10 @@ import CardItem from "@components/cardItem/CardItem";
import * as S from "./PaymentSuccess.style";

const PaymentSuccess = () => {
const [searchParams] = useSearchParams();
const product = searchParams.get("product") ?? "";
const { productId } = useParams();
if (!productId) throw Error("존재하지 않는 productId 입니다.");

const { data } = usePurchaseDetailQuery(product);
const { data } = usePurchaseDetailQuery(productId);

return (
<S.PurchasedContainer>
Expand Down Expand Up @@ -63,7 +63,7 @@ const PaymentSuccess = () => {
</S.BottomCardWrapper>
</S.PurchasedWrapper>
<S.BottomWrapper>
<PaymentSuccessButton productId={product} />
<PaymentSuccessButton productId={productId} />
</S.BottomWrapper>
</S.PurchasedContainer>
);
Expand Down
32 changes: 19 additions & 13 deletions src/pages/roomDetailPage/RoomDetail.tsx
Original file line number Diff line number Diff line change
@@ -1,38 +1,44 @@
import { getRoom } from "@apis/fetchRoom";

import Carousel from "@components/carousel/Carousel";
import RoomHeader from "@pages/roomDetailPage/components/roomHeader/RoomHeader";
import RoomInfo from "@pages/roomDetailPage/components/roomInfo/RoomInfo";
import RoomNavBar from "@pages/roomDetailPage/components/roomNavBar/RoomNavBar";
import { useSuspenseQuery } from "@tanstack/react-query";
import type { RoomData } from "@type/room";
import type { AxiosError } from "axios";

import useToastConfig from "@hooks/common/useToastConfig";
import { useRoomQuery } from "@hooks/api/useRoomQuery";
import { useParams } from "react-router-dom";

import * as S from "./RoomDetail.style";
import { useEffect } from "react";

const RoomDetail = () => {
const { roomId } = useParams();
if (!roomId) throw new Error("존재하지 않는 roomId 입니다.");

const { data } = useSuspenseQuery<RoomData, AxiosError>({
queryKey: ["room"],
queryFn: () => getRoom(roomId),
});
const { data } = useRoomQuery(roomId);
const { rawData, discountRate } = data;

const { handleToast } = useToastConfig();

useEffect(() => {
if (rawData.isSeller) {
handleToast(false, ["내가 판매 중인 상품입니다"]);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [rawData.isSeller]);

return (
<S.Container>
<RoomHeader title={data.hotelName} />
<RoomHeader title={rawData.hotelName} />
<Carousel
images={data.hotelImageUrl}
images={rawData.hotelImageUrlList}
height={300}
arrows={true}
infinite={false}
innerShadow={true}
draggable={true}
/>
<RoomInfo room={data} />
<RoomNavBar room={data} />
<RoomInfo room={rawData} discount={discountRate} />
<RoomNavBar room={rawData} discount={discountRate} roomId={roomId} />
</S.Container>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,25 @@ export const ScrollObserver = styled.div`
`;

export const HeaderContainer = styled.header<{ $visible: boolean }>`
display: flex;
align-items: center;
position: fixed;
top: 0;
display: flex;
align-items: center;
width: 100%;
max-width: 768px;
height: 56px;
z-index: 2;
background-color: ${({ $visible, theme }) =>
$visible ? "unset" : theme.color.white};
$visible ? "transparent" : theme.color.white};
border-bottom: ${({ $visible, theme }) =>
$visible ? "none" : `1px solid ${theme.color.greyScale7}`};
$visible ? "0" : `1px solid ${theme.color.greyScale7}`};
transition:
border-bottom,
background-color 0.5s ease-in;
z-index: 2;
`;

export const Wrapper = styled.div`
Expand Down Expand Up @@ -61,6 +64,7 @@ export const TitleWrapper = styled.div`
overflow: hidden;
text-overflow: ellipsis;
text-align: center;
width: 100%;
`;

export const Title = styled.p<{ $visible: boolean }>`
Expand Down
Loading

0 comments on commit c10bdb7

Please sign in to comment.