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

[Post, Search] 리뷰 쓰기 기능 추가, 검색페이지 변경 대응 및 기능 연결 #91

Merged
merged 10 commits into from
Sep 4, 2023
2 changes: 1 addition & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export default function App(): JSX.Element {
<Route path="/friend-list" element={<FollowPage />} />
</Route>
<Route path="/withdrawal" element={<Withdrawal />} />
<Route path="/post" element={<Post />} />
<Route path="/post/:name" element={<Post />} />
</Route>
<Route element={<AuthRoute needAuth={false} redirectRoute="/" />}>
<Route path="/login" element={<Login />} />
Expand Down
1 change: 0 additions & 1 deletion src/api/index.ts

This file was deleted.

6 changes: 6 additions & 0 deletions src/api/review/entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export interface ReviewParams {
placeId : string;
content : string;
rate : number;
reviewImages : File[];
}
19 changes: 19 additions & 0 deletions src/api/review/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { ReviewParams } from './entity';
import reviewApi from './reviewApiClient';

export const postReview = (params: ReviewParams) => {
const formData = new FormData();
formData.append('content', params.content);
formData.append('placeId', params.placeId);
formData.append('rate', String(params.rate));
params.reviewImages.forEach((image) => {
formData.append('reviewImages', image);
});
return reviewApi.post('/', formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
});
};

export const deleteReview = (reviewId: string) => reviewApi.delete(`/${reviewId}`);
18 changes: 18 additions & 0 deletions src/api/review/reviewApiClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import axios from 'axios';
import { API_PATH } from 'config/constants';

const reviewApi = axios.create({
baseURL: `${API_PATH}/review`,
timeout: 2000,
});

reviewApi.interceptors.request.use(
(config) => {
const accessToken = sessionStorage.getItem('accessToken');
// eslint-disable-next-line no-param-reassign
if (config.headers && accessToken) config.headers.Authorization = `Bearer ${accessToken}`;
return config;
},
);

export default reviewApi;
23 changes: 13 additions & 10 deletions src/api/search/entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,21 @@ export interface ShopsParams {
}

export interface FetchShopsResponse {
content: Shop[];
shopQueryResponseList: Shop[];
}

