From 479594c446eb5d8092482a3a8e2b52e1259428b8 Mon Sep 17 00:00:00 2001 From: solssak Date: Thu, 27 Feb 2025 04:05:30 +0900 Subject: [PATCH 01/14] =?UTF-8?q?feat:=20=EC=A0=95=EB=B3=B4=20=ED=83=AD=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/component-presentation/InfoBox.tsx | 74 +++++++ app/koi-client/src/config/color.ts | 4 +- .../Stock/component/Stock/Information.tsx | 199 ++++++++++++++++++ .../component/Stock/component/Stock/index.tsx | 10 + 4 files changed, 285 insertions(+), 2 deletions(-) create mode 100644 app/koi-client/src/component-presentation/InfoBox.tsx create mode 100644 app/koi-client/src/page/@party@[partyId]/component/Stock/component/Stock/Information.tsx diff --git a/app/koi-client/src/component-presentation/InfoBox.tsx b/app/koi-client/src/component-presentation/InfoBox.tsx new file mode 100644 index 00000000..ac781e68 --- /dev/null +++ b/app/koi-client/src/component-presentation/InfoBox.tsx @@ -0,0 +1,74 @@ +import React, { CSSProperties } from 'react'; +import { styled } from '@linaria/react'; + +interface InfoBoxProps { + title?: string; + value: string; + valueColor?: CSSProperties['color']; + leftTime?: React.ReactNode; + changeTime?: React.ReactNode; +} + +const InfoBox: React.FC = ({ title, value, valueColor, leftTime, changeTime }) => { + return ( + + + + {leftTime} + {changeTime} + + {title && {title}} + + + {value} + + + ); +}; + +const Container = styled.div` + display: flex; + justify-content: space-between; + gap: 12px; + background-color: #252836; + border-radius: 8px; + padding: 16px; + overflow: hidden; + margin-bottom: 12px; +`; + +const Wrapper = styled.div` + display: flex; + align-items: center; + gap: 12px; +`; + +const TimeWrapper = styled.div` + display: flex; + flex-direction: column; + background-color: #374151; + padding: 12px 4px; + border-radius: 4px; + text-align: center; +`; + +const ContainerTitle = styled.div` + font-size: 20px; + line-height: 22px; + letter-spacing: 0.5px; +`; + +const ContainerBolder = styled.div` + font-size: 20px; + font-weight: bolder; + line-height: 22px; + display: flex; + align-items: center; + letter-spacing: 0.5px; +`; + +export default InfoBox; diff --git a/app/koi-client/src/config/color.ts b/app/koi-client/src/config/color.ts index d850b0d2..e7940879 100644 --- a/app/koi-client/src/config/color.ts +++ b/app/koi-client/src/config/color.ts @@ -1,5 +1,5 @@ export const blue = '#007ACC'; export const red = '#BD2C02'; -export const colorUp = '#3f8600'; -export const colorDown = '#cf1322'; +export const colorUp = '#F87171'; +export const colorDown = '#60A5FA'; diff --git a/app/koi-client/src/page/@party@[partyId]/component/Stock/component/Stock/Information.tsx b/app/koi-client/src/page/@party@[partyId]/component/Stock/component/Stock/Information.tsx new file mode 100644 index 00000000..eaadb1c1 --- /dev/null +++ b/app/koi-client/src/page/@party@[partyId]/component/Stock/component/Stock/Information.tsx @@ -0,0 +1,199 @@ +import dayjs from 'dayjs'; +import { useAtomValue } from 'jotai'; +import { css } from '@emotion/react'; +import styled from '@emotion/styled'; +import { getDateDistance } from '@toss/date'; +import { commaizeNumber, objectEntries } from '@toss/utils'; + +import InfoBox from '../../../../../../component-presentation/InfoBox'; +import { colorDown, colorUp } from '../../../../../../config/color'; +import { Query } from '../../../../../../hook'; +import prependZero from '../../../../../../service/prependZero'; +import { UserStore } from '../../../../../../store'; + +interface Props { + stockId: string; +} + +const Information = ({ stockId }: Props) => { + const supabaseSession = useAtomValue(UserStore.supabaseSession); + const userId = supabaseSession?.user.id; + const { data: stock } = Query.Stock.useQueryStock(stockId); + const { user } = Query.Stock.useUser({ stockId, userId }); + + if (!user || !stock) { + return
불러오는 중.
; + } + + const gameTime = `${prependZero( + getDateDistance(dayjs(stock.startedTime).toDate(), new Date()).minutes, + 2, + )}:${prependZero(getDateDistance(dayjs(stock.startedTime).toDate(), new Date()).seconds, 2)}`; + + const myInfos = objectEntries(stock.companies).reduce((myInfos, [company, companyInfos]) => { + companyInfos.forEach((companyInfo, idx) => { + if (companyInfo.정보.some((name) => name === userId)) { + myInfos.push({ + company, + price: idx > 0 ? companyInfo.가격 - companyInfos[idx - 1].가격 : 0, + timeIdx: idx, + }); + } + }); + return myInfos; + }, [] as Array<{ company: string; timeIdx: number; price: number }>); + + const gameTimeInSeconds = parseInt(gameTime.split(':')[0], 10) * 60 + parseInt(gameTime.split(':')[1], 10); + + const { futureInfos, pastInfos } = myInfos.reduce( + (acc, info) => { + const infoTimeInSeconds = stock?.fluctuationsInterval && info.timeIdx * 60 * stock.fluctuationsInterval; + + if (infoTimeInSeconds >= gameTimeInSeconds) { + let index = acc.futureInfos.findIndex((item) => item.timeIdx > info.timeIdx); + if (index === -1) index = acc.futureInfos.length; + acc.futureInfos.splice(index, 0, info); + } else { + let index = acc.pastInfos.findIndex((item) => item.timeIdx < info.timeIdx); + if (index === -1) index = acc.pastInfos.length; + acc.pastInfos.splice(index, 0, info); + } + + return acc; + }, + { + futureInfos: [] as Array<{ company: string; timeIdx: number; price: number }>, + pastInfos: [] as Array<{ company: string; timeIdx: number; price: number }>, + }, + ); + + const gameTimeInMinutes = Math.ceil(parseInt(gameTime.split(':')[0], 10) / 5) * 5; + + return ( + + +

앞으로의 정보

+

{futureInfos.length}개 보유

+
+ {futureInfos.map(({ company, price, timeIdx }) => { + const infoTimeInMinutes = timeIdx * stock.fluctuationsInterval; + const remainingTime = Math.ceil((infoTimeInMinutes - gameTimeInMinutes) / 5) * 5; + + return ( + = 0 ? '▲' : '▼'}${commaizeNumber(Math.abs(price))}`} + valueColor={price >= 0 ? colorUp : colorDown} + leftTime={ +
+ {remainingTime < 3 ? `🚨 임박` : `${Math.abs(remainingTime)}분 후`} +
+ } + changeTime={ +
+ {prependZero(timeIdx * stock.fluctuationsInterval, 2)}:00 +
+ } + /> + ); + })} + + + + +

지난 정보

+

{pastInfos.length}개 보유

+
+ + + {pastInfos.map(({ company, price, timeIdx }) => { + const pastTime = gameTimeInMinutes - timeIdx * stock.fluctuationsInterval; + console.log('pastTime', pastTime); + return ( + = 0 ? '▲' : '▼'}${commaizeNumber(Math.abs(price))}`} + valueColor={price >= 0 ? colorUp : colorDown} + leftTime={ +
+ {pastTime < 5 ? '방금 전' : `${pastTime}분 전`} +
+ } + changeTime={ +
+ {prependZero(timeIdx * stock.fluctuationsInterval, 2)}:00 +
+ } + /> + ); + })} +
+
+ ); +}; + +const Container = styled.div` + width: 100%; +`; + +const TitleWrapper = styled.div` + display: flex; + align-items: center; + gap: 12px; + margin-bottom: 8px; +`; + +const H1 = styled.div` + font-size: 16px; + line-height: 22px; +`; + +const H2 = styled.div` + padding: 2px 8px; + font-size: 10px; + line-height: 22px; + color: #c084fc; + border-radius: 16px; + background-color: rgba(192, 132, 252, 0.2); +`; + +const Divider = styled.div` + border-top: 1px solid #374151; + margin-top: 8px; + margin-bottom: 8px; +`; + +const DimContainer = styled.div` + opacity: 0.5; + pointer-events: none; +`; + +export default Information; diff --git a/app/koi-client/src/page/@party@[partyId]/component/Stock/component/Stock/index.tsx b/app/koi-client/src/page/@party@[partyId]/component/Stock/component/Stock/index.tsx index 4ba78334..c790286a 100644 --- a/app/koi-client/src/page/@party@[partyId]/component/Stock/component/Stock/index.tsx +++ b/app/koi-client/src/page/@party@[partyId]/component/Stock/component/Stock/index.tsx @@ -6,6 +6,7 @@ import { Tabs, TabsProps } from 'antd'; import { css } from '@emotion/react'; import Home from './Home'; import Buy from './Buy'; +import Information from './Information'; import Sell from './Sell'; import Rule from './Rule'; @@ -18,6 +19,11 @@ const items: TabsProps['items'] = [ key: '홈', label: '홈', }, + { + children: <>, + key: '정보', + label: '정보', + }, { children: <>, key: '사기', @@ -53,6 +59,9 @@ const Stock = ({ stockId }: Props) => { case '홈': setSearchParams({ page: '홈' }, { replace: true }); break; + case '정보': + setSearchParams({ page: '정보' }, { replace: true }); + break; case '사기': setSearchParams({ page: '사기' }, { replace: true }); break; @@ -103,6 +112,7 @@ const Stock = ({ stockId }: Props) => { // 기록: , 룰: , 사기: , + 정보: , 팔기: , 홈: , }} From 31a5694e216abddb413a4b956639ebe6e7e5b18e Mon Sep 17 00:00:00 2001 From: solssak Date: Thu, 6 Mar 2025 02:45:56 +0900 Subject: [PATCH 02/14] =?UTF-8?q?feat:=20=EC=A3=BC=EC=8B=9D=20=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=20=EC=8B=9C=EA=B0=84=20=EC=97=85=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=8A=B8=20=EB=A1=9C=EC=A7=81=20=EB=B3=80=EA=B2=BD=20=EB=B0=8F?= =?UTF-8?q?=20=EC=B5=9C=EC=A0=81=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - setInterval을 활용해 1초마다 gameTime 갱신 --- .../src/hook/query/Stock/useQueryStock.tsx | 4 +- .../Stock/component/Stock/Information.tsx | 53 ++++++++++++++----- 2 files changed, 42 insertions(+), 15 deletions(-) diff --git a/app/koi-client/src/hook/query/Stock/useQueryStock.tsx b/app/koi-client/src/hook/query/Stock/useQueryStock.tsx index 9da788bd..2625bb2b 100644 --- a/app/koi-client/src/hook/query/Stock/useQueryStock.tsx +++ b/app/koi-client/src/hook/query/Stock/useQueryStock.tsx @@ -12,7 +12,7 @@ interface Options { } const useQueryStock = (stockId: string | undefined, options?: Options) => { - const { data } = useQuery({ + const { data, refetch } = useQuery({ api: { hostname: serverApiUrl, method: 'GET', @@ -45,7 +45,7 @@ const useQueryStock = (stockId: string | undefined, options?: Options) => { [data?.companies, data?.startedTime, timeIdx], ); - return { companiesPrice, data, timeIdx }; + return { companiesPrice, data, refetch, timeIdx }; }; export default useQueryStock; diff --git a/app/koi-client/src/page/@party@[partyId]/component/Stock/component/Stock/Information.tsx b/app/koi-client/src/page/@party@[partyId]/component/Stock/component/Stock/Information.tsx index eaadb1c1..c2eb3389 100644 --- a/app/koi-client/src/page/@party@[partyId]/component/Stock/component/Stock/Information.tsx +++ b/app/koi-client/src/page/@party@[partyId]/component/Stock/component/Stock/Information.tsx @@ -5,6 +5,7 @@ import styled from '@emotion/styled'; import { getDateDistance } from '@toss/date'; import { commaizeNumber, objectEntries } from '@toss/utils'; +import { useEffect, useRef, useState } from 'react'; import InfoBox from '../../../../../../component-presentation/InfoBox'; import { colorDown, colorUp } from '../../../../../../config/color'; import { Query } from '../../../../../../hook'; @@ -15,20 +16,51 @@ interface Props { stockId: string; } +const getFormattedGameTime = (startTime?: string) => { + if (!startTime) return '00:00'; + + return `${prependZero(getDateDistance(dayjs(startTime).toDate(), new Date()).minutes, 2)}:${prependZero( + getDateDistance(dayjs(startTime).toDate(), new Date()).seconds, + 2, + )}`; +}; + const Information = ({ stockId }: Props) => { const supabaseSession = useAtomValue(UserStore.supabaseSession); const userId = supabaseSession?.user.id; - const { data: stock } = Query.Stock.useQueryStock(stockId); + const { data: stock, refetch } = Query.Stock.useQueryStock(stockId); const { user } = Query.Stock.useUser({ stockId, userId }); + const [gameTime, setGameTime] = useState(getFormattedGameTime(stock?.startedTime)); + const gameTimeRef = useRef(gameTime); + + useEffect(() => { + if (!stock?.startedTime) return () => {}; + + const interval = setInterval(() => { + const newGameTime = getFormattedGameTime(stock.startedTime); + + if (newGameTime !== gameTimeRef.current) { + const newGameMinute = parseInt(newGameTime.split(':')[0], 10); + const lastGameMinute = parseInt(gameTimeRef.current.split(':')[0], 10); + + gameTimeRef.current = newGameTime; + setGameTime(newGameTime); + + if (newGameMinute !== lastGameMinute) { + refetch(); + } + } + }, 1000); + + return () => clearInterval(interval); + }, [stock?.startedTime, refetch]); if (!user || !stock) { return
불러오는 중.
; } - const gameTime = `${prependZero( - getDateDistance(dayjs(stock.startedTime).toDate(), new Date()).minutes, - 2, - )}:${prependZero(getDateDistance(dayjs(stock.startedTime).toDate(), new Date()).seconds, 2)}`; + const gameTimeInSeconds = parseInt(gameTime.split(':')[0], 10) * 60 + parseInt(gameTime.split(':')[1], 10); + const gameTimeInMinutes = Math.ceil(parseInt(gameTime.split(':')[0], 10)); const myInfos = objectEntries(stock.companies).reduce((myInfos, [company, companyInfos]) => { companyInfos.forEach((companyInfo, idx) => { @@ -43,8 +75,6 @@ const Information = ({ stockId }: Props) => { return myInfos; }, [] as Array<{ company: string; timeIdx: number; price: number }>); - const gameTimeInSeconds = parseInt(gameTime.split(':')[0], 10) * 60 + parseInt(gameTime.split(':')[1], 10); - const { futureInfos, pastInfos } = myInfos.reduce( (acc, info) => { const infoTimeInSeconds = stock?.fluctuationsInterval && info.timeIdx * 60 * stock.fluctuationsInterval; @@ -67,8 +97,6 @@ const Information = ({ stockId }: Props) => { }, ); - const gameTimeInMinutes = Math.ceil(parseInt(gameTime.split(':')[0], 10) / 5) * 5; - return ( @@ -77,7 +105,7 @@ const Information = ({ stockId }: Props) => { {futureInfos.map(({ company, price, timeIdx }) => { const infoTimeInMinutes = timeIdx * stock.fluctuationsInterval; - const remainingTime = Math.ceil((infoTimeInMinutes - gameTimeInMinutes) / 5) * 5; + const remainingTime = infoTimeInMinutes - gameTimeInMinutes; return ( { letter-spacing: 0.5px; `} > - {remainingTime < 3 ? `🚨 임박` : `${Math.abs(remainingTime)}분 후`} + {remainingTime <= 1 ? `🚨 임박` : `${remainingTime}분 후`} } changeTime={ @@ -122,7 +150,6 @@ const Information = ({ stockId }: Props) => { {pastInfos.map(({ company, price, timeIdx }) => { const pastTime = gameTimeInMinutes - timeIdx * stock.fluctuationsInterval; - console.log('pastTime', pastTime); return ( { letter-spacing: 0.5px; `} > - {pastTime < 5 ? '방금 전' : `${pastTime}분 전`} + {pastTime <= 1 ? '방금 전' : `${pastTime}분 전`} } changeTime={ From 32e974ca4cadf491a3c99cd0671a83949939d42e Mon Sep 17 00:00:00 2001 From: solssak Date: Thu, 6 Mar 2025 04:21:50 +0900 Subject: [PATCH 03/14] =?UTF-8?q?feat(=ED=99=88):=20=EB=82=B4=20=EC=98=88?= =?UTF-8?q?=EC=B8=A1=20=EC=A0=95=EB=B3=B4=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/component-presentation/InfoBox.tsx | 2 - .../Stock/component/Stock/Home/Home.tsx | 124 +++++++++++++++++- .../Stock/component/Stock/Information.tsx | 3 + .../component/Stock/component/Stock/index.tsx | 71 ++-------- 4 files changed, 133 insertions(+), 67 deletions(-) diff --git a/app/koi-client/src/component-presentation/InfoBox.tsx b/app/koi-client/src/component-presentation/InfoBox.tsx index ac781e68..7ed2cfec 100644 --- a/app/koi-client/src/component-presentation/InfoBox.tsx +++ b/app/koi-client/src/component-presentation/InfoBox.tsx @@ -33,12 +33,10 @@ const InfoBox: React.FC = ({ title, value, valueColor, leftTime, c const Container = styled.div` display: flex; justify-content: space-between; - gap: 12px; background-color: #252836; border-radius: 8px; padding: 16px; overflow: hidden; - margin-bottom: 12px; `; const Wrapper = styled.div` diff --git a/app/koi-client/src/page/@party@[partyId]/component/Stock/component/Stock/Home/Home.tsx b/app/koi-client/src/page/@party@[partyId]/component/Stock/component/Stock/Home/Home.tsx index 37f2ce83..bd7a3d60 100644 --- a/app/koi-client/src/page/@party@[partyId]/component/Stock/component/Stock/Home/Home.tsx +++ b/app/koi-client/src/page/@party@[partyId]/component/Stock/component/Stock/Home/Home.tsx @@ -1,14 +1,20 @@ import { Flex } from 'antd'; import { useAtomValue } from 'jotai'; -import { commaizeNumber } from '@toss/utils'; +import { commaizeNumber, objectEntries } from '@toss/utils'; import styled from '@emotion/styled'; +import { getDateDistance } from '@toss/date'; +import dayjs from 'dayjs'; +import { useEffect, useRef, useState } from 'react'; +import { css } from '@emotion/react'; import { UserStore } from '../../../../../../../store'; import { Query } from '../../../../../../../hook'; -import DrawStockInfo from '../DrawInfo'; import { MyLevel } from './MyLevel'; import Card from '../../../../../../../component-presentation/Card'; import * as COLOR from '../../../../../../../config/color'; import StartLoan from '../StartLoan'; +import prependZero from '../../../../../../../service/prependZero'; +import InfoBox from '../../../../../../../component-presentation/InfoBox'; +import { colorDown, colorUp } from '../../../../../../../config/color'; const getProfitRatio = (v: number) => ((v / 1000000) * 100 - 100).toFixed(2); @@ -16,20 +22,91 @@ interface Props { stockId: string; } +const getFormattedGameTime = (startTime?: string) => { + if (!startTime) return '00:00'; + + return `${prependZero(getDateDistance(dayjs(startTime).toDate(), new Date()).minutes, 2)}:${prependZero( + getDateDistance(dayjs(startTime).toDate(), new Date()).seconds, + 2, + )}`; +}; + const Home = ({ stockId }: Props) => { const supabaseSession = useAtomValue(UserStore.supabaseSession); const userId = supabaseSession?.user.id; - const { data: stock } = Query.Stock.useQueryStock(stockId); + const { data: stock, refetch } = Query.Stock.useQueryStock(stockId); const { data: users } = Query.Stock.useUserList(stockId); const { user } = Query.Stock.useUser({ stockId, userId }); const { allSellPrice, allUserSellPriceDesc } = Query.Stock.useAllSellPrice({ stockId, userId }); + const [gameTime, setGameTime] = useState(getFormattedGameTime(stock?.startedTime)); + const gameTimeRef = useRef(gameTime); + + useEffect(() => { + if (!stock?.startedTime) return () => {}; + + const interval = setInterval(() => { + const newGameTime = getFormattedGameTime(stock.startedTime); + + if (newGameTime !== gameTimeRef.current) { + const newGameMinute = parseInt(newGameTime.split(':')[0], 10); + const lastGameMinute = parseInt(gameTimeRef.current.split(':')[0], 10); + + gameTimeRef.current = newGameTime; + setGameTime(newGameTime); + + if (newGameMinute !== lastGameMinute) { + refetch(); + } + } + }, 1000); + + return () => clearInterval(interval); + }, [stock?.startedTime, refetch]); if (!user || !stock) { return
불러오는 중.
; } + const gameTimeInSeconds = parseInt(gameTime.split(':')[0], 10) * 60 + parseInt(gameTime.split(':')[1], 10); + const gameTimeInMinutes = Math.ceil(parseInt(gameTime.split(':')[0], 10)); + + const myInfos = objectEntries(stock.companies).reduce((myInfos, [company, companyInfos]) => { + companyInfos.forEach((companyInfo, idx) => { + if (companyInfo.정보.some((name) => name === userId)) { + myInfos.push({ + company, + price: idx > 0 ? companyInfo.가격 - companyInfos[idx - 1].가격 : 0, + timeIdx: idx, + }); + } + }); + return myInfos; + }, [] as Array<{ company: string; timeIdx: number; price: number }>); + + const { futureInfos, pastInfos } = myInfos.reduce( + (acc, info) => { + const infoTimeInSeconds = stock?.fluctuationsInterval && info.timeIdx * 60 * stock.fluctuationsInterval; + + if (infoTimeInSeconds >= gameTimeInSeconds) { + let index = acc.futureInfos.findIndex((item) => item.timeIdx > info.timeIdx); + if (index === -1) index = acc.futureInfos.length; + acc.futureInfos.splice(index, 0, info); + } else { + let index = acc.pastInfos.findIndex((item) => item.timeIdx < info.timeIdx); + if (index === -1) index = acc.pastInfos.length; + acc.pastInfos.splice(index, 0, info); + } + + return acc; + }, + { + futureInfos: [] as Array<{ company: string; timeIdx: number; price: number }>, + pastInfos: [] as Array<{ company: string; timeIdx: number; price: number }>, + }, + ); + const allProfitDesc = allUserSellPriceDesc() .map(({ userId, allSellPrice }) => { const user = users.find((v) => v.userId === userId); @@ -90,10 +167,43 @@ const Home = ({ stockId }: Props) => { } />
- -

내가 가진 정보

- -
+

내 예측 정보

+ {futureInfos.slice(0, 2).map(({ company, price, timeIdx }) => { + const infoTimeInMinutes = timeIdx * stock.fluctuationsInterval; + const remainingTime = infoTimeInMinutes - gameTimeInMinutes; + + return ( + = 0 ? '▲' : '▼'}${commaizeNumber(Math.abs(price))}`} + valueColor={price >= 0 ? colorUp : colorDown} + leftTime={ +
+ {remainingTime <= 1 ? `🚨 임박` : `${remainingTime}분 후`} +
+ } + changeTime={ +
+ {prependZero(timeIdx * stock.fluctuationsInterval, 2)}:00 +
+ } + /> + ); + })}
diff --git a/app/koi-client/src/page/@party@[partyId]/component/Stock/component/Stock/Information.tsx b/app/koi-client/src/page/@party@[partyId]/component/Stock/component/Stock/Information.tsx index c2eb3389..868bf8dd 100644 --- a/app/koi-client/src/page/@party@[partyId]/component/Stock/component/Stock/Information.tsx +++ b/app/koi-client/src/page/@party@[partyId]/component/Stock/component/Stock/Information.tsx @@ -189,6 +189,9 @@ const Information = ({ stockId }: Props) => { const Container = styled.div` width: 100%; + display: flex; + flex-direction: column; + gap: 12px; `; const TitleWrapper = styled.div` diff --git a/app/koi-client/src/page/@party@[partyId]/component/Stock/component/Stock/index.tsx b/app/koi-client/src/page/@party@[partyId]/component/Stock/component/Stock/index.tsx index f26dbbba..cdab850f 100644 --- a/app/koi-client/src/page/@party@[partyId]/component/Stock/component/Stock/index.tsx +++ b/app/koi-client/src/page/@party@[partyId]/component/Stock/component/Stock/index.tsx @@ -1,11 +1,9 @@ -import React, { Suspense, useCallback } from 'react'; +import { Suspense, useCallback } from 'react'; import styled from '@emotion/styled'; import { useSearchParams } from 'react-router-dom'; import { SwitchCase } from '@toss/react'; import Buy from './Buy'; import Information from './Information'; -import Sell from './Sell'; -import Rule from './Rule'; import { Tabs, type TabsProps } from './Tabs'; import Home from './Home/Home'; @@ -15,29 +13,12 @@ const items: TabsProps['items'] = [ label: '홈', }, { - children: <>, - key: '정보', - label: '정보', - }, - { - children: <>, - key: '사기', - label: '사기', + key: '주식', + label: '주식', }, { - children: <>, - key: '팔기', - label: '팔기', - }, - // { - // children: <>, - // key: '기록', - // label: '기록', - // }, - { - children: <>, - key: '룰', - label: '룰', + key: '정보', + label: '정보', }, ]; @@ -47,28 +28,18 @@ interface Props { const Stock = ({ stockId }: Props) => { const [searchParams, setSearchParams] = useSearchParams(); - const onClickTab = useCallback( (key: string) => { switch (key) { case '홈': setSearchParams({ page: '홈' }, { replace: true }); break; + case '주식': + setSearchParams({ page: '주식' }, { replace: true }); + break; case '정보': setSearchParams({ page: '정보' }, { replace: true }); break; - case '사기': - setSearchParams({ page: '사기' }, { replace: true }); - break; - case '팔기': - setSearchParams({ page: '팔기' }, { replace: true }); - break; - // case '기록': - // setSearchParams({ page: '기록' }, { replace: true }); - // break; - case '룰': - setSearchParams({ page: '룰' }, { replace: true }); - break; default: setSearchParams({ page: '홈' }, { replace: true }); break; @@ -85,32 +56,16 @@ const Stock = ({ stockId }: Props) => { , + // 룰: , + 정보: , 주식: , 홈: , }} defaultComponent={} /> - - - }> - , - 룰: , - 사기: , - 정보: , - 팔기: , - 홈: , - }} - defaultComponent={} - /> - - - - {/* */} - + + + ); }; From b6beada416a6604d062d3d72d6704524e10539af Mon Sep 17 00:00:00 2001 From: solssak Date: Sat, 8 Mar 2025 02:09:11 +0900 Subject: [PATCH 04/14] style: update pastInfo styles --- .../component/Stock/component/Stock/Information.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/koi-client/src/page/@party@[partyId]/component/Stock/component/Stock/Information.tsx b/app/koi-client/src/page/@party@[partyId]/component/Stock/component/Stock/Information.tsx index 868bf8dd..39f36d72 100644 --- a/app/koi-client/src/page/@party@[partyId]/component/Stock/component/Stock/Information.tsx +++ b/app/koi-client/src/page/@party@[partyId]/component/Stock/component/Stock/Information.tsx @@ -222,6 +222,9 @@ const Divider = styled.div` `; const DimContainer = styled.div` + display: flex; + flex-direction: column; + gap: 12px; opacity: 0.5; pointer-events: none; `; From 461673935c3faf2f37123ab864aca9a4c1f92388 Mon Sep 17 00:00:00 2001 From: solssak Date: Sat, 8 Mar 2025 02:10:36 +0900 Subject: [PATCH 05/14] refactor: move page change logic to Home component --- .../component/Stock/component/Stock/Tabs.tsx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/app/koi-client/src/page/@party@[partyId]/component/Stock/component/Stock/Tabs.tsx b/app/koi-client/src/page/@party@[partyId]/component/Stock/component/Stock/Tabs.tsx index 74dbef95..cbb8afd8 100644 --- a/app/koi-client/src/page/@party@[partyId]/component/Stock/component/Stock/Tabs.tsx +++ b/app/koi-client/src/page/@party@[partyId]/component/Stock/component/Stock/Tabs.tsx @@ -1,6 +1,7 @@ -import React, { useCallback } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import styled from '@emotion/styled'; import { css } from '@emotion/react'; +import { useSearchParams } from 'react-router-dom'; export type TabsProps = { items: { @@ -12,7 +13,14 @@ export type TabsProps = { }; export const Tabs = ({ items, defaultActiveKey, onChange }: TabsProps) => { - const [currentTab, setCurrentTab] = React.useState(() => defaultActiveKey); + const [searchParams] = useSearchParams(); + const pageFromURL = searchParams.get('page') || defaultActiveKey; // ✅ URL에서 page 값 가져오기 + + const [currentTab, setCurrentTab] = useState(pageFromURL); + + useEffect(() => { + setCurrentTab(pageFromURL); + }, [pageFromURL]); const handleTabClick = useCallback( (key: string) => { From af4ddd5e4b853a1f73672c2557c4d52629bd914b Mon Sep 17 00:00:00 2001 From: solssak Date: Sat, 8 Mar 2025 02:13:18 +0900 Subject: [PATCH 06/14] refactor(Home): Improve stock info display and refactor future data handling - Enhanced the display of stock-related information - Added a structured section for future predictions with a "View All" option - Improved sorting and handling of past and future data - Updated UI elements for better readability and user experience --- .../Stock/component/Stock/Home/Home.tsx | 147 +++++++++++++----- 1 file changed, 106 insertions(+), 41 deletions(-) diff --git a/app/koi-client/src/page/@party@[partyId]/component/Stock/component/Stock/Home/Home.tsx b/app/koi-client/src/page/@party@[partyId]/component/Stock/component/Stock/Home/Home.tsx index be82a2e7..6f480865 100644 --- a/app/koi-client/src/page/@party@[partyId]/component/Stock/component/Stock/Home/Home.tsx +++ b/app/koi-client/src/page/@party@[partyId]/component/Stock/component/Stock/Home/Home.tsx @@ -85,7 +85,7 @@ const Home = ({ stockId }: Props) => { return myInfos; }, [] as Array<{ company: string; timeIdx: number; price: number }>); - const { futureInfos, pastInfos } = myInfos.reduce( + const { futureInfos } = myInfos.reduce( (acc, info) => { const infoTimeInSeconds = stock?.fluctuationsInterval && info.timeIdx * 60 * stock.fluctuationsInterval; @@ -166,45 +166,57 @@ const Home = ({ stockId }: Props) => { stock.isVisibleRank ? <>{allProfitDesc.findIndex((v) => v.userId === userId) + 1}위 : <> } /> -
-

내 예측 정보

- {futureInfos.slice(0, 2).map(({ company, price, timeIdx }) => { - const infoTimeInMinutes = timeIdx * stock.fluctuationsInterval; - const remainingTime = infoTimeInMinutes - gameTimeInMinutes; - - return ( - = 0 ? '▲' : '▼'}${commaizeNumber(Math.abs(price))}`} - valueColor={price >= 0 ? colorUp : colorDown} - leftTime={ -
- {remainingTime <= 1 ? `🚨 임박` : `${remainingTime}분 후`} -
- } - changeTime={ -
- {prependZero(timeIdx * stock.fluctuationsInterval, 2)}:00 -
- } - /> - ); - })} + + + +

내 예측 정보

+ +
총 {myInfos.length}개 보유
+
+
+
전체보기 >
+
+

현재 시각 이후의 정보 최대 2개가 표시됩니다

+ + {futureInfos.slice(0, 2).map(({ company, price, timeIdx }) => { + const infoTimeInMinutes = timeIdx * stock.fluctuationsInterval; + const remainingTime = infoTimeInMinutes - gameTimeInMinutes; + + return ( + = 0 ? '▲' : '▼'}${commaizeNumber(Math.abs(price))}`} + valueColor={price >= 0 ? colorUp : colorDown} + leftTime={ +
+ {remainingTime <= 1 ? `🚨 임박` : `${remainingTime}분 후`} +
+ } + changeTime={ +
+ {prependZero(timeIdx * stock.fluctuationsInterval, 2)}:00 +
+ } + /> + ); + })} +
+
@@ -216,12 +228,65 @@ const Container = styled.div` display: flex; flex-direction: column; width: 100%; - height: 100%; gap: 12px; - padding: 12px 0 100px 0; + padding: 12px 0 20px 0; flex: 1 1 0; `; +const Wrapper = styled.div` + width: 100%; +`; + +const TitleWrapper = styled.div` + display: flex; + justify-content: space-between; + align-items: center; + width: 100%; +`; + +const LeftSection = styled.div` + display: flex; + align-items: center; + gap: 8px; +`; + +const H3 = styled.h3` + font-size: 16px; + margin: 0; +`; + +const H5 = styled.h5` + font-size: 10px; + color: #9ca3af; + margin: 0; + padding: 8px 8px; + cursor: pointer; +`; +const H4 = styled.h4` + font-size: 10px; +`; + +const H6 = styled.h6` + font-size: 12px; + margin: 0; + color: #c084fc; +`; + +const H6Wrapper = styled.div` + display: flex; + align-items: center; + justify-content: center; + padding: 8px 8px; + background-color: rgba(192, 132, 252, 0.2); + border-radius: 20px; +`; + +const FutuerInfoWrapper = styled.div` + display: flex; + flex-direction: column; + gap: 12px; +`; + // TODO: 만약 영역이 겹치는 이슈가 발생 시 수정 const StickyBottom = styled.div` position: absolute; From b36bf02d5d642f693dade17f808f0a7471b5212f Mon Sep 17 00:00:00 2001 From: solssak Date: Sat, 8 Mar 2025 02:15:38 +0900 Subject: [PATCH 07/14] feat(Home): Add navigation logic to update URL query parameters - Introduced `useNavigate` and `useLocation` from `react-router-dom` - Implemented a function to modify the `page` query parameter on button click - Enhanced UX by allowing users to navigate to a detailed info page --- .../component/Stock/component/Stock/Home/Home.tsx | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/app/koi-client/src/page/@party@[partyId]/component/Stock/component/Stock/Home/Home.tsx b/app/koi-client/src/page/@party@[partyId]/component/Stock/component/Stock/Home/Home.tsx index 6f480865..fba284bc 100644 --- a/app/koi-client/src/page/@party@[partyId]/component/Stock/component/Stock/Home/Home.tsx +++ b/app/koi-client/src/page/@party@[partyId]/component/Stock/component/Stock/Home/Home.tsx @@ -6,6 +6,7 @@ import { getDateDistance } from '@toss/date'; import dayjs from 'dayjs'; import { useEffect, useRef, useState } from 'react'; import { css } from '@emotion/react'; +import { useNavigate, useLocation } from 'react-router-dom'; import { UserStore } from '../../../../../../../store'; import { Query } from '../../../../../../../hook'; import { MyLevel } from './MyLevel'; @@ -43,6 +44,15 @@ const Home = ({ stockId }: Props) => { const [gameTime, setGameTime] = useState(getFormattedGameTime(stock?.startedTime)); const gameTimeRef = useRef(gameTime); + const navigate = useNavigate(); + const location = useLocation(); + + const handlePageChange = () => { + const params = new URLSearchParams(location.search); + params.set('page', '정보'); + navigate(`${location.pathname}?${params.toString()}`, { replace: true }); + }; + useEffect(() => { if (!stock?.startedTime) return () => {}; @@ -175,7 +185,7 @@ const Home = ({ stockId }: Props) => {
총 {myInfos.length}개 보유
-
전체보기 >
+
전체보기 >

현재 시각 이후의 정보 최대 2개가 표시됩니다

@@ -262,6 +272,7 @@ const H5 = styled.h5` padding: 8px 8px; cursor: pointer; `; + const H4 = styled.h4` font-size: 10px; `; From bcab125d5c00aafda607fed704e7129df857168b Mon Sep 17 00:00:00 2001 From: solssak Date: Sat, 8 Mar 2025 02:21:39 +0900 Subject: [PATCH 08/14] refactor(Home): Simplify futureInfos extraction using filter and sort - Replaced reduce with filter and sort for better readability - Removed unnecessary pastInfos logic - Improved performance by avoiding redundant iterations --- .../Stock/component/Stock/Home/Home.tsx | 25 ++++--------------- 1 file changed, 5 insertions(+), 20 deletions(-) diff --git a/app/koi-client/src/page/@party@[partyId]/component/Stock/component/Stock/Home/Home.tsx b/app/koi-client/src/page/@party@[partyId]/component/Stock/component/Stock/Home/Home.tsx index fba284bc..81d23437 100644 --- a/app/koi-client/src/page/@party@[partyId]/component/Stock/component/Stock/Home/Home.tsx +++ b/app/koi-client/src/page/@party@[partyId]/component/Stock/component/Stock/Home/Home.tsx @@ -95,27 +95,12 @@ const Home = ({ stockId }: Props) => { return myInfos; }, [] as Array<{ company: string; timeIdx: number; price: number }>); - const { futureInfos } = myInfos.reduce( - (acc, info) => { + const futureInfos = myInfos + .filter((info) => { const infoTimeInSeconds = stock?.fluctuationsInterval && info.timeIdx * 60 * stock.fluctuationsInterval; - - if (infoTimeInSeconds >= gameTimeInSeconds) { - let index = acc.futureInfos.findIndex((item) => item.timeIdx > info.timeIdx); - if (index === -1) index = acc.futureInfos.length; - acc.futureInfos.splice(index, 0, info); - } else { - let index = acc.pastInfos.findIndex((item) => item.timeIdx < info.timeIdx); - if (index === -1) index = acc.pastInfos.length; - acc.pastInfos.splice(index, 0, info); - } - - return acc; - }, - { - futureInfos: [] as Array<{ company: string; timeIdx: number; price: number }>, - pastInfos: [] as Array<{ company: string; timeIdx: number; price: number }>, - }, - ); + return infoTimeInSeconds >= gameTimeInSeconds; + }) + .sort((a, b) => a.timeIdx - b.timeIdx); const allProfitDesc = allUserSellPriceDesc() .map(({ userId, allSellPrice }) => { From 081cbd20360ae4fef33e9604f48f037ad88fa6a3 Mon Sep 17 00:00:00 2001 From: solssak Date: Sat, 8 Mar 2025 02:22:29 +0900 Subject: [PATCH 09/14] fix(Home): Correct typo in FutureInfoWrapper component name --- .../component/Stock/component/Stock/Home/Home.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/koi-client/src/page/@party@[partyId]/component/Stock/component/Stock/Home/Home.tsx b/app/koi-client/src/page/@party@[partyId]/component/Stock/component/Stock/Home/Home.tsx index 81d23437..452944cd 100644 --- a/app/koi-client/src/page/@party@[partyId]/component/Stock/component/Stock/Home/Home.tsx +++ b/app/koi-client/src/page/@party@[partyId]/component/Stock/component/Stock/Home/Home.tsx @@ -173,7 +173,7 @@ const Home = ({ stockId }: Props) => {
전체보기 >

현재 시각 이후의 정보 최대 2개가 표시됩니다

- + {futureInfos.slice(0, 2).map(({ company, price, timeIdx }) => { const infoTimeInMinutes = timeIdx * stock.fluctuationsInterval; const remainingTime = infoTimeInMinutes - gameTimeInMinutes; @@ -210,7 +210,7 @@ const Home = ({ stockId }: Props) => { /> ); })} - + @@ -277,7 +277,7 @@ const H6Wrapper = styled.div` border-radius: 20px; `; -const FutuerInfoWrapper = styled.div` +const FutureInfoWrapper = styled.div` display: flex; flex-direction: column; gap: 12px; From dbaa99691f628e0150a215ee80075f8ef2fb5f4f Mon Sep 17 00:00:00 2001 From: solssak Date: Sat, 8 Mar 2025 02:23:11 +0900 Subject: [PATCH 10/14] feat(Home): Add message for empty futureInfos MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Display "현재 시각 이후의 정보가 없습니다" when there is no future info - Ensures better UX by providing feedback when data is unavailable - Keeps the existing behavior when futureInfos exist --- .../component/Stock/component/Stock/Home/Home.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/koi-client/src/page/@party@[partyId]/component/Stock/component/Stock/Home/Home.tsx b/app/koi-client/src/page/@party@[partyId]/component/Stock/component/Stock/Home/Home.tsx index 452944cd..8949a678 100644 --- a/app/koi-client/src/page/@party@[partyId]/component/Stock/component/Stock/Home/Home.tsx +++ b/app/koi-client/src/page/@party@[partyId]/component/Stock/component/Stock/Home/Home.tsx @@ -172,7 +172,11 @@ const Home = ({ stockId }: Props) => {
전체보기 >
-

현재 시각 이후의 정보 최대 2개가 표시됩니다

+ {futureInfos.length === 0 ? ( +

현재 시각 이후의 정보가 없습니다

+ ) : ( +

현재 시각 이후의 정보 최대 2개가 표시됩니다

+ )} {futureInfos.slice(0, 2).map(({ company, price, timeIdx }) => { const infoTimeInMinutes = timeIdx * stock.fluctuationsInterval; From 3cf9deee193b418fe78fc32e09fbe69156efcd29 Mon Sep 17 00:00:00 2001 From: solssak Date: Sat, 8 Mar 2025 04:16:53 +0900 Subject: [PATCH 11/14] feat: implement Information Drawer Button --- app/koi-client/src/config/color.ts | 1 + .../Stock/component/Stock/DrawInfo.tsx | 31 ++++++++++++++++--- .../Stock/component/Stock/Information.tsx | 15 +++++++++ 3 files changed, 42 insertions(+), 5 deletions(-) diff --git a/app/koi-client/src/config/color.ts b/app/koi-client/src/config/color.ts index a7d38bbc..ec62e643 100644 --- a/app/koi-client/src/config/color.ts +++ b/app/koi-client/src/config/color.ts @@ -8,3 +8,4 @@ export const colorDown = '#60A5FA'; export const pastelGreen = '#BEF264'; export const pastelViolet = '#c983ff'; +export const green = '#16A34A'; diff --git a/app/koi-client/src/page/@party@[partyId]/component/Stock/component/Stock/DrawInfo.tsx b/app/koi-client/src/page/@party@[partyId]/component/Stock/component/Stock/DrawInfo.tsx index dd19d69f..41fab7d8 100644 --- a/app/koi-client/src/page/@party@[partyId]/component/Stock/component/Stock/DrawInfo.tsx +++ b/app/koi-client/src/page/@party@[partyId]/component/Stock/component/Stock/DrawInfo.tsx @@ -1,8 +1,10 @@ -import React, { useEffect, useRef, useState } from 'react'; +import styled from '@emotion/styled'; +import { Modal, message } from 'antd'; import { useAtomValue } from 'jotai'; -import { Button, Modal, message } from 'antd'; -import { UserStore } from '../../../../../../store'; +import { useEffect, useRef, useState } from 'react'; +import * as COLOR from '../../../../../../config/color'; import { Query } from '../../../../../../hook'; +import { UserStore } from '../../../../../../store'; type Props = { stockId: string; @@ -62,9 +64,9 @@ const DrawStockInfo = ({ stockId }: Props) => { return ( <> {contextHolder} - +
{ ); }; +const InformationDrawButton = styled.button` + width: 100%; + font-family: 'DungGeunMo'; + padding: 16px; + height: 56px; + border-radius: 4px; + background-color: ${COLOR.green}; + color: white; + font-size: 14px; + border: none; + &:hover > span { + color: white; + } + &:disabled { + opacity: 50%; + cursor: not-allowed; + } +`; + export default DrawStockInfo; diff --git a/app/koi-client/src/page/@party@[partyId]/component/Stock/component/Stock/Information.tsx b/app/koi-client/src/page/@party@[partyId]/component/Stock/component/Stock/Information.tsx index 39f36d72..8e4daf09 100644 --- a/app/koi-client/src/page/@party@[partyId]/component/Stock/component/Stock/Information.tsx +++ b/app/koi-client/src/page/@party@[partyId]/component/Stock/component/Stock/Information.tsx @@ -11,6 +11,7 @@ import { colorDown, colorUp } from '../../../../../../config/color'; import { Query } from '../../../../../../hook'; import prependZero from '../../../../../../service/prependZero'; import { UserStore } from '../../../../../../store'; +import DrawStockInfo from './DrawInfo'; interface Props { stockId: string; @@ -183,6 +184,9 @@ const Information = ({ stockId }: Props) => { ); })} + + + ); }; @@ -229,4 +233,15 @@ const DimContainer = styled.div` pointer-events: none; `; +const StickyBottom = styled.div` + position: absolute; + bottom: 0; + left: 0; + width: 100%; + background-color: #252836; + border-top: 1px solid #374151; + padding: 20px; + box-sizing: border-box; +`; + export default Information; From f678f8577394519ea4075260b7d3a30ae663ba2d Mon Sep 17 00:00:00 2001 From: solssak Date: Sat, 8 Mar 2025 04:19:05 +0900 Subject: [PATCH 12/14] style(home): Add Divider --- .../component/Stock/component/Stock/Home/Home.tsx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/koi-client/src/page/@party@[partyId]/component/Stock/component/Stock/Home/Home.tsx b/app/koi-client/src/page/@party@[partyId]/component/Stock/component/Stock/Home/Home.tsx index 8949a678..c44b7158 100644 --- a/app/koi-client/src/page/@party@[partyId]/component/Stock/component/Stock/Home/Home.tsx +++ b/app/koi-client/src/page/@party@[partyId]/component/Stock/component/Stock/Home/Home.tsx @@ -162,6 +162,7 @@ const Home = ({ stockId }: Props) => { } /> + @@ -287,6 +288,13 @@ const FutureInfoWrapper = styled.div` gap: 12px; `; +const Divider = styled.div` + width: 100%; + height: 1px; + background-color: #374151; + margin-bottom: 20px; +`; + // TODO: 만약 영역이 겹치는 이슈가 발생 시 수정 const StickyBottom = styled.div` position: absolute; From 4d4c33324f320074f8d6a67ae75bfc0ecc2fdb02 Mon Sep 17 00:00:00 2001 From: solssak Date: Sun, 9 Mar 2025 03:30:41 +0900 Subject: [PATCH 13/14] feat: Improve UI/UX for the icebreaking page --- app/koi-client/src/config/color.ts | 1 + .../component/Stock/component/Introduce.tsx | 106 ++++++++++++++---- .../Stock/component/IntroduceResult.tsx | 55 ++++++++- 3 files changed, 134 insertions(+), 28 deletions(-) diff --git a/app/koi-client/src/config/color.ts b/app/koi-client/src/config/color.ts index ec62e643..919fca42 100644 --- a/app/koi-client/src/config/color.ts +++ b/app/koi-client/src/config/color.ts @@ -9,3 +9,4 @@ export const colorDown = '#60A5FA'; export const pastelGreen = '#BEF264'; export const pastelViolet = '#c983ff'; export const green = '#16A34A'; +export const BottomButtonBlue = '#007AFF'; diff --git a/app/koi-client/src/page/@party@[partyId]/component/Stock/component/Introduce.tsx b/app/koi-client/src/page/@party@[partyId]/component/Stock/component/Introduce.tsx index 16384392..6d58dc1e 100644 --- a/app/koi-client/src/page/@party@[partyId]/component/Stock/component/Introduce.tsx +++ b/app/koi-client/src/page/@party@[partyId]/component/Stock/component/Introduce.tsx @@ -1,12 +1,13 @@ -import styled from '@emotion/styled'; -import { Button, Input, List } from 'antd'; import { css } from '@emotion/react'; +import styled from '@emotion/styled'; +import { useDebounce } from '@toss/react'; +import { List } from 'antd'; import { useAtomValue } from 'jotai'; import { useState } from 'react'; -import { useDebounce } from '@toss/react'; -import { useDisableScrollView } from '../../../hook/useDisableScrollView'; +import * as COLOR from '../../../../../config/color'; import { Query } from '../../../../../hook'; import { UserStore } from '../../../../../store'; +import { useDisableScrollView } from '../../../hook/useDisableScrollView'; const rules = [ { @@ -74,7 +75,14 @@ export default function Introduce({ HeaderComponent = <>, stockId }: Props) {

💫 프로필카드 작성

( @@ -86,24 +94,28 @@ export default function Introduce({ HeaderComponent = <>, stockId }: Props) { )} /> - - + + + { + setUser({ + stockId, + userId, + userInfo: { + ...user.userInfo, + introduction, + }, + }); + }} + > + 저장하기 + +
); @@ -116,6 +128,7 @@ const Container = styled.div` height: 100%; padding: 16px; box-sizing: border-box; + position: relative; `; const BodyContainer = styled.div` @@ -127,3 +140,50 @@ const BodyContainer = styled.div` height: 100%; gap: 16px; `; + +const IntroduceArea = styled.textarea` + width: 100%; + height: 100px; + padding: 16px; + border-radius: 16px; + background-color: rgba(217, 217, 217, 0.2); + color: #ffffff; + font-size: 12px; + resize: none; + border: none; + outline: none; + box-sizing: border-box; + border: 1px solid #616161; + font-family: 'DungGeunMo'; +`; + +const StickyBottom = styled.div` + position: absolute; + bottom: 0; + left: 0; + width: 100%; + background-color: #252836; + border-top: 1px solid #374151; + padding: 20px; + box-sizing: border-box; +`; + +const SaveButton = styled.button` + background-color: ${COLOR.BottomButtonBlue}; + width: 100%; + font-family: 'DungGeunMo'; + padding: 16px; + height: 56px; + border-radius: 4px; + color: white; + font-size: 14px; + border: none; + &:hover > span { + color: white; + } + &:disabled { + opacity: 50%; + cursor: not-allowed; + } + cursor: pointer; +`; diff --git a/app/koi-client/src/page/@party@[partyId]/component/Stock/component/IntroduceResult.tsx b/app/koi-client/src/page/@party@[partyId]/component/Stock/component/IntroduceResult.tsx index c0a6b5fb..95d27672 100644 --- a/app/koi-client/src/page/@party@[partyId]/component/Stock/component/IntroduceResult.tsx +++ b/app/koi-client/src/page/@party@[partyId]/component/Stock/component/IntroduceResult.tsx @@ -41,22 +41,59 @@ export default function IntroduceResult({ HeaderComponent = <>, stockId }: Pr

🔍 프로필 추리하기

( {item.emoji}} - title={{item.text}} + title={ + {item.text} + } /> )} /> - + + 📌 당신과 이름이 비슷해요! {userList?.[prevIndex]?.userInfo.introduction} - + + 📌 당신과 비슷한 관심사를 가지고 있어요! {userList?.[nextIndex]?.userInfo.introduction}
@@ -76,9 +113,17 @@ const Container = styled.div` const BodyContainer = styled.div` display: flex; flex-direction: column; - /* justify-content: center; */ align-items: center; width: 100%; height: 100%; gap: 16px; `; + +const MatchingReason = styled.p` + text-align: center; + font-size: 14px; + font-weight: 500; + color: #ffcc00; + margin-bottom: 8px; + margin-top: 0; +`; From fc9dd8780b3bec3ab14b18c4d7c86793002d75f0 Mon Sep 17 00:00:00 2001 From: solssak Date: Sat, 15 Mar 2025 00:05:55 +0900 Subject: [PATCH 14/14] fix: swap color codes for stock price increase and decrease indicators --- .../component/StockScreen/Table.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/koi-client/src/page/@backoffice@screen@[partyId]/component/StockScreen/Table.tsx b/app/koi-client/src/page/@backoffice@screen@[partyId]/component/StockScreen/Table.tsx index 8cedb268..95393db3 100644 --- a/app/koi-client/src/page/@backoffice@screen@[partyId]/component/StockScreen/Table.tsx +++ b/app/koi-client/src/page/@backoffice@screen@[partyId]/component/StockScreen/Table.tsx @@ -42,7 +42,7 @@ const Table = ({ stockId }: Props) => { const diff = timeIdx === 0 ? 0 : companies[company][timeIdx].가격 - companies[company][timeIdx - 1].가격; const 등락 = diff > 0 ? `▲${commaizeNumber(Math.abs(diff))}` : diff < 0 ? `▼${commaizeNumber(Math.abs(diff))}` : '-'; - const color = diff > 0 ? '#60A5FA' : diff < 0 ? '#F87171' : undefined; + const color = diff > 0 ? '#F87171' : diff < 0 ? '#60A5FA' : undefined; return (