From f99cdc798009ec6562e4534c994c3e7346551fee Mon Sep 17 00:00:00 2001 From: JeongwooSeo <98446924+ShipFriend0516@users.noreply.github.com> Date: Sun, 22 Jun 2025 14:55:10 +0900 Subject: [PATCH 1/7] =?UTF-8?q?fix:=20=EC=9E=84=EC=8B=9C=EC=A0=80=EC=9E=A5?= =?UTF-8?q?=EB=B3=B8=EC=97=90=EC=84=9C=20tag=EB=A5=BC=20=EC=A0=9C=EC=99=B8?= =?UTF-8?q?=ED=95=B4=EC=84=9C=20=EA=B0=80=EC=A0=B8=EC=98=A4=EB=8D=98=20?= =?UTF-8?q?=EB=AC=B8=EC=A0=9C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/entities/post/write/BlogForm.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/entities/post/write/BlogForm.tsx b/app/entities/post/write/BlogForm.tsx index db583b8..b0ebdb9 100644 --- a/app/entities/post/write/BlogForm.tsx +++ b/app/entities/post/write/BlogForm.tsx @@ -122,13 +122,14 @@ const BlogForm = () => { const overwriteDraft = () => { if (draft !== null) { if (confirm('임시 저장된 글이 있습니다. 덮어쓰시겠습니까?')) { - const { title, content, subTitle, seriesId, isPrivate } = draft; + const { title, content, subTitle, seriesId, isPrivate, tags } = draft; setTitle(title || ''); setContent(content); setSubTitle(subTitle || ''); setSeriesId(seriesId); setUploadedImages(draftImages || []); setIsPrivate(isPrivate || false); + setTags(tags || []); } } else { toast.error('임시 저장된 글이 없습니다.'); From 666c7d3e7f514031e57192f086c7606a65db1135 Mon Sep 17 00:00:00 2001 From: JeongwooSeo <98446924+ShipFriend0516@users.noreply.github.com> Date: Sun, 22 Jun 2025 15:01:19 +0900 Subject: [PATCH 2/7] =?UTF-8?q?feat:=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=EC=97=85=EB=A1=9C=EB=93=9C=202=EA=B0=9C=20=EC=9D=B4=EC=83=81?= =?UTF-8?q?=20=ED=95=9C=EB=B2=88=EC=97=90=20=EC=A7=80=EC=9B=90=ED=95=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EA=B8=B0=EB=8A=A5=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../post/write/UploadImageContainer.tsx | 32 +++++++++++++------ 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/app/entities/post/write/UploadImageContainer.tsx b/app/entities/post/write/UploadImageContainer.tsx index 9266d42..088d41f 100644 --- a/app/entities/post/write/UploadImageContainer.tsx +++ b/app/entities/post/write/UploadImageContainer.tsx @@ -2,12 +2,12 @@ import UploadedImage from '@/app/entities/post/write/UploadedImage'; import { FaImage } from 'react-icons/fa'; import { upload } from '@vercel/blob/client'; -import { ChangeEvent, useState } from 'react'; +import { ChangeEvent, Dispatch, SetStateAction, useState } from 'react'; interface UploadImageContainerProps { onClick: (link: string) => void; uploadedImages: string[]; - setUploadedImages: (images: string[]) => void; + setUploadedImages: Dispatch>; } const UploadImageContainer = ({ onClick, @@ -22,16 +22,28 @@ const UploadImageContainer = ({ throw new Error('이미지가 선택되지 않았습니다.'); } - const file = target.files[0]; + const files = target.files; - const timestamp = new Date().getTime(); - const pathname = `/images/${timestamp}-${file.name}`; - const newBlob = await upload(pathname, file, { - access: 'public', - handleUploadUrl: '/api/upload', - }); + if (files.length === 0) { + throw new Error('업로드할 파일이 없습니다.'); + } + + for (let i = 0; i < files.length; i++) { + const file = files[i]; + if (!file.type.startsWith('image/')) { + throw new Error('이미지 파일만 업로드할 수 있습니다.'); + } + + const timestamp = new Date().getTime(); + const pathname = `/images/${timestamp}-${file.name}`; + const newBlob = await upload(pathname, file, { + access: 'public', + handleUploadUrl: '/api/upload', + }); + + setUploadedImages((prev) => [...prev, newBlob.url]); + } - setUploadedImages([...uploadedImages, newBlob.url]); return; } catch (error) { console.error('업로드 실패:', error); From f5c5c3a459e879efa366db002aa91bca7e4ac027 Mon Sep 17 00:00:00 2001 From: JeongwooSeo <98446924+ShipFriend0516@users.noreply.github.com> Date: Sun, 22 Jun 2025 15:13:23 +0900 Subject: [PATCH 3/7] =?UTF-8?q?refactor:=20=EB=B8=94=EB=A1=9C=EA=B7=B8=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1=20=EA=B4=80=EB=A0=A8=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?hook=EC=9C=BC=EB=A1=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/entities/post/write/BlogForm.tsx | 190 ++++--------------------- app/hooks/post/usePost.ts | 198 +++++++++++++++++++++++++++ 2 files changed, 225 insertions(+), 163 deletions(-) create mode 100644 app/hooks/post/usePost.ts diff --git a/app/entities/post/write/BlogForm.tsx b/app/entities/post/write/BlogForm.tsx index b0ebdb9..e79dd20 100644 --- a/app/entities/post/write/BlogForm.tsx +++ b/app/entities/post/write/BlogForm.tsx @@ -1,190 +1,54 @@ 'use client'; import '@uiw/react-md-editor/markdown-editor.css'; import '@uiw/react-markdown-preview/markdown.css'; -import { useEffect, useState } from 'react'; +import { useState } from 'react'; import dynamic from 'next/dynamic'; -import { PostBody } from '@/app/types/Post'; -import { StaticImport } from 'next/dist/shared/lib/get-img-props'; -import axios from 'axios'; import useToast from '@/app/hooks/useToast'; import { useBlockNavigate } from '@/app/hooks/common/useBlockNavigate'; import { useRouter, useSearchParams } from 'next/navigation'; import PostWriteButtons from '@/app/entities/post/write/PostWriteButtons'; -import { validatePost } from '@/app/lib/utils/validate/validate'; -import { Series } from '@/app/types/Series'; import Overlay from '@/app/entities/common/Overlay/Overlay'; import CreateSeriesOverlayContainer from '@/app/entities/series/CreateSeriesOverlayContainer'; -import { getAllSeriesData } from '@/app/entities/series/api/series'; import UploadImageContainer from '@/app/entities/post/write/UploadImageContainer'; -import useDraft from '@/app/hooks/post/useDraft'; import PostMetadataForm from '@/app/entities/post/write/PostMetadataForm'; +import usePost from '@/app/hooks/post/usePost'; const MDEditor = dynamic(() => import('@uiw/react-md-editor'), { ssr: false }); const BlogForm = () => { const params = useSearchParams(); const slug = params.get('slug'); - const [submitLoading, setSubmitLoading] = useState(false); - const [title, setTitle] = useState(''); - const [subTitle, setSubTitle] = useState(''); - const [content, setContent] = useState(''); - const [profileImage, setProfileImage] = useState(); - const [thumbnailImage, setThumbnailImage] = useState(); - const [seriesList, setSeriesList] = useState([]); - const [seriesId, setSeriesId] = useState(); - const [seriesLoading, setSeriesLoading] = useState(true); - const [errors, setErrors] = useState([]); - const [tags, setTags] = useState([]); - const [isPrivate, setIsPrivate] = useState(false); - const toast = useToast(); - const router = useRouter(); - const NICKNAME = '개발자 서정우'; - const [createSeriesOpen, setCreateSeriesOpen] = useState(false); - // 임시저장 상태 - const { draft, draftImages, updateDraft, clearDraft } = useDraft(); - // 이미지 상태 - const [uploadedImages, setUploadedImages] = useState([]); - - const postBody: PostBody = { + const { title, subTitle, - author: NICKNAME, - content: content || '', - profileImage, - thumbnailImage, - seriesId: seriesId || '', - tags: tags, - isPrivate: isPrivate, - }; + submitLoading, + seriesLoading, + seriesId, + seriesList, + content, + setTitle, + setSubTitle, + setContent, + setSeriesId, + setIsPrivate, + isPrivate, + tags, + setTags, + uploadedImages, + setUploadedImages, + overwriteDraft, + saveToDraft, + clearDraftInStore, + submitHandler, + postBody, + errors, + handleLinkCopy, + } = usePost(slug || ''); + const [createSeriesOpen, setCreateSeriesOpen] = useState(false); useBlockNavigate({ title, content: content || '' }); - useEffect(() => { - getSeries(); - }, []); - - useEffect(() => { - if (slug) { - getPostDetail(); - } - }, [slug]); - - // 시리즈 - const getSeries = async () => { - try { - const data = await getAllSeriesData(); - setSeriesList(data); - setSeriesId(data[0]._id); - setSeriesLoading(false); - } catch (e) { - console.error('시리즈 조회 중 오류 발생', e); - } - }; - - // 블로그 - const postBlog = async (post: PostBody) => { - try { - const response = await axios.post('/api/posts', post); - if (response.status === 201) { - toast.success('글이 성공적으로 발행되었습니다.'); - router.push('/posts'); - } - } catch (e) { - toast.error('글 발행 중 오류 발생했습니다.'); - console.error('글 발행 중 오류 발생', e); - } - }; - - const updatePost = async (post: PostBody) => { - try { - const response = await axios.put(`/api/posts/${slug}`, post); - if (response.status === 200) { - toast.success('글이 성공적으로 수정되었습니다.'); - router.push('/posts'); - } - } catch (e) { - toast.error('글 수정 중 오류 발생했습니다.'); - console.error('글 수정 중 오류 발생', e); - } - }; - - // 임시저장 관련 함수 - const saveToDraft = () => { - const { success } = updateDraft(postBody, uploadedImages); - if (success) { - toast.success('임시 저장되었습니다.'); - } else { - toast.error('임시 저장 실패'); - } - }; - - const overwriteDraft = () => { - if (draft !== null) { - if (confirm('임시 저장된 글이 있습니다. 덮어쓰시겠습니까?')) { - const { title, content, subTitle, seriesId, isPrivate, tags } = draft; - setTitle(title || ''); - setContent(content); - setSubTitle(subTitle || ''); - setSeriesId(seriesId); - setUploadedImages(draftImages || []); - setIsPrivate(isPrivate || false); - setTags(tags || []); - } - } else { - toast.error('임시 저장된 글이 없습니다.'); - } - }; - - const clearDraftInStore = () => { - clearDraft(); - toast.success('임시 저장이 삭제되었습니다.'); - }; - - const submitHandler = (post: PostBody) => { - try { - setSubmitLoading(true); - const { isValid, errors } = validatePost(post); - setErrors(errors); - if (!isValid) { - toast.error('유효성 검사 실패'); - console.error('유효성 검사 실패', errors); - setSubmitLoading(false); - return; - } - - if (slug) { - updatePost(post); - } else { - postBlog(post); - } - clearDraft(); - } catch (e) { - console.error('글 발행 중 오류 발생', e); - setSubmitLoading(false); - } - }; - - const getPostDetail = async () => { - try { - const response = await axios.get(`/api/posts/${slug}`); - const data = await response.data; - setTitle(data.post.title || ''); - setSubTitle(data.post.subTitle); - setContent(data.post.content); - setSeriesId(data.post.seriesId || ''); - setTags(data.post.tags || []); - setIsPrivate(data.post.isPrivate || false); - } catch (e) { - console.error('글 조회 중 오류 발생', e); - } - }; - - const handleLinkCopy = (image: string) => { - navigator.clipboard.writeText(image); - toast.success('이미지 링크가 복사되었습니다.'); - }; - return (
{ + const [submitLoading, setSubmitLoading] = useState(false); + const [title, setTitle] = useState(''); + const [subTitle, setSubTitle] = useState(''); + const [content, setContent] = useState(''); + const [profileImage, setProfileImage] = useState(); + const [thumbnailImage, setThumbnailImage] = useState(); + const [seriesList, setSeriesList] = useState([]); + const [seriesId, setSeriesId] = useState(); + const [seriesLoading, setSeriesLoading] = useState(true); + const [errors, setErrors] = useState([]); + const [tags, setTags] = useState([]); + const [isPrivate, setIsPrivate] = useState(false); + const [uploadedImages, setUploadedImages] = useState([]); + + const NICKNAME = '개발자 서정우'; + const toast = useToast(); + const router = useRouter(); + const { draft, draftImages, updateDraft, clearDraft } = useDraft(); + + const postBody: PostBody = { + title, + subTitle, + author: NICKNAME, + content: content || '', + profileImage, + thumbnailImage, + seriesId: seriesId || '', + tags: tags, + isPrivate: isPrivate, + }; + + useEffect(() => { + getSeries(); + }, []); + + useEffect(() => { + if (slug) { + getPostDetail(); + } + }, [slug]); + + // 시리즈 + const getSeries = async () => { + try { + const data = await getAllSeriesData(); + setSeriesList(data); + setSeriesId(data[0]._id); + setSeriesLoading(false); + } catch (e) { + console.error('시리즈 조회 중 오류 발생', e); + } + }; + + // Method + const postBlog = async (post: PostBody) => { + try { + const response = await axios.post('/api/posts', post); + if (response.status === 201) { + toast.success('글이 성공적으로 발행되었습니다.'); + router.push('/posts'); + } + } catch (e) { + toast.error('글 발행 중 오류 발생했습니다.'); + console.error('글 발행 중 오류 발생', e); + } + }; + + const updatePost = async (post: PostBody) => { + try { + const response = await axios.put(`/api/posts/${slug}`, post); + if (response.status === 200) { + toast.success('글이 성공적으로 수정되었습니다.'); + router.push('/posts'); + } + } catch (e) { + toast.error('글 수정 중 오류 발생했습니다.'); + console.error('글 수정 중 오류 발생', e); + } + }; + + // 임시저장 관련 함수 + const saveToDraft = () => { + const { success } = updateDraft(postBody, uploadedImages); + if (success) { + toast.success('임시 저장되었습니다.'); + } else { + toast.error('임시 저장 실패'); + } + }; + + const overwriteDraft = () => { + if (draft !== null) { + if (confirm('임시 저장된 글이 있습니다. 덮어쓰시겠습니까?')) { + const { title, content, subTitle, seriesId, isPrivate, tags } = draft; + setTitle(title || ''); + setContent(content); + setSubTitle(subTitle || ''); + setSeriesId(seriesId); + setUploadedImages(draftImages || []); + setIsPrivate(isPrivate || false); + setTags(tags || []); + } + } else { + toast.error('임시 저장된 글이 없습니다.'); + } + }; + + const clearDraftInStore = () => { + clearDraft(); + toast.success('임시 저장이 삭제되었습니다.'); + }; + + const submitHandler = (post: PostBody) => { + try { + setSubmitLoading(true); + const { isValid, errors } = validatePost(post); + setErrors(errors); + if (!isValid) { + toast.error('유효성 검사 실패'); + console.error('유효성 검사 실패', errors); + setSubmitLoading(false); + return; + } + + if (slug) { + updatePost(post); + } else { + postBlog(post); + } + clearDraft(); + } catch (e) { + console.error('글 발행 중 오류 발생', e); + setSubmitLoading(false); + } + }; + + const getPostDetail = async () => { + try { + const response = await axios.get(`/api/posts/${slug}`); + const data = await response.data; + setTitle(data.post.title || ''); + setSubTitle(data.post.subTitle); + setContent(data.post.content); + setSeriesId(data.post.seriesId || ''); + setTags(data.post.tags || []); + setIsPrivate(data.post.isPrivate || false); + } catch (e) { + console.error('글 조회 중 오류 발생', e); + } + }; + + const handleLinkCopy = (image: string) => { + navigator.clipboard.writeText(image); + toast.success('이미지 링크가 복사되었습니다.'); + }; + + return { + title, + subTitle, + content, + seriesId, + isPrivate, + tags, + submitLoading, + postBody, + seriesLoading, + seriesList, + uploadedImages, + setUploadedImages, + setTitle, + setSubTitle, + setContent, + setSeriesId, + setIsPrivate, + setTags, + overwriteDraft, + saveToDraft, + clearDraftInStore, + submitHandler, + handleLinkCopy, + errors, + }; +}; + +export default usePost; From 5f8cae6d8226ce5ff5ce0f53163e59269cd5ad31 Mon Sep 17 00:00:00 2001 From: JeongwooSeo <98446924+ShipFriend0516@users.noreply.github.com> Date: Sun, 22 Jun 2025 15:15:03 +0900 Subject: [PATCH 4/7] =?UTF-8?q?refactor:=20=EB=B8=94=EB=A1=9C=EA=B7=B8=20v?= =?UTF-8?q?alidation=20=EC=98=A4=EB=A5=98=20=EB=B0=95=EC=8A=A4=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/entities/post/write/BlogForm.tsx | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/app/entities/post/write/BlogForm.tsx b/app/entities/post/write/BlogForm.tsx index e79dd20..3e0c1ac 100644 --- a/app/entities/post/write/BlogForm.tsx +++ b/app/entities/post/write/BlogForm.tsx @@ -3,9 +3,8 @@ import '@uiw/react-md-editor/markdown-editor.css'; import '@uiw/react-markdown-preview/markdown.css'; import { useState } from 'react'; import dynamic from 'next/dynamic'; -import useToast from '@/app/hooks/useToast'; import { useBlockNavigate } from '@/app/hooks/common/useBlockNavigate'; -import { useRouter, useSearchParams } from 'next/navigation'; +import { useSearchParams } from 'next/navigation'; import PostWriteButtons from '@/app/entities/post/write/PostWriteButtons'; import Overlay from '@/app/entities/common/Overlay/Overlay'; import CreateSeriesOverlayContainer from '@/app/entities/series/CreateSeriesOverlayContainer'; @@ -92,15 +91,7 @@ const BlogForm = () => { setUploadedImages={setUploadedImages} onClick={handleLinkCopy} /> - {errors && ( -
- {errors.slice(0, 3).map((error, index) => ( -

- {error} -

- ))} -
- )} + {
); }; + +const ErrorBox = ({ errors }: { errors: string[] | null }) => { + if (!errors) return null; + + return ( +
+ {errors.slice(0, 3).map((error, index) => ( +

+ {error} +

+ ))} +
+ ); +}; export default BlogForm; From 47ade412391e912e495e47d3b4091a38036eb42b Mon Sep 17 00:00:00 2001 From: JeongwooSeo Date: Sun, 22 Jun 2025 16:05:36 +0900 Subject: [PATCH 5/7] =?UTF-8?q?refactor:=20search=20params=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=EC=9D=84=20=EB=8F=84=EC=99=80=EC=A3=BC=EB=8A=94=20?= =?UTF-8?q?=ED=9B=85=20=EA=B5=AC=ED=98=84=20=EB=B0=8F=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/hooks/common/useURLSync.ts | 36 ++++++++++++++++++++++++++++++++++ app/posts/page.tsx | 14 ++++++++----- 2 files changed, 45 insertions(+), 5 deletions(-) create mode 100644 app/hooks/common/useURLSync.ts diff --git a/app/hooks/common/useURLSync.ts b/app/hooks/common/useURLSync.ts new file mode 100644 index 0000000..e358553 --- /dev/null +++ b/app/hooks/common/useURLSync.ts @@ -0,0 +1,36 @@ +import { useEffect, useState } from 'react'; +import { useRouter } from 'next/navigation'; + +interface useURLSyncConfig { + baseURL: string; + params: Record | Record; +} + +/** + * 서치파라미터를 쉽게 설정할 수 있는 훅을 만들어봅시다. + * example /posts?page=1&series=seriesSlug&query=query + * 필요한 파라미터는 baseURL, currentPage, seriesSlugParam, query입니다. + * @param baseURL + * @param params + */ + +const useURLSync = ({ baseURL, params }: useURLSyncConfig) => { + const router = useRouter(); + + useEffect(() => { + const searchParams = new URLSearchParams(); + if (params) { + Object.entries(params).forEach(([key, value]) => { + if (value !== undefined && value !== null) { + searchParams.set(key, String(value)); + } + }); + } + const finalUrl = `/${baseURL}?${searchParams.toString()}`; + router.push(finalUrl); + }, [...Object.values(params)]); + + return {}; +}; + +export default useURLSync; diff --git a/app/posts/page.tsx b/app/posts/page.tsx index 1b7bfb3..8cce3f0 100644 --- a/app/posts/page.tsx +++ b/app/posts/page.tsx @@ -11,6 +11,7 @@ import useDataFetch, { } from '@/app/hooks/common/useDataFetch'; import useDebounce from '@/app/hooks/optimize/useDebounce'; import ErrorBox from '../entities/common/Error/ErrorBox'; +import useURLSync from '@/app/hooks/common/useURLSync'; interface PaginationData { totalPosts: number; @@ -57,11 +58,14 @@ const BlogList = () => { const posts = data?.posts || []; - useEffect(() => { - router.push( - `/posts?page=${currentPage}${seriesSlugParam ? `&series=${seriesSlugParam}` : ''}${query ? `&query=${query}` : ''}` - ); - }, [currentPage, seriesSlugParam, query]); + useURLSync({ + baseURL: 'posts', + params: { + page: currentPage, + series: seriesSlugParam, + query: query, + }, + }); if (error) { console.error('Error fetching posts:', error); From 196cb54c5be393ace4aa2591bd0b0b0612e30118 Mon Sep 17 00:00:00 2001 From: JeongwooSeo <98446924+ShipFriend0516@users.noreply.github.com> Date: Sun, 22 Jun 2025 16:22:58 +0900 Subject: [PATCH 6/7] =?UTF-8?q?refactor:=20=EB=B8=94=EB=A1=9C=EA=B7=B8=20?= =?UTF-8?q?=EA=B2=80=EC=83=89=20=EB=A1=9C=EC=A7=81=20=ED=9B=85=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/hooks/post/usePostSearch.ts | 12 ++++++++++++ app/posts/page.tsx | 12 ++++++------ 2 files changed, 18 insertions(+), 6 deletions(-) create mode 100644 app/hooks/post/usePostSearch.ts diff --git a/app/hooks/post/usePostSearch.ts b/app/hooks/post/usePostSearch.ts new file mode 100644 index 0000000..3077d7a --- /dev/null +++ b/app/hooks/post/usePostSearch.ts @@ -0,0 +1,12 @@ +import { useState } from 'react'; +import useSearchQueryStore from '@/app/stores/useSearchQueryStore'; +import useDebounce from '@/app/hooks/optimize/useDebounce'; + +const usePostSearch = () => { + const [query, setQuery] = useState(''); + const addLatestQuery = useSearchQueryStore((state) => state.addSearchQuery); + const debouncedQuery = useDebounce(query, 300); + + return { query, setQuery, addLatestQuery, debouncedQuery }; +}; +export default usePostSearch; diff --git a/app/posts/page.tsx b/app/posts/page.tsx index 8cce3f0..88f1187 100644 --- a/app/posts/page.tsx +++ b/app/posts/page.tsx @@ -1,32 +1,32 @@ 'use client'; -import { useEffect, useMemo, useState } from 'react'; +import { useMemo, useState } from 'react'; import { Post } from '@/app/types/Post'; import PostList from '@/app/entities/post/list/PostList'; import SearchSection from '@/app/entities/post/list/SearchSection'; -import useSearchQueryStore from '@/app/stores/useSearchQueryStore'; import { useRouter, useSearchParams } from 'next/navigation'; import Pagination from '@/app/entities/common/Pagination'; import useDataFetch, { useDataFetchConfig, } from '@/app/hooks/common/useDataFetch'; -import useDebounce from '@/app/hooks/optimize/useDebounce'; import ErrorBox from '../entities/common/Error/ErrorBox'; import useURLSync from '@/app/hooks/common/useURLSync'; +import usePostSearch from '@/app/hooks/post/usePostSearch'; interface PaginationData { totalPosts: number; } const BlogList = () => { - const [query, setQuery] = useState(''); - const addLatestQuery = useSearchQueryStore((state) => state.addSearchQuery); + const { query, debouncedQuery, setQuery, addLatestQuery } = usePostSearch(); + const router = useRouter(); const searchParams = useSearchParams(); const seriesSlugParam = searchParams.get('series'); const currentPage = Number(searchParams.get('page')) || 1; + const [totalPosts, setTotalPosts] = useState(0); const ITEMS_PER_PAGE = 12; - const debouncedQuery = useDebounce(query, 300); + const config = useMemo((): useDataFetchConfig => { return { url: `/api/posts`, From 5bdcf545037ad9dc72b5cd982b332904dab19fdf Mon Sep 17 00:00:00 2001 From: JeongwooSeo <98446924+ShipFriend0516@users.noreply.github.com> Date: Sun, 22 Jun 2025 16:34:02 +0900 Subject: [PATCH 7/7] =?UTF-8?q?chore:=20eslint=20json=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .eslintrc.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index bd2c4cf..b9be207 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -3,7 +3,6 @@ "rules": { "no-unused-vars": "off", "@typescript-eslint/no-unused-vars": "warn", - "@typescript-eslint/no-explicit-any": "warn", - + "@typescript-eslint/no-explicit-any": "warn" } }