Skip to content

Commit

Permalink
리뷰작성페이지 구현 (#58)
Browse files Browse the repository at this point in the history
  • Loading branch information
aaaz425 authored Jul 27, 2024
2 parents 632341d + 575b4fe commit dfe4fde
Show file tree
Hide file tree
Showing 19 changed files with 593 additions and 80 deletions.
5 changes: 5 additions & 0 deletions public/icon/image.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions public/icon/map_pin.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions public/icon/svg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@ export { default as heart } from 'public/icon/heart.svg';
export { default as heart_full } from 'public/icon/heart_full.svg';
export { default as icon_plus } from 'public/icon/icon_plus.svg';
export { default as icon_profile } from 'public/icon/icon_profile.svg';
export { default as image } from 'public/icon/image.svg';
export { default as internet } from 'public/icon/internet.svg';
export { default as logo } from 'public/icon/logo.svg';
export { default as logo_secondary } from 'public/icon/logo_secondary.svg';
export { default as map_pin } from 'public/icon/map_pin.svg';
export { default as menu } from 'public/icon/menu.svg';
export { default as more } from 'public/icon/more.svg';
export { default as phone } from 'public/icon/phone.svg';
Expand Down
15 changes: 0 additions & 15 deletions src/app/(sign)/layout.tsx

This file was deleted.

36 changes: 0 additions & 36 deletions src/app/(sign)/logout/page.tsx

This file was deleted.

9 changes: 9 additions & 0 deletions src/app/review/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import React from 'react';

export default function ReviewLayout({
children,
}: {
children: React.ReactNode;
}) {
return <>{children}</>;
}
44 changes: 44 additions & 0 deletions src/app/review/write/_components/Footer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import React, { MutableRefObject } from 'react';
import { UseFormRegister } from 'react-hook-form';
import Icon from '@/components/Icon';
import { reviewStore } from '@/store/userStore';
import { reviewFormInputs } from '@/types/user/form';

type FooterProps = {
imgRef: MutableRefObject<HTMLInputElement | null>;
clickHandler: () => void;
register: UseFormRegister<reviewFormInputs>;
};

function Footer({ imgRef, clickHandler, register }: FooterProps) {
const { ref: registerRef, ...rest } = register('img');

const reviewState = reviewStore();

return (
<div className="flex z-dim w-full absolute left-0 px-7 py-3 bottom-0 justify-between border-t-[1px] bg-white border-stroke_grey">
<span
onClick={() => clickHandler()}
className="text-primary cursor-pointer">
<Icon className="inline-block" filter="PRIMARY" name="image" /> 사진
</span>
<span
onClick={() => reviewState.switchSearchLoca()}
className="text-primary cursor-pointer">
<Icon className="inline-block" filter="PRIMARY" name="map_pin" /> 위치
</span>
<input
multiple
{...rest}
ref={(e) => {
registerRef(e);
imgRef.current = e;
}}
className="hidden"
type="file"
/>
</div>
);
}

export default Footer;
52 changes: 52 additions & 0 deletions src/app/review/write/_components/Header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import Image from 'next/image';
import { useRouter } from 'next/navigation';
import React from 'react';
import ActiveButton from '@/app/_components/ActiveButton';
import { reviewStore } from '@/store/userStore';

function Header() {
const reviewStatus = reviewStore();
const router = useRouter();

const backBtnHandler = () => {
reviewStatus.setResetReviewState();
router.push('/');
};

return (
<div className="flex flex-col">
<div className="flex justify-between items-center mb-2 bg-bg_white shadow-elevation1 absolute px-7 py-3 top-0 left-0 w-full">
<span
onClick={backBtnHandler}
className="text-16 text-text_light_grey hover:text-text_grey cursor-pointer">
취소
</span>
<span>
<ActiveButton
errorState={
!reviewStatus.content ||
!reviewStatus.likeCategory.length ||
!reviewStatus.cafeLoca ||
!reviewStatus.cafeName ||
!reviewStatus.rate ||
!reviewStatus.imgFiles.length
}
size="xs">
완료
</ActiveButton>
</span>
</div>
<div className="flex mt-[50px]">
<Image
src="/image/test_user.png"
alt="userImage"
width={45}
height={45}
/>
<span className="flex items-center">User1</span>
</div>
</div>
);
}

export default Header;
165 changes: 165 additions & 0 deletions src/app/review/write/_components/Main.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
'use client';

import React, { FocusEvent, useEffect, useRef, useState } from 'react';
import { SubmitHandler, useForm } from 'react-hook-form';
import Icon from '@/components/Icon';
import { reviewStore } from '@/store/userStore';
import { reviewFormInputs } from '@/types/user/form';
import ReviewDetailRating from '../../[reviewId]/_components/ReviewDetailRating';
import ReviewDetailTags from '../../[reviewId]/_components/ReviewDetailTags';
import Footer from './Footer';
import Header from './Header';
import RateStars from './RateStars';
import ReviewImg from './ReviewImg';
import ReviewWriteTags from './ReviewWriteTags';
import SearchForm from './SearchForm';

export default function Main() {
const reviewStatus = reviewStore();
// const imgUrls: string[] = [];
const [urlList, setUrlList] = useState<string[]>();
const textAreaRef = useRef<HTMLTextAreaElement>(null);
const imgRef = useRef<HTMLInputElement | null>(null);

const { register, handleSubmit, watch } = useForm<reviewFormInputs>();

const imgFiles = watch('img');

useEffect(() => {
textAreaRef.current?.focus();
const newUrl: string[] = [];
for (let i = 0; i < imgFiles?.length; i++) {
// 기존에 정의된 변수 사용
const binaryData = [imgFiles[i]];
const urlImage = URL.createObjectURL(
new Blob(binaryData, { type: 'image' })
);
newUrl.push(urlImage);
}
setUrlList(newUrl);
reviewStatus.setImgFiles(newUrl);
}, [imgFiles]);

const onReviewSubmitHandler: SubmitHandler<reviewFormInputs> = () => {
// TO-DO: 데이터 전달하기
};

const onDeleteLocaHandler = () => {
reviewStatus.setResetPlaceState();
reviewStatus.setCafeName('');
};

const onBlurHandler = (e: FocusEvent<HTMLTextAreaElement, Element>) => {
reviewStatus.setContent(e.target.value);
reviewStatus.switchWriteMode();
};

return reviewStatus.searchLoca ? (
<SearchForm />
) : (
<div className="relative h-full flex flex-col justify-between overflow-y-auto overflow-x-hidden p-5 scrollbar-thin scrollbar-thumb-primary scrollbar-track-bg_white">
<form
onSubmit={handleSubmit(onReviewSubmitHandler)}
className="flex flex-col h-auto">
<div className="h-full">
<Header />
{urlList && urlList.length > 0 && (
<ReviewImg urlList={urlList} setList={setUrlList} />
)}
</div>
<div className="max-h-[80px] ">
{reviewStatus.writeMode ? (
<>
<div
onClick={() => reviewStatus.switchWriteMode()}
className="w-full h-full absolute left-0 top-0 z-dim opacity-20"
/>
<textarea
onBlur={onBlurHandler}
ref={textAreaRef}
className="focus:outline-none relative z-modal resize-none w-full h-full max:h-[75px] scrollbar-thin scrollbar-thumb-primary scrollbar-track-bg_white mt-3"
/>
</>
) : (
<>
<div
className="w-full min-h-3 overflow-y-auto cursor-pointer break-words max-h-[70px] max:h-[60px] scrollbar-thin scrollbar-thumb-primary scrollbar-track-bg_white mt-3"
onClick={() => reviewStatus.switchWriteMode()}>
{reviewStatus.content}
</div>
<div className="border-[1px] border-stroke_grey my-3" />
</>
)}
</div>
{reviewStatus.likeCategory.length > 0 && reviewStatus.rate > 0 && (
<div
onClick={() => reviewStatus.setModalState()}
className="flex flex-col my-7 max:my-3 gap-5 cursor-pointer items-center">
<ReviewDetailRating rating={reviewStatus.rate} />
<div className="w-4/5">
<ReviewDetailTags tags={reviewStatus.likeCategory} />
</div>
<input className="hidden" type="text" />
</div>
)}

{reviewStatus.cafeName && (
<div className="flex justify-between items-center px-12 py-20 rounded-md border border-bg_disabled">
<div className="flex flex-col gap-1">
<span className="text-12 font-bold">{reviewStatus.cafeName}</span>
<span className="text-10 text-text_grey">
{reviewStatus.cafeLoca}
</span>
</div>
<button
onClick={onDeleteLocaHandler}
className="flex items-center justify-center p-12 rounded-full border">
<Icon name="close" size={16} />
</button>
</div>
)}
</form>

{reviewStatus.modalState && (
<>
<div
onClick={() => reviewStatus.setModalState()}
className="w-full h-full absolute left-0 top-0 z-dim"
/>
<div className="w-full animate-modalSlide px-5 pb-10 pt-[55px] absolute left-0 bottom-0 bg-white z-modal rounded-t-3xl shadow-elevation5">
<div className="flex justify-center text-18 text-primary font-bold">
'{reviewStatus.cafeName}'을 평가해주세요
</div>
<RateStars />
<div>
<div className="flex justify-center my-5">
어떤 게 좋았나요? (중복 가능)
</div>
<div className="w-full flex justify-center">
<div className="w-2/3">
<ReviewWriteTags
tags={[
'COFFEE',
'BEVERAGE',
'DESSERT',
'MANNER',
'MONEY',
'COZY',
'QUIET',
'ACCESSIBILITY',
]}
/>
</div>
</div>
</div>
</div>
</>
)}
<Footer
clickHandler={() => imgRef.current?.click()}
register={register}
imgRef={imgRef}
/>
</div>
);
}
42 changes: 42 additions & 0 deletions src/app/review/write/_components/RateStars.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
'use client';

import React, { useState } from 'react';
import Icon from '@/components/Icon';
import { reviewStore } from '@/store/userStore';

const StarRating = () => {
const reviewState = reviewStore();
const starArray = [1, 2, 3, 4, 5];
const [coloredStar, setColoredStar] = useState(0);
const [colorFixed, setColorFixed] = useState(false);

const hoverStarHandler = (el: number) => {
!colorFixed && setColoredStar(el);
};

const checkStarHandler = (el: number) => {
setColorFixed(true);
setColoredStar(el);
reviewState.setRate(el);
};

return (
<div className="flex justify-center space-x-1">
{starArray.map((el, idx) => (
<span
className="cursor-pointer my-5"
onMouseEnter={() => hoverStarHandler(el)}
onClick={() => checkStarHandler(el)}
key={idx}>
{coloredStar >= el ? (
<Icon name="star" filter="PRIMARY" />
) : (
<Icon name="star_empty" />
)}
</span>
))}
</div>
);
};

export default StarRating;
Loading

0 comments on commit dfe4fde

Please sign in to comment.