interface Shop {
address: string,
dist: 0,
placeId: string,
placeName: string,
score: number,
shopId: number,
x: string,
y: string,
export interface Shop {
placeId: string;
name: string;
formattedAddress: string;
lat: number;
lng: number;
openNow: boolean
totalRating: number | null;
ratingCount: number | null;
photoToken: string;
dist: number;
category: string; // 추후 카테고리 확인 필요
}

export interface Coords {
Expand Down
5 changes: 4 additions & 1 deletion src/api/search/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,7 @@ export const fetchTrendings = () => searchApi.get<FetchTrendingsResponse>('/tren

export const fetchShop = (shopId: string) => searchApi.get(`/shop?place_id=${shopId}`);

export const fetchShops = (params: ShopsParams) => searchApi.post<FetchShopsResponse>(`/shops?keyword=${params.keyword}&x=${params.location?.latitude}&y=${params.location?.longitude}`);
export const fetchShops = (params: ShopsParams) => searchApi.post<FetchShopsResponse>(`/shops?keyword=${params.keyword}`, {
lat: params.location?.latitude,
lng: params.location?.longitude,
});
7 changes: 4 additions & 3 deletions src/components/StarRating/index.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { useMemo, useState } from 'react';
import { useMemo } from 'react';
import useBooleanState from 'utils/hooks/useBooleanState';
import { useRate } from 'store/review';
import StarRateContext from './StarRateContext';
import EnterStarRateContainer from './EnterStarRateContainer';
import LeaveStarRateContainer from './LeaveStarRateContainer';

export default function StarRating({ onClick }: { onClick: () => void }) {
const [rating, setRating] = useState(0);
const [rating, setRating] = useRate();
const [entered, enter, leave] = useBooleanState(false);

const value = useMemo(() => ({
Expand All @@ -15,7 +16,7 @@ export default function StarRating({ onClick }: { onClick: () => void }) {
setRating(num);
onClick?.();
},
}), [onClick, enter, leave]);
}), [onClick, enter, leave, setRating]);

return (
<StarRateContext.Provider value={value}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
border-radius: 50%;
background-color: transparent;
z-index: 2;
top: -5px;
}

&__image {
Expand Down Expand Up @@ -52,7 +53,7 @@
height: 340px;
}

&--withImage {
&--with-image {
line-height: 15px;
height: 600px;
width: 100%;
Expand Down
7 changes: 4 additions & 3 deletions src/components/editor/TextEditor/AddImage/ImageItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ import styles from './AddImage.module.scss';

interface Props {
value: string,
onDelete: (value: string) => void,
index: number,
onDelete: (index: number) => void,
}

export default function ImageItem({ value, onDelete }: Props) {
export default function ImageItem({ value, onDelete, index }: Props) {
const imageRef = useRef<HTMLInputElement>(null);

return (
Expand All @@ -16,7 +17,7 @@ export default function ImageItem({ value, onDelete }: Props) {
type="button"
aria-label="trash"
className={styles.container__button}
onClick={() => onDelete(value)}
onClick={() => onDelete(index)}
>
<Trash />
</button>
Expand Down
14 changes: 4 additions & 10 deletions src/components/editor/TextEditor/AddImage/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { ReactComponent as Picture } from 'assets/svg/post/picture.svg';
import { useEffect, useRef } from 'react';
import { useRef } from 'react';
import Wysiwyg, { WysiwygType } from 'components/editor/Wysiwyg';
import useBooleanState from 'utils/hooks/useBooleanState';
import cn from 'utils/ts/classNames';
import styles from './AddImage.module.scss';
import useImageList from '../hooks/useImageList';
Expand All @@ -10,25 +9,20 @@ import ImageItem from './ImageItem';
function AddImage() {
const { imageList, addImage, removeImage } = useImageList();
const wysiwygRef = useRef<WysiwygType | null>(null);
const [opened, active, inActive] = useBooleanState(false);
useEffect(() => {
if (imageList === null || imageList.length === 0) inActive();
else active();
}, [imageList, active, inActive]);

return (
<div>
<div className={styles.container}>
{ imageList?.map((value) => (
{imageList.map((value, index) => (
<div key={value} className={styles.container__item}>
<ImageItem value={value} onDelete={removeImage} />
<ImageItem value={value} onDelete={removeImage} index={index} />
</div>
))}
</div>
<div
className={cn({
[styles.editor]: true,
[styles['editor--withImage']]: opened,
[styles['editor--with-image']]: imageList.length > 0,
})}
>
<Wysiwyg ref={wysiwygRef} />
Expand Down
45 changes: 27 additions & 18 deletions src/components/editor/TextEditor/hooks/useImageList.ts
Original file line number Diff line number Diff line change
@@ -1,39 +1,48 @@
import { Dispatch, SetStateAction, useState } from 'react';
import { useState } from 'react';
import { useSetReview } from 'store/review';
import makeToast from 'utils/ts/makeToast';

interface ReturnType {
imageList: string[] | null;
setImageList: Dispatch<SetStateAction<string[] | null>>;
addImage: (event: React.ChangeEvent<HTMLInputElement>) => void;
removeImage: (value: string) => void;
}

export default function useImageList(): ReturnType {
const [imageList, setImageList] = useState<string[] | null>(null);
export default function useImageList() {
const [imageList, setImageList] = useState<string[]>([]);
const setReview = useSetReview();

const addImage = (event: React.ChangeEvent<HTMLInputElement>) => {
const { files } = event.target;
if (files) {
const fileArray: string[] = [];
const fileCount = files.length;

const dataTransfer = new DataTransfer();
for (let i = 0; i < fileCount; i += 1) {
if (files[i].size > 1048576) {
makeToast('error', '최대 1MB 이미지만 업로드 가능합니다.');
return;
}
const reader = new FileReader();
reader.onload = (e) => {
const result = e.target?.result;
if (typeof result === 'string') {
fileArray.push(result);
if (fileArray.length === fileCount) {
setImageList((prev) => (prev ? [...prev, ...fileArray] : fileArray));
}
setImageList((prev) => ([...prev, result]));
}
};
reader.readAsDataURL(files[i]);
dataTransfer.items.add(files[i]);
setReview((prev) => ({
...prev,
reviewImages: [...prev.reviewImages, files[i]],
}));
}
}
};

const removeImage = (value: string) => {
setImageList((prev) => prev?.filter((item) => item !== value) || null);
const removeImage = (index: number) => {
setImageList((prev) => {
const newList = [...prev];
newList.splice(index, 1);
return newList;
});
setReview((prev) => ({
...prev,
reviewImages: prev.reviewImages.filter((_, i) => i !== index),
}));
};

return {
Expand Down
40 changes: 9 additions & 31 deletions src/components/editor/TextEditor/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { ReactComponent as Plus } from 'assets/svg/post/plus.svg';
import cn from 'utils/ts/classNames';
import PreviousButton from 'components/PreviousButton/PreviousButton';
import StarRating from 'components/StarRating';
Expand All @@ -8,11 +7,11 @@ import AddImage from './AddImage';
import styles from './TextEditor.module.scss';

interface Props {
shop: string | null;
getShopname: () => string | null;
shop: string;
onSubmit: () => void;
}

export default function TextEditor({ shop, getShopname }: Props) {
export default function TextEditor({ shop, onSubmit }: Props) {
const [actived, active] = useBooleanState(false);

return (
Expand All @@ -30,6 +29,7 @@ export default function TextEditor({ shop, getShopname }: Props) {
</div>
<button
type="button"
onClick={onSubmit}
className={cn({
[styles['header__save-button']]: true,
[styles['haeader__save-button--active']]: actived,
Expand All @@ -40,37 +40,15 @@ export default function TextEditor({ shop, getShopname }: Props) {
</button>
</header>
<title className={styles.heading}>
{shop == null ? (
<div>
<button
type="button"
className={styles['heading__button--add']}
// 추후 검색 링크로 이동하는 이벤트로 변경
// 해당 검색 링크에서 특정 상점 선택 시 getShopname함수 호출
// 값이 null에서 string으로 변하면서 제목과 별점 생성
onClick={getShopname}
>
<Plus />
</button>
</div>
)
: (
<div className={styles.heading__contents}>
<div className={styles.heading__shopname}>{shop}</div>
<div className={styles['heading__sub-title']}>음식에 대한 별점을 매겨주세요.</div>
<StarRating onClick={active} />
</div>
)}
<div className={styles.heading__contents}>
<div className={styles.heading__shopname}>{shop}</div>
<div className={styles['heading__sub-title']}>음식에 대한 별점을 매겨주세요.</div>
<StarRating onClick={active} />
</div>
</title>
<div className={styles.item}>
<div className={styles.item__tools}>
<AddImage />
{/* <SlideToolBox
bold={() => wysiwygRef.current?.bold()}
heading={() => wysiwygRef.current?.heading()}
paragraph={() => wysiwygRef.current?.paragraph()}
through={() => wysiwygRef.current?.through()}
/> */}
</div>
</div>
</div>
Expand Down
6 changes: 6 additions & 0 deletions src/components/editor/Wysiwyg/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import '@toast-ui/editor/dist/toastui-editor.css';
import './Wysiwyg.scss';
import fontSize from 'tui-editor-plugin-font-size';
import 'tui-editor-plugin-font-size/dist/tui-editor-plugin-font-size.css';
import { useSetReview } from 'store/review';

export interface WysiwygType {
addImg: () => void,
Expand All @@ -15,6 +16,7 @@ export interface WysiwygType {

const Wysiwyg = forwardRef((_props, ref) => {
ChoiWonBeen marked this conversation as resolved.
Show resolved Hide resolved
const editorRef = useRef<Editor>(null);
const setReview = useSetReview();
useImperativeHandle(ref, () => ({
addImg() {
editorRef.current?.getInstance().exec('addImage', { imageUrl: 'https://picsum.photos/200/300' });
Expand Down Expand Up @@ -48,6 +50,10 @@ const Wysiwyg = forwardRef((_props, ref) => {
toolbarItems={[]}
ref={editorRef}
plugins={[fontSize]}
onChange={() => setReview((prev) => ({
...prev,
content: editorRef.current?.getInstance().getMarkdown() || '',
}))}
/>
</div>
);
Expand Down
Loading
Loading