diff --git a/README.md b/README.md index 0459dab4..3f498e9b 100644 --- a/README.md +++ b/README.md @@ -1,30 +1,47 @@ -# React + TypeScript + Vit +# 중간 결과물 서면 피드백 8조 FE +## API 명세 +[스웨거](https://app.swaggerhub.com/apis-docs/strong-potato/trip-vote_api/1.0.0) -This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. +## 다자인 +[피그마](https://www.figma.com/file/ypTLv92s72sihUApnxjP5C/%EA%B0%95%EC%9E%90%EB%B0%AD-%ED%94%BC%EA%B7%B8%EB%A7%88?type=design&node-id=40-3&mode=design&t=ApKRWjdA112o0y1T-0) -Currently, two official plugins are available: +## 역할 +### 박성후 -- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh -- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh +- 알림 +- 친구 초대 -## Expanding the ESLint configuration +--- -If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: +### 은영 & 서현 -- Configure the top-level `parserOptions` property like this: +- 투표 만들기 + - 투표만들기, 투표 만들기 완료페이지 +- 투표하기 + - 투표하기, 투표 페이지에서 일정 추가, 지도보기 기능 -```js -export default { - // other rules... - parserOptions: { - ecmaVersion: 'latest', - sourceType: 'module', - project: ['./tsconfig.json', './tsconfig.node.json'], - tsconfigRootDir: __dirname, - }, -} -``` +- 여행지 후보 등록 +- 장소 검색, 후보 장소 메모작성, 나의 찜 가져오기, 전체 지역 필터 기능 -- Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked` -- Optionally add `plugin:@typescript-eslint/stylistic-type-checked` -- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list +- 여행 스페이스 만들기 + - 여행 날짜 정하기, 여행지 정하기, 완료 페이지 +- 일정 관리 + - 일정 유무에 따라 보여지는 일정 탭 화면, 지도 확대 기능, 일정 편집 기능, 투표에서 불러오기, 루트 최적화, 장소 검색, 찜목록 검색 + +--- + +### 정민 & 상원 + +- gnb +- 홈화면 +- 검색 +- 상세페이지 + - 후보장소 등록 기능, 투표만들기 기능, 상품 검색 기능, 찜하기 + +--- + +### 남궁 & 종민 + +- 리뷰 +- 마이페이지 +- 인증 diff --git a/package-lock.json b/package-lock.json index ea63e1cf..ae51477a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,11 +15,15 @@ "axios": "^1.6.2", "date-fns": "^3.1.0", "dotenv": "^16.3.1", + "firebase": "^9.23.0", "framer-motion": "^10.16.16", + "immutability-helper": "^3.1.1", "jwt-decode": "^4.0.0", "react": "^18.2.0", "react-cookie": "^7.0.1", "react-datepicker": "^4.25.0", + "react-dnd": "^16.0.1", + "react-dnd-html5-backend": "^16.0.1", "react-dom": "^18.2.0", "react-hook-form": "^7.49.2", "react-icons": "^4.12.0", @@ -27,6 +31,7 @@ "react-mobile-datepicker": "^4.0.2", "react-router-dom": "^6.21.1", "recoil": "^0.7.7", + "recoil-persist": "^5.1.0", "swiper": "^11.0.5" }, "devDependencies": { @@ -2524,6 +2529,620 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@firebase/analytics": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.10.0.tgz", + "integrity": "sha512-Locv8gAqx0e+GX/0SI3dzmBY5e9kjVDtD+3zCFLJ0tH2hJwuCAiL+5WkHuxKj92rqQj/rvkBUCfA1ewlX2hehg==", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/installations": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/analytics-compat": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/@firebase/analytics-compat/-/analytics-compat-0.2.6.tgz", + "integrity": "sha512-4MqpVLFkGK7NJf/5wPEEP7ePBJatwYpyjgJ+wQHQGHfzaCDgntOnl9rL2vbVGGKCnRqWtZDIWhctB86UWXaX2Q==", + "dependencies": { + "@firebase/analytics": "0.10.0", + "@firebase/analytics-types": "0.8.0", + "@firebase/component": "0.6.4", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/analytics-types": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@firebase/analytics-types/-/analytics-types-0.8.0.tgz", + "integrity": "sha512-iRP+QKI2+oz3UAh4nPEq14CsEjrjD6a5+fuypjScisAh9kXKFvdJOZJDwk7kikLvWVLGEs9+kIUS4LPQV7VZVw==" + }, + "node_modules/@firebase/app": { + "version": "0.9.13", + "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.9.13.tgz", + "integrity": "sha512-GfiI1JxJ7ecluEmDjPzseRXk/PX31hS7+tjgBopL7XjB2hLUdR+0FTMXy2Q3/hXezypDvU6or7gVFizDESrkXw==", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "idb": "7.1.1", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/app-check": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@firebase/app-check/-/app-check-0.8.0.tgz", + "integrity": "sha512-dRDnhkcaC2FspMiRK/Vbp+PfsOAEP6ZElGm9iGFJ9fDqHoPs0HOPn7dwpJ51lCFi1+2/7n5pRPGhqF/F03I97g==", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/app-check-compat": { + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/@firebase/app-check-compat/-/app-check-compat-0.3.7.tgz", + "integrity": "sha512-cW682AxsyP1G+Z0/P7pO/WT2CzYlNxoNe5QejVarW2o5ZxeWSSPAiVEwpEpQR/bUlUmdeWThYTMvBWaopdBsqw==", + "dependencies": { + "@firebase/app-check": "0.8.0", + "@firebase/app-check-types": "0.5.0", + "@firebase/component": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/app-check-interop-types": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@firebase/app-check-interop-types/-/app-check-interop-types-0.3.0.tgz", + "integrity": "sha512-xAxHPZPIgFXnI+vb4sbBjZcde7ZluzPPaSK7Lx3/nmuVk4TjZvnL8ONnkd4ERQKL8WePQySU+pRcWkh8rDf5Sg==" + }, + "node_modules/@firebase/app-check-types": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@firebase/app-check-types/-/app-check-types-0.5.0.tgz", + "integrity": "sha512-uwSUj32Mlubybw7tedRzR24RP8M8JUVR3NPiMk3/Z4bCmgEKTlQBwMXrehDAZ2wF+TsBq0SN1c6ema71U/JPyQ==" + }, + "node_modules/@firebase/app-compat": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.2.13.tgz", + "integrity": "sha512-j6ANZaWjeVy5zg6X7uiqh6lM6o3n3LD1+/SJFNs9V781xyryyZWXe+tmnWNWPkP086QfJoNkWN9pMQRqSG4vMg==", + "dependencies": { + "@firebase/app": "0.9.13", + "@firebase/component": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/app-types": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.0.tgz", + "integrity": "sha512-AeweANOIo0Mb8GiYm3xhTEBVCmPwTYAu9Hcd2qSkLuga/6+j9b1Jskl5bpiSQWy9eJ/j5pavxj6eYogmnuzm+Q==" + }, + "node_modules/@firebase/auth": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-0.23.2.tgz", + "integrity": "sha512-dM9iJ0R6tI1JczuGSxXmQbXAgtYie0K4WvKcuyuSTCu9V8eEDiz4tfa1sO3txsfvwg7nOY3AjoCyMYEdqZ8hdg==", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "node-fetch": "2.6.7", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/auth-compat": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@firebase/auth-compat/-/auth-compat-0.4.2.tgz", + "integrity": "sha512-Q30e77DWXFmXEt5dg5JbqEDpjw9y3/PcP9LslDPR7fARmAOTIY9MM6HXzm9KC+dlrKH/+p6l8g9ifJiam9mc4A==", + "dependencies": { + "@firebase/auth": "0.23.2", + "@firebase/auth-types": "0.12.0", + "@firebase/component": "0.6.4", + "@firebase/util": "1.9.3", + "node-fetch": "2.6.7", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/auth-interop-types": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.2.1.tgz", + "integrity": "sha512-VOaGzKp65MY6P5FI84TfYKBXEPi6LmOCSMMzys6o2BN2LOsqy7pCuZCup7NYnfbk5OkkQKzvIfHOzTm0UDpkyg==" + }, + "node_modules/@firebase/auth-types": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@firebase/auth-types/-/auth-types-0.12.0.tgz", + "integrity": "sha512-pPwaZt+SPOshK8xNoiQlK5XIrS97kFYc3Rc7xmy373QsOJ9MmqXxLaYssP5Kcds4wd2qK//amx/c+A8O2fVeZA==", + "peerDependencies": { + "@firebase/app-types": "0.x", + "@firebase/util": "1.x" + } + }, + "node_modules/@firebase/component": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.6.4.tgz", + "integrity": "sha512-rLMyrXuO9jcAUCaQXCMjCMUsWrba5fzHlNK24xz5j2W6A/SRmK8mZJ/hn7V0fViLbxC0lPMtrK1eYzk6Fg03jA==", + "dependencies": { + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/database": { + "version": "0.14.4", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.14.4.tgz", + "integrity": "sha512-+Ea/IKGwh42jwdjCyzTmeZeLM3oy1h0mFPsTy6OqCWzcu/KFqRAr5Tt1HRCOBlNOdbh84JPZC47WLU18n2VbxQ==", + "dependencies": { + "@firebase/auth-interop-types": "0.2.1", + "@firebase/component": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "faye-websocket": "0.11.4", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/database-compat": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-0.3.4.tgz", + "integrity": "sha512-kuAW+l+sLMUKBThnvxvUZ+Q1ZrF/vFJ58iUY9kAcbX48U03nVzIF6Tmkf0p3WVQwMqiXguSgtOPIB6ZCeF+5Gg==", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/database": "0.14.4", + "@firebase/database-types": "0.10.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/database-types": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.10.4.tgz", + "integrity": "sha512-dPySn0vJ/89ZeBac70T+2tWWPiJXWbmRygYv0smT5TfE3hDrQ09eKMF3Y+vMlTdrMWq7mUdYW5REWPSGH4kAZQ==", + "dependencies": { + "@firebase/app-types": "0.9.0", + "@firebase/util": "1.9.3" + } + }, + "node_modules/@firebase/firestore": { + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-3.13.0.tgz", + "integrity": "sha512-NwcnU+madJXQ4fbLkGx1bWvL612IJN/qO6bZ6dlPmyf7QRyu5azUosijdAN675r+bOOJxMtP1Bv981bHBXAbUg==", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "@firebase/webchannel-wrapper": "0.10.1", + "@grpc/grpc-js": "~1.7.0", + "@grpc/proto-loader": "^0.6.13", + "node-fetch": "2.6.7", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=10.10.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/firestore-compat": { + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/@firebase/firestore-compat/-/firestore-compat-0.3.12.tgz", + "integrity": "sha512-mazuNGAx5Kt9Nph0pm6ULJFp/+j7GSsx+Ncw1GrnKl+ft1CQ4q2LcUssXnjqkX2Ry0fNGqUzC1mfIUrk9bYtjQ==", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/firestore": "3.13.0", + "@firebase/firestore-types": "2.5.1", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/firestore-types": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@firebase/firestore-types/-/firestore-types-2.5.1.tgz", + "integrity": "sha512-xG0CA6EMfYo8YeUxC8FeDzf6W3FX1cLlcAGBYV6Cku12sZRI81oWcu61RSKM66K6kUENP+78Qm8mvroBcm1whw==", + "peerDependencies": { + "@firebase/app-types": "0.x", + "@firebase/util": "1.x" + } + }, + "node_modules/@firebase/functions": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@firebase/functions/-/functions-0.10.0.tgz", + "integrity": "sha512-2U+fMNxTYhtwSpkkR6WbBcuNMOVaI7MaH3cZ6UAeNfj7AgEwHwMIFLPpC13YNZhno219F0lfxzTAA0N62ndWzA==", + "dependencies": { + "@firebase/app-check-interop-types": "0.3.0", + "@firebase/auth-interop-types": "0.2.1", + "@firebase/component": "0.6.4", + "@firebase/messaging-interop-types": "0.2.0", + "@firebase/util": "1.9.3", + "node-fetch": "2.6.7", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/functions-compat": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@firebase/functions-compat/-/functions-compat-0.3.5.tgz", + "integrity": "sha512-uD4jwgwVqdWf6uc3NRKF8cSZ0JwGqSlyhPgackyUPe+GAtnERpS4+Vr66g0b3Gge0ezG4iyHo/EXW/Hjx7QhHw==", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/functions": "0.10.0", + "@firebase/functions-types": "0.6.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/functions-types": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@firebase/functions-types/-/functions-types-0.6.0.tgz", + "integrity": "sha512-hfEw5VJtgWXIRf92ImLkgENqpL6IWpYaXVYiRkFY1jJ9+6tIhWM7IzzwbevwIIud/jaxKVdRzD7QBWfPmkwCYw==" + }, + "node_modules/@firebase/installations": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/@firebase/installations/-/installations-0.6.4.tgz", + "integrity": "sha512-u5y88rtsp7NYkCHC3ElbFBrPtieUybZluXyzl7+4BsIz4sqb4vSAuwHEUgCgCeaQhvsnxDEU6icly8U9zsJigA==", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/util": "1.9.3", + "idb": "7.0.1", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/installations-compat": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@firebase/installations-compat/-/installations-compat-0.2.4.tgz", + "integrity": "sha512-LI9dYjp0aT9Njkn9U4JRrDqQ6KXeAmFbRC0E7jI7+hxl5YmRWysq5qgQl22hcWpTk+cm3es66d/apoDU/A9n6Q==", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/installations": "0.6.4", + "@firebase/installations-types": "0.5.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/installations-types": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@firebase/installations-types/-/installations-types-0.5.0.tgz", + "integrity": "sha512-9DP+RGfzoI2jH7gY4SlzqvZ+hr7gYzPODrbzVD82Y12kScZ6ZpRg/i3j6rleto8vTFC8n6Len4560FnV1w2IRg==", + "peerDependencies": { + "@firebase/app-types": "0.x" + } + }, + "node_modules/@firebase/installations/node_modules/idb": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/idb/-/idb-7.0.1.tgz", + "integrity": "sha512-UUxlE7vGWK5RfB/fDwEGgRf84DY/ieqNha6msMV99UsEMQhJ1RwbCd8AYBj3QMgnE3VZnfQvm4oKVCJTYlqIgg==" + }, + "node_modules/@firebase/logger": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.4.0.tgz", + "integrity": "sha512-eRKSeykumZ5+cJPdxxJRgAC3G5NknY2GwEbKfymdnXtnT0Ucm4pspfR6GT4MUQEDuJwRVbVcSx85kgJulMoFFA==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/messaging": { + "version": "0.12.4", + "resolved": "https://registry.npmjs.org/@firebase/messaging/-/messaging-0.12.4.tgz", + "integrity": "sha512-6JLZct6zUaex4g7HI3QbzeUrg9xcnmDAPTWpkoMpd/GoSVWH98zDoWXMGrcvHeCAIsLpFMe4MPoZkJbrPhaASw==", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/installations": "0.6.4", + "@firebase/messaging-interop-types": "0.2.0", + "@firebase/util": "1.9.3", + "idb": "7.0.1", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/messaging-compat": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@firebase/messaging-compat/-/messaging-compat-0.2.4.tgz", + "integrity": "sha512-lyFjeUhIsPRYDPNIkYX1LcZMpoVbBWXX4rPl7c/rqc7G+EUea7IEtSt4MxTvh6fDfPuzLn7+FZADfscC+tNMfg==", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/messaging": "0.12.4", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/messaging-interop-types": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@firebase/messaging-interop-types/-/messaging-interop-types-0.2.0.tgz", + "integrity": "sha512-ujA8dcRuVeBixGR9CtegfpU4YmZf3Lt7QYkcj693FFannwNuZgfAYaTmbJ40dtjB81SAu6tbFPL9YLNT15KmOQ==" + }, + "node_modules/@firebase/messaging/node_modules/idb": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/idb/-/idb-7.0.1.tgz", + "integrity": "sha512-UUxlE7vGWK5RfB/fDwEGgRf84DY/ieqNha6msMV99UsEMQhJ1RwbCd8AYBj3QMgnE3VZnfQvm4oKVCJTYlqIgg==" + }, + "node_modules/@firebase/performance": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/@firebase/performance/-/performance-0.6.4.tgz", + "integrity": "sha512-HfTn/bd8mfy/61vEqaBelNiNnvAbUtME2S25A67Nb34zVuCSCRIX4SseXY6zBnOFj3oLisaEqhVcJmVPAej67g==", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/installations": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/performance-compat": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@firebase/performance-compat/-/performance-compat-0.2.4.tgz", + "integrity": "sha512-nnHUb8uP9G8islzcld/k6Bg5RhX62VpbAb/Anj7IXs/hp32Eb2LqFPZK4sy3pKkBUO5wcrlRWQa6wKOxqlUqsg==", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/performance": "0.6.4", + "@firebase/performance-types": "0.2.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/performance-types": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@firebase/performance-types/-/performance-types-0.2.0.tgz", + "integrity": "sha512-kYrbr8e/CYr1KLrLYZZt2noNnf+pRwDq2KK9Au9jHrBMnb0/C9X9yWSXmZkFt4UIdsQknBq8uBB7fsybZdOBTA==" + }, + "node_modules/@firebase/remote-config": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/@firebase/remote-config/-/remote-config-0.4.4.tgz", + "integrity": "sha512-x1ioTHGX8ZwDSTOVp8PBLv2/wfwKzb4pxi0gFezS5GCJwbLlloUH4YYZHHS83IPxnua8b6l0IXUaWd0RgbWwzQ==", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/installations": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/remote-config-compat": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@firebase/remote-config-compat/-/remote-config-compat-0.2.4.tgz", + "integrity": "sha512-FKiki53jZirrDFkBHglB3C07j5wBpitAaj8kLME6g8Mx+aq7u9P7qfmuSRytiOItADhWUj7O1JIv7n9q87SuwA==", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/remote-config": "0.4.4", + "@firebase/remote-config-types": "0.3.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/remote-config-types": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@firebase/remote-config-types/-/remote-config-types-0.3.0.tgz", + "integrity": "sha512-RtEH4vdcbXZuZWRZbIRmQVBNsE7VDQpet2qFvq6vwKLBIQRQR5Kh58M4ok3A3US8Sr3rubYnaGqZSurCwI8uMA==" + }, + "node_modules/@firebase/storage": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/@firebase/storage/-/storage-0.11.2.tgz", + "integrity": "sha512-CtvoFaBI4hGXlXbaCHf8humajkbXhs39Nbh6MbNxtwJiCqxPy9iH3D3CCfXAvP0QvAAwmJUTK3+z9a++Kc4nkA==", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/util": "1.9.3", + "node-fetch": "2.6.7", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/storage-compat": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@firebase/storage-compat/-/storage-compat-0.3.2.tgz", + "integrity": "sha512-wvsXlLa9DVOMQJckbDNhXKKxRNNewyUhhbXev3t8kSgoCotd1v3MmqhKKz93ePhDnhHnDs7bYHy+Qa8dRY6BXw==", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/storage": "0.11.2", + "@firebase/storage-types": "0.8.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/storage-types": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@firebase/storage-types/-/storage-types-0.8.0.tgz", + "integrity": "sha512-isRHcGrTs9kITJC0AVehHfpraWFui39MPaU7Eo8QfWlqW7YPymBmRgjDrlOgFdURh6Cdeg07zmkLP5tzTKRSpg==", + "peerDependencies": { + "@firebase/app-types": "0.x", + "@firebase/util": "1.x" + } + }, + "node_modules/@firebase/util": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.9.3.tgz", + "integrity": "sha512-DY02CRhOZwpzO36fHpuVysz6JZrscPiBXD0fXp6qSrL9oNOx5KWICKdR95C0lSITzxp0TZosVyHqzatE8JbcjA==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/webchannel-wrapper": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-0.10.1.tgz", + "integrity": "sha512-Dq5rYfEpdeel0bLVN+nfD1VWmzCkK+pJbSjIawGE+RY4+NIJqhbUDDQjvV0NUK84fMfwxvtFoCtEe70HfZjFcw==" + }, + "node_modules/@grpc/grpc-js": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.7.3.tgz", + "integrity": "sha512-H9l79u4kJ2PVSxUNA08HMYAnUBLj9v6KjYQ7SQ71hOZcEXhShE/y5iQCesP8+6/Ik/7i2O0a10bPquIcYfufog==", + "dependencies": { + "@grpc/proto-loader": "^0.7.0", + "@types/node": ">=12.12.47" + }, + "engines": { + "node": "^8.13.0 || >=10.10.0" + } + }, + "node_modules/@grpc/grpc-js/node_modules/@grpc/proto-loader": { + "version": "0.7.10", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.10.tgz", + "integrity": "sha512-CAqDfoaQ8ykFd9zqBDn4k6iWT9loLAlc2ETmDFS9JCD70gDcnA4L3AFEo2iV7KyAtAAHFW9ftq1Fz+Vsgq80RQ==", + "dependencies": { + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", + "protobufjs": "^7.2.4", + "yargs": "^17.7.2" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@grpc/grpc-js/node_modules/long": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", + "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" + }, + "node_modules/@grpc/grpc-js/node_modules/protobufjs": { + "version": "7.2.6", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.6.tgz", + "integrity": "sha512-dgJaEDDL6x8ASUZ1YqWciTRrdOuYNzoOf27oHNfdyvKqHr5i0FV7FSLU+aIeFjyFgVxrpTOtQUi0BLLBymZaBw==", + "hasInstallScript": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@grpc/proto-loader": { + "version": "0.6.13", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.6.13.tgz", + "integrity": "sha512-FjxPYDRTn6Ec3V0arm1FtSpmP6V50wuph2yILpyvTKzjc76oDdoihXqM1DzOW5ubvCC8GivfCnNtfaRE8myJ7g==", + "dependencies": { + "@types/long": "^4.0.1", + "lodash.camelcase": "^4.3.0", + "long": "^4.0.0", + "protobufjs": "^6.11.3", + "yargs": "^16.2.0" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@grpc/proto-loader/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/@grpc/proto-loader/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@grpc/proto-loader/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@grpc/proto-loader/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "engines": { + "node": ">=10" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.13", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", @@ -3223,6 +3842,75 @@ "url": "https://opencollective.com/popperjs" } }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" + }, + "node_modules/@react-dnd/asap": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@react-dnd/asap/-/asap-5.0.2.tgz", + "integrity": "sha512-WLyfoHvxhs0V9U+GTsGilGgf2QsPl6ZZ44fnv0/b8T3nQyvzxidxsg/ZltbWssbsRDlYW8UKSQMTGotuTotZ6A==" + }, + "node_modules/@react-dnd/invariant": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@react-dnd/invariant/-/invariant-4.0.2.tgz", + "integrity": "sha512-xKCTqAK/FFauOM9Ta2pswIyT3D8AQlfrYdOi/toTPEhqCuAs1v5tcJ3Y08Izh1cJ5Jchwy9SeAXmMg6zrKs2iw==" + }, + "node_modules/@react-dnd/shallowequal": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@react-dnd/shallowequal/-/shallowequal-4.0.2.tgz", + "integrity": "sha512-/RVXdLvJxLg4QKvMoM5WlwNR9ViO9z8B/qPcc+C0Sa/teJY7QG7kJ441DwzOjMYEY7GmU4dj5EcGHIkKZiQZCA==" + }, "node_modules/@remix-run/router": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.14.1.tgz", @@ -4268,11 +4956,15 @@ "@types/lodash": "*" } }, + "node_modules/@types/long": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", + "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==" + }, "node_modules/@types/node": { "version": "20.10.8", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.8.tgz", "integrity": "sha512-f8nQs3cLxbAFc00vEU59yf9UyGUftkPaLGfvbVOIDdx2i1b8epBqj2aNGyP19fiyXWvlmZ7qC1XLjAzw/OKIeA==", - "dev": true, "dependencies": { "undici-types": "~5.26.4" } @@ -4835,7 +5527,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "engines": { "node": ">=8" } @@ -4844,7 +5535,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -5463,7 +6153,6 @@ "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==", - "dev": true, "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", @@ -5477,7 +6166,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -5519,7 +6207,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "dependencies": { "color-name": "~1.1.4" }, @@ -5530,8 +6217,7 @@ "node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "node_modules/color2k": { "version": "2.0.3", @@ -5908,6 +6594,16 @@ "node": ">=8" } }, + "node_modules/dnd-core": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/dnd-core/-/dnd-core-16.0.1.tgz", + "integrity": "sha512-HK294sl7tbw6F6IeuK16YSBUoorvHpY8RHO+9yFfaJyCDVb6n7PRcezrOEOa2SBCqiYpemh5Jx20ZcjKdFAVng==", + "dependencies": { + "@react-dnd/asap": "^5.0.1", + "@react-dnd/invariant": "^4.0.1", + "redux": "^4.2.0" + } + }, "node_modules/doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -5981,8 +6677,7 @@ "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "node_modules/entities": { "version": "4.5.0", @@ -6066,7 +6761,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true, "engines": { "node": ">=6" } @@ -6498,8 +7192,7 @@ "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "node_modules/fast-glob": { "version": "3.3.2", @@ -6550,6 +7243,17 @@ "reusify": "^1.0.4" } }, + "node_modules/faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/fb-watchman": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", @@ -6628,6 +7332,39 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/firebase": { + "version": "9.23.0", + "resolved": "https://registry.npmjs.org/firebase/-/firebase-9.23.0.tgz", + "integrity": "sha512-/4lUVY0lUvBDIaeY1q6dUYhS8Sd18Qb9CgWkPZICUo9IXpJNCEagfNZXBBFCkMTTN5L5gx2Hjr27y21a9NzUcA==", + "dependencies": { + "@firebase/analytics": "0.10.0", + "@firebase/analytics-compat": "0.2.6", + "@firebase/app": "0.9.13", + "@firebase/app-check": "0.8.0", + "@firebase/app-check-compat": "0.3.7", + "@firebase/app-compat": "0.2.13", + "@firebase/app-types": "0.9.0", + "@firebase/auth": "0.23.2", + "@firebase/auth-compat": "0.4.2", + "@firebase/database": "0.14.4", + "@firebase/database-compat": "0.3.4", + "@firebase/firestore": "3.13.0", + "@firebase/firestore-compat": "0.3.12", + "@firebase/functions": "0.10.0", + "@firebase/functions-compat": "0.3.5", + "@firebase/installations": "0.6.4", + "@firebase/installations-compat": "0.2.4", + "@firebase/messaging": "0.12.4", + "@firebase/messaging-compat": "0.2.4", + "@firebase/performance": "0.6.4", + "@firebase/performance-compat": "0.2.4", + "@firebase/remote-config": "0.4.4", + "@firebase/remote-config-compat": "0.2.4", + "@firebase/storage": "0.11.2", + "@firebase/storage-compat": "0.3.2", + "@firebase/util": "1.9.3" + } + }, "node_modules/flat-cache": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", @@ -6814,7 +7551,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, "engines": { "node": "6.* || 8.* || >= 10.*" } @@ -7108,6 +7844,11 @@ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true }, + "node_modules/http-parser-js": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", + "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==" + }, "node_modules/http-proxy-agent": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", @@ -7156,6 +7897,11 @@ "node": ">=0.10.0" } }, + "node_modules/idb": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz", + "integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==" + }, "node_modules/identity-obj-proxy": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/identity-obj-proxy/-/identity-obj-proxy-3.0.0.tgz", @@ -7197,6 +7943,11 @@ "node": ">= 4" } }, + "node_modules/immutability-helper": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/immutability-helper/-/immutability-helper-3.1.1.tgz", + "integrity": "sha512-Q0QaXjPjwIju/28TsugCHNEASwoCcJSyJV3uO1sOIQGI0jKgm9f41Lvz0DZj3n46cNCyAZTsEYoY4C2bVRUzyQ==" + }, "node_modules/immutable": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.4.tgz", @@ -7461,7 +8212,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, "engines": { "node": ">=8" } @@ -9066,6 +9816,11 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==" + }, "node_modules/lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", @@ -9115,6 +9870,11 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -9385,6 +10145,44 @@ "tslib": "^2.0.3" } }, + "node_modules/node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-fetch/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/node-fetch/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/node-fetch/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -9917,6 +10715,31 @@ "react-is": "^16.13.1" } }, + "node_modules/protobufjs": { + "version": "6.11.4", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.4.tgz", + "integrity": "sha512-5kQWPaJHi1WoCpjTGszzQ32PG2F4+wRY6BmAT4Vfw56Q2FZ4YZzK20xUYQH4YkfehY1e6QSICrJquM6xXZNcrw==", + "hasInstallScript": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/long": "^4.0.1", + "@types/node": ">=13.7.0", + "long": "^4.0.0" + }, + "bin": { + "pbjs": "bin/pbjs", + "pbts": "bin/pbts" + } + }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -10046,6 +10869,43 @@ "url": "https://opencollective.com/date-fns" } }, + "node_modules/react-dnd": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/react-dnd/-/react-dnd-16.0.1.tgz", + "integrity": "sha512-QeoM/i73HHu2XF9aKksIUuamHPDvRglEwdHL4jsp784BgUuWcg6mzfxT0QDdQz8Wj0qyRKx2eMg8iZtWvU4E2Q==", + "dependencies": { + "@react-dnd/invariant": "^4.0.1", + "@react-dnd/shallowequal": "^4.0.1", + "dnd-core": "^16.0.1", + "fast-deep-equal": "^3.1.3", + "hoist-non-react-statics": "^3.3.2" + }, + "peerDependencies": { + "@types/hoist-non-react-statics": ">= 3.3.1", + "@types/node": ">= 12", + "@types/react": ">= 16", + "react": ">= 16.14" + }, + "peerDependenciesMeta": { + "@types/hoist-non-react-statics": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-dnd-html5-backend": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/react-dnd-html5-backend/-/react-dnd-html5-backend-16.0.1.tgz", + "integrity": "sha512-Wu3dw5aDJmOGw8WjH1I1/yTH+vlXEL4vmjk5p+MHxP8HuHJS1lAGeIdG/hze1AvNeXWo/JgULV87LyQOr+r5jw==", + "dependencies": { + "dnd-core": "^16.0.1" + } + }, "node_modules/react-dom": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", @@ -10305,6 +11165,14 @@ } } }, + "node_modules/recoil-persist": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/recoil-persist/-/recoil-persist-5.1.0.tgz", + "integrity": "sha512-sew4k3uBVJjRWKCSFuBw07Y1p1pBOb0UxLJPxn4G2bX/9xNj+r2xlqYy/BRfyofR/ANfqBU04MIvulppU4ZC0w==", + "peerDependencies": { + "recoil": "^0.7.2" + } + }, "node_modules/redent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", @@ -10318,6 +11186,14 @@ "node": ">=8" } }, + "node_modules/redux": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", + "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==", + "dependencies": { + "@babel/runtime": "^7.9.2" + } + }, "node_modules/regenerator-runtime": { "version": "0.14.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", @@ -10344,7 +11220,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -10524,7 +11399,6 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, "funding": [ { "type": "github", @@ -10827,7 +11701,6 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -10841,7 +11714,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -11245,8 +12117,7 @@ "node_modules/undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "dev": true + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" }, "node_modules/universal-cookie": { "version": "7.0.1", @@ -11517,6 +12388,27 @@ "node": ">=12" } }, + "node_modules/websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "dependencies": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/whatwg-encoding": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", @@ -11701,7 +12593,6 @@ "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, "engines": { "node": ">=10" } @@ -11724,7 +12615,6 @@ "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", @@ -11742,7 +12632,6 @@ "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, "engines": { "node": ">=12" } diff --git a/package.json b/package.json index b8752623..95e080ba 100644 --- a/package.json +++ b/package.json @@ -17,11 +17,15 @@ "axios": "^1.6.2", "date-fns": "^3.1.0", "dotenv": "^16.3.1", + "firebase": "^9.23.0", "framer-motion": "^10.16.16", + "immutability-helper": "^3.1.1", "jwt-decode": "^4.0.0", "react": "^18.2.0", "react-cookie": "^7.0.1", "react-datepicker": "^4.25.0", + "react-dnd": "^16.0.1", + "react-dnd-html5-backend": "^16.0.1", "react-dom": "^18.2.0", "react-hook-form": "^7.49.2", "react-icons": "^4.12.0", @@ -29,6 +33,7 @@ "react-mobile-datepicker": "^4.0.2", "react-router-dom": "^6.21.1", "recoil": "^0.7.7", + "recoil-persist": "^5.1.0", "swiper": "^11.0.5" }, "devDependencies": { diff --git a/public/firebase-messaging-sw.js b/public/firebase-messaging-sw.js new file mode 100644 index 00000000..16740290 --- /dev/null +++ b/public/firebase-messaging-sw.js @@ -0,0 +1,28 @@ +self.addEventListener('install', function () { + console.log('[FCM SW] 설치중..'); + self.skipWaiting(); +}); + +self.addEventListener('activate', function () { + console.log('[FCM SW] 실행중..'); +}); + +self.addEventListener('push', function (e) { + if (!e.data.json()) return; + + const resultData = e.data.json().notification; + // 필수! 알람 제목 + const notificationTitle = resultData.title; + const notificationOptions = { + //필수! 알람 설명 + body: resultData.body, + //필수! 알람 이미지(프로필, 로고) + icon: resultData.image, + //필수! 알람 분류태그 (전체, 투표, 나가기...) + tag: resultData.tag, + ...resultData, + }; + console.log('출력'); + + self.registration.showNotification(notificationTitle, notificationOptions); +}); diff --git a/src/App.tsx b/src/App.tsx index d6ee9518..0fa8770b 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,11 +1,15 @@ import {QueryClient, QueryClientProvider} from '@tanstack/react-query'; import {Suspense} from 'react'; import {CookiesProvider} from 'react-cookie'; +import {DndProvider} from 'react-dnd'; +import {HTML5Backend} from 'react-dnd-html5-backend'; import {BrowserRouter} from 'react-router-dom'; +import './firebase/messaging-init-in-sw'; import './sass/index.scss'; import MainRouter from './routes/MainRouter/MainRouter'; +window.Kakao.init(import.meta.env.VITE_KAKAO_KEY); const queryClient = new QueryClient(); function App() { @@ -13,9 +17,11 @@ function App() { - - - + + + + + diff --git a/src/api/invite.ts b/src/api/invite.ts new file mode 100644 index 00000000..d98e1ba0 --- /dev/null +++ b/src/api/invite.ts @@ -0,0 +1,11 @@ +import axios from 'axios'; + +import {InviteCodeRequestParams} from '@/types/Invitation'; + +export const postJoin = async (spaceId: number) => + await axios.post(`/api/auth/join/spaces/${spaceId}`, {withCredentials: true}); +export const postJoinSpaces = async (params: InviteCodeRequestParams) => { + const {spaceId} = params; + const response = await axios.post(`/api/auth/join/spaces/${spaceId}/code`, {withCredentials: true}); + return response.data.data; +}; diff --git a/src/api/notification.ts b/src/api/notification.ts new file mode 100644 index 00000000..a29c93a6 --- /dev/null +++ b/src/api/notification.ts @@ -0,0 +1,21 @@ +import axios from 'axios'; + +import {Token} from '@/types/notification'; + +export const sendNotificationToken = async (token: Token) => { + try { + const response = await axios.post('/api/notifications/token', token, {withCredentials: true}); + return response.data; + } catch (error) { + console.error('[notification]토큰 전송 요청에 실패했습니다', error); + } +}; + +export const GetNotification = async () => { + try { + const response = await axios.get('/api/notifications'); + return response.data.data; + } catch (error) { + console.log('[notification]알림내용을 가져오지 못했습니다'); + } +}; diff --git a/src/api/spaces.ts b/src/api/spaces.ts new file mode 100644 index 00000000..3b60f1af --- /dev/null +++ b/src/api/spaces.ts @@ -0,0 +1,6 @@ +import axios from 'axios'; + +export const spacesRequest = { + getSpaces: () => axios.get('/api/spaces').then((response) => response.data.data.spaces), + postSpaces: () => axios.post('/api/spaces'), +}; diff --git a/src/api/user.ts b/src/api/user.ts new file mode 100644 index 00000000..d52df65b --- /dev/null +++ b/src/api/user.ts @@ -0,0 +1,5 @@ +import axios from 'axios'; + +export const memberRequest = { + getMyInfo: () => axios.get('/api/members/my-info').then((response) => response.data.data), +}; diff --git a/src/api/vote.ts b/src/api/vote.ts index 71091d28..6583339a 100644 --- a/src/api/vote.ts +++ b/src/api/vote.ts @@ -19,7 +19,7 @@ export const getVoteInfo = async (voteId: number): Promise => { //보트 리스트 export const getVoteListInfo = async (spaceId: number): Promise => { - const response = await axios.get(`/api/votes/${spaceId}`); + const response = await axios.get(`/api/votes`, {params: {spaceId, voteStatusOption: 'ALL'}}); return response.data; }; @@ -28,7 +28,7 @@ export const getVoteListInfo = async (spaceId: number): Promise /* ----------------------------------- P O S T ---------------------------------- */ //vote 추가 -export const PostNewVote = async ({spaceId, title}: PostVoteTitleProps) => { +export const postNewVote = async ({spaceId, title}: PostVoteTitleProps) => { try { const response = await axios.post('/api/votes', {spaceId, title}); console.log('axios 포스트 성공', response); diff --git a/src/assets/alarm/alarmBlackBell.png b/src/assets/alarm/alarmBlackBell.png new file mode 100644 index 00000000..b02e0d46 Binary files /dev/null and b/src/assets/alarm/alarmBlackBell.png differ diff --git a/src/assets/alarm/alarmWhiteBell.png b/src/assets/alarm/alarmWhiteBell.png new file mode 100644 index 00000000..7b76106f Binary files /dev/null and b/src/assets/alarm/alarmWhiteBell.png differ diff --git a/src/assets/fonts/SpoqaHanSansNeo-Bold.ttf b/src/assets/fonts/SpoqaHanSansNeo-Bold.ttf new file mode 100644 index 00000000..62a9dcf1 Binary files /dev/null and b/src/assets/fonts/SpoqaHanSansNeo-Bold.ttf differ diff --git a/src/assets/fonts/SpoqaHanSansNeo-Light.ttf b/src/assets/fonts/SpoqaHanSansNeo-Light.ttf new file mode 100644 index 00000000..db53af95 Binary files /dev/null and b/src/assets/fonts/SpoqaHanSansNeo-Light.ttf differ diff --git a/src/assets/fonts/SpoqaHanSansNeo-Medium.ttf b/src/assets/fonts/SpoqaHanSansNeo-Medium.ttf new file mode 100644 index 00000000..035f3796 Binary files /dev/null and b/src/assets/fonts/SpoqaHanSansNeo-Medium.ttf differ diff --git a/src/assets/fonts/SpoqaHanSansNeo-Regular.ttf b/src/assets/fonts/SpoqaHanSansNeo-Regular.ttf new file mode 100644 index 00000000..81916a32 Binary files /dev/null and b/src/assets/fonts/SpoqaHanSansNeo-Regular.ttf differ diff --git a/src/assets/fonts/SpoqaHanSansNeo-Thin.ttf b/src/assets/fonts/SpoqaHanSansNeo-Thin.ttf new file mode 100644 index 00000000..4a75b2e6 Binary files /dev/null and b/src/assets/fonts/SpoqaHanSansNeo-Thin.ttf differ diff --git a/src/assets/homeIcons/search/nullImg.svg b/src/assets/homeIcons/search/nullImg.svg new file mode 100644 index 00000000..ee839bf9 --- /dev/null +++ b/src/assets/homeIcons/search/nullImg.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/InviteLogo.svg b/src/assets/invite/InviteLogo.svg similarity index 100% rename from src/assets/InviteLogo.svg rename to src/assets/invite/InviteLogo.svg diff --git a/src/assets/inviteKakao.svg b/src/assets/invite/inviteKakao.svg similarity index 100% rename from src/assets/inviteKakao.svg rename to src/assets/invite/inviteKakao.svg diff --git a/src/assets/inviteLink.svg b/src/assets/invite/inviteLink.svg similarity index 100% rename from src/assets/inviteLink.svg rename to src/assets/invite/inviteLink.svg diff --git a/src/chakra/chakraCustomTheme.ts b/src/chakra/chakraCustomTheme.ts index 7bcd4dd2..15944b72 100644 --- a/src/chakra/chakraCustomTheme.ts +++ b/src/chakra/chakraCustomTheme.ts @@ -1,214 +1,210 @@ -import { extendTheme } from "@chakra-ui/react"; +import {extendTheme} from '@chakra-ui/react'; -import { avatarTheme } from "./avatarCustom"; -import { buttonTheme } from "./buttonCustom"; -import { checkboxTheme } from "./checkboxCustom"; -import { modalTheme } from "./modalCustom"; -import { tabsTheme } from "./tabsCustom"; -import { tagTheme } from "./tagCustom"; +import {avatarTheme} from './avatarCustom'; +import {buttonTheme} from './buttonCustom'; +import {checkboxTheme} from './checkboxCustom'; +import {modalTheme} from './modalCustom'; +import {tabsTheme} from './tabsCustom'; +import {tagTheme} from './tagCustom'; export const customTheme = extendTheme({ styles: { global: { - ".swiper": { + '.swiper': { _hover: { - ".swiper-button-next": { - opacity: "1", + '.swiper-button-next': { + opacity: '1', }, - ".swiper-button-prev": { - opacity: "1", + '.swiper-button-prev': { + opacity: '1', }, }, }, - ".swiper-button-next": { - height: "5.2rem", - width: "5.2rem", - backgroundSize: "50% auto", - backgroundPosition: "center", - background: "url(src/assets/voteIcons/swiper-next-btn.svg) no-repeat", - opacity: "0", - transition: "opacity 0.3s ease", + '.swiper-button-next': { + height: '5.2rem', + width: '5.2rem', + backgroundSize: '50% auto', + backgroundPosition: 'center', + background: 'url(src/assets/voteIcons/swiper-next-btn.svg) no-repeat', + opacity: '0', + transition: 'opacity 0.3s ease', _after: { - display: "none", + display: 'none', }, }, - ".swiper-button-prev": { - height: "5.2rem", - width: "5.2rem", - backgroundSize: "50% auto", - backgroundPosition: "center", - background: "url(src/assets/voteIcons/swiper-prev-btn.svg) no-repeat", - opacity: "0", - transition: "opacity 0.3s ease", + '.swiper-button-prev': { + height: '5.2rem', + width: '5.2rem', + backgroundSize: '50% auto', + backgroundPosition: 'center', + background: 'url(src/assets/voteIcons/swiper-prev-btn.svg) no-repeat', + opacity: '0', + transition: 'opacity 0.3s ease', _after: { - display: "none", + display: 'none', }, }, - ".swiper-button-disabled": { - display: "none", + '.swiper-button-disabled': { + display: 'none', }, // Calendar Modal - ".react-datepicker": { - border: "none", - padding: "8px 20px", + '.react-datepicker': { + border: 'none', + padding: '8px 20px', - "&__navigation": { - display: "none", + '&__navigation': { + display: 'none', }, - "&__header": { - backgroundColor: "transparent", - border: "none", + '&__header': { + backgroundColor: 'transparent', + border: 'none', }, - "&__current-month": { - display: "flex", - paddingLeft: "calc((100% - 30rem)/2)", - fontSize: "1.6rem", - fontStyle: "normal", - fontWeight: "700", - lineHeight: "2.4rem", - letterSpacing: "-0.16px", - color: "neutral.900", + '&__current-month': { + display: 'flex', + paddingLeft: 'calc((100% - 30rem)/2)', + fontSize: '1.6rem', + fontStyle: 'normal', + fontWeight: '700', + lineHeight: '2.4rem', + letterSpacing: '-0.16px', + color: 'neutral.900', }, - "&__day-names": { - display: "flex", - justifyContent: "center", - margin: "1.6rem 0", - "& > div:nth-child(1)": { - color: "danger.300", + '&__day-names': { + display: 'flex', + justifyContent: 'center', + margin: '1.6rem 0', + '& > div:nth-child(1)': { + color: 'danger.300', }, }, - "&__day-name": { - width: "4.8rem", - fontSize: "1.2rem", - fontStyle: "normal", - fontWeight: "500", - lineHeight: "2rem", - letterSpacing: "-0.12px", - color: "neutral.900", - margin: "0", + '&__day-name': { + width: '4.8rem', + fontSize: '1.2rem', + fontStyle: 'normal', + fontWeight: '500', + lineHeight: '2rem', + letterSpacing: '-0.12px', + color: 'neutral.900', + margin: '0', }, - "&__month-container": { - width: "100%", - marginBottom: "3.2rem", + '&__month-container': { + width: '100%', + marginBottom: '3.2rem', }, - "&__week": { - display: "flex", - justifyContent: "center", - marginBottom: "2rem", - "& > div:nth-child(1)": { - color: "danger.300", + '&__week': { + display: 'flex', + justifyContent: 'center', + marginBottom: '2rem', + '& > div:nth-child(1)': { + color: 'danger.300', }, }, - "&__day": { - display: "flex", - justifyContent: "center", - alignItems: "center", - width: "4.8rem", - height: "3.5rem", - fontSize: "1.6rem", - fontStyle: "normal", - fontWeight: "700", - lineHeight: "2.4rem", - letterSpacing: "-0.16px", - color: "neutral.900", - margin: "0", + '&__day': { + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + width: '4.8rem', + height: '3.5rem', + fontSize: '1.6rem', + fontStyle: 'normal', + fontWeight: '700', + lineHeight: '2.4rem', + letterSpacing: '-0.16px', + color: 'neutral.900', + margin: '0', _hover: { - width: "3.5rem", - margin: "0 0.65rem", - borderRadius: "100px", - backgroundColor: "primary.300 !important", - color: "neutral.0", + width: '3.5rem', + margin: '0 0.65rem', + borderRadius: '100px', + backgroundColor: 'primary.300 !important', + color: 'neutral.0', }, - "&--keyboard-selected": { - backgroundColor: "transparent", + '&--keyboard-selected': { + backgroundColor: 'transparent', }, - "&--disabled": { - color: "neutral.200 !important", + '&--disabled': { + color: 'neutral.200 !important', }, - "&--in-selecting-range, &--in-range": { - backgroundColor: "primary.100 !important", + '&--in-selecting-range, &--in-range': { + backgroundColor: 'primary.100 !important', borderRadius: 0, - position: "relative", + position: 'relative', }, - "&--selecting-range-start, &--range-start, &--selecting-range-end, &--range-end": - { - _hover: { - width: "4.8rem", - margin: 0, - }, + '&--selecting-range-start, &--range-start, &--selecting-range-end, &--range-end': { + _hover: { + width: '4.8rem', + margin: 0, }, + }, - "&--selecting-range-start::before, &--range-start::before, &--selecting-range-end::before, &--range-end::before": + '&--selecting-range-start::before, &--range-start::before, &--selecting-range-end::before, &--range-end::before': { - width: "3.5rem", - height: "3.5rem", - marginLeft: "0.65rem", - borderRadius: "100px", + width: '3.5rem', + height: '3.5rem', + marginLeft: '0.65rem', + borderRadius: '100px', content: "''", - position: "absolute", + position: 'absolute', top: 0, left: 0, - backgroundColor: "primary.300 !important", - color: "neutral.0 !important", - zIndex: "1 !important", + backgroundColor: 'primary.300 !important', + color: 'neutral.0 !important', + zIndex: '1 !important', }, - "&--selecting-range-start::after, &--range-start::after": { - width: "2.4rem", - height: "3.5rem", - borderRadius: "0", + '&--selecting-range-start::after, &--range-start::after': { + width: '2.4rem', + height: '3.5rem', + borderRadius: '0', content: "''", - position: "absolute", + position: 'absolute', top: 0, - left: "50%", - backgroundColor: "primary.100 !important", - color: "neutral.0 !important", - zIndex: "0 !important", + left: '50%', + backgroundColor: 'primary.100 !important', + color: 'neutral.0 !important', + zIndex: '0 !important', }, - "&--selecting-range-end::after, &--range-end::after": { - width: "2.4rem", - height: "3.5rem", - borderRadius: "0", + '&--selecting-range-end::after, &--range-end::after': { + width: '2.4rem', + height: '3.5rem', + borderRadius: '0', content: "''", - position: "absolute", + position: 'absolute', top: 0, - right: "50%", - backgroundColor: "primary.100 !important", - color: "neutral.0 !important", - zIndex: "0 !important", + right: '50%', + backgroundColor: 'primary.100 !important', + color: 'neutral.0 !important', + zIndex: '0 !important', }, - "&--selecting-range-start span, &--range-start span, &--selecting-range-end span, &--range-end span": - { - position: "absolute", - zIndex: "5 !important", - color: "neutral.0", - }, + '&--selecting-range-start span, &--range-start span, &--selecting-range-end span, &--range-end span': { + position: 'absolute', + zIndex: '5 !important', + color: 'neutral.0', + }, }, - ".react-datepicker__day--range-start.react-datepicker__day--in-range,\ + '.react-datepicker__day--range-start.react-datepicker__day--in-range,\ .react-datepicker__day--range-end.react-datepicker__day--in-range,\ .react-datepicker__day--in-selecting-range.react-datepicker__day--selecting-range-start,\ - .react-datepicker__day--in-selecting-range.react-datepicker__day--selecting-range-end": - { - backgroundColor: "transparent !important", - }, - ".react-datepicker__day--range-start.react-datepicker__day--range-end.react-datepicker__day--in-range::after": - { - backgroundColor: "transparent !important", - }, + .react-datepicker__day--in-selecting-range.react-datepicker__day--selecting-range-end': { + backgroundColor: 'transparent !important', + }, + '.react-datepicker__day--range-start.react-datepicker__day--range-end.react-datepicker__day--in-range::after': { + backgroundColor: 'transparent !important', + }, }, }, }, @@ -222,124 +218,124 @@ export const customTheme = extendTheme({ }, fonts: { - heading: `'suit', sans-serif`, - body: `'suit', sans-serif`, + heading: `'spoqaHanSans', sans-serif`, + body: `'spoqaHanSans', sans-serif`, }, colors: { primary: { - 100: "#d4e8ff", - 200: "#62aaff", - 300: "#2388ff", - 400: "#0172d8", + 100: '#d4e8ff', + 200: '#62aaff', + 300: '#2388ff', + 400: '#0172d8', }, secondary: { - 100: "#ffe2ed", - 300: "#ff85b1", - 400: "#e23774", + 100: '#ffe2ed', + 300: '#ff85b1', + 400: '#e23774', }, success: { - 100: "#71d07b", - 300: "#19c43e", - 400: "#04724d", + 100: '#71d07b', + 300: '#19c43e', + 400: '#04724d', }, danger: { - 100: "#fef1f2", - 300: "#e02d3c", - 400: "#ba2532", + 100: '#fef1f2', + 300: '#e02d3c', + 400: '#ba2532', }, neutral: { - 0: "#fff", - 100: "#f2f4f5", - 200: "#e3e5e5", - 300: "#cdcfd0", - 400: "#979c9e", - 700: "#3f444d", - 800: "#23272f", - 900: "#1d2433", - 1000: "#0a0d1", + 0: '#fff', + 100: '#f2f4f5', + 200: '#e3e5e5', + 300: '#cdcfd0', + 400: '#979c9e', + 700: '#3f444d', + 800: '#23272f', + 900: '#1d2433', + 1000: '#0a0d1', }, etc: { - 0: "#fed600", + 0: '#fed600', }, }, shadows: { shadow: { - 100: "0px 0px 8px 0px rgba(20, 20, 20, 0.08), 0px 0px 1px 0px rgba(20, 20, 20, 0.04)", - 200: "0px 1px 8px 2px rgba(20, 20, 20, 0.1), 0px 0px 1px 0px rgba(20, 20, 20, 0.04)", - 300: "0px 1px 24px 8px rgba(20, 20, 20, 0.08)", + 100: '0px 0px 8px 0px rgba(20, 20, 20, 0.08), 0px 0px 1px 0px rgba(20, 20, 20, 0.04)', + 200: '0px 1px 8px 2px rgba(20, 20, 20, 0.1), 0px 0px 1px 0px rgba(20, 20, 20, 0.04)', + 300: '0px 1px 24px 8px rgba(20, 20, 20, 0.08)', }, }, //$typography-font-size-map fontSizes: { - headline: "2.2rem", + headline: '2.2rem', // 22px - titleLarge: "2rem", + titleLarge: '2rem', // 20px" - titleMedium: "1.8rem", + titleMedium: '1.8rem', // 18px - titleSmall: "1.6rem", + titleSmall: '1.6rem', // 16px - subTitle: "1.6rem", + subTitle: '1.6rem', // 16px - bodyLarge: "1.6rem", + bodyLarge: '1.6rem', // 16px - bodySmall: "1.4rem", + bodySmall: '1.4rem', // 14px - button: "1.4rem", + button: '1.4rem', // 14px - tabLabel: "1.4rem", + tabLabel: '1.4rem', // 14px - captionLarge: "1.3rem", + captionLarge: '1.3rem', // 13px - captionMedium: "1.2rem", + captionMedium: '1.2rem', // 12px - captionSmall: "1.2rem", + captionSmall: '1.2rem', // 12px }, //$typography-font-weight-map fontWeights: { - headline: "700", - titleLarge: "700", - titleMedium: "700", - titleSmall: "700", - subTitle: "500", - bodyLarge: "400", - bodySmall: "400", - button: "700", - tabLabel: "500", - captionLarge: "500", - captionMedium: "700", - captionSmall: "500", + headline: '700', + titleLarge: '700', + titleMedium: '700', + titleSmall: '700', + subTitle: '500', + bodyLarge: '400', + bodySmall: '400', + button: '700', + tabLabel: '500', + captionLarge: '500', + captionMedium: '700', + captionSmall: '500', }, //$typography-line-height-map lineHeights: { - headline: "3rem", + headline: '3rem', // 30px - titleLarge: "2.8rem", + titleLarge: '2.8rem', // 28px - titleMedium: "2.6rem", + titleMedium: '2.6rem', // 26px - titleSmall: "2.4rem", + titleSmall: '2.4rem', // 24px - subTitle: "2.4rem", + subTitle: '2.4rem', // 24px - bodyLarge: "2.4rem", + bodyLarge: '2.4rem', // 24px - bodySmall: "2.2rem", + bodySmall: '2.2rem', // 22px - button: "2.2rem", + button: '2.2rem', // 22px - tabLabel: "2.2rem", + tabLabel: '2.2rem', // 22px - captionLarge: "2.2rem", + captionLarge: '2.2rem', // 22px - captionMedium: "2rem", + captionMedium: '2rem', // 20px - captionSmall: "2rem", + captionSmall: '2rem', // 20px }, }); diff --git a/src/components/Alarm/Alarm.tsx b/src/components/Alarm/Alarm.tsx index c7c8d0f3..85c8ccf3 100644 --- a/src/components/Alarm/Alarm.tsx +++ b/src/components/Alarm/Alarm.tsx @@ -1,16 +1,16 @@ -import { Slide } from "@chakra-ui/react"; -import { useEffect, useState } from "react"; +import {Slide} from '@chakra-ui/react'; +import {useEffect, useState} from 'react'; -import styles from "./Alarm.module.scss"; +import styles from './Alarm.module.scss'; -import useLockBodyScroll from "@/hooks/useLockBodyScroll"; +import useLockBodyScroll from '@/hooks/useLockBodyScroll'; -import Back from "@/components/Alarm/Back/Back"; -import TabCapsule from "@/components/Alarm/TabCapsule/TabCapsule"; +import Back from '@/components/Alarm/Back/Back'; +import TabCapsule from '@/components/Alarm/TabCapsule/TabCapsule'; -import { AlarmProps } from "@/types/alarm"; +import {AlarmProps} from '@/types/alarm'; -function Alarm({ isAlarmOpen, alarmClose }: AlarmProps) { +function Alarm({isAlarmOpen, alarmClose}: AlarmProps) { // 알림 스타일링 const [isVisible, setIsVisible] = useState(isAlarmOpen); const [windowWidth, setWindowWidth] = useState(window.innerWidth); @@ -30,34 +30,42 @@ function Alarm({ isAlarmOpen, alarmClose }: AlarmProps) { }; }, [isAlarmOpen]); const containerStyle = { - display: isVisible ? "block" : "none", + display: isVisible ? 'block' : 'none', }; useEffect(() => { const handleResize = () => { setWindowWidth(window.innerWidth); }; - window.addEventListener("resize", handleResize); + window.addEventListener('resize', handleResize); handleResize(); return () => { - window.removeEventListener("resize", handleResize); + window.removeEventListener('resize', handleResize); }; }, []); useLockBodyScroll(isAlarmOpen); + // 새로운 알림 상태관리 + useEffect(() => { + if (isAlarmOpen === true) { + localStorage.removeItem('news'); + console.log('비움'); + } + }, [isAlarmOpen]); + return (
diff --git a/src/components/Alarm/TabCapsule/TabCapsule.tsx b/src/components/Alarm/TabCapsule/TabCapsule.tsx index 7a2bab07..be2feeee 100644 --- a/src/components/Alarm/TabCapsule/TabCapsule.tsx +++ b/src/components/Alarm/TabCapsule/TabCapsule.tsx @@ -1,91 +1,67 @@ -import { - Tab, - TabIndicator, - TabList, - TabPanel, - TabPanels, - Tabs, -} from "@chakra-ui/react"; +import {Tab, TabIndicator, TabList, TabPanel, TabPanels, Tabs} from '@chakra-ui/react'; -import styles from "./TabCapsule.module.scss"; +import styles from './TabCapsule.module.scss'; -import Content from "./Content/Content"; +import Content from './Content/Content'; const AllContents = [ { - url: "https://m.eejmall.com/web/product/big/201708/211_shop1_627935.jpg", + url: 'https://m.eejmall.com/web/product/big/201708/211_shop1_627935.jpg', title: `[강릉 여행]강자밭님이 "밥집 어디갈꺼야" 투표를 만들었어요.`, - time: "2024-01-11T23:09:00", + time: '2024-01-11T23:09:00', }, { - url: "https://private-user-images.githubusercontent.com/120024673/294744934-6e80734e-61fc-46e5-abb4-3d26bad5f1ea.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MDQ2MTQzNDYsIm5iZiI6MTcwNDYxNDA0NiwicGF0aCI6Ii8xMjAwMjQ2NzMvMjk0NzQ0OTM0LTZlODA3MzRlLTYxZmMtNDZlNS1hYmI0LTNkMjZiYWQ1ZjFlYS5wbmc_WC1BbXotQWxnb3JpdGhtPUFXUzQtSE1BQy1TSEEyNTYmWC1BbXotQ3JlZGVudGlhbD1BS0lBVkNPRFlMU0E1M1BRSzRaQSUyRjIwMjQwMTA3JTJGdXMtZWFzdC0xJTJGczMlMkZhd3M0X3JlcXVlc3QmWC1BbXotRGF0ZT0yMDI0MDEwN1QwNzU0MDZaJlgtQW16LUV4cGlyZXM9MzAwJlgtQW16LVNpZ25hdHVyZT0yMGRlZGI3ZjRiZTRkZDY4YTJjNTQxMWNkNmI5YWI5ZDExNzU3OWEwNjJhNTA1NTgzNDQxYWY3MDMyYmVkYmIyJlgtQW16LVNpZ25lZEhlYWRlcnM9aG9zdCZhY3Rvcl9pZD0wJmtleV9pZD0wJnJlcG9faWQ9MCJ9.QMy2zh7OIc5uzN7W7qhAr540FtZN7YbaZ4k0z9Ajmws", - title: "개인정보 보호정책이 변경되었습니다.", - time: "2024-01-11T20:00:00", + url: 'https://github.com/Strong-Potato/TripVote-FE/assets/120024673/089a8673-405e-4a06-b173-4cf3e985b466', + title: '개인정보 보호정책이 변경되었습니다.', + time: '2024-01-11T20:00:00', }, { - url: "https://private-user-images.githubusercontent.com/120024673/294744934-6e80734e-61fc-46e5-abb4-3d26bad5f1ea.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MDQ2MTQzNDYsIm5iZiI6MTcwNDYxNDA0NiwicGF0aCI6Ii8xMjAwMjQ2NzMvMjk0NzQ0OTM0LTZlODA3MzRlLTYxZmMtNDZlNS1hYmI0LTNkMjZiYWQ1ZjFlYS5wbmc_WC1BbXotQWxnb3JpdGhtPUFXUzQtSE1BQy1TSEEyNTYmWC1BbXotQ3JlZGVudGlhbD1BS0lBVkNPRFlMU0E1M1BRSzRaQSUyRjIwMjQwMTA3JTJGdXMtZWFzdC0xJTJGczMlMkZhd3M0X3JlcXVlc3QmWC1BbXotRGF0ZT0yMDI0MDEwN1QwNzU0MDZaJlgtQW16LUV4cGlyZXM9MzAwJlgtQW16LVNpZ25hdHVyZT0yMGRlZGI3ZjRiZTRkZDY4YTJjNTQxMWNkNmI5YWI5ZDExNzU3OWEwNjJhNTA1NTgzNDQxYWY3MDMyYmVkYmIyJlgtQW16LVNpZ25lZEhlYWRlcnM9aG9zdCZhY3Rvcl9pZD0wJmtleV9pZD0wJnJlcG9faWQ9MCJ9.QMy2zh7OIc5uzN7W7qhAr540FtZN7YbaZ4k0z9Ajmws", - title: "개인정보 보호정책이 변경되었습니다.", - time: "2024-01-10T10:00:00", + url: 'https://github.com/Strong-Potato/TripVote-FE/assets/120024673/089a8673-405e-4a06-b173-4cf3e985b466', + title: '개인정보 보호정책이 변경되었습니다.', + time: '2024-01-10T10:00:00', }, { - url: "https://m.eejmall.com/web/product/big/201708/211_shop1_627935.jpg", + url: 'https://m.eejmall.com/web/product/big/201708/211_shop1_627935.jpg', title: `[강릉 여행]강자밭님이 "카페 어디갈꺼야" 투표를 만들었어요.`, - time: "2024-01-03T12:00:00", + time: '2024-01-03T12:00:00', }, { - url: "https://m.eejmall.com/web/product/big/201708/211_shop1_627935.jpg", + url: 'https://m.eejmall.com/web/product/big/201708/211_shop1_627935.jpg', title: `[강릉 여행]강자밭님이 "숙소 어디갈꺼야" 투표를 만들었어요.`, - time: "2023-12-31T12:00:00", + time: '2023-12-31T12:00:00', }, ]; const SpaceTravelContents = [ { - url: "https://m.eejmall.com/web/product/big/201708/211_shop1_627935.jpg", + url: 'https://m.eejmall.com/web/product/big/201708/211_shop1_627935.jpg', title: `[강릉 여행]강자밭님이 "밥집 어디갈꺼야" 투표를 만들었어요.`, - time: "2024-01-11T23:05:00", + time: '2024-01-11T23:05:00', }, { - url: "https://m.eejmall.com/web/product/big/201708/211_shop1_627935.jpg", + url: 'https://m.eejmall.com/web/product/big/201708/211_shop1_627935.jpg', title: `[강릉 여행]강자밭님이 "카페 어디갈꺼야" 투표를 만들었어요.`, - time: "2024-01-03T12:00:00", + time: '2024-01-03T12:00:00', }, { - url: "https://m.eejmall.com/web/product/big/201708/211_shop1_627935.jpg", + url: 'https://m.eejmall.com/web/product/big/201708/211_shop1_627935.jpg', title: `[강릉 여행]강자밭님이 "숙소 어디갈꺼야" 투표를 만들었어요.`, - time: "2023-12-31T12:00:00", + time: '2023-12-31T12:00:00', }, ]; function TabCapsule() { return ( - + - + 전체 - + 여행스페이스 - + diff --git a/src/components/BottomSlide/BottomSlide.module.scss b/src/components/BottomSlide/BottomSlide.module.scss index 5e3196b2..931c6d46 100644 --- a/src/components/BottomSlide/BottomSlide.module.scss +++ b/src/components/BottomSlide/BottomSlide.module.scss @@ -1,4 +1,4 @@ -@use "@/sass" as *; +@use '@/sass' as *; .slideContainer { position: fixed; @@ -25,6 +25,11 @@ display: flex; justify-content: flex-end; } + + .leftCloseButtonContainer { + display: flex; + justify-content: flex-start; + } } } } diff --git a/src/components/BottomSlide/BottomSlide.tsx b/src/components/BottomSlide/BottomSlide.tsx index 5abad792..02566aac 100644 --- a/src/components/BottomSlide/BottomSlide.tsx +++ b/src/components/BottomSlide/BottomSlide.tsx @@ -1,33 +1,33 @@ -import { Slide } from "@chakra-ui/react"; -import { useRef } from "react"; +import {Slide} from '@chakra-ui/react'; +import {useRef} from 'react'; -import styles from "./BottomSlide.module.scss"; +import styles from './BottomSlide.module.scss'; -import useOnClickOutside from "@/hooks/useOnClickOutside"; +import useOnClickOutside from '@/hooks/useOnClickOutside'; -import CloseIcon from "@/assets/close.svg?react"; +import CloseIcon from '@/assets/close.svg?react'; -import { BottomSlideProps } from "../../types/bottomSlide"; +import {BottomSlideProps} from '../../types/bottomSlide'; -function BottomSlide({ isOpen, onClose, children }: BottomSlideProps) { +function BottomSlide({isOpen, onClose, children}: BottomSlideProps) { const containerStyle = { - display: isOpen ? "block" : "none", + display: isOpen ? 'block' : 'none', + }; + + const closeModal = () => { + onClose(); + document.body.style.overflow = 'visible'; }; const slideRef = useRef(null); - useOnClickOutside(slideRef, onClose); + useOnClickOutside(slideRef, closeModal); return (
- +
-
diff --git a/src/components/BottomSlide/BottomSlideLeft.tsx b/src/components/BottomSlide/BottomSlideLeft.tsx new file mode 100644 index 00000000..a40f9352 --- /dev/null +++ b/src/components/BottomSlide/BottomSlideLeft.tsx @@ -0,0 +1,36 @@ +import {Slide} from '@chakra-ui/react'; +import {useRef} from 'react'; + +import styles from './BottomSlide.module.scss'; + +import useOnClickOutside from '@/hooks/useOnClickOutside'; + +import CloseIcon from '@/assets/close.svg?react'; + +import {BottomSlideProps} from '../../types/bottomSlide'; + +function BottomSlideLeft({isOpen, onClose, children}: BottomSlideProps) { + const containerStyle = { + display: isOpen ? 'block' : 'none', + }; + + const slideRef = useRef(null); + useOnClickOutside(slideRef, onClose); + + return ( +
+ +
+
+ +
+
{children}
+
+
+
+ ); +} + +export default BottomSlideLeft; diff --git a/src/components/ButtonsInAddingCandidate/SelectButton/SelectButton.tsx b/src/components/ButtonsInAddingCandidate/SelectButton/SelectButton.tsx index fd7d18a0..061421b6 100644 --- a/src/components/ButtonsInAddingCandidate/SelectButton/SelectButton.tsx +++ b/src/components/ButtonsInAddingCandidate/SelectButton/SelectButton.tsx @@ -1,20 +1,34 @@ import {useState} from 'react'; +import {useRecoilValue} from 'recoil'; import styles from './SelectButton.module.scss'; -import useGetSelectedCandidates from '@/hooks/useGetSelectedCandidates'; +import useGetSelectedArray from '@/hooks/useGetSelectedArray'; + +import {selectedPlaceState} from '@/recoil/vote/selectPlace'; + +// //"선택된 장소 객체" +const placeInfo = { + placeId: 23, + placeName: '안녕호텔', + category: '호텔', + location: '서울', + placeImageURL: 'https://img-cf.kurly.com/shop/data/goodsview/20210218/gv30000159355_1.jpg', + latlng: {lat: 33.450936, lng: 126.569477}, +}; + const SelectButton = () => { const [isClicked, setIsClicked] = useState(false); - const {addCandidateInSelectedList} = useGetSelectedCandidates(); + const selectedPlaces = useRecoilValue(selectedPlaceState); - //"선택된 장소의 ID" - const placeId = 22; + const {toggleItemInNewArray} = useGetSelectedArray(selectedPlaceState); const handleClick = () => { setIsClicked((prev) => !prev); - addCandidateInSelectedList(placeId); + toggleItemInNewArray(placeInfo); }; + console.log('선택한 배열', selectedPlaces); return ( -
@@ -113,15 +104,12 @@ function Invitation({ inviteCode, isLogin, modal }: InvitationProps) { className={styles.wrapperButton__cancel} onClick={() => { modal(false); - removeCookie("inviteCode", { path: "/" }); + removeCookie('join_space_token', {path: '/'}); }} > 취소 -
diff --git a/src/components/Route/DayMove/DayMove.module.scss b/src/components/Route/DayMove/DayMove.module.scss new file mode 100644 index 00000000..6b5466c8 --- /dev/null +++ b/src/components/Route/DayMove/DayMove.module.scss @@ -0,0 +1,30 @@ +@use '@/sass' as *; + +.DayMoveContainer { + display: flex; + flex-direction: column; + padding: 0 12px; + gap: 0.8rem; + + header { + display: flex; + justify-content: center; + margin-top: -0.8rem; + + h1 { + @include typography(button); + color: $neutral700; + } + } + + .dayContainer { + display: flex; + justify-content: space-between; + padding: 12px 0; + + p { + @include typography(bodyLarge); + color: $neutral900; + } + } +} diff --git a/src/components/Route/DayMove/DayMove.tsx b/src/components/Route/DayMove/DayMove.tsx new file mode 100644 index 00000000..37026348 --- /dev/null +++ b/src/components/Route/DayMove/DayMove.tsx @@ -0,0 +1,46 @@ +import {useState} from 'react'; +import {RiCheckboxCircleFill as SelectedIcon} from 'react-icons/ri'; + +import styles from './DayMove.module.scss'; + +interface DayMoveProps { + selectedPlaces: string[]; +} + +function DayMove({selectedPlaces}: DayMoveProps) { + const days = 5; + const [selectedDays, setSelectedDays] = useState(Array(days).fill(false)); + + const handleSelect = (index: number) => { + const updatedSelectedDays = [...selectedDays]; + updatedSelectedDays[index] = !updatedSelectedDays[index]; + setSelectedDays(updatedSelectedDays); + + // TODO: 선택된 장소 카드 선택한 날짜 동선으로 이동 + console.log(index + 1, '여기로 이것을 옮기겠다', selectedPlaces); + }; + + return ( +
+
+

날짜 이동

+
+
+ {Array.from({length: days}, (_, index) => ( +
+

DAY {index + 1}

+ +
+ ))} +
+
+ ); +} + +export default DayMove; diff --git a/src/components/Route/DayNavigationBar/DayNavigationBar.module.scss b/src/components/Route/DayNavigationBar/DayNavigationBar.module.scss index 6bf93015..2a532732 100644 --- a/src/components/Route/DayNavigationBar/DayNavigationBar.module.scss +++ b/src/components/Route/DayNavigationBar/DayNavigationBar.module.scss @@ -1,13 +1,15 @@ @use '@/sass' as *; .container { + position: sticky; + top: 25.5rem; + nav { height: 7rem; display: flex; justify-content: space-between; padding: 16px 20px; - position: sticky; - top: 25.5rem; + background-color: $neutral0; margin-bottom: 2rem; @@ -40,9 +42,9 @@ // FIXME: 그라데이션 수정 background: linear-gradient(90deg, rgba(255, 255, 255, 0) 0.2%, #fff 22.5%); - .wholeEditButton { + .wholeEditButton, + .wholeCompleteButton { @include typography(button); - color: $neutral400; display: flex; justify-content: center; align-items: center; @@ -50,6 +52,14 @@ gap: 0.8rem; white-space: nowrap; } + + .wholeEditButton { + color: $neutral400; + } + + .wholeCompleteButton { + color: $primary300; + } } } } diff --git a/src/components/Route/DayNavigationBar/DayNavigationBar.tsx b/src/components/Route/DayNavigationBar/DayNavigationBar.tsx index 3b07b854..aadaf5dd 100644 --- a/src/components/Route/DayNavigationBar/DayNavigationBar.tsx +++ b/src/components/Route/DayNavigationBar/DayNavigationBar.tsx @@ -4,7 +4,7 @@ import styles from './DayNavigationBar.module.scss'; import {DayNavigationBarProps} from '@/types/route'; -function DayNavigationBar({dateList}: DayNavigationBarProps) { +function DayNavigationBar({dateList, editMode, handleEditMode}: DayNavigationBarProps) { const [selectedDay, setSelectedDay] = useState(1); // TODO: 클릭 시 해당 day로 스크롤 이동 @@ -27,7 +27,9 @@ function DayNavigationBar({dateList}: DayNavigationBarProps) { ))}
- +
diff --git a/src/components/Route/DayRoute/DayRoute.tsx b/src/components/Route/DayRoute/DayRoute.tsx index e520fac0..e5fb8358 100644 --- a/src/components/Route/DayRoute/DayRoute.tsx +++ b/src/components/Route/DayRoute/DayRoute.tsx @@ -1,18 +1,55 @@ -import { useDisclosure } from "@chakra-ui/react"; -import { AiOutlinePlus as PlusIcon } from "react-icons/ai"; +import {useDisclosure} from '@chakra-ui/react'; +import update from 'immutability-helper'; +import {useCallback, useEffect, useState} from 'react'; +import {useDrop} from 'react-dnd'; +import {AiOutlinePlus as PlusIcon} from 'react-icons/ai'; -import styles from "./DayRoute.module.scss"; +import styles from './DayRoute.module.scss'; -import BottomSlide from "@/components/BottomSlide/BottomSlide"; +import BottomSlide from '@/components/BottomSlide/BottomSlide'; -import AddPlace from "../AddPlace/AddPlace"; -import EmptyRoute from "../EmptyRoute/EmptyRoute"; -import PlaceCard from "../PlaceCard/PlaceCard"; +import AddPlace from '../AddPlace/AddPlace'; +import DraggablePlaceCard from '../DraggablePlaceCard/DraggablePlaceCard'; +import EmptyRoute from '../EmptyRoute/EmptyRoute'; -import { DayRouteProps } from "@/types/route"; +import {DayRouteProps} from '@/types/route'; + +function DayRoute({day, date, placeList, editMode, selectedPlaces, handlePlaceSelection}: DayRouteProps) { + const {isOpen, onOpen, onClose} = useDisclosure(); + const [placeCards, setPlaceCards] = useState(placeList); + + const findCard = useCallback( + (id: number) => { + const card = placeCards.filter((item) => item.id == id)[0]; + return { + card, + index: placeCards.indexOf(card), + }; + }, + [placeCards], + ); + + const moveCard = useCallback( + (id: number, atIndex: number) => { + const {card, index} = findCard(id); + setPlaceCards( + update(placeCards, { + $splice: [ + [index, 1], + [atIndex, 0, card], + ], + }), + ); + }, + [findCard, placeCards, setPlaceCards], + ); + + const [, drop] = useDrop(() => ({accept: 'CARD'})); + + useEffect(() => { + console.log(placeList); + }, [placeList]); -function DayRoute({ day, date, placeList }: DayRouteProps) { - const { isOpen, onOpen, onClose } = useDisclosure(); return ( <>
@@ -22,20 +59,26 @@ function DayRoute({ day, date, placeList }: DayRouteProps) { {date}
-
- {placeList.length ? ( - placeList.map((place, index) => ( - + {placeCards.length ? ( + placeCards.map((place) => ( + )) ) : ( diff --git a/src/components/Route/DraggablePlaceCard/DraggablePlaceCard.tsx b/src/components/Route/DraggablePlaceCard/DraggablePlaceCard.tsx new file mode 100644 index 00000000..b8c40b01 --- /dev/null +++ b/src/components/Route/DraggablePlaceCard/DraggablePlaceCard.tsx @@ -0,0 +1,88 @@ +import {useState} from 'react'; +import {useDrag, useDrop} from 'react-dnd'; +import {IoMdMenu as MoveIcon} from 'react-icons/io'; +import {RiCheckboxCircleFill as SelectedIcon} from 'react-icons/ri'; +import {RiCheckboxBlankCircleLine as UnselectedIcon} from 'react-icons/ri'; + +import styles from '../PlaceCard/PlaceCard.module.scss'; + +import {Item} from '@/types/route'; +import {DraggablePlaceCardProps} from '@/types/route'; + +function PlaceCard({ + id, + order, + name, + category, + address, + editMode, + onSelect, + moveCard, + findCard, +}: DraggablePlaceCardProps) { + const [isChecked, setIsChecked] = useState(false); + const originalIndex = findCard(id).index; + + const [, drag] = useDrag( + () => ({ + type: 'CARD', + item: {id, originalIndex}, + canDrag: () => !isChecked && editMode, + end: (item, monitor) => { + const {id: droppedId, originalIndex} = item; + const didDrop = monitor.didDrop(); + if (!didDrop) { + moveCard(droppedId, originalIndex); + } + }, + }), + [id, originalIndex, moveCard, editMode, isChecked], + ); + + const [, drop] = useDrop( + () => ({ + accept: 'CARD', + hover({id: draggedId}: Item) { + if (draggedId !== id) { + const {index: overIndex} = findCard(id); + moveCard(draggedId, overIndex); + } + }, + }), + [findCard, moveCard], + ); + + const handleSelect = () => { + setIsChecked(!isChecked); + // TODO: place id 넘기기 + onSelect(name); + }; + + return ( +
drag(drop(node))} className={styles.cardContainer}> + + +
+ {!editMode &&
{order}
} +
+ {editMode &&
{order}
} +
+

{name}

+

{category}

+

{address}

+
+
+
+
{editMode && }
+
+ ); +} + +export default PlaceCard; diff --git a/src/components/Route/PlaceCard/PlaceCard.module.scss b/src/components/Route/PlaceCard/PlaceCard.module.scss index c719c137..af83c47d 100644 --- a/src/components/Route/PlaceCard/PlaceCard.module.scss +++ b/src/components/Route/PlaceCard/PlaceCard.module.scss @@ -1,4 +1,4 @@ -@use "@/sass" as *; +@use '@/sass' as *; .cardContainer { display: flex; @@ -6,6 +6,14 @@ gap: 0.8rem; // padding: 16px 20px; + .placeInformationContainer { + display: flex; + gap: 0.8rem; + align-items: center; + width: 100%; + margin-left: 8px; + } + .numberContainer { @include typography(captionSmall); display: flex; @@ -16,30 +24,37 @@ background-color: $secondary400; border-radius: 50%; color: $neutral0; + margin-left: -8px; } .placeContainer { width: 100%; display: flex; - flex-direction: column; + align-items: center; + gap: 1.2rem; padding: 16px 20px; border-radius: 16px; background-color: $neutral0; box-shadow: $shadow200; - h1 { - @include typography(titleMedium); - color: $neutral900; - } + .placeInformation { + display: flex; + flex-direction: column; - h2 { - @include typography(captionSmall); - color: $primary200; - } + h1 { + @include typography(titleMedium); + color: $neutral900; + } + + h2 { + @include typography(captionSmall); + color: $primary200; + } - p { - @include typography(captionSmall); - color: $neutral400; + p { + @include typography(captionSmall); + color: $neutral400; + } } } } diff --git a/src/components/Route/PlaceCard/PlaceCard.tsx b/src/components/Route/PlaceCard/PlaceCard.tsx index ee6b84bc..1a78029d 100644 --- a/src/components/Route/PlaceCard/PlaceCard.tsx +++ b/src/components/Route/PlaceCard/PlaceCard.tsx @@ -1,15 +1,19 @@ -import styles from "./PlaceCard.module.scss"; +import styles from './PlaceCard.module.scss'; -import { PlaceCardProps } from "@/types/route"; +import {PlaceCardProps} from '@/types/route'; -function PlaceCard({ index, name, category, address }: PlaceCardProps) { +function PlaceCard({order, name, category, address}: PlaceCardProps) { return (
-
{index}
-
-

{name}

-

{category}

-

{address}

+
+
{order}
+
+
+

{name}

+

{category}

+

{address}

+
+
); diff --git a/src/components/Route/RouteTabPanel/RouteTabPanel.module.scss b/src/components/Route/RouteTabPanel/RouteTabPanel.module.scss index dea14daf..8f6feb87 100644 --- a/src/components/Route/RouteTabPanel/RouteTabPanel.module.scss +++ b/src/components/Route/RouteTabPanel/RouteTabPanel.module.scss @@ -14,13 +14,13 @@ min-width: 36rem; max-width: 45rem; height: 16rem; - } - .zoomInbutton { - position: absolute; - top: 16rem; - right: 2rem; - z-index: 10; + .zoomInButton { + position: absolute; + top: 11rem; + right: 2rem; + z-index: 10; + } } .routeContainer { @@ -31,4 +31,46 @@ padding-bottom: 12rem; } } + + .bottomButtonContainer, + .activeBottomButtonContainer { + position: fixed; + bottom: 0; + width: 100%; + min-width: 36rem; + max-width: 45rem; + z-index: 3; + transition: 0.5s; + + box-shadow: $shadow200; + + display: flex; + // height: 70px; + padding: 16px 20px; + justify-content: center; + align-items: center; + flex-shrink: 0; + + button { + width: 100%; + padding: 8px 0; + display: flex; + justify-content: center; + align-items: center; + gap: 0.4rem; + + span { + @include typography(button); + color: $neutral0; + } + } + } + + .bottomButtonContainer { + background-color: $neutral200; + } + + .activeBottomButtonContainer { + background-color: $primary300; + } } diff --git a/src/components/Route/RouteTabPanel/RouteTabPanel.tsx b/src/components/Route/RouteTabPanel/RouteTabPanel.tsx index 4115798d..8ae88540 100644 --- a/src/components/Route/RouteTabPanel/RouteTabPanel.tsx +++ b/src/components/Route/RouteTabPanel/RouteTabPanel.tsx @@ -1,10 +1,20 @@ +import {useDisclosure} from '@chakra-ui/react'; +import {useEffect, useState} from 'react'; +import {HiOutlineTrash as DeleteIcon} from 'react-icons/hi'; +import {RiArrowUpDownFill as MoveIcon} from 'react-icons/ri'; import {useNavigate} from 'react-router-dom'; +import {useSetRecoilState} from 'recoil'; import styles from './RouteTabPanel.module.scss'; +import AlertModal from '@/components/AlertModal/AlertModal'; +import BottomSlideLeft from '@/components/BottomSlide/BottomSlideLeft'; + import ZoomInIcon from '@/assets/icons/zoomIn.svg?react'; +import {isModalOpenState} from '@/recoil/vote/alertModal'; import {getSpaceId} from '@/utils/getSpaceId'; +import DayMove from '../DayMove/DayMove'; import DayNavigationBar from '../DayNavigationBar/DayNavigationBar'; import DayRoute from '../DayRoute/DayRoute'; import EmptyDate from '../EmptyDate/EmptyDate'; @@ -105,6 +115,33 @@ function RouteTabPanel({mapRef, center}: MapInTripProps) { ], }; + const [isEditMode, setIsEditMode] = useState(false); + const [selectedPlaces, setSelectedPlaces] = useState([]); + const {isOpen, onOpen, onClose} = useDisclosure(); + const setIsModalOpen = useSetRecoilState(isModalOpenState); + + const handleEditMode = () => { + setIsEditMode(!isEditMode); + + // TODO: 완료 버튼 눌렀을 때 처리 + }; + + const handlePlaceSelection = (placeName: string) => { + if (selectedPlaces.includes(placeName)) { + setSelectedPlaces((prevSelectedPlaces) => prevSelectedPlaces.filter((place) => place !== placeName)); + } else { + setSelectedPlaces((prevSelectedPlaces) => [...prevSelectedPlaces, placeName]); + } + }; + + const deletePlaces = (placeList: string[]) => { + console.log('삭제', placeList); + }; + + useEffect(() => { + console.log(selectedPlaces); + }, [selectedPlaces]); + const navigate = useNavigate(); const spaceId = getSpaceId(); @@ -120,19 +157,51 @@ function RouteTabPanel({mapRef, center}: MapInTripProps) {
+
-
- +
{data.journeys && data.journeys.map((journey, index) => ( - + ))}
+ {isEditMode && ( +
0 ? styles.activeBottomButtonContainer : styles.bottomButtonContainer}> + + +
+ )} + 0 && isOpen} + onClose={onClose} + children={} + /> + + deletePlaces(selectedPlaces)} + />
); } diff --git a/src/components/SearchFromHome/SearchBar/SearchBar.tsx b/src/components/SearchFromHome/SearchBar/SearchBar.tsx index 2fd6207a..378ff283 100644 --- a/src/components/SearchFromHome/SearchBar/SearchBar.tsx +++ b/src/components/SearchFromHome/SearchBar/SearchBar.tsx @@ -34,10 +34,9 @@ function SearchBar({forSearch, setForSearch}: PropsType) { } function search() { - const beforeData = forSearch; - beforeData.keyword = inputValue; - setForSearch(beforeData); - navigate(`/home/search?keyword=${inputValue}&category=0&map=false&location=${forSearch.location}&sort=등록순`); + navigate( + `/home/search?keyword=${inputValue}&category=0&map=false&location=${forSearch.location}&sort=등록순&hot=false`, + ); } function removeValue() { @@ -48,6 +47,7 @@ function SearchBar({forSearch, setForSearch}: PropsType) { setInputValue(''); const beforeData = forSearch; beforeData.keyword = ''; + beforeData.hot = 'false'; setForSearch(beforeData); } } diff --git a/src/components/SearchFromHome/SearchHome/HotItems/HotItem/HotItem.module.scss b/src/components/SearchFromHome/SearchHome/HotItems/HotItem/HotItem.module.scss index 8c2e3964..0121a469 100644 --- a/src/components/SearchFromHome/SearchHome/HotItems/HotItem/HotItem.module.scss +++ b/src/components/SearchFromHome/SearchHome/HotItems/HotItem/HotItem.module.scss @@ -1,4 +1,4 @@ -@use "@/sass" as *; +@use '@/sass' as *; .container { display: flex; @@ -7,9 +7,11 @@ img { min-width: 10.6rem; - min-height: 10.6rem; + height: 10.6rem; border-radius: 1.6rem; + + background-color: $neutral200; } .text_box { display: flex; diff --git a/src/components/SearchFromHome/SearchHome/HotItems/HotItem/HotItem.tsx b/src/components/SearchFromHome/SearchHome/HotItems/HotItem/HotItem.tsx index 531b6e4a..b8512538 100644 --- a/src/components/SearchFromHome/SearchHome/HotItems/HotItem/HotItem.tsx +++ b/src/components/SearchFromHome/SearchHome/HotItems/HotItem/HotItem.tsx @@ -1,25 +1,33 @@ -import { Link } from "react-router-dom"; +import {Link} from 'react-router-dom'; -import styles from "./HotItem.module.scss"; +import styles from './HotItem.module.scss'; -import areas from "@/utils/areas.json"; +import {translateCategoryToStr} from '@/hooks/Search/useSearch'; -import { SearchHotItemType } from "@/types/home"; +import nullImg from '@/assets/homeIcons/search/nullImg.svg'; +import areas from '@/utils/areas.json'; +import titleCaseChange from '@/utils/titleCaseChange'; + +import {SearchHotItemType} from '@/types/home'; interface PropsData { data: SearchHotItemType; } -function HotItem({ data }: PropsData) { - const location = areas.filter((area) => area.areaCode === data.areaCode)[0] - .name; +function HotItem({data}: PropsData) { + const location = areas.filter((area) => area.areaCode === data.areaCode)[0].name; + const category = translateCategoryToStr(data.contentTypeId); + const title = titleCaseChange(data.title); + const imgSrc = data.thumbnail ? data.thumbnail : nullImg; return ( - {`${data.title}의 + {`${data.title}의

- {data.title} - {location} + {title} + + {category} · {location} +

); diff --git a/src/components/SearchFromHome/SearchHome/HotItems/HotItems.tsx b/src/components/SearchFromHome/SearchHome/HotItems/HotItems.tsx index db7421be..f28f5d81 100644 --- a/src/components/SearchFromHome/SearchHome/HotItems/HotItems.tsx +++ b/src/components/SearchFromHome/SearchHome/HotItems/HotItems.tsx @@ -9,19 +9,19 @@ import SlideButton from '@/components/SlideButton/SlideButton'; import HotItem from './HotItem/HotItem'; -import {SearchHotItemType} from '@/types/home'; +import {Popular, SearchDataType, SearchHotItemType} from '@/types/home'; interface PropsType { type: number; } function HotItems({type}: PropsType) { - const [data, setData] = useState(); + const [data, setData] = useState(); const [slideLocation, setSlideLocation] = useState(0); const [componentRef, size] = useComponentSize(); useEffect(() => { - async function getData(apiURL: string, set: Dispatch>) { + async function getData(apiURL: string, set: Dispatch>) { try { const fetchData = await axios.get(`${apiURL}`, { params: { @@ -29,12 +29,14 @@ function HotItems({type}: PropsType) { placeTypeId: type, }, }); - set(fetchData.data); + const data: SearchDataType = fetchData.data; + + set(data.data.places); } catch (error) { console.log(error); } } - getData('api/places/popular', setData); + getData('/api/places/popular', setData); }, [type]); return (
diff --git a/src/components/SearchFromHome/SearchHome/SearchHome.tsx b/src/components/SearchFromHome/SearchHome/SearchHome.tsx index 0dcb6628..c98c1edc 100644 --- a/src/components/SearchFromHome/SearchHome/SearchHome.tsx +++ b/src/components/SearchFromHome/SearchHome/SearchHome.tsx @@ -3,16 +3,12 @@ import styles from './SearchHome.module.scss'; import HotItems from './HotItems/HotItems'; import SearchKeyword from './SearchKeyword/SearchKeyword'; -interface Propstype { - setKeywordClick: React.Dispatch>; -} - -function SearchHome({setKeywordClick}: Propstype) { +function SearchHome() { return (

인기 검색 키워드

- +

최근 30일간 인기 장소

diff --git a/src/components/SearchFromHome/SearchHome/SearchKeyword/SearchKeyword.tsx b/src/components/SearchFromHome/SearchHome/SearchKeyword/SearchKeyword.tsx index 9e28494c..dcb4b35e 100644 --- a/src/components/SearchFromHome/SearchHome/SearchKeyword/SearchKeyword.tsx +++ b/src/components/SearchFromHome/SearchHome/SearchKeyword/SearchKeyword.tsx @@ -1,4 +1,5 @@ -import {useEffect, useState} from 'react'; +import axios from 'axios'; +import {Dispatch, useEffect, useState} from 'react'; import {useNavigate} from 'react-router-dom'; import styles from './SearchKeyword.module.scss'; @@ -7,21 +8,26 @@ import useComponentSize from '@/hooks/useComponetSize'; import SlideButton from '@/components/SlideButton/SlideButton'; -import {getData} from '@/mocks/handlers/home'; +import {Keywords, SearchDataType, SearchKeywordType} from '@/types/home'; -interface Propstype { - setKeywordClick: React.Dispatch>; -} - -function SearchKeyword({setKeywordClick}: Propstype) { - const [data, setData] = useState<{name: string; code: string}[] | undefined>(); +function SearchKeyword() { + const [data, setData] = useState(); const [listWidth, setListWidth] = useState(0); const [slideLocation, setSlideLocation] = useState(0); const [componentRef, size] = useComponentSize(); const navigate = useNavigate(); useEffect(() => { - getData<{name: string; code: string}[] | undefined>('api/places/popular/keywords', setData); + async function getData(apiURL: string, set: Dispatch) { + try { + const fetchData = await axios.get(`${apiURL}`); + const data: SearchDataType = fetchData.data; + set(data.data.keywords); + } catch (error) { + console.log(error); + } + } + getData('/api/places/popular/keywords', setData); }, []); // 각 키워드의 너비를 모두 더한 값을 구함 @@ -66,11 +72,9 @@ function SearchKeyword({setKeywordClick}: Propstype) {

{ - setKeywordClick(true); - setTimeout(() => { - setKeywordClick(false); - }, 2000); - navigate(`/home/search?keyword=${keyword.name}&category=0&map=false&location=전국&sort=등록순`); + navigate( + `/home/search?keyword=${keyword.name}&category=0&map=false&location=전국&sort=등록순&hot=true`, + ); }} > {keyword.name} diff --git a/src/components/SearchFromHome/SearchList/DateFilter/DateFilter.module.scss b/src/components/SearchFromHome/SearchList/DateFilter/DateFilter.module.scss index e90ca855..8ecbb00c 100644 --- a/src/components/SearchFromHome/SearchList/DateFilter/DateFilter.module.scss +++ b/src/components/SearchFromHome/SearchList/DateFilter/DateFilter.module.scss @@ -1,6 +1,8 @@ -@use "@/sass" as *; +@use '@/sass' as *; .container { + position: relative; + width: 7.9rem; height: 2.4rem; @@ -17,4 +19,30 @@ width: 2.4rem; height: 2.4rem; } + + .modal { + position: absolute; + top: 32px; + right: 0; + + width: 10.8rem; + + display: flex; + flex-direction: column; + justify-content: space-between; + + padding: 20px 32px; + + border-radius: 16px; + + background-color: $neutral0; + + box-shadow: + 0px 1px 8px 2px rgba(20, 20, 20, 0.1), + 0px 0px 1px 0px rgba(20, 20, 20, 0.04); + + overflow: hidden; + + transition: 0.5s all; + } } diff --git a/src/components/SearchFromHome/SearchList/DateFilter/DateFilter.tsx b/src/components/SearchFromHome/SearchList/DateFilter/DateFilter.tsx index 3db7424d..e26c4bc0 100644 --- a/src/components/SearchFromHome/SearchList/DateFilter/DateFilter.tsx +++ b/src/components/SearchFromHome/SearchList/DateFilter/DateFilter.tsx @@ -1,12 +1,50 @@ -import { BsFilterLeft } from "react-icons/bs"; +import {useEffect, useState} from 'react'; +import {BsFilterLeft} from 'react-icons/bs'; +import {useNavigate} from 'react-router-dom'; -import styles from "./DateFilter.module.scss"; +import styles from './DateFilter.module.scss'; + +import {ForSearchType} from '@/types/home'; + +interface PropsType { + forSearch: ForSearchType; +} + +function DateFilter({forSearch}: PropsType) { + const [click, setClick] = useState(false); + const filterData = ['등록순', '이름순', '인기순']; + const navigate = useNavigate(); + + function handleModal() { + setClick((prev) => !prev); + } + + function selectSort(sort: string) { + navigate( + `/home/search?keyword=${forSearch.keyword}&category=${forSearch.category}&map=${forSearch.map}&location=${forSearch.location}&sort=${sort}&hot=${forSearch.hot}`, + ); + } + + useEffect(() => { + setClick(false); + }, [forSearch.sort]); -function DateFilter() { return ( -

+
- 등록순 + {forSearch.sort} +
+ {filterData.map((data) => ( + { + selectSort(data); + }} + > + {data} + + ))} +
); } diff --git a/src/components/SearchFromHome/SearchList/LocationFilter/LocationFliterPage/LocationFliterPage.tsx b/src/components/SearchFromHome/SearchList/LocationFilter/LocationFliterPage/LocationFliterPage.tsx index db343e43..b75da888 100644 --- a/src/components/SearchFromHome/SearchList/LocationFilter/LocationFliterPage/LocationFliterPage.tsx +++ b/src/components/SearchFromHome/SearchList/LocationFilter/LocationFliterPage/LocationFliterPage.tsx @@ -32,13 +32,18 @@ function LocationFliterPage({forSearch, click, handleClick}: PropsType) { useEffect(() => { const locationData = forSearch.location.split(' '); - setArea(locationData[0]); - setSigungu(locationData[1]); + if (locationData[0] === '전국') { + setArea('전국'); + setSigungu('전체 지역'); + } else { + setArea(locationData[0]); + setSigungu(locationData[1]); + } }, [forSearch.location]); function submit() { navigate( - `/home/search?keyword=${forSearch.keyword}&category=${forSearch.category}&map=${forSearch.map}&location=${area} ${sigungu}&sort=${forSearch.sort}`, + `/home/search?keyword=${forSearch.keyword}&category=${forSearch.category}&map=${forSearch.map}&location=${area} ${sigungu}&sort=${forSearch.sort}&hot=${forSearch.hot}`, ); handleClick(); } @@ -48,7 +53,7 @@ function LocationFliterPage({forSearch, click, handleClick}: PropsType) { className={styles.container} style={{ position: window.innerWidth > 450 ? 'absolute' : 'fixed', - top: window.innerWidth > 450 ? '-88px' : 0, + top: 0, right: click ? '-100%' : 0, height: `${vh * 100}px`, }} diff --git a/src/components/SearchFromHome/SearchList/Map/Map.tsx b/src/components/SearchFromHome/SearchList/Map/Map.tsx index 10e974fd..e43b5669 100644 --- a/src/components/SearchFromHome/SearchList/Map/Map.tsx +++ b/src/components/SearchFromHome/SearchList/Map/Map.tsx @@ -45,7 +45,7 @@ function Map({data, categoryChange}: PropsType) { const markerImage = new window.kakao.maps.MarkerImage(image, imageSize, imageOption); const marker = new window.kakao.maps.Marker({ - position: new window.kakao.maps.LatLng(data.location.latitude, data.location.longtitude), + position: new window.kakao.maps.LatLng(data.location.latitude, data.location.longitude), image: markerImage, }); return marker; @@ -121,6 +121,7 @@ function Map({data, categoryChange}: PropsType) { level: 4, }; setMap(new window.kakao.maps.Map(container, options)); + console.log(data); }, [data]); // 맵 생성 시 현재 데이터 좌표들의 바운드를 만들어 중심점 생성 @@ -130,7 +131,7 @@ function Map({data, categoryChange}: PropsType) { setSmallPin(data); setBigPin(data); data.map((data) => { - bounds.extend(new window.kakao.maps.LatLng(data.location.latitude, data.location.longtitude)); + bounds.extend(new window.kakao.maps.LatLng(data.location.latitude, data.location.longitude)); }); map.setBounds(bounds); } diff --git a/src/components/SearchFromHome/SearchList/Map/MapItems/MapItem/MapItem.tsx b/src/components/SearchFromHome/SearchList/Map/MapItems/MapItem/MapItem.tsx index 20b0c8ed..293e84d6 100644 --- a/src/components/SearchFromHome/SearchList/Map/MapItems/MapItem/MapItem.tsx +++ b/src/components/SearchFromHome/SearchList/Map/MapItems/MapItem/MapItem.tsx @@ -1,36 +1,26 @@ -import { Link } from "react-router-dom"; +import {Link} from 'react-router-dom'; -import styles from "./MapItem.module.scss"; +import styles from './MapItem.module.scss'; -import areas from "@/utils/areas.json"; +import {translateCategoryToStr} from '@/hooks/Search/useSearch'; -import { SearchItemType } from "@/types/home"; +import areas from '@/utils/areas.json'; + +import {SearchItemType} from '@/types/home'; interface PropsType { data: SearchItemType; categoryChange: boolean; } -function MapItem({ data, categoryChange }: PropsType) { - const location = areas.filter( - (area) => area.areaCode === data.location.areaCode, - )[0].name; - const category = - data.category === "관광지" || - data.category === "문화시설" || - data.category === "레포츠" || - data.category === "쇼핑" - ? "관광" - : data.category; +function MapItem({data, categoryChange}: PropsType) { + const location = areas.filter((area) => area.areaCode === data.location.areaCode)[0].name; + const category = translateCategoryToStr(data.contentTypeId); return ( - {`${data.title}의 -

+ {`${data.title}의 +

{data.title} {category}·{location} diff --git a/src/components/SearchFromHome/SearchList/Map/MapItems/MapItems.tsx b/src/components/SearchFromHome/SearchList/Map/MapItems/MapItems.tsx index 60940f27..ef32a661 100644 --- a/src/components/SearchFromHome/SearchList/Map/MapItems/MapItems.tsx +++ b/src/components/SearchFromHome/SearchList/Map/MapItems/MapItems.tsx @@ -1,14 +1,14 @@ -import { useEffect, useState } from "react"; +import {useEffect, useState} from 'react'; -import styles from "./MapItems.module.scss"; +import styles from './MapItems.module.scss'; -import useComponentSize from "@/hooks/useComponetSize"; +import useComponentSize from '@/hooks/useComponetSize'; -import SlideButton from "@/components/SlideButton/SlideButton"; +import SlideButton from '@/components/SlideButton/SlideButton'; -import MapItem from "./MapItem/MapItem"; +import MapItem from './MapItem/MapItem'; -import { SearchItemType } from "@/types/home"; +import {SearchItemType} from '@/types/home'; interface PropsType { data: SearchItemType[]; @@ -32,14 +32,12 @@ function MapItems({ const [throttle, setThrottle] = useState(true); function setCurrentIndex() { - const criterion = document.querySelector("#map_slide_container"); - const elements = document.querySelectorAll("#map_slide"); + const criterion = document.querySelector('#map_slide_container'); + const elements = document.querySelectorAll('#map_slide'); const childrenArray = Array.from(elements[0].children); for (const item of childrenArray) { - const currentLeft = - criterion && - item.getBoundingClientRect().x - criterion.getBoundingClientRect().x; + const currentLeft = criterion && item.getBoundingClientRect().x - criterion.getBoundingClientRect().x; if (currentLeft) { if (0 < currentLeft && currentLeft < 150) { const index = childrenArray.indexOf(item); @@ -65,10 +63,11 @@ function MapItems({ useEffect(() => { if (size.width < 449) { - componentRef.current?.scrollTo({ left: 0, behavior: "smooth" }); + componentRef.current?.scrollTo({left: 0, behavior: 'smooth'}); } else { setSlideLocation(0); } + console.log(data); }, [data, size, componentRef]); useEffect(() => { @@ -96,7 +95,7 @@ function MapItems({ }, [throttle]); return ( -

+
{data && ( - {data && - data.map((data, i) => ( - - ))} + {data && data.map((data, i) => )}
); diff --git a/src/components/SearchFromHome/SearchList/SearchItem/SearchItem.module.scss b/src/components/SearchFromHome/SearchList/SearchItem/SearchItem.module.scss index a5e0b57d..99ed157b 100644 --- a/src/components/SearchFromHome/SearchList/SearchItem/SearchItem.module.scss +++ b/src/components/SearchFromHome/SearchList/SearchItem/SearchItem.module.scss @@ -1,4 +1,4 @@ -@use "@/sass" as *; +@use '@/sass' as *; .container { width: 100%; @@ -18,6 +18,8 @@ opacity: 1; transition: 0.3s all linear; + + background-color: $neutral200; } .text { diff --git a/src/components/SearchFromHome/SearchList/SearchItem/SearchItem.tsx b/src/components/SearchFromHome/SearchList/SearchItem/SearchItem.tsx index 21c82a0d..30508ddd 100644 --- a/src/components/SearchFromHome/SearchList/SearchItem/SearchItem.tsx +++ b/src/components/SearchFromHome/SearchList/SearchItem/SearchItem.tsx @@ -1,37 +1,35 @@ -import { Link } from "react-router-dom"; +import {Link} from 'react-router-dom'; -import styles from "./SearchItem.module.scss"; +import styles from './SearchItem.module.scss'; -import areas from "@/utils/areas.json"; +import {translateCategoryToStr} from '@/hooks/Search/useSearch'; -import { SearchItemType } from "@/types/home"; +import nullImg from '@/assets/homeIcons/search/nullImg.svg'; +import areas from '@/utils/areas.json'; +import titleCaseChange from '@/utils/titleCaseChange'; + +import {SearchItemType} from '@/types/home'; interface PropsType { data: SearchItemType; categoryChange: boolean; } -function SearchItem({ data, categoryChange }: PropsType) { - const location = areas.filter( - (area) => area.areaCode === data.location.areaCode, - )[0].name; - const category = - data.category === "관광지" || - data.category === "문화시설" || - data.category === "레포츠" || - data.category === "쇼핑" - ? "관광" - : data.category; +function SearchItem({data, categoryChange}: PropsType) { + const title = titleCaseChange(data.title); + const location = areas.filter((area) => area.areaCode === data.location.areaCode)[0].name; + const category = translateCategoryToStr(data.contentTypeId); + const imgSrc = data.thumbnail ? data.thumbnail : nullImg; return ( {`${data.title}의 -

- {data.title} +

+ {title} {category}·{location} diff --git a/src/components/SearchFromHome/SearchList/SearchList.module.scss b/src/components/SearchFromHome/SearchList/SearchList.module.scss index 5836bbf5..6688c567 100644 --- a/src/components/SearchFromHome/SearchList/SearchList.module.scss +++ b/src/components/SearchFromHome/SearchList/SearchList.module.scss @@ -1,8 +1,6 @@ -@use "@/sass" as *; +@use '@/sass' as *; .container { - position: relative; - width: 100%; display: flex; diff --git a/src/components/SearchFromHome/SearchList/SearchList.tsx b/src/components/SearchFromHome/SearchList/SearchList.tsx index b27d2703..8de7683e 100644 --- a/src/components/SearchFromHome/SearchList/SearchList.tsx +++ b/src/components/SearchFromHome/SearchList/SearchList.tsx @@ -1,9 +1,9 @@ import {useEffect, useState} from 'react'; -import {useNavigate, useSearchParams} from 'react-router-dom'; +import {useNavigate} from 'react-router-dom'; import styles from './SearchList.module.scss'; -import {getData} from '@/mocks/handlers/home'; +import {keywordSearch, search} from '@/hooks/Search/useSearch'; import DateFilter from './DateFilter/DateFilter'; import LocationFilter from './LocationFilter/LocationFilter'; @@ -16,23 +16,21 @@ import {ForSearchType, SearchItemType} from '@/types/home'; interface PropsType { forSearch: ForSearchType; - keywordClick: boolean; } -function SearchList({forSearch, keywordClick}: PropsType) { - const [data, setData] = useState(); - const [filterData, setFilterData] = useState(); +function SearchList({forSearch}: PropsType) { + const [data, setData] = useState(); + const [filterData, setFilterData] = useState(); const [categoryChange, setCategoryChange] = useState(false); const navigate = useNavigate(); - const [searchParams] = useSearchParams(); useEffect(() => { - if (!keywordClick) { - getData('api/home/search/search', setData); + if (forSearch.hot === 'true') { + keywordSearch(forSearch.keyword, forSearch.location, forSearch.sort, setData); } else { - getData('api/home/search/search', setData); + search(forSearch.keyword, forSearch.location, forSearch.sort, setData); } - }, [searchParams]); + }, [forSearch.keyword, forSearch.location, forSearch.sort, forSearch.hot]); useEffect(() => { if (data) { @@ -52,20 +50,20 @@ function SearchList({forSearch, keywordClick}: PropsType) { function onMap() { navigate( - `/home/search?keyword=${forSearch.keyword}&category=${forSearch.category}&map=true&location=${forSearch.location}&sort=${forSearch.sort}`, + `/home/search?keyword=${forSearch.keyword}&category=${forSearch.category}&map=true&location=${forSearch.location}&sort=${forSearch.sort}&hot=${forSearch.hot}`, ); } return (

- + {forSearch.hot === 'false' && } {forSearch.map === 'true' && filterData ? ( ) : ( <>
- +
    {filterData && diff --git a/src/components/SearchFromHome/SearchList/Tabs/Tab/Tab.tsx b/src/components/SearchFromHome/SearchList/Tabs/Tab/Tab.tsx index f0fdbbdf..5e0a150d 100644 --- a/src/components/SearchFromHome/SearchList/Tabs/Tab/Tab.tsx +++ b/src/components/SearchFromHome/SearchList/Tabs/Tab/Tab.tsx @@ -21,7 +21,7 @@ function Tab({forSearch, thisCategory, setCategoryChange}: PropsType) { setCategoryChange(false); }, 150); navigate( - `/home/search?keyword=${forSearch.keyword}&category=${key}&map=${forSearch.map}&location=${forSearch.location}&sort=${forSearch.sort}`, + `/home/search?keyword=${forSearch.keyword}&category=${key}&map=${forSearch.map}&location=${forSearch.location}&sort=${forSearch.sort}&hot=${forSearch.hot}`, ); } diff --git a/src/components/SideBar/TravelList/TravelList.module.scss b/src/components/SideBar/TravelList/TravelList.module.scss index 9c94acd9..6b761df7 100644 --- a/src/components/SideBar/TravelList/TravelList.module.scss +++ b/src/components/SideBar/TravelList/TravelList.module.scss @@ -1,4 +1,4 @@ -@use "@/sass" as *; +@use '@/sass' as *; .travelSpaceList { display: flex; @@ -14,8 +14,12 @@ padding: 24px 16px; align-items: center; border-radius: 16px; - border: 1px solid $primary200; - background: $primary200; + background: linear-gradient( + 129deg, + #267dff 0.29%, + rgba(98, 170, 255, 0.95) 50.64%, + rgba(142, 255, 144, 0.88) 169.29% + ); &__icon { color: $primary200; @@ -47,9 +51,9 @@ &__item { display: flex; flex-direction: column; + gap: 2px; width: 24.3rem; - height: 7.8rem; - padding: 16px 0px 16px 16px; + padding: 12px 16px; border-radius: 16px; border: 1px solid $primary200; background: $neutral0; @@ -61,9 +65,14 @@ } &__date { + color: $neutral700; + letter-spacing: -0.12px; + @include typography(captionSmall); + } + + &__members { color: $neutral400; - font-style: normal; - letter-spacing: -0.012rem; + letter-spacing: -0.12px; @include typography(captionSmall); } } diff --git a/src/components/SideBar/TravelList/TravelList.tsx b/src/components/SideBar/TravelList/TravelList.tsx index 34aa7f23..01c12d7f 100644 --- a/src/components/SideBar/TravelList/TravelList.tsx +++ b/src/components/SideBar/TravelList/TravelList.tsx @@ -1,25 +1,23 @@ -import { GoPlus } from "react-icons/go"; -import { useNavigate } from "react-router-dom"; +import {GoPlus} from 'react-icons/go'; +import {useNavigate} from 'react-router-dom'; -import styles from "./TravelList.module.scss"; +import styles from './TravelList.module.scss'; -import { useGetSpaces, usePostSpace } from "@/hooks/Spaces/useSpaces"; +import {useGetSpaces, usePostSpace} from '@/hooks/Spaces/useSpaces'; -interface TravelListProps { - isSideOpen: boolean; -} +import {TravelListProp} from '@/types/sidebar'; -function TravelList({ isSideOpen }: TravelListProps) { - const { mutate } = usePostSpace(); +function TravelList({isSideOpen}: TravelListProp) { + const {mutate} = usePostSpace(); const navigate = useNavigate(); - const { data: spaces } = useGetSpaces(isSideOpen); + const {data: spaces} = useGetSpaces(isSideOpen); const handlePostSpace = (e: React.MouseEvent) => { e.preventDefault(); if (spaces!.length >= 15) { - navigate("/mypage?full=true"); + navigate('/user?full=true'); } else { mutate(); } @@ -28,32 +26,22 @@ function TravelList({ isSideOpen }: TravelListProps) { if (!spaces) { return
    로딩 중...
    ; } + return (
    -
      {spaces.map((item, index) => (
    • -
    • ))} diff --git a/src/components/TripSpace/FriendList/FriendList.tsx b/src/components/TripSpace/FriendList/FriendList.tsx index 604b2461..843c1de1 100644 --- a/src/components/TripSpace/FriendList/FriendList.tsx +++ b/src/components/TripSpace/FriendList/FriendList.tsx @@ -1,18 +1,16 @@ -import { Avatar } from "@chakra-ui/react"; +import {Avatar} from '@chakra-ui/react'; -import styles from "./FriendList.module.scss"; +import styles from './FriendList.module.scss'; -import { FriendListProps } from "@/types/FriendList"; -function FriendList({ users }: FriendListProps) { +import {FriendListProps} from '@/types/friendList'; +function FriendList({users}: FriendListProps) { return (
      -

      - 여행을 함께하는 친구 {users.length}명 -

      +

      여행을 함께하는 친구 {users.length}명

      {users.map((user, index) => (
      - +

      {user.name}

      ))} diff --git a/src/components/TripSpace/InviteFriends/InviteFriends.tsx b/src/components/TripSpace/InviteFriends/InviteFriends.tsx index 0d6c5c15..b53ae67a 100644 --- a/src/components/TripSpace/InviteFriends/InviteFriends.tsx +++ b/src/components/TripSpace/InviteFriends/InviteFriends.tsx @@ -1,30 +1,28 @@ -import { useEffect, useState } from "react"; +import {useEffect, useState} from 'react'; -import styles from "./InviteFriends.module.scss"; +import styles from './InviteFriends.module.scss'; -import { inviteCodeRequest } from "@/hooks/Invite/useInviteCode"; -import useKakaoShareButton from "@/hooks/useKakaoShareButton"; -import { useGetMyInfo } from "@/hooks/User/useUser"; +import useKakaoShareButton from '@/hooks/useKakaoShareButton'; -import CopyClipboard from "@/components/Modal/CopyClipboard/CopyClipboard"; +import CopyClipboard from '@/components/Modal/CopyClipboard/CopyClipboard'; -import InviteKakao from "@/assets/inviteKakao.svg?react"; -import InviteLink from "@/assets/inviteLink.svg?react"; -import InviteLogo from "@/assets/InviteLogo.svg?react"; +import {postJoinSpaces} from '@/api/invite'; +import InviteKakao from '@/assets/invite/inviteKakao.svg?react'; +import InviteLink from '@/assets/invite/inviteLink.svg?react'; +import InviteLogo from '@/assets/invite/InviteLogo.svg?react'; -import { InviteFriendsProps } from "@/types/inviteFriends"; +import {InviteFriendsProps} from '@/types/inviteFriends'; -function InviteFriends({ isOpen }: InviteFriendsProps) { +function InviteFriends({isOpen}: InviteFriendsProps) { const [isModalVisible, setIsModalVisible] = useState(false); - const [code, setCode] = useState(""); - const spaceId = window.location.pathname.split("/"); - const { data: myInfo } = useGetMyInfo(isOpen); + const [code, setCode] = useState(''); + const spaceId = window.location.pathname.split('/'); useEffect(() => { const fetchInviteCode = async () => { - if (myInfo && isOpen) { + if (isOpen) { try { - const response = await inviteCodeRequest(myInfo.nickname, spaceId[2]); + const response = await postJoinSpaces({spaceId: spaceId[2]}); setCode(response.code); } catch (error) { console.error(error); @@ -33,12 +31,10 @@ function InviteFriends({ isOpen }: InviteFriendsProps) { }; fetchInviteCode(); - }, [myInfo, isOpen]); + }, [isOpen]); const handleCopyClick = (code: string) => () => { - navigator.clipboard.writeText( - `https://api.tripvote.site/members/join/spaces/${code}`, - ); + navigator.clipboard.writeText(`https://api.tripvote.site/auth/join/spaces/${spaceId[2]}/token?&code=${code}`); setIsModalVisible(true); setTimeout(() => { setIsModalVisible(false); @@ -50,9 +46,7 @@ function InviteFriends({ isOpen }: InviteFriendsProps) {
      -

      - 친구를 초대해보세요 -

      +

      친구를 초대해보세요

      함께 여행 갈 친구나 가족을 초대해보세요
      @@ -60,7 +54,7 @@ function InviteFriends({ isOpen }: InviteFriendsProps) {

      - diff --git a/src/components/Vote/VoteBottomSlideContent/AddToJourney/AddToJourney.module.scss b/src/components/Vote/VoteBottomSlideContent/AddToJourney/AddToJourney.module.scss new file mode 100644 index 00000000..a9033aa2 --- /dev/null +++ b/src/components/Vote/VoteBottomSlideContent/AddToJourney/AddToJourney.module.scss @@ -0,0 +1,21 @@ +@use '@/sass' as *; + +.container { + display: flex; + flex-direction: column; + + width: 100%; + // margin: 15px 0; + padding: 15px 8px 50px; + + // background-color: aqua; + // z-index: 102; +} + +.dayBox { + @include typography(bodyLarge); + padding: 12px 0; + + display: flex; + justify-content: space-between; +} diff --git a/src/components/Vote/VoteBottomSlideContent/AddToJourney/AddToJourney.tsx b/src/components/Vote/VoteBottomSlideContent/AddToJourney/AddToJourney.tsx new file mode 100644 index 00000000..4052bd16 --- /dev/null +++ b/src/components/Vote/VoteBottomSlideContent/AddToJourney/AddToJourney.tsx @@ -0,0 +1,21 @@ +import {Button, Checkbox} from '@chakra-ui/react'; + +import styles from './AddToJourney.module.scss'; + +const AddToJourney = () => { + const days = [1, 2, 3]; + + return ( +
      + {days.map((day, i) => ( + + ))} + +
      + ); +}; + +export default AddToJourney; diff --git a/src/components/Vote/VoteBottomSlideContent/VoteMeatball/VoteMeatball.tsx b/src/components/Vote/VoteBottomSlideContent/VoteMeatball/VoteMeatball.tsx index 59c78950..d1d0ea4a 100644 --- a/src/components/Vote/VoteBottomSlideContent/VoteMeatball/VoteMeatball.tsx +++ b/src/components/Vote/VoteBottomSlideContent/VoteMeatball/VoteMeatball.tsx @@ -21,7 +21,7 @@ import CreateVoteModal from '../../CreateVoteModal/CreateVoteModal'; import {AlertModalProps, VoteMeatballProps} from '@/types/vote'; -const VoteMeatball = ({state, title, isZeroCandidates}: VoteMeatballProps) => { +const VoteMeatball = ({state, title, isZeroCandidates, allCandidatesNotVoted}: VoteMeatballProps) => { const {id: voteId} = useParams(); const setIsCreateModalOpen = useSetRecoilState(isCreateModalOpenState); const setIsBTOpen = useSetRecoilState(isBottomSlideOpenState); @@ -65,7 +65,7 @@ const VoteMeatball = ({state, title, isZeroCandidates}: VoteMeatballProps) => { ) : ( - diff --git a/src/components/Vote/VoteContent/CandidateCard/CandidateCard.module.scss b/src/components/Vote/VoteContent/CandidateCard/CandidateCard.module.scss index 0cc00386..3171b07f 100644 --- a/src/components/Vote/VoteContent/CandidateCard/CandidateCard.module.scss +++ b/src/components/Vote/VoteContent/CandidateCard/CandidateCard.module.scss @@ -69,6 +69,7 @@ // gap: 8px; &__name { + text-align: start; @include typography(titleSmall); color: $neutral900; } @@ -110,3 +111,9 @@ } } } + +.isCandidateSelecting { + * { + color: $neutral300; + } +} diff --git a/src/components/Vote/VoteContent/CandidateCard/CandidateCard.tsx b/src/components/Vote/VoteContent/CandidateCard/CandidateCard.tsx index 2e6ace77..36cec270 100644 --- a/src/components/Vote/VoteContent/CandidateCard/CandidateCard.tsx +++ b/src/components/Vote/VoteContent/CandidateCard/CandidateCard.tsx @@ -1,6 +1,5 @@ import {useState} from 'react'; import {FaRegStar, FaStar} from 'react-icons/fa'; -import {Link} from 'react-router-dom'; import {useRecoilValue} from 'recoil'; import styles from './CandidateCard.module.scss'; @@ -11,6 +10,7 @@ import ThirdIcon from '@/assets/voteIcons/rank_3.svg?react'; import AddDayIcon from '@/assets/voteIcons/vote_addDay.svg?react'; import {isCandidateSelectingState} from '@/recoil/vote/alertModal'; +import AddToJourney from '../../VoteBottomSlideContent/AddToJourney/AddToJourney'; import VotedUserList from '../../VoteBottomSlideContent/VotedUserList/VotedUserList'; import {CandidateCardProps} from '@/types/vote'; @@ -18,7 +18,8 @@ import {CandidateCardProps} from '@/types/vote'; const CandidateCard = ({onBottomSlideOpen, candidate, showResults, index, isMapStyle}: CandidateCardProps) => { const [isVoted, setIsVoted] = useState(false); const isCandidateSelecting = useRecoilValue(isCandidateSelectingState); - const voteCounts = candidate.voteUserId.length; + + const placeInfo = candidate.placeInfo; const getRankClassName = (index: number) => { switch (index) { @@ -50,7 +51,7 @@ const CandidateCard = ({onBottomSlideOpen, candidate, showResults, index, isMapS const RankIcon = showResults && getRankIcon(index); const voteStarIcon = () => { - if (isVoted) return ; + if (isVoted || candidate.amIVoted) return ; else if (isMapStyle) return ; else return ; }; @@ -65,7 +66,7 @@ const CandidateCard = ({onBottomSlideOpen, candidate, showResults, index, isMapS return (
      - {candidate.placeName} + {placeInfo.placeName} {RankIcon && (
      @@ -74,14 +75,18 @@ const CandidateCard = ({onBottomSlideOpen, candidate, showResults, index, isMapS
      - - {candidate.placeName} {'>'} - +
      - {candidate.category} + {placeInfo.category} {'ꞏ'} - {candidate.location} + {placeInfo.location}
      {/* 일정 담기 @@ -90,15 +95,19 @@ const CandidateCard = ({onBottomSlideOpen, candidate, showResults, index, isMapS 있 : 바텀시트 -> 일정 추가api -> 시트close, 완료 토스트 */} - {showResults && ( - )}
      -
      diff --git a/src/components/Vote/VoteContent/CandidateList/CandidateList.module.scss b/src/components/Vote/VoteContent/CandidateList/CandidateList.module.scss index c5ff580d..342c668b 100644 --- a/src/components/Vote/VoteContent/CandidateList/CandidateList.module.scss +++ b/src/components/Vote/VoteContent/CandidateList/CandidateList.module.scss @@ -1,4 +1,4 @@ -@use "@/sass" as *; +@use '@/sass' as *; .container { margin-bottom: 50px; @@ -18,7 +18,7 @@ &__memo { display: flex; align-items: center; - gap: 6px; + gap: 8px; margin-top: 16px; &__text { diff --git a/src/components/Vote/VoteContent/CandidateList/CandidateList.tsx b/src/components/Vote/VoteContent/CandidateList/CandidateList.tsx index 9a8f01dc..4f786258 100644 --- a/src/components/Vote/VoteContent/CandidateList/CandidateList.tsx +++ b/src/components/Vote/VoteContent/CandidateList/CandidateList.tsx @@ -1,8 +1,11 @@ import {Avatar, Checkbox} from '@chakra-ui/react'; +import {useSetRecoilState} from 'recoil'; import styles from './CandidateList.module.scss'; -import useGetSelectedCandidates from '@/hooks/useGetSelectedCandidates'; +import useGetSelectedSet from '@/hooks/useGetSelectedSet'; + +import {selectedCandidatesState} from '@/recoil/vote/candidateList'; import CandidateCard from '../CandidateCard/CandidateCard'; import VoteContentEmpty from '../VoteContentEmpty/VoteContentEmpty'; @@ -10,7 +13,8 @@ import VoteContentEmpty from '../VoteContentEmpty/VoteContentEmpty'; import {CandidateListProps} from '@/types/vote'; const CandidateList = ({candidates, onBottomSlideOpen, showResults, isCandidateSelecting}: CandidateListProps) => { - const {addCandidateInSelectedList} = useGetSelectedCandidates(); + const setSelectedCandidates = useSetRecoilState(selectedCandidatesState); + const {addItemInNewSet} = useGetSelectedSet(setSelectedCandidates); return (
      @@ -24,7 +28,7 @@ const CandidateList = ({candidates, onBottomSlideOpen, showResults, isCandidateS fontSize='2rem' id={`${i}checkbox`} variant='candidateCheckbox' - onChange={() => addCandidateInSelectedList(candidate.id)} + onChange={() => addItemInNewSet(candidate.id)} /> )}
      diff --git a/src/components/Vote/VoteContent/VoteContent.module.scss b/src/components/Vote/VoteContent/VoteContent.module.scss index 64459e1d..5541e0b4 100644 --- a/src/components/Vote/VoteContent/VoteContent.module.scss +++ b/src/components/Vote/VoteContent/VoteContent.module.scss @@ -1,4 +1,4 @@ -@use "@/sass" as *; +@use '@/sass' as *; .container { width: 100%; @@ -26,8 +26,12 @@ } } &__addCandidate { - @include typography(button); + @include typography(captionSmall); color: $neutral900; + + &:disabled { + color: $neutral300; + } } } } diff --git a/src/components/Vote/VoteContent/VoteContent.tsx b/src/components/Vote/VoteContent/VoteContent.tsx index 6de5fc6f..4059ae90 100644 --- a/src/components/Vote/VoteContent/VoteContent.tsx +++ b/src/components/Vote/VoteContent/VoteContent.tsx @@ -14,7 +14,7 @@ import AddCandidate from '../VoteBottomSlideContent/AddCandidate/AddCandidate'; import {VoteContentProps} from '@/types/vote'; -const VoteContent = ({onBottomSlideOpen, data, showResults}: VoteContentProps) => { +const VoteContent = ({onBottomSlideOpen, data, isZeroCandidates, showResults}: VoteContentProps) => { const candidates = data.candidates; const isCandidateSelecting = useRecoilValue(isCandidateSelectingState); @@ -32,12 +32,15 @@ const VoteContent = ({onBottomSlideOpen, data, showResults}: VoteContentProps) = {data.voteStatus}
      - + {!showResults && ( + + )}
      - {/* 후보지&여행지 X -> 상품 추천 없음 */} - + + {!isZeroCandidates && } + {isCandidateSelecting && }
      ); diff --git a/src/components/Vote/VoteContent/VoteRecommendList/VoteRecommendList.module.scss b/src/components/Vote/VoteContent/VoteRecommendList/VoteRecommendList.module.scss index 2780bd0a..2cf284eb 100644 --- a/src/components/Vote/VoteContent/VoteRecommendList/VoteRecommendList.module.scss +++ b/src/components/Vote/VoteContent/VoteRecommendList/VoteRecommendList.module.scss @@ -1,7 +1,8 @@ -@use "@/sass" as *; +@use '@/sass' as *; .container { margin-bottom: 70px; + position: relative; &__title { @include typography(titleLarge); @@ -9,3 +10,11 @@ margin-bottom: 15px; } } + +.dimmedOverlay { + position: absolute; + width: 100%; + height: 30rem; + background: #ffffff80; + z-index: 3; +} diff --git a/src/components/Vote/VoteContent/VoteRecommendList/VoteRecommendList.tsx b/src/components/Vote/VoteContent/VoteRecommendList/VoteRecommendList.tsx index 0cf42f7c..dbbb66a1 100644 --- a/src/components/Vote/VoteContent/VoteRecommendList/VoteRecommendList.tsx +++ b/src/components/Vote/VoteContent/VoteRecommendList/VoteRecommendList.tsx @@ -1,25 +1,21 @@ -import { Navigation } from "swiper/modules"; -import { Swiper, SwiperSlide } from "swiper/react"; -import "swiper/scss"; -import "swiper/scss/navigation"; +import {Navigation} from 'swiper/modules'; +import {Swiper, SwiperSlide} from 'swiper/react'; +import 'swiper/scss'; +import 'swiper/scss/navigation'; -import styles from "./VoteRecommendList.module.scss"; +import styles from './VoteRecommendList.module.scss'; -import VoteRecommendItem from "./VoteRecommendItem/VoteRecommendItem"; +import VoteRecommendItem from './VoteRecommendItem/VoteRecommendItem'; // 후보지&여행지 X -> 상품 추천 없음 -const VoteRecommendList = ({ state }: { state: string }) => { +const VoteRecommendList = ({state, isCandidateSelecting}: {state: string; isCandidateSelecting: boolean}) => { return (
      + {isCandidateSelecting &&
      }

      이런 카페는 어때요?

      - + diff --git a/src/components/Vote/VoteHeader/VoteHeader.module.scss b/src/components/Vote/VoteHeader/VoteHeader.module.scss index 7134ae13..f8032a67 100644 --- a/src/components/Vote/VoteHeader/VoteHeader.module.scss +++ b/src/components/Vote/VoteHeader/VoteHeader.module.scss @@ -1,4 +1,4 @@ -@use "@/sass" as *; +@use '@/sass' as *; .container { width: 100%; @@ -42,4 +42,8 @@ gap: 12px; font-size: 2.2rem; + + & > *:disabled { + color: $neutral300; + } } diff --git a/src/components/Vote/VoteHeader/VoteHeader.tsx b/src/components/Vote/VoteHeader/VoteHeader.tsx index bc7efeb7..52dd3a63 100644 --- a/src/components/Vote/VoteHeader/VoteHeader.tsx +++ b/src/components/Vote/VoteHeader/VoteHeader.tsx @@ -3,15 +3,19 @@ import {BsThreeDots} from 'react-icons/bs'; import {MdOutlineArrowBackIosNew} from 'react-icons/md'; import {RiMap2Line} from 'react-icons/ri'; import {useLocation, useNavigate} from 'react-router-dom'; +import {useRecoilValue} from 'recoil'; import styles from './VoteHeader.module.scss'; +import {isCandidateSelectingState} from '@/recoil/vote/alertModal'; + import {VoteHeaderProps} from '@/types/vote'; -const VoteHeader = ({onBottomSlideOpen, title, isNoCandidate}: VoteHeaderProps) => { +const VoteHeader = ({onBottomSlideOpen, title, isZeroCandidates}: VoteHeaderProps) => { const navigate = useNavigate(); const location = useLocation(); const path = location.pathname.split('/')[3]; + const isCandidateSelecting = useRecoilValue(isCandidateSelectingState); const setRightIcons = (path: string) => { switch (path) { @@ -26,10 +30,13 @@ const VoteHeader = ({onBottomSlideOpen, title, isNoCandidate}: VoteHeaderProps) default: return ( <> - - diff --git a/src/components/VoteMemo/MemoContent.tsx b/src/components/VoteMemo/MemoContent.tsx index ad8629cc..bd9c68cf 100644 --- a/src/components/VoteMemo/MemoContent.tsx +++ b/src/components/VoteMemo/MemoContent.tsx @@ -1,30 +1,24 @@ -import {useEffect} from 'react'; -import {useSetRecoilState} from 'recoil'; - import styles from './MemoContent.module.scss'; -import {selectedCandidatesState} from '@/recoil/vote/candidateList'; - import MemoItem from './MemoItem/MemoItem'; -import {VoteInfo} from '@/types/vote'; +import {TaglineType, VoteInfo} from '@/types/vote'; const MemoContent = ({data}: {data: VoteInfo}) => { - const setSelectedCandidates = useSetRecoilState(selectedCandidatesState); const candidates = data.candidates; - const CheckAllCandidates = () => { - const newCandidateIds = candidates.map((candidate) => candidate.id); - setSelectedCandidates(new Set(newCandidateIds)); - }; - - useEffect(() => { - CheckAllCandidates(); - }, []); + const getExistingTaglines = localStorage.getItem('recoil-persist'); + const existingTaglines: TaglineType[] = getExistingTaglines && JSON.parse(getExistingTaglines).selectedTaglineState; return (
      - {candidates?.map((candidate) => )} + {candidates?.map((candidate) => ( + tagline.placeId === candidate.placeInfo.placeId)} + /> + ))}
      ); }; diff --git a/src/components/VoteMemo/MemoItem/MemoItem.tsx b/src/components/VoteMemo/MemoItem/MemoItem.tsx index 798e8d31..1ce907ba 100644 --- a/src/components/VoteMemo/MemoItem/MemoItem.tsx +++ b/src/components/VoteMemo/MemoItem/MemoItem.tsx @@ -1,49 +1,79 @@ import {Checkbox} from '@chakra-ui/react'; -import {useState} from 'react'; +import {useCallback, useEffect, useState} from 'react'; import styles from './MemoItem.module.scss'; -import useGetSelectedCandidates from '@/hooks/useGetSelectedCandidates'; +import {useDebounce} from '@/hooks/useDebounce'; +import useGetSelectedArray from '@/hooks/useGetSelectedArray'; -import {CandidatesInfo} from '@/types/vote'; +import {selectedTaglineState} from '@/recoil/vote/voteMemo'; -const MemoItem = ({candidate}: {candidate: CandidatesInfo}) => { - const [text, setText] = useState(0); +import {MemoItemProps} from '@/types/vote'; - const {addCandidateInSelectedList} = useGetSelectedCandidates(); +const MemoItem = ({candidate, existingTagline}: MemoItemProps) => { + const [text, setText] = useState(''); + // const [selectedTagline, setSelectedTagline] = useRecoilState(selectedTaglineState); + const {toggleItemInNewArray, setMemoArray} = useGetSelectedArray(selectedTaglineState); + const debouncedText = useDebounce(text, 500); + const placeInfo = candidate.placeInfo; + + useEffect(() => { + if (existingTagline) { + setText(existingTagline.tagline); + } + }, []); + + const handleCheckboxChange = () => { + toggleItemInNewArray({ + placeId: placeInfo.placeId, + tagline: debouncedText, + }); + }; + + const handleDebouncedTextChange = useCallback(() => { + setMemoArray({ + placeId: placeInfo.placeId, + tagline: debouncedText, + }); + }, [debouncedText, placeInfo.placeId, setMemoArray]); + + useEffect(() => { + handleDebouncedTextChange(); + }, [debouncedText]); return (
      addCandidateInSelectedList(candidate.id)} + onChange={handleCheckboxChange} />
      -
      +
      +