diff --git a/public/images/login-guard.png b/public/images/login-guard.png new file mode 100644 index 0000000..fa8a028 Binary files /dev/null and b/public/images/login-guard.png differ diff --git a/public/images/shield.png b/public/images/shield.png index 90875cd..3ecd0cb 100644 Binary files a/public/images/shield.png and b/public/images/shield.png differ diff --git a/src/api/transaction/index.ts b/src/api/transaction/index.ts index 36ee320..6358f28 100644 --- a/src/api/transaction/index.ts +++ b/src/api/transaction/index.ts @@ -258,11 +258,20 @@ export async function cancelTrade({ } } +export interface TradeHistory { + id: number; + buyPrice: number; + remainCount: number; + stockCount: number; + stockName: string; + buyOrder: string; +} + // 매수/매도 체결내역 export async function getTradeHistory( token: string | null, stockName: string, -): Promise { +): Promise { if (token === null) { throw new Error(); } diff --git a/src/app/search/[id]/_components/loading-spiner.tsx b/src/app/search/[id]/_components/loading-spiner.tsx index 1f0e14b..3427564 100644 --- a/src/app/search/[id]/_components/loading-spiner.tsx +++ b/src/app/search/[id]/_components/loading-spiner.tsx @@ -1,6 +1,11 @@ -export default function LoadingSpinner() { +import cn from "@/utils/cn"; + +interface LoadingSpinnerProps { + className?: string; +} +export default function LoadingSpinner({ className }: LoadingSpinnerProps) { return ( -
+
); diff --git a/src/app/search/[id]/_components/transaction-form/edit-cancel/edit-table-body.tsx b/src/app/search/[id]/_components/transaction-form/edit-cancel/edit-table-body.tsx index c3fda0a..6a312d5 100644 --- a/src/app/search/[id]/_components/transaction-form/edit-cancel/edit-table-body.tsx +++ b/src/app/search/[id]/_components/transaction-form/edit-cancel/edit-table-body.tsx @@ -1,5 +1,6 @@ import { OrderHistory } from "@/api/transaction"; import CheckButton from "@/components/common/check-button"; +import cn from "@/utils/cn"; import { getKoreanPrice } from "@/utils/price"; interface EditTableBodyProps { @@ -26,7 +27,15 @@ export default function EditTableBody({
- {data.type} 정정 + + {data.type} 정정 + {data.OrderId}
{data.stockName} diff --git a/src/app/search/[id]/_components/transaction-form/edit-cancel/index.tsx b/src/app/search/[id]/_components/transaction-form/edit-cancel/index.tsx index e257b3b..7cf112e 100644 --- a/src/app/search/[id]/_components/transaction-form/edit-cancel/index.tsx +++ b/src/app/search/[id]/_components/transaction-form/edit-cancel/index.tsx @@ -10,6 +10,7 @@ import { useStockInfoContext } from "@/context/stock-info-context"; import { useAuth } from "@/hooks/use-auth"; import { useToast } from "@/store/use-toast-store"; +import LoadingSpinner from "../../loading-spiner"; import Trade from "../trade"; import TransactionTable from "../transaction-table"; import EditTableBody from "./edit-table-body"; @@ -21,7 +22,11 @@ export default function EditCancel() { const { showToast } = useToast(); const queryClient = useQueryClient(); - const { data: limitOrderData } = useQuery({ + const { + data: limitOrderData, + isLoading, + isPending, + } = useQuery({ queryKey: ["limitOrder"], queryFn: () => getTrade(token, stockName), enabled: !!isAuthenticated && !!token, @@ -65,6 +70,9 @@ export default function EditCancel() { onSettled: () => { setIsCancelTable(false); }, + onError: (error) => { + showToast(error.message, "error"); + }, }); const { mutate: modifyTradeMutate } = useMutation({ @@ -77,6 +85,9 @@ export default function EditCancel() { setIsEditForm(false); setSelectedOrders([]); }, + onError: (error) => { + showToast(error.message, "error"); + }, }); const handleCancelConfirm = (orderId: string) => { @@ -90,6 +101,10 @@ export default function EditCancel() { ); } + if (isLoading || isPending) { + return ; + } + if (isCancelTable) { return ( <> @@ -125,17 +140,19 @@ export default function EditCancel() { {limitOrderData && limitOrderData.length > 0 ? ( - limitOrderData.map((data) => ( - - )) + [...limitOrderData] + .sort((a, b) => b.OrderId - a.OrderId) + .map((data) => ( + + )) ) : ( -
+ -
- - -
+ {limitOrderData && limitOrderData.length > 0 && ( +
+ + +
+ )} ); } diff --git a/src/app/search/[id]/_components/transaction-form/history.tsx b/src/app/search/[id]/_components/transaction-form/history.tsx index 45dca71..ca3f7e4 100644 --- a/src/app/search/[id]/_components/transaction-form/history.tsx +++ b/src/app/search/[id]/_components/transaction-form/history.tsx @@ -1,40 +1,63 @@ import { useQuery } from "@tanstack/react-query"; +import Image from "next/image"; import { getTradeHistory } from "@/api/transaction"; import { useStockInfoContext } from "@/context/stock-info-context"; import { useAuth } from "@/hooks/use-auth"; +import LoadingSpinner from "../loading-spiner"; import TransactionTable from "./transaction-table"; export default function History() { const { token, isAuthenticated } = useAuth(); const { stockName } = useStockInfoContext(); - const { data: tradeHistoryData } = useQuery({ + const { + data: tradeHistoryData, + isLoading, + isPending, + } = useQuery({ queryKey: ["tradeHistory"], queryFn: () => getTradeHistory(token, stockName), enabled: !!isAuthenticated && !!token, }); + if (isLoading || isPending) { + return ; + } + return (
{tradeHistoryData && tradeHistoryData?.length > 0 ? ( - tradeHistoryData.map((history) => ( - - )) + [...tradeHistoryData] + .sort((a, b) => b.id - a.id) + .map((history) => ( + + )) ) : ( -
체결내역이 없습니다. 거래를 시작해보세요!
+
+ 지갑 그림 + 체결내역이 없습니다. +
+ 거래를 시작해보세요! +
)}
); diff --git a/src/app/search/[id]/_components/transaction-form/index.tsx b/src/app/search/[id]/_components/transaction-form/index.tsx index 18d5ed7..b95d890 100644 --- a/src/app/search/[id]/_components/transaction-form/index.tsx +++ b/src/app/search/[id]/_components/transaction-form/index.tsx @@ -1,5 +1,8 @@ "use client"; +import Image from "next/image"; +import Link from "next/link"; + import { Tabs, TabsContent, @@ -7,8 +10,10 @@ import { TabsTrigger, } from "@/components/common/tabs"; import { StockInfoProvider } from "@/context/stock-info-context"; +import { useAuth } from "@/hooks/use-auth"; import { StockInfo } from "../../types"; +import LoadingSpinner from "../loading-spiner"; import EditCancel from "./edit-cancel"; import History from "./history"; import Trade from "./trade"; @@ -22,41 +27,76 @@ export default function TransactionForm({ stockName, stockInfo, }: TransactionFormProps) { + const { isAuthenticated, isInitialized } = useAuth(); + + if (!isInitialized) { + return ( +
+

거래하기

+ +
+ ); + } + return (

거래하기

- - - - - 매수 - - - 매도 - - 체결내역 - 정정 / 취소 - - - - - - - - - - - - - - - - + {isAuthenticated ? ( + + + + + 매수 + + + 매도 + + 체결내역 + 정정 / 취소 + + + + + + + + + + + + + + + + ) : ( +
+ 보안 아이콘 +
+ 로그인이 필요해요! +
+ + 가상 거래를 하기 위해서는 로그인이 필수적이에요! + + + 로그인 하기 + +
+
+ )}
); } diff --git a/src/app/search/[id]/_components/transaction-form/trade/index.tsx b/src/app/search/[id]/_components/transaction-form/trade/index.tsx index 6c87621..5b003f6 100644 --- a/src/app/search/[id]/_components/transaction-form/trade/index.tsx +++ b/src/app/search/[id]/_components/transaction-form/trade/index.tsx @@ -15,6 +15,7 @@ import { import { useTabsContext } from "@/components/common/tabs"; import { useStockInfoContext } from "@/context/stock-info-context"; import { useAuth } from "@/hooks/use-auth"; +import { useToast } from "@/store/use-toast-store"; import { calculateTotalOrderAmount } from "@/utils/price"; import { BuyFormData, @@ -42,6 +43,7 @@ export default function Trade({ type, defaultData, handleMutate }: TradeProps) { const { setActiveTab } = useTabsContext(); const { stockName, stockInfo } = useStockInfoContext(); const { token } = useAuth(); + const { showToast } = useToast(); const queryClient = useQueryClient(); const { @@ -92,29 +94,47 @@ export default function Trade({ type, defaultData, handleMutate }: TradeProps) { mutationFn: buyAtMarketPrice, onSuccess: () => { setActiveTab("history"); + showToast("현재가로 매수가 완료되었습니다.", "success"); + queryClient.invalidateQueries({ queryKey: ["tradeHistory"] }); + }, + onError: (error) => { + showToast(error.message, "error"); }, }); - const { mutate: buyAtLimitPriceMutate } = useMutation({ - mutationFn: buyAtLimitPrice, + const { mutate: sellAtMarketPriceMutate } = useMutation({ + mutationFn: sellAtMarketPrice, onSuccess: () => { setActiveTab("history"); - queryClient.invalidateQueries({ queryKey: ["limitOrder"] }); + showToast("현재가로 매도가 완료되었습니다.", "success"); + queryClient.invalidateQueries({ queryKey: ["tradeHistory"] }); + }, + onError: (error) => { + showToast(error.message, "error"); }, }); - const { mutate: sellAtMarketPriceMutate } = useMutation({ - mutationFn: sellAtMarketPrice, + const { mutate: buyAtLimitPriceMutate } = useMutation({ + mutationFn: buyAtLimitPrice, onSuccess: () => { - setActiveTab("history"); + setActiveTab("edit-cancel"); + queryClient.invalidateQueries({ queryKey: ["limitOrder"] }); + showToast("지정가로 매수가 완료되었습니다.", "success"); + }, + onError: (error) => { + showToast(error.message, "error"); }, }); const { mutate: sellAtLimitPriceMutate } = useMutation({ mutationFn: sellAtLimitPrice, onSuccess: () => { - setActiveTab("history"); + setActiveTab("edit-cancel"); queryClient.invalidateQueries({ queryKey: ["limitOrder"] }); + showToast("지정가로 매도가 완료되었습니다.", "success"); + }, + onError: (error) => { + showToast(error.message, "error"); }, }); diff --git a/src/components/common/button/index.tsx b/src/components/common/button/index.tsx index 15e1d81..2e5057d 100644 --- a/src/components/common/button/index.tsx +++ b/src/components/common/button/index.tsx @@ -31,7 +31,7 @@ export default function Button({ variant === "outline" && !isDisabled, "bg-white text-black border border-[#B6B6B6] hover:bg-[#B6B6B6]/10 !rounded-4 px-24 py-9": variant === "outline-gray" && !isDisabled, - "bg-[#E43851] text-white hover:bg-[#E43851]/95 !rounded-4 px-24 py-9": + "bg-[#F12E35] hover:bg-[#F12E35]/95 text-white !rounded-4 px-24 py-9": variant === "red" && !isDisabled, "opacity-50 cursor-not-allowed bg-neutral-300": isDisabled, },