From dd3d231f29b5371472785d17e1d1e683590cf028 Mon Sep 17 00:00:00 2001 From: JeongwooSeo Date: Wed, 25 Dec 2024 01:19:55 +0900 Subject: [PATCH 01/33] =?UTF-8?q?feat:=20=EC=8B=9C=EB=A6=AC=EC=A6=88=20?= =?UTF-8?q?=EC=8A=A4=ED=82=A4=EB=A7=88=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/Series.ts | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 app/models/Series.ts diff --git a/app/models/Series.ts b/app/models/Series.ts new file mode 100644 index 0000000..cf541ed --- /dev/null +++ b/app/models/Series.ts @@ -0,0 +1,21 @@ +import { Schema, model, models } from 'mongoose'; + +const seriesSchema = new Schema( + { + slug: { type: String, required: true, unique: true }, + title: { type: String, required: true }, + description: { type: String, required: false }, + date: { type: Number, required: true, default: () => Date.now() }, + posts: [{ type: Schema.Types.ObjectId, ref: 'Post' }], + order: [{ type: String }], + postCount: { type: Number, required: true, default: 0 }, + thumbnailImage: { type: String, required: false, default: '' }, + }, + { + timestamps: true, // createdAt, updatedAt 자동 생성 + } +); + +const Series = models.Series || model('Series', seriesSchema); + +export default Series; From a0d6fce582b58e599c5396e43814cf5951ab33a2 Mon Sep 17 00:00:00 2001 From: JeongwooSeo Date: Wed, 25 Dec 2024 01:20:15 +0900 Subject: [PATCH 02/33] =?UTF-8?q?feat:=20Post=20=EC=8A=A4=ED=82=A4?= =?UTF-8?q?=EB=A7=88=EC=97=90=20=ED=83=9C=EA=B7=B8=20=EB=B0=8F=20=EC=8B=9C?= =?UTF-8?q?=EB=A6=AC=EC=A6=88=20=EC=95=84=EC=9D=B4=EB=94=94=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/Post.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/app/models/Post.ts b/app/models/Post.ts index d04eebb..2d0ed99 100644 --- a/app/models/Post.ts +++ b/app/models/Post.ts @@ -46,6 +46,16 @@ const postSchema = new Schema( required: false, default: '', }, + seriesId: { + type: Schema.Types.ObjectId, + ref: 'Series', + required: false, + }, + tags: { + type: [String], + required: false, + default: [], + }, }, { timestamps: true, // createdAt, updatedAt 자동 생성 From bd0a1eab394c30e3b67a1b3794d4d9e581d83fd2 Mon Sep 17 00:00:00 2001 From: JeongwooSeo Date: Wed, 25 Dec 2024 03:11:52 +0900 Subject: [PATCH 03/33] =?UTF-8?q?feat:=20Select=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/entities/common/Select.tsx | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 app/entities/common/Select.tsx diff --git a/app/entities/common/Select.tsx b/app/entities/common/Select.tsx new file mode 100644 index 0000000..aa596f7 --- /dev/null +++ b/app/entities/common/Select.tsx @@ -0,0 +1,29 @@ +interface SelectOption { + value: T; + label: string; +} +interface SelectProps { + options: SelectOption[]; + defaultValue: T; + setValue: (value: T) => void; +} +const Select = ({ + options, + defaultValue, + setValue, +}: SelectProps) => { + return ( + + ); +}; +export default Select; From e49c860036d8f48e721f5629579768ffe34ccdf2 Mon Sep 17 00:00:00 2001 From: JeongwooSeo Date: Wed, 25 Dec 2024 03:12:22 +0900 Subject: [PATCH 04/33] =?UTF-8?q?feat:=20=EC=8B=9C=EB=A6=AC=EC=A6=88=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20=EB=B0=8F=20=EC=84=A0=ED=83=9D=20ui=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=20=EB=B0=8F=20=EB=B2=84=ED=8A=BC=EB=93=A4=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=EB=A1=9C=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/entities/post/write/BlogForm.tsx | 116 +++++++++---------- app/entities/post/write/PostWriteButtons.tsx | 43 +++++++ 2 files changed, 95 insertions(+), 64 deletions(-) create mode 100644 app/entities/post/write/PostWriteButtons.tsx diff --git a/app/entities/post/write/BlogForm.tsx b/app/entities/post/write/BlogForm.tsx index 0907920..4014f1d 100644 --- a/app/entities/post/write/BlogForm.tsx +++ b/app/entities/post/write/BlogForm.tsx @@ -3,14 +3,16 @@ import '@uiw/react-md-editor/markdown-editor.css'; import '@uiw/react-markdown-preview/markdown.css'; import { useEffect, useState } from 'react'; import dynamic from 'next/dynamic'; -import Link from 'next/link'; import { PostBody } from '@/app/types/Post'; import { StaticImport } from 'next/dist/shared/lib/get-img-props'; -import LoadingSpinner from '@/app/entities/common/Loading/LoadingSpinner'; import axios from 'axios'; import useToast from '@/app/hooks/useToast'; import { useBlockNavigate } from '@/app/hooks/useBlockNavigate'; import { useRouter, useSearchParams } from 'next/navigation'; +import PostWriteButtons from '@/app/entities/post/write/PostWriteButtons'; +import { validatePost } from '@/app/lib/utils/validate/validate'; +import Select from '@/app/entities/common/Select'; +import { Series } from '@/app/types/Series'; const MDEditor = dynamic(() => import('@uiw/react-md-editor'), { ssr: false }); @@ -23,10 +25,11 @@ const BlogForm = () => { const [content, setContent] = useState(''); const [profileImage, setProfileImage] = useState(); const [thumbnailImage, setThumbnailImage] = useState(); + const [series, setSeries] = useState(null); + const [newSeries, setNewSeries] = useState(null); const [errors, setErrors] = useState([]); const toast = useToast(); const router = useRouter(); - const buttonStyle = `font-bold py-2 px-4 rounded mr-2 disabled:bg-opacity-75 `; const NICKNAME = '개발자 서정우'; useBlockNavigate({ title, content: content || '' }); @@ -72,50 +75,11 @@ const BlogForm = () => { } }; - const validatePost = ( - post: PostBody - ): { isValid: boolean; errors: string[] } => { - const errors: string[] = []; - - // 필수 필드 검사 - if (!post.title?.trim()) { - errors.push('제목은 필수입니다'); - } - if (!post.content?.trim()) { - errors.push('내용은 필수입니다'); - } - - // 길이 제한 검사 - if (post.title && post.title.length > 100) { - errors.push('제목은 100자를 초과할 수 없습니다'); - } - if (post.subTitle && post.subTitle.length > 200) { - errors.push('부제목은 200자를 초과할 수 없습니다'); - } - if (post.content && post.content.length > 50000) { - errors.push('내용은 20000자를 초과할 수 없습니다'); - } - - // 최소 길이 검사 - if (post.title && post.title.length < 2) { - errors.push('제목은 최소 2자 이상이어야 합니다'); - } - - if (post.content && post.content.length < 10) { - errors.push('내용은 최소 10자 이상이어야 합니다'); - } - - setErrors(errors); - return { - isValid: errors.length === 0, - errors, - }; - }; - const submitHandler = (post: PostBody) => { try { setSubmitLoading(true); const { isValid, errors } = validatePost(post); + setErrors(errors); if (!isValid) { toast.error('유효성 검사 실패'); console.error('유효성 검사 실패', errors); @@ -146,6 +110,23 @@ const BlogForm = () => { } }; + const seriesMock: Series[] = [ + { + _id: '1', + title: '블로그 개발기', + slug: '블로그-개발기', + description: 'Nextjs로 만드는 나만의 블로그', + posts: ['nextjs로-블로그-만들기'], + order: ['nextjs로-블로그-만들기'], + postCount: 1, + thumbnailImage: + 'https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbvXERD%2FbtsLpc0LWRA%2Fj66Mq1K3kYYbYnp704XIT1%2Fimg.webp', + date: new Date().getTime(), + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + }, + ]; + return (
{ onChange={(e) => setSubTitle(e.target.value)} value={subTitle} /> +
+
+
{ ))} )} -
- - - - - -
+ ); }; diff --git a/app/entities/post/write/PostWriteButtons.tsx b/app/entities/post/write/PostWriteButtons.tsx new file mode 100644 index 0000000..f1d8432 --- /dev/null +++ b/app/entities/post/write/PostWriteButtons.tsx @@ -0,0 +1,43 @@ +import Link from 'next/link'; +import LoadingSpinner from '@/app/entities/common/Loading/LoadingSpinner'; +import { PostBody } from '@/app/types/Post'; + +interface PostWriteButtonsProps { + slug: string | null; + postBody: PostBody; + submitLoading: boolean; + submitHandler: (postBody: PostBody) => void; +} +const PostWriteButtons = ({ + slug, + submitLoading, + submitHandler, + postBody, +}: PostWriteButtonsProps) => { + const buttonStyle = `font-bold py-2 px-4 rounded mr-2 disabled:bg-opacity-75 `; + return ( +
+ + + + + +
+ ); +}; + +export default PostWriteButtons; From 8439df51fb69f5591f58dad491d1ab990833a786 Mon Sep 17 00:00:00 2001 From: JeongwooSeo Date: Wed, 25 Dec 2024 03:12:33 +0900 Subject: [PATCH 05/33] =?UTF-8?q?refactor:=20validate=ED=95=A8=EC=88=98=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/lib/utils/validate/validate.ts | 40 ++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 app/lib/utils/validate/validate.ts diff --git a/app/lib/utils/validate/validate.ts b/app/lib/utils/validate/validate.ts new file mode 100644 index 0000000..e49dcd1 --- /dev/null +++ b/app/lib/utils/validate/validate.ts @@ -0,0 +1,40 @@ +import { PostBody } from '@/app/types/Post'; + +export const validatePost = ( + post: PostBody +): { isValid: boolean; errors: string[] } => { + const errors: string[] = []; + + // 필수 필드 검사 + if (!post.title?.trim()) { + errors.push('제목은 필수입니다'); + } + if (!post.content?.trim()) { + errors.push('내용은 필수입니다'); + } + + // 길이 제한 검사 + if (post.title && post.title.length > 100) { + errors.push('제목은 100자를 초과할 수 없습니다'); + } + if (post.subTitle && post.subTitle.length > 200) { + errors.push('부제목은 200자를 초과할 수 없습니다'); + } + if (post.content && post.content.length > 50000) { + errors.push('내용은 20000자를 초과할 수 없습니다'); + } + + // 최소 길이 검사 + if (post.title && post.title.length < 2) { + errors.push('제목은 최소 2자 이상이어야 합니다'); + } + + if (post.content && post.content.length < 10) { + errors.push('내용은 최소 10자 이상이어야 합니다'); + } + + return { + isValid: errors.length === 0, + errors, + }; +}; From a86f25f803cdc942ca6afa2562ae814e45150085 Mon Sep 17 00:00:00 2001 From: JeongwooSeo Date: Wed, 25 Dec 2024 03:12:41 +0900 Subject: [PATCH 06/33] =?UTF-8?q?feat:=20=EC=8B=9C=EB=A6=AC=EC=A6=88=20?= =?UTF-8?q?=ED=83=80=EC=9E=85=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/types/Series.d.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 app/types/Series.d.ts diff --git a/app/types/Series.d.ts b/app/types/Series.d.ts new file mode 100644 index 0000000..afbed49 --- /dev/null +++ b/app/types/Series.d.ts @@ -0,0 +1,13 @@ +export interface Series { + _id: string; + slug: string; + title: string; + description: string; + date: number; + posts: string[]; + order?: string[]; + postCount: number; + thumbnailImage?: string; + createdAt: string; + updatedAt: string; +} From 512e902eb9c3a11ead1c2ef0bf9ea95f10291c0f Mon Sep 17 00:00:00 2001 From: JeongwooSeo Date: Wed, 25 Dec 2024 03:21:00 +0900 Subject: [PATCH 07/33] =?UTF-8?q?chore:=20any=20=ED=83=80=EC=9E=85=20?= =?UTF-8?q?=EA=B2=BD=EA=B3=A0=20=EC=88=98=EC=A4=80=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EB=82=B4=EB=A6=AC=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .eslintrc.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.eslintrc.json b/.eslintrc.json index b07d60f..b9be207 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -2,6 +2,7 @@ "extends": ["next/core-web-vitals", "next/typescript"], "rules": { "no-unused-vars": "off", - "@typescript-eslint/no-unused-vars": "warn" + "@typescript-eslint/no-unused-vars": "warn", + "@typescript-eslint/no-explicit-any": "warn" } } From 73f854402364884761d3b027fac578df70b34128 Mon Sep 17 00:00:00 2001 From: JeongwooSeo Date: Wed, 25 Dec 2024 03:21:16 +0900 Subject: [PATCH 08/33] =?UTF-8?q?feat:=20series=20=EA=B4=80=EB=A0=A8=20CRU?= =?UTF-8?q?D=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/series/[slug]/route.ts | 91 ++++++++++++++++++++++++++++++++++ app/api/series/route.ts | 48 ++++++++++++++++++ 2 files changed, 139 insertions(+) create mode 100644 app/api/series/[slug]/route.ts create mode 100644 app/api/series/route.ts diff --git a/app/api/series/[slug]/route.ts b/app/api/series/[slug]/route.ts new file mode 100644 index 0000000..6035643 --- /dev/null +++ b/app/api/series/[slug]/route.ts @@ -0,0 +1,91 @@ +import { NextResponse } from 'next/server'; +import dbConnect from '@/app/lib/dbConnect'; +import Series from '@/app/models/Series'; + +export async function GET( + request: Request, + { params }: { params: { slug: string } } +) { + try { + await dbConnect(); + const series = await Series.findOne({ slug: params.slug }).populate( + 'posts' + ); + + if (!series) { + return NextResponse.json( + { error: '해당 시리즈를 찾을 수 없습니다.' }, + { status: 404 } + ); + } + + return NextResponse.json(series); + } catch (error: any) { + return NextResponse.json( + { error: error.message || '시리즈 조회에 실패했습니다.' }, + { status: 500 } + ); + } +} + +export async function PUT( + request: Request, + { params }: { params: { slug: string } } +) { + try { + await dbConnect(); + const body = await request.json(); + + const updatedSeries = await Series.findOneAndUpdate( + { slug: params.slug }, + { + title: body.title, + description: body.description, + thumbnailImage: body.thumbnailImage, + order: body.order, + posts: body.posts, + }, + { new: true } + ).populate('posts'); + + if (!updatedSeries) { + return NextResponse.json( + { error: '수정할 시리즈를 찾을 수 없습니다.' }, + { status: 404 } + ); + } + + return NextResponse.json(updatedSeries); + } catch (error: any) { + return NextResponse.json( + { error: error.message || '시리즈 수정에 실패했습니다.' }, + { status: 500 } + ); + } +} + +export async function DELETE( + request: Request, + { params }: { params: { slug: string } } +) { + try { + await dbConnect(); + const deletedSeries = await Series.findOneAndDelete({ slug: params.slug }); + + if (!deletedSeries) { + return NextResponse.json( + { error: '삭제할 시리즈를 찾을 수 없습니다.' }, + { status: 404 } + ); + } + + return NextResponse.json({ + message: '시리즈가 성공적으로 삭제되었습니다.', + }); + } catch (error: any) { + return NextResponse.json( + { error: error.message || '시리즈 삭제에 실패했습니다.' }, + { status: 500 } + ); + } +} diff --git a/app/api/series/route.ts b/app/api/series/route.ts new file mode 100644 index 0000000..8528193 --- /dev/null +++ b/app/api/series/route.ts @@ -0,0 +1,48 @@ +import { NextResponse } from 'next/server'; +import dbConnect from '@/app/lib/dbConnect'; +import Series from '@/app/models/Series'; +import { createPostSlug } from '@/app/lib/utils/post'; + +export async function POST(request: Request) { + try { + await dbConnect(); + const body = await request.json(); + + const series = await Series.create({ + slug: createPostSlug(body.title), + title: body.title, + description: body.description, + thumbnailImage: body.thumbnailImage || '', + order: body.order || [], + posts: body.posts || [], + }); + + return NextResponse.json(series, { status: 201 }); + } catch (error: any) { + return NextResponse.json( + { error: error.message || '시리즈 생성에 실패했습니다.' }, + { status: 500 } + ); + } +} + +export async function GET(request: Request) { + try { + await dbConnect(); + const { searchParams } = new URL(request.url); + const populate = searchParams.get('populate') === 'true'; + + const query = Series.find({}); + if (populate) { + query.populate('posts'); + } + + const series = await query; + return NextResponse.json(series); + } catch (error: any) { + return NextResponse.json( + { error: error.message || '시리즈 목록을 불러오는데 실패했습니다.' }, + { status: 500 } + ); + } +} From 71068e1e1a2dddea70170d5f4b3c58cc19448e4c Mon Sep 17 00:00:00 2001 From: JeongwooSeo Date: Wed, 25 Dec 2024 16:46:23 +0900 Subject: [PATCH 09/33] =?UTF-8?q?fix:=20post=20=EC=A0=9C=EB=AA=A9=20?= =?UTF-8?q?=EC=97=86=EC=9D=84=20=EA=B2=BD=EC=9A=B0=20=EC=97=90=EB=9F=AC?= =?UTF-8?q?=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/series/route.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/api/series/route.ts b/app/api/series/route.ts index 8528193..f668200 100644 --- a/app/api/series/route.ts +++ b/app/api/series/route.ts @@ -7,6 +7,12 @@ export async function POST(request: Request) { try { await dbConnect(); const body = await request.json(); + if (!body.title) { + return NextResponse.json( + { error: '시리즈 제목을 입력해주세요.' }, + { status: 400 } + ); + } const series = await Series.create({ slug: createPostSlug(body.title), From 028d27bc11cac02d84c5a05b0f85735dd2fce450 Mon Sep 17 00:00:00 2001 From: JeongwooSeo Date: Wed, 25 Dec 2024 17:12:34 +0900 Subject: [PATCH 10/33] =?UTF-8?q?fix:=20post=20api=20=EC=A0=9C=EB=AA=A9?= =?UTF-8?q?=EB=A7=8C=20=ED=95=84=EC=88=98=EB=A1=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/entities/series/api/series.ts | 40 +++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 app/entities/series/api/series.ts diff --git a/app/entities/series/api/series.ts b/app/entities/series/api/series.ts new file mode 100644 index 0000000..b9d38cb --- /dev/null +++ b/app/entities/series/api/series.ts @@ -0,0 +1,40 @@ +import axios from 'axios'; + +export const getAllSeriesData = async (isPopulate: boolean = false) => { + const response = await axios.get('/api/series', { + params: { + populate: isPopulate, + }, + }); + return response.data; +}; + +export const getSeriesData = async (slug: string) => { + const response = await axios.get(`/api/series/${slug}`); + return response.data; +}; + +export const createSeries = async (data: { + title: string; + description?: string; + thumbnailImage?: string; + order?: string[]; + posts?: string[]; +}) => { + const response = await axios.post('/api/series', data); + return response.data; +}; + +export const updateSeries = async ( + slug: string, + data: { + title: string; + description: string; + thumbnailImage: string; + order: string[]; + posts: string[]; + } +) => { + const response = await axios.put(`/api/series/${slug}`, data); + return response.data; +}; From 1e6b4984a07ae2dc666b5cb6b0cadb6885d5cada Mon Sep 17 00:00:00 2001 From: JeongwooSeo Date: Wed, 25 Dec 2024 17:39:14 +0900 Subject: [PATCH 11/33] =?UTF-8?q?feat:=20=EC=8B=9C=EB=A6=AC=EC=A6=88=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20=EC=98=A4=EB=B2=84=EB=A0=88=EC=9D=B4=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/entities/common/Overlay/Overlay.tsx | 20 +++-- app/entities/post/write/BlogForm.tsx | 39 ++++++--- .../series/CreateSeriesOverlayContainer.tsx | 87 +++++++++++++++++++ 3 files changed, 128 insertions(+), 18 deletions(-) create mode 100644 app/entities/series/CreateSeriesOverlayContainer.tsx diff --git a/app/entities/common/Overlay/Overlay.tsx b/app/entities/common/Overlay/Overlay.tsx index 2bce718..038f794 100644 --- a/app/entities/common/Overlay/Overlay.tsx +++ b/app/entities/common/Overlay/Overlay.tsx @@ -1,10 +1,15 @@ import { ReactNode, useEffect, useRef } from 'react'; interface OverlayProps { + overlayOpen: boolean; setOverlayOpen: (open: boolean) => void; children: ReactNode; } -const Overlay = ({ setOverlayOpen, children }: OverlayProps) => { +const Overlay = ({ + overlayOpen = false, + setOverlayOpen, + children, +}: OverlayProps) => { const overlayRef = useRef(null); useEffect(() => { const handleClickOutside = (event: MouseEvent) => { @@ -20,11 +25,16 @@ const Overlay = ({ setOverlayOpen, children }: OverlayProps) => { }, []); return ( -
-
- {children} + overlayOpen && ( +
+
+ {children} +
-
+ ) ); }; diff --git a/app/entities/post/write/BlogForm.tsx b/app/entities/post/write/BlogForm.tsx index 4014f1d..665b6ae 100644 --- a/app/entities/post/write/BlogForm.tsx +++ b/app/entities/post/write/BlogForm.tsx @@ -13,6 +13,9 @@ import PostWriteButtons from '@/app/entities/post/write/PostWriteButtons'; import { validatePost } from '@/app/lib/utils/validate/validate'; import Select from '@/app/entities/common/Select'; import { Series } from '@/app/types/Series'; +import Overlay from '@/app/entities/common/Overlay/Overlay'; +import { FaPlus } from 'react-icons/fa6'; +import CreateSeriesOverlayContainer from '@/app/entities/series/CreateSeriesOverlayContainer'; const MDEditor = dynamic(() => import('@uiw/react-md-editor'), { ssr: false }); @@ -26,11 +29,12 @@ const BlogForm = () => { const [profileImage, setProfileImage] = useState(); const [thumbnailImage, setThumbnailImage] = useState(); const [series, setSeries] = useState(null); - const [newSeries, setNewSeries] = useState(null); + const [errors, setErrors] = useState([]); const toast = useToast(); const router = useRouter(); const NICKNAME = '개발자 서정우'; + const [createSeriesOpen, setCreateSeriesOpen] = useState(false); useBlockNavigate({ title, content: content || '' }); @@ -143,8 +147,10 @@ const BlogForm = () => { onChange={(e) => setSubTitle(e.target.value)} value={subTitle} /> -
-
+ + + + void; +} + +const CreateSeriesOverlayContainer = ({ + setCreateSeriesOpen, +}: CreateSeriesOverlayContainerProps) => { + const [seriesTitle, setSeriesTitle] = useState(''); + const [seriesDescription, setSeriesDescription] = useState(''); + const [seriesThumbnail, setSeriesThumbnail] = useState(''); + const toast = useToast(); + + const postSeries = async () => { + try { + const data = await createSeries({ + title: seriesTitle, + description: seriesDescription, + thumbnailImage: seriesThumbnail, + }); + if (data._id) { + toast.success('시리즈가 성공적으로 생성되었습니다.'); + } else { + toast.error('시리즈 생성 중 오류가 발생했습니다.'); + } + } catch (e) { + toast.error('시리즈 생성 중 오류가 발생했습니다.'); + console.error('시리즈 생성 중 오류 발생', e); + } finally { + setCreateSeriesOpen(false); + } + }; + + return ( +
+

새로운 시리즈 만들기

+

+ 새로운 시리즈를 생성합니다. 제목은 필수로 작성해야합니다. +

+ +