diff --git a/.eslintrc.json b/.eslintrc.json index a24c94e..f7d6649 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,15 +1,31 @@ { - "extends": [ - "react-app", - "react-app/jest", - "plugin:react/recommended", - "plugin:prettier/recommended" - ], - "plugins": ["prettier"], - "rules": { - "prettier/prettier": "error", - "react/react-in-jsx-scope": "off", - "react/prop-types": "off" + "env": { + "browser": true, + "es2021": true + }, + "extends": [ + "eslint:recommended", + "plugin:react/recommended", + "plugin:@typescript-eslint/recommended", + "plugin:react-hooks/recommended" + ], + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaFeatures": { + "jsx": true + }, + "ecmaVersion": "latest", + "sourceType": "module" + }, + "plugins": ["react", "@typescript-eslint", "react-hooks"], + "rules": { + "react/react-in-jsx-scope": "off", + "react/prop-types": "off", + "@typescript-eslint/explicit-module-boundary-types": "off" + }, + "settings": { + "react": { + "version": "detect" } } - \ No newline at end of file +} diff --git a/.vscode/settings.json b/.vscode/settings.json index f5ca5f3..7f3c887 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,10 +1,15 @@ { - "editor.formatOnSave": true, - "editor.defaultFormatter": "esbenp.prettier-vscode", - "editor.codeActionsOnSave": { - "source.formatDocument": "explicit", - "source.fixAll.eslint": "explicit" - }, - "files.eol": "\n" - } - \ No newline at end of file + "editor.formatOnSave": true, + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.codeActionsOnSave": { + "source.formatDocument": "explicit", + "source.fixAll.eslint": "explicit" + }, + "files.eol": "\n", + "eslint.validate": [ + "javascript", + "javascriptreact", + "typescript", + "typescriptreact" + ] +} diff --git a/package-lock.json b/package-lock.json index aea0056..607bfea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,7 +21,6 @@ "@types/styled-components": "^5.1.34", "@vitejs/plugin-react": "^4.3.3", "axios": "^1.7.7", - "msw": "^2.6.5", "react": "^18.3.1", "react-daum-postcode": "^3.1.3", "react-dom": "^18.3.1", @@ -38,10 +37,10 @@ "@types/react-slick": "^0.23.13", "eslint": "^8.57.1", "eslint-config-prettier": "^9.1.0", + "eslint-config-react": "^1.1.7", "eslint-plugin-import": "^2.31.0", "eslint-plugin-jsx-a11y": "^6.10.2", "eslint-plugin-prettier": "^5.2.1", - "eslint-plugin-react": "^7.37.2", "eslint-plugin-react-hooks": "^5.0.0", "globals": "^15.12.0", "husky": "^9.1.7", @@ -2037,39 +2036,6 @@ "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==" }, - "node_modules/@bundled-es-modules/cookie": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@bundled-es-modules/cookie/-/cookie-2.0.1.tgz", - "integrity": "sha512-8o+5fRPLNbjbdGRRmJj3h6Hh1AQJf2dk3qQ/5ZFb+PXkRNiSoMGGUKlsgLfrxneb72axVJyIYji64E2+nNfYyw==", - "dependencies": { - "cookie": "^0.7.2" - } - }, - "node_modules/@bundled-es-modules/cookie/node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/@bundled-es-modules/statuses": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@bundled-es-modules/statuses/-/statuses-1.0.1.tgz", - "integrity": "sha512-yn7BklA5acgcBr+7w064fGV+SGIFySjCKpqjcWgBAIfrAkY+4GQTJJHQMeT3V/sgz23VTEVV8TtOmkvJAhFVfg==", - "dependencies": { - "statuses": "^2.0.1" - } - }, - "node_modules/@bundled-es-modules/tough-cookie": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/@bundled-es-modules/tough-cookie/-/tough-cookie-0.1.6.tgz", - "integrity": "sha512-dvMHbL464C0zI+Yqxbz6kZ5TOEp7GLW+pry/RWndAR8MJQAXZ2rPmIs8tziTZjeIyhSNZgZbCePtfSbdWqStJw==", - "dependencies": { - "@types/tough-cookie": "^4.0.5", - "tough-cookie": "^4.1.4" - } - }, "node_modules/@csstools/normalize.css": { "version": "12.1.1", "resolved": "https://registry.npmjs.org/@csstools/normalize.css/-/normalize.css-12.1.1.tgz", @@ -2832,68 +2798,6 @@ "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", "deprecated": "Use @eslint/object-schema instead" }, - "node_modules/@inquirer/core": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.1.0.tgz", - "integrity": "sha512-I+ETk2AL+yAVbvuKx5AJpQmoaWhpiTFOg/UJb7ZkMAK4blmtG8ATh5ct+T/8xNld0CZG/2UhtkdMwpgvld92XQ==", - "dependencies": { - "@inquirer/figures": "^1.0.8", - "@inquirer/type": "^3.0.1", - "ansi-escapes": "^4.3.2", - "cli-width": "^4.1.0", - "mute-stream": "^2.0.0", - "signal-exit": "^4.1.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^6.2.0", - "yoctocolors-cjs": "^2.1.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@inquirer/core/node_modules/@inquirer/type": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.1.tgz", - "integrity": "sha512-+ksJMIy92sOAiAccGpcKZUc3bYO07cADnscIxHBknEm3uNts3movSmBofc1908BNy5edKscxYeAdaX1NXkHS6A==", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - } - }, - "node_modules/@inquirer/core/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@inquirer/core/node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@inquirer/figures": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.8.tgz", - "integrity": "sha512-tKd+jsmhq21AP1LhexC0pPwsCxEhGgAkg28byjJAd+xhmIs8LUX8JbUc3vBf3PhLxWiB5EvyBE5X7JSPAqMAqg==", - "engines": { - "node": ">=18" - } - }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -3335,22 +3239,6 @@ "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==" }, - "node_modules/@mswjs/interceptors": { - "version": "0.37.1", - "resolved": "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.37.1.tgz", - "integrity": "sha512-SvE+tSpcX884RJrPCskXxoS965Ky/pYABDEhWW6oeSRhpUDLrS5nTvT5n1LLSDVDYvty4imVmXsy+3/ROVuknA==", - "dependencies": { - "@open-draft/deferred-promise": "^2.2.0", - "@open-draft/logger": "^0.3.0", - "@open-draft/until": "^2.0.0", - "is-node-process": "^1.2.0", - "outvariant": "^1.4.3", - "strict-event-emitter": "^0.5.1" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { "version": "5.1.1-v1", "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", @@ -3411,25 +3299,6 @@ "node": ">= 8" } }, - "node_modules/@open-draft/deferred-promise": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@open-draft/deferred-promise/-/deferred-promise-2.2.0.tgz", - "integrity": "sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==" - }, - "node_modules/@open-draft/logger": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@open-draft/logger/-/logger-0.3.0.tgz", - "integrity": "sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==", - "dependencies": { - "is-node-process": "^1.2.0", - "outvariant": "^1.4.0" - } - }, - "node_modules/@open-draft/until": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@open-draft/until/-/until-2.1.0.tgz", - "integrity": "sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==" - }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -4312,11 +4181,6 @@ "@types/node": "*" } }, - "node_modules/@types/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==" - }, "node_modules/@types/eslint": { "version": "8.56.12", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.12.tgz", @@ -4607,11 +4471,6 @@ "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==" }, - "node_modules/@types/statuses": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@types/statuses/-/statuses-2.0.5.tgz", - "integrity": "sha512-jmIUGWrAiwu3dZpxntxieC+1n/5c3mjrImkmOSQ2NC5uP6cYO4aAZDdSmRcI5C1oiTmqlZGHC+/NmJrKogbP5A==" - }, "node_modules/@types/styled-components": { "version": "5.1.34", "resolved": "https://registry.npmjs.org/@types/styled-components/-/styled-components-5.1.34.tgz", @@ -4635,11 +4494,6 @@ "@types/jest": "*" } }, - "node_modules/@types/tough-cookie": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", - "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==" - }, "node_modules/@types/trusted-types": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", @@ -6312,14 +6166,6 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/cli-width": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", - "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", - "engines": { - "node": ">= 12" - } - }, "node_modules/cliui": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", @@ -7892,6 +7738,12 @@ "eslint": ">=7.0.0" } }, + "node_modules/eslint-config-react": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/eslint-config-react/-/eslint-config-react-1.1.7.tgz", + "integrity": "sha512-P4Z6u68wf0BvIvZNu+U8uQsk3DcZ1CcCI1XpUkJlG6vOa+iVcSQLgE01f2DB2kXlKRcT8/3dsH+wveLgvEgbkQ==", + "dev": true + }, "node_modules/eslint-config-react-app": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/eslint-config-react-app/-/eslint-config-react-app-7.0.1.tgz", @@ -9327,14 +9179,6 @@ "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==" }, - "node_modules/graphql": { - "version": "16.9.0", - "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.9.0.tgz", - "integrity": "sha512-GGTKBX4SD7Wdb8mqeDLni2oaRGYQWjWHGKPQ24ZMnUtKfcsVoiv4uX8+LJr1K6U5VW2Lu1BwJnj7uiori0YtRw==", - "engines": { - "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" - } - }, "node_modules/gzip-size": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz", @@ -9441,11 +9285,6 @@ "he": "bin/he" } }, - "node_modules/headers-polyfill": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/headers-polyfill/-/headers-polyfill-4.0.3.tgz", - "integrity": "sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ==" - }, "node_modules/hoist-non-react-statics": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", @@ -10112,11 +9951,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-node-process": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/is-node-process/-/is-node-process-1.2.0.tgz", - "integrity": "sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==" - }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -12364,129 +12198,6 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, - "node_modules/msw": { - "version": "2.6.5", - "resolved": "https://registry.npmjs.org/msw/-/msw-2.6.5.tgz", - "integrity": "sha512-PnlnTpUlOrj441kYQzzFhzMzMCGFT6a2jKUBG7zSpLkYS5oh8Arrbc0dL8/rNAtxaoBy0EVs2mFqj2qdmWK7lQ==", - "hasInstallScript": true, - "dependencies": { - "@bundled-es-modules/cookie": "^2.0.1", - "@bundled-es-modules/statuses": "^1.0.1", - "@bundled-es-modules/tough-cookie": "^0.1.6", - "@inquirer/confirm": "^5.0.0", - "@mswjs/interceptors": "^0.37.0", - "@open-draft/deferred-promise": "^2.2.0", - "@open-draft/until": "^2.1.0", - "@types/cookie": "^0.6.0", - "@types/statuses": "^2.0.4", - "chalk": "^4.1.2", - "graphql": "^16.8.1", - "headers-polyfill": "^4.0.2", - "is-node-process": "^1.2.0", - "outvariant": "^1.4.3", - "path-to-regexp": "^6.3.0", - "strict-event-emitter": "^0.5.1", - "type-fest": "^4.26.1", - "yargs": "^17.7.2" - }, - "bin": { - "msw": "cli/index.js" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/mswjs" - }, - "peerDependencies": { - "typescript": ">= 4.8.x" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/msw/node_modules/@inquirer/confirm": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.0.2.tgz", - "integrity": "sha512-KJLUHOaKnNCYzwVbryj3TNBxyZIrr56fR5N45v6K9IPrbT6B7DcudBMfylkV1A8PUdJE15mybkEQyp2/ZUpxUA==", - "dependencies": { - "@inquirer/core": "^10.1.0", - "@inquirer/type": "^3.0.1" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - } - }, - "node_modules/msw/node_modules/@inquirer/type": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.1.tgz", - "integrity": "sha512-+ksJMIy92sOAiAccGpcKZUc3bYO07cADnscIxHBknEm3uNts3movSmBofc1908BNy5edKscxYeAdaX1NXkHS6A==", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - } - }, - "node_modules/msw/node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/msw/node_modules/path-to-regexp": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", - "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==" - }, - "node_modules/msw/node_modules/type-fest": { - "version": "4.27.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.27.0.tgz", - "integrity": "sha512-3IMSWgP7C5KSQqmo1wjhKrwsvXAtF33jO3QY+Uy++ia7hqvgSK6iXbbg5PbDBc1P2ZbNEDgejOrN4YooXvhwCw==", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/msw/node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/msw/node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "engines": { - "node": ">=12" - } - }, "node_modules/multicast-dns": { "version": "7.2.5", "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", @@ -12499,14 +12210,6 @@ "multicast-dns": "cli.js" } }, - "node_modules/mute-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz", - "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==", - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, "node_modules/mz": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", @@ -12862,11 +12565,6 @@ "node": ">= 0.8.0" } }, - "node_modules/outvariant": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/outvariant/-/outvariant-1.4.3.tgz", - "integrity": "sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==" - }, "node_modules/p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", @@ -16158,11 +15856,6 @@ "node": ">= 0.4" } }, - "node_modules/strict-event-emitter": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.5.1.tgz", - "integrity": "sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==" - }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -18755,17 +18448,6 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } - }, - "node_modules/yoctocolors-cjs": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.2.tgz", - "integrity": "sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } } } } diff --git a/package.json b/package.json index 86a29be..c3a9495 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,6 @@ "@types/styled-components": "^5.1.34", "@vitejs/plugin-react": "^4.3.3", "axios": "^1.7.7", - "msw": "^2.6.5", "react": "^18.3.1", "react-daum-postcode": "^3.1.3", "react-dom": "^18.3.1", @@ -60,10 +59,10 @@ "@types/react-slick": "^0.23.13", "eslint": "^8.57.1", "eslint-config-prettier": "^9.1.0", + "eslint-config-react": "^1.1.7", "eslint-plugin-import": "^2.31.0", "eslint-plugin-jsx-a11y": "^6.10.2", "eslint-plugin-prettier": "^5.2.1", - "eslint-plugin-react": "^7.37.2", "eslint-plugin-react-hooks": "^5.0.0", "globals": "^15.12.0", "husky": "^9.1.7", diff --git a/public/images/googlelogo.png b/public/images/googlelogo.png deleted file mode 100644 index 73d85e0..0000000 Binary files a/public/images/googlelogo.png and /dev/null differ diff --git a/public/images/naverlogo.png b/public/images/naverlogo.png new file mode 100644 index 0000000..842781b Binary files /dev/null and b/public/images/naverlogo.png differ diff --git a/src/api/mypageApi.ts b/src/api/mypageApi.ts index b898a63..9f0f803 100644 --- a/src/api/mypageApi.ts +++ b/src/api/mypageApi.ts @@ -1,5 +1,15 @@ import axiosInstance from './axiosInstance'; +export type OrderType = { + delivery: null | string; + orderDate: string; + paymentStatus: string; + payment_key: null | string; + price: number; + productName: string; + quantity: number; +}; + export const postPasswordVerify = async (body: { currentPassword: string }) => { const response = await axiosInstance.post(`/mypage/verfiy`, body); @@ -17,3 +27,9 @@ export const postLocationChange = async (body: { newAddress: string }) => { return response.data; }; + +export const getOrderList = async () => { + const response = await axiosInstance.get(`/mypage/orders`); + + return response.data; +}; diff --git a/src/assets/icons/default-featured-image.png.jpg b/src/assets/icons/default-featured-image.png.jpg new file mode 100644 index 0000000..7133496 Binary files /dev/null and b/src/assets/icons/default-featured-image.png.jpg differ diff --git a/src/components/common/Header.tsx b/src/components/common/Header.tsx index bf766a7..2473cce 100644 --- a/src/components/common/Header.tsx +++ b/src/components/common/Header.tsx @@ -1,7 +1,7 @@ import React, { useEffect, useState } from 'react'; import { Link } from 'react-router-dom'; import styled from 'styled-components'; -import cart from '../../assets/icons/icon.png'; +// import cart from '../../assets/icons/icon.png'; import logo from '../../assets/icons/goodbuyus-logo.svg'; import menu from '../../assets/icons/menu.svg'; const Header = () => { @@ -9,27 +9,20 @@ const Header = () => { const [isAdmin, setIsAdmin] = useState(false); const [isUser, setIsUser] = useState(false); const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false); - const [isAdminDropdownOpen, setIsAdminDropdownOpen] = useState(false); useEffect(() => { - const token = localStorage.getItem('jwt'); - setIsLoggedIn(!!token); + const token = localStorage.getItem('token'); + const userRole = localStorage.getItem('role'); - // 관리자 여부 확인 (예: localStorage에서 'role'의 ADMIN 여부 확인) - const adminStatus = localStorage.getItem('role') === 'ROLE_ADMIN'; - const userStatus = localStorage.getItem('role') === 'ROLE_USER'; - setIsAdmin(adminStatus); - setIsUser(userStatus); + setIsLoggedIn(!!token); + setIsAdmin(userRole === 'ROLE_ADMIN'); + setIsUser(userRole === 'ROLE_USER'); }, []); const toggleMobileMenu = () => { setIsMobileMenuOpen(!isMobileMenuOpen); }; - const toggleAdminDropdown = () => { - setIsAdminDropdownOpen((prev) => !prev); - }; - const handleCommunityClick = ( e: React.MouseEvent ) => { @@ -77,40 +70,26 @@ const Header = () => { Community - {isAdmin ? ( - - - Admin Page - - {isAdminDropdownOpen && ( - - - - 게시물 관리 - - - - 채팅방 관리 - - - - - )} - - ) : ( - - - My Page - - - )} + + + {isAdmin ? 'Admin Page' : 'My Page'} + + {isAdmin && ( + + + Post Management + + + + Chat Management + + + + )} + {!isLoggedIn ? ( @@ -119,18 +98,22 @@ const Header = () => { ) : ( <> - - - LogOut - + { + localStorage.removeItem('role'); + localStorage.removeItem('token'); + setIsLoggedIn(false); + }} + > + LogOut - + {/* 장바구니 아이콘 - + */} )}{' '} @@ -229,9 +212,50 @@ const NavList = styled.ul` } `; -const NavItem = styled.li` +const SubMenu = styled.ul` + display: none; + position: absolute; + top: 100%; + left: 0; + background: white; + border: 1px solid #ccc; + border-radius: 8px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + list-style: none; + padding: 5px 0; margin: 0; + z-index: 1000; + + @media (min-width: 576px) and (max-width: 767px) { + position: static; + box-shadow: none; + border: none; + padding: 0; + background: transparent; + } +`; + +const SubMenuItem = styled.li` + padding: 8px 12px; + a { + text-decoration: none; + color: black; + display: block; + } + + &:hover { + background: #f4f4f4; + } + + @media (min-width: 576px) and (max-width: 767px) { + padding: 10px; + text-align: center; + } +`; +const NavItem = styled.li` + margin: 0; + position: relative; a { display: block; padding-bottom: 15px; @@ -240,7 +264,9 @@ const NavItem = styled.li` font-weight: bold; border-radius: 5px; } - + &:hover > ${SubMenu} { + display: block; + } &:hover { background-color: #f4f4f4; border-radius: 8px; @@ -249,7 +275,12 @@ const NavItem = styled.li` @media (min-width: 576px) and (max-width: 767px) { width: 100%; text-align: center; - + &:hover > ${SubMenu} { + display: flex; + flex-direction: column; + align-items: center; + gap: 10px; + } a { padding: 10px; width: 100%; @@ -293,13 +324,16 @@ const LogOut = styled.li` height: 30px; border-radius: 5px; overflow: hidden; + display: inline-flex; + align-items: center; + padding: 0px 10px; a { color: white; text-decoration: none; display: block; text-align: center; - margin-top: -1px; + margin-top: 1px; font-weight: bold; } @@ -336,55 +370,16 @@ const StyledLink = styled(Link)` } `; -const CartIcon = styled.div` - img { - width: 20px; - height: 20px; - } - - @media (min-width: 576px) and (max-width: 767px) { - img { - width: 25px; - height: 25px; - } - } -`; - -const AdminMenu = styled.li` - position: relative; - margin-left: 12.5px; - a { - font-weight: bold; - padding: 5px 5px; - border-radius: 5px; - text-decoration: none; - color: black; - } - - &:hover { - background-color: #f4f4f4; - } -`; - -const AdminDropdown = styled.ul` - position: absolute; - top: 100%; - left: 0; - background-color: white; - list-style: none; - margin: 0; - padding: 10px 0; - border-radius: 5px; - transform: translateY(-10%); /* 추가로 위치 조정 */ - z-index: 10; /* 다른 요소 위로 표시 */ -`; - -const DropdownItem = styled.li` - font-size: 0.9rem; - - a { - text-decoration: none; - color: black; - display: block; - } -`; +// const CartIcon = styled.div` +// img { +// width: 20px; +// height: 20px; +// } + +// @media (min-width: 576px) and (max-width: 767px) { +// img { +// width: 25px; +// height: 25px; +// } +// } +// `; diff --git a/src/components/common/Pagination.tsx b/src/components/common/Pagination.tsx index 1c51f82..f2928f6 100644 --- a/src/components/common/Pagination.tsx +++ b/src/components/common/Pagination.tsx @@ -36,7 +36,9 @@ const Container = styled.div` gap: 8px; `; -const Button = styled.button<{ active: boolean }>` +const Button = styled.button.withConfig({ + shouldForwardProp: (prop) => prop !== 'active', +})<{ active: boolean }>` padding: 8px 16px; font-size: 1rem; font-weight: 500; diff --git a/src/components/pages/HomePage/CategoryProduct.tsx b/src/components/pages/HomePage/CategoryProduct.tsx index ac7a7d8..b799e03 100644 --- a/src/components/pages/HomePage/CategoryProduct.tsx +++ b/src/components/pages/HomePage/CategoryProduct.tsx @@ -1,7 +1,7 @@ import React, { useEffect, useMemo, useState } from 'react'; import styled from 'styled-components'; import StarRating from '../../common/StarRating'; - +import DEFAULT_IMG from '../../../assets/icons/default-featured-image.png.jpg'; import { Product } from './model/productSchema'; import { Card, @@ -24,15 +24,18 @@ import { interface CategoryProductsProps { categories: string[]; - products: Product[]; + products: Product[] | undefined; } const CategoryProduct: React.FC = ({ categories, products, }) => { + if (!products) { + return
No products available
; + } const [isExpanded, setIsExpanded] = useState(false); - const [selectedCategory, setSelectedCategory] = useState('생활용품'); + const [selectedCategory, setSelectedCategory] = useState('LIFESTYLE'); const handleToggle = () => setIsExpanded(!isExpanded); const handleCategoryClick = (category: string) => { @@ -79,7 +82,13 @@ const CategoryProduct: React.FC = ({ {displayedProducts.map((product) => ( - + { + e.currentTarget.src = DEFAULT_IMG; + }} + /> {product.name} @@ -87,12 +96,8 @@ const CategoryProduct: React.FC = ({ - - ${product.originalPrice.toFixed(2)} - - - ${product.discountPrice.toFixed(2)} - + {product.originalprice}원 + {product.discountprice}원 diff --git a/src/components/pages/HomePage/HomePage.tsx b/src/components/pages/HomePage/HomePage.tsx index c3ddf7c..418adef 100644 --- a/src/components/pages/HomePage/HomePage.tsx +++ b/src/components/pages/HomePage/HomePage.tsx @@ -3,33 +3,58 @@ import styled from 'styled-components'; import RecommendProduct from './RecommendProduct'; import PopularProduct from './PopularProduct'; import CategoryProduct from './CategoryProduct'; -import { categories, popularProduct, products } from '../../../mocks/products'; -// import { QueryHandler, useProductsQuery } from '../../../hooks/useGetProduct'; +import ScrollToTopButton from '../../common/ScrollToTopButton'; +import { categories } from '../../../mocks/products'; +import { QueryHandler, useProductsQuery } from '../../../hooks/useGetProduct'; +import { Link } from 'react-router-dom'; const HomePage: React.FC = () => { - // const { data: products, isLoading, isError } = useProductsQuery(); + const { data: products, isLoading, isError } = useProductsQuery(); + const popularProduct = products?.sort( + (a, b) => b.currentStock - a.currentStock + )[0]; + return ( <> - {/* */} - - - - - - - - - - - - - - - - {/* */} + + + + + {' '} + + + + + + + + + + + + + + + + ); }; +const StyledLink = styled(Link)` + text-decoration: none; + color: inherit; + width: 100%; + &:link, + &:visited, + &:hover, + &:active { + color: inherit; + text-decoration: none; + } +`; const ContainerBox = styled.div` display: flex; margin-top: 5vh; diff --git a/src/components/pages/HomePage/PopularProduct.tsx b/src/components/pages/HomePage/PopularProduct.tsx index 2b19d77..6934af7 100644 --- a/src/components/pages/HomePage/PopularProduct.tsx +++ b/src/components/pages/HomePage/PopularProduct.tsx @@ -1,54 +1,64 @@ import React from 'react'; import styled from 'styled-components'; -import { Product } from './model/productSchema'; import StarRating from '../../common/StarRating'; -import { Link } from 'react-router-dom'; -// import { Link } from 'react-router-dom'; +import { QueryHandler, useProductQuery } from '../../../hooks/useGetProduct'; +import DEFAULT_IMG from '../../../assets/icons/default-featured-image.png.jpg'; +import { Product } from './model/productSchema'; interface PopularProductProps { - product: Product; + popular: Product | undefined; + category: string | undefined; } +const PopularProduct: React.FC = ({ + popular, + category, +}) => { + if (!popular) { + return
No popular product available
; + } -const PopularProduct: React.FC = ({ product }) => { - return ( - - - - - - - - - - - - {product.name} + const { data: product, isLoading, isError } = useProductQuery(popular.id); - {product.category} - - 상품설명- 추후 추가 예정 - {/* {product.description} */} - - - - + if (!product) { + return
Product data is not available
; + } + return ( + <> + + + + + { + e.currentTarget.src = DEFAULT_IMG; + }} + /> + + + + + + + + {product.name} + {category} + + {product.description} + + + + + + ); }; -const StyledLink = styled(Link)` +const BannerWrapper = styled.div` width: 80%; margin: 0 auto; display: block; - text-decoration: none; - color: inherit; - - &:link, - &:visited, - &:hover, - &:active { - color: inherit; - text-decoration: none; - } `; const BannerContainer = styled.div` diff --git a/src/components/pages/HomePage/RecommendProduct.tsx b/src/components/pages/HomePage/RecommendProduct.tsx index de4acd3..3e35b92 100644 --- a/src/components/pages/HomePage/RecommendProduct.tsx +++ b/src/components/pages/HomePage/RecommendProduct.tsx @@ -18,15 +18,20 @@ import { StyledLink, StyledMoreButton, } from './style/CardStyle'; +import DEFAULT_IMG from '../../../assets/icons/default-featured-image.png.jpg'; + interface PopularProductsListProps { - products: Product[]; + products: Product[] | undefined; } const RecommendProduct: React.FC = ({ products }) => { + if (!products) { + return
No products available
; + } const getTopProducts = (products: Product[]): Product[] => { // comments.length를 기준으로 내림차순 정렬 const sortedByComments = [...products].sort( - (a, b) => b.reviews.length - a.reviews.length + (a, b) => (b.reviews?.length || 0) - (a.reviews?.length || 0) ); // 상위 8개 선택 return sortedByComments.slice(0, 8); @@ -37,6 +42,7 @@ const RecommendProduct: React.FC = ({ products }) => { const changeLike = () => { //찜하기, 찜 취소하기 api }; + return ( 실시간 인기 상품 @@ -45,19 +51,21 @@ const RecommendProduct: React.FC = ({ products }) => { {displayedProducts.map((product) => ( - + { + e.currentTarget.src = DEFAULT_IMG; + }} + /> {product.name} - - ${product.originalPrice.toFixed(2)} - - - ${product.discountPrice.toFixed(2)} - + {product.originalprice}원 + {product.discountprice}원 diff --git a/src/components/pages/HomePage/api/productApi.ts b/src/components/pages/HomePage/api/productApi.ts index b7ca9d6..ace5399 100644 --- a/src/components/pages/HomePage/api/productApi.ts +++ b/src/components/pages/HomePage/api/productApi.ts @@ -3,9 +3,9 @@ import { Product } from '../model/productSchema'; export const getProducts = async (): Promise => { try { - const URL = `/goodbuyUs/true`; - const { data } = await axiosInstance.get(URL); - return data; + const URL = `/goodbuyUs`; + const response = await axiosInstance.get(URL); + return response.data; } catch { throw new Error('상품 정보를 가져오는 데 실패했습니다.'); } diff --git a/src/components/pages/HomePage/model/productSchema.ts b/src/components/pages/HomePage/model/productSchema.ts index 799b1ed..35376d1 100644 --- a/src/components/pages/HomePage/model/productSchema.ts +++ b/src/components/pages/HomePage/model/productSchema.ts @@ -7,15 +7,17 @@ export interface Product { id: number; name: string; rating: number; - originalPrice: number; - discountPrice: number; + originalprice: number; + discountprice: number; url: string; category: string; minamount: number; deadline: string; now: number; currentStock: number; + initstock: number; description: string; reviews: Review[]; likes: boolean; + available: boolean; } diff --git a/src/components/pages/Payment/PaymentFailPage.tsx b/src/components/pages/Payment/PaymentFailPage.tsx new file mode 100644 index 0000000..cb0f8fb --- /dev/null +++ b/src/components/pages/Payment/PaymentFailPage.tsx @@ -0,0 +1,151 @@ +import React from 'react'; +import { Link, useParams } from 'react-router-dom'; +import styled from 'styled-components'; +// import { QueryHandler, useProductQuery } from '../../../hooks/useGetProduct'; +import { products } from '../../../mocks/products'; + +const PaymentFailPage = () => { + const { id } = useParams(); + if (!id) { + return

상품 번호가 유실되었습니다.

; + } + const productId = Number(id); + // const { data: product, isLoading, isError } = useProductQuery(productId); + const product = products.filter((p) => p.id === productId)[0]; + console.log(product); + if (!product) { + return

해당 상품을 찾을 수 없습니다.

; + } + + return ( + <> + {/* */} + + + 결제 실패 + 죄송합니다. 결제에 실패하였습니다. + + + + + {product?.name} + + + + {product?.discountprice} 원 + + + + + + 다시 시도하기 + 메인으로 돌아가기 + + + {/* */} + + ); +}; + +const Container = styled.div` + width: 100%; + max-width: 650px; + margin: 0 auto; + padding: 20px; + background-color: #f8fafc; + display: flex; + flex-direction: column; + align-items: center; +`; + +const FailureSection = styled.div` + width: 100%; + background-color: white; + border-radius: 12px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); + padding: 32px 20px; + text-align: center; + margin-bottom: 24px; +`; + +const Title = styled.h1` + font-size: 1.5rem; + font-weight: 700; + color: #1e293b; + margin-bottom: 12px; +`; + +const Subtitle = styled.p` + color: #64748b; + font-size: 1rem; + margin-bottom: 24px; +`; + +const OrderSummary = styled.div` + background-color: #f8fafc; + border-radius: 8px; + padding: 16px; + margin-top: 20px; +`; + +const SummaryRow = styled.div` + display: flex; + justify-content: space-between; + margin-bottom: 12px; + + &:last-child { + margin-bottom: 0; + } +`; + +const Label = styled.span` + color: #64748b; + font-size: 0.875rem; +`; + +const Value = styled.span` + color: #1e293b; + font-weight: 500; +`; + +const ButtonGroup = styled.div` + display: flex; + gap: 12px; + width: 100%; +`; + +const BaseButton = styled(Link)` + flex: 1; + text-decoration: none; + display: flex; + justify-content: center; + align-items: center; + padding: 12px; + border-radius: 8px; + font-weight: 600; + font-size: 0.875rem; + cursor: pointer; + transition: all 0.2s; +`; + +const RetryButton = styled(BaseButton)` + background-color: #2563eb; + border: none; + color: white; + + &:hover { + background-color: #1d4ed8; + } +`; + +const BackButton = styled(BaseButton)` + background-color: white; + border: 1px solid #e2e8f0; + color: #475569; + + &:hover { + background-color: #f8fafc; + } +`; + +export default PaymentFailPage; diff --git a/src/components/pages/Payment/PaymentForm.tsx b/src/components/pages/Payment/PaymentForm.tsx index ea31bf3..cf52959 100644 --- a/src/components/pages/Payment/PaymentForm.tsx +++ b/src/components/pages/Payment/PaymentForm.tsx @@ -1,45 +1,65 @@ -import React, { useState } from 'react'; -import { - Link, - // useLocation, - useNavigate, - useParams, -} from 'react-router-dom'; +import React, { useMemo, useState } from 'react'; +import { Link, useLocation, useParams } from 'react-router-dom'; import styled from 'styled-components'; import { handlePayment } from './api/paymentApi'; -import { products } from '../../../mocks/products'; -// import { QueryHandler, useProductQuery } from '../../../hooks/useGetProduct'; +import { QueryHandler, useProductQuery } from '../../../hooks/useGetProduct'; const PaymentForm = () => { - //URL 쿼리 스트링을 통한 데이터 수신 - // const location = useLocation(); - // const query = new URLSearchParams(location.search); - // const data = query.get('data') || ''; // null일 경우 빈 문자열 반환 - // const product = JSON.parse(decodeURIComponent(data)); + // URL 쿼리 스트링을 통한 데이터 수신 + const location = useLocation(); + const productData = location.state; + const { id } = useParams(); - if (!id) { - return

상품 번호가 유실되었습니다.

; + const productId = useMemo(() => { + if (!id || isNaN(Number(id))) { + return null; + } + return Number(id); + }, [id]); + + if (!productId) { + return

잘못된 상품 ID입니다.

; } - const productId = Number(id); - // const { data: product, isLoading, isError } = useProductQuery(productId); - const product = products.find((p) => p.id === productId); + const { data: product, isLoading, isError } = useProductQuery(productId); if (!product) { return

해당 상품을 찾을 수 없습니다.

; } - const navigate = useNavigate(); const [userName, setName] = useState(''); const [basicAddress, setBasicAddress] = useState(''); const [detailAddress, setDetailAddress] = useState(''); const [needed, setNeeded] = useState(''); const [payment, setPayment] = useState(''); + + const validateForm = () => { + if (!userName.trim()) { + alert('수령인을 입력해주세요.'); + return false; + } + if (!basicAddress.trim()) { + alert('기본 주소를 입력해주세요.'); + return false; + } + if (!detailAddress.trim()) { + alert('상세 주소를 입력해주세요.'); + return false; + } + if (!needed.trim()) { + alert('배송 시 요청사항을 입력해주세요.'); + return false; + } + if (!payment) { + alert('결제 방법을 선택해주세요.'); + return false; + } + return true; + }; + const payload = { productName: product.name, url: product.url, - price: product.discountPrice, - // price : product.discountPrice * product.amount - quantity: 1, - //quantity : product.amount + price: product.discountprice * productData.amount, + quantity: productData.amount, payMethod: payment, deliveryRequestDTO: { name: userName, @@ -54,122 +74,120 @@ const PaymentForm = () => { setPayment(e.target.value); }; const onPaymentSubmit = async () => { + if (!validateForm()) { + return; + } try { - const paymentResult = await handlePayment(product.id, payload); - if (paymentResult == 'success') { - navigate('/payment-success'); - } + const paymentResult = await handlePayment(productId, payload); + + window.location.href = paymentResult; } catch (e) { alert(`결제에 실패하였습니다 ${e}`); } }; return ( <> - {/* */} - -
- 주문 상품 정보 - - - {product.name} - ₩{product.discountPrice.toLocaleString()} - - - - 수량: 1개 - {/* product.amount */} - - - - 합계: - - ₩{product.discountPrice.toLocaleString()} - {/* *product.amount */} - - - -
- -
- 배송 정보 확인 - - -