From f6558818477e7373c1998181a9557613eee43744 Mon Sep 17 00:00:00 2001 From: ghlim00 Date: Wed, 28 Jan 2026 01:42:24 +0900 Subject: [PATCH 1/4] =?UTF-8?q?=EA=B0=9C=EB=B0=9C=20=ED=99=98=EA=B2=BD=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20=EB=B0=8F=20=EA=B2=8C=EC=8B=9C=EA=B8=80=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Frontend/.env.development | 1 + Frontend/.env.production | 1 + Frontend/index.html | 5 +- Frontend/package-lock.json | 49 ++ Frontend/package.json | 1 + Frontend/src/api/api.js | 9 + .../pages/Community/CommunityDeatailPage.jsx | 270 ++++++----- .../src/pages/Community/CommunityListPage.jsx | 450 ++++++++---------- .../pages/Community/CommunityWritePage.jsx | 182 ++++--- Frontend/src/pages/MyPage/MyPageLoggedOut.jsx | 2 +- Frontend/vite.config.js | 4 + 11 files changed, 515 insertions(+), 459 deletions(-) create mode 100644 Frontend/.env.development create mode 100644 Frontend/.env.production create mode 100644 Frontend/src/api/api.js diff --git a/Frontend/.env.development b/Frontend/.env.development new file mode 100644 index 0000000..460e575 --- /dev/null +++ b/Frontend/.env.development @@ -0,0 +1 @@ +VITE_API_URL=http://localhost:8080 diff --git a/Frontend/.env.production b/Frontend/.env.production new file mode 100644 index 0000000..831278c --- /dev/null +++ b/Frontend/.env.production @@ -0,0 +1 @@ +VITE_API_URL=http://solvemeup.com diff --git a/Frontend/index.html b/Frontend/index.html index 5870203..5af3882 100644 --- a/Frontend/index.html +++ b/Frontend/index.html @@ -4,7 +4,10 @@ - + + frontend diff --git a/Frontend/package-lock.json b/Frontend/package-lock.json index 5848388..a7594ff 100644 --- a/Frontend/package-lock.json +++ b/Frontend/package-lock.json @@ -12,6 +12,7 @@ "@tailwindcss/postcss": "^4.1.18", "@tanstack/react-query": "^5.90.16", "@tanstack/react-query-devtools": "^5.91.2", + "axios": "^1.13.3", "react": "^19.2.0", "react-dom": "^19.2.0", "react-redux": "^9.2.0", @@ -4439,6 +4440,33 @@ "integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==", "license": "MIT" }, + "node_modules/axios": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.3.tgz", + "integrity": "sha512-ERT8kdX7DZjtUm7IitEyV7InTHAF42iJuMArIiDIV5YtPanJkgw4hw5Dyg9fh0mihdWNn1GKaeIWErfe56UQ1g==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/axios/node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/babel-code-frame": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", @@ -6709,6 +6737,21 @@ "node": ">= 0.4" } }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/esbuild": { "version": "0.27.2", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", @@ -17334,6 +17377,12 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, "node_modules/prr": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", diff --git a/Frontend/package.json b/Frontend/package.json index 5e1e675..20070cb 100644 --- a/Frontend/package.json +++ b/Frontend/package.json @@ -14,6 +14,7 @@ "@tailwindcss/postcss": "^4.1.18", "@tanstack/react-query": "^5.90.16", "@tanstack/react-query-devtools": "^5.91.2", + "axios": "^1.13.3", "react": "^19.2.0", "react-dom": "^19.2.0", "react-redux": "^9.2.0", diff --git a/Frontend/src/api/api.js b/Frontend/src/api/api.js new file mode 100644 index 0000000..0199575 --- /dev/null +++ b/Frontend/src/api/api.js @@ -0,0 +1,9 @@ +import axios from "axios"; + +const api = axios.create({ + baseURL: import.meta.env.VITE_API_URL, + withCredentials: true, + headers: { "Content-Type": "application/json" }, +}); + +export default api; diff --git a/Frontend/src/pages/Community/CommunityDeatailPage.jsx b/Frontend/src/pages/Community/CommunityDeatailPage.jsx index c3718f0..6db15b7 100644 --- a/Frontend/src/pages/Community/CommunityDeatailPage.jsx +++ b/Frontend/src/pages/Community/CommunityDeatailPage.jsx @@ -1,62 +1,69 @@ import React, { useEffect, useMemo, useState } from "react"; import { Link, useNavigate, useParams } from "react-router-dom"; - -const LS_POSTS = "community_posts_v1"; - -function loadPosts() { - const raw = localStorage.getItem(LS_POSTS); - if (!raw) return []; - try { - const parsed = JSON.parse(raw); - return Array.isArray(parsed) ? parsed : []; - } catch { - return []; - } -} - -function savePosts(posts) { - localStorage.setItem(LS_POSTS, JSON.stringify(posts)); -} +import axios from "axios"; const ghostBtn = - "bg-transparent p-0 border-0 outline-none focus:outline-none text-gray-500 hover:text-black"; + "bg-transparent flex items-center gap-1 p-0 border-0 outline-none focus:outline-none text-gray-500 hover:text-black"; + +function formatKST(iso) { + if (!iso) return ""; + const d = new Date(iso); + if (Number.isNaN(d.getTime())) return String(iso); + return new Intl.DateTimeFormat("ko-KR", { + dateStyle: "medium", + timeStyle: "short", + }).format(d); +} export default function CommunityDetailPage() { const { postId } = useParams(); const navigate = useNavigate(); + const [raw, setRaw] = useState(null); const [post, setPost] = useState(null); - + const [errMsg, setErrMsg] = useState(""); + const [debug, setDebug] = useState("INIT"); const [commentText, setCommentText] = useState(""); - - const [comments] = useState([ - { - id: "default-1", - author: "USERNAME", - createdAt: "2024-11-11 18:12", - content: "짱 ~", - likeCount: 0, - dislikeCount: 0, - }, - ]); - useEffect(() => { - const posts = loadPosts(); - const found = posts.find((p) => String(p.id) === String(postId)); - setPost(found ?? null); + if (!postId) return; + + setDebug("EFFECT_STARTED"); + setErrMsg(""); + + (async () => { + try { + const res = await axios.get(`/api/posts/${postId}`, { + withCredentials: true, + }); + + setDebug("GET_SUCCESS"); + setRaw(res.data); + setPost(res.data ?? null); + } catch (e) { + setDebug("GET_FAILED"); + console.log("❌ GET failed:", e?.response?.status, e?.response?.data, e); + setErrMsg("게시글을 불러오지 못했습니다."); + setPost(null); + } + })(); }, [postId]); - const commentCount = useMemo(() => comments.length, [comments]); + // ✅ 댓글은 서버 응답(post.comments)을 사용 (없으면 빈 배열) + const comments = useMemo(() => post?.comments ?? [], [post]); - if (!post) { + // ✅ 댓글 수는 서버 commentCount 우선, 없으면 comments.length + const commentCount = useMemo(() => { + if (typeof post?.commentCount === "number") return post.commentCount; + return comments.length; + }, [post, comments]); + + if (errMsg) { return (
-
- 게시글을 찾을 수 없습니다. -
+
{errMsg}
+ +
DEBUG: {debug}
+
+{JSON.stringify(raw, null, 2)}
+          
); } + if (!post) { + return ( +
+
+
게시글을 찾을 수 없습니다.
+
+ +
- const updatePostCounts = (updater) => { - const posts = loadPosts(); - const next = posts.map((p) => { - if (String(p.id) !== String(postId)) return p; - return updater(p); - }); - savePosts(next); - const refreshed = next.find((p) => String(p.id) === String(postId)); - setPost(refreshed ?? null); - }; - - - const onClickAddComment = () => { - - alert("아직 안됨"); - }; - +
DEBUG: {debug}
+
+{JSON.stringify(raw, null, 2)}
+          
+
+
+ ); + } - const onClickLikeComment = () => { - alert("아직 안됨"); - }; + const onClickAddComment = () => alert("아직 안됨"); + const onClickLikeComment = () => alert("아직 안됨"); + const onClickDislikeComment = () => alert("아직 안됨"); - const onClickDislikeComment = () => { - alert("아직 안됨"); - }; + // ✅ author가 객체이므로 nickname 사용 + const authorNickname = post?.author?.nickname ?? "(author 없음)"; + const createdAtText = formatKST(post?.createdAt); + const updatedAtText = formatKST(post?.updatedAt); return (
@@ -108,57 +125,44 @@ export default function CommunityDetailPage() {
-
- 👤 - {post.author} - {post.createdAt} + face + {authorNickname} + {createdAtText} + {updatedAtText ? ( + (수정 {updatedAtText}) + ) : null}
-
- - - - -
💬 {commentCount}
+
+ thumb_up + {post.likeCount ?? 0} +
+
+ thumb_down + {post.dislikeCount ?? 0} +
+
+ comment + {commentCount} +
-

{post.title}

+

+ {post.title ?? "(title 없음)"} +

- {post.content} + {post.content ?? "(content 없음)"}
-
-
- {comments.map((c) => ( -
-
-
- 👤 - {c.author} - {c.createdAt} -
- - -
- - + {comments.length === 0 ? ( +
댓글이 없습니다.
+ ) : ( + comments.map((c) => { + // 서버 댓글 스키마가 아직 확정 전이라 방어적으로 + const cAuthor = + c?.author?.nickname ?? c?.authorNickname ?? c?.author ?? "익명"; + const cCreatedAt = formatKST(c?.createdAt); + + return ( +
+
+
+ face + {cAuthor} + {cCreatedAt} +
+ +
+ + +
+
+ +

{c.content ?? ""}

-
- -

{c.content}

-
- ))} + ); + }) + )}
+ +
DEBUG: {debug}
+
+{JSON.stringify(raw, null, 2)}
+          
diff --git a/Frontend/src/pages/Community/CommunityListPage.jsx b/Frontend/src/pages/Community/CommunityListPage.jsx index bb10a7d..54714d8 100644 --- a/Frontend/src/pages/Community/CommunityListPage.jsx +++ b/Frontend/src/pages/Community/CommunityListPage.jsx @@ -1,88 +1,128 @@ -// import React, { useEffect, useState } from "react"; +// import React, { useEffect, useMemo, useState } from "react"; // import { Link, useNavigate } from "react-router-dom"; - -// /** -// * localStorage keys -// */ -// const LS_POSTS = "community_posts_v1"; - - -// const SEED_POSTS = [ -// { -// id: "1", -// author: "zelsa", -// title: "SSAFY VS SKALA", -// content: "둘 다 붙으면 어디를 가는 게 좋을까요?\n의견 부탁드립니다!", -// createdAt: "2024-11-11", -// likeCount: 2, -// commentCount: 1, -// }, -// { -// id: "2", -// author: "zelsa", -// title: "SSAFY VS SKALA", -// content: "둘 다 붙으면 어디를 가는 게 좋을까요?\n(중복 테스트)", -// createdAt: "2024-11-11", -// likeCount: 2, -// commentCount: 1, -// }, -// { -// id: "3", -// author: "zelsa", -// title: "SSAFY VS SKALA", -// content: "둘 다 붙으면 어디를 가는 게 좋을까요?\n(리스트 UI 테스트)", -// createdAt: "2024-11-11", -// likeCount: 2, -// commentCount: 0, -// }, -// ]; - -// function loadPosts() { -// const raw = localStorage.getItem(LS_POSTS); -// if (!raw) { -// localStorage.setItem(LS_POSTS, JSON.stringify(SEED_POSTS)); -// return SEED_POSTS; -// } -// try { -// const parsed = JSON.parse(raw); -// return Array.isArray(parsed) ? parsed : []; -// } catch { -// return []; -// } -// } - -// function savePosts(posts) { -// localStorage.setItem(LS_POSTS, JSON.stringify(posts)); -// } +// import axios from "axios"; // function formatDate(yyyyMmDd) { -// const [y, m, d] = yyyyMmDd.split("-").map(Number); +// const [y, m, d] = String(yyyyMmDd || "").split("-").map(Number); // const monthNames = [ // "Jan.", "Feb.", "Mar.", "Apr.", "May.", "Jun.", // "Jul.", "Aug.", "Sep.", "Oct.", "Nov.", "Dec." // ]; -// if (!y || !m || !d) return yyyyMmDd; +// if (!y || !m || !d) return yyyyMmDd || ""; // return `${monthNames[m - 1]}${d}.${y}`; // } +// function normalizeListResponse(data) { +// // ✅ 백엔드가 리스트를 주는 흔한 형태들을 모두 커버: +// // 1) Array: [ {...}, {...} ] +// // 2) Page: { content: [...] } +// // 3) Wrapper: { data: [...] } / { result: [...] } +// if (Array.isArray(data)) return data; +// if (Array.isArray(data?.content)) return data.content; +// if (Array.isArray(data?.data)) return data.data; +// if (Array.isArray(data?.result)) return data.result; +// return []; +// } + +// function toYmd(createdAtLike) { +// // createdAt이 "2026-01-26T..." 또는 "2026-01-26" 등 다양할 수 있어서 앞 10자리만 +// const s = String(createdAtLike || ""); +// return s.length >= 10 ? s.slice(0, 10) : s; +// } + // export default function CommunityListPage() { +// console.log("🔥 CommunityListPage FILE LOADED v2"); + // const navigate = useNavigate(); // const [posts, setPosts] = useState([]); -// const [q, setQ] = useState(""); +// const [q, setQ] = useState(""); +// const [loading, setLoading] = useState(false); +// const [errorMsg, setErrorMsg] = useState(""); + +// // ✅ 임시: onLike가 아직 없으면 렌더 에러 나므로 안전하게 처리 +// const onLike = async (postId) => { +// alert("좋아요 API 연결 전입니다."); +// }; // useEffect(() => { -// setPosts(loadPosts()); +// console.log("[CommunityListPage] mounted", new Date().toISOString()); // }, []); -// const onLike = (postId) => { -// const next = posts.map((p) => -// String(p.id) === String(postId) -// ? { ...p, likeCount: (p.likeCount ?? 0) + 1 } -// : p -// ); -// setPosts(next); -// savePosts(next); -// }; +// useEffect(() => { +// const fetchPosts = async () => { +// setLoading(true); +// setErrorMsg(""); + +// try { +// const res = await axios.get("/api/posts", { +// withCredentials: true, // ✅ 세션/쿠키 OAuth 가능성 고려 +// // JWT 방식이면 여기서 Authorization 넣거나 axios instance에서 처리 +// }); + +// console.log("RAW res.data =", res.data); + +// const rawList = normalizeListResponse(res.data); +// console.log("rawList length =", rawList.length, "first =", rawList[0]); + +// setPosts(rawList); // ✅ 일단 매핑 없이 그대로 넣어보기(임시) + + +// // 디버깅용: 실제로 어떤 응답이 오는지 확인 +// console.log("[GET /api/posts] status:", res.status); +// console.log("[GET /api/posts] data:", res.data); + +// const rawList = normalizeListResponse(res.data); + +// const mapped = rawList.map((p) => ({ +// id: String(p.id ?? p.postId ?? ""), +// author: +// typeof p.author === "string" +// ? p.author +// : (p.author?.nickname ?? +// p.user?.nickname ?? +// p.writer?.nickname ?? +// "unknown"), +// title: p.title ?? "", +// content: p.content ?? "", +// createdAt: toYmd(p.createdAt ?? p.created_at ?? p.createdDate ?? p.created_date), +// likeCount: p.likeCount ?? p.like_count ?? p.likes ?? 0, +// commentCount: p.commentCount ?? p.comment_count ?? p.comments ?? 0, +// })); + +// // id가 비어있는 데이터가 있으면 렌더/링크가 깨지므로 필터 +// const safe = mapped.filter((p) => p.id && p.title); + +// setPosts(safe); +// } catch (err) { +// const status = err?.response?.status; +// console.error("[GET /api/posts] failed:", err); + +// if (status === 401) { +// setErrorMsg("로그인이 필요합니다. 로그인 후 다시 시도해 주세요."); +// } else if (status === 403) { +// setErrorMsg("권한이 없습니다."); +// } else { +// setErrorMsg("게시글을 불러오지 못했습니다."); +// } +// setPosts([]); +// } finally { +// setLoading(false); +// } +// }; + +// fetchPosts(); +// }, []); + +// const filtered = useMemo(() => { +// const keyword = q.trim().toLowerCase(); +// if (!keyword) return posts; +// return posts.filter((p) => { +// const t = String(p.title || "").toLowerCase(); +// const c = String(p.content || "").toLowerCase(); +// const a = String(p.author || "").toLowerCase(); +// return t.includes(keyword) || c.includes(keyword) || a.includes(keyword); +// }); +// }, [posts, q]); // return ( //
@@ -92,17 +132,11 @@ //
//
- // //
+//
+// {loading ? ( +//
불러오는 중...
+// ) : errorMsg ? ( +//
{errorMsg}
+// ) : null} +//
+ // {/* 리스트 */} -//
-// {posts.length === 0 ? ( +//
+// {filtered.length === 0 && !loading ? ( //
// 게시글이 없습니다. //
// ) : ( -// posts.map((post) => ( +// filtered.map((post) => ( //
- //
// 👤 // {post.author} @@ -143,7 +184,7 @@ // // {post.title} // @@ -163,12 +204,8 @@ // type="button" // onClick={() => onLike(post.id)} // className=" -// bg-transparent -// p-0 -// border-0 -// text-gray-500 -// hover:text-black -// focus:outline-none +// bg-transparent p-0 border-0 text-gray-500 +// hover:text-black focus:outline-none // " // aria-label="like" // > @@ -190,212 +227,103 @@ // } - import React, { useEffect, useState } from "react"; import { Link, useNavigate } from "react-router-dom"; +import axios from "axios"; -/** - * localStorage keys - */ -const LS_POSTS = "community_posts_v1"; - - -const SEED_POSTS = [ - { - id: "1", - author: "zelsa", - title: "SSAFY VS SKALA", - content: "둘 다 붙으면 어디를 가는 게 좋을까요?\n의견 부탁드립니다!", - createdAt: "2024-11-11", - likeCount: 2, - commentCount: 1, - }, - { - id: "2", - author: "zelsa", - title: "SSAFY VS SKALA", - content: "둘 다 붙으면 어디를 가는 게 좋을까요?\n(중복 테스트)", - createdAt: "2024-11-11", - likeCount: 2, - commentCount: 1, - }, - { - id: "3", - author: "zelsa", - title: "SSAFY VS SKALA", - content: "둘 다 붙으면 어디를 가는 게 좋을까요?\n(리스트 UI 테스트)", - createdAt: "2024-11-11", - likeCount: 2, - commentCount: 0, - }, -]; - -function loadPosts() { - const raw = localStorage.getItem(LS_POSTS); - if (!raw) { - localStorage.setItem(LS_POSTS, JSON.stringify(SEED_POSTS)); - return SEED_POSTS; - } - try { - const parsed = JSON.parse(raw); - return Array.isArray(parsed) ? parsed : []; - } catch { - return []; - } -} - -function savePosts(posts) { - localStorage.setItem(LS_POSTS, JSON.stringify(posts)); -} +console.log("✅ MODULE LOADED:", import.meta.url); -function formatDate(yyyyMmDd) { - const [y, m, d] = yyyyMmDd.split("-").map(Number); - const monthNames = [ - "Jan.", "Feb.", "Mar.", "Apr.", "May.", "Jun.", - "Jul.", "Aug.", "Sep.", "Oct.", "Nov.", "Dec." - ]; - if (!y || !m || !d) return yyyyMmDd; - return `${monthNames[m - 1]}${d}.${y}`; -} export default function CommunityListPage() { + console.log("🔥 CommunityListPage FILE LOADED (REAL)"); + console.log("🔥 RENDER:", import.meta.url); + const navigate = useNavigate(); + const [raw, setRaw] = useState(null); const [posts, setPosts] = useState([]); - const [q, setQ] = useState(""); + const [errMsg, setErrMsg] = useState(""); + const [debug, setDebug] = useState("INIT"); + useEffect(() => { console.log("[CommunityListPage] mounted", new Date().toISOString()); }, []); - useEffect(() => { - (async () => { - try { - const res = await fetch("/api/posts"); // nginx가 backend로 프록시 - if (!res.ok) throw new Error(`HTTP ${res.status}`); - const data = await res.json(); - - // ✅ 서버 응답 형태에 맞게 매핑 (필드명은 실제 응답 보고 조정) - const mapped = (Array.isArray(data) ? data : data?.content ?? []).map((p) => ({ - id: String(p.id), - author: - typeof p.author === "string" - ? p.author - : (p.author?.nickname ?? p.user?.nickname ?? "unknown"), - title: p.title, - content: p.content, - createdAt: (p.createdAt ?? p.created_at ?? "").slice(0, 10), // "YYYY-MM-DD" - likeCount: p.likeCount ?? p.like_count ?? 0, - commentCount: p.commentCount ?? p.comment_count ?? 0, - })); - - setPosts(mapped); - } catch (e) { - console.error("Failed to load posts:", e); - // 서버 장애 시 임시 데이터 fallback 쓰고 싶으면 아래 주석 해제 - // setPosts(loadPosts()); - setPosts([]); - } - })(); -}, []); - + setDebug("EFFECT_STARTED"); + console.log("✅ effect started: GET /api/posts"); + + (async () => { + try { + const res = await axios.get("/api/posts", { withCredentials: true }); + setDebug("GET_SUCCESS"); + console.log("✅ GET status:", res.status); + console.log("✅ GET data:", res.data); + + setRaw(res.data); + + // 일단 가장 흔한 케이스만: + const list = + Array.isArray(res.data) ? res.data : + Array.isArray(res.data?.content) ? res.data.content : + Array.isArray(res.data?.data) ? res.data.data : + Array.isArray(res.data?.result) ? res.data.result : + Array.isArray(res.data?.data?.content) ? res.data.data.content : + []; + + console.log("✅ parsed list length:", list.length); + setPosts(list); + } catch (e) { + setDebug("GET_FAILED"); + console.log("❌ GET failed:", e?.response?.status, e?.response?.data, e); + setErrMsg("불러오기 실패"); + setPosts([]); + } + })(); + }, []); return (
-
- 커뮤니티 -
- -
- - setQ(e.target.value)} - /> - - - +
+
커뮤니티
- {/* 리스트 */} -
+ {errMsg ? ( +
{errMsg}
+ ) : null} + + {/* ✅ 응답 구조 확인용 (임시) */} +
+{JSON.stringify(raw, null, 2)}
+        
+ +
DEBUG: {debug}
+ +
{posts.length === 0 ? (
게시글이 없습니다.
) : ( - posts.map((post) => ( -
- -
- 👤 - {post.author} -
- - - {post.title} - - + posts.map((p, idx) => ( +
- {post.content} + {p.title ?? "(title 없음)"} - -
-
- {formatDate(post.createdAt)} - - - - 댓글 {post.commentCount ?? 0} -
+
+ {p.content ?? "(content 없음)"}
- -
+
)) )} @@ -403,4 +331,4 @@ export default function CommunityListPage() {
); -} \ No newline at end of file +} diff --git a/Frontend/src/pages/Community/CommunityWritePage.jsx b/Frontend/src/pages/Community/CommunityWritePage.jsx index 4c133d7..e2b092e 100644 --- a/Frontend/src/pages/Community/CommunityWritePage.jsx +++ b/Frontend/src/pages/Community/CommunityWritePage.jsx @@ -1,98 +1,138 @@ import React, { useState } from "react"; -import { Link, useNavigate } from "react-router-dom"; - -const LS_POSTS = "community_posts_v1"; - -function loadPosts() { - const raw = localStorage.getItem(LS_POSTS); - if (!raw) return []; - try { - const parsed = JSON.parse(raw); - return Array.isArray(parsed) ? parsed : []; - } catch { - return []; - } -} +import axios from "axios"; -function savePosts(posts) { - localStorage.setItem(LS_POSTS, JSON.stringify(posts)); -} - -function todayString() { - const d = new Date(); - const yyyy = d.getFullYear(); - const mm = String(d.getMonth() + 1).padStart(2, "0"); - const dd = String(d.getDate()).padStart(2, "0"); - return `${yyyy}-${mm}-${dd}`; -} +/** ✅ 도메인 고정 + 세션 쿠키 포함 */ +const api = axios.create({ + baseURL: "http://solvemeup.com", + withCredentials: true, + headers: { "Content-Type": "application/json" }, +}); export default function CommunityWritePage() { - const navigate = useNavigate(); const [title, setTitle] = useState(""); const [content, setContent] = useState(""); - const onSubmit = () => { - const t = title.trim(); - const c = content.trim(); - if (!t || !c) return; - - const posts = loadPosts(); - const newPost = { - id: String(Date.now()), - author: "USERNAME", - title: t, - content: c, - createdAt: todayString(), - likeCount: 0, - dislikeCount: 0, - commentCount: 0, - }; - - // 최신 글이 위로 - savePosts([newPost, ...posts]); - navigate("/community"); + const [isSubmitting, setIsSubmitting] = useState(false); + const [errorMsg, setErrorMsg] = useState(""); + const [lastPostRes, setLastPostRes] = useState(null); + const [lastGetRes, setLastGetRes] = useState(null); + + const submit = async () => { + const t = title.trim() || "테스트 제목"; + const c = content.trim() || "테스트 내용"; + + setIsSubmitting(true); + setErrorMsg(""); + setLastPostRes(null); + + try { + const res = await api.post("/api/posts", { title: t, content: c }); + setLastPostRes({ status: res.status, data: res.data }); + console.log("✅ POST OK", res.status, res.data); + alert("POST 성공. 아래 응답 확인!"); + } catch (err) { + const status = err?.response?.status; + const data = err?.response?.data; + console.log("❌ POST FAIL", status, data, err); + + setErrorMsg( + `POST 실패 (status=${status ?? "?"})\n` + + (data?.message || data?.error || JSON.stringify(data || {}, null, 2) || err.message) + ); + } finally { + setIsSubmitting(false); + } + }; + + const refetch = async () => { + setErrorMsg(""); + setLastGetRes(null); + + try { + const res = await api.get("/api/posts"); + setLastGetRes({ status: res.status, data: res.data }); + console.log("✅ GET OK", res.status, res.data); + alert("GET 성공. 아래 응답 확인!"); + } catch (err) { + const status = err?.response?.status; + const data = err?.response?.data; + console.log("❌ GET FAIL", status, data, err); + + setErrorMsg( + `GET 실패 (status=${status ?? "?"})\n` + + (data?.message || data?.error || JSON.stringify(data || {}, null, 2) || err.message) + ); + } }; return ( +
+
+ WRITE DEBUG BUILD: 2026-01-27 (v999) +
-
커뮤니티 글쓰기
+
커뮤니티 글쓰기 (디버그 모드)
-
+
-
- setTitle(e.target.value)} - /> -
+ setTitle(e.target.value)} + disabled={isSubmitting} + /> - -
-