Skip to content

Commit c8a858d

Browse files
committed
feat: postedit 기능 로딩 suspense 변경 및 에러바운더리 추가
1 parent 2c02ac1 commit c8a858d

File tree

4 files changed

+148
-133
lines changed

4 files changed

+148
-133
lines changed
Lines changed: 12 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -1,135 +1,27 @@
11
'use client';
22

3-
import React, { useMemo } from 'react';
4-
import dynamic from 'next/dynamic';
3+
import React, { Suspense } from 'react';
54
import { useSession } from 'next-auth/react';
6-
import { usePostEdit } from '@/hooks/queries/usePostEdit';
75
import '@/styles/pages/community/community.scss';
8-
import 'react-quill/dist/quill.snow.css';
9-
import type Quill from 'quill';
10-
import { axiosInstance } from '@/services/common/axiosInstance';
11-
import { API_URLS } from '@/constants/urls';
12-
import Cookies from 'js-cookie';
13-
import { ALERT_MESSAGES } from '@/constants/alertMessage';
14-
15-
const ReactQuill = dynamic(() => import('react-quill'), { ssr: false });
6+
import ErrorBoundary from '@/components/common/ErrorBoundary';
7+
import LoadingSpinner from '@/components/common/LoadingSpinner';
8+
import EditPostContent from '@/components/community/EditPostContent';
9+
import { ERROR_MESSAGES } from '@/constants/errors';
1610

