Skip to content

Commit

Permalink
Feat: 리뷰 보기 / 리뷰 작성 페이지 퍼블리싱 (#50)
Browse files Browse the repository at this point in the history
* feat: 리뷰 탭 상단 부분 퍼블리싱

* feat: 리뷰 목록 퍼블리싱

* feat: 리뷰 바텀시트 퍼블리싱

* feat: 기본 뷰 퍼블리싱

* feat: 편의시설 선택하기 바텀시트

* feat: 편의시설 선택

* feat: textarea state 추가

* feat: img list state 추가

* feat: 바뀐 디자인 적용

* feat: 편의시설 css margin 조절

* feat: textarea focus border

* chore: 임시 커밋

* feat: 리뷰 없을 때 화면 퍼블리싱

* feat: 리뷰 없을 때 화면 조건분기

* feat: 평균 rate

* feat: 리뷰 개수

* feat: 필터 버튼 재구현

* feat: 리뷰 가이드 구현

* feat: 리뷰 말줄임표 더보기 구현

* feat: img length 9 개까지만 첨부 버튼 띄우기

* feat: 리뷰 등록 토스트

* fix: rem 값 수정

* feat: 리뷰 필터링 구현

* feat: post 객체 미리 만들기

* feat: default 카테고리 로직 구현

* feat: 리뷰 필터 가이드 body overflow hidden 핸들링

* fix: 리뷰 작성 페이지 초기값 선택 없도록 수정

* fix: 토스트 초기값 수정
  • Loading branch information
seobbang authored Sep 24, 2024
1 parent 1806fbe commit 47db888
Show file tree
Hide file tree
Showing 35 changed files with 1,416 additions and 20 deletions.
2 changes: 2 additions & 0 deletions src/Router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { createBrowserRouter, RouterProvider } from 'react-router-dom';

import Settings from './components/Settings';
import DetailPage from './views/Detail/pages/DetailPage';
import WriteReviewPage from './views/Detail/pages/WriteReviewPage';
import ErrorReportPage from './views/ErrorReport/pages/ErrorReportPage';
import LoginCallBack from './views/Login/components/LoginCallBack';
import SignUpPage from './views/Login/pages/SignUpPage';
Expand All @@ -22,6 +23,7 @@ const router = createBrowserRouter([
],
},
{ path: '/detail', element: <DetailPage /> },
{ path: '/detail/review/write', element: <WriteReviewPage /> },
{
path: '/search',
element: <SearchPage />,
Expand Down
3 changes: 3 additions & 0 deletions src/assets/icon/icon-big-star-fill.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions src/assets/icon/icon-big-star.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions src/assets/icon/icon-camera.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions src/assets/icon/icon-pencil-mono.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 8 additions & 1 deletion src/assets/icon/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,9 +102,13 @@ export { default as VideoGuideInActiveIcon } from './icon_videoguide_inactive.sv
export { default as WheelChairAcitveIcon } from './icon_wheelchair_active.svg?react';
export { default as WheelChairInAcitveIcon } from './icon_wheelchair_inactive.svg?react';

export { default as EmptyPhotoIcon } from './icon_empty_photo.svg?react';
export { default as PencilMonoIcon } from './icon-pencil-mono.svg?react';
export { default as SmallStarIcon } from './small_star.svg?react';
export { default as BigStarIcon } from './icon-big-star.svg?react';
export { default as BigStarFillIcon } from './icon-big-star-fill.svg?react';
export { default as ArrowBackIconIosDownIcon } from './icon-arrow-back-ios-down.svg?react';
export { default as ArrowBackIconIosUpIcon } from './icon-arrow-back-ios-up.svg?react';
export { default as EmptyPhotoIcon } from './icon_empty_photo.svg?react';

//Universal Filter
export { default as AudioGuideDefaultIcon } from './audioguide_default.svg?react';
Expand All @@ -129,6 +133,9 @@ export { default as GuideSystemDefaultIcon } from './guidesystem_default.svg?rea
export { default as GuideSystemSelectedIcon } from './guidesystem_selected.svg?react';
export { default as HelpDogDefaultIcon } from './helpdog_default.svg?react';
export { default as HelpDogSelectedIcon } from './helpdog_selected.svg?react';
export { default as CameraIcon } from './icon-camera.svg?react';
export { default as ToggleXFillIcon } from './toggle-fill-x.svg?react';
export { default as NoReviewIcon } from './no_reveiw_image.svg?react';
export { default as LactationRoomDefaultIcon } from './lactationroom_default.svg?react';
export { default as LactationRoomSelectedIcon } from './lactationroom_selected.svg?react';
export { default as ParkingDefaultIcon } from './parking_default.svg?react';
Expand Down
18 changes: 18 additions & 0 deletions src/assets/icon/no_reveiw_image.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 10 additions & 0 deletions src/assets/icon/small_star.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions src/assets/icon/toggle-fill-x.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
20 changes: 16 additions & 4 deletions src/components/ToastMessage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,11 @@ const ToastMessage = (props: ToastMessageProps) => {
}, [setToast]);

return (
<div css={toastMessageContainer}>
<CheckFilledYellowIcon />
<span>{children}</span>
<div css={rootContainer}>
<div css={toastMessageContainer}>
<CheckFilledYellowIcon />
<span>{children}</span>
</div>
</div>
);
};
Expand All @@ -36,14 +38,24 @@ const fadeout = keyframes`
100% {opacity:0};
`;

