From 84044c3f282cf3dd351ea5a3594fa66ddc6cd600 Mon Sep 17 00:00:00 2001 From: JeongwooSeo <98446924+ShipFriend0516@users.noreply.github.com> Date: Fri, 27 Jun 2025 15:51:25 +0900 Subject: [PATCH 1/9] =?UTF-8?q?feat:=20=EC=96=B4=EB=93=9C=EB=AF=BC=20?= =?UTF-8?q?=EB=8C=80=EC=8B=9C=EB=B3=B4=EB=93=9C=EC=97=90=20=EC=8B=9C?= =?UTF-8?q?=EB=A6=AC=EC=A6=88=20=EA=B4=80=EB=A6=AC=20=ED=83=AD=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/admin/page.tsx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/admin/page.tsx b/app/admin/page.tsx index bc1590a..487ce41 100644 --- a/app/admin/page.tsx +++ b/app/admin/page.tsx @@ -12,6 +12,7 @@ import GithubLogin from '@/app/entities/common/Button/GithubLogin'; import BubbleBackground from '@/app/entities/common/Background/BubbleBackground'; import { useEffect } from 'react'; import useToast from '@/app/hooks/useToast'; +import { FaBuffer } from 'react-icons/fa6'; const AdminDashboard = () => { const { data: session } = useSession(); @@ -67,6 +68,13 @@ const AdminDashboard = () => { bgColor: 'bg-purple-950/20', // 짙은 보라색의 투명도 적용 link: '/admin/analytics', }, + { + title: '시리즈 관리', + icon: , + description: '블로그 시리즈를 관리합니다.', + bgColor: 'bg-emerald-950/20', // 짙은 보라색의 투명도 적용 + link: '/admin/series', + }, { title: '댓글 확인 및 관리', icon: , From 35d77d697baef8803c68ff799684f96a11981d10 Mon Sep 17 00:00:00 2001 From: JeongwooSeo <98446924+ShipFriend0516@users.noreply.github.com> Date: Fri, 27 Jun 2025 16:27:29 +0900 Subject: [PATCH 2/9] =?UTF-8?q?feat:=20=EC=8B=9C=EB=A6=AC=EC=A6=88=20?= =?UTF-8?q?=EB=A6=AC=EC=8A=A4=ED=8A=B8,=20=EC=95=84=EC=9D=B4=ED=85=9C=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/admin/series/page.tsx | 44 +++++++++++++ app/entities/series/list/AdminSeriesList.tsx | 26 ++++++++ .../series/list/AdminSeriesListItem.tsx | 61 +++++++++++++++++++ 3 files changed, 131 insertions(+) create mode 100644 app/admin/series/page.tsx create mode 100644 app/entities/series/list/AdminSeriesList.tsx create mode 100644 app/entities/series/list/AdminSeriesListItem.tsx diff --git a/app/admin/series/page.tsx b/app/admin/series/page.tsx new file mode 100644 index 0000000..be4c316 --- /dev/null +++ b/app/admin/series/page.tsx @@ -0,0 +1,44 @@ +'use client'; +import { Series } from '@/app/types/Series'; +import useDataFetch, { + useDataFetchConfig, +} from '@/app/hooks/common/useDataFetch'; +import React from 'react'; +import AdminSeriesList from '@/app/entities/series/list/AdminSeriesList'; +const AdminSeriesPage = () => { + const getSeriesListConfig: useDataFetchConfig = { + url: '/api/series', + method: 'GET', + config: { + params: { + compact: 'true', + }, + }, + }; + + const { data: seriesList, loading } = + useDataFetch(getSeriesListConfig); + + return ( +
+

시리즈 관리

+

+ 시리즈를 관리하는 페이지입니다. 시리즈를 추가, 수정, 삭제할 수 있습니다. +

+
+ +
+
+

+ 등록된 시리즈 목록 ({seriesList?.length || 0}) +

+
+ +
+
+ ); +}; + +export default AdminSeriesPage; diff --git a/app/entities/series/list/AdminSeriesList.tsx b/app/entities/series/list/AdminSeriesList.tsx new file mode 100644 index 0000000..d486661 --- /dev/null +++ b/app/entities/series/list/AdminSeriesList.tsx @@ -0,0 +1,26 @@ +import { Series } from '@/app/types/Series'; +import React from 'react'; +import AdminSeriesListItem from '@/app/entities/series/list/AdminSeriesListItem'; + +interface AdminSeriesListProps { + seriesList: Series[] | null | undefined; + loading: boolean; +} +const AdminSeriesList = ({ loading, seriesList }: AdminSeriesListProps) => { + if (loading) { + return

로딩 중...

; + } + if (!seriesList || seriesList.length === 0) { + return

등록된 시리즈가 없습니다.

; + } + return ( +
    + {loading &&

    로딩 중...

    } + {seriesList.map((series, index) => ( + + ))} +
+ ); +}; + +export default AdminSeriesList; diff --git a/app/entities/series/list/AdminSeriesListItem.tsx b/app/entities/series/list/AdminSeriesListItem.tsx new file mode 100644 index 0000000..fc90920 --- /dev/null +++ b/app/entities/series/list/AdminSeriesListItem.tsx @@ -0,0 +1,61 @@ +import { Series } from '@/app/types/Series'; +import Image from 'next/image'; +import { FaBookOpen, FaCalendar } from 'react-icons/fa'; +import React from 'react'; + +const AdminSeriesListItem = ({ series }: { series: Series }) => { + return ( +
  • +
    + {series.thumbnailImage ? ( + {series.title} + ) : ( +
    + +
    + )} +
    +
    +

    + {series.title} +

    + +
    + + + {new Date(series.date).toLocaleDateString()} + + + + {series.posts.length || 0} posts + +
    + +

    + {series.description || 'No description available'} +

    +
      + + +
    +
    +
  • + ); +}; + +export default AdminSeriesListItem; From 58f4a30b0aeeb3853a80c6201280fd9c442860cd Mon Sep 17 00:00:00 2001 From: JeongwooSeo <98446924+ShipFriend0516@users.noreply.github.com> Date: Fri, 27 Jun 2025 16:40:20 +0900 Subject: [PATCH 3/9] =?UTF-8?q?refactor:=20=EA=B8=B0=EC=A1=B4=20=EC=8B=9C?= =?UTF-8?q?=EB=A6=AC=EC=A6=88=20=EC=83=9D=EC=84=B1=20=EC=98=A4=EB=B2=84?= =?UTF-8?q?=EB=A0=88=EC=9D=B4=EB=A5=BC=20=EC=88=98=EC=A0=95=20=EB=AA=A8?= =?UTF-8?q?=EB=93=9C=EB=8F=84=20=EA=B0=80=EB=8A=A5=ED=95=98=EA=B2=8C=20?= =?UTF-8?q?=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81=20=EB=B0=8F=20=EC=9E=AC?= =?UTF-8?q?=EC=82=AC=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/admin/series/page.tsx | 35 ++++++++++++++-- .../series/CreateSeriesOverlayContainer.tsx | 41 +++++++++++++++---- app/entities/series/api/series.ts | 2 - app/entities/series/list/AdminSeriesList.tsx | 13 +++++- .../series/list/AdminSeriesListItem.tsx | 15 ++++++- 5 files changed, 90 insertions(+), 16 deletions(-) diff --git a/app/admin/series/page.tsx b/app/admin/series/page.tsx index be4c316..6705b4f 100644 --- a/app/admin/series/page.tsx +++ b/app/admin/series/page.tsx @@ -3,8 +3,10 @@ import { Series } from '@/app/types/Series'; import useDataFetch, { useDataFetchConfig, } from '@/app/hooks/common/useDataFetch'; -import React from 'react'; +import { useState } from 'react'; import AdminSeriesList from '@/app/entities/series/list/AdminSeriesList'; +import Overlay from '@/app/entities/common/Overlay/Overlay'; +import CreateSeriesOverlayContainer from '@/app/entities/series/CreateSeriesOverlayContainer'; const AdminSeriesPage = () => { const getSeriesListConfig: useDataFetchConfig = { url: '/api/series', @@ -18,6 +20,16 @@ const AdminSeriesPage = () => { const { data: seriesList, loading } = useDataFetch(getSeriesListConfig); + const [createSeriesOpen, setCreateSeriesOpen] = useState(false); + const [toUpdateSeries, setToUpdateSeries] = useState(null); + const handleUpdateSeries = (series: Series) => { + setCreateSeriesOpen(true); + setToUpdateSeries(series); + }; + const handleCloseOverlay = () => { + setCreateSeriesOpen(false); + setToUpdateSeries(null); + }; return (
    @@ -26,7 +38,10 @@ const AdminSeriesPage = () => { 시리즈를 관리하는 페이지입니다. 시리즈를 추가, 수정, 삭제할 수 있습니다.

    -
    @@ -35,8 +50,22 @@ const AdminSeriesPage = () => { 등록된 시리즈 목록 ({seriesList?.length || 0})
    - + + + +
    ); }; diff --git a/app/entities/series/CreateSeriesOverlayContainer.tsx b/app/entities/series/CreateSeriesOverlayContainer.tsx index 4f844e6..f7331a9 100644 --- a/app/entities/series/CreateSeriesOverlayContainer.tsx +++ b/app/entities/series/CreateSeriesOverlayContainer.tsx @@ -1,17 +1,23 @@ import { ChangeEvent, useState } from 'react'; -import { createSeries } from '@/app/entities/series/api/series'; +import { createSeries, updateSeries } from '@/app/entities/series/api/series'; import useToast from '@/app/hooks/useToast'; +import { Series } from '@/app/types/Series'; interface CreateSeriesOverlayContainerProps { setCreateSeriesOpen: (open: boolean) => void; + series?: Series; + handleCloseOverlay?: () => void; } const CreateSeriesOverlayContainer = ({ setCreateSeriesOpen, + series, + handleCloseOverlay, }: CreateSeriesOverlayContainerProps) => { const [seriesTitle, setSeriesTitle] = useState(''); const [seriesDescription, setSeriesDescription] = useState(''); const [seriesThumbnail, setSeriesThumbnail] = useState(''); + const isEditMode = !!series; const toast = useToast(); const postSeries = async () => { @@ -34,10 +40,31 @@ const CreateSeriesOverlayContainer = ({ } }; + const editSeries = async () => { + try { + if (isEditMode) { + await updateSeries(series.slug, { + title: seriesTitle, + description: seriesDescription, + thumbnailImage: seriesThumbnail, + }); + } + } catch (e) { + toast.error('시리즈 수정 중 오류가 발생했습니다.'); + console.error('시리즈 수정 중 오류 발생', e); + } finally { + if (handleCloseOverlay) { + handleCloseOverlay(); + } else { + setCreateSeriesOpen(false); + } + } + }; + return (

    - 새로운 시리즈 만들기 + {isEditMode ? '시리즈 수정하기' : '새로운 시리즈 만들기'}

    새로운 시리즈를 생성합니다. 제목은 필수로 작성해야합니다. @@ -53,7 +80,7 @@ const CreateSeriesOverlayContainer = ({ placeholder="시리즈의 이름" className="w-full p-2.5 border border-gray-300 rounded-md focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500 outline-none transition text-black" onChange={(e) => setSeriesTitle(e.target.value)} - value={seriesTitle || ''} + value={isEditMode ? series?.title : seriesTitle || ''} />

    @@ -65,7 +92,7 @@ const CreateSeriesOverlayContainer = ({ placeholder="시리즈의 설명" className="w-full p-2.5 border border-gray-300 rounded-md focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500 outline-none transition text-black min-h-[100px] resize-y" onChange={(e) => setSeriesDescription(e.target.value)} - value={seriesDescription || ''} + value={isEditMode ? series?.description : seriesDescription || ''} /> @@ -78,7 +105,7 @@ const CreateSeriesOverlayContainer = ({ placeholder="시리즈의 썸네일 링크" className="w-full p-2.5 border border-gray-300 rounded-md focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500 outline-none transition text-black" onChange={(e) => setSeriesThumbnail(e.target.value)} - value={seriesThumbnail || ''} + value={isEditMode ? series?.thumbnailImage : seriesThumbnail || ''} /> @@ -91,10 +118,10 @@ const CreateSeriesOverlayContainer = ({ 취소 diff --git a/app/entities/series/api/series.ts b/app/entities/series/api/series.ts index b9d38cb..5780a4c 100644 --- a/app/entities/series/api/series.ts +++ b/app/entities/series/api/series.ts @@ -31,8 +31,6 @@ export const updateSeries = async ( title: string; description: string; thumbnailImage: string; - order: string[]; - posts: string[]; } ) => { const response = await axios.put(`/api/series/${slug}`, data); diff --git a/app/entities/series/list/AdminSeriesList.tsx b/app/entities/series/list/AdminSeriesList.tsx index d486661..134e3cd 100644 --- a/app/entities/series/list/AdminSeriesList.tsx +++ b/app/entities/series/list/AdminSeriesList.tsx @@ -5,8 +5,13 @@ import AdminSeriesListItem from '@/app/entities/series/list/AdminSeriesListItem' interface AdminSeriesListProps { seriesList: Series[] | null | undefined; loading: boolean; + handleUpdateSeries: (series: Series) => void; } -const AdminSeriesList = ({ loading, seriesList }: AdminSeriesListProps) => { +const AdminSeriesList = ({ + loading, + seriesList, + handleUpdateSeries, +}: AdminSeriesListProps) => { if (loading) { return

    로딩 중...

    ; } @@ -17,7 +22,11 @@ const AdminSeriesList = ({ loading, seriesList }: AdminSeriesListProps) => {
      {loading &&

      로딩 중...

      } {seriesList.map((series, index) => ( - + ))}
    ); diff --git a/app/entities/series/list/AdminSeriesListItem.tsx b/app/entities/series/list/AdminSeriesListItem.tsx index fc90920..112340f 100644 --- a/app/entities/series/list/AdminSeriesListItem.tsx +++ b/app/entities/series/list/AdminSeriesListItem.tsx @@ -3,7 +3,15 @@ import Image from 'next/image'; import { FaBookOpen, FaCalendar } from 'react-icons/fa'; import React from 'react'; -const AdminSeriesListItem = ({ series }: { series: Series }) => { +interface AdminSeriesListItemProps { + series: Series; + handleUpdateSeries: (series: Series) => void; +} + +const AdminSeriesListItem = ({ + series, + handleUpdateSeries, +}: AdminSeriesListItemProps) => { return (
  • { {series.description || 'No description available'}

      -
    diff --git a/app/entities/series/list/AdminSeriesListItem.tsx b/app/entities/series/list/AdminSeriesListItem.tsx index 5aab521..2f57ac1 100644 --- a/app/entities/series/list/AdminSeriesListItem.tsx +++ b/app/entities/series/list/AdminSeriesListItem.tsx @@ -6,11 +6,13 @@ import React from 'react'; interface AdminSeriesListItemProps { series: Series; handleUpdateSeries: (series: Series) => void; + handleDeleteClick: (slug: string) => void; } const AdminSeriesListItem = ({ series, handleUpdateSeries, + handleDeleteClick, }: AdminSeriesListItemProps) => { return (
  • 시리즈 수정 - From 7ded2ac8ea5cc2e458bcf0eaa378cf9799622c2f Mon Sep 17 00:00:00 2001 From: JeongwooSeo <98446924+ShipFriend0516@users.noreply.github.com> Date: Fri, 27 Jun 2025 17:07:09 +0900 Subject: [PATCH 8/9] =?UTF-8?q?fix:=20=EC=8B=9C=EB=A6=AC=EC=A6=88=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20API=20=EC=9D=B8=EC=A6=9D=EC=9D=B4=20?= =?UTF-8?q?=ED=95=84=EC=9A=94=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/series/[slug]/route.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/app/api/series/[slug]/route.ts b/app/api/series/[slug]/route.ts index 79e3530..420453c 100644 --- a/app/api/series/[slug]/route.ts +++ b/app/api/series/[slug]/route.ts @@ -2,6 +2,7 @@ import { NextResponse } from 'next/server'; import dbConnect from '@/app/lib/dbConnect'; import Series from '@/app/models/Series'; import '@/app/models/Post'; +import { getServerSession } from 'next-auth'; export async function GET( request: Request, @@ -41,6 +42,12 @@ export async function PUT( { params }: { params: { slug: string } } ) { try { + const session = await getServerSession(); + + if (!session) { + return new Response('Unauthorized', { status: 401 }); + } + await dbConnect(); const body = await request.json(); @@ -77,6 +84,12 @@ export async function DELETE( { params }: { params: { slug: string } } ) { try { + const session = await getServerSession(); + + if (!session) { + return new Response('Unauthorized', { status: 401 }); + } + await dbConnect(); const deletedSeries = await Series.findOneAndDelete({ slug: params.slug }); From 664de05036594c43a71ad874ce1c33d82d198c5f Mon Sep 17 00:00:00 2001 From: JeongwooSeo <98446924+ShipFriend0516@users.noreply.github.com> Date: Fri, 27 Jun 2025 17:07:24 +0900 Subject: [PATCH 9/9] =?UTF-8?q?feat:=20=EC=8B=9C=EB=A6=AC=EC=A6=88=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C=20=EA=B8=B0=EB=8A=A5=20=EB=B9=84=ED=99=9C?= =?UTF-8?q?=EC=84=B1=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/entities/series/list/AdminSeriesListItem.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/entities/series/list/AdminSeriesListItem.tsx b/app/entities/series/list/AdminSeriesListItem.tsx index 2f57ac1..33860bc 100644 --- a/app/entities/series/list/AdminSeriesListItem.tsx +++ b/app/entities/series/list/AdminSeriesListItem.tsx @@ -64,7 +64,10 @@ const AdminSeriesListItem = ({