From b3ba5c237466ab6c0444eb595a3185d7a07129f8 Mon Sep 17 00:00:00 2001 From: ParkWonYeop Date: Sun, 28 Dec 2025 23:26:37 +0900 Subject: [PATCH] . --- src/app/write/page.tsx | 179 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 162 insertions(+), 17 deletions(-) diff --git a/src/app/write/page.tsx b/src/app/write/page.tsx index a90d2ec..84d72b1 100644 --- a/src/app/write/page.tsx +++ b/src/app/write/page.tsx @@ -7,7 +7,7 @@ import { createPost, updatePost, getPost } from '@/api/posts'; import { getCategories } from '@/api/category'; import { uploadImage } from '@/api/image'; import { useAuthStore } from '@/store/authStore'; -import { Loader2, Save, Image as ImageIcon, ArrowLeft, Folder, UploadCloud } from 'lucide-react'; +import { Loader2, Save, Image as ImageIcon, ArrowLeft, Folder, UploadCloud, FileText, Trash2, X, Clock } from 'lucide-react'; import toast from 'react-hot-toast'; import dynamic from 'next/dynamic'; import axios from 'axios'; @@ -17,6 +17,14 @@ const MDEditor = dynamic( { ssr: false } ); +// πŸ› οΈ μž„μ‹œμ €μž₯ 데이터 νƒ€μž… μ •μ˜ +interface DraftPost { + id: number; + title: string; + content: string; + savedAt: string; +} + // πŸ› οΈ 1. 토큰 만료 체크 μœ ν‹Έλ¦¬ν‹° function isTokenExpired(token: string) { try { @@ -51,6 +59,10 @@ function WritePageContent() { const [isUploading, setIsUploading] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false); + // πŸ’Ύ μž„μ‹œμ €μž₯ κ΄€λ ¨ μƒνƒœ + const [drafts, setDrafts] = useState([]); + const [showDraftList, setShowDraftList] = useState(false); + useEffect(() => { if (_hasHydrated && (!role || !role.includes('ADMIN'))) { toast.error('κ΄€λ¦¬μž κΆŒν•œμ΄ ν•„μš”ν•©λ‹ˆλ‹€.'); @@ -58,6 +70,18 @@ function WritePageContent() { } }, [role, _hasHydrated, router]); + // μ»΄ν¬λ„ŒνŠΈ 마운트 μ‹œ λ‘œμ»¬μŠ€ν† λ¦¬μ§€μ—μ„œ μž„μ‹œμ €μž₯ λͺ©λ‘ 뢈러였기 + useEffect(() => { + const savedDrafts = localStorage.getItem('temp_drafts'); + if (savedDrafts) { + try { + setDrafts(JSON.parse(savedDrafts)); + } catch (e) { + console.error('Failed to parse drafts', e); + } + } + }, []); + const { data: categories } = useQuery({ queryKey: ['categories'], queryFn: getCategories, @@ -96,21 +120,59 @@ function WritePageContent() { return null; }; + // πŸ’Ύ μž„μ‹œμ €μž₯ κΈ°λŠ₯ + const handleTempSave = () => { + if (!title.trim() && !content.trim()) { + toast.error('제λͺ©μ΄λ‚˜ λ‚΄μš©μ„ μž…λ ₯ν•΄μ£Όμ„Έμš”.'); + return; + } + + if (drafts.length >= 10) { + toast.error('μž„μ‹œμ €μž₯은 μ΅œλŒ€ 10κ°œκΉŒμ§€λ§Œ κ°€λŠ₯ν•©λ‹ˆλ‹€.\nκΈ°μ‘΄ μ €μž₯뢄을 μ‚­μ œν•΄μ£Όμ„Έμš”.'); + setShowDraftList(true); // λͺ©λ‘ μ—΄μ–΄μ£ΌκΈ° + return; + } + + const newDraft: DraftPost = { + id: Date.now(), + title: title || '(제λͺ© μ—†μŒ)', + content: content, + savedAt: new Date().toLocaleString(), + }; + + const newDrafts = [newDraft, ...drafts]; + setDrafts(newDrafts); + localStorage.setItem('temp_drafts', JSON.stringify(newDrafts)); + toast.success('μž„μ‹œμ €μž₯ λ˜μ—ˆμŠ΅λ‹ˆλ‹€!'); + }; + + // πŸ’Ύ μž„μ‹œμ €μž₯ 뢈러였기 + const handleLoadDraft = (draft: DraftPost) => { + if (confirm('ν˜„μž¬ μž‘μ„± 쀑인 λ‚΄μš©μ΄ μ‚¬λΌμ§‘λ‹ˆλ‹€.\nμ„ νƒν•œ μž„μ‹œμ €μž₯ 글을 λΆˆλŸ¬μ˜€μ‹œκ² μŠ΅λ‹ˆκΉŒ?')) { + setTitle(draft.title === '(제λͺ© μ—†μŒ)' ? '' : draft.title); + setContent(draft.content); + setShowDraftList(false); + toast.success('뢈러였기 μ™„λ£Œ'); + } + }; + + // πŸ’Ύ μž„μ‹œμ €μž₯ μ‚­μ œ + const handleDeleteDraft = (id: number) => { + const newDrafts = drafts.filter(d => d.id !== id); + setDrafts(newDrafts); + localStorage.setItem('temp_drafts', JSON.stringify(newDrafts)); + toast.success('μ‚­μ œλ˜μ—ˆμŠ΅λ‹ˆλ‹€.'); + }; + const mutation = useMutation({ mutationFn: (data: any) => isEditMode ? updatePost(existingPost!.id, data) : createPost(data), onSuccess: async (response: any) => { const savedPost = response?.data; const newSlug = savedPost?.slug || editSlug; - // 🚨 [핡심 μˆ˜μ •] λͺ©λ‘ μΊμ‹œλ₯Ό 'μ΄ˆκΈ°ν™”(Reset)'ν•©λ‹ˆλ‹€. - // μ΄λ ‡κ²Œ ν•˜λ©΄ λͺ©λ‘ νŽ˜μ΄μ§€(Home, Category λ“±)둜 μ΄λ™ν–ˆμ„ λ•Œ - // κΈ°μ‘΄ μΊμ‹œ(μ˜›λ‚  κΈ€ λͺ©λ‘)λ₯Ό 보여주지 μ•Šκ³ , μ¦‰μ‹œ μ„œλ²„μ—μ„œ μƒˆ 데이터λ₯Ό κ°€μ Έμ˜΅λ‹ˆλ‹€. await queryClient.resetQueries({ queryKey: ['posts'] }); - - // μΉ΄ν…Œκ³ λ¦¬ 데이터(κΈ€ 개수 λ“±)도 κ°±μ‹  await queryClient.invalidateQueries({ queryKey: ['categories'] }); - // 상세 νŽ˜μ΄μ§€ μΊμ‹œ μ‚­μ œ (λ‹€μ‹œ λ“€μ–΄κ°€λ©΄ μƒˆλ‘œ 받도둝) if (editSlug) { queryClient.removeQueries({ queryKey: ['post', editSlug] }); } @@ -119,7 +181,6 @@ function WritePageContent() { } toast.success(isEditMode ? 'κ²Œμ‹œκΈ€μ΄ μˆ˜μ •λ˜μ—ˆμŠ΅λ‹ˆλ‹€.' : 'κ²Œμ‹œκΈ€μ΄ λ°œν–‰λ˜μ—ˆμŠ΅λ‹ˆλ‹€!'); - router.push(isEditMode ? `/posts/${newSlug}` : '/'); }, onError: (err: any) => { @@ -277,6 +338,7 @@ function WritePageContent() { return (
+ {/* 상단 헀더 μ˜μ—­ */}