const rootContainer = css`
position: fixed;
left: 0;
bottom: 7.5rem;
width: 100%;
padding: 0 2rem;
`;

const toastMessageContainer = () => css`
display: flex;
gap: 1.2rem;
align-items: center;
width: 100%;
padding: 1.7rem 0 1.7rem 2.4rem;
border-radius: 1.6rem;
border-radius: 1rem;
background-color: ${COLORS.brand1};
Expand Down
7 changes: 7 additions & 0 deletions src/types/api/review.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export interface ReviewResponse {
writer: string;
rate: number;
description: string;
convenience: string[];
imgUrl: string[];
}
16 changes: 6 additions & 10 deletions src/utils/storageHideGuide.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,22 @@
import { STORAGE_KEY } from '@/views/Search/constants/localStorageKey';

const key = STORAGE_KEY.hideSearchGuide;

const getStorageHideGuide = () => {
const getStorageHideGuide = (key: string) => {
const hideGuideTime = localStorage.getItem(key);
return hideGuideTime ? Number(hideGuideTime) : null;
};

// 24시간을 ms로
const EXPIRATION_PERIOD = 24 * 60 * 60 * 1000;
export const setStorageHideGuide = () => {
export const setStorageHideGuide = (key: string) => {
const date = new Date();
const expirationTime = date.getTime() + EXPIRATION_PERIOD;
localStorage.setItem(key, String(expirationTime));
};

const removeStorageHideGuide = () => {
const removeStorageHideGuide = (key: string) => {
localStorage.removeItem(key);
};

export const isGuideShown = () => {
const hideGuideTime = getStorageHideGuide();
export const isGuideShown = (key: string) => {
const hideGuideTime = getStorageHideGuide(key);
const nowDate = new Date();

if (hideGuideTime) {
Expand All @@ -30,7 +26,7 @@ export const isGuideShown = () => {
}
// 만료 O
else {
removeStorageHideGuide();
removeStorageHideGuide(key);
return true;
}
}
Expand Down
123 changes: 123 additions & 0 deletions src/views/Detail/components/Review.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import { css } from '@emotion/react';
import { useEffect, useState } from 'react';

import { ReviewResponse } from '@/types/api/review';
import { isGuideShown } from '@/utils/storageHideGuide';
import {
createInitialFilterState,
getFilterList,
} from '@/views/Search/constants/category';
import { category } from '@/views/Search/types/category';

import { STORAGE_KEY } from '../constants/localStorageKey';
import CategoryBottomSheet from './review/CategoryBottomSheet';
import Guide from './review/Guide';
import NoReview from './review/NoReview';
import ReviewCard from './review/ReviewCard';
import SelectedCategory from './review/SelectedCategory';
import TotalReview from './review/TotalReview';
import TotalScore from './review/TotalScore';

const REVIEW_DATA: ReviewResponse[] = [
{
writer: '왕이샹',
rate: 5,
description:
'앱에서 보았던 것과 같이 작품마다 점자표지판으로 설명이 있어 시각장애인도 불편하지 않게 관람이 가능했어요. 오디오 가이드 대여 서비스도 제공하니 필요하신 분들은 꼭 대여해서 쓰세요!!앱에서 보았던 것과 같이 작품마다 점자표지판으로 설명이 있어 시각장애인도 불편하지 않게 관람이 가능했어요. 오디오 가이드 대여 서비스도 제공하니 필요하신 분들은 꼭 대여해서 쓰세요!!',
convenience: ['주차장', '경사로'],
imgUrl: ['~~~', '~~~~'],
},
{
writer: '왕이샹',
rate: 3,
description:
'앱에서 보았던 것과 같이 작품마다 점자표지판으로 설명이 있어 시각장애인도 불편하지 않게 관람이 가능했어요. 오디오 가이드 대여 서비스도 제공하니 필요하신 분들은 꼭 대여해서 쓰세요!!',
convenience: ['주차장', '경사로'],
imgUrl: ['~~~', '~~~~'],
},
{
writer: '왕이샹',
rate: 2,
description:
'앱에서 보았던 것과 같이 작품마다 점자표지판으로 설명이 있어 시각장애인도 불편하지 않게 관람이 가능했어요. 오디오 가이드 대여 서비스도 제공하니 필요하신 분들은 꼭 대여해서 쓰세요!!',
convenience: ['주차장', '경사로'],
imgUrl: ['~~~', '~~~~'],
},
];

const Review = () => {
const [isBottomSheetOpen, setIsBottomSheetOpen] = useState(false);
const [showGuide, setShowGuide] = useState(() =>
isGuideShown(STORAGE_KEY.hideReviewFilterGuide),
);

const [filterState, setFilterState] = useState(() =>
createInitialFilterState('physical'),
);

const openBottomSheet = () => {
setIsBottomSheetOpen(true);
};
const closeBottomSheet = () => {
setIsBottomSheetOpen(false);
};

const handleSetShowGuide = (value: boolean) => {
setShowGuide(value);
document.body.style.overflow = '';
};

const handleFilterState = (category: category, facility: string) => {
const categoryFacilities = filterState[category];

setFilterState((prev) => ({
...prev,
[category]: {
...categoryFacilities,
[facility]: !categoryFacilities[facility],
},
}));
};

const selectedFilterList = getFilterList(filterState);

if (REVIEW_DATA.length === 0) return <NoReview />;

useEffect(() => {
if (showGuide) document.body.style.overflow = 'hidden';
}, []);

return (
<>
<TotalScore reviewData={REVIEW_DATA} />
<TotalReview reviewCount={REVIEW_DATA.length} />
<SelectedCategory
filterState={filterState}
handleFilterState={handleFilterState}
openBottomSheet={openBottomSheet}
/>
<ul css={reviewCardContainerCss}>
{REVIEW_DATA.filter(({ convenience }) =>
convenience.some((c) => selectedFilterList.includes(c)),
).map((item, idx) => {
return <ReviewCard key={idx + item.writer + item.rate} {...item} />;
})}
</ul>

{showGuide && <Guide handleSetShowGuide={handleSetShowGuide} />}
{isBottomSheetOpen && (
<CategoryBottomSheet
closeBottomSheet={closeBottomSheet}
filterState={filterState}
handleFilterState={handleFilterState}
/>
)}
</>
);
};

export default Review;

const reviewCardContainerCss = css`
padding: 2.3rem 2rem 0;
`;
Loading

0 comments on commit 47db888

Please sign in to comment.