Skip to content

Commit

Permalink
Feat: 리뷰 api 연결 (#62)
Browse files Browse the repository at this point in the history
* fix: 리뷰 작성하기 페이지 접속시 최상단으로

* fix: lint

* fix: margin 수정

* feat: 이미지 업로드 로직

* feat: api params 수정

* feat: 리뷰 get api 연결

* fix: 이미지 post 수정 시도

* fix: 이미지 저장 api 수정

* chore:불필요한 코드 삭제

* fix:빌드 오류 해결

* feat: 리뷰 날짜 받아오기

* feat:날짜 저장

* feat: 리뷰 탭 필터 로직 수정

* feat: 리뷰 탭 회색 별 추가

* feat: 필터 바깥 누르면 저장 X 되도록 로직 수정

* feat: key 변경

* feat: 리뷰 탭 라우터 분리

* feat: 글자 가운데 * 처리

* feat: 리뷰 탭 디폴트 카테고리 유저 정보 받아오기 구현'

* fix: 서버 오류 수정

* fix: 파일명 필터링 기능 추가

* fix:유니코드 수정

* fix: key warning 해결

* feat: 이름 글자수 해결

* feat: 리뷰 등록 후 리뷰 get 오류 수정

* feat: 이미지 비율 조정

* feat: 바텀시트, 가이드 중앙정렬

* refactor: map으로 돌리기

* feat: textarea 리사이징 막기

* fix: 디폴트 카테고리 로직

---------

Co-authored-by: aazkgh <[email protected]>
  • Loading branch information
seobbang and aazkgh authored Sep 30, 2024
1 parent f5fe3e5 commit 47a5d61
Show file tree
Hide file tree
Showing 26 changed files with 344 additions and 209 deletions.
9 changes: 7 additions & 2 deletions src/Router.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { createBrowserRouter, RouterProvider } from 'react-router-dom';

import Settings from './components/Settings';
import Review from './views/Detail/components/Review';
import DetailPage from './views/Detail/pages/DetailPage';
import WriteReviewPage from './views/Detail/pages/WriteReviewPage';
import ErrorReportPage from './views/ErrorReport/pages/ErrorReportPage';
Expand All @@ -22,8 +23,12 @@ const router = createBrowserRouter([
{ path: '/sign-up', element: <SignUpPage /> },
],
},
{ path: '/detail', element: <DetailPage /> },
{ path: '/detail/review/write', element: <WriteReviewPage /> },
{
path: '/:contentId',
element: <DetailPage />,
children: [{ path: 'review', element: <Review /> }],
},
{ path: '/:contentId/review/write', element: <WriteReviewPage /> },
{
path: '/search',
element: <SearchPage />,
Expand Down
10 changes: 2 additions & 8 deletions src/apis/supabase/getReviews.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,10 @@
import { useLocation } from 'react-router-dom';

import { unitripSupabase } from '@/utils/supabaseClient';

const getReviews = async () => {
const location = useLocation();
const queryParams = new URLSearchParams(location.search);
const place = queryParams.get('contentId');

const getReviews = async (contentId: string) => {
const { data, error } = await unitripSupabase
.from('REVIEW')
.select('*, USER(name)')
.eq('place', place);
.eq('place', contentId);

if (error) {
throw new Error('서버에 문제가 있습니다');
Expand Down
1 change: 1 addition & 0 deletions src/apis/supabase/postAddUser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const postAddUser = async ({
profile: thumbnail_image_url,
universal_type: travelerType,
region: `${region.city} ${region.town}`,
favorite_list: [],
},
])
.select();
Expand Down
24 changes: 9 additions & 15 deletions src/apis/supabase/postImgReview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,31 +5,25 @@ const postImgReview = async (imgs: File[]) => {

// 각 파일을 Supabase Storage에 업로드
for (const img of imgs) {
const cleanedFileName = decodeURIComponent(img.name).replace(
/[<>:"/\\|?* ]|[\u0400-\u04FF\uAC00-\uD7AF\u3131-\u314E\u1100-\u1112\u1161-\u1175\u11A8-\u11C2]+/g,
'_',
);

const { error: uploadError } = await unitripSupabase.storage
.from('REVIEW_IMAGES')
.upload(`images/${img.name}`, img);
.upload(`${cleanedFileName}`, img);

if (uploadError) {
throw new Error('이미지 업로드 과정에서 에러가 발생했습니다');
}

// 업로드한 파일의 URL 생성
const { data } = unitripSupabase.storage
.from('REVIEW_IMAGES')
.getPublicUrl(`images/${img.name}`);

if (data) {
const { publicUrl } = data;
imgUrls.push(publicUrl);
}
const publicUrl = `${import.meta.env.VITE_STORAGE_URL}/${cleanedFileName}`;
imgUrls.push(publicUrl);
}

// 파일 URL을 데이터베이스에 저장
const { error: dbError } = await unitripSupabase.from('USER').insert(imgUrls);

if (dbError) {
throw new Error('서버에 문제가 있습니다');
}
return imgUrls;
};

export default postImgReview;
22 changes: 12 additions & 10 deletions src/apis/supabase/postReview.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { useLocation } from 'react-router-dom';

import { unitripSupabase } from '@/utils/supabaseClient';

import postImgReview from './postImgReview';
Expand All @@ -9,38 +7,42 @@ interface postReviewProps {
description: string;
convenience: string[];
imgs: File[];
contentId: number | undefined;
}

const postReview = async ({
rate,
description,
convenience,
imgs,
contentId,
}: postReviewProps) => {
const location = useLocation();
const queryParams = new URLSearchParams(location.search);
const place = queryParams.get('contentId');

const writer = sessionStorage.getItem('kakao_id');

const { error } = await unitripSupabase
const imgUrls = await postImgReview(imgs);

const date = new Date().toLocaleDateString().replace(/\s/g, '');

const { error, status } = await unitripSupabase
.from('REVIEW')
.insert([
{
place,
place: contentId,
writer,
rate,
description,
convenience,
imgUrls,
date,
},
])
.select();

await postImgReview(imgs);

if (error) {
throw new Error('서버에 문제가 있습니다');
}

return status;
};

export default postReview;
10 changes: 10 additions & 0 deletions src/assets/icon/icon-star-gray.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions src/assets/icon/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,3 +158,4 @@ export { default as VideoGuideDefaultIcon } from './videoguide_default.svg?react
export { default as VideoGuideSelectedIcon } from './videoguide_selected.svg?react';
export { default as WheelChairDefaultIcon } from './wheelchair_default.svg?react';
export { default as WheelChairSelectedIcon } from './wheelchair_selected.svg?react';
export { default as StarGrayIcon } from './icon-star-gray.svg?react';
9 changes: 6 additions & 3 deletions src/components/BottomSheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,10 @@ const BottomSheet = (props: BottomSheetProps) => {
</div>
);

return createPortal(portalContent, document.body);
return createPortal(
portalContent,
document.getElementById('root') as HTMLElement,
);
};

export default BottomSheet;
Expand All @@ -87,11 +90,11 @@ const backgroundCss = css`
align-items: center;
position: fixed;
top: 0;
left: 0;
z-index: 999;
width: 100vw;
height: 100vh;
margin: 0 auto;
background-color: rgb(0 0 0 / 30%);
`;
Expand All @@ -109,7 +112,7 @@ const containerCss = (height: string) => css`
`;

const buttonCotainerCss = css`
position: fixed;
position: absolute;
bottom: 1.2rem;
left: 0;
Expand Down
1 change: 0 additions & 1 deletion src/components/PlaceCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,6 @@ const backgroundCss = css`
background: linear-gradient(
180deg,
rgb(0 0 0 / 0%) 0%,
rgb(0 0 0 / 34%) 100% rgb(0 0 0 / 0%) 0%,
rgb(0 0 0 / 34%) 100%
);
Expand Down
10 changes: 8 additions & 2 deletions src/types/api/review.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
export interface ReviewResponse {
writer: string;
id: number;
place: number;
writer: number;
rate: number;
description: string;
convenience: string[];
imgUrl: string[];
imgUrls: string[];
date: string;
USER: {
name: string;
};
}
9 changes: 0 additions & 9 deletions src/types/supabaseAPI.ts

This file was deleted.

92 changes: 50 additions & 42 deletions src/views/Detail/components/Review.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
import { css } from '@emotion/react';
import { useEffect, useState } from 'react';
import { useLocation, useParams } from 'react-router-dom';

import getReviews from '@/apis/supabase/getReviews';
import getUserData from '@/apis/supabase/getUserData';
import ToastMessage from '@/components/ToastMessage';
import { useAsyncEffect } from '@/hooks/use-async-effect';
import { ReviewResponse } from '@/types/api/review';
import { UserDataResponse } from '@/types/userAPI';
import { isGuideShown } from '@/utils/storageHideGuide';
import {
createInitialFilterState,
getFilterList,
INITIAL_FILTER_STATE,
} from '@/views/Search/constants/category';
import { category } from '@/views/Search/types/category';
import { category, filterState } from '@/views/Search/types/category';

import { STORAGE_KEY } from '../constants/localStorageKey';
import CategoryBottomSheet from './review/CategoryBottomSheet';
Expand All @@ -18,42 +24,19 @@ 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 { contentId } = useParams();
const location = useLocation();

const [userData, setUserData] = useState<UserDataResponse | null>(null);
const [reviewData, setReviewData] = useState<ReviewResponse[]>();
const [isBottomSheetOpen, setIsBottomSheetOpen] = useState(false);
const [showGuide, setShowGuide] = useState(() =>
isGuideShown(STORAGE_KEY.hideReviewFilterGuide),
);
const [toast, setToast] = useState(false);

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

const openBottomSheet = () => {
setIsBottomSheetOpen(true);
Expand All @@ -79,39 +62,64 @@ const Review = () => {
}));
};

const selectedFilterList = getFilterList(filterState);
const handleFilterStateObject = (value: filterState) => {
setFilterState(value);
};

if (REVIEW_DATA.length === 0) return <NoReview />;
const selectedFilterList = getFilterList(filterState);

useEffect(() => {
if (showGuide) document.body.style.overflow = 'hidden';
if (sessionStorage.getItem(STORAGE_KEY.successToast)) {
setToast(true);
sessionStorage.removeItem(STORAGE_KEY.successToast);
}
}, []);

useAsyncEffect(async () => {
const data = await getReviews(contentId as string);
setReviewData(data);

const kakaoId = sessionStorage.getItem('kakao_id');
if (!kakaoId) return;
const userData = await getUserData(Number(kakaoId));
setUserData(userData);
}, [location.pathname]);

if (!reviewData || reviewData.length === 0) return <NoReview />;

return (
<>
<TotalScore reviewData={REVIEW_DATA} />
<TotalReview reviewCount={REVIEW_DATA.length} />
<TotalScore reviewData={reviewData} />
<TotalReview reviewCount={reviewData.length} />
<SelectedCategory
filterState={filterState}
handleFilterState={handleFilterState}
openBottomSheet={openBottomSheet}
defaultCategory={userData?.universal_type}
/>
<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} />;
})}
{reviewData
?.filter(({ convenience }) =>
selectedFilterList.every((c) => convenience.includes(c)),
)
.map((item) => {
return <ReviewCard key={item.id} {...item} />;
})}
</ul>

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

{toast && (
<ToastMessage setToast={setToast}>리뷰가 저장되었습니다.</ToastMessage>
)}
</>
);
};
Expand Down
Loading

0 comments on commit 47a5d61

Please sign in to comment.