1711
export default function EditPostPage({ params }: { params: { id: string } }) {
18-
const { id } = params;
1912
const { data: session } = useSession();
20-
2113
const userId = session?.user?.id;
2214
const accessToken = session?.user?.accessToken || '';
2315

24-
const { title, setTitle, content, setContent, loading, handleUpdatePost, handleDeletePost } = usePostEdit(id, userId, accessToken);
25-
26-
function handleImageUpload(this: { quill: Quill }) {
27-
const editor = this.quill;
28-
29-
const input = document.createElement("input");
30-
input.type = "file";
31-
input.accept = "image/*";
32-
input.click();
33-
34-
input.onchange = async () => {
35-
if (input.files && input.files.length > 0) {
36-
const file = input.files[0];
37-
try {
38-
const formData = new FormData();
39-
formData.append("image", file);
40-
41-
const response = await axiosInstance.post(API_URLS.UPLOADS, formData, {
42-
headers: {
43-
"Content-Type": "multipart/form-data",
44-
Authorization: `Bearer ${Cookies.get("accessToken")}`,
45-
},
46-
});
47-
48-
const imageUrl = response.data.url;
49-
const range = editor.getSelection(true);
50-
editor.insertEmbed(range.index, "image", imageUrl);
51-
editor.setSelection(range.index + 1);
52-
} catch (error) {
53-
console.error("Image upload failed:", error);
54-
alert(ALERT_MESSAGES.ERROR.POST.IMAGE_UPLOAD_ERROR);
55-
}
56-
}
57-
};
58-
}
59-
60-
const modules = useMemo(() => {
61-
return {
62-
toolbar: {
63-
container: [
64-
[{ header: '1' }, { header: '2' }, { font: [] }],
65-
[{ size: [] }],
66-
['bold', 'italic', 'underline', 'strike', 'blockquote'],
67-
[
68-
{ list: 'ordered' },
69-
{ list: 'bullet' },
70-
{ indent: '-1' },
71-
{ indent: '+1' },
72-
],
73-
['link', 'image'],
74-
['clean'],
75-
],
76-
handlers: {
77-
image: handleImageUpload,
78-
},
79-
},
80-
};
81-
}, []);
82-
83-
const formats = useMemo(
84-
() => [
85-
'header',
86-
'font',
87-
'size',
88-
'bold',
89-
'italic',
90-
'underline',
91-
'strike',
92-
'blockquote',
93-
'list',
94-
'bullet',
95-
'indent',
96-
'link',
97-
'image',
98-
],
99-
[]
100-
);
101-
102-
if (loading) {
103-
return <p>게시글을 불러오는 중...</p>;
16+
if (!userId) {
17+
throw new Error(ERROR_MESSAGES.LOGIN_REQUIRED);
10418
}
10519

10620
return (
107-
<div className="community edit_post">
108-
<h1>커뮤니티</h1>
109-
<p className="sub_title">자유롭게 건강에 관련 지식을 공유해봅시다!</p>
110-
<div className="form_group">
111-
<input
112-
id="title"
113-
type="text"
114-
value={title}
115-
onChange={(e) => setTitle(e.target.value)}
116-
/>
117-
</div>
118-
<div className="form_group">
119-
<ReactQuill
120-
theme="snow"
121-
modules={modules}
122-
formats={formats}
123-
value={content}
124-
onChange={(val) => setContent(val)}
125-
/>
126-
</div>
127-
<div className="actions">
128-
<button onClick={handleUpdatePost} className='create_button'>수정 완료</button>
129-
<button onClick={handleDeletePost} className="delete_button">
130-
삭제
131-
</button>
132-
</div>
133-
</div>
21+
<ErrorBoundary>
22+
<Suspense fallback={<LoadingSpinner />}>
23+
<EditPostContent params={params} userId={userId} accessToken={accessToken} />
24+
</Suspense>
25+
</ErrorBoundary>
13426
);
13527
}
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
'use client';
2+
3+
import React, { useMemo } from 'react';
4+
import dynamic from 'next/dynamic';
5+
import { usePostEdit } from '@/hooks/queries/usePostEdit';
6+
import 'react-quill/dist/quill.snow.css';
7+
import type Quill from 'quill';
8+
import { axiosInstance } from '@/services/common/axiosInstance';
9+
import { API_URLS } from '@/constants/urls';
10+
import Cookies from 'js-cookie';
11+
import { ALERT_MESSAGES } from '@/constants/alertMessage';
12+
13+
const ReactQuill = dynamic(() => import('react-quill'), { ssr: false });
14+
15+
interface EditPostContentProps {
16+
params: { id: string };
17+
userId: string;
18+
accessToken: string;
19+
}
20+
21+
export default function EditPostContent({ params, userId, accessToken }: EditPostContentProps) {
22+
const { id } = params;
23+
const { title, setTitle, content, setContent, handleUpdatePost, handleDeletePost } = usePostEdit(id, userId, accessToken);
24+
25+
function handleImageUpload(this: { quill: Quill }) {
26+
const editor = this.quill;
27+
28+
const input = document.createElement("input");
29+
input.type = "file";
30+
input.accept = "image/*";
31+
input.click();
32+
33+
input.onchange = async () => {
34+
if (input.files && input.files.length > 0) {
35+
const file = input.files[0];
36+
try {
37+
const formData = new FormData();
38+
formData.append("image", file);
39+
40+
const response = await axiosInstance.post(API_URLS.UPLOADS, formData, {
41+
headers: {
42+
"Content-Type": "multipart/form-data",
43+
Authorization: `Bearer ${Cookies.get("accessToken")}`,
44+
},
45+
});
46+
47+
const imageUrl = response.data.url;
48+
const range = editor.getSelection(true);
49+
editor.insertEmbed(range.index, "image", imageUrl);
50+
editor.setSelection(range.index + 1);
51+
} catch (error) {
52+
console.error("Image upload failed:", error);
53+
alert(ALERT_MESSAGES.ERROR.POST.IMAGE_UPLOAD_ERROR);
54+
}
55+
}
56+
};
57+
}
58+
59+
const modules = useMemo(() => {
60+
return {
61+
toolbar: {
62+
container: [
63+
[{ header: '1' }, { header: '2' }, { font: [] }],
64+
[{ size: [] }],
65+
['bold', 'italic', 'underline', 'strike', 'blockquote'],
66+
[
67+
{ list: 'ordered' },
68+
{ list: 'bullet' },
69+
{ indent: '-1' },
70+
{ indent: '+1' },
71+
],
72+
['link', 'image'],
73+
['clean'],
74+
],
75+
handlers: {
76+
image: handleImageUpload,
77+
},
78+
},
79+
};
80+
}, []);
81+
82+
const formats = useMemo(
83+
() => [
84+
'header',
85+
'font',
86+
'size',
87+
'bold',
88+
'italic',
89+
'underline',
90+
'strike',
91+
'blockquote',
92+
'list',
93+
'bullet',
94+
'indent',
95+
'link',
96+
'image',
97+
],
98+
[]
99+
);
100+
101+
return (
102+
<div className="community edit_post">
103+
<h1>커뮤니티</h1>
104+
<p className="sub_title">자유롭게 건강에 관련 지식을 공유해봅시다!</p>
105+
<div className="form_group">
106+
<input
107+
id="title"
108+
type="text"
109+
value={title}
110+
onChange={(e) => setTitle(e.target.value)}
111+
/>
112+
</div>
113+
<div className="form_group">
114+
<ReactQuill
115+
theme="snow"
116+
modules={modules}
117+
formats={formats}
118+
value={content}
119+
onChange={(val) => setContent(val)}
120+
/>
121+
</div>
122+
<div className="actions">
123+
<button onClick={handleUpdatePost} className='create_button'>수정 완료</button>
124+
<button onClick={handleDeletePost} className="delete_button">
125+
삭제
126+
</button>
127+
</div>
128+
</div>
129+
);
130+
}

apps/next-client/src/constants/errors.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export const ERROR_MESSAGES = {
22
LOGIN_FAILED: "이메일이나 비밀번호를 다시 확인해주세요.",
3+
LOGIN_REQUIRED: "로그인이 필요한 서비스입니다.",
34
UNKNOWN_ERROR: "알 수 없는 오류가 발생했습니다.",
45
INVALID_CREDENTIAL: "유효하지 않은 자격 증명입니다.",
56
LOGIN_ERROR: "로그인 중 문제가 발생했습니다.",

apps/next-client/src/hooks/queries/usePostEdit.ts

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { useState, useEffect } from 'react';
22
import { useRouter } from 'next/navigation';
3-
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
3+
import { useSuspenseQuery, useMutation, useQueryClient } from '@tanstack/react-query';
44
import { axiosInstance } from '@/services/common/axiosInstance';
55
import { API_URLS } from '@/constants/urls';
66
import { ALERT_MESSAGES } from '@/constants/alertMessage';
@@ -11,8 +11,8 @@ export const usePostEdit = (id: string, userId: string | undefined, accessToken:
1111
const router = useRouter();
1212
const queryClient = useQueryClient();
1313

14-
// 게시글 조회
15-
const { isLoading: loading, error, data } = useQuery({
14+
// 게시글 조회 - useSuspenseQuery 사용 (enabled 옵션 제거)
15+
const { data } = useSuspenseQuery({
1616
queryKey: ['post-edit', id],
1717
queryFn: async () => {
1818
const response = await axiosInstance.get(`${API_URLS.POSTS}/${id}`);
@@ -26,26 +26,18 @@ export const usePostEdit = (id: string, userId: string | undefined, accessToken:
2626

2727
return post;
2828
},
29-
enabled: !!id && !!userId,
3029
staleTime: 5 * 60 * 1000,
3130
gcTime: 10 * 60 * 1000,
3231
});
3332

33+
// 데이터로부터 상태 설정
3434
useEffect(() => {
3535
if (data) {
3636
setTitle(data.title);
3737
setContent(data.content);
3838
}
3939
}, [data]);
4040

41-
useEffect(() => {
42-
if (error) {
43-
console.error('Error fetching post:', error);
44-
alert(ALERT_MESSAGES.ERROR.POST.POST_FETCH_ERROR);
45-
router.push('/community');
46-
}
47-
}, [error, router]);
48-
4941
// 게시글 업데이트
5042
const updatePostMutation = useMutation({
5143
mutationFn: async () => {
@@ -101,5 +93,5 @@ export const usePostEdit = (id: string, userId: string | undefined, accessToken:
10193
deletePostMutation.mutate();
10294
};
10395

104-
return { title, setTitle, content, setContent, loading, handleUpdatePost, handleDeletePost };
96+
return { title, setTitle, content, setContent, handleUpdatePost, handleDeletePost };
10597
};

0 commit comments

Comments
 (0)