diff --git a/src/assets/post/close.svg b/src/assets/post/close.svg
new file mode 100644
index 00000000..517027ec
--- /dev/null
+++ b/src/assets/post/close.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/assets/post/plus-disabled.svg b/src/assets/post/plus-disabled.svg
new file mode 100644
index 00000000..9ab32165
--- /dev/null
+++ b/src/assets/post/plus-disabled.svg
@@ -0,0 +1,4 @@
+
diff --git a/src/assets/post/plus.svg b/src/assets/post/plus.svg
new file mode 100644
index 00000000..3910e81c
--- /dev/null
+++ b/src/assets/post/plus.svg
@@ -0,0 +1,4 @@
+
diff --git a/src/components/common/BookSearchBottomSheet/BookSearchBottomSheet.tsx b/src/components/common/BookSearchBottomSheet/BookSearchBottomSheet.tsx
index bc3a3c19..b8aa5dff 100644
--- a/src/components/common/BookSearchBottomSheet/BookSearchBottomSheet.tsx
+++ b/src/components/common/BookSearchBottomSheet/BookSearchBottomSheet.tsx
@@ -37,7 +37,7 @@ interface BookSearchBottomSheetProps {
type TabType = 'saved' | 'group';
// Mock Data
-const mockBooks: Book[] = [
+const mockSavedBooks: Book[] = [
{
id: 1,
title: '토마토 컵라면',
@@ -56,51 +56,53 @@ const mockBooks: Book[] = [
author: '작가명',
cover: '/src/assets/books/hormone.svg',
},
+];
+
+const mockGroupBooks: Book[] = [
{
id: 4,
- title: '토마토 컵라면',
+ title: '단 한번의 삶',
author: '작가명',
- cover: '/src/assets/books/tomato.svg',
+ cover: '/src/assets/books/life.svg',
},
{
id: 5,
- title: '사슴',
- author: '작가명',
- cover: '/src/assets/books/deer.svg',
- },
- {
- id: 6,
title: '호르몬 체인지',
author: '작가명',
cover: '/src/assets/books/hormone.svg',
},
{
- id: 7,
- title: '단 한번의 삶',
+ id: 6,
+ title: '토마토 컵라면',
author: '작가명',
- cover: '/src/assets/books/life.svg',
+ cover: '/src/assets/books/tomato.svg',
},
];
const BookSearchBottomSheet = ({ isOpen, onClose, onSelectBook }: BookSearchBottomSheetProps) => {
// State
const [searchQuery, setSearchQuery] = useState('');
- const [filteredBooks, setFilteredBooks] = useState(mockBooks);
+ const [filteredBooks, setFilteredBooks] = useState(mockSavedBooks);
const [activeTab, setActiveTab] = useState('saved');
// Effects
useEffect(() => {
+ // 현재 활성화된 탭의 책 목록 가져오기
+ const currentTabBooks = activeTab === 'saved' ? mockSavedBooks : mockGroupBooks;
+
if (searchQuery.trim() === '') {
- setFilteredBooks(mockBooks);
+ // 검색어가 없을 때는 선택된 탭의 전체 목록 표시
+ setFilteredBooks(currentTabBooks);
} else {
- const filtered = mockBooks.filter(
+ // 검색어가 있을 때는 선택된 탭 내에서만 검색
+ const filtered = currentTabBooks.filter(
book =>
book.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
book.author.toLowerCase().includes(searchQuery.toLowerCase()),
);
setFilteredBooks(filtered);
}
- }, [searchQuery]);
+ }, [searchQuery, activeTab]);
useEffect(() => {
if (isOpen) {
@@ -145,6 +147,26 @@ const BookSearchBottomSheet = ({ isOpen, onClose, onSelectBook }: BookSearchBott
setSearchQuery('');
};
+ const handleTabChange = (tab: TabType) => {
+ setActiveTab(tab);
+ // 탭 변경 시 현재 검색어로 새로운 탭에서 다시 검색
+ const newTabBooks = tab === 'saved' ? mockSavedBooks : mockGroupBooks;
+
+ if (searchQuery.trim() === '') {
+ setFilteredBooks(newTabBooks);
+ } else {
+ const filtered = newTabBooks.filter(
+ book =>
+ book.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
+ book.author.toLowerCase().includes(searchQuery.toLowerCase()),
+ );
+ setFilteredBooks(filtered);
+ }
+ };
+
+ // 검색어가 없을 때만 탭 표시
+ const showTabs = searchQuery.trim() === '';
+
return (
@@ -169,15 +191,17 @@ const BookSearchBottomSheet = ({ isOpen, onClose, onSelectBook }: BookSearchBott
- {/* 탭 영역 */}
-
- setActiveTab('saved')}>
- 저장한 책
-
- setActiveTab('group')}>
- 모임 책
-
-
+ {/* 탭 영역 - 검색어가 없을 때만 표시 */}
+ {showTabs && (
+
+ handleTabChange('saved')}>
+ 저장한 책
+
+ handleTabChange('group')}>
+ 모임 책
+
+
+ )}
{/* 책 목록 영역 */}
diff --git a/src/components/createpost/PhotoSection.styled.ts b/src/components/createpost/PhotoSection.styled.ts
new file mode 100644
index 00000000..95164f97
--- /dev/null
+++ b/src/components/createpost/PhotoSection.styled.ts
@@ -0,0 +1,85 @@
+import styled from '@emotion/styled';
+import { typography, semanticColors, colors } from '../../styles/global/global';
+
+export const PhotoContainer = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+`;
+
+export const PhotoGrid = styled.div`
+ display: flex;
+ gap: 12px;
+ align-items: center;
+`;
+
+export const AddPhotoButton = styled.button`
+ width: 80px;
+ height: 80px;
+ border: 1px solid ${colors.grey[300]};
+ background-color: ${semanticColors.background.cardDark};
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ transition: all 0.2s;
+
+ img {
+ width: 24px;
+ height: 24px;
+ }
+
+ &:hover:not(:disabled) {
+ border-color: ${semanticColors.text.primary};
+ }
+
+ &:disabled {
+ background-color: ${colors.darkgrey.dark};
+ border: 1px solid ${colors.darkgrey.main};
+ cursor: not-allowed;
+ }
+`;
+
+export const PhotoItem = styled.div`
+ position: relative;
+ width: 80px;
+ height: 80px;
+`;
+
+export const PhotoImage = styled.img`
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ border: 1px solid ${colors.grey[300]};
+`;
+
+export const RemoveButton = styled.button`
+ position: absolute;
+ top: 0px;
+ right: 0px;
+ width: 24px;
+ height: 24px;
+ background-color: rgba(0, 0, 0, 0.6);
+ border: none;
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border: 1px solid ${colors.grey[300]};
+
+ img {
+ width: 12px;
+ height: 12px;
+ }
+
+ &:hover {
+ background-color: rgba(0, 0, 0, 0.8);
+ }
+`;
+
+export const PhotoCount = styled.div`
+ align-self: flex-end;
+ color: ${semanticColors.text.point.green};
+ font-size: ${typography.fontSize.xs};
+ font-weight: ${typography.fontWeight.regular};
+`;
diff --git a/src/components/createpost/PhotoSection.tsx b/src/components/createpost/PhotoSection.tsx
new file mode 100644
index 00000000..5a3cd9dc
--- /dev/null
+++ b/src/components/createpost/PhotoSection.tsx
@@ -0,0 +1,74 @@
+import { useRef } from 'react';
+import { Section, SectionTitle } from '../../pages/group/CommonSection.styled';
+import {
+ PhotoContainer,
+ PhotoGrid,
+ AddPhotoButton,
+ PhotoImage,
+ RemoveButton,
+ PhotoCount,
+} from './PhotoSection.styled';
+import plusIcon from '../../assets/post/plus.svg';
+import plusDisabledIcon from '../../assets/post/plus-disabled.svg';
+import closeIcon from '../../assets/post/close.svg';
+
+interface PhotoSectionProps {
+ photos: File[];
+ onPhotoAdd: (files: File[]) => void;
+ onPhotoRemove: (index: number) => void;
+}
+
+const PhotoSection = ({ photos, onPhotoAdd, onPhotoRemove }: PhotoSectionProps) => {
+ const fileInputRef = useRef(null);
+
+ const handleFileInputClick = () => {
+ fileInputRef.current?.click();
+ };
+
+ const handleFileChange = (e: React.ChangeEvent) => {
+ const files = Array.from(e.target.files || []);
+ if (files.length > 0) {
+ onPhotoAdd(files);
+ }
+ // input 값 초기화 (같은 파일을 다시 선택할 수 있도록)
+ e.target.value = '';
+ };
+
+ const createImageUrl = (file: File) => {
+ return URL.createObjectURL(file);
+ };
+
+ const isDisabled = photos.length >= 3;
+
+ return (
+
+ 사진 추가
+
+
+
+
+
+ {photos.map((photo, index) => (
+
+
+
onPhotoRemove(index)}>
+
+
+
+ ))}
+
+ {photos.length}/3개
+
+
+
+ );
+};
+
+export default PhotoSection;
diff --git a/src/components/createpost/PostContentSection.styled.ts b/src/components/createpost/PostContentSection.styled.ts
new file mode 100644
index 00000000..15b52217
--- /dev/null
+++ b/src/components/createpost/PostContentSection.styled.ts
@@ -0,0 +1,33 @@
+import styled from '@emotion/styled';
+import { typography, semanticColors } from '../../styles/global/global';
+
+export const TextAreaBox = styled.div`
+ position: relative;
+ display: flex;
+ flex-direction: column;
+`;
+
+export const TextArea = styled.textarea`
+ width: 100%;
+ min-height: 100px;
+ background-color: ${semanticColors.background.primary};
+ color: ${semanticColors.text.secondary};
+ font-size: ${typography.fontSize.sm};
+ font-weight: ${typography.fontWeight.regular};
+ font-family: ${typography.fontFamily.primary};
+ resize: none;
+ outline: none;
+ border: none;
+
+ &::placeholder {
+ color: ${semanticColors.text.ghost};
+ }
+`;
+
+export const CharacterCount = styled.div`
+ align-self: flex-end;
+ margin-top: 12px;
+ color: ${semanticColors.text.point.green};
+ font-size: ${typography.fontSize.xs};
+ font-weight: ${typography.fontWeight.regular};
+`;
diff --git a/src/components/createpost/PostContentSection.tsx b/src/components/createpost/PostContentSection.tsx
new file mode 100644
index 00000000..4579e034
--- /dev/null
+++ b/src/components/createpost/PostContentSection.tsx
@@ -0,0 +1,31 @@
+import { Section, SectionTitle } from '../../pages/group/CommonSection.styled';
+import { TextAreaBox, TextArea, CharacterCount } from './PostContentSection.styled';
+
+interface PostContentSectionProps {
+ content: string;
+ onContentChange: (value: string) => void;
+}
+
+const PostContentSection = ({ content, onContentChange }: PostContentSectionProps) => {
+ const maxLength = 2000;
+
+ return (
+
+ );
+};
+
+export default PostContentSection;
diff --git a/src/components/createpost/PrivacyToggleSection.styled.ts b/src/components/createpost/PrivacyToggleSection.styled.ts
new file mode 100644
index 00000000..1d2c4867
--- /dev/null
+++ b/src/components/createpost/PrivacyToggleSection.styled.ts
@@ -0,0 +1,36 @@
+import styled from '@emotion/styled';
+import { typography, semanticColors } from '../../styles/global/global';
+
+export const ToggleContainer = styled.div`
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+`;
+
+export const ToggleLabel = styled.span`
+ color: ${semanticColors.text.primary};
+ font-size: ${typography.fontSize.base};
+ font-weight: ${typography.fontWeight.regular};
+`;
+
+export const ToggleSwitch = styled.div<{ active: boolean }>`
+ width: 48px;
+ height: 28px;
+ background-color: ${({ active }) =>
+ active ? semanticColors.button.fill.primary : semanticColors.background.card};
+ border-radius: 14px;
+ position: relative;
+ cursor: pointer;
+ transition: background-color 0.3s;
+`;
+
+export const ToggleSlider = styled.div<{ active: boolean }>`
+ width: 20px;
+ height: 20px;
+ background-color: ${semanticColors.text.primary};
+ border-radius: 50%;
+ position: absolute;
+ top: 4px;
+ left: ${({ active }) => (active ? '24px' : '4px')};
+ transition: left 0.3s;
+`;
diff --git a/src/components/createpost/PrivacyToggleSection.tsx b/src/components/createpost/PrivacyToggleSection.tsx
new file mode 100644
index 00000000..abf1505c
--- /dev/null
+++ b/src/components/createpost/PrivacyToggleSection.tsx
@@ -0,0 +1,28 @@
+import { Section, SectionTitle } from '../../pages/group/CommonSection.styled';
+import {
+ ToggleContainer,
+ ToggleLabel,
+ ToggleSwitch,
+ ToggleSlider,
+} from './PrivacyToggleSection.styled';
+
+interface PrivacyToggleSectionProps {
+ isPrivate: boolean;
+ onToggle: () => void;
+}
+
+const PrivacyToggleSection = ({ isPrivate, onToggle }: PrivacyToggleSectionProps) => {
+ return (
+
+ 공개 설정
+
+ 비공개로 설정
+
+
+
+
+
+ );
+};
+
+export default PrivacyToggleSection;
diff --git a/src/components/createpost/TagSelectionSection.styled.ts b/src/components/createpost/TagSelectionSection.styled.ts
new file mode 100644
index 00000000..dd0d6db4
--- /dev/null
+++ b/src/components/createpost/TagSelectionSection.styled.ts
@@ -0,0 +1,109 @@
+import styled from '@emotion/styled';
+import { colors, typography, semanticColors } from '../../styles/global/global';
+
+export const TagContainer = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+`;
+
+export const GenreButtonGroup = styled.div`
+ display: flex;
+ flex-wrap: wrap;
+ gap: 12px;
+`;
+
+export const GenreButton = styled.button<{ active: boolean }>`
+ background-color: ${({ active }) =>
+ active ? semanticColors.button.fill.primary : colors.grey[400]};
+ border: none;
+ border-radius: 20px;
+ padding: 8px 12px;
+ color: ${semanticColors.text.primary};
+ font-size: ${typography.fontSize.xs};
+ font-weight: ${typography.fontWeight.regular};
+ cursor: pointer;
+ transition: all 0.2s;
+
+ &:hover {
+ background-color: ${({ active }) =>
+ active ? semanticColors.button.fill.background : colors.grey[300]};
+ }
+`;
+
+export const SubTagGrid = styled.div`
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+`;
+
+export const SubTagButton = styled.button<{ active: boolean; disabled: boolean }>`
+ background-color: transparent;
+ border: 1px solid
+ ${({ active }) => (active ? semanticColors.button.fill.primary : colors.grey[300])};
+ border-radius: 20px;
+ padding: 8px 12px;
+ color: ${({ active }) =>
+ active ? semanticColors.text.point.purple : semanticColors.text.secondary};
+ font-size: ${typography.fontSize.xs};
+ font-weight: ${typography.fontWeight.regular};
+ cursor: ${({ disabled }) => (disabled ? 'not-allowed' : 'pointer')};
+ transition: all 0.2s;
+ opacity: ${({ disabled }) => (disabled ? 0.5 : 1)};
+ white-space: nowrap;
+ width: auto;
+
+ &:hover:not(:disabled) {
+ border-color: ${semanticColors.button.fill.primary};
+ background-color: ${({ active }) => (active ? 'transparent' : 'rgba(104, 104, 255, 0.1)')};
+ }
+`;
+
+export const TagCount = styled.div`
+ align-self: flex-end;
+ color: ${semanticColors.text.point.green};
+ font-size: ${typography.fontSize.xs};
+ font-weight: ${typography.fontWeight.regular};
+`;
+
+export const SelectedTagsSection = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+`;
+
+export const SelectedTagsTitle = styled.div`
+ color: ${semanticColors.text.primary};
+ font-size: ${typography.fontSize.sm};
+ font-weight: ${typography.fontWeight.medium};
+`;
+
+export const SelectedTagList = styled.div`
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+`;
+
+export const SelectedTagItem = styled.button`
+ background-color: transparent;
+ border: 1px solid ${colors.grey[200]};
+ border-radius: 20px;
+ padding: 6px 12px;
+ color: ${semanticColors.text.primary};
+ font-size: ${typography.fontSize.xs};
+ font-weight: ${typography.fontWeight.regular};
+ cursor: pointer;
+ transition: all 0.2s;
+ display: flex;
+ align-items: center;
+ gap: 4px;
+
+ img {
+ width: 12px;
+ height: 12px;
+ }
+
+ &:hover {
+ background-color: rgba(104, 104, 255, 0.1);
+ }
+`;
diff --git a/src/components/createpost/TagSelectionSection.tsx b/src/components/createpost/TagSelectionSection.tsx
new file mode 100644
index 00000000..78884b97
--- /dev/null
+++ b/src/components/createpost/TagSelectionSection.tsx
@@ -0,0 +1,112 @@
+import { useState } from 'react';
+import { Section, SectionTitle } from '../../pages/group/CommonSection.styled';
+import {
+ TagContainer,
+ GenreButtonGroup,
+ GenreButton,
+ SubTagGrid,
+ SubTagButton,
+ SelectedTagsSection,
+ SelectedTagsTitle,
+ SelectedTagList,
+ SelectedTagItem,
+ TagCount,
+} from './TagSelectionSection.styled';
+import closeIcon from '../../assets/post/close.svg';
+
+interface TagSelectionSectionProps {
+ selectedTags: string[];
+ onTagToggle: (tag: string) => void;
+}
+
+// 상위 장르와 하위 태그 매핑
+const genreTagsMap: Record = {
+ 문학: ['소설', '시', '에세이', '인문학', '철학'],
+ '과학·IT': ['기술', '과학', 'AI', '데이터'],
+ 사회과학: ['정치', '경제', '사회학', '심리학', '역사'],
+ 인문학: ['철학', '역사', '문화', '언어학', '종교'],
+ 예술: ['미술', '음악', '영화', '디자인', '사진'],
+};
+
+const availableGenres = Object.keys(genreTagsMap);
+
+const TagSelectionSection = ({ selectedTags, onTagToggle }: TagSelectionSectionProps) => {
+ const [selectedGenre, setSelectedGenre] = useState('문학');
+
+ const handleGenreSelect = (genre: string) => {
+ setSelectedGenre(genre);
+ };
+
+ const handleTagToggle = (tag: string) => {
+ // 이미 선택된 태그면 해제
+ if (selectedTags.includes(tag)) {
+ onTagToggle(tag);
+ return;
+ }
+
+ // 5개 미만이면 추가 가능
+ if (selectedTags.length < 5) {
+ onTagToggle(tag);
+ }
+ };
+
+ const handleSelectedTagRemove = (tag: string) => {
+ onTagToggle(tag);
+ };
+
+ const currentSubTags = genreTagsMap[selectedGenre] || [];
+
+ return (
+
+ 태그
+
+ {/* 상위 장르 선택 */}
+
+ {availableGenres.map(genre => (
+ handleGenreSelect(genre)}
+ >
+ {genre}
+
+ ))}
+
+
+ {/* 하위 태그 그리드 */}
+
+ {currentSubTags.map(tag => (
+ = 5}
+ onClick={() => handleTagToggle(tag)}
+ >
+ {tag}
+
+ ))}
+
+
+ {/* 태그 선택 개수 표시 */}
+ {selectedTags.length} / 5개
+
+ {/* 선택된 태그 목록 */}
+ {selectedTags.length > 0 && (
+
+ 선택된 태그
+
+ {selectedTags.map(tag => (
+ handleSelectedTagRemove(tag)}>
+ #{tag}
+
+
+ ))}
+
+
+ )}
+
+
+ );
+};
+
+export default TagSelectionSection;
diff --git a/src/pages/index.tsx b/src/pages/index.tsx
index ff9ffe45..078c448f 100644
--- a/src/pages/index.tsx
+++ b/src/pages/index.tsx
@@ -10,6 +10,7 @@ import SignupGenre from './signup/SignupGenre';
import SignupNickname from './signup/SignupNickname';
import SignupDone from './signup/SignupDone';
import CreateGroup from './group/CreateGroup';
+import CreatePost from './post/CreatePost';
import Group from './group/Group';
import Feed from './feed/Feed';
import GroupSearch from './groupSearch/GroupSearch';
@@ -34,6 +35,7 @@ const Router = () => {
} />
} />
+ } />
} />
} />
} />
diff --git a/src/pages/post/CreatePost.styled.ts b/src/pages/post/CreatePost.styled.ts
new file mode 100644
index 00000000..ca28490b
--- /dev/null
+++ b/src/pages/post/CreatePost.styled.ts
@@ -0,0 +1,14 @@
+import styled from '@emotion/styled';
+import { semanticColors } from '../../styles/global/global';
+
+export const Container = styled.div`
+ display: flex;
+ flex-direction: column;
+ background-color: ${semanticColors.background.primary};
+ min-width: 360px;
+ max-width: 767px;
+ min-height: 100vh;
+ margin: 0 auto;
+ padding: 96px 20px 100px 20px;
+ box-sizing: border-box;
+`;
diff --git a/src/pages/post/CreatePost.tsx b/src/pages/post/CreatePost.tsx
new file mode 100644
index 00000000..38eb55ca
--- /dev/null
+++ b/src/pages/post/CreatePost.tsx
@@ -0,0 +1,136 @@
+import { useState } from 'react';
+import { useNavigate } from 'react-router-dom';
+import TitleHeader from '../../components/common/TitleHeader';
+import BookSearchBottomSheet from '../../components/common/BookSearchBottomSheet/BookSearchBottomSheet';
+import BookSelectionSection from '../../components/creategroup/BookSelectionSection';
+import PostContentSection from '../../components/createpost/PostContentSection';
+import PhotoSection from '../../components/createpost/PhotoSection';
+import PrivacyToggleSection from '../../components/createpost/PrivacyToggleSection';
+import TagSelectionSection from '../../components/createpost/TagSelectionSection';
+import leftarrow from '../../assets/common/leftArrow.svg';
+import { Container } from './CreatePost.styled';
+import { Section } from '../group/CommonSection.styled';
+
+interface Book {
+ id: number;
+ title: string;
+ author: string;
+ cover: string;
+}
+
+const CreatePost = () => {
+ const navigate = useNavigate();
+ const [selectedBook, setSelectedBook] = useState(null);
+ const [postContent, setPostContent] = useState('');
+ const [selectedPhotos, setSelectedPhotos] = useState([]);
+ const [isPrivate, setIsPrivate] = useState(false);
+ const [selectedTags, setSelectedTags] = useState([]);
+ const [isBookSearchOpen, setIsBookSearchOpen] = useState(false);
+
+ const handleBackClick = () => {
+ navigate(-1);
+ };
+
+ const handleCompleteClick = () => {
+ // 필수 항목 검증
+ if (!isFormValid) {
+ console.log('필수 항목을 입력해주세요.');
+ return;
+ }
+
+ // 글 작성 완료 로직
+ console.log('글 작성 완료');
+ console.log('필수 - 선택된 책:', selectedBook);
+ console.log('필수 - 글 내용:', postContent);
+ console.log('선택 - 선택된 사진:', selectedPhotos);
+ console.log('선택 - 공개 설정:', isPrivate ? '비공개' : '공개');
+ console.log('선택 - 선택된 태그:', selectedTags);
+
+ // TODO: API 호출하여 글 등록
+ // 완료 후 이전 페이지로 이동
+ navigate(-1);
+ };
+
+ const handleBookSearchOpen = () => {
+ setIsBookSearchOpen(true);
+ };
+
+ const handleChangeBook = () => {
+ setIsBookSearchOpen(true);
+ };
+
+ const handleBookSearchClose = () => {
+ setIsBookSearchOpen(false);
+ };
+
+ const handleBookSelect = (book: Book) => {
+ setSelectedBook(book);
+ };
+
+ const handlePhotoAdd = (files: File[]) => {
+ setSelectedPhotos(prev => [...prev, ...files].slice(0, 3)); // 최대 3개까지
+ };
+
+ const handlePhotoRemove = (index: number) => {
+ setSelectedPhotos(prev => prev.filter((_, i) => i !== index));
+ };
+
+ const handlePrivacyToggle = () => {
+ setIsPrivate(!isPrivate);
+ };
+
+ const handleTagToggle = (tag: string) => {
+ setSelectedTags(prev => (prev.includes(tag) ? prev.filter(t => t !== tag) : [...prev, tag]));
+ };
+
+ // 책 선택과 글 내용만 필수, 나머지는 선택사항
+ const isFormValid = !!selectedBook && postContent.trim() !== '';
+
+ return (
+ <>
+ }
+ title="새 글"
+ rightButton="완료"
+ onLeftClick={handleBackClick}
+ onRightClick={handleCompleteClick}
+ isNextActive={isFormValid}
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+};
+
+export default CreatePost;