From 4f51714fe0ebedd43653a3da98ccba27f3771d07 Mon Sep 17 00:00:00 2001 From: ssumanlife Date: Mon, 4 Nov 2024 17:58:48 +0900 Subject: [PATCH] =?UTF-8?q?feat:addTravel=EC=97=90=EC=84=9C=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=ED=95=9C=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20?= =?UTF-8?q?=EC=9E=AC=ED=99=9C=EC=9A=A9=ED=95=98=EC=97=AC=20addForFindGuide?= =?UTF-8?q?=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=9E=91=EC=97=85(#101)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/addTravel/Course.tsx | 8 +- src/components/addTravel/Details.tsx | 8 +- src/components/addTravel/DetailsList.tsx | 6 + src/components/addTravel/FloatingMenu.tsx | 162 +++++++++++------- src/components/addTravel/ScheduleTeam.tsx | 118 +++++++------ .../{useImageUpload.ts => useGetImageUrls.ts} | 4 +- src/hooks/useHandleImageUpload.tsx | 24 +++ src/pages/AddForFindGuide.tsx | 40 +++++ src/pages/AddTravel.tsx | 59 ++----- src/routes/router.tsx | 5 + src/stores/useImageStore.ts | 2 +- src/stores/useSectionsStore.ts | 24 +++ src/utils/prepareImageUpload.ts | 20 +++ 13 files changed, 309 insertions(+), 171 deletions(-) rename src/hooks/{useImageUpload.ts => useGetImageUrls.ts} (85%) create mode 100644 src/hooks/useHandleImageUpload.tsx create mode 100644 src/pages/AddForFindGuide.tsx create mode 100644 src/stores/useSectionsStore.ts create mode 100644 src/utils/prepareImageUpload.ts diff --git a/src/components/addTravel/Course.tsx b/src/components/addTravel/Course.tsx index 1010b6c..6b410a6 100644 --- a/src/components/addTravel/Course.tsx +++ b/src/components/addTravel/Course.tsx @@ -13,8 +13,10 @@ const Course = () => { const handleAddCourse = () => { if (courseRef.current) { - addField('courseList', courseRef.current.value); - courseRef.current.value = ''; + if (courseRef.current.value.trim() !== '') { + addField('courseList', courseRef.current.value); + courseRef.current.value = ''; + } } }; @@ -49,7 +51,7 @@ const Course = () => { onKeyDown={(e) => handleKeyDown(e)} onCompositionStart={() => setIsComposing(true)} onCompositionEnd={() => setIsComposing(false)} - placeholder="40자 내외로 여행 코스를 작성해주세요." + placeholder="40자 내외로 여행 코스를 추가해주세요." /> - - + )} ); }; @@ -125,7 +135,7 @@ const scheduleList = css` border-radius: 8px; background-color: #f8f8f8; margin-bottom: 10px; - padding: 10px 20px; + padding: 20px; & button { color: #888; margin-left: 10px; @@ -133,6 +143,14 @@ const scheduleList = css` :hover { transform: scale(1.2); } + * { + display: block; + } + } + & div { + display: flex; + align-items: center; + justify-content: center; } `; diff --git a/src/hooks/useImageUpload.ts b/src/hooks/useGetImageUrls.ts similarity index 85% rename from src/hooks/useImageUpload.ts rename to src/hooks/useGetImageUrls.ts index 2397010..acad5bd 100644 --- a/src/hooks/useImageUpload.ts +++ b/src/hooks/useGetImageUrls.ts @@ -6,7 +6,7 @@ interface UseImageUploadPM { enabled: boolean; } -const useImageUpload = ({ formData, enabled }: UseImageUploadPM) => { +const useGetImageUrls = ({ formData, enabled }: UseImageUploadPM) => { return useQuery({ queryKey: ['imageUpload', formData], queryFn: async () => { @@ -24,4 +24,4 @@ const useImageUpload = ({ formData, enabled }: UseImageUploadPM) => { enabled, }); }; -export default useImageUpload; +export default useGetImageUrls; diff --git a/src/hooks/useHandleImageUpload.tsx b/src/hooks/useHandleImageUpload.tsx new file mode 100644 index 0000000..e306346 --- /dev/null +++ b/src/hooks/useHandleImageUpload.tsx @@ -0,0 +1,24 @@ +import useGetImageUrls from '@/hooks/useGetImageUrls'; +import { ImageStore } from '@/stores/useImageStore'; +import prepareImageUpload from '@/utils/prepareImageUpload'; +import { useState } from 'react'; + +const useHandleImageUpload = (images: ImageStore) => { + const [enabled, setEnabled] = useState(false); + const formData = new FormData(); + const { data: uploadedImages } = useGetImageUrls({ formData, enabled }); + + const uploadImages = () => { + if (images.thumbnail === '') return; + + setEnabled(true); + prepareImageUpload(images, formData); + if (uploadedImages) { + setEnabled(false); + console.log(uploadedImages); + } + }; + + return { uploadImages }; +}; +export default useHandleImageUpload; diff --git a/src/pages/AddForFindGuide.tsx b/src/pages/AddForFindGuide.tsx new file mode 100644 index 0000000..82018e0 --- /dev/null +++ b/src/pages/AddForFindGuide.tsx @@ -0,0 +1,40 @@ +import FloatingMenu from '@/components/addTravel/FloatingMenu'; +import Introduction from '@/components/addTravel/Introduction'; +import ScheduleTeam from '@/components/addTravel/ScheduleTeam'; +import Thumbnail from '@/components/addTravel/Thumbnail'; +import GrayBack from '@/components/GrayBack'; +import useHandleImageUpload from '@/hooks/useHandleImageUpload'; +import { addTravelWrapper, noneStyleInput, pageLayoutWrapper } from '@/pages/AddTravel'; +import useImageStore from '@/stores/useImageStore'; +import useSectionsStore from '@/stores/useSectionsStore'; +import { useRef } from 'react'; + +const AddForFindGuide = () => { + const sections = useSectionsStore((state) => state.sections); + const images = useImageStore((state) => state.images); + const titleRef = useRef(null); + const { uploadImages } = useHandleImageUpload(images); + + return ( +
+
+

가이드를 찾습니다

+ + + + {sections.includes('대표이미지') && } + + +
+ + +
+ ); +}; + +export default AddForFindGuide; diff --git a/src/pages/AddTravel.tsx b/src/pages/AddTravel.tsx index 2880b17..7f0e896 100644 --- a/src/pages/AddTravel.tsx +++ b/src/pages/AddTravel.tsx @@ -5,48 +5,20 @@ import Details from '@/components/addTravel/Details'; import Thumbnail from '@/components/addTravel/Thumbnail'; import GrayBack from '@/components/GrayBack'; import { css } from '@emotion/react'; -import { useRef, useState } from 'react'; +import { useRef } from 'react'; import Introduction from '@/components/addTravel/Introduction'; import useImageStore from '@/stores/useImageStore'; -import useImageUpload from '@/hooks/useImageUpload'; import FloatingMenu from '@/components/addTravel/FloatingMenu'; +import useSectionsStore from '@/stores/useSectionsStore'; +import useHandleImageUpload from '@/hooks/useHandleImageUpload'; const AddTravel = () => { - const [enabled, setEnabled] = useState(false); - const [openSections, setOpenSections] = useState([]); - + const sections = useSectionsStore((state) => state.sections); + const images = useImageStore((state) => state.images); const titleRef = useRef(null); const priceRef = useRef(null); - const images = useImageStore((state) => state.images); - - const toggleSection = (section: string) => { - setOpenSections((prev) => - prev.includes(section) ? prev.filter((item) => item !== section) : [...prev, section], - ); - }; - const formData = new FormData(); - const { data: uploadedImages } = useImageUpload({ formData, enabled }); - const handleUpload = () => { - if (images.thumbnail === '') return; - setEnabled(true); - const uploadSrc = [images.thumbnail].concat(images.introSrcs); // 0번째는 무조건 썸네일url입니다 - uploadSrc.forEach((src) => { - const [mimeString, base64Data] = src.split(','); - const byteString = atob(base64Data); - const ab = new Uint8Array(byteString.length); - for (let i = 0; i < byteString.length; i++) { - ab[i] = byteString.charCodeAt(i); - } - const file = new Blob([ab], { type: mimeString.split(':')[1].split(';')[0] }); - formData.append('images', file, `image-${Math.random()}.jpg`); - }); - if (uploadedImages) { - // uploadedImages 이미지 배포된 링크들 (0번째 썸네일, 1번째부터는 intro에 삽입한 이미지) - setEnabled(false); - console.log(uploadedImages); - } - }; + const { uploadImages } = useHandleImageUpload(images); return (
@@ -71,25 +43,20 @@ const AddTravel = () => { / 1인 - {openSections.includes('포함내용') &&
} - {openSections.includes('미포함내용') &&
} - {openSections.includes('이용안내') &&
} - {openSections.includes('FAQ') &&
} + {sections.includes('포함내용') &&
} + {sections.includes('미포함내용') &&
} + {sections.includes('이용안내') &&
} + {sections.includes('FAQ') &&
}
- + ); }; export default AddTravel; -// 스타일 정의 -const addTravelWrapper = css` +export const addTravelWrapper = css` position: relative; width: 680px; margin-right: 200px; @@ -109,7 +76,7 @@ export const noneStyleInput = css` } `; -const pageLayoutWrapper = css` +export const pageLayoutWrapper = css` display: flex; position: relative; `; diff --git a/src/routes/router.tsx b/src/routes/router.tsx index 90062ff..522caa2 100644 --- a/src/routes/router.tsx +++ b/src/routes/router.tsx @@ -13,6 +13,7 @@ import MyCreatedTravel from '@/pages/MyCreatedTravel'; // 내가 만든 여행 import TravelDetail from '@/pages/TravelDetail'; import Bookmark from '@/pages/Bookmark'; import MyAccount from '@/pages/MyAccount'; +import AddForFindGuide from '@/pages/AddForFindGuide'; const PATH = { HOME: '/', @@ -103,6 +104,10 @@ const router = createBrowserRouter([ path: 'add-travel', element: , }, + { + path: 'add-for-find-guide', + element: , + }, ], }, ], diff --git a/src/stores/useImageStore.ts b/src/stores/useImageStore.ts index 745f4c7..fadbae1 100644 --- a/src/stores/useImageStore.ts +++ b/src/stores/useImageStore.ts @@ -1,6 +1,6 @@ import { create } from 'zustand'; -interface ImageStore { +export interface ImageStore { thumbnail: string; meetingSpace: string; introSrcs: string[]; diff --git a/src/stores/useSectionsStore.ts b/src/stores/useSectionsStore.ts new file mode 100644 index 0000000..6b74493 --- /dev/null +++ b/src/stores/useSectionsStore.ts @@ -0,0 +1,24 @@ +import { create } from 'zustand'; + +interface State { + sections: string[]; +} + +interface Action { + setOpenSection: (section: string) => void; +} + +const useSectionsStore = create((set) => ({ + sections: [], + setOpenSection: (section: string) => + set((state: State) => { + if (state.sections.includes(section)) { + const filterSections = state.sections.filter((item) => item !== section); + return { sections: filterSections }; + } else { + return { sections: [...state.sections, section] }; + } + }), +})); + +export default useSectionsStore; diff --git a/src/utils/prepareImageUpload.ts b/src/utils/prepareImageUpload.ts new file mode 100644 index 0000000..2bfa77c --- /dev/null +++ b/src/utils/prepareImageUpload.ts @@ -0,0 +1,20 @@ +import { ImageStore } from '@/stores/useImageStore'; + +const prepareImageUpload = (images: ImageStore, formData: FormData) => { + const uploadSrc = [images.thumbnail].concat(images.introSrcs); + + uploadSrc.forEach((src) => { + const [mimeString, base64Data] = src.split(','); + const byteString = atob(base64Data); + const ab = new Uint8Array(byteString.length); + + for (let i = 0; i < byteString.length; i++) { + ab[i] = byteString.charCodeAt(i); + } + + const file = new Blob([ab], { type: mimeString.split(':')[1].split(';')[0] }); + formData.append('images', file, `image-${Math.random()}.jpg`); + }); +}; + +export default prepareImageUpload;