From c78ff68e449677b71c4b55745aa071f1f75f9f7e Mon Sep 17 00:00:00 2001 From: aquaman122 Date: Mon, 14 Oct 2024 19:32:00 +0900 Subject: [PATCH 01/18] =?UTF-8?q?fix:=20product=20tab=20=EC=8A=A4=ED=83=80?= =?UTF-8?q?=EC=9D=BC=20=EB=B3=80=EA=B2=BD=20=ED=8F=B0=ED=8A=B8=20=EB=B3=BC?= =?UTF-8?q?=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/mockServiceWorker.js | 140 +++++++++--------- src/components/order/OrderListTab.tsx | 4 +- src/components/payment/PaymentForm.tsx | 8 +- src/components/productList/ProductButtons.tsx | 26 ++-- .../productList/ProductListTabs.tsx | 4 +- src/components/user/UserOrderTab.tsx | 4 +- src/pages/Payment.tsx | 18 +-- 7 files changed, 102 insertions(+), 102 deletions(-) diff --git a/public/mockServiceWorker.js b/public/mockServiceWorker.js index d9750a78..cbd28e53 100644 --- a/public/mockServiceWorker.js +++ b/public/mockServiceWorker.js @@ -8,42 +8,42 @@ * - Please do NOT serve this file on production. */ -const PACKAGE_VERSION = '2.3.4'; -const INTEGRITY_CHECKSUM = '26357c79639bfa20d64c0efca2a87423'; -const IS_MOCKED_RESPONSE = Symbol('isMockedResponse'); -const activeClientIds = new Set(); +const PACKAGE_VERSION = '2.3.4' +const INTEGRITY_CHECKSUM = '26357c79639bfa20d64c0efca2a87423' +const IS_MOCKED_RESPONSE = Symbol('isMockedResponse') +const activeClientIds = new Set() self.addEventListener('install', function () { - self.skipWaiting(); -}); + self.skipWaiting() +}) self.addEventListener('activate', function (event) { - event.waitUntil(self.clients.claim()); -}); + event.waitUntil(self.clients.claim()) +}) self.addEventListener('message', async function (event) { - const clientId = event.source.id; + const clientId = event.source.id if (!clientId || !self.clients) { - return; + return } - const client = await self.clients.get(clientId); + const client = await self.clients.get(clientId) if (!client) { - return; + return } const allClients = await self.clients.matchAll({ type: 'window', - }); + }) switch (event.data) { case 'KEEPALIVE_REQUEST': { sendToClient(client, { type: 'KEEPALIVE_RESPONSE', - }); - break; + }) + break } case 'INTEGRITY_CHECK_REQUEST': { @@ -53,78 +53,78 @@ self.addEventListener('message', async function (event) { packageVersion: PACKAGE_VERSION, checksum: INTEGRITY_CHECKSUM, }, - }); - break; + }) + break } case 'MOCK_ACTIVATE': { - activeClientIds.add(clientId); + activeClientIds.add(clientId) sendToClient(client, { type: 'MOCKING_ENABLED', payload: true, - }); - break; + }) + break } case 'MOCK_DEACTIVATE': { - activeClientIds.delete(clientId); - break; + activeClientIds.delete(clientId) + break } case 'CLIENT_CLOSED': { - activeClientIds.delete(clientId); + activeClientIds.delete(clientId) const remainingClients = allClients.filter((client) => { - return client.id !== clientId; - }); + return client.id !== clientId + }) // Unregister itself when there are no more clients if (remainingClients.length === 0) { - self.registration.unregister(); + self.registration.unregister() } - break; + break } } -}); +}) self.addEventListener('fetch', function (event) { - const { request } = event; + const { request } = event // Bypass navigation requests. if (request.mode === 'navigate') { - return; + return } // Opening the DevTools triggers the "only-if-cached" request // that cannot be handled by the worker. Bypass such requests. if (request.cache === 'only-if-cached' && request.mode !== 'same-origin') { - return; + return } // Bypass all requests when there are no active clients. // Prevents the self-unregistered worked from handling requests // after it's been deleted (still remains active until the next reload). if (activeClientIds.size === 0) { - return; + return } // Generate unique request ID. - const requestId = crypto.randomUUID(); - event.respondWith(handleRequest(event, requestId)); -}); + const requestId = crypto.randomUUID() + event.respondWith(handleRequest(event, requestId)) +}) async function handleRequest(event, requestId) { - const client = await resolveMainClient(event); - const response = await getResponse(event, client, requestId); + const client = await resolveMainClient(event) + const response = await getResponse(event, client, requestId) // Send back the response clone for the "response:*" life-cycle events. // Ensure MSW is active and ready to handle the message, otherwise // this message will pend indefinitely. if (client && activeClientIds.has(client.id)) { - (async function () { - const responseClone = response.clone(); + ;(async function () { + const responseClone = response.clone() sendToClient( client, @@ -141,11 +141,11 @@ async function handleRequest(event, requestId) { }, }, [responseClone.body], - ); - })(); + ) + })() } - return response; + return response } // Resolve the main client for the given event. @@ -153,49 +153,49 @@ async function handleRequest(event, requestId) { // that registered the worker. It's with the latter the worker should // communicate with during the response resolving phase. async function resolveMainClient(event) { - const client = await self.clients.get(event.clientId); + const client = await self.clients.get(event.clientId) if (client?.frameType === 'top-level') { - return client; + return client } const allClients = await self.clients.matchAll({ type: 'window', - }); + }) return allClients .filter((client) => { // Get only those clients that are currently visible. - return client.visibilityState === 'visible'; + return client.visibilityState === 'visible' }) .find((client) => { // Find the client ID that's recorded in the // set of clients that have registered the worker. - return activeClientIds.has(client.id); - }); + return activeClientIds.has(client.id) + }) } async function getResponse(event, client, requestId) { - const { request } = event; + const { request } = event // Clone the request because it might've been already used // (i.e. its body has been read and sent to the client). - const requestClone = request.clone(); + const requestClone = request.clone() function passthrough() { - const headers = Object.fromEntries(requestClone.headers.entries()); + const headers = Object.fromEntries(requestClone.headers.entries()) // Remove internal MSW request header so the passthrough request // complies with any potential CORS preflight checks on the server. // Some servers forbid unknown request headers. - delete headers['x-msw-intention']; + delete headers['x-msw-intention'] - return fetch(requestClone, { headers }); + return fetch(requestClone, { headers }) } // Bypass mocking when the client is not active. if (!client) { - return passthrough(); + return passthrough() } // Bypass initial page load requests (i.e. static assets). @@ -203,11 +203,11 @@ async function getResponse(event, client, requestId) { // means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet // and is not ready to handle requests. if (!activeClientIds.has(client.id)) { - return passthrough(); + return passthrough() } // Notify the client that a request has been intercepted. - const requestBuffer = await request.arrayBuffer(); + const requestBuffer = await request.arrayBuffer() const clientMessage = await sendToClient( client, { @@ -230,38 +230,38 @@ async function getResponse(event, client, requestId) { }, }, [requestBuffer], - ); + ) switch (clientMessage.type) { case 'MOCK_RESPONSE': { - return respondWithMock(clientMessage.data); + return respondWithMock(clientMessage.data) } case 'PASSTHROUGH': { - return passthrough(); + return passthrough() } } - return passthrough(); + return passthrough() } function sendToClient(client, message, transferrables = []) { return new Promise((resolve, reject) => { - const channel = new MessageChannel(); + const channel = new MessageChannel() channel.port1.onmessage = (event) => { if (event.data && event.data.error) { - return reject(event.data.error); + return reject(event.data.error) } - resolve(event.data); - }; + resolve(event.data) + } client.postMessage( message, [channel.port2].concat(transferrables.filter(Boolean)), - ); - }); + ) + }) } async function respondWithMock(response) { @@ -270,15 +270,15 @@ async function respondWithMock(response) { // instance will have status code set to 0. Since it's not possible to create // a Response instance with status code 0, handle that use-case separately. if (response.status === 0) { - return Response.error(); + return Response.error() } - const mockedResponse = new Response(response.body, response); + const mockedResponse = new Response(response.body, response) Reflect.defineProperty(mockedResponse, IS_MOCKED_RESPONSE, { value: true, enumerable: true, - }); + }) - return mockedResponse; + return mockedResponse } diff --git a/src/components/order/OrderListTab.tsx b/src/components/order/OrderListTab.tsx index 4e1a296e..f5496a11 100644 --- a/src/components/order/OrderListTab.tsx +++ b/src/components/order/OrderListTab.tsx @@ -28,7 +28,7 @@ const OrderListTab = ({ activeTab, setActiveTab }: OrderListTabProps) => { className={classNames( tabClass, activeTab === 'AuctionHistory' - ? 'border-b-2 border-cheeseYellow font-bold' + ? 'border-b-2 border-cheeseYellow cursor-pointer font-bold' : 'text-gray2 border-b-2 border-gray-300', isWidthScreen && 'p-2', )} @@ -48,7 +48,7 @@ const OrderListTab = ({ activeTab, setActiveTab }: OrderListTabProps) => { className={classNames( tabClass, activeTab === 'AuctionsWon' - ? 'border-b-2 border-cheeseYellow font-bold' + ? 'border-b-2 border-cheeseYellow cursor-pointer font-bold' : 'text-gray2 border-b-2 border-gray-300', isWidthScreen && 'p-2', )} diff --git a/src/components/payment/PaymentForm.tsx b/src/components/payment/PaymentForm.tsx index 400b415b..13171ffe 100644 --- a/src/components/payment/PaymentForm.tsx +++ b/src/components/payment/PaymentForm.tsx @@ -5,9 +5,9 @@ // import { loadTossPayments } from '@tosspayments/tosspayments-sdk'; // import { toast } from 'sonner'; -const PaymentForm: React.FC = () => { - return <>; -}; +// const PaymentForm: React.FC = () => { +// return <>; +// }; // const [amount, setAmount] = useState(0); // const [orderId, setOrderId] = useState(''); // const [orderName, setOrderName] = useState(''); @@ -89,4 +89,4 @@ const PaymentForm: React.FC = () => { // }); // }; -export default PaymentForm; +// export default PaymentForm; diff --git a/src/components/productList/ProductButtons.tsx b/src/components/productList/ProductButtons.tsx index d8f49286..5e541493 100644 --- a/src/components/productList/ProductButtons.tsx +++ b/src/components/productList/ProductButtons.tsx @@ -21,6 +21,19 @@ const ProductButtons = ({ setSortType }: ProductButtonsProps) => { return (
+ -
); }; diff --git a/src/components/productList/ProductListTabs.tsx b/src/components/productList/ProductListTabs.tsx index b24f59d2..9cf82575 100644 --- a/src/components/productList/ProductListTabs.tsx +++ b/src/components/productList/ProductListTabs.tsx @@ -12,7 +12,7 @@ const ProductListTabs = ({ activeTab, setActiveTab }: ProductListTabsProps) => { className={classNames( 'flex justify-center items-center w-full py-2 cursor-pointer text-sm', activeTab === 'ongoing' - ? 'border-b-2 border-cheeseYellow' + ? 'border-b-2 border-cheeseYellow cursor-pointer font-bold' : 'border-b-2 border-gray-300', )} onClick={() => setActiveTab('ongoing')} @@ -23,7 +23,7 @@ const ProductListTabs = ({ activeTab, setActiveTab }: ProductListTabsProps) => { className={classNames( 'flex justify-center w-full items-center py-2 cursor-pointer text-sm', activeTab === 'pre-enroll' - ? 'border-b-2 border-cheeseYellow' + ? 'border-b-2 border-cheeseYellow cursor-pointer font-bold' : 'border-b-2 border-gray-300', )} onClick={() => setActiveTab('pre-enroll')} diff --git a/src/components/user/UserOrderTab.tsx b/src/components/user/UserOrderTab.tsx index 8a50eade..b0ff1a4a 100644 --- a/src/components/user/UserOrderTab.tsx +++ b/src/components/user/UserOrderTab.tsx @@ -10,7 +10,7 @@ const UserOrderTab = ({ activeTab, setActiveTab }: UserOrderTabProps) => {
{
{ - return ( -
- -
- ); -}; +// const Payment = () => { +// return ( +//
+// +//
+// ); +// }; -export default Payment; +// export default Payment; From 153d3de8ee1d9f4e546e3078de9ba255e2a3d857 Mon Sep 17 00:00:00 2001 From: aquaman122 Date: Mon, 14 Oct 2024 23:19:36 +0900 Subject: [PATCH 02/18] =?UTF-8?q?chore:=20=EA=B2=B0=EC=A0=9C=20sdk=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 86 ++++++++++++++++++++ package.json | 1 + src/components/payment/Checkout.tsx | 119 ++++++++++++++++++++++++++++ src/pages/AddressBook.tsx | 26 +++++- src/pages/Payment.tsx | 22 ++--- 5 files changed, 241 insertions(+), 13 deletions(-) create mode 100644 src/components/payment/Checkout.tsx diff --git a/package-lock.json b/package-lock.json index 79abeed3..9f5c1cce 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "@reduxjs/toolkit": "^2.2.6", "@tanstack/react-query": "^5.51.11", "@tanstack/react-query-devtools": "^5.51.11", + "@tosspayments/payment-widget-sdk": "^0.12.0", "@tosspayments/tosspayments-sdk": "^2.3.2", "axios": "^1.7.2", "class-variance-authority": "^0.7.0", @@ -2408,6 +2409,91 @@ "@testing-library/dom": ">=7.21.4" } }, + "node_modules/@tosspayments/payment__types": { + "version": "1.68.0", + "resolved": "https://registry.npmjs.org/@tosspayments/payment__types/-/payment__types-1.68.0.tgz", + "integrity": "sha512-O6mEo7izwDP+Snk0ffENEWQBQ4l7ljT/YBzGlLP/2qtJJS/i3kALg7qUnvKsr71OrV2IEznkRW9CoSGg5DU5oQ==", + "dependencies": { + "@tosspayments/sdk-constants": "^0.2.2" + } + }, + "node_modules/@tosspayments/payment-widget__types": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@tosspayments/payment-widget__types/-/payment-widget__types-1.1.0.tgz", + "integrity": "sha512-1yVQIDNwgjzpm/n8ppsAk3Q0IKha+vOTuRIP6INzL9AkzCsrVjhHr5/eT0fe+tjyekSRL902Uq0TuRyG2sjsWw==", + "dependencies": { + "@tosspayments/brandpay-types": "^0.2.6", + "@tosspayments/payment__types": "^1.68.0", + "@tosspayments/payment-widget-types": "^0.0.5" + } + }, + "node_modules/@tosspayments/payment-widget__types/node_modules/@tosspayments/brandpay-types": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/@tosspayments/brandpay-types/-/brandpay-types-0.2.6.tgz", + "integrity": "sha512-FY8aOm/2rqtXjYux9ZdNTL6GC5+VNIw8eOQ7TP01dS2uGWSzAtR6uuw9HVsgkmJtSyJgmo97tjmbio5oyeyFlg==", + "peerDependencies": { + "typescript": "^4.1.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@tosspayments/payment-widget__types/node_modules/@tosspayments/payment-widget-types": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/@tosspayments/payment-widget-types/-/payment-widget-types-0.0.5.tgz", + "integrity": "sha512-Daaui9u7aRPzJhw4omJIUAbYBSmNAFNjWWkuUnX9uHxzE3gtjcJHIpGrjlzmCFm+j3ZH4rgyrgv4pkazVYH3OQ==", + "peerDependencies": { + "typescript": "^4.7.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@tosspayments/payment-widget__types/node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "optional": true, + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/@tosspayments/payment-widget-sdk": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@tosspayments/payment-widget-sdk/-/payment-widget-sdk-0.12.0.tgz", + "integrity": "sha512-Ll3K2Zh9FdTOtINnO79FdVpeEWvJOfgMHdhUfzUs/QohdbOpT78mRXW9UwexMqBdEAJD/q4NJjOqGA7cFDjsjQ==", + "dependencies": { + "@tosspayments/payment-widget__types": "1.1.0" + } + }, + "node_modules/@tosspayments/sdk-constants": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@tosspayments/sdk-constants/-/sdk-constants-0.2.2.tgz", + "integrity": "sha512-qTIcD9JnVtN65/yiW5sPAlvK5fpQtDluv4IEyslubncbQLQKE+ePSnGZU1fLNPxcchv6QTnwmgj9Ndm6EN/Ymg==", + "dependencies": { + "type-fest": "^2.11.2" + } + }, + "node_modules/@tosspayments/sdk-constants/node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@tosspayments/tosspayments-sdk": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/@tosspayments/tosspayments-sdk/-/tosspayments-sdk-2.3.2.tgz", diff --git a/package.json b/package.json index 23912193..bb67e19d 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "@reduxjs/toolkit": "^2.2.6", "@tanstack/react-query": "^5.51.11", "@tanstack/react-query-devtools": "^5.51.11", + "@tosspayments/payment-widget-sdk": "^0.12.0", "@tosspayments/tosspayments-sdk": "^2.3.2", "axios": "^1.7.2", "class-variance-authority": "^0.7.0", diff --git a/src/components/payment/Checkout.tsx b/src/components/payment/Checkout.tsx new file mode 100644 index 00000000..adcdf252 --- /dev/null +++ b/src/components/payment/Checkout.tsx @@ -0,0 +1,119 @@ +import { useEffect, useRef, useState } from "react"; +import { PaymentWidgetInstance, loadPaymentWidget, ANONYMOUS } from "@tosspayments/payment-widget-sdk"; +import { nanoid } from "nanoid"; +import { useQuery } from "@tanstack/react-query"; + +const selector = "#payment-widget"; + +// TODO: clientKey는 개발자센터의 결제위젯 연동 키 > 클라이언트 키로 바꾸세요. +// TODO: customerKey는 구매자와 1:1 관계로 무작위한 고유값을 생성하세요. +// @docs https://docs.tosspayments.com/reference/widget-sdk#sdk-설치-및-초기화 +const clientKey = 'test_gck_docs_Ovk5rk1EwkEbP0W43n07xlzm'; +const customerKey = nanoid(); + +const CheckoutPage = () => { + const { data: paymentWidget } = usePaymentWidget(clientKey, customerKey); + // const paymentWidget = usePaymentWidget(clientKey, ANONYMOUS); // 비회원 결제 + const paymentMethodsWidgetRef = useRef | null>(null); + const [price, setPrice] = useState(1); + const [paymentMethodsWidgetReady, isPaymentMethodsWidgetReady] = useState(false); + + useEffect(() => { + if (paymentWidget == null) { + return; + } + + // ------ 결제 UI 렌더링 ------ + // @docs https://docs.tosspayments.com/reference/widget-sdk#renderpaymentmethods선택자-결제-금액-옵션 + const paymentMethodsWidget = paymentWidget.renderPaymentMethods(selector, { value: price }, { variantKey: "DEFAULT" }); + + // ------ 이용약관 UI 렌더링 ------ + // @docs https://docs.tosspayments.com/reference/widget-sdk#renderagreement선택자-옵션 + paymentWidget.renderAgreement("#agreement", { variantKey: "AGREEMENT" }); + + // ------ 결제 UI 렌더링 완료 이벤트 ------ + paymentMethodsWidget.on("ready", () => { + paymentMethodsWidgetRef.current = paymentMethodsWidget; + isPaymentMethodsWidgetReady(true); + }); + }, [paymentWidget]); + + useEffect(() => { + const paymentMethodsWidget = paymentMethodsWidgetRef.current; + + if (paymentMethodsWidget == null) { + return; + } + + // ------ 금액 업데이트 ------ + // @docs https://docs.tosspayments.com/reference/widget-sdk#updateamount결제-금액 + paymentMethodsWidget.updateAmount(price); + }, [price]); + + return ( +
+
+
+
+
+
+ +
+
+ + +
+
+ ); +} + +function usePaymentWidget(clientKey: string, customerKey: string) { + return useQuery({ + queryKey: ["payment-widget", clientKey, customerKey], + queryFn: () => { + // ------ 결제위젯 초기화 ------ + // @docs https://docs.tosspayments.com/reference/widget-sdk#sdk-설치-및-초기화 + return loadPaymentWidget(clientKey, customerKey); + }, + }); +} + +export default CheckoutPage; \ No newline at end of file diff --git a/src/pages/AddressBook.tsx b/src/pages/AddressBook.tsx index 67ca3dfb..32005402 100644 --- a/src/pages/AddressBook.tsx +++ b/src/pages/AddressBook.tsx @@ -1,4 +1,4 @@ -import { useNavigate } from 'react-router-dom'; +import { useNavigate, useParams } from 'react-router-dom'; import Button from '@/components/common/Button'; import BlackShose from '@/assets/images/jordan_black.jpeg'; import { z } from 'zod'; @@ -8,7 +8,7 @@ import FormField from '@/components/common/form/FormField'; import { Input } from '@/components/ui/input'; import { ChevronDown } from 'lucide-react'; import SelectBank from '@/components/profile/SelectBank'; -import { useState } from 'react'; +import { useRef, useState } from 'react'; import Layout from '@/components/layout/Layout'; type FormFields = z.infer; @@ -21,6 +21,8 @@ const defaultValues = { const AddressBook = () => { const navigate = useNavigate(); + const formRef = useRef(null); + const { auctionId } = useParams<{ auctionId: string }>(); const [bank, setBank] = useState(''); const [activeButtonSheet, setActiveButtonSheet] = useState(false); @@ -35,6 +37,18 @@ const AddressBook = () => { setActiveButtonSheet(!activeButtonSheet); }; + const handleSubmitClick = () => { + if (formRef.current) { + formRef.current.dispatchEvent( + new Event('submit', { cancelable: true, bubbles: true }), + ); + } + }; + + const onSubmit = () => { + navigate(`/payment/${auctionId}`); + } + return ( navigate('/')} /> @@ -59,7 +73,10 @@ const AddressBook = () => {
{/* 수령자 정보 입력 */} -
+ { type="submit" className="w-full h-[47px] rounded-lg" color="cheeseYellow" + onClick={handleSubmitClick} > - 입력 완료 + 결제 하기 diff --git a/src/pages/Payment.tsx b/src/pages/Payment.tsx index 96037684..de1b5a65 100644 --- a/src/pages/Payment.tsx +++ b/src/pages/Payment.tsx @@ -1,11 +1,15 @@ -// import PaymentForm from '@/components/payment/PaymentForm'; +import Layout from "@/components/layout/Layout"; +import CheckoutPage from "@/components/payment/Checkout"; -// const Payment = () => { -// return ( -//
-// -//
-// ); -// }; +const Payment = () => { + return ( + + + + + + + ) +}; -// export default Payment; +export default Payment; \ No newline at end of file From ded8765bfcc932f0d4d0578cd195fb5a3aaf3e2f Mon Sep 17 00:00:00 2001 From: aquaman122 Date: Tue, 15 Oct 2024 15:04:24 +0900 Subject: [PATCH 03/18] =?UTF-8?q?feat:=20=EA=B2=B0=EC=A0=9C=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 86 --------------- package.json | 1 - src/components/payment/Checkout.tsx | 160 +++++++++++----------------- src/constants/api.ts | 2 + src/constants/route.ts | 3 +- src/hooks/usePayment.ts | 19 ++++ src/pages/AddressBook.tsx | 6 +- src/pages/Payment.tsx | 6 +- src/pages/PaymentSuccess.tsx | 57 ++++++++++ src/router.tsx | 5 + 10 files changed, 158 insertions(+), 187 deletions(-) create mode 100644 src/hooks/usePayment.ts create mode 100644 src/pages/PaymentSuccess.tsx diff --git a/package-lock.json b/package-lock.json index 9f5c1cce..79abeed3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,6 @@ "@reduxjs/toolkit": "^2.2.6", "@tanstack/react-query": "^5.51.11", "@tanstack/react-query-devtools": "^5.51.11", - "@tosspayments/payment-widget-sdk": "^0.12.0", "@tosspayments/tosspayments-sdk": "^2.3.2", "axios": "^1.7.2", "class-variance-authority": "^0.7.0", @@ -2409,91 +2408,6 @@ "@testing-library/dom": ">=7.21.4" } }, - "node_modules/@tosspayments/payment__types": { - "version": "1.68.0", - "resolved": "https://registry.npmjs.org/@tosspayments/payment__types/-/payment__types-1.68.0.tgz", - "integrity": "sha512-O6mEo7izwDP+Snk0ffENEWQBQ4l7ljT/YBzGlLP/2qtJJS/i3kALg7qUnvKsr71OrV2IEznkRW9CoSGg5DU5oQ==", - "dependencies": { - "@tosspayments/sdk-constants": "^0.2.2" - } - }, - "node_modules/@tosspayments/payment-widget__types": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@tosspayments/payment-widget__types/-/payment-widget__types-1.1.0.tgz", - "integrity": "sha512-1yVQIDNwgjzpm/n8ppsAk3Q0IKha+vOTuRIP6INzL9AkzCsrVjhHr5/eT0fe+tjyekSRL902Uq0TuRyG2sjsWw==", - "dependencies": { - "@tosspayments/brandpay-types": "^0.2.6", - "@tosspayments/payment__types": "^1.68.0", - "@tosspayments/payment-widget-types": "^0.0.5" - } - }, - "node_modules/@tosspayments/payment-widget__types/node_modules/@tosspayments/brandpay-types": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/@tosspayments/brandpay-types/-/brandpay-types-0.2.6.tgz", - "integrity": "sha512-FY8aOm/2rqtXjYux9ZdNTL6GC5+VNIw8eOQ7TP01dS2uGWSzAtR6uuw9HVsgkmJtSyJgmo97tjmbio5oyeyFlg==", - "peerDependencies": { - "typescript": "^4.1.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@tosspayments/payment-widget__types/node_modules/@tosspayments/payment-widget-types": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/@tosspayments/payment-widget-types/-/payment-widget-types-0.0.5.tgz", - "integrity": "sha512-Daaui9u7aRPzJhw4omJIUAbYBSmNAFNjWWkuUnX9uHxzE3gtjcJHIpGrjlzmCFm+j3ZH4rgyrgv4pkazVYH3OQ==", - "peerDependencies": { - "typescript": "^4.7.4" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@tosspayments/payment-widget__types/node_modules/typescript": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", - "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", - "optional": true, - "peer": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=4.2.0" - } - }, - "node_modules/@tosspayments/payment-widget-sdk": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@tosspayments/payment-widget-sdk/-/payment-widget-sdk-0.12.0.tgz", - "integrity": "sha512-Ll3K2Zh9FdTOtINnO79FdVpeEWvJOfgMHdhUfzUs/QohdbOpT78mRXW9UwexMqBdEAJD/q4NJjOqGA7cFDjsjQ==", - "dependencies": { - "@tosspayments/payment-widget__types": "1.1.0" - } - }, - "node_modules/@tosspayments/sdk-constants": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@tosspayments/sdk-constants/-/sdk-constants-0.2.2.tgz", - "integrity": "sha512-qTIcD9JnVtN65/yiW5sPAlvK5fpQtDluv4IEyslubncbQLQKE+ePSnGZU1fLNPxcchv6QTnwmgj9Ndm6EN/Ymg==", - "dependencies": { - "type-fest": "^2.11.2" - } - }, - "node_modules/@tosspayments/sdk-constants/node_modules/type-fest": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", - "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/@tosspayments/tosspayments-sdk": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/@tosspayments/tosspayments-sdk/-/tosspayments-sdk-2.3.2.tgz", diff --git a/package.json b/package.json index bb67e19d..23912193 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,6 @@ "@reduxjs/toolkit": "^2.2.6", "@tanstack/react-query": "^5.51.11", "@tanstack/react-query-devtools": "^5.51.11", - "@tosspayments/payment-widget-sdk": "^0.12.0", "@tosspayments/tosspayments-sdk": "^2.3.2", "axios": "^1.7.2", "class-variance-authority": "^0.7.0", diff --git a/src/components/payment/Checkout.tsx b/src/components/payment/Checkout.tsx index adcdf252..e2676f58 100644 --- a/src/components/payment/Checkout.tsx +++ b/src/components/payment/Checkout.tsx @@ -1,103 +1,76 @@ -import { useEffect, useRef, useState } from "react"; -import { PaymentWidgetInstance, loadPaymentWidget, ANONYMOUS } from "@tosspayments/payment-widget-sdk"; -import { nanoid } from "nanoid"; -import { useQuery } from "@tanstack/react-query"; +import { loadTossPayments, ANONYMOUS, TossPaymentsPayment } from "@tosspayments/tosspayments-sdk"; +import { useEffect, useState } from "react"; -const selector = "#payment-widget"; +// ------ SDK 초기화 ------ +// TODO: clientKey는 개발자센터의 API 개별 연동 키 > 결제창 연동에 사용하려할 MID > 클라이언트 키로 바꾸세요. +// TODO: server.js 의 secretKey 또한 결제위젯 연동 키가 아닌 API 개별 연동 키의 시크릿 키로 변경해야 합니다. +// TODO: 구매자의 고유 아이디를 불러와서 customerKey로 설정하세요. 이메일・전화번호와 같이 유추가 가능한 값은 안전하지 않습니다. +// @docs https://docs.tosspayments.com/sdk/v2/js#토스페이먼츠-초기화 +const clientKey = 'test_ck_P9BRQmyarYleDvqAJl9vVJ07KzLN'; +// const clientKey = `${import.meta.env.VITE_TOSS_CLIENT_KEY}`; +const customerKey = generateRandomString(); -// TODO: clientKey는 개발자센터의 결제위젯 연동 키 > 클라이언트 키로 바꾸세요. -// TODO: customerKey는 구매자와 1:1 관계로 무작위한 고유값을 생성하세요. -// @docs https://docs.tosspayments.com/reference/widget-sdk#sdk-설치-및-초기화 -const clientKey = 'test_gck_docs_Ovk5rk1EwkEbP0W43n07xlzm'; -const customerKey = nanoid(); +const amount = { + currency: "KRW", + value: 50000, +}; -const CheckoutPage = () => { - const { data: paymentWidget } = usePaymentWidget(clientKey, customerKey); - // const paymentWidget = usePaymentWidget(clientKey, ANONYMOUS); // 비회원 결제 - const paymentMethodsWidgetRef = useRef | null>(null); - const [price, setPrice] = useState(1); - const [paymentMethodsWidgetReady, isPaymentMethodsWidgetReady] = useState(false); +const PaymentCheckoutPage = ({auctionId} : {auctionId : string}) => { + const [payment, setPayment] = useState(); + const [selectedPaymentMethod, setSelectedPaymentMethod] = useState(); - useEffect(() => { - if (paymentWidget == null) { - return; - } - - // ------ 결제 UI 렌더링 ------ - // @docs https://docs.tosspayments.com/reference/widget-sdk#renderpaymentmethods선택자-결제-금액-옵션 - const paymentMethodsWidget = paymentWidget.renderPaymentMethods(selector, { value: price }, { variantKey: "DEFAULT" }); - - // ------ 이용약관 UI 렌더링 ------ - // @docs https://docs.tosspayments.com/reference/widget-sdk#renderagreement선택자-옵션 - paymentWidget.renderAgreement("#agreement", { variantKey: "AGREEMENT" }); - - // ------ 결제 UI 렌더링 완료 이벤트 ------ - paymentMethodsWidget.on("ready", () => { - paymentMethodsWidgetRef.current = paymentMethodsWidget; - isPaymentMethodsWidgetReady(true); - }); - }, [paymentWidget]); + function selectPaymentMethod(method: any) { + setSelectedPaymentMethod(method); + } useEffect(() => { - const paymentMethodsWidget = paymentMethodsWidgetRef.current; + async function fetchPayment() { + try { + const tossPayments = await loadTossPayments(clientKey); + + // 회원 결제 + // @docs https://docs.tosspayments.com/sdk/v2/js#tosspaymentspayment + const payment = tossPayments.payment({ + customerKey, + }); + // 비회원 결제 + // const payment = tossPayments.payment({ customerKey: ANONYMOUS }); - if (paymentMethodsWidget == null) { - return; + setPayment(payment); + } catch (error) { + console.error("Error fetching payment:", error); + } } - // ------ 금액 업데이트 ------ - // @docs https://docs.tosspayments.com/reference/widget-sdk#updateamount결제-금액 - paymentMethodsWidget.updateAmount(price); - }, [price]); + fetchPayment(); + }, [clientKey, customerKey]); + + // ------ '결제하기' 버튼 누르면 결제창 띄우기 ------ + // @docs https://docs.tosspayments.com/sdk/v2/js#paymentrequestpayment + async function requestPayment() { + await payment?.requestPayment({ + method: "TRANSFER", // 계좌이체 결제 + amount, + orderId: generateRandomString(), + orderName: "토스 티셔츠 외 2건", + successUrl: window.location.origin + "/payment/success", + failUrl: window.location.origin + "/fail", + customerEmail: "customer123@gmail.com", + customerName: "김토스", + customerMobilePhone: "01012341234", + transfer: { + cashReceipt: { + type: "소득공제", + }, + useEscrow: false, + }, + }); + } return (
-
-
-
-
- -
-
- -
@@ -105,15 +78,8 @@ const CheckoutPage = () => { ); } -function usePaymentWidget(clientKey: string, customerKey: string) { - return useQuery({ - queryKey: ["payment-widget", clientKey, customerKey], - queryFn: () => { - // ------ 결제위젯 초기화 ------ - // @docs https://docs.tosspayments.com/reference/widget-sdk#sdk-설치-및-초기화 - return loadPaymentWidget(clientKey, customerKey); - }, - }); +function generateRandomString() { + return window.btoa(Math.random().toString()).slice(0, 20); } -export default CheckoutPage; \ No newline at end of file +export default PaymentCheckoutPage; \ No newline at end of file diff --git a/src/constants/api.ts b/src/constants/api.ts index 838f9a22..b4f4df8b 100644 --- a/src/constants/api.ts +++ b/src/constants/api.ts @@ -26,4 +26,6 @@ export const API_END_POINT = { PRE_AUCTION_ITEM: '/api/v1/pre-auction/:auctionId', BID: '/api/v1/bids', + PAYMENT: '/api/v1/payments/approval', + CREATE_ORDERID: '/api/v1/payments/order-id', }; diff --git a/src/constants/route.ts b/src/constants/route.ts index 764caf2b..f92f3067 100644 --- a/src/constants/route.ts +++ b/src/constants/route.ts @@ -21,7 +21,8 @@ const ROUTERS = Object.freeze({ ADDRESSBOOK: '/auctions/:auctionId/shipping', BID: '/auctions/bid/:auctionId', FINAL_BIDDER_LIST: '/auctions/:auctionId/final-bidder-list', - PAYMENT: '/payment/:auctionId' + PAYMENT: '/payment/:auctionId', + PAYMENT_SUCCESS: '/success', }); export default ROUTERS; diff --git a/src/hooks/usePayment.ts b/src/hooks/usePayment.ts new file mode 100644 index 00000000..4197f17a --- /dev/null +++ b/src/hooks/usePayment.ts @@ -0,0 +1,19 @@ +import { httpClient } from "@/api/axios"; +import { API_END_POINT } from "@/constants/api"; +import { UseMutateFunction, useMutation } from "@tanstack/react-query"; + +export const usePostOrderId = () => { + const createOrderId = async () => { + const response = await httpClient.post(`${API_END_POINT.CREATE_ORDERID}`); + return response.data; + }; + + const { mutate } = useMutation({ + mutationFn: createOrderId, + onSuccess: (data) => { + console.log("성공", data); + } + }); + + return { mutate }; +}; \ No newline at end of file diff --git a/src/pages/AddressBook.tsx b/src/pages/AddressBook.tsx index 32005402..835035de 100644 --- a/src/pages/AddressBook.tsx +++ b/src/pages/AddressBook.tsx @@ -10,6 +10,7 @@ import { ChevronDown } from 'lucide-react'; import SelectBank from '@/components/profile/SelectBank'; import { useRef, useState } from 'react'; import Layout from '@/components/layout/Layout'; +import { usePostOrderId } from '@/hooks/usePayment'; type FormFields = z.infer; @@ -22,6 +23,7 @@ const defaultValues = { const AddressBook = () => { const navigate = useNavigate(); const formRef = useRef(null); + const {mutate: postOrderId} = usePostOrderId(); const { auctionId } = useParams<{ auctionId: string }>(); const [bank, setBank] = useState(''); const [activeButtonSheet, setActiveButtonSheet] = useState(false); @@ -46,7 +48,9 @@ const AddressBook = () => { }; const onSubmit = () => { - navigate(`/payment/${auctionId}`); + // postOrderId(); + // orderId, orderName, amount, paymentKey, auctionId + navigate(`/payment/${auctionId}`, { state: { auctionId : auctionId }}); } return ( diff --git a/src/pages/Payment.tsx b/src/pages/Payment.tsx index de1b5a65..4b862d1e 100644 --- a/src/pages/Payment.tsx +++ b/src/pages/Payment.tsx @@ -1,12 +1,16 @@ import Layout from "@/components/layout/Layout"; import CheckoutPage from "@/components/payment/Checkout"; +import { useLocation } from "react-router-dom"; const Payment = () => { + const location = useLocation(); + const { auctionId } = location.state; + return ( - + ) diff --git a/src/pages/PaymentSuccess.tsx b/src/pages/PaymentSuccess.tsx new file mode 100644 index 00000000..83b28aba --- /dev/null +++ b/src/pages/PaymentSuccess.tsx @@ -0,0 +1,57 @@ +import { httpClient } from "@/api/axios"; +import { API_END_POINT } from "@/constants/api"; +import { useEffect } from "react"; +import { useNavigate, useSearchParams } from "react-router-dom"; + +interface RequestData { + orderId: string | null; + amount: string | null; + paymentKey: string | null; + auctionId: string | null; +} + +const PaymentSuccess = () => { + const navigate = useNavigate(); + const [searchParams] = useSearchParams(); + const auctionId = searchParams.get("auctionId"); + + useEffect(() => { + const requestData: RequestData = { + auctionId: auctionId, + orderId: searchParams.get("orderId"), + amount: searchParams.get("amount"), + paymentKey: searchParams.get("paymentKey"), + }; + + const confirm = async () => { + try { + const response = await httpClient.post(API_END_POINT.PAYMENT, requestData); + + if (response.status !== 200) { + navigate(`/fail?message=${response.data.message}&code=${response.data.code}`); + return; + } + // 결제 성공 비지니스 로직 구현 + + } catch (error) { + console.error(error); + } + } + + confirm(); + }, [searchParams, navigate]); + + return ( +
+
+

결제 성공

+

{`주문번호: ${searchParams.get("orderId")}`}

+

{`결제 금액: ${Number( + searchParams.get("amount") + ).toLocaleString()}원`}

+

{`paymentKey: ${searchParams.get("paymentKey")}`}

+
+
+ ); +} +export default PaymentSuccess; \ No newline at end of file diff --git a/src/router.tsx b/src/router.tsx index 3109abee..036375fe 100644 --- a/src/router.tsx +++ b/src/router.tsx @@ -24,6 +24,7 @@ import Signup from './pages/Signup'; import User from './pages/User'; import OrderHistory from './pages/UserParticipatedList'; import UserRegisteredList from './pages/UserRegisteredList'; +import PaymentSuccess from './pages/PaymentSuccess'; const layoutWithNavRouteList = [ { @@ -84,6 +85,10 @@ const privateRouteList = [ path: ROUTERS.PAYMENT, element: , }, + { + path: ROUTERS.PAYMENT_SUCCESS, + element: + } ]; const publicRouteList = [ From 6d4259bc8c1de0b19ad83c52eb7676f2434fdb68 Mon Sep 17 00:00:00 2001 From: aquaman122 Date: Tue, 15 Oct 2024 15:13:10 +0900 Subject: [PATCH 04/18] =?UTF-8?q?chore:=20build=20=EC=98=A4=EB=A5=98=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/payment/Checkout.tsx | 16 ++++++++-------- src/constants/route.ts | 2 +- src/hooks/usePayment.ts | 2 +- src/pages/AddressBook.tsx | 4 ++-- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/components/payment/Checkout.tsx b/src/components/payment/Checkout.tsx index e2676f58..d0bce959 100644 --- a/src/components/payment/Checkout.tsx +++ b/src/components/payment/Checkout.tsx @@ -1,4 +1,4 @@ -import { loadTossPayments, ANONYMOUS, TossPaymentsPayment } from "@tosspayments/tosspayments-sdk"; +import { loadTossPayments, TossPaymentsPayment } from "@tosspayments/tosspayments-sdk"; import { useEffect, useState } from "react"; // ------ SDK 초기화 ------ @@ -15,13 +15,13 @@ const amount = { value: 50000, }; -const PaymentCheckoutPage = ({auctionId} : {auctionId : string}) => { +const CheckoutPage = ({ auctionId } : {auctionId : string}) => { const [payment, setPayment] = useState(); - const [selectedPaymentMethod, setSelectedPaymentMethod] = useState(); + // const [selectedPaymentMethod, setSelectedPaymentMethod] = useState(); - function selectPaymentMethod(method: any) { - setSelectedPaymentMethod(method); - } + // function selectPaymentMethod(method: any) { + // setSelectedPaymentMethod(method); + // } useEffect(() => { async function fetchPayment() { @@ -53,7 +53,7 @@ const PaymentCheckoutPage = ({auctionId} : {auctionId : string}) => { amount, orderId: generateRandomString(), orderName: "토스 티셔츠 외 2건", - successUrl: window.location.origin + "/payment/success", + successUrl: window.location.origin + `/payment/success?auctionId=${auctionId}`, failUrl: window.location.origin + "/fail", customerEmail: "customer123@gmail.com", customerName: "김토스", @@ -82,4 +82,4 @@ function generateRandomString() { return window.btoa(Math.random().toString()).slice(0, 20); } -export default PaymentCheckoutPage; \ No newline at end of file +export default CheckoutPage; \ No newline at end of file diff --git a/src/constants/route.ts b/src/constants/route.ts index f92f3067..51ec5796 100644 --- a/src/constants/route.ts +++ b/src/constants/route.ts @@ -22,7 +22,7 @@ const ROUTERS = Object.freeze({ BID: '/auctions/bid/:auctionId', FINAL_BIDDER_LIST: '/auctions/:auctionId/final-bidder-list', PAYMENT: '/payment/:auctionId', - PAYMENT_SUCCESS: '/success', + PAYMENT_SUCCESS: '/payment/success', }); export default ROUTERS; diff --git a/src/hooks/usePayment.ts b/src/hooks/usePayment.ts index 4197f17a..87ea7ab4 100644 --- a/src/hooks/usePayment.ts +++ b/src/hooks/usePayment.ts @@ -2,7 +2,7 @@ import { httpClient } from "@/api/axios"; import { API_END_POINT } from "@/constants/api"; import { UseMutateFunction, useMutation } from "@tanstack/react-query"; -export const usePostOrderId = () => { +export const usePostOrderId = (): { mutate: UseMutateFunction } => { const createOrderId = async () => { const response = await httpClient.post(`${API_END_POINT.CREATE_ORDERID}`); return response.data; diff --git a/src/pages/AddressBook.tsx b/src/pages/AddressBook.tsx index 835035de..c702ddae 100644 --- a/src/pages/AddressBook.tsx +++ b/src/pages/AddressBook.tsx @@ -10,7 +10,7 @@ import { ChevronDown } from 'lucide-react'; import SelectBank from '@/components/profile/SelectBank'; import { useRef, useState } from 'react'; import Layout from '@/components/layout/Layout'; -import { usePostOrderId } from '@/hooks/usePayment'; +// import { usePostOrderId } from '@/hooks/usePayment'; type FormFields = z.infer; @@ -23,7 +23,7 @@ const defaultValues = { const AddressBook = () => { const navigate = useNavigate(); const formRef = useRef(null); - const {mutate: postOrderId} = usePostOrderId(); + // const { mutate: postOrderId } = usePostOrderId(); const { auctionId } = useParams<{ auctionId: string }>(); const [bank, setBank] = useState(''); const [activeButtonSheet, setActiveButtonSheet] = useState(false); From b9780c82f4e490f436226c5b3a6517a49cac52a0 Mon Sep 17 00:00:00 2001 From: aquaman122 Date: Tue, 15 Oct 2024 17:14:39 +0900 Subject: [PATCH 05/18] =?UTF-8?q?feat:=20=EA=B2=B0=EC=A0=9C=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/payment/Checkout.tsx | 85 ------------------------ src/components/payment/PaymentForm.tsx | 92 -------------------------- src/constants/route.ts | 1 - src/hooks/usePayment.ts | 70 +++++++++++++++++++- src/pages/AddressBook.tsx | 14 ++-- src/pages/Payment.tsx | 19 ------ src/pages/PaymentSuccess.tsx | 28 +++++--- src/router.tsx | 8 --- 8 files changed, 92 insertions(+), 225 deletions(-) delete mode 100644 src/components/payment/Checkout.tsx delete mode 100644 src/components/payment/PaymentForm.tsx delete mode 100644 src/pages/Payment.tsx diff --git a/src/components/payment/Checkout.tsx b/src/components/payment/Checkout.tsx deleted file mode 100644 index d0bce959..00000000 --- a/src/components/payment/Checkout.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import { loadTossPayments, TossPaymentsPayment } from "@tosspayments/tosspayments-sdk"; -import { useEffect, useState } from "react"; - -// ------ SDK 초기화 ------ -// TODO: clientKey는 개발자센터의 API 개별 연동 키 > 결제창 연동에 사용하려할 MID > 클라이언트 키로 바꾸세요. -// TODO: server.js 의 secretKey 또한 결제위젯 연동 키가 아닌 API 개별 연동 키의 시크릿 키로 변경해야 합니다. -// TODO: 구매자의 고유 아이디를 불러와서 customerKey로 설정하세요. 이메일・전화번호와 같이 유추가 가능한 값은 안전하지 않습니다. -// @docs https://docs.tosspayments.com/sdk/v2/js#토스페이먼츠-초기화 -const clientKey = 'test_ck_P9BRQmyarYleDvqAJl9vVJ07KzLN'; -// const clientKey = `${import.meta.env.VITE_TOSS_CLIENT_KEY}`; -const customerKey = generateRandomString(); - -const amount = { - currency: "KRW", - value: 50000, -}; - -const CheckoutPage = ({ auctionId } : {auctionId : string}) => { - const [payment, setPayment] = useState(); - // const [selectedPaymentMethod, setSelectedPaymentMethod] = useState(); - - // function selectPaymentMethod(method: any) { - // setSelectedPaymentMethod(method); - // } - - useEffect(() => { - async function fetchPayment() { - try { - const tossPayments = await loadTossPayments(clientKey); - - // 회원 결제 - // @docs https://docs.tosspayments.com/sdk/v2/js#tosspaymentspayment - const payment = tossPayments.payment({ - customerKey, - }); - // 비회원 결제 - // const payment = tossPayments.payment({ customerKey: ANONYMOUS }); - - setPayment(payment); - } catch (error) { - console.error("Error fetching payment:", error); - } - } - - fetchPayment(); - }, [clientKey, customerKey]); - - // ------ '결제하기' 버튼 누르면 결제창 띄우기 ------ - // @docs https://docs.tosspayments.com/sdk/v2/js#paymentrequestpayment - async function requestPayment() { - await payment?.requestPayment({ - method: "TRANSFER", // 계좌이체 결제 - amount, - orderId: generateRandomString(), - orderName: "토스 티셔츠 외 2건", - successUrl: window.location.origin + `/payment/success?auctionId=${auctionId}`, - failUrl: window.location.origin + "/fail", - customerEmail: "customer123@gmail.com", - customerName: "김토스", - customerMobilePhone: "01012341234", - transfer: { - cashReceipt: { - type: "소득공제", - }, - useEscrow: false, - }, - }); - } - - return ( -
-
- -
-
- ); -} - -function generateRandomString() { - return window.btoa(Math.random().toString()).slice(0, 20); -} - -export default CheckoutPage; \ No newline at end of file diff --git a/src/components/payment/PaymentForm.tsx b/src/components/payment/PaymentForm.tsx deleted file mode 100644 index 13171ffe..00000000 --- a/src/components/payment/PaymentForm.tsx +++ /dev/null @@ -1,92 +0,0 @@ -/* eslint-disable prettier/prettier */ -// src/components/PaymentForm.tsx - -// import React, { useState, useEffect } from 'react'; -// import { loadTossPayments } from '@tosspayments/tosspayments-sdk'; -// import { toast } from 'sonner'; - -// const PaymentForm: React.FC = () => { -// return <>; -// }; -// const [amount, setAmount] = useState(0); -// const [orderId, setOrderId] = useState(''); -// const [orderName, setOrderName] = useState(''); -// const [error, setError] = useState(null); -// const [tossPayments, setTossPayments] = useState(null); - -// useEffect(() => { -// const initializeTossPayments = async () => { -// const toss = await loadTossPayments(import.meta.env.VITE_TOSS_CLIENT_KEY); -// setTossPayments(toss); -// }; -// initializeTossPayments(); -// }, []); - -// console.log(tossPayments); - -// const handlePayment = async (e: React.FormEvent) => { -// e.preventDefault(); - -// const paymentData = { -// amount, -// orderId, -// orderName, -// failUrl: 'https://yourdomain.com/payment-fail', -// // 필요한 다른 필드들 추가 -// }; - -// try { -// const response = await initiatePayment(paymentData); -// console.log(response, paymentData); -// } catch (err: any) { -// setError(err.message); -// toast.error(err.message); // 에러 메시지를 사용자에게 표시 -// } -// }; - -// return ( -// -//
-// -// setAmount(parseInt(e.target.value, 10))} -// /> -//
-//
-// -// setOrderId(e.target.value)} -// /> -//
-//
-// -// setOrderName(e.target.value)} -// /> -//
-// {error &&

{error}

} -// -// -// ); -// }; - -// const initiatePayment = async (paymentData: any) => { -// // Implement the payment initiation logic here -// // This is a placeholder function -// return new Promise((resolve, reject) => { -// setTimeout(() => { -// resolve('Payment successful'); -// console.log(reject); -// }, 1000); -// }); -// }; - -// export default PaymentForm; diff --git a/src/constants/route.ts b/src/constants/route.ts index 51ec5796..422690c2 100644 --- a/src/constants/route.ts +++ b/src/constants/route.ts @@ -21,7 +21,6 @@ const ROUTERS = Object.freeze({ ADDRESSBOOK: '/auctions/:auctionId/shipping', BID: '/auctions/bid/:auctionId', FINAL_BIDDER_LIST: '/auctions/:auctionId/final-bidder-list', - PAYMENT: '/payment/:auctionId', PAYMENT_SUCCESS: '/payment/success', }); diff --git a/src/hooks/usePayment.ts b/src/hooks/usePayment.ts index 87ea7ab4..d7edd26f 100644 --- a/src/hooks/usePayment.ts +++ b/src/hooks/usePayment.ts @@ -1,8 +1,72 @@ import { httpClient } from "@/api/axios"; import { API_END_POINT } from "@/constants/api"; import { UseMutateFunction, useMutation } from "@tanstack/react-query"; +import { TossPaymentsPayment, loadTossPayments } from "@tosspayments/tosspayments-sdk"; +import { useEffect, useState } from "react"; -export const usePostOrderId = (): { mutate: UseMutateFunction } => { +const clientKey = `${import.meta.env.VITE_TOSS_CLIENT_KEY}`; +const customerKey = generateRandomString(); + +export const postPayment = () => { + const [payment, setPayment] = useState(null); + const [amount, setAmount] = useState({ + currency: "KRW", + value: 1, + }); + + useEffect(() => { + const fetchPayment = async () => { + try { + const tossPayments = await loadTossPayments(clientKey); + const payment = tossPayments.payment({ customerKey }); + setPayment(payment); + } catch (error) { + console.error("Error fetching payment:", error); + } + }; + + fetchPayment(); + }, []); + + // ------ '결제하기' 버튼 누르면 결제창 띄우기 ------ + const requestPayment = async (auctionId: string, orderId: string) => { + if (!payment) { + console.error("Payment not initialized"); + return; + } + + try { + await payment.requestPayment({ + method: "CARD", // 카드 결제 + amount, + orderId, + orderName: "토스 티셔츠 외 2건", + successUrl: window.location.origin + `/payment/success?auctionId=${auctionId}&orderId=${orderId}`, + failUrl: window.location.origin + "/fail", + customerEmail: "customer123@gmail.com", + customerName: "김토스", + customerMobilePhone: "01012341234", + card: { + useEscrow: false, + flowMode: "DEFAULT", + useCardPoint: false, + useAppCardOnly: false, + }, + }); + } catch (error) { + console.error("Error during payment request:", error); + } + }; + + return { requestPayment, setAmount }; +}; + +function generateRandomString() { + return window.btoa(Math.random().toString()).slice(0, 20); +} + +export const usePostOrderId = (auctionId: string | undefined): { mutate: UseMutateFunction } => { + const { requestPayment } = postPayment(); const createOrderId = async () => { const response = await httpClient.post(`${API_END_POINT.CREATE_ORDERID}`); return response.data; @@ -11,7 +75,9 @@ export const usePostOrderId = (): { mutate: UseMutateFunction } => { const { mutate } = useMutation({ mutationFn: createOrderId, onSuccess: (data) => { - console.log("성공", data); + if (auctionId && data.orderId) { + requestPayment(auctionId, data.orderId); + } } }); diff --git a/src/pages/AddressBook.tsx b/src/pages/AddressBook.tsx index c702ddae..64b9d1c6 100644 --- a/src/pages/AddressBook.tsx +++ b/src/pages/AddressBook.tsx @@ -10,7 +10,7 @@ import { ChevronDown } from 'lucide-react'; import SelectBank from '@/components/profile/SelectBank'; import { useRef, useState } from 'react'; import Layout from '@/components/layout/Layout'; -// import { usePostOrderId } from '@/hooks/usePayment'; +import { usePostOrderId } from '@/hooks/usePayment'; type FormFields = z.infer; @@ -23,8 +23,10 @@ const defaultValues = { const AddressBook = () => { const navigate = useNavigate(); const formRef = useRef(null); - // const { mutate: postOrderId } = usePostOrderId(); const { auctionId } = useParams<{ auctionId: string }>(); + + const { mutate: postOrderId } = usePostOrderId(auctionId); + const [bank, setBank] = useState(''); const [activeButtonSheet, setActiveButtonSheet] = useState(false); @@ -48,14 +50,12 @@ const AddressBook = () => { }; const onSubmit = () => { - // postOrderId(); - // orderId, orderName, amount, paymentKey, auctionId - navigate(`/payment/${auctionId}`, { state: { auctionId : auctionId }}); - } + postOrderId(); + }; return ( - navigate('/')} /> + navigate('/')} />
{/* 기본 정보 입력 */} diff --git a/src/pages/Payment.tsx b/src/pages/Payment.tsx deleted file mode 100644 index 4b862d1e..00000000 --- a/src/pages/Payment.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import Layout from "@/components/layout/Layout"; -import CheckoutPage from "@/components/payment/Checkout"; -import { useLocation } from "react-router-dom"; - -const Payment = () => { - const location = useLocation(); - const { auctionId } = location.state; - - return ( - - - - - - - ) -}; - -export default Payment; \ No newline at end of file diff --git a/src/pages/PaymentSuccess.tsx b/src/pages/PaymentSuccess.tsx index 83b28aba..59f895f7 100644 --- a/src/pages/PaymentSuccess.tsx +++ b/src/pages/PaymentSuccess.tsx @@ -1,4 +1,5 @@ import { httpClient } from "@/api/axios"; +import Layout from "@/components/layout/Layout"; import { API_END_POINT } from "@/constants/api"; import { useEffect } from "react"; import { useNavigate, useSearchParams } from "react-router-dom"; @@ -13,11 +14,11 @@ interface RequestData { const PaymentSuccess = () => { const navigate = useNavigate(); const [searchParams] = useSearchParams(); - const auctionId = searchParams.get("auctionId"); + console.log(searchParams.get("orderId")); useEffect(() => { const requestData: RequestData = { - auctionId: auctionId, + auctionId: searchParams.get("auctionId"), orderId: searchParams.get("orderId"), amount: searchParams.get("amount"), paymentKey: searchParams.get("paymentKey"), @@ -42,16 +43,21 @@ const PaymentSuccess = () => { }, [searchParams, navigate]); return ( -
-
-

결제 성공

-

{`주문번호: ${searchParams.get("orderId")}`}

-

{`결제 금액: ${Number( - searchParams.get("amount") - ).toLocaleString()}원`}

-

{`paymentKey: ${searchParams.get("paymentKey")}`}

+ + + +
+
+

결제 성공

+

{`주문번호: ${searchParams.get("orderId")}`}

+

{`결제 금액: ${Number( + searchParams.get("amount") + ).toLocaleString()}원`}

+

{`paymentKey: ${searchParams.get("paymentKey")}`}

+
-
+ + ); } export default PaymentSuccess; \ No newline at end of file diff --git a/src/router.tsx b/src/router.tsx index 036375fe..5b5a8e7b 100644 --- a/src/router.tsx +++ b/src/router.tsx @@ -16,7 +16,6 @@ import Heart from './pages/Heart'; import Home from './pages/Home'; import Login from './pages/Login'; import Notification from './pages/Notification'; -import Payment from './pages/Payment'; import PreAuctionDetails, { loader as preAuctionDetailsLoader } from './pages/PreAuctionDetails'; import ProfileEdit from './pages/ProfileEdit'; import Register, { loader as registerLoader } from './pages/Register'; @@ -81,10 +80,6 @@ const privateRouteList = [ path: ROUTERS.ADDRESSBOOK, element: , }, - { - path: ROUTERS.PAYMENT, - element: , - }, { path: ROUTERS.PAYMENT_SUCCESS, element: @@ -158,9 +153,6 @@ export const router = createBrowserRouter([ ), loader: preAuctionDetailsLoader, }, - { - path: `${ROUTERS.PAYMENT}/:auctionId`, - }, ], }, ]); From cb59011b3ab9ccbb11a9c2c6b590e7c61339d9d8 Mon Sep 17 00:00:00 2001 From: aquaman122 Date: Tue, 15 Oct 2024 19:11:19 +0900 Subject: [PATCH 06/18] =?UTF-8?q?feat:=20=EA=B2=B0=EC=A0=9C=ED=95=98?= =?UTF-8?q?=EA=B8=B0=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=84=B0=20=EB=B0=9B=EC=95=84=EC=98=A4=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/address/queries.ts | 7 +++++++ src/constants/queryKeys.ts | 1 + src/hooks/usePayment.ts | 20 +++++++++++++++++--- src/pages/AddressBook.tsx | 13 ++++++------- 4 files changed, 31 insertions(+), 10 deletions(-) create mode 100644 src/components/address/queries.ts diff --git a/src/components/address/queries.ts b/src/components/address/queries.ts new file mode 100644 index 00000000..85c6beca --- /dev/null +++ b/src/components/address/queries.ts @@ -0,0 +1,7 @@ +import { httpClient } from "@/api/axios"; +import { API_END_POINT } from "@/constants/api"; + +export const useGetAddressDetail = async (auctionId: string | undefined) => { + const response = await httpClient.get(`${API_END_POINT.AUCTIONS}/${auctionId}?type=simple`); + return response.data; +} \ No newline at end of file diff --git a/src/constants/queryKeys.ts b/src/constants/queryKeys.ts index c16a1071..4bec0f76 100644 --- a/src/constants/queryKeys.ts +++ b/src/constants/queryKeys.ts @@ -5,6 +5,7 @@ export const queryKeys = Object.freeze({ PROFILE: 'PROFILE', PRODUCT: 'PRODUCT', NICKNAME: 'NICKNAME', + AUCTION: 'AUCTION', BEST_AUCTIONS: 'BEST_AUCTIONS', IMMINENT_AUCTIONS: 'IMMINENT_AUCTIONS', PRE_AUCTIONS: 'PRE_AUCTIONS', diff --git a/src/hooks/usePayment.ts b/src/hooks/usePayment.ts index d7edd26f..22410b6e 100644 --- a/src/hooks/usePayment.ts +++ b/src/hooks/usePayment.ts @@ -1,9 +1,18 @@ import { httpClient } from "@/api/axios"; +import { useGetAddressDetail } from "@/components/address/queries"; import { API_END_POINT } from "@/constants/api"; -import { UseMutateFunction, useMutation } from "@tanstack/react-query"; +import { queryKeys } from "@/constants/queryKeys"; +import { UseMutateFunction, useMutation, useQuery } from "@tanstack/react-query"; import { TossPaymentsPayment, loadTossPayments } from "@tosspayments/tosspayments-sdk"; import { useEffect, useState } from "react"; +interface AddressData { + imageUrl: string; + productName: string; + minPrice: number; + participantCount: number; +} + const clientKey = `${import.meta.env.VITE_TOSS_CLIENT_KEY}`; const customerKey = generateRandomString(); @@ -65,7 +74,7 @@ function generateRandomString() { return window.btoa(Math.random().toString()).slice(0, 20); } -export const usePostOrderId = (auctionId: string | undefined): { mutate: UseMutateFunction } => { +export const usePostOrderId = (auctionId: string | undefined): { mutate: UseMutateFunction, addressData: AddressData } => { const { requestPayment } = postPayment(); const createOrderId = async () => { const response = await httpClient.post(`${API_END_POINT.CREATE_ORDERID}`); @@ -81,5 +90,10 @@ export const usePostOrderId = (auctionId: string | undefined): { mutate: UseMuta } }); - return { mutate }; + const { data: addressData } = useQuery({ + queryKey: [queryKeys.AUCTION], + queryFn: () => useGetAddressDetail(auctionId) + }); + + return { mutate, addressData }; }; \ No newline at end of file diff --git a/src/pages/AddressBook.tsx b/src/pages/AddressBook.tsx index 64b9d1c6..29690a9d 100644 --- a/src/pages/AddressBook.tsx +++ b/src/pages/AddressBook.tsx @@ -1,6 +1,5 @@ import { useNavigate, useParams } from 'react-router-dom'; import Button from '@/components/common/Button'; -import BlackShose from '@/assets/images/jordan_black.jpeg'; import { z } from 'zod'; import { AddressBookSchema } from '@/constants/schema'; import { useForm } from 'react-hook-form'; @@ -25,7 +24,7 @@ const AddressBook = () => { const formRef = useRef(null); const { auctionId } = useParams<{ auctionId: string }>(); - const { mutate: postOrderId } = usePostOrderId(auctionId); + const { mutate: postOrderId, addressData } = usePostOrderId(auctionId); const [bank, setBank] = useState(''); const [activeButtonSheet, setActiveButtonSheet] = useState(false); @@ -62,16 +61,16 @@ const AddressBook = () => {

기본 정보 입력

{/* 상품 정보 */} -
+
product
-

[나이키] 신발

-

시작가 10,000원

-

❤️ 30개

+

{addressData.productName}

+

{`시작가: ${addressData.minPrice}원`}

+

{`참여자 수: ${addressData.participantCount}명`}

From 50a82ec8bacbcbea8a25a0f265d3019aad9fb43b Mon Sep 17 00:00:00 2001 From: aquaman122 Date: Tue, 15 Oct 2024 20:16:28 +0900 Subject: [PATCH 07/18] =?UTF-8?q?fix:=20=EC=A4=91=EB=B3=B5=ED=99=95?= =?UTF-8?q?=EC=9D=B8=20=EB=88=84=EB=A5=BC=EB=95=8C=20input=20=EC=B0=BD=20?= =?UTF-8?q?=EC=95=84=EB=9E=98=EC=97=90=20=EB=A9=94=EC=84=B8=EC=A7=80=20?= =?UTF-8?q?=EB=9D=84=EC=9A=B0=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/usePayment.ts | 5 +++-- src/pages/ProfileEdit.tsx | 21 ++++++++++++++------- src/pages/Signup.tsx | 21 +++++++++++++-------- 3 files changed, 30 insertions(+), 17 deletions(-) diff --git a/src/hooks/usePayment.ts b/src/hooks/usePayment.ts index 22410b6e..7173d5cc 100644 --- a/src/hooks/usePayment.ts +++ b/src/hooks/usePayment.ts @@ -39,6 +39,7 @@ export const postPayment = () => { // ------ '결제하기' 버튼 누르면 결제창 띄우기 ------ const requestPayment = async (auctionId: string, orderId: string) => { + const { addressData } = usePostOrderId(auctionId); if (!payment) { console.error("Payment not initialized"); return; @@ -49,12 +50,12 @@ export const postPayment = () => { method: "CARD", // 카드 결제 amount, orderId, - orderName: "토스 티셔츠 외 2건", + orderName: addressData.productName, successUrl: window.location.origin + `/payment/success?auctionId=${auctionId}&orderId=${orderId}`, failUrl: window.location.origin + "/fail", customerEmail: "customer123@gmail.com", customerName: "김토스", - customerMobilePhone: "01012341234", + customerMobilePhone: "010-1234-5678", card: { useEscrow: false, flowMode: "DEFAULT", diff --git a/src/pages/ProfileEdit.tsx b/src/pages/ProfileEdit.tsx index d32d98a6..1c15fe36 100644 --- a/src/pages/ProfileEdit.tsx +++ b/src/pages/ProfileEdit.tsx @@ -16,6 +16,7 @@ const ProfileEdit = () => { const formRef = useRef(null); const navigate = useNavigate(); const [isNicknameChecked, setIsNicknameChecked] = useState(false); + const [nicknameError, setNicknameError] = useState(null); const [profileImage, setProfileImage] = useState(null); const [profileFile, setProfileFile] = useState(null); const [useDefaultImage, setUseDefaultImage] = useState(false); @@ -61,15 +62,18 @@ const ProfileEdit = () => { handleEditProfile(formData); } else { // 에러 띄우기 닉네임 중복 확인을 해주세요. - alert('닉네임바꿔'); + setNicknameError('닉네임 중복 확인을 해주세요.'); } }; const onNicknameCheck = async () => { + if (!nickname || nickname.trim() === '') { + setNicknameError('닉네임을 입력해주세요.'); + return; + } if (nickname === originalNickname) { setIsNicknameChecked(true); - // 띄우기 - alert('닉네임 변경 안됨'); + setNicknameError('기존 닉네임입니다. 사용가능합니다.'); return; } @@ -78,11 +82,9 @@ const ProfileEdit = () => { setIsNicknameChecked(isAvailable); if (isAvailable) { - // 사용 가능한 닉네임입니다. 띄워주기 - alert('사용 가능') + setNicknameError('사용 가능한 닉네임입니다.'); } else { - // 이미 사용중인 닉네임입니다. 띄워주기 - alert('이미 사용 중') + setNicknameError('이미 사용중인 닉네임입니다.'); } }; @@ -129,6 +131,11 @@ const ProfileEdit = () => {
+ {nicknameError && ( +

+ {nicknameError} +

+ )} { const [selectBank, setSelectBank] = useState(''); const [isNicknameChecked, setIsNicknameChecked] = useState(false); + const [nicknameError, setNicknameError] = useState(null); const formRef = useRef(null); const navigate = useNavigate(); const { @@ -40,16 +41,19 @@ const Signup = () => { }); const onNicknameCheck = async () => { + if (!nickname || nickname.trim() === '') { + setNicknameError('닉네임을 입력해주세요.'); + return; + } + const { data } = await checkNickname(); const { isAvailable } = data; setIsNicknameChecked(isAvailable); if (isAvailable === true) { - // 사용 가능한 닉네임입니다. 띄워주기 - alert('사용 가능') + setNicknameError('사용 가능한 닉네임입니다.') } else { - // 이미 사용중인 닉네임입니다. 띄워주기 - alert('이미 사용 중') + setNicknameError('이미 사용중인 닉네임입니다.') } }; @@ -66,10 +70,6 @@ const Signup = () => { }); return; } - if (!isNicknameChecked) { - alert('닉네임 중복 확인 해주세요.'); - return; - } if (formRef.current) { formRef.current.dispatchEvent( @@ -109,6 +109,11 @@ const Signup = () => {
+ {nicknameError && ( +

+ {nicknameError} +

+ )}
setActiveButtonSheet(!activeButtonSheet)} From e6f8d546e1b855e013fa6e2eb0d29a7629cf8424 Mon Sep 17 00:00:00 2001 From: aquaman122 Date: Tue, 15 Oct 2024 20:51:48 +0900 Subject: [PATCH 08/18] =?UTF-8?q?fix:=20=EC=9D=80=ED=96=89=20=ED=85=8D?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=EA=B0=92=EC=9D=80=20=ED=95=9C=EA=B8=80?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/profile/SelectBank.tsx | 10 ++++----- src/constants/bank.ts | 20 ++++++++--------- src/hooks/useSignup.ts | 2 +- src/pages/Signup.tsx | 31 +++++++++++++++++++-------- 4 files changed, 38 insertions(+), 25 deletions(-) diff --git a/src/components/profile/SelectBank.tsx b/src/components/profile/SelectBank.tsx index eae674c5..f17061c2 100644 --- a/src/components/profile/SelectBank.tsx +++ b/src/components/profile/SelectBank.tsx @@ -5,13 +5,13 @@ import Modal from './Modal'; interface Props { onClose: () => void; - onSelect: (bank: string) => void; + onSelect: (displayName: string, serverName: string) => void; } const SelectBank = ({ onClose, onSelect }: Props) => { return ( -
+

은행

))}
diff --git a/src/constants/bank.ts b/src/constants/bank.ts index e8515bcc..d66820a6 100644 --- a/src/constants/bank.ts +++ b/src/constants/bank.ts @@ -10,14 +10,14 @@ import SHINHAN from '@/assets/banks/bank_shinhan.svg'; import URI from '@/assets/banks/bank_uri.svg'; export const banks = [ - { id: 1, name: 'NH', img: NH }, - { id: 2, name: 'KB', img: KB }, - { id: 3, name: 'KAKAO', img: KAKAO }, - { id: 4, name: 'SHINHAN', img: SHINHAN }, - { id: 5, name: 'WOORI', img: URI }, - { id: 6, name: 'IBK', img: IBK }, - { id: 7, name: 'HANA', img: HANA }, - { id: 8, name: 'SAEMAUL', img: SAEMAUL }, - { id: 9, name: 'CITI', img: CITY }, - { id: 10, name: 'KBANK', img: KBANK }, + { id: 1, displayName: 'NH농협', serverName: 'NH', img: NH }, + { id: 2, displayName: 'KB국민', serverName: 'KB', img: KB }, + { id: 3, displayName: '카카오뱅크', serverName: 'KAKAO', img: KAKAO }, + { id: 4, displayName: '신한', serverName: 'SHINHAN', img: SHINHAN }, + { id: 5, displayName: '우리', serverName: 'WOORI', img: URI }, + { id: 6, displayName: 'IBK기업', serverName: 'IBK', img: IBK }, + { id: 7, displayName: '하나', serverName: 'HANA', img: HANA }, + { id: 8, displayName: '새마을', serverName: 'SAEMAUL', img: SAEMAUL }, + { id: 9, displayName: '시티', serverName: 'CITI', img: CITY }, + { id: 10, displayName: 'K뱅크', serverName: 'KBANK', img: KBANK }, ]; diff --git a/src/hooks/useSignup.ts b/src/hooks/useSignup.ts index aebfa6ab..4321a8ff 100644 --- a/src/hooks/useSignup.ts +++ b/src/hooks/useSignup.ts @@ -40,7 +40,7 @@ export const useSignup = (): any => { }, // eslint-disable-next-line @typescript-eslint/no-explicit-any onError: (error: any) => { - if (error.repsonse && error.repsonse.status === 400) { + if (error.response && error.response.status === 400) { const errorMessage = error.response.data?.message || ''; if (errorMessage.includes('닉네임이 중복되었습니다.')) { setError('nickname', { message: '이미 사용 중인 닉네임입니다.' }); diff --git a/src/pages/Signup.tsx b/src/pages/Signup.tsx index 11230b3a..e17c5a35 100644 --- a/src/pages/Signup.tsx +++ b/src/pages/Signup.tsx @@ -1,4 +1,4 @@ -import { useRef, useState } from 'react'; +import { useEffect, useRef, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import Layout from '@/components/layout/Layout'; import Button from '@/components/common/Button'; @@ -13,16 +13,18 @@ import { queryKeys } from '@/constants/queryKeys'; import { nicknameCheck } from '@/components/login/queries'; const Signup = () => { - const [selectBank, setSelectBank] = useState(''); + const [selectBankDisplay, setSelectBankDisplay] = useState(''); + const [selectBankServer, setSelectBankServer] = useState(''); const [isNicknameChecked, setIsNicknameChecked] = useState(false); const [nicknameError, setNicknameError] = useState(null); + const [isSubmitEnabled, setIsSubmitEnabled] = useState(false); const formRef = useRef(null); const navigate = useNavigate(); const { control, setValue, watch, - formState: { errors, isValid }, + formState: { errors }, activeButtonSheet, setActiveButtonSheet, onCloseBottomSheet, @@ -32,6 +34,7 @@ const Signup = () => { const nickname = watch('nickname'); const accountNumber = watch('accountNumber'); + const bankname = watch('bankName'); const { refetch: checkNickname } = useQuery({ @@ -57,9 +60,10 @@ const Signup = () => { } }; - const handleSelectBank = (bank: string) => { - setValue('bankName', bank); - setSelectBank(bank); + const handleSelectBank = (displayName: string, serverName: string) => { + setSelectBankDisplay(displayName); + setSelectBankServer(serverName); + setValue('bankName', serverName); setActiveButtonSheet(false); }; @@ -78,6 +82,14 @@ const Signup = () => { } }; + useEffect(() => { + if (nickname && bankname && accountNumber && isNicknameChecked) { + setIsSubmitEnabled(true); + } else { + setIsSubmitEnabled(false); + } + }, [nickname, bankname, accountNumber, isNicknameChecked]) + return ( navigate('/')} /> @@ -110,7 +122,7 @@ const Signup = () => {
{nicknameError && ( -

+

{nicknameError}

)} @@ -129,7 +141,7 @@ const Signup = () => { placeholder="은행을 선택해주세요" className="focus-visible:ring-cheeseYellow" {...field} - value={selectBank} + value={selectBankDisplay} /> )} /> @@ -194,8 +206,9 @@ const Signup = () => { From 2f2b4e7ddcdcc125fcf16a80f643ea56abfe85b3 Mon Sep 17 00:00:00 2001 From: aquaman122 Date: Tue, 15 Oct 2024 22:05:19 +0900 Subject: [PATCH 09/18] =?UTF-8?q?fix:=20=EA=B3=84=EC=A2=8C=EB=B2=88?= =?UTF-8?q?=ED=98=B8=20=EC=9E=85=EB=A0=A5=EC=8B=9C=20=EC=88=AB=EC=9E=90=20?= =?UTF-8?q?=EA=B2=80=EC=A6=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useSignup.ts | 18 +++++++++++++++++- src/pages/ProfileEdit.tsx | 5 ++--- src/pages/Signup.tsx | 11 ++++------- 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/src/hooks/useSignup.ts b/src/hooks/useSignup.ts index 4321a8ff..e2d488b4 100644 --- a/src/hooks/useSignup.ts +++ b/src/hooks/useSignup.ts @@ -4,7 +4,7 @@ import { postSignup } from '@/components/login/queries'; import { useForm } from 'react-hook-form'; import { useMutation } from '@tanstack/react-query'; import { useNavigate } from 'react-router-dom'; -import { useState } from 'react'; +import { useEffect, useState } from 'react'; import { z } from 'zod'; type FormFields = z.infer; @@ -29,6 +29,7 @@ export const useSignup = (): any => { watch, formState: { errors }, setError, + clearErrors, } = useForm({ defaultValues, }); @@ -62,6 +63,20 @@ export const useSignup = (): any => { } return link; }; + const accountNumber = watch('accountNumber'); + useEffect(() => { + if (accountNumber && accountNumber.length < 10) { + setError('accountNumber', { + message: '계좌번호는 최소 10자리 이상이어야 합니다.', + }); + } else if (accountNumber && accountNumber.length > 15) { + setError('accountNumber', { + message: '계좌번호는 최대 15자리 이하여야 합니다.', + }); + } else { + clearErrors('accountNumber'); + } + }, [accountNumber, setError, clearErrors]); // eslint-disable-next-line @typescript-eslint/no-explicit-any const onSubmit = handleSubmit((data: any) => { @@ -79,6 +94,7 @@ export const useSignup = (): any => { setValue, activeButtonSheet, setError, + clearErrors, setActiveButtonSheet, onCloseBottomSheet, onSubmit, diff --git a/src/pages/ProfileEdit.tsx b/src/pages/ProfileEdit.tsx index 1c15fe36..59b49e76 100644 --- a/src/pages/ProfileEdit.tsx +++ b/src/pages/ProfileEdit.tsx @@ -11,6 +11,7 @@ import { useQuery } from '@tanstack/react-query'; import { queryKeys } from '@/constants/queryKeys'; import { nicknameCheck } from '@/components/login/queries'; import ProfileImageUploader from '@/components/profile/ProfileImageUploader'; +import ErrorMessage from '@/components/common/error/ErrorMessage'; const ProfileEdit = () => { const formRef = useRef(null); @@ -132,9 +133,7 @@ const ProfileEdit = () => {
{nicknameError && ( -

- {nicknameError} -

+ )} { const [selectBankDisplay, setSelectBankDisplay] = useState(''); - const [selectBankServer, setSelectBankServer] = useState(''); const [isNicknameChecked, setIsNicknameChecked] = useState(false); const [nicknameError, setNicknameError] = useState(null); const [isSubmitEnabled, setIsSubmitEnabled] = useState(false); @@ -62,7 +62,6 @@ const Signup = () => { const handleSelectBank = (displayName: string, serverName: string) => { setSelectBankDisplay(displayName); - setSelectBankServer(serverName); setValue('bankName', serverName); setActiveButtonSheet(false); }; @@ -70,7 +69,7 @@ const Signup = () => { const handleSubmitClick = () => { if (!accountNumber || accountNumber.length < 10 || accountNumber.length > 15) { setError('accountNumber', { - message: '10~15자리 숫자로 입력해주세요.', + message: '계좌번호는 10자리 이상 15자리 이하로 입력해주세요.', }); return; } @@ -122,9 +121,7 @@ const Signup = () => {
{nicknameError && ( -

- {nicknameError} -

+ )}
{ render={(field) => ( Date: Tue, 15 Oct 2024 23:04:28 +0900 Subject: [PATCH 10/18] =?UTF-8?q?feat:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=EC=A0=9C=EA=B3=B5=EC=9E=90=20=EB=B6=80=EB=B6=84=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/user/UserOrderList.tsx | 10 ++------- src/components/user/UserProfile.tsx | 31 ++++++++++++++++++++------- 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/src/components/user/UserOrderList.tsx b/src/components/user/UserOrderList.tsx index 06ca78f8..8baba778 100644 --- a/src/components/user/UserOrderList.tsx +++ b/src/components/user/UserOrderList.tsx @@ -5,11 +5,7 @@ import { useNavigate } from 'react-router-dom'; const userList = [ { - id: 2, - title: '설정', - }, - { - id: 3, + id: 1, title: '로그아웃', }, ]; @@ -21,15 +17,13 @@ const UserOrderList = () => { const handleItemClick = (title: string) => { if (title === '내가 등록한 경매 내역') { navigate(ROUTERS.REGISTERED_LIST); - } else if (title === '설정') { - // navigate('user/settings'); } else if (title === '로그아웃') { handleLogout(); } }; return ( -
+
{userList.map((item) => (
+
{profileImageUrl ? ( - 프로필 이미지 + 프로필 이미지 ) : ( - 기본 프로필 이미지 + 기본 프로필 이미지 )}
-

+

{userNickname}

- +
+ {providerType === 'KAKAO' ? ( +
+ + 카카오톡과 연결 +
+ ) : ( +
+ + 네이버와 연결 +
+ )}
-
자기소개
+
자기소개
{userBio}
- {providerType === 'kakao' ? (
kakao
) : (
naver
)}
); From 303b8fc73ece29109a57dbf4230d66f0c107897e Mon Sep 17 00:00:00 2001 From: aquaman122 Date: Tue, 15 Oct 2024 23:54:26 +0900 Subject: [PATCH 11/18] =?UTF-8?q?fix:=20=ED=94=84=EB=A1=9C=ED=95=84=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/profile/ProfileImageUploader.tsx | 4 ++-- src/components/user/UserProfile.tsx | 4 ++-- src/pages/ProfileEdit.tsx | 16 +++++++++++++--- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/components/profile/ProfileImageUploader.tsx b/src/components/profile/ProfileImageUploader.tsx index faedc531..9f04750d 100644 --- a/src/components/profile/ProfileImageUploader.tsx +++ b/src/components/profile/ProfileImageUploader.tsx @@ -17,7 +17,7 @@ const ProfileImageUploader = ({ file, setFile, image, setImage }: ImageUploaderP return (
{image ? ( -
+
프로필 사진
) : ( -
+
프로필 사진
)} diff --git a/src/components/user/UserProfile.tsx b/src/components/user/UserProfile.tsx index 72f177e2..22a10d06 100644 --- a/src/components/user/UserProfile.tsx +++ b/src/components/user/UserProfile.tsx @@ -22,9 +22,9 @@ const UserProfile = ({ nickname, bio, link, profileImageUrl, providerType }: Pro return (
{profileImageUrl ? ( - 프로필 이미지 + 프로필 이미지 ) : ( - 기본 프로필 이미지 + 기본 프로필 이미지 )}
diff --git a/src/pages/ProfileEdit.tsx b/src/pages/ProfileEdit.tsx index 59b49e76..fc25f871 100644 --- a/src/pages/ProfileEdit.tsx +++ b/src/pages/ProfileEdit.tsx @@ -20,7 +20,8 @@ const ProfileEdit = () => { const [nicknameError, setNicknameError] = useState(null); const [profileImage, setProfileImage] = useState(null); const [profileFile, setProfileFile] = useState(null); - const [useDefaultImage, setUseDefaultImage] = useState(false); + const [_useDefaultImage, setUseDefaultImage] = useState(false); + const [isSubmitEnabled, setIsSubmitEnabled] = useState(false); const { control, watch, handleSubmit, handleEditProfile, originalNickname, userProfileImageUrl } = useEditProfile(); const nickname = watch('nickname'); @@ -46,7 +47,7 @@ const ProfileEdit = () => { nickname, bio, link, - useDefaultImage + useDefaultImage: !profileFile }; if (profileFile) { @@ -95,6 +96,14 @@ const ProfileEdit = () => { } }, [userProfileImageUrl]); + useEffect(() => { + if (nickname && isNicknameChecked) { + setIsSubmitEnabled(true); + } else { + setIsSubmitEnabled(false); + } + }, [nickname, isNicknameChecked]) + return ( navigate('/user')} /> @@ -168,8 +177,9 @@ const ProfileEdit = () => { From a161d5563b287bca999094288de46a015e2e329e Mon Sep 17 00:00:00 2001 From: aquaman122 Date: Wed, 16 Oct 2024 19:16:41 +0900 Subject: [PATCH 12/18] =?UTF-8?q?feat:=20=ED=94=84=EB=A1=9C=ED=95=84=20?= =?UTF-8?q?=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/common/MenuAccordion.tsx | 33 +++++++++++++ .../profile/ProfileImageUploader.tsx | 49 +++++++++++++------ src/hooks/useProfileImageUploader.ts | 2 + 3 files changed, 70 insertions(+), 14 deletions(-) create mode 100644 src/components/common/MenuAccordion.tsx diff --git a/src/components/common/MenuAccordion.tsx b/src/components/common/MenuAccordion.tsx new file mode 100644 index 00000000..660d0b69 --- /dev/null +++ b/src/components/common/MenuAccordion.tsx @@ -0,0 +1,33 @@ +import { LuPencil } from "react-icons/lu"; +import { BsTrash } from "react-icons/bs"; +import { RefObject } from "react"; + +interface Props { + fileInputRef: RefObject; + deleteImage: () => void; + setOnMenu: (item: boolean) => void; +} + +const MenuAccordion = ({ fileInputRef, deleteImage, setOnMenu }: Props) => { + return ( +
+
{ + fileInputRef.current?.click(); + setOnMenu(false) + }} className="flex items-center gap-5 w-full text-gray-700 hover:bg-gray-200 border-b"> + + +
+
+ + +
+
+ ); +}; + +export default MenuAccordion \ No newline at end of file diff --git a/src/components/profile/ProfileImageUploader.tsx b/src/components/profile/ProfileImageUploader.tsx index 9f04750d..e3d0f47c 100644 --- a/src/components/profile/ProfileImageUploader.tsx +++ b/src/components/profile/ProfileImageUploader.tsx @@ -1,8 +1,9 @@ -import { Dispatch, SetStateAction } from 'react'; +import { Dispatch, SetStateAction, useEffect, useRef, useState } from 'react'; import { Input } from '../ui/input'; import { useProfileImageUploader } from '@/hooks/useProfileImageUploader'; -import { Button } from '../ui/button'; -import ProfileEdit from '@/assets/icons/profile_edit.svg' +import ProfileEdit from '@/assets/icons/profile_edit.svg'; +import Profile from '@/assets/icons/profile.svg'; +import MenuAccordion from '../common/MenuAccordion'; interface ImageUploaderProps { image: string | null; @@ -12,25 +13,45 @@ interface ImageUploaderProps { } const ProfileImageUploader = ({ file, setFile, image, setImage }: ImageUploaderProps) => { - const { fileInputRef, deleteImage, handleImage, handleBoxClick } = useProfileImageUploader(image, setImage, file, setFile); + const [onMenu, setOnMenu] = useState(false); + const menuRef = useRef(null); + const { fileInputRef, deleteImage, handleImage, handleBoxClick } = useProfileImageUploader(image, setImage, file, setFile, setOnMenu); + + const onClickImage = () => { + if (!onMenu) { + setOnMenu(true); + } + }; + + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if (menuRef.current && !menuRef.current.contains(event.target as Node)) { + setOnMenu(false); + } + }; + document.addEventListener('mousedown', handleClickOutside); + + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, [menuRef]); return (
{image ? ( -
+
프로필 사진 - + 프로필 사진 옆 카메라 + {onMenu && +
+ +
+ }
) : (
- 프로필 사진 + 프로필 사진 + 프로필 사진 옆 카메라
)} >, file: File | null, setFile: Dispatch>, + setOnMenu: Dispatch> ) => { const fileInputRef = useRef(null); @@ -43,6 +44,7 @@ export const useProfileImageUploader = ( const deleteImage = () => { setFile(null); setState(null); + setOnMenu(false); }; const handleBoxClick = () => { From 477c3f63271fde5e04cc5f48099fb71ff46f3c7f Mon Sep 17 00:00:00 2001 From: aquaman122 Date: Wed, 16 Oct 2024 22:14:59 +0900 Subject: [PATCH 13/18] =?UTF-8?q?feat:=ED=81=B4=EB=A6=AD=ED=95=98=EB=A9=B4?= =?UTF-8?q?=20=EC=83=81=EC=84=B8=ED=8E=98=EC=9D=B4=EC=A7=80=EB=A1=9C=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99=EA=B0=80=EB=8A=A5=ED=95=98=EA=B2=8C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/@types/AuctionItem.d.ts | 5 ++++- src/components/common/item/ProductItem.tsx | 4 ++-- src/components/order/OrderHistoryProduct.tsx | 4 +++- src/components/user/OngoingMyRegister.tsx | 4 +++- 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/@types/AuctionItem.d.ts b/src/@types/AuctionItem.d.ts index d4c2322e..413948e1 100644 --- a/src/@types/AuctionItem.d.ts +++ b/src/@types/AuctionItem.d.ts @@ -16,6 +16,7 @@ declare module 'AuctionItem' { createdAt: string; participantCount: number; timeRemaining: number; + auctionId: number; } export interface IPreAuctionItem extends IAuctionItemBase { @@ -37,5 +38,7 @@ declare module 'AuctionItem' { highestAmount: number; auctionId: number; } - export interface IUserAuctionHistoryItem extends Omit {} + export interface IUserAuctionHistoryItem extends Omit { + auctionId: number; + } } diff --git a/src/components/common/item/ProductItem.tsx b/src/components/common/item/ProductItem.tsx index 243b53d3..5f161ae0 100644 --- a/src/components/common/item/ProductItem.tsx +++ b/src/components/common/item/ProductItem.tsx @@ -15,9 +15,9 @@ export interface ProductProps { createdAt?: string; } -const ProductItem = ({ product, children }: { product: ProductProps; children: ReactNode }) => { +const ProductItem = ({ product, children, onClick }: { product: ProductProps; children: ReactNode, onClick?: () => void }) => { return ( -
+
diff --git a/src/components/order/OrderHistoryProduct.tsx b/src/components/order/OrderHistoryProduct.tsx index 820b2d1f..dea5613f 100644 --- a/src/components/order/OrderHistoryProduct.tsx +++ b/src/components/order/OrderHistoryProduct.tsx @@ -3,12 +3,14 @@ import { IoPricetagsOutline } from 'react-icons/io5'; import { LuUsers } from 'react-icons/lu'; import ProductItem from '../common/item/ProductItem'; import { formatCurrencyWithWon } from '@/utils/formatCurrencyWithWon'; +import { useNavigate } from 'react-router-dom'; const OrderHistoryProduct = ({ product }: { product: IUserAuctionHistoryItem }) => { + const navigate = useNavigate(); const formattedPrice = formatCurrencyWithWon(product.minPrice); return ( - + navigate(`/auctions/bid/${product.auctionId}`)}>
diff --git a/src/components/user/OngoingMyRegister.tsx b/src/components/user/OngoingMyRegister.tsx index f7368544..ffa12c81 100644 --- a/src/components/user/OngoingMyRegister.tsx +++ b/src/components/user/OngoingMyRegister.tsx @@ -3,12 +3,14 @@ import { IoPricetagsOutline } from 'react-icons/io5'; import ProductItem from '../common/item/ProductItem'; import type { IAuctionRegisteredItem } from 'AuctionItem'; import { formatCurrencyWithWon } from '@/utils/formatCurrencyWithWon'; +import { useNavigate } from 'react-router-dom'; const OngoingMyRegister = ({ product }: { product: IAuctionRegisteredItem }) => { + const navigate = useNavigate(); const formattedPrice = formatCurrencyWithWon(product.minPrice); return ( - + navigate(`/auctions/bid/${product.auctionId}`)}>
From aa5396ec68a219d1b9f5b12100bc23b7a2a7cc23 Mon Sep 17 00:00:00 2001 From: aquaman122 Date: Thu, 17 Oct 2024 12:25:27 +0900 Subject: [PATCH 14/18] =?UTF-8?q?fix:=20=ED=94=BC=EB=93=9C=EB=B0=B1=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/address/queries.ts | 2 +- src/constants/queryKeys.ts | 2 +- src/hooks/usePayment.ts | 9 ++++----- src/pages/AddressBook.tsx | 4 +++- src/pages/PaymentSuccess.tsx | 3 +-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/components/address/queries.ts b/src/components/address/queries.ts index 85c6beca..e55cb504 100644 --- a/src/components/address/queries.ts +++ b/src/components/address/queries.ts @@ -1,7 +1,7 @@ import { httpClient } from "@/api/axios"; import { API_END_POINT } from "@/constants/api"; -export const useGetAddressDetail = async (auctionId: string | undefined) => { +export const useGetAddressDetail = async (auctionId: string) => { const response = await httpClient.get(`${API_END_POINT.AUCTIONS}/${auctionId}?type=simple`); return response.data; } \ No newline at end of file diff --git a/src/constants/queryKeys.ts b/src/constants/queryKeys.ts index 4bec0f76..d7f6648b 100644 --- a/src/constants/queryKeys.ts +++ b/src/constants/queryKeys.ts @@ -5,7 +5,7 @@ export const queryKeys = Object.freeze({ PROFILE: 'PROFILE', PRODUCT: 'PRODUCT', NICKNAME: 'NICKNAME', - AUCTION: 'AUCTION', + AUCTION_ADDRESS_DETAIL: 'AUCTION', BEST_AUCTIONS: 'BEST_AUCTIONS', IMMINENT_AUCTIONS: 'IMMINENT_AUCTIONS', PRE_AUCTIONS: 'PRE_AUCTIONS', diff --git a/src/hooks/usePayment.ts b/src/hooks/usePayment.ts index 7173d5cc..4cc99cef 100644 --- a/src/hooks/usePayment.ts +++ b/src/hooks/usePayment.ts @@ -30,7 +30,7 @@ export const postPayment = () => { const payment = tossPayments.payment({ customerKey }); setPayment(payment); } catch (error) { - console.error("Error fetching payment:", error); + throw error; } }; @@ -41,7 +41,6 @@ export const postPayment = () => { const requestPayment = async (auctionId: string, orderId: string) => { const { addressData } = usePostOrderId(auctionId); if (!payment) { - console.error("Payment not initialized"); return; } @@ -64,7 +63,7 @@ export const postPayment = () => { }, }); } catch (error) { - console.error("Error during payment request:", error); + throw error; } }; @@ -75,7 +74,7 @@ function generateRandomString() { return window.btoa(Math.random().toString()).slice(0, 20); } -export const usePostOrderId = (auctionId: string | undefined): { mutate: UseMutateFunction, addressData: AddressData } => { +export const usePostOrderId = (auctionId: string): { mutate: UseMutateFunction, addressData: AddressData } => { const { requestPayment } = postPayment(); const createOrderId = async () => { const response = await httpClient.post(`${API_END_POINT.CREATE_ORDERID}`); @@ -92,7 +91,7 @@ export const usePostOrderId = (auctionId: string | undefined): { mutate: UseMuta }); const { data: addressData } = useQuery({ - queryKey: [queryKeys.AUCTION], + queryKey: [queryKeys.AUCTION_ADDRESS_DETAIL], queryFn: () => useGetAddressDetail(auctionId) }); diff --git a/src/pages/AddressBook.tsx b/src/pages/AddressBook.tsx index 29690a9d..1d09b00f 100644 --- a/src/pages/AddressBook.tsx +++ b/src/pages/AddressBook.tsx @@ -23,7 +23,9 @@ const AddressBook = () => { const navigate = useNavigate(); const formRef = useRef(null); const { auctionId } = useParams<{ auctionId: string }>(); - + if (!auctionId) { + return; + } const { mutate: postOrderId, addressData } = usePostOrderId(auctionId); const [bank, setBank] = useState(''); diff --git a/src/pages/PaymentSuccess.tsx b/src/pages/PaymentSuccess.tsx index 59f895f7..3f1ae3cd 100644 --- a/src/pages/PaymentSuccess.tsx +++ b/src/pages/PaymentSuccess.tsx @@ -14,7 +14,6 @@ interface RequestData { const PaymentSuccess = () => { const navigate = useNavigate(); const [searchParams] = useSearchParams(); - console.log(searchParams.get("orderId")); useEffect(() => { const requestData: RequestData = { @@ -35,7 +34,7 @@ const PaymentSuccess = () => { // 결제 성공 비지니스 로직 구현 } catch (error) { - console.error(error); + throw error; } } From 59a11ab9e9d15bac52ac3c1d375218fd51024a03 Mon Sep 17 00:00:00 2001 From: aquaman122 Date: Thu, 17 Oct 2024 15:08:54 +0900 Subject: [PATCH 15/18] =?UTF-8?q?chore:=20ProductItem=20=ED=86=B5=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/common/ProductItem.tsx | 50 ---------------------- src/components/common/item/ProductItem.tsx | 43 +++++++++++++------ 2 files changed, 30 insertions(+), 63 deletions(-) delete mode 100644 src/components/common/ProductItem.tsx diff --git a/src/components/common/ProductItem.tsx b/src/components/common/ProductItem.tsx deleted file mode 100644 index 2416151e..00000000 --- a/src/components/common/ProductItem.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import TimeLabel from './atomic/TimeLabel'; - -export interface ProductProps { - id: number; - name: string; - minPrice: number; - timeRemaining?: number; - participantCount?: number; - isParticipating?: boolean; - likeCount?: number; - isLiked?: boolean; - status?: string; - createdAt?: string; - imageUrl?: string; -} - -const ProductItem = ({ - product, - children, -}: { - product: ProductProps; - children: React.ReactNode; -}) => { - - return ( -
-
-
-
- Jordan Black Shoes - {product.timeRemaining && } -
-
- -
-
-

{product.name}

-
-
{children}
-
-
-
- ); -}; - -export default ProductItem; diff --git a/src/components/common/item/ProductItem.tsx b/src/components/common/item/ProductItem.tsx index 5f161ae0..be13d7d1 100644 --- a/src/components/common/item/ProductItem.tsx +++ b/src/components/common/item/ProductItem.tsx @@ -1,11 +1,12 @@ -import TimeLabel from '../atomic/TimeLabel'; import { ReactNode } from 'react'; +import TimeLabel from '../atomic/TimeLabel'; export interface ProductProps { + id?: number; auctionId?: number; - productName: string; + name?: string; + productName?: string; minPrice: number; - imageUrl?: string; timeRemaining?: number; participantCount?: number; isParticipating?: boolean; @@ -13,28 +14,44 @@ export interface ProductProps { isLiked?: boolean; status?: string; createdAt?: string; + imageUrl?: string; } -const ProductItem = ({ product, children, onClick }: { product: ProductProps; children: ReactNode, onClick?: () => void }) => { +const ProductItem = ({ + product, + children, + onClick, +}: { + product: ProductProps; + children: ReactNode; + onClick?: () => void; +}) => { + const displayName = product.productName || product.name; + const productId = product.auctionId || product.id; + return ( -
-
-
-
- 제품 사진 +
+
+
+
+ {displayName {product.timeRemaining && }
-
+
-

{product.productName}

+

{displayName}

-
{children}
+
{children}
); }; -export default ProductItem; +export default ProductItem; \ No newline at end of file From 4c63b8668e77c4f091793b764da8b11fe6d40042 Mon Sep 17 00:00:00 2001 From: aquaman122 Date: Thu, 17 Oct 2024 15:28:04 +0900 Subject: [PATCH 16/18] =?UTF-8?q?chore:=20=EA=B3=B5=ED=86=B5=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=82=AC=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/order/OrderHistoryProduct.tsx | 22 +++------------- src/components/order/OrderLostProduct.tsx | 11 ++------ src/components/order/OrderWonProduct.tsx | 10 ++------ src/components/productList/OngoingProduct.tsx | 22 +++------------- .../productList/PreEnrollProduct.tsx | 22 +++------------- src/components/user/OngoingMyRegister.tsx | 22 +++------------- src/components/user/PreEnrollMyRegister.tsx | 25 ++++--------------- 7 files changed, 25 insertions(+), 109 deletions(-) diff --git a/src/components/order/OrderHistoryProduct.tsx b/src/components/order/OrderHistoryProduct.tsx index dea5613f..a9e82681 100644 --- a/src/components/order/OrderHistoryProduct.tsx +++ b/src/components/order/OrderHistoryProduct.tsx @@ -1,30 +1,16 @@ import type { IUserAuctionHistoryItem } from 'AuctionItem'; -import { IoPricetagsOutline } from 'react-icons/io5'; -import { LuUsers } from 'react-icons/lu'; import ProductItem from '../common/item/ProductItem'; -import { formatCurrencyWithWon } from '@/utils/formatCurrencyWithWon'; import { useNavigate } from 'react-router-dom'; +import MinPrice from '../common/atomic/MinPrice'; +import ParticipantCount from '../common/atomic/ParticipantCount'; const OrderHistoryProduct = ({ product }: { product: IUserAuctionHistoryItem }) => { const navigate = useNavigate(); - const formattedPrice = formatCurrencyWithWon(product.minPrice); return ( navigate(`/auctions/bid/${product.auctionId}`)}> -
-
- -

시작가

-
-

{formattedPrice}

-
-
-
- -

참여자

-
-

{`${product.participantCount}명`}

-
+ +
); }; diff --git a/src/components/order/OrderLostProduct.tsx b/src/components/order/OrderLostProduct.tsx index 408edf31..42402118 100644 --- a/src/components/order/OrderLostProduct.tsx +++ b/src/components/order/OrderLostProduct.tsx @@ -1,8 +1,8 @@ import type { IUserAuctionLostItem } from 'AuctionItem'; -import { IoPricetagsOutline } from 'react-icons/io5'; import { LuUsers } from 'react-icons/lu'; import ProductItem from '../common/item/ProductItem'; import { formatCurrencyWithWon } from '@/utils/formatCurrencyWithWon'; +import MinPrice from '../common/atomic/MinPrice'; const OrderLostProduct = ({ product }: { product: IUserAuctionLostItem }) => { const date = new Date(product.endDateTime); @@ -11,19 +11,12 @@ const OrderLostProduct = ({ product }: { product: IUserAuctionLostItem }) => { const day = String(date.getDate()).padStart(2, '0'); const formattedDate = `${year}년 ${month}월 ${day}일`; - const formattedMinPrice = formatCurrencyWithWon(product.minPrice); const formattedHighPrice = formatCurrencyWithWon(product.highestAmount); return ( -
-
- -

시작가

-
-

{formattedMinPrice}

-
+
diff --git a/src/components/order/OrderWonProduct.tsx b/src/components/order/OrderWonProduct.tsx index 607aea3a..d3b691a0 100644 --- a/src/components/order/OrderWonProduct.tsx +++ b/src/components/order/OrderWonProduct.tsx @@ -3,6 +3,7 @@ import { IoPricetagsOutline } from 'react-icons/io5'; import { LuUsers } from 'react-icons/lu'; import ProductItem from '../common/item/ProductItem'; import { formatCurrencyWithWon } from '@/utils/formatCurrencyWithWon'; +import MinPrice from '../common/atomic/MinPrice'; const OrderWonProduct = ({ product }: { product: IUserAuctionWonItem }) => { const date = new Date(product.endDateTime); @@ -11,18 +12,11 @@ const OrderWonProduct = ({ product }: { product: IUserAuctionWonItem }) => { const day = String(date.getDate()).padStart(2, '0'); const formattedDate = `${year}년 ${month}월 ${day}일`; - const formattedMinPrice = formatCurrencyWithWon(product.minPrice); const formattedWinningPrice = formatCurrencyWithWon(product.winningAmount); return ( -
-
- -

시작가

-
-

{formattedMinPrice}

-
+
diff --git a/src/components/productList/OngoingProduct.tsx b/src/components/productList/OngoingProduct.tsx index 3d23f12a..caad84a9 100644 --- a/src/components/productList/OngoingProduct.tsx +++ b/src/components/productList/OngoingProduct.tsx @@ -1,32 +1,18 @@ import Button from '../common/Button'; import type { IAuctionItem } from 'AuctionItem'; -import { IoPricetagsOutline } from 'react-icons/io5'; -import { LuUsers } from 'react-icons/lu'; import ProductItem from '../common/item/ProductItem'; import { useNavigate } from 'react-router-dom'; -import { formatCurrencyWithWon } from '@/utils/formatCurrencyWithWon'; +import MinPrice from '../common/atomic/MinPrice'; +import ParticipantCount from '../common/atomic/ParticipantCount'; const OngoingProduct = ({ product }: { product: IAuctionItem }) => { const navigate = useNavigate(); const handleClick = () => navigate(`/auctions/bid/${product.auctionId}`); - const formattedPrice = formatCurrencyWithWon(product.minPrice); return ( -
-
- -

시작가

-
-

{formattedPrice}

-
-
-
- -

참여자

-
-

{`${product.participantCount}명`}

-
+ + diff --git a/src/components/user/OngoingMyRegister.tsx b/src/components/user/OngoingMyRegister.tsx index ffa12c81..902d52ae 100644 --- a/src/components/user/OngoingMyRegister.tsx +++ b/src/components/user/OngoingMyRegister.tsx @@ -1,30 +1,16 @@ -import { LuUsers } from 'react-icons/lu'; -import { IoPricetagsOutline } from 'react-icons/io5'; import ProductItem from '../common/item/ProductItem'; import type { IAuctionRegisteredItem } from 'AuctionItem'; -import { formatCurrencyWithWon } from '@/utils/formatCurrencyWithWon'; import { useNavigate } from 'react-router-dom'; +import MinPrice from '../common/atomic/MinPrice'; +import ParticipantCount from '../common/atomic/ParticipantCount'; const OngoingMyRegister = ({ product }: { product: IAuctionRegisteredItem }) => { const navigate = useNavigate(); - const formattedPrice = formatCurrencyWithWon(product.minPrice); return ( navigate(`/auctions/bid/${product.auctionId}`)}> -
-
- -

시작가

-
-

{formattedPrice}

-
-
-
- -

참여자

-
-

{`${product.participantCount}명`}

-
+ +
); }; diff --git a/src/components/user/PreEnrollMyRegister.tsx b/src/components/user/PreEnrollMyRegister.tsx index fa927232..5824d5a3 100644 --- a/src/components/user/PreEnrollMyRegister.tsx +++ b/src/components/user/PreEnrollMyRegister.tsx @@ -1,28 +1,13 @@ -import { FaHeart } from 'react-icons/fa'; import type { IPreAuctionRegisteredItem } from 'AuctionItem'; -import { IoPricetagsOutline } from 'react-icons/io5'; import ProductItem from '../common/item/ProductItem'; -import { formatCurrencyWithWon } from '@/utils/formatCurrencyWithWon'; +import LikeCount from '../common/atomic/LikeCount'; +import MinPrice from '../common/atomic/MinPrice'; -const PreEnrollMyRegister = ({ product }: { product: IPreAuctionRegisteredItem }) => { - const formattedPrice = formatCurrencyWithWon(product.minPrice); - +const PreEnrollMyRegister = ({ product }: { product: IPreAuctionRegisteredItem }) => { return ( -
-
- -

시작가

-
-

{formattedPrice}

-
-
-
- -

좋아요

-
-

{`${product.likeCount}`}

-
+ +
); }; From c4e8cd92aeb7a6a85340ff1d417e3b7615f86b6e Mon Sep 17 00:00:00 2001 From: aquaman122 Date: Thu, 17 Oct 2024 15:28:58 +0900 Subject: [PATCH 17/18] =?UTF-8?q?chore:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=EC=BD=94=EB=93=9C=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/order/OrderWonProduct.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/order/OrderWonProduct.tsx b/src/components/order/OrderWonProduct.tsx index d3b691a0..df331486 100644 --- a/src/components/order/OrderWonProduct.tsx +++ b/src/components/order/OrderWonProduct.tsx @@ -1,5 +1,4 @@ import type { IUserAuctionWonItem } from 'AuctionItem'; -import { IoPricetagsOutline } from 'react-icons/io5'; import { LuUsers } from 'react-icons/lu'; import ProductItem from '../common/item/ProductItem'; import { formatCurrencyWithWon } from '@/utils/formatCurrencyWithWon'; From 9ace6e50097e31320281b3ce5d8b71ac670969b2 Mon Sep 17 00:00:00 2001 From: aquaman122 Date: Thu, 17 Oct 2024 16:36:40 +0900 Subject: [PATCH 18/18] =?UTF-8?q?merge=20=ED=9B=84=20router=20=EB=B3=91?= =?UTF-8?q?=ED=95=A9=20=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/router.tsx | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/router.tsx b/src/router.tsx index 9fdeb3c2..db715750 100644 --- a/src/router.tsx +++ b/src/router.tsx @@ -1,9 +1,7 @@ import AuctionDetails, { loader as auctionDetailsLoader } from './pages/AuctionDetails'; import Bid, { loader as bidLoader } from './pages/Bid'; import BidderList, { loader as bidderListLoader } from './pages/BidderList'; -import PreAuctionDetails, { loader as preAuctionDetailsLoader } from './pages/PreAuctionDetails'; import Register, { loader as registerLoader } from './pages/Register'; - import ROUTERS from '@/constants/route'; import ProductList from '@/pages/ProductList'; import { createBrowserRouter } from 'react-router-dom'; @@ -156,9 +154,6 @@ export const router = createBrowserRouter([ ), loader: preAuctionDetailsLoader, }, - { - path: `${ROUTERS.PAYMENT}/:auctionId`, - }, { path: `/test`, element: