diff --git a/src/components/Feed/FeedDetail.jsx b/src/components/Feed/FeedDetail.jsx new file mode 100644 index 0000000..0dd645b --- /dev/null +++ b/src/components/Feed/FeedDetail.jsx @@ -0,0 +1,31 @@ +import React from "react"; +import FeedBoard from "./FeedShow/FeedBoard"; +import * as S from "../../style/GlobalStyle"; +import styled from "styled-components"; + +const FeedDetail = ({ setShowDetail }) => { + console.log("detail"); + return ( + + + setShowDetail(false)} + src="/icon/X.svg" + alt="x" + /> + + + + ); +}; + +const BtnDiv = styled.div` + display: flex; + background-color: white; +`; + +const CloseButton = styled.img` + margin: 10px 10px 10px auto; +`; + +export default FeedDetail; diff --git a/src/components/Feed/FeedShow/Feed.jsx b/src/components/Feed/FeedShow/Feed.jsx new file mode 100644 index 0000000..38eee56 --- /dev/null +++ b/src/components/Feed/FeedShow/Feed.jsx @@ -0,0 +1,74 @@ +import React, { useEffect, useState } from "react"; +import FeedBoard from "./FeedBoard"; +import FeedOrder from "./FeedOrder"; +import FeedReturns from "./FeedReturns"; +import FeedVoteNot from "./FeedVoteNot"; +import FeedVoteYes from "./FeedVoteYes"; +import FeedDetail from "../FeedDetail"; +import styled from "styled-components"; +import { fetchAllFeed } from "../../../store/reducers/Feed/feed"; +import { useDispatch, useSelector } from "react-redux"; + +const Feed = () => { + const [showDetail, setShowDetail] = useState(false); + const toggleDetail = () => { + setShowDetail(true); + }; + + useEffect(() => { + setShowDetail(false); + dispatch(fetchAllFeed()); + }, []); + console.log(showDetail); + + const dispatch = useDispatch(); + const allFeedData = useSelector((state) => state.feed.allFeed); + console.log("allFeedData", allFeedData); + return ( + <> + {allFeedData.map((item, idx) => { + console.log("item", item); + return ( +
+ {item.isOrder ? ( + + ) : item.isProfit ? ( + + ) : item.isVote && item.myVote ? ( + + ) : item.isVote ? ( + + ) : ( + + )} +
+ ); + })} + + + {showDetail ? ( + <> + + + ) : ( + <> + )} + + + ); +}; + +const DetailContainer = styled.div` + width: 400px; + height: 100%; + background-color: #fff; + transform: translateX(${(props) => (props.$showdetail ? "0" : "100%")}); + transition: transform 0.3s ease; + position: absolute; + top: 0; + left: 0; + overflow: hidden; + z-index: 999; +`; + +export default Feed; diff --git a/src/components/Feed/FeedShow/FeedBoard.jsx b/src/components/Feed/FeedShow/FeedBoard.jsx new file mode 100644 index 0000000..9af8833 --- /dev/null +++ b/src/components/Feed/FeedShow/FeedBoard.jsx @@ -0,0 +1,52 @@ +import React from "react"; +import styled from "styled-components"; +import * as S from "../../../style/GlobalStyle"; + +const FeedBoard = ({ item, toggleDetail }) => { + console.log("board", item); + return ( + <> + + + {item.user.nickname} + {item.createdAt} + + + {item.body} + {item.photoUrl ? ( + + 본문 사진 + + ) : ( + <> + )} + + + + 좋아요 +
{item.like}
+
+ + 댓글 +
7
+
+
+
+ + ); +}; + +const Img = styled.img` + width: 100%; + /* object-fit: cover; */ +`; + +export default FeedBoard; diff --git a/src/components/Feed/FeedShow/FeedOrder.jsx b/src/components/Feed/FeedShow/FeedOrder.jsx new file mode 100644 index 0000000..674040e --- /dev/null +++ b/src/components/Feed/FeedShow/FeedOrder.jsx @@ -0,0 +1,98 @@ +import React from "react"; +import styled from "styled-components"; +import * as S from "../../../style/GlobalStyle"; + +//TODO : 로고 사진 변경 +import default_Img from "/icon/+.svg"; +const FeedOrder = ({ item }) => { + console.log("order", item); + const getLogoFileName = (name, code) => { + if (name.includes("스팩")) { + return "SPAC_230706"; + } else if (name.includes("ETN")) { + return "ETN_230706"; + } else if ( + name.includes("KODEX") || + name.includes("KOSEF") || + name.includes("KoAct") || + name.includes("TIGER") || + name.includes("ACE") || + name.includes("ARIRANG") || + name.includes("합성 H") || + name.includes("HANARO") || + name.includes("SOL") + ) { + return "ETF_230706"; + } else { + return `kr/${code}`; + } + }; + + const onErrorImg = (e) => { + e.target.src = default_Img; + }; + return ( + <> + + + {item.user.nickname} + {item.createdAt} + + + + + + 삼성생명 + 10주 + 매수 + + + + + + 좋아요 +
{item.like}
+
+ + 댓글 +
7
+
+
+
+ + ); +}; + +const QuantityDiv = styled.div` + margin-left: 7px; + color: #000; + font-size: 16px; + font-weight: 500; +`; + +const OrderDiv = styled.div` + margin-left: 7px; + color: #000; + font-size: 16px; + font-weight: 500; +`; + +export default FeedOrder; diff --git a/src/components/Feed/FeedShow/FeedReturns.jsx b/src/components/Feed/FeedShow/FeedReturns.jsx new file mode 100644 index 0000000..b5c2620 --- /dev/null +++ b/src/components/Feed/FeedShow/FeedReturns.jsx @@ -0,0 +1,51 @@ +import React from "react"; +import styled from "styled-components"; +import * as S from "../../../style/GlobalStyle"; + +const FeedReturns = ({ item }) => { + console.log("profit", item); + return ( + <> + + + {item.user.nickname} + {item.createdAt} + + + + + 나의 전체 수익률은? + {item.profit} + + + + + + 좋아요 +
{item.like}
+
+ + 댓글 +
7
+
+
+
+ + ); +}; + +const ReturnDiv = styled.div` + margin-left: auto; + color: ${(props) => + parseFloat(props.$returns) >= 0 ? "#ee2f2a" : "#2679ed"}; +`; + +export default FeedReturns; diff --git a/src/components/Feed/FeedShow/FeedVoteNot.jsx b/src/components/Feed/FeedShow/FeedVoteNot.jsx new file mode 100644 index 0000000..655ad21 --- /dev/null +++ b/src/components/Feed/FeedShow/FeedVoteNot.jsx @@ -0,0 +1,55 @@ +import React from "react"; +import styled from "styled-components"; +import * as S from "../../../style/GlobalStyle"; + +const FeedVoteNot = ({ item }) => { + console.log("votenot", item); + return ( + <> + + + {item.user.nickname} + {item.createdAt} + + + + {item.body} + + + O + + + X + + + + + + + ); +}; + +const ButtonWrapper = styled.div` + display: flex; + gap: 30px; + margin-bottom: 25px; + margin-top: 5px; +`; + +const VoteBtn = styled.button` + width: 150px; + height: 44px; + border: none; + border-radius: 10px; + background: ${(props) => props.color}; + + color: #fff; + font-size: 25px; + font-weight: 600; + + &:hover { + background: ${(props) => props.$hover}; + } +`; + +export default FeedVoteNot; diff --git a/src/components/Feed/FeedShow/FeedVoteYes.jsx b/src/components/Feed/FeedShow/FeedVoteYes.jsx new file mode 100644 index 0000000..bf8dda2 --- /dev/null +++ b/src/components/Feed/FeedShow/FeedVoteYes.jsx @@ -0,0 +1,79 @@ +import React from "react"; +import styled from "styled-components"; +import * as S from "../../../style/GlobalStyle"; + +const FeedVoteYes = ({ item }) => { + console.log("voteyes", item); + const calc = (num) => { + return Math.round((num / (item.vote.yes + item.vote.no)) * 100); + }; + return ( + <> + + + {item.user.nickname} + {item.createdAt} + + + + {item.body} + + + O
+ {calc(item.vote.yes)}% +
+ + + + + + X
+ {calc(item.vote.no)}% +
+
+
+
+
+ + ); +}; + +const BarDiv = styled.div` + position: relative; + width: 250px; + height: 30px; +`; + +const ODiv = styled.div` + position: absolute; + left: 0; + top: 0; + width: ${(props) => props.$width}%; + height: 30px; + border-radius: 10px 0px 0px 10px; + background: #bee4ff; +`; + +const XDiv = styled.div` + position: absolute; + right: 0; + top: 0; + width: ${(props) => props.$width}%; + height: 30px; + border-radius: 0px 10px 10px 0px; + background: #ffe3d7; +`; + +const OXWrapper = styled.div` + display: flex; + gap: 10px; + margin-bottom: 25px; + margin-top: 5px; +`; + +const OXDiv = styled.div` + text-align: center; + font-size: 15px; +`; + +export default FeedVoteYes; diff --git a/src/lib/apis/api.jsx b/src/lib/apis/api.jsx index ae786db..d53f757 100644 --- a/src/lib/apis/api.jsx +++ b/src/lib/apis/api.jsx @@ -22,3 +22,10 @@ export const baseUserInstance = axios.create({ }, }); +export const formdataInstance = axios.create({ + baseURL: BASE_URL, + headers: { + "Content-Type": "multipart/form-data", + Authorization: `Bearer ${getCookie("token")}`, + }, +}); diff --git a/src/lib/apis/feed.jsx b/src/lib/apis/feed.jsx new file mode 100644 index 0000000..8c9074f --- /dev/null +++ b/src/lib/apis/feed.jsx @@ -0,0 +1,60 @@ +import { baseUserInstance, formdataInstance } from "./api"; + +export const fetchAFeed = async (feedId) => { + const baseUrl = `/feed/${feedId}`; + try { + const response = await baseUserInstance.get(baseUrl); + const data = response.data; + return data; + } catch (err) { + console.error(err); + } +}; + +export const fetchMyFeed = async (userId) => { + const baseUrl = `/feed/user/${userId}`; + try { + const response = await baseUserInstance.get(baseUrl); + const data = response.data; + return data; + } catch (err) { + console.error(err); + } +}; + +export const fetchAllFeed = async () => { + const baseUrl = `/feed`; + try { + const response = await baseUserInstance.get(baseUrl); + const data = response.data; + return data; + } catch (err) { + console.error(err); + } +}; + +export const postBoardFeed = async (formData) => { + const baseUrl = "/feed"; + try { + const response = await formdataInstance.post(baseUrl, formData); + const data = response.data; + console.log(data); + return data; + } catch (err) { + console.error(err); + } +}; + +export const postVoteFeed = async (body) => { + const baseUrl = "/feed/vote"; + try { + const response = await baseUserInstance.post(baseUrl, { + body, + }); + const data = response.data; + console.log(data); + return data; + } catch (err) { + console.error(err); + } +}; diff --git a/src/routes/Feed/FeedPage.jsx b/src/routes/Feed/FeedPage.jsx index f2b34f7..c0613f0 100644 --- a/src/routes/Feed/FeedPage.jsx +++ b/src/routes/Feed/FeedPage.jsx @@ -1,9 +1,9 @@ import React, { useState } from "react"; import styled from "styled-components"; -import FeedWriting from "../../components/Feed/FeedWriting"; +import * as S from "../../style/GlobalStyle"; -//TODO : 로고 사진 변경 -import default_Img from "/icon/+.svg"; +import FeedWriting from "~/components/Feed/FeedWriting"; +import Feed from "../../components/Feed/FeedShow/Feed"; const FeedPage = () => { const [isWrite, setIsWrite] = useState(false); @@ -12,33 +12,8 @@ const FeedPage = () => { setIsWrite(true); }; - const getLogoFileName = (name, code) => { - if (name.includes("스팩")) { - return "SPAC_230706"; - } else if (name.includes("ETN")) { - return "ETN_230706"; - } else if ( - name.includes("KODEX") || - name.includes("KOSEF") || - name.includes("KoAct") || - name.includes("TIGER") || - name.includes("ACE") || - name.includes("ARIRANG") || - name.includes("합성 H") || - name.includes("HANARO") || - name.includes("SOL") - ) { - return "ETF_230706"; - } else { - return `kr/${code}`; - } - }; - - const onErrorImg = (e) => { - e.target.src = default_Img; - }; return ( - + {isWrite ? ( ) : ( @@ -48,168 +23,12 @@ const FeedPage = () => { )} - - - 게시글 쓰는 미나리 - 2024.03.08 - - - - 저번주 공시인데 괜찮아 보이네요! 시가배당률도 매년 올라가고 있어서 - 눈여겨 보는 중입니다. - - 본문 사진 - - - - 좋아요 -
3
-
- - 댓글 -
7
-
-
-
- - - 매수한 미나리 - 2024.03.08 - - - - - 삼성생명 - 10주 - 매수 - - - - - 좋아요 -
3
-
- - 댓글 -
7
-
-
-
- - - 수익률 공유한 미나리 - 2024.03.08 - - - - 나의 전체 수익률은? - 0.01% - - - - - 좋아요 -
3
-
- - 댓글 -
7
-
-
-
- - - 투표 아직 안 한 미나리 - 2024.03.08 - - - - 티웨이 항공 vs 에어부산, 에어부산이 더 오른다 - - - - O - - - X - - - - - - - 투표한 미나리 - 2024.03.08 - - - - 티웨이 항공 vs 에어부산, 에어부산이 더 오른다 - - - - O
- 32% -
- - - - - - X
- 68% -
-
-
-
+
-
+ ); }; -const Container = styled.div` - display: flex; - flex-direction: column; - width: 400px; - height: 100%; - position: relative; - overflow: hidden; - background-color: #f3f3f3; -`; - const WritingContainer = styled.div` display: flex; flex-direction: row; @@ -249,169 +68,4 @@ const FeedContainer = styled.div` } `; -const FeedWrapper = styled.div` - display: flex; - flex-direction: column; - background-color: white; - margin-bottom: 5px; -`; - -const UserDiv = styled.div` - margin: 20px 25px 0px 25px; -`; - -const UserNickname = styled.div` - font-size: 16px; - font-weight: 600; - line-height: normal; - margin-bottom: 5px; -`; - -const DateDiv = styled.div` - color: #c1c1c1; - font-size: 13px; - font-style: normal; - font-weight: 600; - line-height: normal; -`; - -const BodyWrapper = styled.div` - display: flex; - flex-direction: column; - align-items: center; - margin: 15px 25px 0px 25px; -`; - -const BodyDiv = styled.div` - margin-bottom: 15px; - font-weight: ${(props) => props.$weight || "400"}; -`; - -const Img = styled.img` - width: 100%; - /* object-fit: cover; */ -`; - -const BottomWrapper = styled.div` - display: flex; - margin: 20px 25px 0px 25px; - padding: 15px 0px; - border-top: 1px solid #dadada; - gap: 20px; -`; - -const IconDiv = styled.div` - display: flex; - align-items: center; -`; - -const Div = styled.div``; - -const StockWrapper = styled.div` - display: flex; - flex-direction: row; - align-items: center; - width: 95%; - padding: 11px 22px; - border-radius: 10px; - background-color: ${(props) => - props.$buy === "sell" - ? "#FFE3D7" - : props.$buy === "returns" - ? "#EFEFEF" - : "#BEE4FF"}; -`; - -const StockDiv = styled.div` - margin-left: ${(props) => props.$margin || "10px"}; - color: #000; - font-size: 18px; - font-style: normal; - font-weight: 600; - line-height: normal; -`; - -const QuantityDiv = styled.div` - margin-left: 7px; - color: #000; - font-size: 16px; - font-weight: 500; -`; - -const OrderDiv = styled.div` - margin-left: 7px; - color: #000; - font-size: 16px; - font-weight: 500; -`; - -const ReturnDiv = styled.div` - margin-left: auto; - color: ${(props) => - parseFloat(props.$returns) >= 0 ? "#ee2f2a" : "#2679ed"}; -`; - -const ButtonWrapper = styled.div` - display: flex; - gap: 30px; - margin-bottom: 25px; - margin-top: 5px; -`; - -const VoteBtn = styled.button` - width: 150px; - height: 44px; - border: none; - border-radius: 10px; - background: ${(props) => props.color}; - - color: #fff; - font-size: 25px; - font-weight: 600; - - &:hover { - background: ${(props) => props.$hover}; - } -`; - -const BarDiv = styled.div` - position: relative; - width: 250px; - height: 30px; - /* background-color: #ffe3d7; - border-radius: 10px; */ -`; - -const ODiv = styled.div` - position: absolute; - left: 0; - top: 0; - width: 32%; - height: 30px; - border-radius: 10px 0px 0px 10px; - background: #bee4ff; -`; - -const XDiv = styled.div` - position: absolute; - right: 0; - top: 0; - width: 68%; - height: 30px; - border-radius: 0px 10px 10px 0px; - background: #ffe3d7; -`; - -const OXWrapper = styled.div` - display: flex; - gap: 10px; - margin-bottom: 25px; - margin-top: 5px; -`; - -const OXDiv = styled.div` - text-align: center; - font-size: 15px; -`; - export default FeedPage; diff --git a/src/store/store.js b/src/store/store.js index 93b40c8..880a844 100644 --- a/src/store/store.js +++ b/src/store/store.js @@ -15,28 +15,29 @@ import logger from "redux-logger"; import tradingReducer from "./reducers/Trading/trading"; import chartValuesReducer from "./reducers/Trading/chartValues"; import indicatorValuesReducer from "./reducers/Trading/indicatorValues"; -import chartReducer from './reducers/Chart/chart.jsx'; -import companyReducer from './reducers/Chart/clickCompany.jsx'; -import clickIndicatorsReducer from './reducers/Chart/Indicators/clickIndicators.jsx'; -import getChartIndicatorReducer from './reducers/Chart/Indicators/chart.jsx'; -import getSubIndicatorReducer from './reducers/Chart/Indicators/sub.jsx'; +import chartReducer from "./reducers/Chart/chart.jsx"; +import companyReducer from "./reducers/Chart/clickCompany.jsx"; +import clickIndicatorsReducer from "./reducers/Chart/Indicators/clickIndicators.jsx"; +import getChartIndicatorReducer from "./reducers/Chart/Indicators/chart.jsx"; +import getSubIndicatorReducer from "./reducers/Chart/Indicators/sub.jsx"; import searchReducer from "./reducers/Trading/search"; import userReducer from "./reducers/User/user"; +import feedReducer from "./reducers/Feed/feed"; const rootPersistConfig = { key: "root", storage: storage, // whitelist: ["chartValues", "indicatorValues"], whitelist: [ - "user", - "chartValues", - "indicatorValues", - "search", + "user", + "chartValues", + "indicatorValues", + "search", "chart", "clickIndicator", - "getChartIndicator", + "getChartIndicator", "getSubIndicator", - "company" + "company", ], }; @@ -54,6 +55,7 @@ const rootReducer = persistReducer( getSubIndicator: getSubIndicatorReducer, search: searchReducer, user: userReducer, + feed: feedReducer, }) ); const myMiddlewares = [logger];