Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor/64: 로딩/에러 처리 #68

Merged
merged 10 commits into from
Dec 9, 2024
Binary file added public/images/login-guard.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified public/images/shield.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 10 additions & 1 deletion src/api/transaction/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<OrderHistory[]> {
): Promise<TradeHistory[]> {
if (token === null) {
throw new Error();
}
Expand Down
9 changes: 7 additions & 2 deletions src/app/search/[id]/_components/loading-spiner.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
export default function LoadingSpinner() {
import cn from "@/utils/cn";

interface LoadingSpinnerProps {
className?: string;
}
export default function LoadingSpinner({ className }: LoadingSpinnerProps) {
return (
<div className="flex h-64 items-center justify-center">
<div className={cn("flex h-64 items-center justify-center", className)}>
<div className="loadingSpinner" />
</div>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -26,7 +27,15 @@ export default function EditTableBody({
</td>
<td className=" py-10 pl-20 text-left">
<div className="pb-6 text-12-400">
<span className="pr-3 text-[#F20000]">{data.type} 정정</span>
<span
className={cn(
"pr-3",
data.type === "매수" && "text-[#F20000]",
data.type === "매도" && "text-[#4882FA]",
)}
>
{data.type} 정정
</span>
<span className="text-[#9B9B9B]">{data.OrderId}</span>
</div>
{data.stockName}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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,
Expand Down Expand Up @@ -65,6 +70,9 @@ export default function EditCancel() {
onSettled: () => {
setIsCancelTable(false);
},
onError: (error) => {
showToast(error.message, "error");
},
Comment on lines +73 to +75
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

에러 메시지 처리 방식 개선이 필요합니다.

현재 에러 메시지를 직접 표시하고 있는데, 사용자 친화적인 에러 메시지로 변환하는 것이 좋겠습니다.

     onError: (error) => {
-      showToast(error.message, "error");
+      const userFriendlyMessage = error.message === "Failed to fetch stocks" 
+        ? "거래 정보를 불러오는데 실패했습니다. 잠시 후 다시 시도해주세요." 
+        : "요청 처리 중 오류가 발생했습니다.";
+      showToast(userFriendlyMessage, "error");
     },

Also applies to: 88-90

});

const { mutate: modifyTradeMutate } = useMutation({
Expand All @@ -77,6 +85,9 @@ export default function EditCancel() {
setIsEditForm(false);
setSelectedOrders([]);
},
onError: (error) => {
showToast(error.message, "error");
},
});

const handleCancelConfirm = (orderId: string) => {
Expand All @@ -90,6 +101,10 @@ export default function EditCancel() {
);
}

if (isLoading || isPending) {
return <LoadingSpinner className="mt-230" />;
}

if (isCancelTable) {
return (
<>
Expand Down Expand Up @@ -125,17 +140,19 @@ export default function EditCancel() {
<table className="w-full text-center text-14-500">
<EditTableHeader />
{limitOrderData && limitOrderData.length > 0 ? (
limitOrderData.map((data) => (
<EditTableBody
key={data.OrderId}
data={data}
isChecked={selectedOrders.includes(data.OrderId.toString())}
toggleSelection={toggleOrderSelection}
/>
))
[...limitOrderData]
.sort((a, b) => b.OrderId - a.OrderId)
.map((data) => (
<EditTableBody
key={data.OrderId}
data={data}
isChecked={selectedOrders.includes(data.OrderId.toString())}
toggleSelection={toggleOrderSelection}
/>
))
Comment on lines +143 to +152
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

성능 최적화가 필요합니다.

데이터 정렬 로직이 렌더링마다 실행되고 있습니다. useMemo를 사용하여 최적화하는 것이 좋겠습니다.

+  const sortedLimitOrderData = useMemo(
+    () => 
+      limitOrderData 
+        ? [...limitOrderData].sort((a, b) => b.OrderId - a.OrderId)
+        : [],
+    [limitOrderData]
+  );

-            [...limitOrderData]
-              .sort((a, b) => b.OrderId - a.OrderId)
-              .map((data) => (
+            sortedLimitOrderData.map((data) => (
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
[...limitOrderData]
.sort((a, b) => b.OrderId - a.OrderId)
.map((data) => (
<EditTableBody
key={data.OrderId}
data={data}
isChecked={selectedOrders.includes(data.OrderId.toString())}
toggleSelection={toggleOrderSelection}
/>
))
const sortedLimitOrderData = useMemo(
() =>
limitOrderData
? [...limitOrderData].sort((a, b) => b.OrderId - a.OrderId)
: [],
[limitOrderData]
);
sortedLimitOrderData.map((data) => (
<EditTableBody
key={data.OrderId}
data={data}
isChecked={selectedOrders.includes(data.OrderId.toString())}
toggleSelection={toggleOrderSelection}
/>
))

) : (
<tr>
<td colSpan={5} className="py-20 text-center text-gray-500">
<td colSpan={5} className="py-20 text-center text-16-500">
<Image
src="/images/green-wallet.png"
width={150}
Expand All @@ -149,18 +166,20 @@ export default function EditCancel() {
)}
</table>
</div>
<div className="mt-20 text-center">
<Button
variant="red"
className="mr-10 w-120 bg-[#1DA65A] hover:bg-[#1DA65A]/95"
onClick={handleEdit}
>
정정
</Button>
<Button variant="red" className="w-120" onClick={handleCancel}>
취소
</Button>
</div>
{limitOrderData && limitOrderData.length > 0 && (
<div className="mt-20 text-center">
<Button
variant="red"
className="mr-10 w-120 text-black bg-[#0FED78] hover:bg-[#0FED78]/95"
onClick={handleEdit}
>
정정
</Button>
Comment on lines +171 to +177
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

버튼 스타일링에 일관성이 필요합니다.

variant="red"로 설정되어 있지만, className에서 초록색 배경을 덮어쓰고 있습니다. 이는 혼란을 야기할 수 있습니다.

다음과 같이 수정하는 것을 추천드립니다:

- <Button
-   variant="red"
-   className="mr-10 w-120 text-black bg-[#0FED78] hover:bg-[#0FED78]/95"
-   onClick={handleEdit}
- >
+ <Button
+   variant="primary"
+   className="mr-10 w-120"
+   onClick={handleEdit}
+ >

Committable suggestion skipped: line range outside the PR's diff.

<Button variant="red" className="w-120" onClick={handleCancel}>
취소
</Button>
</div>
)}
</>
);
}
55 changes: 39 additions & 16 deletions src/app/search/[id]/_components/transaction-form/history.tsx
Original file line number Diff line number Diff line change
@@ -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 <LoadingSpinner className="mt-230" />;
}

return (
<div className="flex h-470 flex-col gap-20 overflow-auto">
{tradeHistoryData && tradeHistoryData?.length > 0 ? (
tradeHistoryData.map((history) => (
<TransactionTable
key={history.OrderId}
color="green"
isSubmit={false}
submittedData={{
stockName: history.stockName,
count: history.stockCount,
bidding: history.buyPrice,
totalAmount: history.buyPrice * history.stockCount,
buyOrder: history.type,
}}
/>
))
[...tradeHistoryData]
.sort((a, b) => b.id - a.id)
.map((history) => (
<TransactionTable
key={history.id}
color={history.buyOrder === "매수" ? "red" : "blue"}
isSubmit={false}
submittedData={{
stockName: history.stockName,
count: history.stockCount,
bidding: history.buyPrice,
totalAmount: history.buyPrice * history.stockCount,
buyOrder: history.buyOrder,
}}
/>
))
) : (
<div>체결내역이 없습니다. 거래를 시작해보세요!</div>
<div className="py-20 text-center font-medium leading-5">
<Image
src="/images/green-wallet.png"
width={150}
height={150}
className="mx-auto mb-30 mt-65"
alt="지갑 그림"
/>
체결내역이 없습니다.
<br />
거래를 시작해보세요!
</div>
)}
</div>
);
Expand Down
104 changes: 72 additions & 32 deletions src/app/search/[id]/_components/transaction-form/index.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
"use client";

import Image from "next/image";
import Link from "next/link";

import {
Tabs,
TabsContent,
TabsList,
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";
Expand All @@ -22,41 +27,76 @@ export default function TransactionForm({
stockName,
stockInfo,
}: TransactionFormProps) {
const { isAuthenticated, isInitialized } = useAuth();

if (!isInitialized) {
return (
<div className="ml-17 h-630 min-w-450 rounded-10 bg-white px-22 py-30">
<h3 className="mb-250 text-20-700">거래하기</h3>
<LoadingSpinner />
</div>
);
}

return (
<div className="ml-17 h-630 min-w-450 rounded-10 bg-white px-22 py-30">
<h3 className="mb-16 text-20-700">거래하기</h3>
<StockInfoProvider
value={{
stockName,
stockInfo,
}}
>
<Tabs defaultValue="buy">
<TabsList>
<TabsTrigger value="buy" buttonColor="red">
매수
</TabsTrigger>
<TabsTrigger value="sell" buttonColor="blue">
매도
</TabsTrigger>
<TabsTrigger value="history">체결내역</TabsTrigger>
<TabsTrigger value="edit-cancel">정정 / 취소</TabsTrigger>
</TabsList>

<TabsContent value="buy">
<Trade type="buy" />
</TabsContent>
<TabsContent value="sell">
<Trade type="sell" />
</TabsContent>
<TabsContent value="history">
<History />
</TabsContent>
<TabsContent value="edit-cancel">
<EditCancel />
</TabsContent>
</Tabs>
</StockInfoProvider>
{isAuthenticated ? (
<StockInfoProvider
value={{
stockName,
stockInfo,
}}
>
<Tabs defaultValue="buy">
<TabsList>
<TabsTrigger value="buy" buttonColor="red">
매수
</TabsTrigger>
<TabsTrigger value="sell" buttonColor="blue">
매도
</TabsTrigger>
<TabsTrigger value="history">체결내역</TabsTrigger>
<TabsTrigger value="edit-cancel">정정 / 취소</TabsTrigger>
</TabsList>
<TabsContent value="buy">
<Trade type="buy" />
</TabsContent>
<TabsContent value="sell">
<Trade type="sell" />
</TabsContent>
<TabsContent value="history">
<History />
</TabsContent>
<TabsContent value="edit-cancel">
<EditCancel />
</TabsContent>
</Tabs>
</StockInfoProvider>
) : (
<div className="relative">
<Image
src="/images/login-guard.png"
alt="보안 아이콘"
width={300}
height={300}
className="absolute right-1/2 top-40 translate-x-1/2"
/>
<div className="relative top-250 w-full text-center leading-8">
<span className="text-20-700">로그인이 필요해요!</span>
<br />
<span className="text-16-500 text-gray-500">
가상 거래를 하기 위해서는 로그인이 필수적이에요!
</span>
<Link
href="/login"
className="m-auto mt-40 block w-150 rounded-4 bg-[#11E977] py-16 text-center text-16-700"
>
로그인 하기
</Link>
</div>
</div>
)}
</div>
);
}
Loading
Loading