Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added src/assets/gif/ticket_alt.gif
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 src/assets/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import HeaderLogo from '@/assets/icons/header_logo.svg?react';
import GearIcon from '@/assets/icons/icon_gear.svg?react';
import DefaultProfile from '@/assets/icons/default_profile.svg?react';
import CheckIcon from '@/assets/icons/check.svg?react';
import TicketAlt from '@/assets/gif/ticket_alt.gif';

export {
MoreVerticalIcon,
Expand Down Expand Up @@ -70,4 +71,5 @@ export {
GearIcon,
DefaultProfile,
CheckIcon,
TicketAlt,
};
5 changes: 4 additions & 1 deletion src/components/review/ReviewLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Button, Header } from '@/components';

interface ReviewStepLayoutProps {
title?: string;
description?: string;
children: ReactNode;
onClickNext: () => void;
onClickBack: () => void;
Expand All @@ -13,6 +14,7 @@ interface ReviewStepLayoutProps {

export default function ReviewStepLayout({
title,
description,
children,
onClickNext,
onClickBack,
Expand All @@ -25,10 +27,11 @@ export default function ReviewStepLayout({
<div className="fixed top-0 right-0 left-0 z-50 bg-gray-900">
<Header title="" showBack onBackClick={onClickBack} showLike={false} showBookmark={false} />
</div>
<div className={`flex flex-col gap-6 px-5 ${title ? 'pt-4' : ''}`}>
<div className={`flex flex-col gap-6 overflow-y-auto px-5 pb-[100px] ${title ? 'pt-4' : ''}`}>
{title && (
<div className="w-full max-w-[430px] text-left">
<h2 className="text-title-2 text-white">{title}</h2>
{description && <p className="text-caption-3 pt-1 text-red-300">{description}</p>}
</div>
)}

Expand Down
4 changes: 2 additions & 2 deletions src/components/review/TagSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export default function TagSection({
return (
<section className="mb-6">
<h2 className="text-caption-2 mb-3">
{title} {required && <span className="text-red-500">*</span>}
{title} {required && <span className="text-red-400">*</span>}
</h2>
<div className="flex flex-wrap gap-2">
{options.map((option) => (
Expand All @@ -27,7 +27,7 @@ export default function TagSection({
onClick={() => onChange(option)}
variant="secondary-assistive"
color="gray"
size="sm"
size="xs"
rounded="md"
selected={selected.includes(option)}
>
Expand Down
11 changes: 9 additions & 2 deletions src/constants/reviewTags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,20 @@ export const tagSections: TagSectionConfig[] = [
key: 'sound',
title: '음향',
required: true,
options: ['#음향최고', '#음향아쉬움', '#소리선명', '#음질만족', '#돌비사운드굿', '#잡음있음'],
options: ['#음향최고', '#음질깨끗', '#몰입감좋음', '#서라운드좋음', '#소리선명', '#현장감있음'],
},
{
key: 'environment',
title: '관람 환경',
required: true,
options: ['#좌석편함', '#시야좋음', '#청결상태굿', '#주변산만', '#출입구근처', '#조용함'],
options: [
'#좌석편함',
'#시야탁트임',
'#쾌적한환경',
'#입출입편리',
'#청결유지잘됨',
'#분위기좋음',
],
},
{
key: 'companion',
Expand Down
3 changes: 2 additions & 1 deletion src/pages/review/MovieInfoStep.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@ export default function MovieInfoForm() {
placeholder="관람하신 영화관을 선택해주세요"
onClickPlus={() => navigate('/review/info/cinema')}
/>

</div>
<div className="flex flex-col gap-2">
{/*추후 좌석 페이지 연결 시 readOnly 속성 추가*/}
<InputField
label="좌석"
Expand Down
25 changes: 17 additions & 8 deletions src/pages/review/ReviewContent.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useEffect } from 'react';
import { ReviewStepLayout, Textarea, ImagePreviewItem } from '@/components';
import { ReviewStepLayout, Textarea, ImagePreviewItem, InputField } from '@/components';
import { PlusIcon } from '@/assets';
import { useReviewStore, useModalStore } from '@/store';
import { useNavigate } from 'react-router-dom';
Expand All @@ -9,10 +9,11 @@ const MAX_IMAGES = 5;
const MIN_TEXT_LENGTH = 30;

export default function ReviewTextForm() {
const { text, setText } = useReviewStore();
const { text, setText, reviewTitle, setReviewTitle, isInitialized } = useReviewStore();
const { images, addImages, removeImage } = useImgUpload(5);
const navigate = useNavigate();

const isValid = text.trim().length >= MIN_TEXT_LENGTH;
const isValid = reviewTitle.trim().length > 0 && text.trim().length >= MIN_TEXT_LENGTH;
const { openModal } = useModalStore();
const handleSubmit = () => {
if (!text.trim()) return;
Expand All @@ -26,9 +27,6 @@ export default function ReviewTextForm() {
},
});
};
const { isInitialized } = useReviewStore();
const navigate = useNavigate();

useEffect(() => {
if (!isInitialized) {
navigate('/review');
Expand Down Expand Up @@ -71,9 +69,20 @@ export default function ReviewTextForm() {
</div>

{/* 텍스트 입력 */}
<div className="flex flex-col gap-1 pt-6">
<div className="flex flex-col gap-1 pt-8">
<InputField
label="제목"
placeholder="후기의 제목을 적어주세요"
value={reviewTitle}
onChange={(value) => {
if (value.length <= 20) setReviewTitle(value);
}}
/>
<div className="text-caption-2 pt-8 text-white">
후기 내용 <span className="text-red-400">*</span>
</div>
<Textarea
title="주제"
title=""
placeholder="관람 경험을 자유롭게 적어주세요. (예: 사운드 중심 좌석으로 돌비 효과를 제대로 느낄 수 있어서 좋았어요!)"
minLength={30}
placeholderColorType="gray"
Expand Down
25 changes: 14 additions & 11 deletions src/pages/review/TagPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,24 @@ export default function ReviewTagsPage() {

return (
<ReviewStepLayout
title="상영관의 어떤 점이 좋았나요?"
title="관람하신 상영관은 어땠나요?"
description="최대 5개까지 선택할 수 있어요."
onClickBack={() => navigate('/review/rating')}
onClickNext={handleNext}
disabled={!canProceed}
>
{tagSections.map(({ key, title, required, options }) => (
<TagSection
key={key}
title={title}
options={options}
required={required}
selected={tags[key]}
onChange={(value) => toggleTag(key, value)}
/>
))}
<div className="flex flex-col overflow-y-auto pb-[88px]">
{tagSections.map(({ key, title, required, options }) => (
<TagSection
key={key}
title={title}
options={options}
required={required}
selected={tags[key]}
onChange={(value) => toggleTag(key, value)}
/>
))}
</div>
</ReviewStepLayout>
);
}
10 changes: 8 additions & 2 deletions src/pages/review/TicketPage.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Button, Header } from '@/components';
import { Button, Header, Image } from '@/components';
import { useNavigate } from 'react-router-dom';
import { useReviewStore } from '@/store';
import { useEffect } from 'react';
import { TicketAlt } from '@/assets';

export const TicketUploadStep = () => {
const { setInitialized } = useReviewStore();
Expand Down Expand Up @@ -36,7 +37,12 @@ export const TicketUploadStep = () => {

{/* 로고 or 티켓 영역 자리 (임시 checker 배경) */}
<div className="flex justify-center pt-15">
<div className="h-[149px] w-[160px] flex-shrink-0 bg-gray-700 bg-[url('/checker.png')] bg-cover bg-center" />
<Image
src={TicketAlt} // public/assets 경로에 있는 경우
alt="티켓 캐릭터"
className="w-full object-contain"
aspectRatio=""
/>
</div>
</div>

Expand Down
35 changes: 28 additions & 7 deletions src/store/useReviewStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ interface CinemaInfo {
}

interface ReviewState {
reviewTitle: string; //후기 제목
setReviewTitle: (title: string) => void;
movieTitle: string;
cinema: CinemaInfo | null;
seats: string[];
Expand All @@ -30,6 +32,8 @@ interface ReviewState {
}

export const useReviewStore = create<ReviewState>((set) => ({
reviewTitle: '',
setReviewTitle: (title) => set({ reviewTitle: title }),
movieTitle: '',
cinema: null,
seats: [],
Expand All @@ -50,6 +54,7 @@ export const useReviewStore = create<ReviewState>((set) => ({
reset: () =>
set({
movieTitle: '',
reviewTitle: '',
cinema: null,
seats: [],
rating: 0,
Expand All @@ -76,12 +81,28 @@ export const useReviewStore = create<ReviewState>((set) => ({
toggleTag: (type, tag) =>
set((state) => {
const current = state.tags[type];
const updated = current.includes(tag) ? current.filter((t) => t !== tag) : [...current, tag];
return {
tags: {
...state.tags,
[type]: updated,
},
};
const isSelected = current.includes(tag);

const totalSelected =
state.tags.sound.length + state.tags.sound.length + state.tags.companion.length;

if (isSelected) {
return {
tags: {
...state.tags,
[type]: current.filter((t) => t !== tag),
},
};
}
if (totalSelected < 5) {
return {
tags: {
...state.tags,
[type]: [...current, tag],
},
};
}

return {};
}),
}));