{isEditMode ? 'κ²Œμ‹œκΈ€ μˆ˜μ •' : 'μƒˆ κΈ€ μž‘μ„±'}

- + +
+ {/* πŸ’Ύ μž„μ‹œμ €μž₯ λ²„νŠΌ κ·Έλ£Ή */} +
+
+ + +
+ + {/* πŸ’Ύ μž„μ‹œμ €μž₯ λͺ©λ‘ νŒμ—… */} + {showDraftList && ( + <> +
setShowDraftList(false)} + /> +
+
+

μž„μ‹œμ €μž₯ λͺ©λ‘ ({drafts.length}/10)

+ +
+ +
+ {drafts.length === 0 ? ( +
+ μ €μž₯된 글이 μ—†μŠ΅λ‹ˆλ‹€. +
+ ) : ( +
    + {drafts.map((draft) => ( +
  • +
    + + +
    +
  • + ))} +
+ )} +
+
+ + )} +
+ + +
@@ -324,6 +464,10 @@ function WritePageContent() {
+ {/* πŸ› οΈ [Legacy] νƒœκ·Έ μž…λ ₯ UI λΉ„ν™œμ„±ν™” + μ‚¬μš©μž μš”μ²­μœΌλ‘œ UIμ—μ„œ μˆ¨κΉ€ 처리 (데이터 κ΅¬μ‘°λŠ” μœ μ§€) + */} + {/*

🏷️ νƒœκ·Έ @@ -337,8 +481,9 @@ function WritePageContent() { />

예: React, Next.js, νŠœν† λ¦¬μ–Ό

+ */} -
+

이미지 μ—…λ‘œλ“œ