diff --git a/FE/src/components/Mypage/Account.tsx b/FE/src/components/Mypage/Account.tsx index 3b9e5f09..a5558241 100644 --- a/FE/src/components/Mypage/Account.tsx +++ b/FE/src/components/Mypage/Account.tsx @@ -4,8 +4,10 @@ import MyStocksList from './MyStocksList'; import { getAssets } from 'service/assets'; export default function Account() { - const { data, isLoading, isError } = useQuery(['account', 'assets'], () => - getAssets(), + const { data, isLoading, isError } = useQuery( + ['account', 'assets'], + () => getAssets(), + { staleTime: 1000 }, ); if (isLoading) return
loading
; @@ -15,7 +17,7 @@ export default function Account() { const { asset, stocks } = data; return ( -
+
diff --git a/FE/src/components/Mypage/CancleAlertModal.tsx b/FE/src/components/Mypage/CancleAlertModal.tsx new file mode 100644 index 00000000..5caeac15 --- /dev/null +++ b/FE/src/components/Mypage/CancleAlertModal.tsx @@ -0,0 +1,38 @@ +import Overay from 'components/ModalOveray'; +import useOrderCancelAlertModalStore from 'store/orderCancleAlertModalStore'; + +export default function CancleAlertModal() { + const { close, onSuccess, order } = useOrderCancelAlertModalStore(); + if (!order) return; + + const { stock_name, amount, trade_type } = order; + + return ( + <> + close()} /> +
+
+ {stock_name} {amount}주 {trade_type === 'BUY' ? '매수' : '매도'} 주문 + 요청을 취소하시겠습니까? +
+
+ + +
+
+ + ); +} diff --git a/FE/src/components/Mypage/MyInfo.tsx b/FE/src/components/Mypage/MyInfo.tsx new file mode 100644 index 00000000..9ec088a0 --- /dev/null +++ b/FE/src/components/Mypage/MyInfo.tsx @@ -0,0 +1,35 @@ +import { useQuery } from '@tanstack/react-query'; +import { getMyProfile } from 'service/user'; + +export default function MyInfo() { + const { data, isLoading, isError } = useQuery( + ['myInfo', 'profile'], + () => getMyProfile(), + { staleTime: 1000 }, + ); + + if (isLoading) return
loading
; + if (!data) return
No data
; + if (isError) return
error
; + + const { name, email } = data; + + return ( +
+
+
+

+ Username +

+

{name}

+
+
+

+ Email +

+

{email}

+
+
+
+ ); +} diff --git a/FE/src/components/Mypage/MyStocksList.tsx b/FE/src/components/Mypage/MyStocksList.tsx index d28ae082..890eddd6 100644 --- a/FE/src/components/Mypage/MyStocksList.tsx +++ b/FE/src/components/Mypage/MyStocksList.tsx @@ -6,14 +6,14 @@ type MyStocksListProps = { export default function MyStocksList({ stocks }: MyStocksListProps) { return ( -
+

종목

보유 수량

평균 가격

-
    +
      {stocks.map((stock) => { const { code, name, avg_price, quantity } = stock; return ( diff --git a/FE/src/components/Mypage/Order.tsx b/FE/src/components/Mypage/Order.tsx index b8a2bbb0..d96696b8 100644 --- a/FE/src/components/Mypage/Order.tsx +++ b/FE/src/components/Mypage/Order.tsx @@ -1,21 +1,20 @@ import useOrders from 'hooks/useOrder'; +import useOrderCancelAlertModalStore from 'store/orderCancleAlertModalStore'; import { parseTimestamp } from 'utils/common'; +import CancleAlertModal from './CancleAlertModal'; export default function Order() { const { orderQuery, removeOrder } = useOrders(); const { data, isLoading, isError } = orderQuery; + const { isOpen, open } = useOrderCancelAlertModalStore(); if (isLoading) return
      loading
      ; if (!data) return
      No data
      ; if (isError) return
      error
      ; - const handleCancelOrder = (id: number) => { - removeOrder.mutate(id); - }; - return ( -
      +

      종목

      요청 유형

      @@ -25,7 +24,7 @@ export default function Order() {

      -
        +
          {data.map((order) => { const { id, @@ -59,8 +58,8 @@ export default function Order() {

          @@ -69,6 +68,7 @@ export default function Order() { ); })}

        + {isOpen && }
      ); } diff --git a/FE/src/components/StocksDetail/Chart.tsx b/FE/src/components/StocksDetail/Chart.tsx index 3b85e48f..7789ab1e 100644 --- a/FE/src/components/StocksDetail/Chart.tsx +++ b/FE/src/components/StocksDetail/Chart.tsx @@ -64,12 +64,13 @@ export default function Chart({ code }: StocksDeatailChartProps) { y: 0, }); - const { data, isLoading } = useQuery({ - queryKey: ['stocksChartData', code, timeCategory], - queryFn: () => getStocksChartDataByCode(code, timeCategory), - staleTime: 1000, - cacheTime: 1000 * 60, - }); + + const { data, isLoading } = useQuery( + ['stocksChartData', code, timeCategory], + () => getStocksChartDataByCode(code, timeCategory), + { staleTime: 1000 }, + ); + const handleMouseDown = useCallback((e: MouseEvent) => { e.preventDefault(); @@ -360,7 +361,7 @@ export default function Chart({ code }: StocksDeatailChartProps) { return (
      -
      +

      차트

      {lowerLimitFlag && ( -
      +
      이 주식의 최소 가격은 {(+stck_llam).toLocaleString()}입니다.
      )} @@ -112,9 +121,9 @@ export default function BuySection({ code, detailInfo }: BuySectionProps) {

      수량

      setCount(+e.target.value)} + onChange={handleCountChange} className='flex-1 py-1 rounded-lg' min={1} /> diff --git a/FE/src/components/StocksDetail/TradeSection/SellSection.tsx b/FE/src/components/StocksDetail/TradeSection/SellSection.tsx index eeb8f1cf..c94035c0 100644 --- a/FE/src/components/StocksDetail/TradeSection/SellSection.tsx +++ b/FE/src/components/StocksDetail/TradeSection/SellSection.tsx @@ -1,7 +1,7 @@ import Lottie from 'lottie-react'; import emptyAnimation from 'assets/emptyAnimation.json'; import { useQuery } from '@tanstack/react-query'; -import { getSellPossibleStockCnt } from 'service/assets'; +import { getSellInfo } from 'service/assets'; import { ChangeEvent, FocusEvent, FormEvent, useRef, useState } from 'react'; import { StockDetailType } from 'types'; import useAuthStore from 'store/authStore'; @@ -19,12 +19,13 @@ export default function SellSection({ code, detailInfo }: SellSectionProps) { const { data, isLoading, isError } = useQuery( ['detail', 'sellPosiible', code], - () => getSellPossibleStockCnt(code), + () => getSellInfo(code), + { staleTime: 1000 }, ); const [currPrice, setCurrPrice] = useState(stck_prpr); const { isLogin } = useAuthStore(); - const [count, setCount] = useState(0); + const [count, setCount] = useState(1); const [upperLimitFlag, setUpperLimitFlag] = useState(false); const [lowerLimitFlag, setLowerLimitFlag] = useState(false); @@ -38,14 +39,26 @@ export default function SellSection({ code, detailInfo }: SellSectionProps) { if (isError) return
      error
      ; const quantity = data.quantity; + const avg_price = data.avg_price; + + const pl = (+currPrice - avg_price) * count; + const totalPrice = +currPrice * count; + const plRate = ((pl / totalPrice) * 100).toFixed(2); + const handlePriceChange = (e: ChangeEvent) => { - if (!isNumericString(e.target.value)) return; + const s = e.target.value.replace(/,/g, ''); + if (!isNumericString(s)) return; + setCurrPrice(s); + }; - setCurrPrice(e.target.value); + const handleCountChange = (e: ChangeEvent) => { + const s = e.target.value; + if (!isNumericString(s)) return; + setCount(+s); }; const handlePriceInputBlur = (e: FocusEvent) => { - const n = +e.target.value; + const n = +e.target.value.replace(/,/g, ''); if (n > +stck_mxpr) { if (timerRef.current) { clearTimeout(timerRef.current); @@ -91,15 +104,6 @@ export default function SellSection({ code, detailInfo }: SellSectionProps) { const handleSell = async (e: FormEvent) => { e.preventDefault(); - // const price = +currPrice * count; - - // if (price > data.cash_balance) { - // setLackAssetFlag(true); - // timerRef.current = window.setTimeout(() => { - // setLackAssetFlag(false); - // }, 2000); - // return; - // } toggleModal(); }; @@ -124,14 +128,14 @@ export default function SellSection({ code, detailInfo }: SellSectionProps) {

      매도 가격

      {lowerLimitFlag && ( -
      +
      이 주식의 최소 가격은 {(+stck_llam).toLocaleString()}입니다.
      )} @@ -143,9 +147,9 @@ export default function SellSection({ code, detailInfo }: SellSectionProps) {

      수량

      setCount(+e.target.value)} + onChange={handleCountChange} onBlur={handleCntInputBlur} className='flex-1 py-1 rounded-lg' min={1} @@ -162,12 +166,27 @@ export default function SellSection({ code, detailInfo }: SellSectionProps) {
      +
      +
      +

      예상 수익률

      +

      + {plRate}% +

      +
      +
      +
      +
      +

      예상 손익

      +

      {pl.toLocaleString()}원

      +
      +

      총 매도 금액

      -

      {(+currPrice * count).toLocaleString()}원

      +

      {totalPrice.toLocaleString()}원

      -
      diff --git a/FE/src/page/StocksDetail.tsx b/FE/src/page/StocksDetail.tsx index e30f2e11..42e336eb 100644 --- a/FE/src/page/StocksDetail.tsx +++ b/FE/src/page/StocksDetail.tsx @@ -13,7 +13,7 @@ export default function StocksDetail() { const { data, isLoading } = useQuery( ['stocks', code], () => getStocksByCode(code!), - { enabled: !!code }, + { staleTime: 1000, enabled: !!code }, ); if (!code) return
      Non code
      ; diff --git a/FE/src/service/assets.ts b/FE/src/service/assets.ts index 98596288..bb60b8c9 100644 --- a/FE/src/service/assets.ts +++ b/FE/src/service/assets.ts @@ -29,9 +29,9 @@ export async function getCash(): Promise<{ cash_balance: number }> { }); } -export async function getSellPossibleStockCnt( +export async function getSellInfo( code: string, -): Promise<{ quantity: number }> { +): Promise<{ quantity: number; avg_price: number }> { const url = import.meta.env.PROD ? `${import.meta.env.VITE_API_URL}/assets/stocks/${code}` : `/api/assets/stocks/${code}`; @@ -42,7 +42,7 @@ export async function getSellPossibleStockCnt( 'Content-Type': 'application/json', }, }).then((res) => { - if (res.status === 401) return { quantity: 0 }; + if (res.status === 401) return { quantity: 0, avg_price: 0 }; return res.json(); }); } diff --git a/FE/src/service/stocks.ts b/FE/src/service/stocks.ts index 9f5d4bb4..fae307b9 100644 --- a/FE/src/service/stocks.ts +++ b/FE/src/service/stocks.ts @@ -22,3 +22,12 @@ export async function getStocksChartDataByCode( }), }).then((res) => res.json()); } + +export async function unsbscribe(code: string) { + return fetch( + `${import.meta.env.VITE_API_URL}/stocks/trade-history/${code}/unsubscribe`, + { + headers: { 'Content-Type': 'application/json' }, + }, + ); +} diff --git a/FE/src/service/user.ts b/FE/src/service/user.ts new file mode 100644 index 00000000..5aa9caaf --- /dev/null +++ b/FE/src/service/user.ts @@ -0,0 +1,12 @@ +import { Profile } from 'types'; + +export async function getMyProfile(): Promise { + const url = import.meta.env.PROD + ? `${import.meta.env.VITE_API_URL}/user/profile` + : '/api/user/profile'; + + return fetch(url, { + credentials: 'include', + headers: { 'Content-Type': 'application/json' }, + }).then((res) => res.json()); +} diff --git a/FE/src/store/orderCancleAlertModalStore.ts b/FE/src/store/orderCancleAlertModalStore.ts new file mode 100644 index 00000000..6f48162f --- /dev/null +++ b/FE/src/store/orderCancleAlertModalStore.ts @@ -0,0 +1,21 @@ +import { Order } from 'types'; +import { create } from 'zustand'; + +type ModalStore = { + isOpen: boolean; + order: Order | null; + onSuccess: () => void; + open: (order: Order, callback?: () => void) => void; + close: () => void; +}; + +const useOrderCancelAlertModalStore = create((set) => ({ + isOpen: false, + order: null, + onSuccess: () => {}, + open: (order, callback?) => + set(() => ({ isOpen: true, order, onSuccess: callback })), + close: () => set(() => ({ isOpen: false })), +})); + +export default useOrderCancelAlertModalStore; diff --git a/FE/src/types.ts b/FE/src/types.ts index 1c61149e..417126df 100644 --- a/FE/src/types.ts +++ b/FE/src/types.ts @@ -80,3 +80,8 @@ export type ChartSizeConfigType = { yAxisWidth: number; xAxisHeight: number; }; + +export type Profile = { + name: string; + email: string; +};