diff --git a/index.html b/index.html index efacdb7..26ad6b6 100644 --- a/index.html +++ b/index.html @@ -2,7 +2,7 @@ - + CoInsight diff --git a/public/favicon.png b/public/favicon.png deleted file mode 100644 index 8eedac4..0000000 Binary files a/public/favicon.png and /dev/null differ diff --git a/public/logo.png b/public/logo.png index 73330ad..31f0128 100644 Binary files a/public/logo.png and b/public/logo.png differ diff --git a/src/App.jsx b/src/App.jsx index 0cddd74..aec81d5 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -14,7 +14,7 @@ function App() { } /> } /> - } /> + } /> } /> } /> } /> diff --git a/src/apis/News/detail.js b/src/apis/News/detail.js new file mode 100644 index 0000000..d32dcc8 --- /dev/null +++ b/src/apis/News/detail.js @@ -0,0 +1,11 @@ +import { instance } from "../../utils/axios"; + +export const getNewsDetail = async (id) => { + try { + const res = await instance.get(`/api/v1/news/${id}/detail`); + return res.data; + } catch (error) { + console.error("뉴스 상세 불러오기 실패: ", error); + throw error; + } +}; diff --git a/src/components/MainPage/CoinSearchBar.jsx b/src/components/MainPage/CoinSearchBar.jsx deleted file mode 100644 index a25e613..0000000 --- a/src/components/MainPage/CoinSearchBar.jsx +++ /dev/null @@ -1,16 +0,0 @@ -const CoinSearchBar = () => { - return ( -
-
- -
- -
- ) -} - -export default CoinSearchBar; diff --git a/src/components/MainPage/NewsCard.jsx b/src/components/MainPage/NewsCard.jsx index 48d41e8..c032aba 100644 --- a/src/components/MainPage/NewsCard.jsx +++ b/src/components/MainPage/NewsCard.jsx @@ -1,3 +1,4 @@ +import { useNavigate } from "react-router-dom"; import RoundButton from "../common/RoundButton"; const sentimentMap = { @@ -19,13 +20,14 @@ const formatTimeAgo = (dateStr) => { return `${diffDay}일 전`; }; -const NewsCard = ({ title, sentimentLabel, sentimentScore, relatedCryptos, publishedAt }) => { +const NewsCard = ({ id, title, sentimentLabel, sentimentScore, relatedCryptos, publishedAt }) => { + const nav = useNavigate(); const sentiment = sentimentMap[sentimentLabel] || sentimentMap.NEUTRAL; const coinNames = relatedCryptos?.map(c => c.ticker).join(", ") || "-"; const confidence = Math.round((sentimentScore || 0) * 100); return ( -
+
id && nav(`/newsdetail/${id}`)}>
diff --git a/src/components/MainPage/NewsSearchBar.jsx b/src/components/MainPage/NewsSearchBar.jsx deleted file mode 100644 index 02a8373..0000000 --- a/src/components/MainPage/NewsSearchBar.jsx +++ /dev/null @@ -1,49 +0,0 @@ -import { useState } from "react"; - -const NewsSearchBar = ({ onSearch }) => { - const [keyword, setKeyword] = useState(""); - - const handleSearch = () => { - onSearch(keyword.trim()); - }; - - const handleKeyDown = (e) => { - if (e.key === "Enter") { - handleSearch(); - } - }; - - const handleClear = () => { - setKeyword(""); - onSearch(""); - }; - - return ( -
-
- -
- setKeyword(e.target.value)} - onKeyDown={handleKeyDown} - placeholder="뉴스 검색 (예: ETF, 규제, 상승)" - className="w-full h-12 pl-12 pr-10 border border-[#233554] rounded-lg bg-[#112240] text-[14px] text-[#CCD6F6] placeholder-[#495670] outline-none focus:border-[#64FFDA] transition-colors" - /> - {keyword && ( -
- -
- )} -
- ) -} - -export default NewsSearchBar; diff --git a/src/pages/MainPage.jsx b/src/pages/MainPage.jsx index d36859d..f13ad8b 100644 --- a/src/pages/MainPage.jsx +++ b/src/pages/MainPage.jsx @@ -72,6 +72,7 @@ const MainPage = () => { {newsList.map((news) => ( { + if (!dateStr) return ""; + const d = new Date(dateStr); + return `${d.getFullYear()}년 ${d.getMonth() + 1}월 ${d.getDate()}일 ${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`; +}; const NewsDetailPage = () => { + const { id } = useParams(); + const nav = useNavigate(); + const [news, setNews] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + const fetch = async () => { + try { + setLoading(true); + const res = await getNewsDetail(id); + setNews(res.data); + } catch (err) { + setError("뉴스를 불러오는데 실패했습니다."); + console.error(err); + } finally { + setLoading(false); + } + }; + fetch(); + }, [id]); + + const sentiment = sentimentMap[news?.analysis?.sentimentLabel] || sentimentMap.NEUTRAL; + const confidence = Math.round((news?.analysis?.sentimentScore || 0) * 100); + return (
- News Detail Page +
+ {loading ? ( +
로딩 중...
+ ) : error ? ( +
{error}
+ ) : news && ( +
+ + {/* 뉴스 본문 카드 */} +
+ {/* 감성 배지 + 제목 */} +
+ +
+

+ {news.title} +

+ + {/* 메타 정보 */} +
+ {formatDate(news.publishedAt)} + | + 출처: {news.publisher} + | + 신뢰도: {confidence}% +
+ + {/* 본문 */} +

+ {news.content} +

+ + {/* 원문 링크 */} + {news.originalLink && ( + + 원문 보기 + + + )} +
+ + {/* 영향 받는 코인 */} +
+

영향 받는 코인

+ {news.relatedCryptos?.length === 0 ? ( +

관련 코인 정보가 없습니다.

+ ) : ( +
+
+ 코인명 + 예상 영향 +
+ {news.relatedCryptos.map((coin) => ( +
nav(`/coindetail/${coin.ticker}`)} + > +
+
+ {coin.logoUrl ? ( + {coin.name} { + e.target.style.display = "none"; + e.target.parentElement.innerHTML = `${coin.ticker.slice(0, 2)}`; + }} + /> + ) : ( + {coin.ticker.slice(0, 2)} + )} +
+ {coin.name} ({coin.ticker}) +
+
+ +
+
+ ))} +
+ )} +
+ + {/* AI 분석 */} +
+

AI 종합 분석

+

+ {news.analysis?.summary || "분석 정보가 없습니다."} +

+
+ +
+ )} +
- ) -} + ); +}; -export default NewsDetailPage; \ No newline at end of file +export default NewsDetailPage;