diff --git a/README.md b/README.md index 77fa0b13..52cde81a 100644 --- a/README.md +++ b/README.md @@ -14,5 +14,5 @@ - Docs : 내부 문서 추가/수정 - Test : 테스트 추가/수정 - Chore : 빌드 관련 코드 수정 -- Rename : 파일 및 폴더명 수정 +- Rename : 파일 및 폴더명 수정. - Remove : 파일 삭제. diff --git a/index.html b/index.html index 6a7807f4..df4b0b24 100644 --- a/index.html +++ b/index.html @@ -11,7 +11,4 @@
- diff --git a/package-lock.json b/package-lock.json index 8dbc2df4..ee1cecd4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,8 @@ "dependencies": { "@pnpm-monorepo/shared": "^1.0.0", "@radix-ui/react-collapsible": "^1.0.3", + "@radix-ui/react-radio-group": "^1.1.3", + "@radix-ui/react-toggle-group": "^1.0.4", "@svgr/rollup": "^8.1.0", "@tanstack/react-query": "^5.14.6", "@tanstack/react-query-devtools": "^5.14.6", @@ -20,6 +22,7 @@ "react-dom": "^18.2.0", "react-infinite-scroller": "^1.2.6", "react-kakao-maps-sdk": "^1.1.24", + "react-modal": "^3.16.1", "react-router-dom": "^6.21.1", "recoil": "^0.7.7", "styled-components": "^6.1.3", @@ -29,6 +32,7 @@ "@types/react": "^18.2.43", "@types/react-dom": "^18.2.17", "@types/react-infinite-scroller": "^1.2.5", + "@types/react-modal": "^3.16.3", "@types/uuid": "^9.0.7", "@typescript-eslint/eslint-plugin": "^6.14.0", "@typescript-eslint/parser": "^6.14.0", @@ -41,6 +45,7 @@ "prettier": "^3.1.1", "prettier-plugin-tailwindcss": "^0.5.9", "tailwindcss": "^3.4.0", + "terser": "^5.26.0", "typescript": "^5.2.2", "vite": "^5.0.8" } @@ -2484,6 +2489,16 @@ "node": ">=6.0.0" } }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", + "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.4.15", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", @@ -2613,6 +2628,32 @@ } } }, + "node_modules/@radix-ui/react-collection": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.0.3.tgz", + "integrity": "sha512-3SzW+0PW7yBBoQlT8wNcGtaxaD0XSu0uLUFgrtHY08Acx05TaHaOmVLR73c0j/cqpDy53KBMO7s0dx2wmOIDIA==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-context": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-slot": "1.0.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-compose-refs": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.1.tgz", @@ -2647,6 +2688,23 @@ } } }, + "node_modules/@radix-ui/react-direction": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.0.1.tgz", + "integrity": "sha512-RXcvnXgyvYvBEOhCBuddKecVkoMiI10Jcm5cTI7abJRAHYfFxeu+FBQs/DvdxSYucxR5mna0dNsL6QFlds5TMA==", + "dependencies": { + "@babel/runtime": "^7.13.10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-id": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.0.1.tgz", @@ -2712,6 +2770,69 @@ } } }, + "node_modules/@radix-ui/react-radio-group": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-radio-group/-/react-radio-group-1.1.3.tgz", + "integrity": "sha512-x+yELayyefNeKeTx4fjK6j99Fs6c4qKm3aY38G3swQVTN6xMpsrbigC0uHs2L//g8q4qR7qOcww8430jJmi2ag==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/primitive": "1.0.1", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-context": "1.0.1", + "@radix-ui/react-direction": "1.0.1", + "@radix-ui/react-presence": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-roving-focus": "1.0.4", + "@radix-ui/react-use-controllable-state": "1.0.1", + "@radix-ui/react-use-previous": "1.0.1", + "@radix-ui/react-use-size": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-roving-focus": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.0.4.tgz", + "integrity": "sha512-2mUg5Mgcu001VkGy+FfzZyzbmuUWzgWkj3rvv4yu+mLw03+mTzbxZHvfcGyFp2b8EkQeMkpRQ5FiA2Vr2O6TeQ==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/primitive": "1.0.1", + "@radix-ui/react-collection": "1.0.3", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-context": "1.0.1", + "@radix-ui/react-direction": "1.0.1", + "@radix-ui/react-id": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-use-callback-ref": "1.0.1", + "@radix-ui/react-use-controllable-state": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-slot": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.2.tgz", @@ -2730,6 +2851,60 @@ } } }, + "node_modules/@radix-ui/react-toggle": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.0.3.tgz", + "integrity": "sha512-Pkqg3+Bc98ftZGsl60CLANXQBBQ4W3mTFS9EJvNxKMZ7magklKV69/id1mlAlOFDDfHvlCms0fx8fA4CMKDJHg==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/primitive": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-use-controllable-state": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toggle-group": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle-group/-/react-toggle-group-1.0.4.tgz", + "integrity": "sha512-Uaj/M/cMyiyT9Bx6fOZO0SAG4Cls0GptBWiBmBxofmDbNVnYYoyRWj/2M/6VCi/7qcXFWnHhRUfdfZFvvkuu8A==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/primitive": "1.0.1", + "@radix-ui/react-context": "1.0.1", + "@radix-ui/react-direction": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-roving-focus": "1.0.4", + "@radix-ui/react-toggle": "1.0.3", + "@radix-ui/react-use-controllable-state": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-use-callback-ref": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.1.tgz", @@ -2782,6 +2957,41 @@ } } }, + "node_modules/@radix-ui/react-use-previous": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.0.1.tgz", + "integrity": "sha512-cV5La9DPwiQ7S0gf/0qiD6YgNqM5Fk97Kdrlc5yBcrF3jyEZQwm7vYFqMo4IfeHgJXsRaMvLABFtd0OVEmZhDw==", + "dependencies": { + "@babel/runtime": "^7.13.10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-size": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.0.1.tgz", + "integrity": "sha512-ibay+VqrgcaI6veAojjofPATwledXiSmX+C0KrBk/xgpX9rBzPV3OsfwlhQdUOFbh+LKQorLYT+xTXW9V8yd0g==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-use-layout-effect": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@remix-run/router": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.14.1.tgz", @@ -3395,6 +3605,15 @@ "@types/react": "*" } }, + "node_modules/@types/react-modal": { + "version": "3.16.3", + "resolved": "https://registry.npmjs.org/@types/react-modal/-/react-modal-3.16.3.tgz", + "integrity": "sha512-xXuGavyEGaFQDgBv4UVm8/ZsG+qxeQ7f77yNrW3n+1J6XAstUy5rYHeIHPh1KzsGc6IkCIdu6lQ2xWzu1jBTLg==", + "dev": true, + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/scheduler": { "version": "0.16.8", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz", @@ -4085,6 +4304,12 @@ "ieee754": "^1.1.13" } }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -5038,6 +5263,11 @@ "node": ">=0.8.x" } }, + "node_modules/exenv": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/exenv/-/exenv-1.2.2.tgz", + "integrity": "sha512-Z+ktTxTwv9ILfgKCk32OX3n/doe+OcLTRtqK9pcL+JsP3J1/VW8Uvl4ZjLlKqeW4rzK4oesDOGMEMRIZqtP4Iw==" + }, "node_modules/external-editor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", @@ -6913,6 +7143,29 @@ "react-dom": "^16.8 || ^17 || ^18" } }, + "node_modules/react-lifecycles-compat": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", + "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" + }, + "node_modules/react-modal": { + "version": "3.16.1", + "resolved": "https://registry.npmjs.org/react-modal/-/react-modal-3.16.1.tgz", + "integrity": "sha512-VStHgI3BVcGo7OXczvnJN7yT2TWHJPDXZWyI/a0ssFNhGZWsPmB8cF0z33ewDXq4VfYMO1vXgiv/g8Nj9NDyWg==", + "dependencies": { + "exenv": "^1.2.0", + "prop-types": "^15.7.2", + "react-lifecycles-compat": "^3.0.0", + "warning": "^4.0.3" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "react": "^0.14.0 || ^15.0.0 || ^16 || ^17 || ^18", + "react-dom": "^0.14.0 || ^15.0.0 || ^16 || ^17 || ^18" + } + }, "node_modules/react-refresh": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz", @@ -7299,6 +7552,15 @@ "tslib": "^2.0.3" } }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-js": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", @@ -7307,6 +7569,16 @@ "node": ">=0.10.0" } }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -7605,6 +7877,30 @@ "node": ">=14.0.0" } }, + "node_modules/terser": { + "version": "5.26.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.26.0.tgz", + "integrity": "sha512-dytTGoE2oHgbNV9nTzgBEPaqAWvcJNl66VZ0BkJqlvp71IjO8CxdBx/ykCNb47cLnCmCvRZ6ZR0tLkqvZCdVBQ==", + "dev": true, + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -7895,6 +8191,14 @@ } } }, + "node_modules/warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, "node_modules/wcwidth": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", @@ -9643,6 +9947,16 @@ "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==" }, + "@jridgewell/source-map": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", + "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", + "dev": true, + "requires": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, "@jridgewell/sourcemap-codec": { "version": "1.4.15", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", @@ -9746,6 +10060,18 @@ "@radix-ui/react-use-layout-effect": "1.0.1" } }, + "@radix-ui/react-collection": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.0.3.tgz", + "integrity": "sha512-3SzW+0PW7yBBoQlT8wNcGtaxaD0XSu0uLUFgrtHY08Acx05TaHaOmVLR73c0j/cqpDy53KBMO7s0dx2wmOIDIA==", + "requires": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-context": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-slot": "1.0.2" + } + }, "@radix-ui/react-compose-refs": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.1.tgz", @@ -9762,6 +10088,14 @@ "@babel/runtime": "^7.13.10" } }, + "@radix-ui/react-direction": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.0.1.tgz", + "integrity": "sha512-RXcvnXgyvYvBEOhCBuddKecVkoMiI10Jcm5cTI7abJRAHYfFxeu+FBQs/DvdxSYucxR5mna0dNsL6QFlds5TMA==", + "requires": { + "@babel/runtime": "^7.13.10" + } + }, "@radix-ui/react-id": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.0.1.tgz", @@ -9790,6 +10124,41 @@ "@radix-ui/react-slot": "1.0.2" } }, + "@radix-ui/react-radio-group": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-radio-group/-/react-radio-group-1.1.3.tgz", + "integrity": "sha512-x+yELayyefNeKeTx4fjK6j99Fs6c4qKm3aY38G3swQVTN6xMpsrbigC0uHs2L//g8q4qR7qOcww8430jJmi2ag==", + "requires": { + "@babel/runtime": "^7.13.10", + "@radix-ui/primitive": "1.0.1", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-context": "1.0.1", + "@radix-ui/react-direction": "1.0.1", + "@radix-ui/react-presence": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-roving-focus": "1.0.4", + "@radix-ui/react-use-controllable-state": "1.0.1", + "@radix-ui/react-use-previous": "1.0.1", + "@radix-ui/react-use-size": "1.0.1" + } + }, + "@radix-ui/react-roving-focus": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.0.4.tgz", + "integrity": "sha512-2mUg5Mgcu001VkGy+FfzZyzbmuUWzgWkj3rvv4yu+mLw03+mTzbxZHvfcGyFp2b8EkQeMkpRQ5FiA2Vr2O6TeQ==", + "requires": { + "@babel/runtime": "^7.13.10", + "@radix-ui/primitive": "1.0.1", + "@radix-ui/react-collection": "1.0.3", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-context": "1.0.1", + "@radix-ui/react-direction": "1.0.1", + "@radix-ui/react-id": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-use-callback-ref": "1.0.1", + "@radix-ui/react-use-controllable-state": "1.0.1" + } + }, "@radix-ui/react-slot": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.2.tgz", @@ -9799,6 +10168,32 @@ "@radix-ui/react-compose-refs": "1.0.1" } }, + "@radix-ui/react-toggle": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.0.3.tgz", + "integrity": "sha512-Pkqg3+Bc98ftZGsl60CLANXQBBQ4W3mTFS9EJvNxKMZ7magklKV69/id1mlAlOFDDfHvlCms0fx8fA4CMKDJHg==", + "requires": { + "@babel/runtime": "^7.13.10", + "@radix-ui/primitive": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-use-controllable-state": "1.0.1" + } + }, + "@radix-ui/react-toggle-group": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle-group/-/react-toggle-group-1.0.4.tgz", + "integrity": "sha512-Uaj/M/cMyiyT9Bx6fOZO0SAG4Cls0GptBWiBmBxofmDbNVnYYoyRWj/2M/6VCi/7qcXFWnHhRUfdfZFvvkuu8A==", + "requires": { + "@babel/runtime": "^7.13.10", + "@radix-ui/primitive": "1.0.1", + "@radix-ui/react-context": "1.0.1", + "@radix-ui/react-direction": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-roving-focus": "1.0.4", + "@radix-ui/react-toggle": "1.0.3", + "@radix-ui/react-use-controllable-state": "1.0.1" + } + }, "@radix-ui/react-use-callback-ref": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.1.tgz", @@ -9824,6 +10219,23 @@ "@babel/runtime": "^7.13.10" } }, + "@radix-ui/react-use-previous": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.0.1.tgz", + "integrity": "sha512-cV5La9DPwiQ7S0gf/0qiD6YgNqM5Fk97Kdrlc5yBcrF3jyEZQwm7vYFqMo4IfeHgJXsRaMvLABFtd0OVEmZhDw==", + "requires": { + "@babel/runtime": "^7.13.10" + } + }, + "@radix-ui/react-use-size": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.0.1.tgz", + "integrity": "sha512-ibay+VqrgcaI6veAojjofPATwledXiSmX+C0KrBk/xgpX9rBzPV3OsfwlhQdUOFbh+LKQorLYT+xTXW9V8yd0g==", + "requires": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-use-layout-effect": "1.0.1" + } + }, "@remix-run/router": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.14.1.tgz", @@ -10196,6 +10608,15 @@ "@types/react": "*" } }, + "@types/react-modal": { + "version": "3.16.3", + "resolved": "https://registry.npmjs.org/@types/react-modal/-/react-modal-3.16.3.tgz", + "integrity": "sha512-xXuGavyEGaFQDgBv4UVm8/ZsG+qxeQ7f77yNrW3n+1J6XAstUy5rYHeIHPh1KzsGc6IkCIdu6lQ2xWzu1jBTLg==", + "dev": true, + "requires": { + "@types/react": "*" + } + }, "@types/scheduler": { "version": "0.16.8", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz", @@ -10647,6 +11068,12 @@ "ieee754": "^1.1.13" } }, + "buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, "callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -11319,6 +11746,11 @@ "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==" }, + "exenv": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/exenv/-/exenv-1.2.2.tgz", + "integrity": "sha512-Z+ktTxTwv9ILfgKCk32OX3n/doe+OcLTRtqK9pcL+JsP3J1/VW8Uvl4ZjLlKqeW4rzK4oesDOGMEMRIZqtP4Iw==" + }, "external-editor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", @@ -12581,6 +13013,22 @@ "kakao.maps.d.ts": "^0.1.39" } }, + "react-lifecycles-compat": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", + "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" + }, + "react-modal": { + "version": "3.16.1", + "resolved": "https://registry.npmjs.org/react-modal/-/react-modal-3.16.1.tgz", + "integrity": "sha512-VStHgI3BVcGo7OXczvnJN7yT2TWHJPDXZWyI/a0ssFNhGZWsPmB8cF0z33ewDXq4VfYMO1vXgiv/g8Nj9NDyWg==", + "requires": { + "exenv": "^1.2.0", + "prop-types": "^15.7.2", + "react-lifecycles-compat": "^3.0.0", + "warning": "^4.0.3" + } + }, "react-refresh": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz", @@ -12849,11 +13297,27 @@ "tslib": "^2.0.3" } }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, "source-map-js": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==" }, + "source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, "statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -13060,6 +13524,26 @@ "sucrase": "^3.32.0" } }, + "terser": { + "version": "5.26.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.26.0.tgz", + "integrity": "sha512-dytTGoE2oHgbNV9nTzgBEPaqAWvcJNl66VZ0BkJqlvp71IjO8CxdBx/ykCNb47cLnCmCvRZ6ZR0tLkqvZCdVBQ==", + "dev": true, + "requires": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "dependencies": { + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + } + } + }, "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -13237,6 +13721,14 @@ "rollup": "^4.2.0" } }, + "warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "requires": { + "loose-envify": "^1.0.0" + } + }, "wcwidth": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", diff --git a/package.json b/package.json index 8439277e..d2479fde 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,8 @@ "dependencies": { "@pnpm-monorepo/shared": "^1.0.0", "@radix-ui/react-collapsible": "^1.0.3", + "@radix-ui/react-radio-group": "^1.1.3", + "@radix-ui/react-toggle-group": "^1.0.4", "@svgr/rollup": "^8.1.0", "@tanstack/react-query": "^5.14.6", "@tanstack/react-query-devtools": "^5.14.6", @@ -22,6 +24,7 @@ "react-dom": "^18.2.0", "react-infinite-scroller": "^1.2.6", "react-kakao-maps-sdk": "^1.1.24", + "react-modal": "^3.16.1", "react-router-dom": "^6.21.1", "recoil": "^0.7.7", "styled-components": "^6.1.3", @@ -32,6 +35,7 @@ "@types/react-dom": "^18.2.17", "@types/react-infinite-scroller": "^1.2.5", "@types/uuid": "^9.0.7", + "@types/react-modal": "^3.16.3", "@typescript-eslint/eslint-plugin": "^6.14.0", "@typescript-eslint/parser": "^6.14.0", "@vitejs/plugin-react": "^4.2.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3163368e..091dfc27 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,6 +11,12 @@ dependencies: '@radix-ui/react-collapsible': specifier: ^1.0.3 version: 1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-radio-group': + specifier: ^1.1.3 + version: 1.1.3(@types/react-dom@18.2.18)(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-toggle-group': + specifier: ^1.0.4 + version: 1.0.4(@types/react-dom@18.2.18)(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0) '@svgr/rollup': specifier: ^8.1.0 version: 8.1.0(typescript@5.3.3) @@ -41,6 +47,9 @@ dependencies: react-kakao-maps-sdk: specifier: ^1.1.24 version: 1.1.24(react-dom@18.2.0)(react@18.2.0) + react-modal: + specifier: ^3.16.1 + version: 3.16.1(react-dom@18.2.0)(react@18.2.0) react-router-dom: specifier: ^6.21.1 version: 6.21.1(react-dom@18.2.0)(react@18.2.0) @@ -64,6 +73,9 @@ devDependencies: '@types/react-infinite-scroller': specifier: ^1.2.5 version: 1.2.5 + '@types/react-modal': + specifier: ^3.16.3 + version: 3.16.3 '@types/uuid': specifier: ^9.0.7 version: 9.0.7 @@ -1822,6 +1834,30 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /@radix-ui/react-collection@1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-3SzW+0PW7yBBoQlT8wNcGtaxaD0XSu0uLUFgrtHY08Acx05TaHaOmVLR73c0j/cqpDy53KBMO7s0dx2wmOIDIA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@babel/runtime': 7.23.6 + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.45)(react@18.2.0) + '@radix-ui/react-context': 1.0.1(@types/react@18.2.45)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-slot': 1.0.2(@types/react@18.2.45)(react@18.2.0) + '@types/react': 18.2.45 + '@types/react-dom': 18.2.18 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /@radix-ui/react-compose-refs@1.0.1(@types/react@18.2.45)(react@18.2.0): resolution: {integrity: sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw==} peerDependencies: @@ -1850,6 +1886,20 @@ packages: react: 18.2.0 dev: false + /@radix-ui/react-direction@1.0.1(@types/react@18.2.45)(react@18.2.0): + resolution: {integrity: sha512-RXcvnXgyvYvBEOhCBuddKecVkoMiI10Jcm5cTI7abJRAHYfFxeu+FBQs/DvdxSYucxR5mna0dNsL6QFlds5TMA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.23.6 + '@types/react': 18.2.45 + react: 18.2.0 + dev: false + /@radix-ui/react-id@1.0.1(@types/react@18.2.45)(react@18.2.0): resolution: {integrity: sha512-tI7sT/kqYp8p96yGWY1OAnLHrqDgzHefRBKQ2YAkBS5ja7QLcZ9Z/uY7bEjPUatf8RomoXM8/1sMj1IJaE5UzQ==} peerDependencies: @@ -1908,6 +1958,65 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /@radix-ui/react-radio-group@1.1.3(@types/react-dom@18.2.18)(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-x+yELayyefNeKeTx4fjK6j99Fs6c4qKm3aY38G3swQVTN6xMpsrbigC0uHs2L//g8q4qR7qOcww8430jJmi2ag==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@babel/runtime': 7.23.6 + '@radix-ui/primitive': 1.0.1 + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.45)(react@18.2.0) + '@radix-ui/react-context': 1.0.1(@types/react@18.2.45)(react@18.2.0) + '@radix-ui/react-direction': 1.0.1(@types/react@18.2.45)(react@18.2.0) + '@radix-ui/react-presence': 1.0.1(@types/react-dom@18.2.18)(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-roving-focus': 1.0.4(@types/react-dom@18.2.18)(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.45)(react@18.2.0) + '@radix-ui/react-use-previous': 1.0.1(@types/react@18.2.45)(react@18.2.0) + '@radix-ui/react-use-size': 1.0.1(@types/react@18.2.45)(react@18.2.0) + '@types/react': 18.2.45 + '@types/react-dom': 18.2.18 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + + /@radix-ui/react-roving-focus@1.0.4(@types/react-dom@18.2.18)(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-2mUg5Mgcu001VkGy+FfzZyzbmuUWzgWkj3rvv4yu+mLw03+mTzbxZHvfcGyFp2b8EkQeMkpRQ5FiA2Vr2O6TeQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@babel/runtime': 7.23.6 + '@radix-ui/primitive': 1.0.1 + '@radix-ui/react-collection': 1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.45)(react@18.2.0) + '@radix-ui/react-context': 1.0.1(@types/react@18.2.45)(react@18.2.0) + '@radix-ui/react-direction': 1.0.1(@types/react@18.2.45)(react@18.2.0) + '@radix-ui/react-id': 1.0.1(@types/react@18.2.45)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.45)(react@18.2.0) + '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.45)(react@18.2.0) + '@types/react': 18.2.45 + '@types/react-dom': 18.2.18 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /@radix-ui/react-slot@1.0.2(@types/react@18.2.45)(react@18.2.0): resolution: {integrity: sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==} peerDependencies: @@ -1923,6 +2032,56 @@ packages: react: 18.2.0 dev: false + /@radix-ui/react-toggle-group@1.0.4(@types/react-dom@18.2.18)(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-Uaj/M/cMyiyT9Bx6fOZO0SAG4Cls0GptBWiBmBxofmDbNVnYYoyRWj/2M/6VCi/7qcXFWnHhRUfdfZFvvkuu8A==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@babel/runtime': 7.23.6 + '@radix-ui/primitive': 1.0.1 + '@radix-ui/react-context': 1.0.1(@types/react@18.2.45)(react@18.2.0) + '@radix-ui/react-direction': 1.0.1(@types/react@18.2.45)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-roving-focus': 1.0.4(@types/react-dom@18.2.18)(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-toggle': 1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.45)(react@18.2.0) + '@types/react': 18.2.45 + '@types/react-dom': 18.2.18 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + + /@radix-ui/react-toggle@1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-Pkqg3+Bc98ftZGsl60CLANXQBBQ4W3mTFS9EJvNxKMZ7magklKV69/id1mlAlOFDDfHvlCms0fx8fA4CMKDJHg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@babel/runtime': 7.23.6 + '@radix-ui/primitive': 1.0.1 + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.45)(react@18.2.0) + '@types/react': 18.2.45 + '@types/react-dom': 18.2.18 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /@radix-ui/react-use-callback-ref@1.0.1(@types/react@18.2.45)(react@18.2.0): resolution: {integrity: sha512-D94LjX4Sp0xJFVaoQOd3OO9k7tpBYNOXdVhkltUbGv2Qb9OXdrg/CpsjlZv7ia14Sylv398LswWBVVu5nqKzAQ==} peerDependencies: @@ -1966,6 +2125,35 @@ packages: react: 18.2.0 dev: false + /@radix-ui/react-use-previous@1.0.1(@types/react@18.2.45)(react@18.2.0): + resolution: {integrity: sha512-cV5La9DPwiQ7S0gf/0qiD6YgNqM5Fk97Kdrlc5yBcrF3jyEZQwm7vYFqMo4IfeHgJXsRaMvLABFtd0OVEmZhDw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.23.6 + '@types/react': 18.2.45 + react: 18.2.0 + dev: false + + /@radix-ui/react-use-size@1.0.1(@types/react@18.2.45)(react@18.2.0): + resolution: {integrity: sha512-ibay+VqrgcaI6veAojjofPATwledXiSmX+C0KrBk/xgpX9rBzPV3OsfwlhQdUOFbh+LKQorLYT+xTXW9V8yd0g==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.23.6 + '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.2.45)(react@18.2.0) + '@types/react': 18.2.45 + react: 18.2.0 + dev: false + /@remix-run/router@1.14.1: resolution: {integrity: sha512-Qg4DMQsfPNAs88rb2xkdk03N3bjK4jgX5fR24eHCTR9q6PrhZQZ4UJBPzCHJkIpTRN1UKxx2DzjZmnC+7Lj0Ow==} engines: {node: '>=14.0.0'} @@ -2353,6 +2541,12 @@ packages: '@types/react': 18.2.45 dev: true + /@types/react-modal@3.16.3: + resolution: {integrity: sha512-xXuGavyEGaFQDgBv4UVm8/ZsG+qxeQ7f77yNrW3n+1J6XAstUy5rYHeIHPh1KzsGc6IkCIdu6lQ2xWzu1jBTLg==} + dependencies: + '@types/react': 18.2.45 + dev: true + /@types/react@18.2.45: resolution: {integrity: sha512-TtAxCNrlrBp8GoeEp1npd5g+d/OejJHFxS3OWmrPBMFaVQMSN0OFySozJio5BHxTuTeug00AVXVAjfDSfk+lUg==} dependencies: @@ -3265,6 +3459,10 @@ packages: engines: {node: '>=0.8.x'} dev: false + /exenv@1.2.2: + resolution: {integrity: sha512-Z+ktTxTwv9ILfgKCk32OX3n/doe+OcLTRtqK9pcL+JsP3J1/VW8Uvl4ZjLlKqeW4rzK4oesDOGMEMRIZqtP4Iw==} + dev: false + /external-editor@3.1.0: resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} engines: {node: '>=4'} @@ -4249,6 +4447,25 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /react-lifecycles-compat@3.0.4: + resolution: {integrity: sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==} + dev: false + + /react-modal@3.16.1(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-VStHgI3BVcGo7OXczvnJN7yT2TWHJPDXZWyI/a0ssFNhGZWsPmB8cF0z33ewDXq4VfYMO1vXgiv/g8Nj9NDyWg==} + engines: {node: '>=8'} + peerDependencies: + react: ^0.14.0 || ^15.0.0 || ^16 || ^17 || ^18 + react-dom: ^0.14.0 || ^15.0.0 || ^16 || ^17 || ^18 + dependencies: + exenv: 1.2.2 + prop-types: 15.8.1 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + react-lifecycles-compat: 3.0.4 + warning: 4.0.3 + dev: false + /react-refresh@0.14.0: resolution: {integrity: sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==} engines: {node: '>=0.10.0'} @@ -4869,6 +5086,12 @@ packages: fsevents: 2.3.3 dev: true + /warning@4.0.3: + resolution: {integrity: sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==} + dependencies: + loose-envify: 1.4.0 + dev: false + /wcwidth@1.0.1: resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} dependencies: diff --git a/src/@types/tours.types.ts b/src/@types/tours.types.ts index f3789a88..c819462e 100644 --- a/src/@types/tours.types.ts +++ b/src/@types/tours.types.ts @@ -32,4 +32,7 @@ export interface TourType { ratingAverage: number; reviewCount: number; smallThumbnailUrl: string; + tourAddress?: string; + longitude?: string; + latitude?: string; } diff --git a/src/api/client.ts b/src/api/client.ts index 930438d1..4e7b5bb2 100644 --- a/src/api/client.ts +++ b/src/api/client.ts @@ -1,9 +1,16 @@ import axios from 'axios'; +let accessToken; +if (window.localStorage.getItem('accessToken')) { + accessToken = window.localStorage.getItem('accessToken'); +} + +// axios 인스턴스를 생성합니다. const client = axios.create({ baseURL: import.meta.env.VITE_SERVER_URL, headers: { 'Content-Type': 'application/json', + ...(accessToken ? { Authorization: `Bearer ${accessToken}` } : {}), }, withCredentials: true, }); diff --git a/src/api/comments.ts b/src/api/comments.ts index 16540bc0..72114584 100644 --- a/src/api/comments.ts +++ b/src/api/comments.ts @@ -3,7 +3,7 @@ import client from './client'; // 댓글 관련 API // 댓글수정 -export const putComments = async (content: string, commentId: string) => { +export const putComments = async (content: string, commentId: number) => { const res = await client.put(`comments/${commentId}`, { content, }); @@ -11,14 +11,14 @@ export const putComments = async (content: string, commentId: string) => { }; // 댓글삭제 -export const deleteComments = async (commentId: string) => { +export const deleteComments = async (commentId: number) => { const res = await client.delete(`comments/${commentId}`); return res; }; // 댓글작성 export const postComments = async (content: string, reviewId: number) => { - const res = await client.post(`comments/`, { + const res = await client.post(`comments`, { content: content, reviewId: reviewId, }); diff --git a/src/api/member.ts b/src/api/member.ts index b4c6b4ae..5bcd784c 100644 --- a/src/api/member.ts +++ b/src/api/member.ts @@ -39,7 +39,7 @@ export const getMemberReviews = async () => { }; // 나의 관심 여행지 삭제 -export const deleteMemberTours = async (tourId: number) => { - const res = await client.delete(`member/tours/${tourId}`); +export const deleteMemberTours = async (tourItemId: number) => { + const res = await client.delete(`member/tours/${tourItemId}`); return res; }; diff --git a/src/api/review.ts b/src/api/review.ts index a914f451..be1721d1 100644 --- a/src/api/review.ts +++ b/src/api/review.ts @@ -31,7 +31,7 @@ export const getReviewComments = async (reviewId: number) => { }; // 리뷰키워드조회 -export const getReviewKeywords = async (code: number) => { - const res = await client.get(`reviews/keywords?code=${code}`); +export const getReviewKeywords = async (keywordType: string) => { + const res = await client.get(`reviews/keywords?keywordType=${keywordType}`); return res; }; diff --git a/src/api/tours.ts b/src/api/tours.ts index a7baee6c..87ed8604 100644 --- a/src/api/tours.ts +++ b/src/api/tours.ts @@ -35,21 +35,33 @@ export const getDetailTours = async (tourItemId: number) => { // 여행 상품 리뷰 조회 export const getToursReviews = async (tourItemId: number) => { const res = await client.get(`tours/${tourItemId}/reviews`); - console.log('res', res); - console.log('res.data.data.reviewInfos', res.data.data.reviewInfos); return res; }; // 여행지 검색 -export const getToursSearch = async ( - region: number, - category: number, - searchWord: string, - page: number, - size: number, -) => { - const res = await client.get( - `tours/search?region=${region}&category=${category}&searchWord=${searchWord}&page=${page}&size=${size}}`, - ); +export const getToursSearch = async (options: { + region?: string; + searchWord: string; + category?: string; + page?: number; + size?: number; +}) => { + const { region, searchWord, category, page = 0, size } = options; + + let query = `tours/search?searchWord=${searchWord}`; + + if (region) { + query += `®ion=${region}`; + } + + if (category) { + query += `&category=${category}`; + } + query += `&page=${page}`; + + if (size) { + query += `&size=${size}`; + } + const res = await client.get(query); return res; }; diff --git a/src/assets/images/Calendar.svg b/src/assets/images/Calendar.svg deleted file mode 100644 index 6e8a7da8..00000000 --- a/src/assets/images/Calendar.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/src/assets/images/Check.svg b/src/assets/images/Check.svg deleted file mode 100644 index 1343696b..00000000 --- a/src/assets/images/Check.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/src/assets/images/DeleteInput.svg b/src/assets/images/DeleteInput.svg new file mode 100644 index 00000000..b1020d4f --- /dev/null +++ b/src/assets/images/DeleteInput.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/images/Heart.svg b/src/assets/images/Heart.svg deleted file mode 100644 index 52d4d386..00000000 --- a/src/assets/images/Heart.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/src/assets/images/Left.svg b/src/assets/images/Left.svg deleted file mode 100644 index 36ba334f..00000000 --- a/src/assets/images/Left.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/src/assets/images/Map.svg b/src/assets/images/Map.svg deleted file mode 100644 index bd149bac..00000000 --- a/src/assets/images/Map.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/src/assets/images/Pen.svg b/src/assets/images/Pen.svg deleted file mode 100644 index 257e6bac..00000000 --- a/src/assets/images/Pen.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/src/assets/images/Phone.svg b/src/assets/images/Phone.svg deleted file mode 100644 index 123ea8fb..00000000 --- a/src/assets/images/Phone.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/src/assets/images/Search.svg b/src/assets/images/Search.svg new file mode 100644 index 00000000..d8e513bb --- /dev/null +++ b/src/assets/images/Search.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/assets/images/Star.svg b/src/assets/images/Star.svg deleted file mode 100644 index 5c27ea56..00000000 --- a/src/assets/images/Star.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - \ No newline at end of file diff --git a/src/components/DetailSectionBottom/DetailReview.tsx b/src/components/DetailSectionBottom/DetailReview.tsx deleted file mode 100644 index b68f9377..00000000 --- a/src/components/DetailSectionBottom/DetailReview.tsx +++ /dev/null @@ -1,85 +0,0 @@ -// import { getToursReviews } from '@api/tours'; -// import { useEffect, useState } from 'react'; -// import InfiniteScroll from 'react-infinite-scroller'; -// import { useInfiniteQuery } from '@tanstack/react-query'; -// import ReviewItem from './ReviewItem'; -// import { StarIcon } from '@components/common/icons/Icons'; - -// export default function DetailReview() { -// const [reviewDataLength, setReviewDataLength] = useState(0); -// const tourItemId = 1; - -// const { -// data: toursReviews, -// fetchNextPage, -// hasNextPage, -// } = useInfiniteQuery({ -// queryKey: ['toursReviews'], -// queryFn: ({ pageParam }) => getToursReviews(tourItemId), -// initialPageParam: 0, -// getNextPageParam: (lastPage, allPages, lastPageParam) => { -// const lastData = lastPage?.data?.data?.reviewInfos; -// return lastData && lastData.length === 4 ? lastPageParam + 1 : undefined; -// }, -// }); - -// useEffect(() => { -// if (toursReviews) { -// const totalCount = toursReviews.pages.reduce( -// (accumulator, page) => -// accumulator + (page?.data?.data?.reviewInfos?.length || 0), -// 0, -// ); -// setReviewDataLength(totalCount); -// } -// }, [toursReviews]); - -// return ( -// <> -//
-// 리뷰 {reviewDataLength} -//
-// {reviewDataLength > 0 && ( -// fetchNextPage()} -// initialLoad={false}> -// {toursReviews?.pages?.map((page, pageIndex) => ( -//
-// {page?.data?.data?.reviewInfos?.map( -// (item: any, index: number) => ( -// -// ), -// )} -//
-// ))} -//
-// )} -// {reviewDataLength == 0 && ( -//
-//
-// {Array.from({ length: 5 }, (_, index) => ( -// -// ))} -//
-//
첫번째 리뷰를 남겨주세요!
-//
-// )} -// -// ); -// } diff --git a/src/components/DetailSectionBottom/DetailReviews.tsx b/src/components/DetailSectionBottom/DetailReviews.tsx new file mode 100644 index 00000000..87893173 --- /dev/null +++ b/src/components/DetailSectionBottom/DetailReviews.tsx @@ -0,0 +1,116 @@ +import { getToursReviews } from '@api/tours'; +import { useEffect, useState } from 'react'; +// import InfiniteScroll from 'react-infinite-scroller'; +import { useQuery } from '@tanstack/react-query'; +import ReviewItem from './ReviewItem'; +import { StarIcon } from '@components/common/icons/Icons'; +import { useNavigate, useParams } from 'react-router-dom'; +import { useSetRecoilState, useRecoilState } from 'recoil'; +import { isModalOpenState, titleState } from '@recoil/modal'; +import { + ratingState, + keywordsState, + contentState, + targetReviewIdState, + tourItemIdState, + contentTypeIdState, + isModifyingReviewState, +} from '@recoil/review'; +import { Modal } from '@components/common/modal'; + +interface reviewProps { + reviewData: any; +} + +export default function DetailReviews({ reviewData }: reviewProps) { + const [reviewDataLength, setReviewDataLength] = useState(0); + const { title, contentTypeId } = reviewData; + const params = useParams(); + const tourItemId = Number(params.id); + const navigate = useNavigate(); + const setRating = useSetRecoilState(ratingState); + const setKeywords = useSetRecoilState(keywordsState); + const setContent = useSetRecoilState(contentState); + const setTitle = useSetRecoilState(titleState); + const setTourItemId = useSetRecoilState(tourItemIdState); + const setContentTypeId = useSetRecoilState(contentTypeIdState); + const setTargetReviewId = useSetRecoilState(targetReviewIdState); + const setIsModifyingReview = useSetRecoilState(isModifyingReviewState); + + const { data: toursReviews } = useQuery({ + queryKey: ['toursReviews'], + queryFn: () => getToursReviews(tourItemId), + }); + + const handleReviewClick = (item: any) => { + const reviewId = item.reviewId; + navigate(`/reviewComment/${reviewId}`, { state: { item, tourItemId } }); + }; + + const handlePostingReivew = () => { + navigate(`/reviewPosting/${tourItemId}`, { + state: { title, contentTypeId }, + }); + }; + + const [isModalOpen, setIsModalOpen] = useRecoilState(isModalOpenState); + + const closeModal = () => { + setTitle(''); + setTourItemId(0); + if (contentTypeId) { + setContentTypeId(0); + } + setRating(0); + setKeywords([]); + setContent(''); + setTargetReviewId(0); + setIsModifyingReview(false); + setIsModalOpen(false); + }; + + useEffect(() => { + setReviewDataLength(toursReviews?.data?.data?.reviewTotalCount); + }, [toursReviews]); + + return ( + <> +
+ 리뷰{reviewDataLength} +
+ {reviewDataLength > 0 && ( +
+ {toursReviews?.data?.data?.reviewInfos?.content?.map((item: any) => ( + handleReviewClick(item)} + tourItemId={tourItemId} + contentTypeId={contentTypeId} + /> + ))} +
+ )} + {reviewDataLength == 0 && ( +
+
+ {Array.from({ length: 5 }, (_, index) => ( + + ))} +
+
첫번째 리뷰를 남겨주세요!
+
+ )} + + + ); +} diff --git a/src/components/DetailSectionBottom/DetailSectionBottom.tsx b/src/components/DetailSectionBottom/DetailSectionBottom.tsx index 9455ba17..354bf93f 100644 --- a/src/components/DetailSectionBottom/DetailSectionBottom.tsx +++ b/src/components/DetailSectionBottom/DetailSectionBottom.tsx @@ -1,11 +1,23 @@ -import { DetailReviewStats } from '.'; +import { DetailReviews, DetailReviewStats } from '.'; +import { useParams } from 'react-router-dom'; +import { useQuery } from '@tanstack/react-query'; +import { getDetailTours } from '@api/tours'; -// 담당 컴포넌트들 호출하는 컴포넌트(분업 때문에 페이지 느낌으로 나눠봤습니다), API 호출 컴포넌트, export default function DetailSectionBottom() { - return ( - <> - - {/* */} - - ); + const params = useParams(); + const tourItemId = Number(params.id); + const { isError, data } = useQuery({ + queryKey: ['details', tourItemId], + queryFn: () => getDetailTours(tourItemId), + }); + if (data) { + return ( + <> + + + + ); + } + + if (isError) console.log('error'); } diff --git a/src/components/DetailSectionBottom/ReviewItem.tsx b/src/components/DetailSectionBottom/ReviewItem.tsx index ae09f613..157f284d 100644 --- a/src/components/DetailSectionBottom/ReviewItem.tsx +++ b/src/components/DetailSectionBottom/ReviewItem.tsx @@ -1,5 +1,14 @@ -import { useEffect } from 'react'; -import { StarIcon, ChatIcon } from '@components/common/icons/Icons'; +import { StarIcon, ChatIcon, MoreIcon } from '@components/common/icons/Icons'; +import { useSetRecoilState, useRecoilState } from 'recoil'; +import { isModalOpenState, titleState } from '@recoil/modal'; +import { + ratingState, + keywordsState, + contentState, + targetReviewIdState, + tourItemIdState, + contentTypeIdState, +} from '@recoil/review'; interface Keyword { keywordId: number; @@ -8,6 +17,7 @@ interface Keyword { } interface ItemProps { + reviewId: number; authorNickname: string; authorProfileImageUrl: string; rating: number; @@ -15,10 +25,14 @@ interface ItemProps { content: string; keywords: Keyword[]; // keywordId, content, type commentCount: number; + onClick?: () => void; + tourItemId: number; + contentTypeId?: number; } const Item: React.FC = (props: ItemProps) => { const { + reviewId, authorNickname, // authorProfileImageUrl, rating, @@ -26,7 +40,32 @@ const Item: React.FC = (props: ItemProps) => { content, keywords, commentCount, + onClick, + tourItemId, + contentTypeId, } = props; + const [_, setIsModalOpen] = useRecoilState(isModalOpenState); + + const setRating = useSetRecoilState(ratingState); + const setKeywords = useSetRecoilState(keywordsState); + const setContent = useSetRecoilState(contentState); + const setTitle = useSetRecoilState(titleState); + const setTourItemId = useSetRecoilState(tourItemIdState); + const setContentTypeId = useSetRecoilState(contentTypeIdState); + const setTargetReviewId = useSetRecoilState(targetReviewIdState); + const openModal = (title: string, reviewId: number, e: React.MouseEvent) => { + e.stopPropagation(); + setTitle(title); + setTourItemId(tourItemId); + if (contentTypeId) { + setContentTypeId(contentTypeId); + } + setRating(rating); + setKeywords(keywords); + setContent(content); + setTargetReviewId(reviewId); + setIsModalOpen(true); + }; const formatCreatedTime = (timeString: string): string => { const date = new Date(timeString); @@ -38,58 +77,63 @@ const Item: React.FC = (props: ItemProps) => { return formattedDate; }; - useEffect(() => { - console.log('commentCount', commentCount); - }, []); + return ( -
-
- {/* {authorProfileImageUrl} */} -
- 유저 프로필 -
-
-
{authorNickname}
-
- {Array.from({ length: 5 }, (_, index) => ( - - ))} + <> +
+
+ {/* {authorProfileImageUrl} */} +
+ 유저 프로필 +
+
+
{authorNickname}
+
+ {Array.from({ length: 5 }, (_, index) => ( + + ))} +
+
+
+ {formatCreatedTime(createdTime)} +
+
openModal('내 리뷰', reviewId, e)}> +
-
- {formatCreatedTime(createdTime)} -
-
-
{content}
-
+
{content}
- {keywords.map((keyword, idx) => { - return ( -
- {keyword.content} -
- ); - })} -
-
- -
{commentCount}
+
+ {keywords.map((keyword, idx) => { + return ( +
+ {keyword.content} +
+ ); + })} +
+
+ +
{commentCount}
+
-
+ ); }; diff --git a/src/components/DetailSectionBottom/index.tsx b/src/components/DetailSectionBottom/index.tsx index d1ca1b41..6513d306 100644 --- a/src/components/DetailSectionBottom/index.tsx +++ b/src/components/DetailSectionBottom/index.tsx @@ -1,3 +1,4 @@ import DetailReviewStats from './DetailReviewStats'; +import DetailReviews from './DetailReviews'; -export { DetailReviewStats }; +export { DetailReviews, DetailReviewStats }; diff --git a/src/components/DetailSectionTop/DetailSectionTop.tsx b/src/components/DetailSectionTop/DetailSectionTop.tsx index 34eb2cbf..d4845979 100644 --- a/src/components/DetailSectionTop/DetailSectionTop.tsx +++ b/src/components/DetailSectionTop/DetailSectionTop.tsx @@ -13,26 +13,26 @@ import { export default function DetailSectionTop() { const params = useParams(); - const tourId = Number(params.id); + const tourItemId = Number(params.id); const detailQuery = useQuery({ - queryKey: ['details', tourId], - queryFn: () => getDetailTours(tourId), + queryKey: ['details', tourItemId], + queryFn: () => getDetailTours(tourItemId), }); const reviewQuery = useQuery({ - queryKey: ['reviews', tourId], - queryFn: () => getToursReviews(tourId), + queryKey: ['reviews', tourItemId], + queryFn: () => getToursReviews(tourItemId), }); if (detailQuery.error || reviewQuery.error) console.log('error - 예외 처리'); return detailQuery.data && reviewQuery.data?.data.data ? ( -
+
- +
) : null; } diff --git a/src/components/DetailSectionTop/DetailToursButtons.tsx b/src/components/DetailSectionTop/DetailToursButtons.tsx index 05e79202..bc13a706 100644 --- a/src/components/DetailSectionTop/DetailToursButtons.tsx +++ b/src/components/DetailSectionTop/DetailToursButtons.tsx @@ -1,7 +1,27 @@ -import { ReactComponent as PenIcon } from '@assets/images/Pen.svg'; -import { ReactComponent as CalendarIcon } from '@assets/images/Calendar.svg'; +import { PenIcon, CalendarIcon } from '@components/common/icons/Icons'; +import { useNavigate, useParams } from 'react-router-dom'; +import { useEffect } from 'react'; -export default function DetailTourButtons() { +interface reviewProps { + reviewData: any; +} + +export default function DetailTourButtons({ reviewData }: reviewProps) { + const { title, contentTypeId } = reviewData; + const params = useParams(); + const tourItemId = Number(params.id); + const navigate = useNavigate(); + + const handlePostingReivew = () => { + navigate(`/reviewPosting/${tourItemId}`, { + state: { title, contentTypeId }, + }); + }; + + useEffect(() => { + console.log('contentTypeId', contentTypeId); + }, [contentTypeId]); + return (
); diff --git a/src/components/DetailSectionTop/DetailToursInfo.tsx b/src/components/DetailSectionTop/DetailToursInfo.tsx index 4c906c1a..b48a5343 100644 --- a/src/components/DetailSectionTop/DetailToursInfo.tsx +++ b/src/components/DetailSectionTop/DetailToursInfo.tsx @@ -1,4 +1,4 @@ -import { ReactComponent as HeartIcon } from '@assets/images/Heart.svg'; +import { HeartIcon } from '@components/common/icons/Icons'; interface DetailToursInfoProps { infoData: tourDetail; @@ -17,14 +17,16 @@ export default function DetailToursInfo({ infoData }: DetailToursInfoProps) { />
-

{title}

+

+ {title} +

{liked ? (
- +
) : (
- +
)}
diff --git a/src/components/DetailSectionTop/DetailToursMap.tsx b/src/components/DetailSectionTop/DetailToursMap.tsx index 90719757..2c1de11f 100644 --- a/src/components/DetailSectionTop/DetailToursMap.tsx +++ b/src/components/DetailSectionTop/DetailToursMap.tsx @@ -1,9 +1,9 @@ -import { ReactComponent as MapIcon } from '@assets/images/Map.svg'; -import { ReactComponent as CheckIcon } from '@assets/images/Check.svg'; -import { ReactComponent as PhoneIcon } from '@assets/images/Phone.svg'; - -import { Map, MapMarker } from 'react-kakao-maps-sdk'; import { useState } from 'react'; +import { Map, MapMarker } from 'react-kakao-maps-sdk'; +import { useKakaoLoader } from 'react-kakao-maps-sdk'; +import { PhoneIcon, MapIcon, CheckIcon } from '@components/common/icons/Icons'; + +const VITE_KAKAO_MAP_API_KEY = import.meta.env.VITE_KAKAO_MAP_API_KEY; interface DetailToursMapProps { mapData: tourDetail; @@ -12,6 +12,11 @@ interface DetailToursMapProps { export default function DetailToursMap({ mapData }: DetailToursMapProps) { const { fullAddress, longitude, latitude, tel } = mapData; const [isMapVisible, setIsMapVisible] = useState(false); + const [isAddressVisible, setIsAddressVisible] = useState(false); + + const [_] = useKakaoLoader({ + appkey: VITE_KAKAO_MAP_API_KEY, + }); const MapStyle = { width: '100%', @@ -27,15 +32,27 @@ export default function DetailToursMap({ mapData }: DetailToursMapProps) { return (
-
-
- - {fullAddress} +
+
setIsAddressVisible(!isAddressVisible)}> + + {!isAddressVisible ? ( +

+ {fullAddress} +

+ ) : ( +

+ {fullAddress} +

+ )}
- + +
-
- - {tel} +
+ +
+

+ {tel ? tel : '전화번호가 없어요'} +

+
+
+
+
); diff --git a/src/components/Review/CommentItem.tsx b/src/components/Review/CommentItem.tsx new file mode 100644 index 00000000..43f43686 --- /dev/null +++ b/src/components/Review/CommentItem.tsx @@ -0,0 +1,123 @@ +import { MoreIcon } from '@components/common/icons/Icons'; +import { useSetRecoilState, useRecoilState } from 'recoil'; +import { isModalOpenState, titleState } from '@recoil/modal'; +import { + commentState, + targetCommentIdState, + isModifyingCommentState, +} from '@recoil/review'; +import { ChangeEvent } from 'react'; +import { putComments } from '@api/comments'; + +interface ItemProps { + commentId: number; + authorNickname: string; + authorProfileImageUrl: string; + createdTime: any; + content: string; + onClick?: () => void; +} + +const CommentItem: React.FC = (props: ItemProps) => { + const { + commentId, + authorNickname, + // authorProfileImageUrl, + createdTime, + content, + // onClick, + } = props; + const [_, setIsModalOpen] = useRecoilState(isModalOpenState); + const setTitle = useSetRecoilState(titleState); + const [targetCommentId, setTargetCommentId] = + useRecoilState(targetCommentIdState); + const [comment, setComment] = useRecoilState(commentState); + const [isModifyingComment, setIsModifyingComment] = useRecoilState( + isModifyingCommentState, + ); + + const openModal = (title: string, commentId: number) => { + setTitle(title); + setTargetCommentId(commentId); + setComment(content); + setIsModalOpen(true); + }; + + const formatCreatedTime = (timeString: string): string => { + const date = new Date(timeString); + const formattedDate = new Intl.DateTimeFormat('ko-KR', { + year: 'numeric', + month: '2-digit', + day: '2-digit', + }).format(date); + + return formattedDate; + }; + + const handleTextChange = (event: ChangeEvent) => { + const inputText = event.target.value; + setComment(inputText); + }; + + const cancleCommentEdit = () => { + setIsModifyingComment(false); + }; + const handeCommentEdit = () => { + putComments(comment, targetCommentId); + setIsModifyingComment(false); + }; + + return ( +
+
+ {/* {authorProfileImageUrl} */} +
+ 유저 프로필 +
+
+
{authorNickname}
+
+ {formatCreatedTime(createdTime)} +
+
+
openModal('내 댓글', commentId)}> + +
+
+ {isModifyingComment && commentId == targetCommentId && ( +
+ +
+ + +
+
+ )} + {!isModifyingComment && ( +
{content}
+ )} +
+ ); +}; + +export default CommentItem; diff --git a/src/components/Review/DetailReview.tsx b/src/components/Review/DetailReview.tsx new file mode 100644 index 00000000..f4c32b90 --- /dev/null +++ b/src/components/Review/DetailReview.tsx @@ -0,0 +1,26 @@ +import ReviewItem from '@components/DetailSectionBottom/ReviewItem'; + +import { useLocation } from 'react-router-dom'; + +export default function DetailReview() { + const location = useLocation(); + const { state } = location; + const { item, tourItemId } = state; + + return ( +
+ +
+ ); +} diff --git a/src/components/Review/InputComment.tsx b/src/components/Review/InputComment.tsx new file mode 100644 index 00000000..f53a9762 --- /dev/null +++ b/src/components/Review/InputComment.tsx @@ -0,0 +1,51 @@ +import { KeyboardEvent, ChangeEvent, useState } from 'react'; +import { postComments } from '@api/comments'; +import { useParams } from 'react-router-dom'; + +interface InputCommentProps { + classNameName?: string; +} + +export const InputComment: React.FC = () => { + const [comment, setComment] = useState(''); + const params = useParams(); + const reviewId = Number(params.id); + + const handleTextChange = (event: ChangeEvent) => { + const inputText = event.target.value; + setComment(inputText); + }; + + const handleSubmit = () => { + postComments(comment, reviewId); + setComment(''); + }; + const handleKeyPress = (event: KeyboardEvent) => { + if (event.key === 'Enter') { + handleSubmit(); + } + }; + return ( + <> +
+
+
+ +
+
+ +
+
+ + ); +}; diff --git a/src/components/Review/Review.tsx b/src/components/Review/Review.tsx index 6114bb50..e8f4c3f7 100644 --- a/src/components/Review/Review.tsx +++ b/src/components/Review/Review.tsx @@ -2,14 +2,60 @@ import ReviewButton from './ReviewButton'; import ReviewKeyword from './ReviewKeyword'; import ReviewPosting from './ReviewPosting'; import ReviewRating from './ReviewRating'; +import { postReview, putReview } from '@api/review'; +import { + ratingState, + keywordsState, + contentState, + isModifyingReviewState, + targetReviewIdState, +} from '@recoil/review'; +import { isModalOpenState } from '@recoil/modal'; +import { useRecoilState, useRecoilValue } from 'recoil'; +import { useParams, useNavigate } from 'react-router-dom'; export default function Review() { + const params = useParams(); + const navigate = useNavigate(); + const tourItemId = Number(params.id); + const [rating] = useRecoilState(ratingState); + const [keywords] = useRecoilState(keywordsState); + const [content] = useRecoilState(contentState); + const isModifyingReview = useRecoilValue(isModifyingReviewState); + const targetReviewId = useRecoilValue(targetReviewIdState); + const [_, setIsModalOpen] = useRecoilState(isModalOpenState); + + const handlePostReview = async () => { + try { + const reviewData = { + tourItemId: tourItemId, + rating: rating, + keywords: keywords, + content: content, + }; + if (isModifyingReview) { + const response = await putReview(reviewData, targetReviewId); + console.log('리뷰가 성공적으로 수정되었습니다.', response.data); + } else { + const response = await postReview(reviewData); + console.log('리뷰가 성공적으로 등록되었습니다.', response.data); + } + setIsModalOpen(false); + navigate(`/detail/${tourItemId}`); + } catch (error) { + console.error( + `리뷰 ${isModifyingReview ? '수정' : '등록'} 중 오류 발생:`, + error, + ); + } + }; + return ( <> - + ); } diff --git a/src/components/Review/ReviewButton.tsx b/src/components/Review/ReviewButton.tsx index eaf9c5a4..3b9cde51 100644 --- a/src/components/Review/ReviewButton.tsx +++ b/src/components/Review/ReviewButton.tsx @@ -1,5 +1,50 @@ import { ButtonPrimary } from '@components/common/button/Button'; +import { useState, useEffect } from 'react'; +import { contentState, keywordsState } from '@recoil/review'; +import { useRecoilState, useRecoilValue } from 'recoil'; -export default function ReviewButton() { - return {}}>완료; +interface ButtonProps { + onClick: () => void; } + +const ReviewButton = (props: ButtonProps) => { + const { onClick } = props; + const [content] = useRecoilState(contentState); + const keywords = useRecoilValue(keywordsState); + const [isContentValid, setIsContentValid] = useState(false); + const [isKeywordsValid, setIsKeywordsValid] = useState(false); + + useEffect(() => { + if (content.length >= 10) { + setIsContentValid(true); + } else if (content.length < 10) { + setIsContentValid(false); + } + }, [content]); + + useEffect(() => { + if (keywords.length > 0) { + setIsKeywordsValid(true); + } else if (keywords.length <= 0) { + setIsKeywordsValid(false); + } + }, [keywords]); + + return ( + <> + {isContentValid === false && isKeywordsValid === false && ( +
+ 키워드를 선택하거나 텍스트를 10자 이상 입력해주세요 +
+ )} + + + 완료 + + + ); +}; + +export default ReviewButton; diff --git a/src/components/Review/ReviewComments.tsx b/src/components/Review/ReviewComments.tsx new file mode 100644 index 00000000..de67591e --- /dev/null +++ b/src/components/Review/ReviewComments.tsx @@ -0,0 +1,55 @@ +import { getReviewComments } from '@api/review'; +import { useQuery } from '@tanstack/react-query'; +import { useParams } from 'react-router-dom'; +import CommentItem from './CommentItem'; +import { useRecoilState } from 'recoil'; +import { isModalOpenState } from '@recoil/modal'; +import { Modal } from '@components/common/modal'; +// import { targetCommentIdState } from '@recoil/review'; + +export default function ReviewComments() { + const params = useParams(); + const reviewId = Number(params.id); + const [isModalOpen, setIsModalOpen] = useRecoilState(isModalOpenState); + // const setTitle = useSetRecoilState(titleState); + // const setTargetCommentId = useSetRecoilState(targetCommentIdState); + + const { data: reviewComments } = useQuery({ + queryKey: ['reviewComments'], + queryFn: () => getReviewComments(reviewId), + }); + + const closeModal = () => { + setIsModalOpen(false); + }; + + return ( + <> +
+ 댓글 + + {reviewComments?.data?.data?.comments?.totalElements} + +
+ {reviewComments?.data?.data?.comments?.totalElements == 0 && ( +
+
댓글이 없습니다.
+
첫 댓글을 작성해보세요!
+
+ )} + {reviewComments?.data?.data?.comments?.content?.map((item: any) => { + return ( + + ); + })} + + + ); +} diff --git a/src/components/Review/ReviewKeyword.tsx b/src/components/Review/ReviewKeyword.tsx index 79bb0f5e..49197347 100644 --- a/src/components/Review/ReviewKeyword.tsx +++ b/src/components/Review/ReviewKeyword.tsx @@ -1,47 +1,88 @@ +import { useEffect, useState } from 'react'; +import { useLocation } from 'react-router-dom'; +import { getReviewKeywords } from '@api/review'; +import { useQuery } from '@tanstack/react-query'; +import { useRecoilState } from 'recoil'; +import { keywordsState } from '@recoil/review'; + +interface Keyword { + keywordId: number; + content: string; +} + export default function ReviewKeyword() { + const location = useLocation(); + const { state } = location; + const { contentTypeId } = state; + const [keywordType, setKeywordType] = useState(''); + const [selectedKeywords, setSelectedKeywords] = useRecoilState(keywordsState); + + useEffect(() => { + if (contentTypeId === 12) { + setKeywordType('ATTRACTION '); + } else if (contentTypeId === 32) { + setKeywordType('ACCOMMODATION'); + } else if (contentTypeId === 39) { + setKeywordType('DINING '); + } + }, [contentTypeId]); + + const { data: reviewKeywords } = useQuery({ + queryKey: ['reviewKeywords', keywordType], + queryFn: () => getReviewKeywords(keywordType), + }); + + const handleKeywordClick = (keyword: Keyword) => { + const isAlreadySelected = selectedKeywords.some( + (selectedKeyword) => selectedKeyword.keywordId === keyword.keywordId, + ); + if (isAlreadySelected) { + setSelectedKeywords((prevSelectedKeywords) => + prevSelectedKeywords.filter( + (selectedKeyword) => selectedKeyword.keywordId !== keyword.keywordId, + ), + ); + } else { + setSelectedKeywords((prevSelectedKeywords) => [ + ...prevSelectedKeywords, + { keywordId: keyword.keywordId, content: keyword.content }, + ]); + } + }; + + // 5x2 형태로 배치하기 위해 행(row)과 열(column)을 계산 + // const rows = 5; + const columns = 2; + return ( -
-
어떤 점이 좋았나요?
-
-
-
- 깨끗해요 -
-
- 친절해요 -
-
- 뷰가 좋아요 -
-
-
-
- 침구가 좋아요 -
-
- 주차하기 편해요 -
-
- 냉난방이 잘돼요 -
-
-
-
- 대중교통이 편해요 -
-
- 호캉스하기 좋아요 -
-
-
-
- 조식이 맛있어요 -
-
- 사진 찍기 좋아요 -
-
+ <> +
어떤 점이 좋았나요?
+
+ {reviewKeywords?.data?.data?.keywords?.map( + (keyword: any, index: number) => { + const row = Math.floor(index / columns) + 1; + const col = (index % columns) + 1; + + const isSelected = selectedKeywords.some( + (selectedKeyword) => + selectedKeyword.keywordId === keyword.keywordId, + ); + + return ( + + ); + }, + )}
-
+ ); } diff --git a/src/components/Review/ReviewPosting.tsx b/src/components/Review/ReviewPosting.tsx index e0a37d4a..5f2ef5fb 100644 --- a/src/components/Review/ReviewPosting.tsx +++ b/src/components/Review/ReviewPosting.tsx @@ -1,25 +1,28 @@ -import { useState, ChangeEvent } from 'react'; +import { ChangeEvent } from 'react'; +import { contentState } from '@recoil/review'; +import { useRecoilState } from 'recoil'; export default function ReviewPosting() { - const [textLength, setTextLength] = useState(0); + const [content, setContent] = useRecoilState(contentState); const handleTextChange = (event: ChangeEvent) => { const inputText = event.target.value; - setTextLength(inputText.length); + setContent(inputText); }; return (
-
리뷰를 작성해주세요
+
리뷰를 작성해주세요