헤드리스 CMS 라는 백엔드와 bolt 라는 웹사이트 AI 코딩 프론트 엔드를 결합하여 사이트를 만들었다.
초반은 잘 되다가 토큰이 떨어져서 챗GPT로 코드를 수정하여 생성, 삭제를 추가했다.
배포는 계속 되는 것 같아 웹사이트 유지하기에는 좋은 것 같다.
import React, { useState, useEffect } from 'react';
import Header from './components/Header';
import PostList from './components/PostList';
import PostForm from './components/PostForm';
import PostDetail from './components/PostDetail';
import { Post, ApiItem, ApiResponse } from './types';
// Access token for the API
const ACCESS_TOKEN = "";
// API URL
const API_URL = "https://api.memexdata.io/memex/api/projects/b545c4b1/models/article/contents/search/v2";
function App() {
const [posts, setPosts] = useState<Post[]>([]);
const [selectedPost, setSelectedPost] = useState<Post | null>(null);
const [loading, setLoading] = useState<boolean>(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
fetchPosts();
}, []);
const parseEditorJSContent = (jsonString: string): string => {
try {
const editorData = JSON.parse(jsonString);
if (editorData.blocks) {
// Extract text from blocks
return editorData.blocks
.map((block: any) => {
if (block.type === 'paragraph' || block.type === 'header') {
return block.data.text;
} else if (block.type === 'list') {
return block.data.items.join('\n');
}
return '';
})
.filter(Boolean)
.join('\n\n');
}
return jsonString;
} catch (e) {
// If parsing fails, return the original string
return jsonString;
}
};
const fetchPosts = async () => {
try {
setLoading(true);
setError(null);
const response = await fetch(API_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Access-Token': ACCESS_TOKEN
},
body: JSON.stringify({
size: 10,
page: 0,
direction: "DESC",
orderCond: {
type: "DATE_CREATE"
}
})
});
if (!response.ok) {
throw new Error(`API 요청 실패: ${response.status}`);
}
const data: ApiResponse = await response.json();
if (!data.list || !Array.isArray(data.list)) {
throw new Error('API 응답 형식이 올바르지 않습니다.');
}
let count = 1;
// Transform API data to our Post format
const transformedPosts: Post[] = data.list.map((item: ApiItem, index: number) => {
// Extract categories if available
const categories = item.data.category
? item.data.category.map(cat => cat.languageMap.KO)
: [];
// Parse content if it's in EditorJS format
const contentRaw = item.data.content?.KO || '';
const parsedContent = parseEditorJSContent(contentRaw);
return {
uid: item.uid,
id: count++,
title: item.data.title?.KO || '제목 없음',
content: parsedContent,
author: item.data.author?.KO || '익명',
createdAt: new Date(item.data.date || Date.now()),
category: categories
};
});
setPosts(transformedPosts);
} catch (err) {
console.error('데이터를 가져오는 중 오류 발생:', err);
setError('데이터를 불러오는 데 실패했습니다. 나중에 다시 시도해주세요.');
// Fallback to sample data if API fails
const fallbackPosts: Post[] = [
{
id: 1,
title: '첫 번째 게시물입니다',
content: '게시판에 오신 것을 환영합니다. 이 게시판은 React와 TypeScript로 만들어졌습니다.',
author: '관리자',
createdAt: new Date('2025-01-15')
},
{
id: 2,
title: '게시판 사용 방법',
content: '게시물을 작성하려면 "새 게시물 작성" 버튼을 클릭하세요.\n게시물을 보려면 제목을 클릭하세요.\n게시물을 삭제하려면 삭제 아이콘을 클릭하세요.',
author: '관리자',
createdAt: new Date('2025-01-16')
}
];
setPosts(fallbackPosts);
} finally {
setLoading(false);
}
};
const handleCreatePost = async (title: string, content: string, author: string) => {
try {
const requestBody = {
publish: true,
data: {
title: { KO: title },
date: new Date().toISOString().replace('Z', ''),
content: { KO: content },
author: { KO: author },
category: []
}
};
const response = await fetch(
'https://api.memexdata.io/memex/external/projects/b545c4b1/models/article/contents',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Access-Token': ACCESS_TOKEN,
},
body: JSON.stringify(requestBody),
}
);
//if (response) {
// throw new Error('게시물 생성 실패');
//}
//const responseData = await response.json();
console.log('게시물 생성 성공:', response);
window.location.reload(); // 페이지 새로고침
} catch (error) {
console.error('Error creating post:', error);
alert('게시물 생성 중 오류가 발생했습니다.');
}
};
const handleViewPost = (post: Post) => {
setSelectedPost(post);
};
const handleBackToList = () => {
setSelectedPost(null);
};
const handleDeletePost = async (id: number) => {
if (window.confirm('정말로 이 게시물을 삭제하시겠습니까?')) {
try {
const response = await fetch(
'https://api.memexdata.io/memex/external/projects/b545c4b1/models/article/contents',
{
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
'Access-Token': ACCESS_TOKEN,
},
body: JSON.stringify([id]),
}
);
if (!response.ok) {
throw new Error('게시물 삭제 실패');
}
setPosts(posts.filter(post => post.id !== id));
if (selectedPost && selectedPost.id === id) {
setSelectedPost(null);
}
window.location.reload(); // 페이지 새로고침
} catch (error) {
console.error('Error deleting post:', error);
alert('게시물 삭제 중 오류가 발생했습니다.');
}
}
};
const handleRefresh = () => {
fetchPosts();
};
return (
<div className="min-h-screen bg-gray-100">
<Header />
<main className="container mx-auto px-4 py-8">
{selectedPost ? (
<PostDetail post={selectedPost} onBack={handleBackToList} />
) : (
<>
<PostForm onSubmit={handleCreatePost} />
{loading ? (
<div className="flex justify-center items-center py-12">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600"></div>
</div>
) : error ? (
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mt-6">
<p>{error}</p>
<button
onClick={handleRefresh}
className="mt-2 bg-red-200 hover:bg-red-300 text-red-700 font-bold py-1 px-3 rounded text-sm"
>
다시 시도
</button>
</div>
) : (
<PostList
posts={posts}
onViewPost={handleViewPost}
onDeletePost={handleDeletePost}
onRefresh={handleRefresh}
/>
)}
</>
)}
</main>
<footer className="bg-gray-800 text-white py-4 mt-8">
<div className="container mx-auto px-4 text-center">
<p>© 2025 게시판 웹사이트. All rights reserved.</p>
</div>
</footer>
</div>
);
}
export default App;
댓글 달기