From c9992f56d40fb13da9624b2c46ac7eec52d34198 Mon Sep 17 00:00:00 2001 From: jiwon Date: Tue, 16 Apr 2024 15:55:29 +0900 Subject: [PATCH 01/17] =?UTF-8?q?[FE]=20HOTFIX:=20=EC=96=B4=EB=93=9C?= =?UTF-8?q?=EB=AF=BC=20=EC=8A=AC=EB=9E=99=20=EC=95=8C=EB=A6=BC=20=EC=9D=B4?= =?UTF-8?q?=EB=AA=A8=ED=8B=B0=EC=BD=98=20=EC=98=A4=EB=A5=98=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/Cabinet/assets/data/SlackAlarm.ts | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/frontend/src/Cabinet/assets/data/SlackAlarm.ts b/frontend/src/Cabinet/assets/data/SlackAlarm.ts index b3e586e40..c971b2504 100644 --- a/frontend/src/Cabinet/assets/data/SlackAlarm.ts +++ b/frontend/src/Cabinet/assets/data/SlackAlarm.ts @@ -39,7 +39,7 @@ export const SlackAlarmTemplates: ISlackAlarmTemplate[] = [ 안녕하세요. Cabi 팀입니다! :happy_ccabi: 현시간부로 서비스 이용이 정상화되었습니다. :portal_blue_parrot: 서비스는 cabi.42seoul.io 를 이용해주시면 됩니다. :portal_orange_parrot: - :파일_수납장: 서비스 개선과 관련한 사항은 Cabi 채널 문의주세요! :파일_수납장: + :file_cabinet: 서비스 개선과 관련한 사항은 Cabi 채널 문의주세요! :file_cabinet: :party-dinosaur::party-dinosaur::party-dinosaur::party-dinosaur:기다려주셔서 감사합니다! :party-dinosaur::party-dinosaur::party-dinosaur::party-dinosaur:`, }, { @@ -53,23 +53,23 @@ export const SlackAlarmTemplates: ISlackAlarmTemplate[] = [ }, { title: "이용 안내서", - content: `:파일_수납장: Cabi 이용 안내서 :파일_수납장: + content: `:file_cabinet: Cabi 이용 안내서 :file_cabinet: :embarrassed_cabi: 42seoul의 사물함 대여 서비스를 운영중인 Cabi 팀입니다.:embarrassed_cabi: 자세한 이용 방법은 Cabi 가입 후 홈페이지의 이용 안내서를 참고해 주세요! - :오른쪽을_가리키는_손_모양: https://cabi.42seoul.io/home + :point_right: https://cabi.42seoul.io/home :alert: Cabi FAQ :alert: - :압정: 사물함의 물리적인 문제가 있습니다 (고장 났거나 잠겨있는 경우) + :pushpin: 사물함의 물리적인 문제가 있습니다 (고장 났거나 잠겨있는 경우) :happy_ccabi: 사물함의 물리적인 문제는 데스크에 문의 부탁드립니다! - :압정: 사물함 비밀번호를 모릅니다 (잊어버렸습니다). + :pushpin: 사물함 비밀번호를 모릅니다 (잊어버렸습니다). :happy_ccabi: 저희 서비스에서 대여한 화면과 슬랙 화면을 준비해서 데스크에 문의해주시기 바랍니다! - :압정: 사물함을 닫으려는데 빨간 열쇠 표시가 뜨면서 경고음이 나고 잠기지 않습니다. + :pushpin: 사물함을 닫으려는데 빨간 열쇠 표시가 뜨면서 경고음이 나고 잠기지 않습니다. :happy_ccabi: 사물함 안이 꽉 차거나 제대로 닫히지 않은 경우에 발생하는데, 문을 누른 상태로 비밀번호를 입력해 보시고, 그래도 되지 않는다면 데스크에 문의 부탁드립니다! - :압정: 사물함 대여 후 사용하려고 했더니 안에 짐이 가득 차 있습니다. + :pushpin: 사물함 대여 후 사용하려고 했더니 안에 짐이 가득 차 있습니다. :happy_ccabi: 이전 사용자의 짐과 관련한 문의는 데스크에 문의 부탁드립니다! - :압정: 공유 사물함을 대여했는데 비밀번호는 어디서 알 수 있을까요? + :pushpin: 공유 사물함을 대여했는데 비밀번호는 어디서 알 수 있을까요? :happy_ccabi: 같이 사용하는 사람이 있다면 대여 내역에서 공유 메모에 적혀 있을 수 있습니다. 또는 함께 사용하는 분에게 여쭤보세요! - :압정: 사물함을 연체 했는데 패널티는 무엇인가요? - :happy_ccabi: 연체일만큼 누적 연체일이 증가하고, 누적일 만큼 대여가 불가능합니다:울음을_참는_얼굴:`, + :pushpin: 사물함을 연체 했는데 패널티는 무엇인가요? + :happy_ccabi: 연체일만큼 누적 연체일이 증가하고, 누적일 만큼 대여가 불가능합니다:face_holding_back_tears:`, }, { title: "모집 공고", @@ -88,7 +88,7 @@ export const SlackAlarmTemplates: ISlackAlarmTemplate[] = [ :four_leaf_clover::four_leaf_clover::four_leaf_clover::four_leaf_clover::four_leaf_clover::four_leaf_clover::four_leaf_clover::four_leaf_clover::four_leaf_clover: :four_leaf_clover::arrow_right:지금 바로 지원하기:arrow_left::four_leaf_clover: :four_leaf_clover::four_leaf_clover::four_leaf_clover::four_leaf_clover::four_leaf_clover::four_leaf_clover::four_leaf_clover::four_leaf_clover::four_leaf_clover: - :절하는_남성: 상세한 정보는 구글 폼을 참고해주시고, 이외에 모집과 관련한 문의는 + :man-bowing: 상세한 정보는 구글 폼을 참고해주시고, 이외에 모집과 관련한 문의는 @sanan 에게 DM 부탁드립니다! :man-bowing:`, }, From bb32a6543316f9f32b844cf1d2400c63ac8f9f56 Mon Sep 17 00:00:00 2001 From: jiwon Date: Tue, 16 Apr 2024 16:03:42 +0900 Subject: [PATCH 02/17] =?UTF-8?q?[FE]=20HOTFIX:=20=EC=96=B4=EB=93=9C?= =?UTF-8?q?=EB=AF=BC=20=EC=8A=AC=EB=9E=99=20=EC=95=8C=EB=A6=BC=20=EB=9D=84?= =?UTF-8?q?=EC=96=B4=EC=93=B0=EA=B8=B0,=20=EC=A4=84=EB=B0=94=EA=BF=88=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/Cabinet/assets/data/SlackAlarm.ts | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/frontend/src/Cabinet/assets/data/SlackAlarm.ts b/frontend/src/Cabinet/assets/data/SlackAlarm.ts index c971b2504..123470f87 100644 --- a/frontend/src/Cabinet/assets/data/SlackAlarm.ts +++ b/frontend/src/Cabinet/assets/data/SlackAlarm.ts @@ -38,9 +38,9 @@ export const SlackAlarmTemplates: ISlackAlarmTemplate[] = [ content: `:dancing_kirby::dancing_kirby::dancing_kirby::dancing_kirby::dancing_kirby::dancing_kirby: 안녕하세요. Cabi 팀입니다! :happy_ccabi: 현시간부로 서비스 이용이 정상화되었습니다. - :portal_blue_parrot: 서비스는 cabi.42seoul.io 를 이용해주시면 됩니다. :portal_orange_parrot: + :portal_blue_parrot: 서비스는 cabi.42seoul.io 를 이용해주시면 됩니다. :portal_orange_parrot: :file_cabinet: 서비스 개선과 관련한 사항은 Cabi 채널 문의주세요! :file_cabinet: - :party-dinosaur::party-dinosaur::party-dinosaur::party-dinosaur:기다려주셔서 감사합니다! :party-dinosaur::party-dinosaur::party-dinosaur::party-dinosaur:`, + :party-dinosaur::party-dinosaur::party-dinosaur::party-dinosaur:기다려주셔서 감사합니다! :party-dinosaur::party-dinosaur::party-dinosaur::party-dinosaur:`, }, { title: "업데이트", @@ -56,20 +56,20 @@ export const SlackAlarmTemplates: ISlackAlarmTemplate[] = [ content: `:file_cabinet: Cabi 이용 안내서 :file_cabinet: :embarrassed_cabi: 42seoul의 사물함 대여 서비스를 운영중인 Cabi 팀입니다.:embarrassed_cabi: 자세한 이용 방법은 Cabi 가입 후 홈페이지의 이용 안내서를 참고해 주세요! - :point_right: https://cabi.42seoul.io/home + :point_right: https://cabi.42seoul.io/home :alert: Cabi FAQ :alert: :pushpin: 사물함의 물리적인 문제가 있습니다 (고장 났거나 잠겨있는 경우) :happy_ccabi: 사물함의 물리적인 문제는 데스크에 문의 부탁드립니다! :pushpin: 사물함 비밀번호를 모릅니다 (잊어버렸습니다). - :happy_ccabi: 저희 서비스에서 대여한 화면과 슬랙 화면을 준비해서 데스크에 문의해주시기 바랍니다! - :pushpin: 사물함을 닫으려는데 빨간 열쇠 표시가 뜨면서 경고음이 나고 잠기지 않습니다. - :happy_ccabi: 사물함 안이 꽉 차거나 제대로 닫히지 않은 경우에 발생하는데, 문을 누른 상태로 비밀번호를 입력해 보시고, 그래도 되지 않는다면 데스크에 문의 부탁드립니다! - :pushpin: 사물함 대여 후 사용하려고 했더니 안에 짐이 가득 차 있습니다. - :happy_ccabi: 이전 사용자의 짐과 관련한 문의는 데스크에 문의 부탁드립니다! - :pushpin: 공유 사물함을 대여했는데 비밀번호는 어디서 알 수 있을까요? - :happy_ccabi: 같이 사용하는 사람이 있다면 대여 내역에서 공유 메모에 적혀 있을 수 있습니다. 또는 함께 사용하는 분에게 여쭤보세요! - :pushpin: 사물함을 연체 했는데 패널티는 무엇인가요? - :happy_ccabi: 연체일만큼 누적 연체일이 증가하고, 누적일 만큼 대여가 불가능합니다:face_holding_back_tears:`, + :happy_ccabi: 저희 서비스에서 대여한 화면과 슬랙 화면을 준비해서 데스크에 문의해주시기 바랍니다! + :pushpin: 사물함을 닫으려는데 빨간 열쇠 표시가 뜨면서 경고음이 나고 잠기지 않습니다. + :happy_ccabi: 사물함 안이 꽉 차거나 제대로 닫히지 않은 경우에 발생하는데, 문을 누른 상태로 비밀번호를 입력해 보시고, 그래도 되지 않는다면 데스크에 문의 부탁드립니다! + :pushpin: 사물함 대여 후 사용하려고 했더니 안에 짐이 가득 차 있습니다. + :happy_ccabi: 이전 사용자의 짐과 관련한 문의는 데스크에 문의 부탁드립니다! + :pushpin: 공유 사물함을 대여했는데 비밀번호는 어디서 알 수 있을까요? + :happy_ccabi: 같이 사용하는 사람이 있다면 대여 내역에서 공유 메모에 적혀 있을 수 있습니다. 또는 함께 사용하는 분에게 여쭤보세요! + :pushpin: 사물함을 연체 했는데 패널티는 무엇인가요? + :happy_ccabi: 연체일만큼 누적 연체일이 증가하고, 누적일 만큼 대여가 불가능합니다:face_holding_back_tears:`, }, { title: "모집 공고", @@ -89,8 +89,7 @@ export const SlackAlarmTemplates: ISlackAlarmTemplate[] = [ :four_leaf_clover::arrow_right:지금 바로 지원하기:arrow_left::four_leaf_clover: :four_leaf_clover::four_leaf_clover::four_leaf_clover::four_leaf_clover::four_leaf_clover::four_leaf_clover::four_leaf_clover::four_leaf_clover::four_leaf_clover: :man-bowing: 상세한 정보는 구글 폼을 참고해주시고, 이외에 모집과 관련한 문의는 - @sanan - 에게 DM 부탁드립니다! :man-bowing:`, + @jpark2 에게 DM 부탁드립니다! :man-bowing:`, }, { title: "동아리 사물함", From cc981fd0dd739abf6bf64a8bad0b6e11d56fb279 Mon Sep 17 00:00:00 2001 From: jiwon Date: Tue, 16 Apr 2024 17:21:35 +0900 Subject: [PATCH 03/17] =?UTF-8?q?[FE]=20=EB=A1=A4=EB=B0=B1=203=EC=B0=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/package-lock.json | 7866 ++++++++++++++++- frontend/src/App.tsx | 4 +- .../LeftMainNav/LeftMainNav.container.tsx | 4 +- 3 files changed, 7868 insertions(+), 6 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 9f08481bf..80b634fe4 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -7612,7 +7612,13 @@ "version": "29.4.1", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.4.1.tgz", "integrity": "sha512-dt/Z761JUVsrIKaY215o1xQJBGlSmTx/h4cSqXqjHLnU1+Kt+mavVE7UgqJJO5ukx5HjSswHfmXz4LjS2oIJfg==", - "dev": true, + " + + + + + + ": true, "dependencies": { "@jest/schemas": "^29.4.0", "ansi-styles": "^5.0.0", @@ -10525,5 +10531,7863 @@ "url": "https://github.com/sponsors/sindresorhus" } } + }, + "dependencies": { + "@adobe/css-tools": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.1.0.tgz", + "integrity": "sha512-mMVJ/j/GbZ/De4ZHWbQAQO1J6iVnjtZLc9WEdkUQb8S/Bu2cAF2bETXUgMAdvMG3/ngtKmcNBe+Zms9bg6jnQQ==", + "dev": true + }, + "@ampproject/remapping": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", + "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", + "dev": true, + "requires": { + "@jridgewell/gen-mapping": "^0.1.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "@babel/code-frame": { + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", + "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", + "requires": { + "@babel/highlight": "^7.22.13", + "chalk": "^2.4.2" + } + }, + "@babel/compat-data": { + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.2.tgz", + "integrity": "sha512-0S9TQMmDHlqAZ2ITT95irXKfxN9bncq8ZCoJhun3nHL/lLUxd2NKBJYoNGWH7S0hz6fRQwWlAWn/ILM0C70KZQ==", + "dev": true + }, + "@babel/core": { + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.2.tgz", + "integrity": "sha512-n7s51eWdaWZ3vGT2tD4T7J6eJs3QoBXydv7vkUM06Bf1cbVD2Kc2UrkzhiQwobfV7NwOnQXYL7UBJ5VPU+RGoQ==", + "dev": true, + "requires": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-module-transforms": "^7.23.0", + "@babel/helpers": "^7.23.2", + "@babel/parser": "^7.23.0", + "@babel/template": "^7.22.15", + "@babel/traverse": "^7.23.2", + "@babel/types": "^7.23.0", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + } + }, + "@babel/generator": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", + "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", + "requires": { + "@babel/types": "^7.23.0", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + }, + "dependencies": { + "@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "requires": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + } + } + } + }, + "@babel/helper-annotate-as-pure": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz", + "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==", + "requires": { + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.15.tgz", + "integrity": "sha512-QkBXwGgaoC2GtGZRoma6kv7Szfv06khvhFav67ZExau2RaXzy8MpHSMO2PNoP2XtmQphJQRHFfg77Bq731Yizw==", + "dev": true, + "requires": { + "@babel/types": "^7.22.15" + } + }, + "@babel/helper-compilation-targets": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz", + "integrity": "sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.22.9", + "@babel/helper-validator-option": "^7.22.15", + "browserslist": "^4.21.9", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "dependencies": { + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + } + } + }, + "@babel/helper-create-class-features-plugin": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.15.tgz", + "integrity": "sha512-jKkwA59IXcvSaiK2UN45kKwSC9o+KuoXsBDvHvU/7BecYIp8GQ2UwrVvFgJASUT+hBnwJx6MhvMCuMzwZZ7jlg==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-function-name": "^7.22.5", + "@babel/helper-member-expression-to-functions": "^7.22.15", + "@babel/helper-optimise-call-expression": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "semver": "^6.3.1" + } + }, + "@babel/helper-create-regexp-features-plugin": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.15.tgz", + "integrity": "sha512-29FkPLFjn4TPEa3RE7GpW+qbE8tlsu3jntNYNfcGsc49LphF1PQIiD+vMZ1z1xVOKt+93khA9tc2JBs3kBjA7w==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "regexpu-core": "^5.3.1", + "semver": "^6.3.1" + } + }, + "@babel/helper-define-polyfill-provider": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.3.tgz", + "integrity": "sha512-WBrLmuPP47n7PNwsZ57pqam6G/RGo1vw/87b0Blc53tZNGZ4x7YvZ6HgQe2vo1W/FR20OgjeZuGXzudPiXHFug==", + "dev": true, + "requires": { + "@babel/helper-compilation-targets": "^7.22.6", + "@babel/helper-plugin-utils": "^7.22.5", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2" + } + }, + "@babel/helper-environment-visitor": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==" + }, + "@babel/helper-function-name": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", + "requires": { + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" + } + }, + "@babel/helper-hoist-variables": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "requires": { + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.23.0.tgz", + "integrity": "sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA==", + "dev": true, + "requires": { + "@babel/types": "^7.23.0" + } + }, + "@babel/helper-module-imports": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", + "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", + "requires": { + "@babel/types": "^7.22.15" + } + }, + "@babel/helper-module-transforms": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.0.tgz", + "integrity": "sha512-WhDWw1tdrlT0gMgUJSlX0IQvoO1eN279zrAUbVB+KpV2c3Tylz8+GnKOLllCS6Z/iZQEyVYxhZVUdPTqs2YYPw==", + "dev": true, + "requires": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-simple-access": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/helper-validator-identifier": "^7.22.20" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz", + "integrity": "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==", + "dev": true, + "requires": { + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", + "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", + "dev": true + }, + "@babel/helper-remap-async-to-generator": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.20.tgz", + "integrity": "sha512-pBGyV4uBqOns+0UvhsTO8qgl8hO89PmiDYv+/COyp1aeMcmfrfruz+/nCMFiYyFF/Knn0yfrC85ZzNFjembFTw==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-wrap-function": "^7.22.20" + } + }, + "@babel/helper-replace-supers": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.22.20.tgz", + "integrity": "sha512-qsW0In3dbwQUbK8kejJ4R7IHVGwHJlV6lpG6UA7a9hSa2YEiAib+N1T2kr6PEeUT+Fl7najmSOS6SmAwCHK6Tw==", + "dev": true, + "requires": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-member-expression-to-functions": "^7.22.15", + "@babel/helper-optimise-call-expression": "^7.22.5" + } + }, + "@babel/helper-simple-access": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", + "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", + "dev": true, + "requires": { + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz", + "integrity": "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==", + "dev": true, + "requires": { + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "requires": { + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-string-parser": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", + "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==" + }, + "@babel/helper-validator-identifier": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==" + }, + "@babel/helper-validator-option": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz", + "integrity": "sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA==", + "dev": true + }, + "@babel/helper-wrap-function": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.22.20.tgz", + "integrity": "sha512-pms/UwkOpnQe/PDAEdV/d7dVCoBbB+R4FvYoHGZz+4VPcg7RtYy2KP7S2lbuWM6FCSgob5wshfGESbC/hzNXZw==", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.22.5", + "@babel/template": "^7.22.15", + "@babel/types": "^7.22.19" + } + }, + "@babel/helpers": { + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.2.tgz", + "integrity": "sha512-lzchcp8SjTSVe/fPmLwtWVBFC7+Tbn8LGHDVfDp9JGxpAY5opSaEFgt8UQvrnECWOTdji2mOWMz1rOhkHscmGQ==", + "dev": true, + "requires": { + "@babel/template": "^7.22.15", + "@babel/traverse": "^7.23.2", + "@babel/types": "^7.23.0" + } + }, + "@babel/highlight": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", + "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", + "requires": { + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", + "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==" + }, + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.22.15.tgz", + "integrity": "sha512-FB9iYlz7rURmRJyXRKEnalYPPdn87H5no108cyuQQyMwlpJ2SJtpIUBI27kdTin956pz+LPypkPVPUTlxOmrsg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.22.15.tgz", + "integrity": "sha512-Hyph9LseGvAeeXzikV88bczhsrLrIZqDPxO+sSmAunMPaGrBGhfMWzCPYTtiW9t+HzSE2wtV8e5cc5P6r1xMDQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/plugin-transform-optional-chaining": "^7.22.15" + } + }, + "@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "dev": true, + "requires": {} + }, + "@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.12.13" + } + }, + "@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/plugin-syntax-import-assertions": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.22.5.tgz", + "integrity": "sha512-rdV97N7KqsRzeNGoWUOK6yUsWarLjE5Su/Snk9IYPU9CwkWHs4t+rTGOvffTR8XGkJMTAdLfO0xVnXm8wugIJg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-syntax-import-attributes": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.22.5.tgz", + "integrity": "sha512-KwvoWDeNKPETmozyFE0P2rOLqh39EoQHNjqizrI5B8Vt0ZNS7M56s7dAiAqbYfiAYOuIzIh96z3iR2ktgu3tEg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-jsx": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.22.5.tgz", + "integrity": "sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-syntax-typescript": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.22.5.tgz", + "integrity": "sha512-1mS2o03i7t1c6VzH6fdQ3OA8tcEIxwG18zIPRp+UY1Ihv6W+XZzBCVxExF9upussPXJ0xE9XRHwMoNs1ep/nRQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-arrow-functions": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.22.5.tgz", + "integrity": "sha512-26lTNXoVRdAnsaDXPpvCNUq+OVWEVC6bx7Vvz9rC53F2bagUWW4u4ii2+h8Fejfh7RYqPxn+libeFBBck9muEw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-async-generator-functions": { + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.23.2.tgz", + "integrity": "sha512-BBYVGxbDVHfoeXbOwcagAkOQAm9NxoTdMGfTqghu1GrvadSaw6iW3Je6IcL5PNOw8VwjxqBECXy50/iCQSY/lQ==", + "dev": true, + "requires": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-remap-async-to-generator": "^7.22.20", + "@babel/plugin-syntax-async-generators": "^7.8.4" + } + }, + "@babel/plugin-transform-async-to-generator": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.22.5.tgz", + "integrity": "sha512-b1A8D8ZzE/VhNDoV1MSJTnpKkCG5bJo+19R4o4oy03zM7ws8yEMK755j61Dc3EyvdysbqH5BOOTquJ7ZX9C6vQ==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-remap-async-to-generator": "^7.22.5" + } + }, + "@babel/plugin-transform-block-scoped-functions": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.22.5.tgz", + "integrity": "sha512-tdXZ2UdknEKQWKJP1KMNmuF5Lx3MymtMN/pvA+p/VEkhK8jVcQ1fzSy8KM9qRYhAf2/lV33hoMPKI/xaI9sADA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-block-scoping": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.23.0.tgz", + "integrity": "sha512-cOsrbmIOXmf+5YbL99/S49Y3j46k/T16b9ml8bm9lP6N9US5iQ2yBK7gpui1pg0V/WMcXdkfKbTb7HXq9u+v4g==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-class-properties": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.22.5.tgz", + "integrity": "sha512-nDkQ0NfkOhPTq8YCLiWNxp1+f9fCobEjCb0n8WdbNUBc4IB5V7P1QnX9IjpSoquKrXF5SKojHleVNs2vGeHCHQ==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-class-static-block": { + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.22.11.tgz", + "integrity": "sha512-GMM8gGmqI7guS/llMFk1bJDkKfn3v3C4KHK9Yg1ey5qcHcOlKb0QvcMrgzvxo+T03/4szNh5lghY+fEC98Kq9g==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.22.11", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-class-static-block": "^7.14.5" + } + }, + "@babel/plugin-transform-classes": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.22.15.tgz", + "integrity": "sha512-VbbC3PGjBdE0wAWDdHM9G8Gm977pnYI0XpqMd6LrKISj8/DJXEsWqgRuTYaNE9Bv0JGhTZUzHDlMk18IpOuoqw==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-function-name": "^7.22.5", + "@babel/helper-optimise-call-expression": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.9", + "@babel/helper-split-export-declaration": "^7.22.6", + "globals": "^11.1.0" + } + }, + "@babel/plugin-transform-computed-properties": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.22.5.tgz", + "integrity": "sha512-4GHWBgRf0krxPX+AaPtgBAlTgTeZmqDynokHOX7aqqAB4tHs3U2Y02zH6ETFdLZGcg9UQSD1WCmkVrE9ErHeOg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/template": "^7.22.5" + } + }, + "@babel/plugin-transform-destructuring": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.23.0.tgz", + "integrity": "sha512-vaMdgNXFkYrB+8lbgniSYWHsgqK5gjaMNcc84bMIOMRLH0L9AqYq3hwMdvnyqj1OPqea8UtjPEuS/DCenah1wg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-dotall-regex": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.22.5.tgz", + "integrity": "sha512-5/Yk9QxCQCl+sOIB1WelKnVRxTJDSAIxtJLL2/pqL14ZVlbH0fUQUZa/T5/UnQtBNgghR7mfB8ERBKyKPCi7Vw==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-duplicate-keys": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.22.5.tgz", + "integrity": "sha512-dEnYD+9BBgld5VBXHnF/DbYGp3fqGMsyxKbtD1mDyIA7AkTSpKXFhCVuj/oQVOoALfBs77DudA0BE4d5mcpmqw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-dynamic-import": { + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.22.11.tgz", + "integrity": "sha512-g/21plo58sfteWjaO0ZNVb+uEOkJNjAaHhbejrnBmu011l/eNDScmkbjCC3l4FKb10ViaGU4aOkFznSu2zRHgA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3" + } + }, + "@babel/plugin-transform-exponentiation-operator": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.22.5.tgz", + "integrity": "sha512-vIpJFNM/FjZ4rh1myqIya9jXwrwwgFRHPjT3DkUA9ZLHuzox8jiXkOLvwm1H+PQIP3CqfC++WPKeuDi0Sjdj1g==", + "dev": true, + "requires": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-export-namespace-from": { + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.22.11.tgz", + "integrity": "sha512-xa7aad7q7OiT8oNZ1mU7NrISjlSkVdMbNxn9IuLZyL9AJEhs1Apba3I+u5riX1dIkdptP5EKDG5XDPByWxtehw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + } + }, + "@babel/plugin-transform-for-of": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.22.15.tgz", + "integrity": "sha512-me6VGeHsx30+xh9fbDLLPi0J1HzmeIIyenoOQHuw2D4m2SAU3NrspX5XxJLBpqn5yrLzrlw2Iy3RA//Bx27iOA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-function-name": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.22.5.tgz", + "integrity": "sha512-UIzQNMS0p0HHiQm3oelztj+ECwFnj+ZRV4KnguvlsD2of1whUeM6o7wGNj6oLwcDoAXQ8gEqfgC24D+VdIcevg==", + "dev": true, + "requires": { + "@babel/helper-compilation-targets": "^7.22.5", + "@babel/helper-function-name": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-json-strings": { + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.22.11.tgz", + "integrity": "sha512-CxT5tCqpA9/jXFlme9xIBCc5RPtdDq3JpkkhgHQqtDdiTnTI0jtZ0QzXhr5DILeYifDPp2wvY2ad+7+hLMW5Pw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-json-strings": "^7.8.3" + } + }, + "@babel/plugin-transform-literals": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.22.5.tgz", + "integrity": "sha512-fTLj4D79M+mepcw3dgFBTIDYpbcB9Sm0bpm4ppXPaO+U+PKFFyV9MGRvS0gvGw62sd10kT5lRMKXAADb9pWy8g==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-logical-assignment-operators": { + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.22.11.tgz", + "integrity": "sha512-qQwRTP4+6xFCDV5k7gZBF3C31K34ut0tbEcTKxlX/0KXxm9GLcO14p570aWxFvVzx6QAfPgq7gaeIHXJC8LswQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + } + }, + "@babel/plugin-transform-member-expression-literals": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.22.5.tgz", + "integrity": "sha512-RZEdkNtzzYCFl9SE9ATaUMTj2hqMb4StarOJLrZRbqqU4HSBE7UlBw9WBWQiDzrJZJdUWiMTVDI6Gv/8DPvfew==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-modules-amd": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.23.0.tgz", + "integrity": "sha512-xWT5gefv2HGSm4QHtgc1sYPbseOyf+FFDo2JbpE25GWl5BqTGO9IMwTYJRoIdjsF85GE+VegHxSCUt5EvoYTAw==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.23.0", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-modules-commonjs": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.23.0.tgz", + "integrity": "sha512-32Xzss14/UVc7k9g775yMIvkVK8xwKE0DPdP5JTapr3+Z9w4tzeOuLNY6BXDQR6BdnzIlXnCGAzsk/ICHBLVWQ==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.23.0", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-simple-access": "^7.22.5" + } + }, + "@babel/plugin-transform-modules-systemjs": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.23.0.tgz", + "integrity": "sha512-qBej6ctXZD2f+DhlOC9yO47yEYgUh5CZNz/aBoH4j/3NOlRfJXJbY7xDQCqQVf9KbrqGzIWER1f23doHGrIHFg==", + "dev": true, + "requires": { + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-module-transforms": "^7.23.0", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20" + } + }, + "@babel/plugin-transform-modules-umd": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.22.5.tgz", + "integrity": "sha512-+S6kzefN/E1vkSsKx8kmQuqeQsvCKCd1fraCM7zXm4SFoggI099Tr4G8U81+5gtMdUeMQ4ipdQffbKLX0/7dBQ==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz", + "integrity": "sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-new-target": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.22.5.tgz", + "integrity": "sha512-AsF7K0Fx/cNKVyk3a+DW0JLo+Ua598/NxMRvxDnkpCIGFh43+h/v2xyhRUYf6oD8gE4QtL83C7zZVghMjHd+iw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.22.11.tgz", + "integrity": "sha512-YZWOw4HxXrotb5xsjMJUDlLgcDXSfO9eCmdl1bgW4+/lAGdkjaEvOnQ4p5WKKdUgSzO39dgPl0pTnfxm0OAXcg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + } + }, + "@babel/plugin-transform-numeric-separator": { + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.22.11.tgz", + "integrity": "sha512-3dzU4QGPsILdJbASKhF/V2TVP+gJya1PsueQCxIPCEcerqF21oEcrob4mzjsp2Py/1nLfF5m+xYNMDpmA8vffg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + } + }, + "@babel/plugin-transform-object-rest-spread": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.22.15.tgz", + "integrity": "sha512-fEB+I1+gAmfAyxZcX1+ZUwLeAuuf8VIg67CTznZE0MqVFumWkh8xWtn58I4dxdVf080wn7gzWoF8vndOViJe9Q==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.22.9", + "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.22.15" + } + }, + "@babel/plugin-transform-object-super": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.22.5.tgz", + "integrity": "sha512-klXqyaT9trSjIUrcsYIfETAzmOEZL3cBYqOYLJxBHfMFFggmXOv+NYSX/Jbs9mzMVESw/WycLFPRx8ba/b2Ipw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.5" + } + }, + "@babel/plugin-transform-optional-catch-binding": { + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.22.11.tgz", + "integrity": "sha512-rli0WxesXUeCJnMYhzAglEjLWVDF6ahb45HuprcmQuLidBJFWjNnOzssk2kuc6e33FlLaiZhG/kUIzUMWdBKaQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + } + }, + "@babel/plugin-transform-optional-chaining": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.23.0.tgz", + "integrity": "sha512-sBBGXbLJjxTzLBF5rFWaikMnOGOk/BmK6vVByIdEggZ7Vn6CvWXZyRkkLFK6WE0IF8jSliyOkUN6SScFgzCM0g==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + } + }, + "@babel/plugin-transform-parameters": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.22.15.tgz", + "integrity": "sha512-hjk7qKIqhyzhhUvRT683TYQOFa/4cQKwQy7ALvTpODswN40MljzNDa0YldevS6tGbxwaEKVn502JmY0dP7qEtQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-private-methods": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.22.5.tgz", + "integrity": "sha512-PPjh4gyrQnGe97JTalgRGMuU4icsZFnWkzicB/fUtzlKUqvsWBKEpPPfr5a2JiyirZkHxnAqkQMO5Z5B2kK3fA==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-private-property-in-object": { + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.22.11.tgz", + "integrity": "sha512-sSCbqZDBKHetvjSwpyWzhuHkmW5RummxJBVbYLkGkaiTOWGxml7SXt0iWa03bzxFIx7wOj3g/ILRd0RcJKBeSQ==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-create-class-features-plugin": "^7.22.11", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + } + }, + "@babel/plugin-transform-property-literals": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.22.5.tgz", + "integrity": "sha512-TiOArgddK3mK/x1Qwf5hay2pxI6wCZnvQqrFSqbtg1GLl2JcNMitVH/YnqjP+M31pLUeTfzY1HAXFDnUBV30rQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-react-constant-elements": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.22.5.tgz", + "integrity": "sha512-BF5SXoO+nX3h5OhlN78XbbDrBOffv+AxPP2ENaJOVqjWCgBDeOY3WcaUcddutGSfoap+5NEQ/q/4I3WZIvgkXA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-react-display-name": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.22.5.tgz", + "integrity": "sha512-PVk3WPYudRF5z4GKMEYUrLjPl38fJSKNaEOkFuoprioowGuWN6w2RKznuFNSlJx7pzzXXStPUnNSOEO0jL5EVw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-react-jsx": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.22.15.tgz", + "integrity": "sha512-oKckg2eZFa8771O/5vi7XeTvmM6+O9cxZu+kanTU7tD4sin5nO/G8jGJhq8Hvt2Z0kUoEDRayuZLaUlYl8QuGA==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-jsx": "^7.22.5", + "@babel/types": "^7.22.15" + } + }, + "@babel/plugin-transform-react-jsx-development": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.22.5.tgz", + "integrity": "sha512-bDhuzwWMuInwCYeDeMzyi7TaBgRQei6DqxhbyniL7/VG4RSS7HtSL2QbY4eESy1KJqlWt8g3xeEBGPuo+XqC8A==", + "dev": true, + "requires": { + "@babel/plugin-transform-react-jsx": "^7.22.5" + } + }, + "@babel/plugin-transform-react-jsx-self": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.18.6.tgz", + "integrity": "sha512-A0LQGx4+4Jv7u/tWzoJF7alZwnBDQd6cGLh9P+Ttk4dpiL+J5p7NSNv/9tlEFFJDq3kjxOavWmbm6t0Gk+A3Ig==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-react-jsx-source": { + "version": "7.19.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.19.6.tgz", + "integrity": "sha512-RpAi004QyMNisst/pvSanoRdJ4q+jMCWyk9zdw/CyLB9j8RXEahodR6l2GyttDRyEVWZtbN+TpLiHJ3t34LbsQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.19.0" + } + }, + "@babel/plugin-transform-react-pure-annotations": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.22.5.tgz", + "integrity": "sha512-gP4k85wx09q+brArVinTXhWiyzLl9UpmGva0+mWyKxk6JZequ05x3eUcIUE+FyttPKJFRRVtAvQaJ6YF9h1ZpA==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-regenerator": { + "version": "7.22.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.22.10.tgz", + "integrity": "sha512-F28b1mDt8KcT5bUyJc/U9nwzw6cV+UmTeRlXYIl2TNqMMJif0Jeey9/RQ3C4NOd2zp0/TRsDns9ttj2L523rsw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "regenerator-transform": "^0.15.2" + } + }, + "@babel/plugin-transform-reserved-words": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.22.5.tgz", + "integrity": "sha512-DTtGKFRQUDm8svigJzZHzb/2xatPc6TzNvAIJ5GqOKDsGFYgAskjRulbR/vGsPKq3OPqtexnz327qYpP57RFyA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-shorthand-properties": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.22.5.tgz", + "integrity": "sha512-vM4fq9IXHscXVKzDv5itkO1X52SmdFBFcMIBZ2FRn2nqVYqw6dBexUgMvAjHW+KXpPPViD/Yo3GrDEBaRC0QYA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-spread": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.22.5.tgz", + "integrity": "sha512-5ZzDQIGyvN4w8+dMmpohL6MBo+l2G7tfC/O2Dg7/hjpgeWvUx8FzfeOKxGog9IimPa4YekaQ9PlDqTLOljkcxg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" + } + }, + "@babel/plugin-transform-sticky-regex": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.22.5.tgz", + "integrity": "sha512-zf7LuNpHG0iEeiyCNwX4j3gDg1jgt1k3ZdXBKbZSoA3BbGQGvMiSvfbZRR3Dr3aeJe3ooWFZxOOG3IRStYp2Bw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-template-literals": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.22.5.tgz", + "integrity": "sha512-5ciOehRNf+EyUeewo8NkbQiUs4d6ZxiHo6BcBcnFlgiJfu16q0bQUw9Jvo0b0gBKFG1SMhDSjeKXSYuJLeFSMA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-typeof-symbol": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.22.5.tgz", + "integrity": "sha512-bYkI5lMzL4kPii4HHEEChkD0rkc+nvnlR6+o/qdqR6zrm0Sv/nodmyLhlq2DO0YKLUNd2VePmPRjJXSBh9OIdA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-typescript": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.22.15.tgz", + "integrity": "sha512-1uirS0TnijxvQLnlv5wQBwOX3E1wCFX7ITv+9pBV2wKEk4K+M5tqDaoNXnTH8tjEIYHLO98MwiTWO04Ggz4XuA==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-create-class-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-typescript": "^7.22.5" + } + }, + "@babel/plugin-transform-unicode-escapes": { + "version": "7.22.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.22.10.tgz", + "integrity": "sha512-lRfaRKGZCBqDlRU3UIFovdp9c9mEvlylmpod0/OatICsSfuQ9YFthRo1tpTkGsklEefZdqlEFdY4A2dwTb6ohg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-unicode-property-regex": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.22.5.tgz", + "integrity": "sha512-HCCIb+CbJIAE6sXn5CjFQXMwkCClcOfPCzTlilJ8cUatfzwHlWQkbtV0zD338u9dZskwvuOYTuuaMaA8J5EI5A==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-unicode-regex": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.22.5.tgz", + "integrity": "sha512-028laaOKptN5vHJf9/Arr/HiJekMd41hOEZYvNsrsXqJ7YPYuX2bQxh31fkZzGmq3YqHRJzYFFAVYvKfMPKqyg==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-unicode-sets-regex": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.22.5.tgz", + "integrity": "sha512-lhMfi4FC15j13eKrh3DnYHjpGj6UKQHtNKTbtc1igvAhRy4+kLhV07OpLcsN0VgDEw/MjAvJO4BdMJsHwMhzCg==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/preset-env": { + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.23.2.tgz", + "integrity": "sha512-BW3gsuDD+rvHL2VO2SjAUNTBe5YrjsTiDyqamPDWY723na3/yPQ65X5oQkFVJZ0o50/2d+svm1rkPoJeR1KxVQ==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.23.2", + "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-validator-option": "^7.22.15", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.22.15", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.22.15", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-import-assertions": "^7.22.5", + "@babel/plugin-syntax-import-attributes": "^7.22.5", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.22.5", + "@babel/plugin-transform-async-generator-functions": "^7.23.2", + "@babel/plugin-transform-async-to-generator": "^7.22.5", + "@babel/plugin-transform-block-scoped-functions": "^7.22.5", + "@babel/plugin-transform-block-scoping": "^7.23.0", + "@babel/plugin-transform-class-properties": "^7.22.5", + "@babel/plugin-transform-class-static-block": "^7.22.11", + "@babel/plugin-transform-classes": "^7.22.15", + "@babel/plugin-transform-computed-properties": "^7.22.5", + "@babel/plugin-transform-destructuring": "^7.23.0", + "@babel/plugin-transform-dotall-regex": "^7.22.5", + "@babel/plugin-transform-duplicate-keys": "^7.22.5", + "@babel/plugin-transform-dynamic-import": "^7.22.11", + "@babel/plugin-transform-exponentiation-operator": "^7.22.5", + "@babel/plugin-transform-export-namespace-from": "^7.22.11", + "@babel/plugin-transform-for-of": "^7.22.15", + "@babel/plugin-transform-function-name": "^7.22.5", + "@babel/plugin-transform-json-strings": "^7.22.11", + "@babel/plugin-transform-literals": "^7.22.5", + "@babel/plugin-transform-logical-assignment-operators": "^7.22.11", + "@babel/plugin-transform-member-expression-literals": "^7.22.5", + "@babel/plugin-transform-modules-amd": "^7.23.0", + "@babel/plugin-transform-modules-commonjs": "^7.23.0", + "@babel/plugin-transform-modules-systemjs": "^7.23.0", + "@babel/plugin-transform-modules-umd": "^7.22.5", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.22.5", + "@babel/plugin-transform-new-target": "^7.22.5", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.22.11", + "@babel/plugin-transform-numeric-separator": "^7.22.11", + "@babel/plugin-transform-object-rest-spread": "^7.22.15", + "@babel/plugin-transform-object-super": "^7.22.5", + "@babel/plugin-transform-optional-catch-binding": "^7.22.11", + "@babel/plugin-transform-optional-chaining": "^7.23.0", + "@babel/plugin-transform-parameters": "^7.22.15", + "@babel/plugin-transform-private-methods": "^7.22.5", + "@babel/plugin-transform-private-property-in-object": "^7.22.11", + "@babel/plugin-transform-property-literals": "^7.22.5", + "@babel/plugin-transform-regenerator": "^7.22.10", + "@babel/plugin-transform-reserved-words": "^7.22.5", + "@babel/plugin-transform-shorthand-properties": "^7.22.5", + "@babel/plugin-transform-spread": "^7.22.5", + "@babel/plugin-transform-sticky-regex": "^7.22.5", + "@babel/plugin-transform-template-literals": "^7.22.5", + "@babel/plugin-transform-typeof-symbol": "^7.22.5", + "@babel/plugin-transform-unicode-escapes": "^7.22.10", + "@babel/plugin-transform-unicode-property-regex": "^7.22.5", + "@babel/plugin-transform-unicode-regex": "^7.22.5", + "@babel/plugin-transform-unicode-sets-regex": "^7.22.5", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "@babel/types": "^7.23.0", + "babel-plugin-polyfill-corejs2": "^0.4.6", + "babel-plugin-polyfill-corejs3": "^0.8.5", + "babel-plugin-polyfill-regenerator": "^0.5.3", + "core-js-compat": "^3.31.0", + "semver": "^6.3.1" + } + }, + "@babel/preset-modules": { + "version": "0.1.6-no-external-plugins", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", + "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + } + }, + "@babel/preset-react": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.22.15.tgz", + "integrity": "sha512-Csy1IJ2uEh/PecCBXXoZGAZBeCATTuePzCSB7dLYWS0vOEj6CNpjxIhW4duWwZodBNueH7QO14WbGn8YyeuN9w==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-validator-option": "^7.22.15", + "@babel/plugin-transform-react-display-name": "^7.22.5", + "@babel/plugin-transform-react-jsx": "^7.22.15", + "@babel/plugin-transform-react-jsx-development": "^7.22.5", + "@babel/plugin-transform-react-pure-annotations": "^7.22.5" + } + }, + "@babel/preset-typescript": { + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.23.2.tgz", + "integrity": "sha512-u4UJc1XsS1GhIGteM8rnGiIvf9rJpiVgMEeCnwlLA7WJPC+jcXWJAGxYmeqs5hOZD8BbAfnV5ezBOxQbb4OUxA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-validator-option": "^7.22.15", + "@babel/plugin-syntax-jsx": "^7.22.5", + "@babel/plugin-transform-modules-commonjs": "^7.23.0", + "@babel/plugin-transform-typescript": "^7.22.15" + } + }, + "@babel/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==", + "dev": true + }, + "@babel/runtime": { + "version": "7.20.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.6.tgz", + "integrity": "sha512-Q+8MqP7TiHMWzSfwiJwXCjyf4GYA4Dgw3emg/7xmwsdLJOZUp+nMqcOwOzzYheuM1rhDu8FSj2l0aoMygEuXuA==", + "dev": true, + "requires": { + "regenerator-runtime": "^0.13.11" + } + }, + "@babel/runtime-corejs3": { + "version": "7.20.6", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.20.6.tgz", + "integrity": "sha512-tqeujPiuEfcH067mx+7otTQWROVMKHXEaOQcAeNV5dDdbPWvPcFA8/W9LXw2NfjNmOetqLl03dfnG2WALPlsRQ==", + "dev": true, + "requires": { + "core-js-pure": "^3.25.1", + "regenerator-runtime": "^0.13.11" + } + }, + "@babel/template": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", + "requires": { + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" + } + }, + "@babel/traverse": { + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", + "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", + "requires": { + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.0", + "@babel/types": "^7.23.0", + "debug": "^4.1.0", + "globals": "^11.1.0" + } + }, + "@babel/types": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", + "requires": { + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", + "to-fast-properties": "^2.0.0" + } + }, + "@emotion/is-prop-valid": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.0.tgz", + "integrity": "sha512-3aDpDprjM0AwaxGE09bOPkNxHpBd+kA6jty3RnaEXdweX1DF1U3VQpPYb0g1IStAuK7SVQ1cy+bNBBKp4W3Fjg==", + "requires": { + "@emotion/memoize": "^0.8.0" + } + }, + "@emotion/memoize": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.0.tgz", + "integrity": "sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA==" + }, + "@emotion/stylis": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz", + "integrity": "sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ==" + }, + "@emotion/unitless": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", + "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" + }, + "@esbuild/android-arm": { + "version": "0.16.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.16.9.tgz", + "integrity": "sha512-kW5ccqWHVOOTGUkkJbtfoImtqu3kA1PFkivM+9QPFSHphPfPBlBalX9eDRqPK+wHCqKhU48/78T791qPgC9e9A==", + "dev": true, + "optional": true + }, + "@esbuild/android-arm64": { + "version": "0.16.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.16.9.tgz", + "integrity": "sha512-ndIAZJUeLx4O+4AJbFQCurQW4VRUXjDsUvt1L+nP8bVELOWdmdCEOtlIweCUE6P+hU0uxYbEK2AEP0n5IVQvhg==", + "dev": true, + "optional": true + }, + "@esbuild/android-x64": { + "version": "0.16.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.16.9.tgz", + "integrity": "sha512-UbMcJB4EHrAVOnknQklREPgclNU2CPet2h+sCBCXmF2mfoYWopBn/CfTfeyOkb/JglOcdEADqAljFndMKnFtOw==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-arm64": { + "version": "0.16.9", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.16.9.tgz", + "integrity": "sha512-d7D7/nrt4CxPul98lx4PXhyNZwTYtbdaHhOSdXlZuu5zZIznjqtMqLac8Bv+IuT6SVHiHUwrkL6ywD7mOgLW+A==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-x64": { + "version": "0.16.9", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.16.9.tgz", + "integrity": "sha512-LZc+Wlz06AkJYtwWsBM3x2rSqTG8lntDuftsUNQ3fCx9ZttYtvlDcVtgb+NQ6t9s6K5No5zutN3pcjZEC2a4iQ==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-arm64": { + "version": "0.16.9", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.16.9.tgz", + "integrity": "sha512-gIj0UQZlQo93CHYouHKkpzP7AuruSaMIm1etcWIxccFEVqCN1xDr6BWlN9bM+ol/f0W9w3hx3HDuEwcJVtGneQ==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-x64": { + "version": "0.16.9", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.16.9.tgz", + "integrity": "sha512-GNors4vaMJ7lzGOuhzNc7jvgsQZqErGA8rsW+nck8N1nYu86CvsJW2seigVrQQWOV4QzEP8Zf3gm+QCjA2hnBQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm": { + "version": "0.16.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.16.9.tgz", + "integrity": "sha512-cNx1EF99c2t1Ztn0lk9N+MuwBijGF8mH6nx9GFsB3e0lpUpPkCE/yt5d+7NP9EwJf5uzqdjutgVYoH1SNqzudA==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm64": { + "version": "0.16.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.16.9.tgz", + "integrity": "sha512-YPxQunReYp8RQ1FvexFrOEqqf+nLbS3bKVZF5FRT2uKM7Wio7BeATqAwO02AyrdSEntt3I5fhFsujUChIa8CZg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ia32": { + "version": "0.16.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.16.9.tgz", + "integrity": "sha512-zb12ixDIKNwFpIqR00J88FFitVwOEwO78EiUi8wi8FXlmSc3GtUuKV/BSO+730Kglt0B47+ZrJN1BhhOxZaVrw==", + "dev": true, + "optional": true + }, + "@esbuild/linux-loong64": { + "version": "0.16.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.16.9.tgz", + "integrity": "sha512-X8te4NLxtHiNT6H+4Pfm5RklzItA1Qy4nfyttihGGX+Koc53Ar20ViC+myY70QJ8PDEOehinXZj/F7QK3A+MKQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-mips64el": { + "version": "0.16.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.16.9.tgz", + "integrity": "sha512-ZqyMDLt02c5smoS3enlF54ndK5zK4IpClLTxF0hHfzHJlfm4y8IAkIF8LUW0W7zxcKy7oAwI7BRDqeVvC120SA==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ppc64": { + "version": "0.16.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.16.9.tgz", + "integrity": "sha512-k+ca5W5LDBEF3lfDwMV6YNXwm4wEpw9krMnNvvlNz3MrKSD2Eb2c861O0MaKrZkG/buTQAP4vkavbLwgIe6xjg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-riscv64": { + "version": "0.16.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.16.9.tgz", + "integrity": "sha512-GuInVdogjmg9DhgkEmNipHkC+3tzkanPJzgzTC2ihsvrruLyFoR1YrTGixblNSMPudQLpiqkcwGwwe0oqfrvfA==", + "dev": true, + "optional": true + }, + "@esbuild/linux-s390x": { + "version": "0.16.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.16.9.tgz", + "integrity": "sha512-49wQ0aYkvwXonGsxc7LuuLNICMX8XtO92Iqmug5Qau0kpnV6SP34jk+jIeu4suHwAbSbRhVFtDv75yRmyfQcHw==", + "dev": true, + "optional": true + }, + "@esbuild/linux-x64": { + "version": "0.16.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.16.9.tgz", + "integrity": "sha512-Nx4oKEAJ6EcQlt4dK7qJyuZUoXZG7CAeY22R7rqZijFzwFfMOD+gLP56uV7RrV86jGf8PeRY8TBsRmOcZoG42w==", + "dev": true, + "optional": true + }, + "@esbuild/netbsd-x64": { + "version": "0.16.9", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.16.9.tgz", + "integrity": "sha512-d0WnpgJ+FTiMZXEQ1NOv9+0gvEhttbgKEvVqWWAtl1u9AvlspKXbodKHzQ5MLP6YV1y52Xp+p8FMYqj8ykTahg==", + "dev": true, + "optional": true + }, + "@esbuild/openbsd-x64": { + "version": "0.16.9", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.16.9.tgz", + "integrity": "sha512-jccK11278dvEscHFfMk5EIPjF4wv1qGD0vps7mBV1a6TspdR36O28fgPem/SA/0pcsCPHjww5ouCLwP+JNAFlw==", + "dev": true, + "optional": true + }, + "@esbuild/sunos-x64": { + "version": "0.16.9", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.16.9.tgz", + "integrity": "sha512-OetwTSsv6mIDLqN7I7I2oX9MmHGwG+AP+wKIHvq+6sIHwcPPJqRx+DJB55jy9JG13CWcdcQno/7V5MTJ5a0xfQ==", + "dev": true, + "optional": true + }, + "@esbuild/win32-arm64": { + "version": "0.16.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.16.9.tgz", + "integrity": "sha512-tKSSSK6unhxbGbHg+Cc+JhRzemkcsX0tPBvG0m5qsWbkShDK9c+/LSb13L18LWVdOQZwuA55Vbakxmt6OjBDOQ==", + "dev": true, + "optional": true + }, + "@esbuild/win32-ia32": { + "version": "0.16.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.16.9.tgz", + "integrity": "sha512-ZTQ5vhNS5gli0KK8I6/s6+LwXmNEfq1ftjnSVyyNm33dBw8zDpstqhGXYUbZSWWLvkqiRRjgxgmoncmi6Yy7Ng==", + "dev": true, + "optional": true + }, + "@esbuild/win32-x64": { + "version": "0.16.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.16.9.tgz", + "integrity": "sha512-C4ZX+YFIp6+lPrru3tpH6Gaapy8IBRHw/e7l63fzGDhn/EaiGpQgbIlT5paByyy+oMvRFQoxxyvC4LE0AjJMqQ==", + "dev": true, + "optional": true + }, + "@eslint/eslintrc": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.0.tgz", + "integrity": "sha512-7yfvXy6MWLgWSFsLhz5yH3iQ52St8cdUY6FoGieKkRDVxuxmrNuUetIuu6cmjNWwniUHiWXjxCr5tTXDrbYS5A==", + "dev": true, + "requires": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.4.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "dependencies": { + "globals": { + "version": "13.19.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.19.0.tgz", + "integrity": "sha512-dkQ957uSRWHw7CFXLUtUHQI3g3aWApYhfNR2O6jn/907riyTYKVBmxYVROkBcY614FSSeSJh7Xm7SrUWCxvJMQ==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + } + } + }, + "@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==", + "requires": { + "@firebase/component": "0.6.4", + "@firebase/installations": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + } + } + }, + "@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==", + "requires": { + "@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" + }, + "dependencies": { + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + } + } + }, + "@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==" + }, + "@firebase/app": { + "version": "0.9.19", + "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.9.19.tgz", + "integrity": "sha512-t/SHyZ3xWkR77ZU9VMoobDNFLdDKQ5xqoCAn4o16gTsA1C8sJ6ZOMZ02neMOPxNHuQXVE4tA8ukilnDbnK7uJA==", + "requires": { + "@firebase/component": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "idb": "7.1.1", + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + } + } + }, + "@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==", + "requires": { + "@firebase/component": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + } + } + }, + "@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==", + "requires": { + "@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" + }, + "dependencies": { + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + } + } + }, + "@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==" + }, + "@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==" + }, + "@firebase/app-compat": { + "version": "0.2.19", + "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.2.19.tgz", + "integrity": "sha512-QkJDqYqjhvs4fTMcRVXQkP9hbo5yfoJXDWkhU4VA5Vzs8Qsp76VPzYbqx5SD5OmBy+bz/Ot1UV8qySPGI4aKuw==", + "requires": { + "@firebase/app": "0.9.19", + "@firebase/component": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + } + } + }, + "@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==" + }, + "@firebase/auth": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-1.3.0.tgz", + "integrity": "sha512-vjK4CHbY9aWdiVOrKi6mpa8z6uxeaf7LB/MZTHuZOiGHMcUoTGB6TeMbRShyqk1uaMrxhhZ5Ar/dR0965E1qyA==", + "requires": { + "@firebase/component": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "node-fetch": "2.6.7", + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + } + } + }, + "@firebase/auth-compat": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/@firebase/auth-compat/-/auth-compat-0.4.6.tgz", + "integrity": "sha512-pKp1d4fSf+yoy1EBjTx9ISxlunqhW0vTICk0ByZ3e+Lp6ZIXThfUy4F1hAJlEafD/arM0oepRiAh7LXS1xn/BA==", + "requires": { + "@firebase/auth": "1.3.0", + "@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" + }, + "dependencies": { + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + } + } + }, + "@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==" + }, + "@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==", + "requires": {} + }, + "@firebase/component": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.6.4.tgz", + "integrity": "sha512-rLMyrXuO9jcAUCaQXCMjCMUsWrba5fzHlNK24xz5j2W6A/SRmK8mZJ/hn7V0fViLbxC0lPMtrK1eYzk6Fg03jA==", + "requires": { + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + } + } + }, + "@firebase/database": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-1.0.1.tgz", + "integrity": "sha512-VAhF7gYwunW4Lw/+RQZvW8dlsf2r0YYqV9W0Gi2Mz8+0TGg1mBJWoUtsHfOr8kPJXhcLsC4eP/z3x6L/Fvjk/A==", + "requires": { + "@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" + }, + "dependencies": { + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + } + } + }, + "@firebase/database-compat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-1.0.1.tgz", + "integrity": "sha512-ky82yLIboLxtAIWyW/52a6HLMVTzD2kpZlEilVDok73pNPLjkJYowj8iaIWK5nTy7+6Gxt7d00zfjL6zckGdXQ==", + "requires": { + "@firebase/component": "0.6.4", + "@firebase/database": "1.0.1", + "@firebase/database-types": "1.0.0", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + } + } + }, + "@firebase/database-types": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-1.0.0.tgz", + "integrity": "sha512-SjnXStoE0Q56HcFgNQ+9SsmJc0c8TqGARdI/T44KXy+Ets3r6x/ivhQozT66bMnCEjJRywYoxNurRTMlZF8VNg==", + "requires": { + "@firebase/app-types": "0.9.0", + "@firebase/util": "1.9.3" + } + }, + "@firebase/firestore": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-4.2.0.tgz", + "integrity": "sha512-iKZqIdOBJpJUcwY5airLX0W04TLrQSJuActOP1HG5WoIY5oyGTQE4Ml7hl5GW7mBqFieT4ojtUuDXj6MLrn1lA==", + "requires": { + "@firebase/component": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "@firebase/webchannel-wrapper": "0.10.3", + "@grpc/grpc-js": "~1.9.0", + "@grpc/proto-loader": "^0.7.8", + "node-fetch": "2.6.7", + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + } + } + }, + "@firebase/firestore-compat": { + "version": "0.3.18", + "resolved": "https://registry.npmjs.org/@firebase/firestore-compat/-/firestore-compat-0.3.18.tgz", + "integrity": "sha512-hkqv4mb1oScKbEtzfcK8Go8c0VpDWmbAvbD6B6XnphLqi27pkXgo9Rp+aSKlD7cBL29VMEekP5bEm9lSVfZpNw==", + "requires": { + "@firebase/component": "0.6.4", + "@firebase/firestore": "4.2.0", + "@firebase/firestore-types": "3.0.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + } + } + }, + "@firebase/firestore-types": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@firebase/firestore-types/-/firestore-types-3.0.0.tgz", + "integrity": "sha512-Meg4cIezHo9zLamw0ymFYBD4SMjLb+ZXIbuN7T7ddXN6MGoICmOTq3/ltdCGoDCS2u+H1XJs2u/cYp75jsX9Qw==", + "requires": {} + }, + "@firebase/functions": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@firebase/functions/-/functions-0.10.0.tgz", + "integrity": "sha512-2U+fMNxTYhtwSpkkR6WbBcuNMOVaI7MaH3cZ6UAeNfj7AgEwHwMIFLPpC13YNZhno219F0lfxzTAA0N62ndWzA==", + "requires": { + "@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" + }, + "dependencies": { + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + } + } + }, + "@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==", + "requires": { + "@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" + }, + "dependencies": { + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + } + } + }, + "@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==" + }, + "@firebase/installations": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/@firebase/installations/-/installations-0.6.4.tgz", + "integrity": "sha512-u5y88rtsp7NYkCHC3ElbFBrPtieUybZluXyzl7+4BsIz4sqb4vSAuwHEUgCgCeaQhvsnxDEU6icly8U9zsJigA==", + "requires": { + "@firebase/component": "0.6.4", + "@firebase/util": "1.9.3", + "idb": "7.0.1", + "tslib": "^2.1.0" + }, + "dependencies": { + "idb": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/idb/-/idb-7.0.1.tgz", + "integrity": "sha512-UUxlE7vGWK5RfB/fDwEGgRf84DY/ieqNha6msMV99UsEMQhJ1RwbCd8AYBj3QMgnE3VZnfQvm4oKVCJTYlqIgg==" + }, + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + } + } + }, + "@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==", + "requires": { + "@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" + }, + "dependencies": { + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + } + } + }, + "@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==", + "requires": {} + }, + "@firebase/logger": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.4.0.tgz", + "integrity": "sha512-eRKSeykumZ5+cJPdxxJRgAC3G5NknY2GwEbKfymdnXtnT0Ucm4pspfR6GT4MUQEDuJwRVbVcSx85kgJulMoFFA==", + "requires": { + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + } + } + }, + "@firebase/messaging": { + "version": "0.12.4", + "resolved": "https://registry.npmjs.org/@firebase/messaging/-/messaging-0.12.4.tgz", + "integrity": "sha512-6JLZct6zUaex4g7HI3QbzeUrg9xcnmDAPTWpkoMpd/GoSVWH98zDoWXMGrcvHeCAIsLpFMe4MPoZkJbrPhaASw==", + "requires": { + "@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" + }, + "dependencies": { + "idb": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/idb/-/idb-7.0.1.tgz", + "integrity": "sha512-UUxlE7vGWK5RfB/fDwEGgRf84DY/ieqNha6msMV99UsEMQhJ1RwbCd8AYBj3QMgnE3VZnfQvm4oKVCJTYlqIgg==" + }, + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + } + } + }, + "@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==", + "requires": { + "@firebase/component": "0.6.4", + "@firebase/messaging": "0.12.4", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + } + } + }, + "@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==" + }, + "@firebase/performance": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/@firebase/performance/-/performance-0.6.4.tgz", + "integrity": "sha512-HfTn/bd8mfy/61vEqaBelNiNnvAbUtME2S25A67Nb34zVuCSCRIX4SseXY6zBnOFj3oLisaEqhVcJmVPAej67g==", + "requires": { + "@firebase/component": "0.6.4", + "@firebase/installations": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + } + } + }, + "@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==", + "requires": { + "@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" + }, + "dependencies": { + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + } + } + }, + "@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==" + }, + "@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==", + "requires": { + "@firebase/component": "0.6.4", + "@firebase/installations": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + } + } + }, + "@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==", + "requires": { + "@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" + }, + "dependencies": { + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + } + } + }, + "@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==" + }, + "@firebase/storage": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/@firebase/storage/-/storage-0.11.2.tgz", + "integrity": "sha512-CtvoFaBI4hGXlXbaCHf8humajkbXhs39Nbh6MbNxtwJiCqxPy9iH3D3CCfXAvP0QvAAwmJUTK3+z9a++Kc4nkA==", + "requires": { + "@firebase/component": "0.6.4", + "@firebase/util": "1.9.3", + "node-fetch": "2.6.7", + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + } + } + }, + "@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==", + "requires": { + "@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" + }, + "dependencies": { + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + } + } + }, + "@firebase/storage-types": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@firebase/storage-types/-/storage-types-0.8.0.tgz", + "integrity": "sha512-isRHcGrTs9kITJC0AVehHfpraWFui39MPaU7Eo8QfWlqW7YPymBmRgjDrlOgFdURh6Cdeg07zmkLP5tzTKRSpg==", + "requires": {} + }, + "@firebase/util": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.9.3.tgz", + "integrity": "sha512-DY02CRhOZwpzO36fHpuVysz6JZrscPiBXD0fXp6qSrL9oNOx5KWICKdR95C0lSITzxp0TZosVyHqzatE8JbcjA==", + "requires": { + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + } + } + }, + "@firebase/webchannel-wrapper": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-0.10.3.tgz", + "integrity": "sha512-+ZplYUN3HOpgCfgInqgdDAbkGGVzES1cs32JJpeqoh87SkRobGXElJx+1GZSaDqzFL+bYiX18qEcBK76mYs8uA==" + }, + "@grpc/grpc-js": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.9.3.tgz", + "integrity": "sha512-b8iWtdrYIeT5fdZdS4Br/6h/kuk0PW5EVBUGk1amSbrpL8DlktJD43CdcCWwRdd6+jgwHhADSbL9CsNnm6EUPA==", + "requires": { + "@grpc/proto-loader": "^0.7.8", + "@types/node": ">=12.12.47" + } + }, + "@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==", + "requires": { + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", + "protobufjs": "^7.2.4", + "yargs": "^17.7.2" + } + }, + "@humanwhocodes/config-array": { + "version": "0.11.8", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", + "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", + "dev": true, + "requires": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + } + }, + "@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true + }, + "@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, + "@icons/material": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@icons/material/-/material-0.2.4.tgz", + "integrity": "sha512-QPcGmICAPbGLGb6F/yNf/KzKqvFx8z5qx3D1yFqVAjoFmXK35EgyW+cJ57Te3CNsmzblwtzakLGFqHPqrfb4Tw==", + "requires": {} + }, + "@jest/expect-utils": { + "version": "29.4.1", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.4.1.tgz", + "integrity": "sha512-w6YJMn5DlzmxjO00i9wu2YSozUYRBhIoJ6nQwpMYcBMtiqMGJm1QBzOf6DDgRao8dbtpDoaqLg6iiQTvv0UHhQ==", + "dev": true, + "requires": { + "jest-get-type": "^29.2.0" + } + }, + "@jest/schemas": { + "version": "29.4.0", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.4.0.tgz", + "integrity": "sha512-0E01f/gOZeNTG76i5eWWSupvSHaIINrTie7vCyjiYFKgzNdyEGd12BUv4oNBFHOqlHDbtoJi3HrQ38KCC90NsQ==", + "dev": true, + "requires": { + "@sinclair/typebox": "^0.25.16" + } + }, + "@jest/types": { + "version": "29.4.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.4.1.tgz", + "integrity": "sha512-zbrAXDUOnpJ+FMST2rV7QZOgec8rskg2zv8g2ajeqitp4tvZiyqTCYXANrKsM+ryj5o+LI+ZN2EgU9drrkiwSA==", + "dev": true, + "requires": { + "@jest/schemas": "^29.4.0", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "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, + "requires": { + "color-name": "~1.1.4" + } + }, + "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 + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@jridgewell/gen-mapping": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", + "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", + "dev": true, + "requires": { + "@jridgewell/set-array": "^1.0.0", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==" + }, + "@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==" + }, + "@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" + }, + "@jridgewell/trace-mapping": { + "version": "0.3.17", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", + "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", + "requires": { + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" + } + }, + "@nivo/annotations": { + "version": "0.80.0", + "resolved": "https://registry.npmjs.org/@nivo/annotations/-/annotations-0.80.0.tgz", + "integrity": "sha512-bC9z0CLjU07LULTMWsqpjovRtHxP7n8oJjqBQBLmHOGB4IfiLbrryBfu9+aEZH3VN2jXHhdpWUz+HxeZzOzsLg==", + "requires": { + "@nivo/colors": "0.80.0", + "@react-spring/web": "9.4.5", + "lodash": "^4.17.21" + } + }, + "@nivo/arcs": { + "version": "0.80.0", + "resolved": "https://registry.npmjs.org/@nivo/arcs/-/arcs-0.80.0.tgz", + "integrity": "sha512-g5m/wM36Ey45J3hrVDBPMw1Z6GOgIRwgb5zTh7TFoPuhRBZEDQLmctk8XYOm0xOMVCzsm6WkU5wlSQUeBY6IHQ==", + "requires": { + "@nivo/colors": "0.80.0", + "@react-spring/web": "9.4.5", + "d3-shape": "^1.3.5" + } + }, + "@nivo/axes": { + "version": "0.80.0", + "resolved": "https://registry.npmjs.org/@nivo/axes/-/axes-0.80.0.tgz", + "integrity": "sha512-AsUyaSHGwQVSEK8QXpsn8X+poZxvakLMYW7crKY1xTGPNw+SU4SSBohPVumm2jMH3fTSLNxLhAjWo71GBJXfdA==", + "requires": { + "@nivo/scales": "0.80.0", + "@react-spring/web": "9.4.5", + "d3-format": "^1.4.4", + "d3-time-format": "^3.0.0" + } + }, + "@nivo/bar": { + "version": "0.80.0", + "resolved": "https://registry.npmjs.org/@nivo/bar/-/bar-0.80.0.tgz", + "integrity": "sha512-woE/S12Sp+RKQeOHtp302WXfy5usj73cV/gjP95PzJxMv+Rn01i1Uwys3BILzc9h4+OxYuWTFqLADAySAmi7qQ==", + "requires": { + "@nivo/annotations": "0.80.0", + "@nivo/axes": "0.80.0", + "@nivo/colors": "0.80.0", + "@nivo/legends": "0.80.0", + "@nivo/scales": "0.80.0", + "@nivo/tooltip": "0.80.0", + "@react-spring/web": "9.4.5", + "d3-scale": "^3.2.3", + "d3-shape": "^1.3.5", + "lodash": "^4.17.21" + } + }, + "@nivo/colors": { + "version": "0.80.0", + "resolved": "https://registry.npmjs.org/@nivo/colors/-/colors-0.80.0.tgz", + "integrity": "sha512-T695Zr411FU4RPo7WDINOAn8f79DPP10SFJmDdEqELE+cbzYVTpXqLGZ7JMv88ko7EOf9qxLQgcBqY69rp9tHQ==", + "requires": { + "d3-color": "^2.0.0", + "d3-scale": "^3.2.3", + "d3-scale-chromatic": "^2.0.0", + "lodash": "^4.17.21" + } + }, + "@nivo/core": { + "version": "0.80.0", + "resolved": "https://registry.npmjs.org/@nivo/core/-/core-0.80.0.tgz", + "integrity": "sha512-6caih0RavXdWWSfde+rC2pk17WrX9YQlqK26BrxIdXzv3Ydzlh5SkrC7dR2TEvMGBhunzVeLOfiC2DWT1S8CFg==", + "requires": { + "@nivo/recompose": "0.80.0", + "@react-spring/web": "9.4.5", + "d3-color": "^2.0.0", + "d3-format": "^1.4.4", + "d3-interpolate": "^2.0.1", + "d3-scale": "^3.2.3", + "d3-scale-chromatic": "^2.0.0", + "d3-shape": "^1.3.5", + "d3-time-format": "^3.0.0", + "lodash": "^4.17.21" + } + }, + "@nivo/legends": { + "version": "0.80.0", + "resolved": "https://registry.npmjs.org/@nivo/legends/-/legends-0.80.0.tgz", + "integrity": "sha512-h0IUIPGygpbKIZZZWIxkkxOw4SO0rqPrqDrykjaoQz4CvL4HtLIUS3YRA4akKOVNZfS5agmImjzvIe0s3RvqlQ==", + "requires": {} + }, + "@nivo/line": { + "version": "0.80.0", + "resolved": "https://registry.npmjs.org/@nivo/line/-/line-0.80.0.tgz", + "integrity": "sha512-6UAD/y74qq3DDRnVb+QUPvXYojxMtwXMipGSNvCGk8omv1QZNTaUrbV+eQacvn9yh//a0yZcWipnpq0tGJyJCA==", + "requires": { + "@nivo/annotations": "0.80.0", + "@nivo/axes": "0.80.0", + "@nivo/colors": "0.80.0", + "@nivo/legends": "0.80.0", + "@nivo/scales": "0.80.0", + "@nivo/tooltip": "0.80.0", + "@nivo/voronoi": "0.80.0", + "@react-spring/web": "9.4.5", + "d3-shape": "^1.3.5" + } + }, + "@nivo/pie": { + "version": "0.80.0", + "resolved": "https://registry.npmjs.org/@nivo/pie/-/pie-0.80.0.tgz", + "integrity": "sha512-Zj2PtozUg5wizxdI/2o13YzwnBwf8lLrgc8vH7ucsgOu5nj6oLLpGTuNd3CBmRJHFGIGNT39bP63lKnB3P6qOQ==", + "requires": { + "@nivo/arcs": "0.80.0", + "@nivo/colors": "0.80.0", + "@nivo/legends": "0.80.0", + "@nivo/tooltip": "0.80.0", + "d3-shape": "^1.3.5" + } + }, + "@nivo/recompose": { + "version": "0.80.0", + "resolved": "https://registry.npmjs.org/@nivo/recompose/-/recompose-0.80.0.tgz", + "integrity": "sha512-iL3g7j3nJGD9+mRDbwNwt/IXDXH6E29mhShY1I7SP91xrfusZV9pSFf4EzyYgruNJk/2iqMuaqn+e+TVFra44A==", + "requires": { + "react-lifecycles-compat": "^3.0.4" + } + }, + "@nivo/scales": { + "version": "0.80.0", + "resolved": "https://registry.npmjs.org/@nivo/scales/-/scales-0.80.0.tgz", + "integrity": "sha512-4y2pQdCg+f3n4TKXC2tYuq71veZM+xPRQbOTgGYJpuBvMc7pQsXF9T5z7ryeIG9hkpXkrlyjecU6XcAG7tLSNg==", + "requires": { + "d3-scale": "^3.2.3", + "d3-time": "^1.0.11", + "d3-time-format": "^3.0.0", + "lodash": "^4.17.21" + }, + "dependencies": { + "d3-time": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-1.1.0.tgz", + "integrity": "sha512-Xh0isrZ5rPYYdqhAVk8VLnMEidhz5aP7htAADH6MfzgmmicPkTo8LhkLxci61/lCB7n7UmE3bN0leRt+qvkLxA==" + } + } + }, + "@nivo/tooltip": { + "version": "0.80.0", + "resolved": "https://registry.npmjs.org/@nivo/tooltip/-/tooltip-0.80.0.tgz", + "integrity": "sha512-qGmrreRwnCsYjn/LAuwBtxBn/tvG8y+rwgd4gkANLBAoXd3bzJyvmkSe+QJPhUG64bq57ibDK+lO2pC48a3/fw==", + "requires": { + "@react-spring/web": "9.4.5" + } + }, + "@nivo/voronoi": { + "version": "0.80.0", + "resolved": "https://registry.npmjs.org/@nivo/voronoi/-/voronoi-0.80.0.tgz", + "integrity": "sha512-zaJV3I3cRu1gHpsXCIEvp6GGlGY8P7D9CwAVCjYDGrz3W/+GKN0kA7qGyHTC97zVxJtfefxSPlP/GtOdxac+qw==", + "requires": { + "d3-delaunay": "^5.3.0", + "d3-scale": "^3.2.3" + } + }, + "@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true + }, + "@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "requires": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + } + }, + "@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==" + }, + "@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" + }, + "@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" + }, + "@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" + }, + "@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "requires": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" + }, + "@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" + }, + "@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" + }, + "@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" + }, + "@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" + }, + "@react-spring/animated": { + "version": "9.4.5", + "resolved": "https://registry.npmjs.org/@react-spring/animated/-/animated-9.4.5.tgz", + "integrity": "sha512-KWqrtvJSMx6Fj9nMJkhTwM9r6LIriExDRV6YHZV9HKQsaolUFppgkOXpC+rsL1JEtEvKv6EkLLmSqHTnuYjiIA==", + "requires": { + "@react-spring/shared": "~9.4.5", + "@react-spring/types": "~9.4.5" + } + }, + "@react-spring/core": { + "version": "9.4.5", + "resolved": "https://registry.npmjs.org/@react-spring/core/-/core-9.4.5.tgz", + "integrity": "sha512-83u3FzfQmGMJFwZLAJSwF24/ZJctwUkWtyPD7KYtNagrFeQKUH1I05ZuhmCmqW+2w1KDW1SFWQ43RawqfXKiiQ==", + "requires": { + "@react-spring/animated": "~9.4.5", + "@react-spring/rafz": "~9.4.5", + "@react-spring/shared": "~9.4.5", + "@react-spring/types": "~9.4.5" + } + }, + "@react-spring/rafz": { + "version": "9.4.5", + "resolved": "https://registry.npmjs.org/@react-spring/rafz/-/rafz-9.4.5.tgz", + "integrity": "sha512-swGsutMwvnoyTRxvqhfJBtGM8Ipx6ks0RkIpNX9F/U7XmyPvBMGd3GgX/mqxZUpdlsuI1zr/jiYw+GXZxAlLcQ==" + }, + "@react-spring/shared": { + "version": "9.4.5", + "resolved": "https://registry.npmjs.org/@react-spring/shared/-/shared-9.4.5.tgz", + "integrity": "sha512-JhMh3nFKsqyag0KM5IIM8BQANGscTdd0mMv3BXsUiMZrcjQTskyfnv5qxEeGWbJGGar52qr5kHuBHtCjQOzniA==", + "requires": { + "@react-spring/rafz": "~9.4.5", + "@react-spring/types": "~9.4.5" + } + }, + "@react-spring/types": { + "version": "9.4.5", + "resolved": "https://registry.npmjs.org/@react-spring/types/-/types-9.4.5.tgz", + "integrity": "sha512-mpRIamoHwql0ogxEUh9yr4TP0xU5CWyZxVQeccGkHHF8kPMErtDXJlxyo0lj+telRF35XNihtPTWoflqtyARmg==" + }, + "@react-spring/web": { + "version": "9.4.5", + "resolved": "https://registry.npmjs.org/@react-spring/web/-/web-9.4.5.tgz", + "integrity": "sha512-NGAkOtKmOzDEctL7MzRlQGv24sRce++0xAY7KlcxmeVkR7LRSGkoXHaIfm9ObzxPMcPHQYQhf3+X9jepIFNHQA==", + "requires": { + "@react-spring/animated": "~9.4.5", + "@react-spring/core": "~9.4.5", + "@react-spring/shared": "~9.4.5", + "@react-spring/types": "~9.4.5" + } + }, + "@remix-run/router": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.1.0.tgz", + "integrity": "sha512-rGl+jH/7x1KBCQScz9p54p0dtPLNeKGb3e0wD2H5/oZj41bwQUnXdzbj2TbUAFhvD7cp9EyEQA4dEgpUFa1O7Q==" + }, + "@rollup/pluginutils": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.0.5.tgz", + "integrity": "sha512-6aEYR910NyP73oHiJglti74iRyOwgFU4x3meH/H8OJx6Ry0j6cOVZ5X/wTvub7G7Ao6qaHBEaNsV3GLJkSsF+Q==", + "dev": true, + "requires": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^2.3.1" + } + }, + "@sinclair/typebox": { + "version": "0.25.21", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.25.21.tgz", + "integrity": "sha512-gFukHN4t8K4+wVC+ECqeqwzBDeFeTzBXroBTqE6vcWrQGbEUpHO7LYdG0f4xnvYq4VOEwITSlHlp0JBAIFMS/g==", + "dev": true + }, + "@svgr/babel-plugin-add-jsx-attribute": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz", + "integrity": "sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==", + "dev": true, + "requires": {} + }, + "@svgr/babel-plugin-remove-jsx-attribute": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-8.0.0.tgz", + "integrity": "sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==", + "dev": true, + "requires": {} + }, + "@svgr/babel-plugin-remove-jsx-empty-expression": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-8.0.0.tgz", + "integrity": "sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==", + "dev": true, + "requires": {} + }, + "@svgr/babel-plugin-replace-jsx-attribute-value": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-8.0.0.tgz", + "integrity": "sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==", + "dev": true, + "requires": {} + }, + "@svgr/babel-plugin-svg-dynamic-title": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-8.0.0.tgz", + "integrity": "sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==", + "dev": true, + "requires": {} + }, + "@svgr/babel-plugin-svg-em-dimensions": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-8.0.0.tgz", + "integrity": "sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==", + "dev": true, + "requires": {} + }, + "@svgr/babel-plugin-transform-react-native-svg": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-8.1.0.tgz", + "integrity": "sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==", + "dev": true, + "requires": {} + }, + "@svgr/babel-plugin-transform-svg-component": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-8.0.0.tgz", + "integrity": "sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==", + "dev": true, + "requires": {} + }, + "@svgr/babel-preset": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-8.1.0.tgz", + "integrity": "sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==", + "dev": true, + "requires": { + "@svgr/babel-plugin-add-jsx-attribute": "8.0.0", + "@svgr/babel-plugin-remove-jsx-attribute": "8.0.0", + "@svgr/babel-plugin-remove-jsx-empty-expression": "8.0.0", + "@svgr/babel-plugin-replace-jsx-attribute-value": "8.0.0", + "@svgr/babel-plugin-svg-dynamic-title": "8.0.0", + "@svgr/babel-plugin-svg-em-dimensions": "8.0.0", + "@svgr/babel-plugin-transform-react-native-svg": "8.1.0", + "@svgr/babel-plugin-transform-svg-component": "8.0.0" + } + }, + "@svgr/core": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/core/-/core-8.1.0.tgz", + "integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==", + "dev": true, + "requires": { + "@babel/core": "^7.21.3", + "@svgr/babel-preset": "8.1.0", + "camelcase": "^6.2.0", + "cosmiconfig": "^8.1.3", + "snake-case": "^3.0.4" + }, + "dependencies": { + "cosmiconfig": { + "version": "8.3.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "dev": true, + "requires": { + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0", + "path-type": "^4.0.0" + } + }, + "typescript": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", + "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", + "dev": true, + "optional": true, + "peer": true + } + } + }, + "@svgr/hast-util-to-babel-ast": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-8.0.0.tgz", + "integrity": "sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q==", + "dev": true, + "requires": { + "@babel/types": "^7.21.3", + "entities": "^4.4.0" + } + }, + "@svgr/plugin-jsx": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-8.1.0.tgz", + "integrity": "sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA==", + "dev": true, + "requires": { + "@babel/core": "^7.21.3", + "@svgr/babel-preset": "8.1.0", + "@svgr/hast-util-to-babel-ast": "8.0.0", + "svg-parser": "^2.0.4" + } + }, + "@svgr/plugin-svgo": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/plugin-svgo/-/plugin-svgo-8.1.0.tgz", + "integrity": "sha512-Ywtl837OGO9pTLIN/onoWLmDQ4zFUycI1g76vuKGEz6evR/ZTJlJuz3G/fIkb6OVBJ2g0o6CGJzaEjfmEo3AHA==", + "dev": true, + "requires": { + "cosmiconfig": "^8.1.3", + "deepmerge": "^4.3.1", + "svgo": "^3.0.2" + }, + "dependencies": { + "cosmiconfig": { + "version": "8.3.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "dev": true, + "requires": { + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0", + "path-type": "^4.0.0" + } + }, + "typescript": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", + "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", + "dev": true, + "optional": true, + "peer": true + } + } + }, + "@svgr/rollup": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/rollup/-/rollup-8.1.0.tgz", + "integrity": "sha512-0XR1poYvPQoPpmfDYLEqUGu5ePAQ4pdgN3VFsZBNAeze7qubVpsIY1o1R6PZpKep/DKu33GSm2NhwpCLkMs2Cw==", + "dev": true, + "requires": { + "@babel/core": "^7.21.3", + "@babel/plugin-transform-react-constant-elements": "^7.21.3", + "@babel/preset-env": "^7.20.2", + "@babel/preset-react": "^7.18.6", + "@babel/preset-typescript": "^7.21.0", + "@rollup/pluginutils": "^5.0.2", + "@svgr/core": "8.1.0", + "@svgr/plugin-jsx": "8.1.0", + "@svgr/plugin-svgo": "8.1.0" + } + }, + "@testing-library/dom": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.20.0.tgz", + "integrity": "sha512-d9ULIT+a4EXLX3UU8FBjauG9NnsZHkHztXoIcTsOKoOw030fyjheN9svkTULjJxtYag9DZz5Jz5qkWZDPxTFwA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "^5.0.0", + "chalk": "^4.1.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.4.4", + "pretty-format": "^27.0.2" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "aria-query": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz", + "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==", + "dev": true, + "requires": { + "deep-equal": "^2.0.5" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "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, + "requires": { + "color-name": "~1.1.4" + } + }, + "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 + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@testing-library/jest-dom": { + "version": "5.16.5", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.16.5.tgz", + "integrity": "sha512-N5ixQ2qKpi5OLYfwQmUb/5mSV9LneAcaUfp32pn4yCnpb8r/Yz0pXFPck21dIicKmi+ta5WRAknkZCfA8refMA==", + "dev": true, + "requires": { + "@adobe/css-tools": "^4.0.1", + "@babel/runtime": "^7.9.2", + "@types/testing-library__jest-dom": "^5.9.1", + "aria-query": "^5.0.0", + "chalk": "^3.0.0", + "css.escape": "^1.5.1", + "dom-accessibility-api": "^0.5.6", + "lodash": "^4.17.15", + "redent": "^3.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "aria-query": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz", + "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==", + "dev": true, + "requires": { + "deep-equal": "^2.0.5" + } + }, + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "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, + "requires": { + "color-name": "~1.1.4" + } + }, + "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 + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@testing-library/react": { + "version": "13.4.0", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-13.4.0.tgz", + "integrity": "sha512-sXOGON+WNTh3MLE9rve97ftaZukN3oNf2KjDy7YTx6hcTO2uuLHuCGynMDhFwGw/jYf4OJ2Qk0i4i79qMNNkyw==", + "dev": true, + "requires": { + "@babel/runtime": "^7.12.5", + "@testing-library/dom": "^8.5.0", + "@types/react-dom": "^18.0.0" + } + }, + "@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "dev": true + }, + "@trivago/prettier-plugin-sort-imports": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@trivago/prettier-plugin-sort-imports/-/prettier-plugin-sort-imports-4.1.1.tgz", + "integrity": "sha512-dQ2r2uzNr1x6pJsuh/8x0IRA3CBUB+pWEW3J/7N98axqt7SQSm+2fy0FLNXvXGg77xEDC7KHxJlHfLYyi7PDcw==", + "dev": true, + "requires": { + "@babel/generator": "7.17.7", + "@babel/parser": "^7.20.5", + "@babel/traverse": "7.17.3", + "@babel/types": "7.17.0", + "javascript-natural-sort": "0.7.1", + "lodash": "^4.17.21" + }, + "dependencies": { + "@babel/generator": { + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.7.tgz", + "integrity": "sha512-oLcVCTeIFadUoArDTwpluncplrYBmTCCZZgXCbgNGvOBBiSDDK3eWO4b/+eOTli5tKv1lg+a5/NAXg+nTcei1w==", + "dev": true, + "requires": { + "@babel/types": "^7.17.0", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + } + }, + "@babel/traverse": { + "version": "7.17.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.17.3.tgz", + "integrity": "sha512-5irClVky7TxRWIRtxlh2WPUUOLhcPN06AGgaQSB8AEwuyEBgJVuJ5imdHm5zxk8w0QS5T+tDfnDxAlhWjpb7cw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.16.7", + "@babel/generator": "^7.17.3", + "@babel/helper-environment-visitor": "^7.16.7", + "@babel/helper-function-name": "^7.16.7", + "@babel/helper-hoist-variables": "^7.16.7", + "@babel/helper-split-export-declaration": "^7.16.7", + "@babel/parser": "^7.17.3", + "@babel/types": "^7.17.0", + "debug": "^4.1.0", + "globals": "^11.1.0" + } + }, + "@babel/types": { + "version": "7.17.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.17.0.tgz", + "integrity": "sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.16.7", + "to-fast-properties": "^2.0.0" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "dev": true + } + } + }, + "@trysound/sax": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", + "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==", + "dev": true + }, + "@types/aria-query": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.1.tgz", + "integrity": "sha512-XTIieEY+gvJ39ChLcB4If5zHtPxt3Syj5rgZR+e1ctpmK8NjPf0zFqsz4JpLJT0xla9GFDKjy8Cpu331nrmE1Q==", + "dev": true + }, + "@types/chai": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.4.tgz", + "integrity": "sha512-KnRanxnpfpjUTqTCXslZSEdLfXExwgNxYPdiO2WGUj8+HDjFi8R3k5RVKPeSCzLjCcshCAtVO2QBbVuAV4kTnw==", + "dev": true + }, + "@types/chai-subset": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@types/chai-subset/-/chai-subset-1.3.3.tgz", + "integrity": "sha512-frBecisrNGz+F4T6bcc+NLeolfiojh5FxW2klu669+8BARtyQv2C/GkNW6FUodVe4BroGMP/wER/YDGc7rEllw==", + "dev": true, + "requires": { + "@types/chai": "*" + } + }, + "@types/cookie": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.3.3.tgz", + "integrity": "sha512-LKVP3cgXBT9RYj+t+9FDKwS5tdI+rPBXaNSkma7hvqy35lc7mAokC2zsqWJH0LaqIt3B962nuYI77hsJoT1gow==" + }, + "@types/estree": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.3.tgz", + "integrity": "sha512-CS2rOaoQ/eAgAfcTfq6amKG7bsN+EMcgGY4FAFQdvSj2y1ixvOZTUA9mOtCai7E1SYu283XNw7urKK30nP3wkQ==", + "dev": true + }, + "@types/hoist-non-react-statics": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", + "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==", + "requires": { + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0" + } + }, + "@types/istanbul-lib-coverage": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", + "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", + "dev": true + }, + "@types/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "*" + } + }, + "@types/istanbul-reports": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", + "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", + "dev": true, + "requires": { + "@types/istanbul-lib-report": "*" + } + }, + "@types/jest": { + "version": "29.4.0", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.4.0.tgz", + "integrity": "sha512-VaywcGQ9tPorCX/Jkkni7RWGFfI11whqzs8dvxF41P17Z+z872thvEvlIbznjPJ02kl1HMX3LmLOonsj2n7HeQ==", + "dev": true, + "requires": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + }, + "pretty-format": { + "version": "29.4.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.4.1.tgz", + "integrity": "sha512-dt/Z761JUVsrIKaY215o1xQJBGlSmTx/h4cSqXqjHLnU1+Kt+mavVE7UgqJJO5ukx5HjSswHfmXz4LjS2oIJfg==", + "dev": true, + "requires": { + "@jest/schemas": "^29.4.0", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + } + } + } + }, + "@types/json-schema": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", + "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", + "dev": true + }, + "@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true + }, + "@types/node": { + "version": "18.11.17", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.17.tgz", + "integrity": "sha512-HJSUJmni4BeDHhfzn6nF0sVmd1SMezP7/4F0Lq+aXzmp2xm9O7WXrUtHW/CHlYVtZUbByEvWidHqRtcJXGF2Ng==" + }, + "@types/prop-types": { + "version": "15.7.5", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", + "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" + }, + "@types/react": { + "version": "18.0.26", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.26.tgz", + "integrity": "sha512-hCR3PJQsAIXyxhTNSiDFY//LhnMZWpNNr5etoCqx/iUfGc5gXWtQR2Phl908jVR6uPXacojQWTg4qRpkxTuGug==", + "requires": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "@types/react-color": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/react-color/-/react-color-3.0.7.tgz", + "integrity": "sha512-IGZA7e8Oia0+Sb3/1KP0qTThGelZ9DRspfeLrFWQWv5vXHiYlJJQMC2kgQr75CtP4uL8/kvT8qBgrOVlxVoNTw==", + "requires": { + "@types/react": "*", + "@types/reactcss": "*" + } + }, + "@types/react-dom": { + "version": "18.0.9", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.9.tgz", + "integrity": "sha512-qnVvHxASt/H7i+XG1U1xMiY5t+IHcPGUK7TDMDzom08xa7e86eCeKOiLZezwCKVxJn6NEiiy2ekgX8aQssjIKg==", + "dev": true, + "requires": { + "@types/react": "*" + } + }, + "@types/reactcss": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/reactcss/-/reactcss-1.2.7.tgz", + "integrity": "sha512-MYPuVierMjIo0EDQnNauvBA94IOeB9lfjC619g+26u7ilsTtoFv6X7eQvaw79Fqqpi0yzoSMz0nUazeJlQUZnA==", + "requires": { + "@types/react": "*" + } + }, + "@types/scheduler": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", + "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==" + }, + "@types/semver": { + "version": "7.3.13", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz", + "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==", + "dev": true + }, + "@types/stack-utils": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", + "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", + "dev": true + }, + "@types/styled-components": { + "version": "5.1.26", + "resolved": "https://registry.npmjs.org/@types/styled-components/-/styled-components-5.1.26.tgz", + "integrity": "sha512-KuKJ9Z6xb93uJiIyxo/+ksS7yLjS1KzG6iv5i78dhVg/X3u5t1H7juRWqVmodIdz6wGVaIApo1u01kmFRdJHVw==", + "dev": true, + "requires": { + "@types/hoist-non-react-statics": "*", + "@types/react": "*", + "csstype": "^3.0.2" + } + }, + "@types/testing-library__jest-dom": { + "version": "5.14.5", + "resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.5.tgz", + "integrity": "sha512-SBwbxYoyPIvxHbeHxTZX2Pe/74F/tX2/D3mMvzabdeJ25bBojfW0TyB8BHrbq/9zaaKICJZjLP+8r6AeZMFCuQ==", + "dev": true, + "requires": { + "@types/jest": "*" + } + }, + "@types/yargs": { + "version": "17.0.20", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.20.tgz", + "integrity": "sha512-eknWrTHofQuPk2iuqDm1waA7V6xPlbgBoaaXEgYkClhLOnB0TtbW+srJaOToAgawPxPlHQzwypFA2bhZaUGP5A==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "@types/yargs-parser": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", + "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", + "dev": true + }, + "@typescript-eslint/eslint-plugin": { + "version": "5.46.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.46.1.tgz", + "integrity": "sha512-YpzNv3aayRBwjs4J3oz65eVLXc9xx0PDbIRisHj+dYhvBn02MjYOD96P8YGiWEIFBrojaUjxvkaUpakD82phsA==", + "dev": true, + "requires": { + "@typescript-eslint/scope-manager": "5.46.1", + "@typescript-eslint/type-utils": "5.46.1", + "@typescript-eslint/utils": "5.46.1", + "debug": "^4.3.4", + "ignore": "^5.2.0", + "natural-compare-lite": "^1.4.0", + "regexpp": "^3.2.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "dependencies": { + "semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, + "@typescript-eslint/parser": { + "version": "5.46.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.46.1.tgz", + "integrity": "sha512-RelQ5cGypPh4ySAtfIMBzBGyrNerQcmfA1oJvPj5f+H4jI59rl9xxpn4bonC0tQvUKOEN7eGBFWxFLK3Xepneg==", + "dev": true, + "requires": { + "@typescript-eslint/scope-manager": "5.46.1", + "@typescript-eslint/types": "5.46.1", + "@typescript-eslint/typescript-estree": "5.46.1", + "debug": "^4.3.4" + } + }, + "@typescript-eslint/scope-manager": { + "version": "5.46.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.46.1.tgz", + "integrity": "sha512-iOChVivo4jpwUdrJZyXSMrEIM/PvsbbDOX1y3UCKjSgWn+W89skxWaYXACQfxmIGhPVpRWK/VWPYc+bad6smIA==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.46.1", + "@typescript-eslint/visitor-keys": "5.46.1" + } + }, + "@typescript-eslint/type-utils": { + "version": "5.46.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.46.1.tgz", + "integrity": "sha512-V/zMyfI+jDmL1ADxfDxjZ0EMbtiVqj8LUGPAGyBkXXStWmCUErMpW873zEHsyguWCuq2iN4BrlWUkmuVj84yng==", + "dev": true, + "requires": { + "@typescript-eslint/typescript-estree": "5.46.1", + "@typescript-eslint/utils": "5.46.1", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + } + }, + "@typescript-eslint/types": { + "version": "5.46.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.46.1.tgz", + "integrity": "sha512-Z5pvlCaZgU+93ryiYUwGwLl9AQVB/PQ1TsJ9NZ/gHzZjN7g9IAn6RSDkpCV8hqTwAiaj6fmCcKSQeBPlIpW28w==", + "dev": true + }, + "@typescript-eslint/typescript-estree": { + "version": "5.46.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.46.1.tgz", + "integrity": "sha512-j9W4t67QiNp90kh5Nbr1w92wzt+toiIsaVPnEblB2Ih2U9fqBTyqV9T3pYWZBRt6QoMh/zVWP59EpuCjc4VRBg==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.46.1", + "@typescript-eslint/visitor-keys": "5.46.1", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "dependencies": { + "semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, + "@typescript-eslint/utils": { + "version": "5.46.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.46.1.tgz", + "integrity": "sha512-RBdBAGv3oEpFojaCYT4Ghn4775pdjvwfDOfQ2P6qzNVgQOVrnSPe5/Pb88kv7xzYQjoio0eKHKB9GJ16ieSxvA==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.46.1", + "@typescript-eslint/types": "5.46.1", + "@typescript-eslint/typescript-estree": "5.46.1", + "eslint-scope": "^5.1.1", + "eslint-utils": "^3.0.0", + "semver": "^7.3.7" + }, + "dependencies": { + "eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + } + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + }, + "semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, + "@typescript-eslint/visitor-keys": { + "version": "5.46.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.46.1.tgz", + "integrity": "sha512-jczZ9noovXwy59KjRTk1OftT78pwygdcmCuBf8yMoWt/8O8l+6x2LSEze0E4TeepXK4MezW3zGSyoDRZK7Y9cg==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.46.1", + "eslint-visitor-keys": "^3.3.0" + } + }, + "@vitejs/plugin-react": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-3.0.0.tgz", + "integrity": "sha512-1mvyPc0xYW5G8CHQvJIJXLoMjl5Ct3q2g5Y2s6Ccfgwm45y48LBvsla7az+GkkAtYikWQ4Lxqcsq5RHLcZgtNQ==", + "dev": true, + "requires": { + "@babel/core": "^7.20.5", + "@babel/plugin-transform-react-jsx-self": "^7.18.6", + "@babel/plugin-transform-react-jsx-source": "^7.19.6", + "magic-string": "^0.27.0", + "react-refresh": "^0.14.0" + } + }, + "@vitest/expect": { + "version": "0.28.3", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-0.28.3.tgz", + "integrity": "sha512-dnxllhfln88DOvpAK1fuI7/xHwRgTgR4wdxHldPaoTaBu6Rh9zK5b//v/cjTkhOfNP/AJ8evbNO8H7c3biwd1g==", + "dev": true, + "requires": { + "@vitest/spy": "0.28.3", + "@vitest/utils": "0.28.3", + "chai": "^4.3.7" + } + }, + "@vitest/runner": { + "version": "0.28.3", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-0.28.3.tgz", + "integrity": "sha512-P0qYbATaemy1midOLkw7qf8jraJszCoEvjQOSlseiXZyEDaZTZ50J+lolz2hWiWv6RwDu1iNseL9XLsG0Jm2KQ==", + "dev": true, + "requires": { + "@vitest/utils": "0.28.3", + "p-limit": "^4.0.0", + "pathe": "^1.1.0" + }, + "dependencies": { + "p-limit": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", + "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "dev": true, + "requires": { + "yocto-queue": "^1.0.0" + } + }, + "yocto-queue": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", + "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", + "dev": true + } + } + }, + "@vitest/spy": { + "version": "0.28.3", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-0.28.3.tgz", + "integrity": "sha512-jULA6suS6CCr9VZfr7/9x97pZ0hC55prnUNHNrg5/q16ARBY38RsjsfhuUXt6QOwvIN3BhSS0QqPzyh5Di8g6w==", + "dev": true, + "requires": { + "tinyspy": "^1.0.2" + } + }, + "@vitest/utils": { + "version": "0.28.3", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-0.28.3.tgz", + "integrity": "sha512-YHiQEHQqXyIbhDqETOJUKx9/psybF7SFFVCNfOvap0FvyUqbzTSDCa3S5lL4C0CLXkwVZttz9xknDoyHMguFRQ==", + "dev": true, + "requires": { + "cli-truncate": "^3.1.0", + "diff": "^5.1.0", + "loupe": "^2.3.6", + "picocolors": "^1.0.0", + "pretty-format": "^27.5.1" + } + }, + "abab": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", + "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", + "dev": true + }, + "acorn": { + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", + "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==", + "dev": true + }, + "acorn-globals": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz", + "integrity": "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==", + "dev": true, + "requires": { + "acorn": "^8.1.0", + "acorn-walk": "^8.0.2" + } + }, + "acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "requires": {} + }, + "acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true + }, + "agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "requires": { + "debug": "4" + } + }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "aria-query": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz", + "integrity": "sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==", + "dev": true, + "requires": { + "@babel/runtime": "^7.10.2", + "@babel/runtime-corejs3": "^7.10.2" + } + }, + "array-includes": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz", + "integrity": "sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "get-intrinsic": "^1.1.3", + "is-string": "^1.0.7" + } + }, + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true + }, + "array.prototype.flat": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz", + "integrity": "sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0" + } + }, + "array.prototype.flatmap": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz", + "integrity": "sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0" + } + }, + "array.prototype.tosorted": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.1.tgz", + "integrity": "sha512-pZYPXPRl2PqWcsUs6LOMn+1f1532nEoPTYowBtqLwAW+W8vSVhkIGnmOX1t/UQjD6YGI0vcD2B1U7ZFGQH9jnQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0", + "get-intrinsic": "^1.1.3" + } + }, + "assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true + }, + "ast-types-flow": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", + "integrity": "sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==", + "dev": true + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "dev": true + }, + "axe-core": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.6.1.tgz", + "integrity": "sha512-lCZN5XRuOnpG4bpMq8v0khrWtUOn+i8lZSb6wHZH56ZfbIEv6XwJV84AAueh9/zi7qPVJ/E4yz6fmsiyOmXR4w==", + "dev": true + }, + "axios": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.2.1.tgz", + "integrity": "sha512-I88cFiGu9ryt/tfVEi4kX2SITsvDddTajXTOFmt2uK1ZVA8LytjtdeyefdQWEf5PU8w+4SSJDoYnggflB5tW4A==", + "requires": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "axobject-query": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz", + "integrity": "sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA==", + "dev": true + }, + "babel-plugin-polyfill-corejs2": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.6.tgz", + "integrity": "sha512-jhHiWVZIlnPbEUKSSNb9YoWcQGdlTLq7z1GHL4AjFxaoOUMuuEVJ+Y4pAaQUGOGk93YsVCKPbqbfw3m0SM6H8Q==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.22.6", + "@babel/helper-define-polyfill-provider": "^0.4.3", + "semver": "^6.3.1" + } + }, + "babel-plugin-polyfill-corejs3": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.5.tgz", + "integrity": "sha512-Q6CdATeAvbScWPNLB8lzSO7fgUVBkQt6zLgNlfyeCr/EQaEQR+bWiBYYPYAFyE528BMjRhL+1QBMOI4jc/c5TA==", + "dev": true, + "requires": { + "@babel/helper-define-polyfill-provider": "^0.4.3", + "core-js-compat": "^3.32.2" + } + }, + "babel-plugin-polyfill-regenerator": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.3.tgz", + "integrity": "sha512-8sHeDOmXC8csczMrYEOf0UTNa4yE2SxV5JGeT/LP1n0OYVDUUFPxG9vdk2AlDlIit4t+Kf0xCtpgXPBwnn/9pw==", + "dev": true, + "requires": { + "@babel/helper-define-polyfill-provider": "^0.4.3" + } + }, + "babel-plugin-styled-components": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-2.0.7.tgz", + "integrity": "sha512-i7YhvPgVqRKfoQ66toiZ06jPNA3p6ierpfUuEWxNF+fV27Uv5gxBkf8KZLHUCc1nFA9j6+80pYoIpqCeyW3/bA==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.16.0", + "@babel/helper-module-imports": "^7.16.0", + "babel-plugin-syntax-jsx": "^6.18.0", + "lodash": "^4.17.11", + "picomatch": "^2.3.0" + } + }, + "babel-plugin-syntax-jsx": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", + "integrity": "sha512-qrPaCSo9c8RHNRHIotaufGbuOBN8rtdC4QrrFFc43vyWCCz7Kl7GL1PGaXtMGQZUXrkCjNEgxDfmAuAabr/rlw==" + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "browserslist": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.1.tgz", + "integrity": "sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30001541", + "electron-to-chromium": "^1.4.535", + "node-releases": "^2.0.13", + "update-browserslist-db": "^1.0.13" + } + }, + "buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true + }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true + }, + "camelize": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", + "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==" + }, + "caniuse-lite": { + "version": "1.0.30001551", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001551.tgz", + "integrity": "sha512-vtBAez47BoGMMzlbYhfXrMV1kvRF2WP/lqiMuDu1Sb4EE4LKEgjopFDSRtZfdVnslNRpOqV/woE+Xgrwj6VQlg==", + "dev": true + }, + "chai": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.7.tgz", + "integrity": "sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==", + "dev": true, + "requires": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^4.1.2", + "get-func-name": "^2.0.0", + "loupe": "^2.3.1", + "pathval": "^1.1.1", + "type-detect": "^4.0.5" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==", + "dev": true + }, + "ci-info": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.7.1.tgz", + "integrity": "sha512-4jYS4MOAaCIStSRwiuxc4B8MYhIe676yO1sYGzARnjXkWpmzZMMYxY6zu8WYWDhSuth5zhrQ1rhNSibyyvv4/w==", + "dev": true + }, + "cli-truncate": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-3.1.0.tgz", + "integrity": "sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==", + "dev": true, + "requires": { + "slice-ansi": "^5.0.0", + "string-width": "^5.0.0" + } + }, + "cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "dependencies": { + "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==" + }, + "is-fullwidth-code-point": { + "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==" + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "confusing-browser-globals": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz", + "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==", + "dev": true + }, + "convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==" + }, + "core-js-compat": { + "version": "3.33.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.33.1.tgz", + "integrity": "sha512-6pYKNOgD/j/bkC5xS5IIg6bncid3rfrI42oBH1SQJbsmYPKF7rhzcFzYCcxYMmNQQ0rCEB8WqpW7QHndOggaeQ==", + "dev": true, + "requires": { + "browserslist": "^4.22.1" + } + }, + "core-js-pure": { + "version": "3.26.1", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.26.1.tgz", + "integrity": "sha512-VVXcDpp/xJ21KdULRq/lXdLzQAtX7+37LzpyfFM973il0tWSsDEoyzG38G14AjTpK9VTfiNM9jnFauq/CpaWGQ==", + "dev": true + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "css-color-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", + "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==" + }, + "css-select": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "dev": true, + "requires": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + } + }, + "css-to-react-native": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.0.0.tgz", + "integrity": "sha512-Ro1yETZA813eoyUp2GDBhG2j+YggidUmzO1/v9eYBKR2EHVEniE2MI/NqpTQ954BMpTPZFsGNPm46qFB9dpaPQ==", + "requires": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^4.0.2" + } + }, + "css-tree": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", + "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", + "dev": true, + "requires": { + "mdn-data": "2.0.30", + "source-map-js": "^1.0.1" + } + }, + "css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "dev": true + }, + "css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "dev": true + }, + "csso": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", + "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", + "dev": true, + "requires": { + "css-tree": "~2.2.0" + }, + "dependencies": { + "css-tree": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", + "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", + "dev": true, + "requires": { + "mdn-data": "2.0.28", + "source-map-js": "^1.0.1" + } + }, + "mdn-data": { + "version": "2.0.28", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", + "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", + "dev": true + } + } + }, + "cssom": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", + "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==", + "dev": true + }, + "cssstyle": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", + "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", + "dev": true, + "requires": { + "cssom": "~0.3.6" + }, + "dependencies": { + "cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", + "dev": true + } + } + }, + "csstype": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz", + "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==" + }, + "d3-array": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", + "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", + "requires": { + "internmap": "^1.0.0" + } + }, + "d3-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-2.0.0.tgz", + "integrity": "sha512-SPXi0TSKPD4g9tw0NMZFnR95XVgUZiBH+uUTqQuDu1OsE2zomHU7ho0FISciaPvosimixwHFl3WHLGabv6dDgQ==" + }, + "d3-delaunay": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-5.3.0.tgz", + "integrity": "sha512-amALSrOllWVLaHTnDLHwMIiz0d1bBu9gZXd1FiLfXf8sHcX9jrcj81TVZOqD4UX7MgBZZ07c8GxzEgBpJqc74w==", + "requires": { + "delaunator": "4" + } + }, + "d3-format": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.4.5.tgz", + "integrity": "sha512-J0piedu6Z8iB6TbIGfZgDzfXxUFN3qQRMofy2oPdXzQibYGqPB/9iMcxr/TGalU+2RsyDO+U4f33id8tbnSRMQ==" + }, + "d3-interpolate": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-2.0.1.tgz", + "integrity": "sha512-c5UhwwTs/yybcmTpAVqwSFl6vrQ8JZJoT5F7xNFK9pymv5C0Ymcc9/LIJHtYIggg/yS9YHw8i8O8tgb9pupjeQ==", + "requires": { + "d3-color": "1 - 2" + } + }, + "d3-path": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", + "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==" + }, + "d3-scale": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-3.3.0.tgz", + "integrity": "sha512-1JGp44NQCt5d1g+Yy+GeOnZP7xHo0ii8zsQp6PGzd+C1/dl0KGsp9A7Mxwp+1D1o4unbTTxVdU/ZOIEBoeZPbQ==", + "requires": { + "d3-array": "^2.3.0", + "d3-format": "1 - 2", + "d3-interpolate": "1.2.0 - 2", + "d3-time": "^2.1.1", + "d3-time-format": "2 - 3" + } + }, + "d3-scale-chromatic": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-2.0.0.tgz", + "integrity": "sha512-LLqy7dJSL8yDy7NRmf6xSlsFZ6zYvJ4BcWFE4zBrOPnQERv9zj24ohnXKRbyi9YHnYV+HN1oEO3iFK971/gkzA==", + "requires": { + "d3-color": "1 - 2", + "d3-interpolate": "1 - 2" + } + }, + "d3-shape": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", + "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", + "requires": { + "d3-path": "1" + } + }, + "d3-time": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-2.1.1.tgz", + "integrity": "sha512-/eIQe/eR4kCQwq7yxi7z4c6qEXf2IYGcjoWB5OOQy4Tq9Uv39/947qlDcN2TLkiTzQWzvnsuYPB9TrWaNfipKQ==", + "requires": { + "d3-array": "2" + } + }, + "d3-time-format": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-3.0.0.tgz", + "integrity": "sha512-UXJh6EKsHBTjopVqZBhFysQcoXSv/5yLONZvkQ5Kk3qbwiUYkdX17Xa1PT6U1ZWXGGfB1ey5L8dKMlFq2DO0Ag==", + "requires": { + "d3-time": "1 - 2" + } + }, + "damerau-levenshtein": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", + "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", + "dev": true + }, + "data-urls": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz", + "integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==", + "dev": true, + "requires": { + "abab": "^2.0.6", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^11.0.0" + } + }, + "date-fns": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz", + "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==", + "dev": true + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "requires": { + "ms": "2.1.2" + } + }, + "decimal.js": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", + "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==", + "dev": true + }, + "deep-eql": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", + "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", + "dev": true, + "requires": { + "type-detect": "^4.0.0" + } + }, + "deep-equal": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.0.tgz", + "integrity": "sha512-RdpzE0Hv4lhowpIUKKMJfeH6C1pXdtT1/it80ubgWqwI3qpuxUBpC1S4hnHg+zjnuOoDkzUtUCEEkG+XG5l3Mw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "es-get-iterator": "^1.1.2", + "get-intrinsic": "^1.1.3", + "is-arguments": "^1.1.1", + "is-array-buffer": "^3.0.1", + "is-date-object": "^1.0.5", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "isarray": "^2.0.5", + "object-is": "^1.1.5", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.4.3", + "side-channel": "^1.0.4", + "which-boxed-primitive": "^1.0.2", + "which-collection": "^1.0.1", + "which-typed-array": "^1.1.9" + } + }, + "deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true + }, + "define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==" + }, + "define-properties": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", + "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", + "dev": true, + "requires": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + } + }, + "delaunator": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-4.0.1.tgz", + "integrity": "sha512-WNPWi1IRKZfCt/qIDMfERkDp93+iZEmOxN2yy4Jg+Xhv8SLk2UTqqbe1sfiipn0and9QrE914/ihdx82Y/Giag==" + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" + }, + "diff": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz", + "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==", + "dev": true + }, + "diff-sequences": { + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.3.1.tgz", + "integrity": "sha512-hlM3QR272NXCi4pq+N4Kok4kOp6EsgOM3ZSpJI7Da3UAs+Ttsi8MRmB6trM/lhyzUxGfOgnpkHtgqm5Q/CTcfQ==", + "dev": true + }, + "dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "requires": { + "path-type": "^4.0.0" + } + }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "dev": true + }, + "dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dev": true, + "requires": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + } + }, + "domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true + }, + "domexception": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", + "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==", + "dev": true, + "requires": { + "webidl-conversions": "^7.0.0" + } + }, + "domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dev": true, + "requires": { + "domelementtype": "^2.3.0" + } + }, + "domutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", + "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "dev": true, + "requires": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + } + }, + "dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "dev": true, + "requires": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + }, + "dependencies": { + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "dev": true + } + } + }, + "eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, + "electron-to-chromium": { + "version": "1.4.559", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.559.tgz", + "integrity": "sha512-iS7KhLYCSJbdo3rUSkhDTVuFNCV34RKs2UaB9Ecr7VlqzjjWW//0nfsFF5dtDmyXlZQaDYYtID5fjtC/6lpRug==", + "dev": true + }, + "emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "entities": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.4.0.tgz", + "integrity": "sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==", + "dev": true + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es-abstract": { + "version": "1.20.5", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.5.tgz", + "integrity": "sha512-7h8MM2EQhsCA7pU/Nv78qOXFpD8Rhqd12gYiSJVkrH9+e8VuA8JlPJK/hQjjlLv6pJvx/z1iRFKzYb0XT/RuAQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "function.prototype.name": "^1.1.5", + "get-intrinsic": "^1.1.3", + "get-symbol-description": "^1.0.0", + "gopd": "^1.0.1", + "has": "^1.0.3", + "has-property-descriptors": "^1.0.0", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.3", + "is-callable": "^1.2.7", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "is-string": "^1.0.7", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.2", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.4.3", + "safe-regex-test": "^1.0.0", + "string.prototype.trimend": "^1.0.6", + "string.prototype.trimstart": "^1.0.6", + "unbox-primitive": "^1.0.2" + } + }, + "es-get-iterator": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", + "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "has-symbols": "^1.0.3", + "is-arguments": "^1.1.1", + "is-map": "^2.0.2", + "is-set": "^2.0.2", + "is-string": "^1.0.7", + "isarray": "^2.0.5", + "stop-iteration-iterator": "^1.0.0" + } + }, + "es-shim-unscopables": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", + "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "esbuild": { + "version": "0.16.9", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.16.9.tgz", + "integrity": "sha512-gkH83yHyijMSZcZFs1IWew342eMdFuWXmQo3zkDPTre25LIPBJsXryg02M3u8OpTwCJdBkdaQwqKkDLnAsAeLQ==", + "dev": true, + "requires": { + "@esbuild/android-arm": "0.16.9", + "@esbuild/android-arm64": "0.16.9", + "@esbuild/android-x64": "0.16.9", + "@esbuild/darwin-arm64": "0.16.9", + "@esbuild/darwin-x64": "0.16.9", + "@esbuild/freebsd-arm64": "0.16.9", + "@esbuild/freebsd-x64": "0.16.9", + "@esbuild/linux-arm": "0.16.9", + "@esbuild/linux-arm64": "0.16.9", + "@esbuild/linux-ia32": "0.16.9", + "@esbuild/linux-loong64": "0.16.9", + "@esbuild/linux-mips64el": "0.16.9", + "@esbuild/linux-ppc64": "0.16.9", + "@esbuild/linux-riscv64": "0.16.9", + "@esbuild/linux-s390x": "0.16.9", + "@esbuild/linux-x64": "0.16.9", + "@esbuild/netbsd-x64": "0.16.9", + "@esbuild/openbsd-x64": "0.16.9", + "@esbuild/sunos-x64": "0.16.9", + "@esbuild/win32-arm64": "0.16.9", + "@esbuild/win32-ia32": "0.16.9", + "@esbuild/win32-x64": "0.16.9" + } + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==" + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==" + }, + "escodegen": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", + "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==", + "dev": true, + "requires": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.6.1" + }, + "dependencies": { + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dev": true, + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + } + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", + "dev": true + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2" + } + } + } + }, + "eslint": { + "version": "8.30.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.30.0.tgz", + "integrity": "sha512-MGADB39QqYuzEGov+F/qb18r4i7DohCDOfatHaxI2iGlPuC65bwG2gxgO+7DkyL38dRFaRH7RaRAgU6JKL9rMQ==", + "dev": true, + "requires": { + "@eslint/eslintrc": "^1.4.0", + "@humanwhocodes/config-array": "^0.11.8", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.1.1", + "eslint-utils": "^3.0.0", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.4.0", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-sdsl": "^4.1.4", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "regexpp": "^3.2.0", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "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, + "requires": { + "color-name": "~1.1.4" + } + }, + "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 + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "globals": { + "version": "13.19.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.19.0.tgz", + "integrity": "sha512-dkQ957uSRWHw7CFXLUtUHQI3g3aWApYhfNR2O6jn/907riyTYKVBmxYVROkBcY614FSSeSJh7Xm7SrUWCxvJMQ==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "eslint-config-airbnb": { + "version": "19.0.4", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb/-/eslint-config-airbnb-19.0.4.tgz", + "integrity": "sha512-T75QYQVQX57jiNgpF9r1KegMICE94VYwoFQyMGhrvc+lB8YF2E/M/PYDaQe1AJcWaEgqLE+ErXV1Og/+6Vyzew==", + "dev": true, + "requires": { + "eslint-config-airbnb-base": "^15.0.0", + "object.assign": "^4.1.2", + "object.entries": "^1.1.5" + } + }, + "eslint-config-airbnb-base": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-15.0.0.tgz", + "integrity": "sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig==", + "dev": true, + "requires": { + "confusing-browser-globals": "^1.0.10", + "object.assign": "^4.1.2", + "object.entries": "^1.1.5", + "semver": "^6.3.0" + } + }, + "eslint-config-prettier": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz", + "integrity": "sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==", + "dev": true, + "requires": {} + }, + "eslint-import-resolver-alias": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-alias/-/eslint-import-resolver-alias-1.1.2.tgz", + "integrity": "sha512-WdviM1Eu834zsfjHtcGHtGfcu+F30Od3V7I9Fi57uhBEwPkjDcii7/yW8jAT+gOhn4P/vOxxNAXbFAKsrrc15w==", + "dev": true, + "requires": {} + }, + "eslint-import-resolver-node": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz", + "integrity": "sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==", + "dev": true, + "requires": { + "debug": "^3.2.7", + "resolve": "^1.20.0" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "eslint-module-utils": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.4.tgz", + "integrity": "sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==", + "dev": true, + "requires": { + "debug": "^3.2.7" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "eslint-plugin-import": { + "version": "2.26.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.26.0.tgz", + "integrity": "sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==", + "dev": true, + "requires": { + "array-includes": "^3.1.4", + "array.prototype.flat": "^1.2.5", + "debug": "^2.6.9", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.6", + "eslint-module-utils": "^2.7.3", + "has": "^1.0.3", + "is-core-module": "^2.8.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.values": "^1.1.5", + "resolve": "^1.22.0", + "tsconfig-paths": "^3.14.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + } + } + }, + "eslint-plugin-jsx-a11y": { + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.6.1.tgz", + "integrity": "sha512-sXgFVNHiWffBq23uiS/JaP6eVR622DqwB4yTzKvGZGcPq6/yZ3WmOZfuBks/vHWo9GaFOqC2ZK4i6+C35knx7Q==", + "dev": true, + "requires": { + "@babel/runtime": "^7.18.9", + "aria-query": "^4.2.2", + "array-includes": "^3.1.5", + "ast-types-flow": "^0.0.7", + "axe-core": "^4.4.3", + "axobject-query": "^2.2.0", + "damerau-levenshtein": "^1.0.8", + "emoji-regex": "^9.2.2", + "has": "^1.0.3", + "jsx-ast-utils": "^3.3.2", + "language-tags": "^1.0.5", + "minimatch": "^3.1.2", + "semver": "^6.3.0" + } + }, + "eslint-plugin-prettier": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz", + "integrity": "sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==", + "dev": true, + "requires": { + "prettier-linter-helpers": "^1.0.0" + } + }, + "eslint-plugin-react": { + "version": "7.31.11", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.31.11.tgz", + "integrity": "sha512-TTvq5JsT5v56wPa9OYHzsrOlHzKZKjV+aLgS+55NJP/cuzdiQPC7PfYoUjMoxlffKtvijpk7vA/jmuqRb9nohw==", + "dev": true, + "requires": { + "array-includes": "^3.1.6", + "array.prototype.flatmap": "^1.3.1", + "array.prototype.tosorted": "^1.1.1", + "doctrine": "^2.1.0", + "estraverse": "^5.3.0", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.6", + "object.fromentries": "^2.0.6", + "object.hasown": "^1.1.2", + "object.values": "^1.1.6", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.3", + "semver": "^6.3.0", + "string.prototype.matchall": "^4.0.8" + }, + "dependencies": { + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "resolve": { + "version": "2.0.0-next.4", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.4.tgz", + "integrity": "sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ==", + "dev": true, + "requires": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + } + } + }, + "eslint-plugin-react-hooks": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz", + "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==", + "dev": true, + "requires": {} + }, + "eslint-scope": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", + "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + } + }, + "eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^2.0.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true + } + } + }, + "eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "dev": true + }, + "espree": { + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.1.tgz", + "integrity": "sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==", + "dev": true, + "requires": { + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.3.0" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dev": true, + "requires": { + "estraverse": "^5.1.0" + } + }, + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "requires": { + "estraverse": "^5.2.0" + } + }, + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + }, + "estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, + "expect": { + "version": "29.4.1", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.4.1.tgz", + "integrity": "sha512-OKrGESHOaMxK3b6zxIq9SOW8kEXztKff/Dvg88j4xIJxur1hspEbedVkR3GpHe5LO+WB2Qw7OWN0RMTdp6as5A==", + "dev": true, + "requires": { + "@jest/expect-utils": "^29.4.1", + "jest-get-type": "^29.2.0", + "jest-matcher-utils": "^29.4.1", + "jest-message-util": "^29.4.1", + "jest-util": "^29.4.1" + } + }, + "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 + }, + "fast-diff": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", + "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", + "dev": true + }, + "fast-glob": { + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "dependencies": { + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + } + } + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "fastq": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.14.0.tgz", + "integrity": "sha512-eR2D+V9/ExcbF9ls441yIuN6TI2ED1Y2ZcA5BmMtJsOkWOFRJQ0Jt0g1UwqXJJVAb+V+umH5Dfr8oh4EVP7VVg==", + "dev": true, + "requires": { + "reusify": "^1.0.4" + } + }, + "faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "requires": { + "websocket-driver": ">=0.5.1" + } + }, + "file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "requires": { + "flat-cache": "^3.0.4" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "firebase": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/firebase/-/firebase-10.4.0.tgz", + "integrity": "sha512-3Z8WsNwA7kbcKGZ+nrTZ/ES518pk0K440ZJYD8nUNKN5hV6ll+unhUw30t1msedN6yIFjhsC/9OwT4Z0ohwO2w==", + "requires": { + "@firebase/analytics": "0.10.0", + "@firebase/analytics-compat": "0.2.6", + "@firebase/app": "0.9.19", + "@firebase/app-check": "0.8.0", + "@firebase/app-check-compat": "0.3.7", + "@firebase/app-compat": "0.2.19", + "@firebase/app-types": "0.9.0", + "@firebase/auth": "1.3.0", + "@firebase/auth-compat": "0.4.6", + "@firebase/database": "1.0.1", + "@firebase/database-compat": "1.0.1", + "@firebase/firestore": "4.2.0", + "@firebase/firestore-compat": "0.3.18", + "@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" + } + }, + "flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "requires": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + } + }, + "flatted": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", + "dev": true + }, + "follow-redirects": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==" + }, + "for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "requires": { + "is-callable": "^1.1.3" + } + }, + "form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "function.prototype.name": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", + "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0", + "functions-have-names": "^1.2.2" + } + }, + "functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true + }, + "gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" + }, + "get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==", + "dev": true + }, + "get-intrinsic": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", + "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + } + }, + "get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + } + }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "requires": { + "is-glob": "^4.0.3" + } + }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" + }, + "globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "requires": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + } + }, + "gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "requires": { + "get-intrinsic": "^1.1.3" + } + }, + "graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true + }, + "grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "dev": true + }, + "hamt_plus": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/hamt_plus/-/hamt_plus-1.0.2.tgz", + "integrity": "sha512-t2JXKaehnMb9paaYA7J0BX8QQAY8lwfQ9Gjf4pg/mk4krt+cmwmU652HOoWonf+7+EQV97ARPMhhVgU1ra2GhA==" + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==" + }, + "has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "dev": true, + "requires": { + "get-intrinsic": "^1.1.1" + } + }, + "has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true + }, + "has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dev": true, + "requires": { + "has-symbols": "^1.0.2" + } + }, + "hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "requires": { + "react-is": "^16.7.0" + }, + "dependencies": { + "react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + } + } + }, + "html-encoding-sniffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", + "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", + "dev": true, + "requires": { + "whatwg-encoding": "^2.0.0" + } + }, + "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==" + }, + "http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, + "requires": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + } + }, + "https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "requires": { + "agent-base": "6", + "debug": "4" + } + }, + "iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + }, + "idb": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz", + "integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==" + }, + "ignore": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.1.tgz", + "integrity": "sha512-d2qQLzTJ9WxQftPAuEQpSPmKqzxePjzVbpAVv62AQ64NTL+wR4JkrVqR/LqFsFEUsHDAiId52mJteHDFuDkElA==", + "dev": true + }, + "import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true + }, + "indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "internal-slot": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.4.tgz", + "integrity": "sha512-tA8URYccNzMo94s5MQZgH8NB/XTa6HsOo0MLfXTKKEnHVVdegzaQoFZ7Jp44bdvLvY2waT5dc+j5ICEswhi7UQ==", + "dev": true, + "requires": { + "get-intrinsic": "^1.1.3", + "has": "^1.0.3", + "side-channel": "^1.0.4" + } + }, + "internmap": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz", + "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==" + }, + "is-arguments": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", + "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-array-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.1.tgz", + "integrity": "sha512-ASfLknmY8Xa2XtB4wmbz13Wu202baeA18cJBCeCy0wXUHZF0IPyVEXqKEcd+t2fNSLLL1vC6k7lxZEojNbISXQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-typed-array": "^1.1.10" + } + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "requires": { + "has-bigints": "^1.0.1" + } + }, + "is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true + }, + "is-core-module": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", + "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==" + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", + "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", + "dev": true + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-map": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", + "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==", + "dev": true + }, + "is-negative-zero": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "dev": true + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true + }, + "is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true + }, + "is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-set": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz", + "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==", + "dev": true + }, + "is-shared-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2" + } + }, + "is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "requires": { + "has-symbols": "^1.0.2" + } + }, + "is-typed-array": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", + "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", + "dev": true, + "requires": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0" + } + }, + "is-weakmap": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", + "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==", + "dev": true + }, + "is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2" + } + }, + "is-weakset": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz", + "integrity": "sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + } + }, + "is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "requires": { + "is-docker": "^2.0.0" + } + }, + "isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "javascript-natural-sort": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz", + "integrity": "sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==", + "dev": true + }, + "jest-diff": { + "version": "29.4.1", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.4.1.tgz", + "integrity": "sha512-uazdl2g331iY56CEyfbNA0Ut7Mn2ulAG5vUaEHXycf1L6IPyuImIxSz4F0VYBKi7LYIuxOwTZzK3wh5jHzASMw==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "diff-sequences": "^29.3.1", + "jest-get-type": "^29.2.0", + "pretty-format": "^29.4.1" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "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, + "requires": { + "color-name": "~1.1.4" + } + }, + "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 + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "pretty-format": { + "version": "29.4.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.4.1.tgz", + "integrity": "sha512-dt/Z761JUVsrIKaY215o1xQJBGlSmTx/h4cSqXqjHLnU1+Kt+mavVE7UgqJJO5ukx5HjSswHfmXz4LjS2oIJfg==", + "dev": true, + "requires": { + "@jest/schemas": "^29.4.0", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + } + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-get-type": { + "version": "29.2.0", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.2.0.tgz", + "integrity": "sha512-uXNJlg8hKFEnDgFsrCjznB+sTxdkuqiCL6zMgA75qEbAJjJYTs9XPrvDctrEig2GDow22T/LvHgO57iJhXB/UA==", + "dev": true + }, + "jest-matcher-utils": { + "version": "29.4.1", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.4.1.tgz", + "integrity": "sha512-k5h0u8V4nAEy6lSACepxL/rw78FLDkBnXhZVgFneVpnJONhb2DhZj/Gv4eNe+1XqQ5IhgUcqj745UwH0HJmMnA==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "jest-diff": "^29.4.1", + "jest-get-type": "^29.2.0", + "pretty-format": "^29.4.1" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "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, + "requires": { + "color-name": "~1.1.4" + } + }, + "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 + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "pretty-format": { + "version": "29.4.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.4.1.tgz", + "integrity": "sha512-dt/Z761JUVsrIKaY215o1xQJBGlSmTx/h4cSqXqjHLnU1+Kt+mavVE7UgqJJO5ukx5HjSswHfmXz4LjS2oIJfg==", + "dev": true, + "requires": { + "@jest/schemas": "^29.4.0", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + } + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-message-util": { + "version": "29.4.1", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.4.1.tgz", + "integrity": "sha512-H4/I0cXUaLeCw6FM+i4AwCnOwHRgitdaUFOdm49022YD5nfyr8C/DrbXOBEyJaj+w/y0gGJ57klssOaUiLLQGQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.4.1", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.4.1", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "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, + "requires": { + "color-name": "~1.1.4" + } + }, + "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 + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "pretty-format": { + "version": "29.4.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.4.1.tgz", + "integrity": "sha512-dt/Z761JUVsrIKaY215o1xQJBGlSmTx/h4cSqXqjHLnU1+Kt+mavVE7UgqJJO5ukx5HjSswHfmXz4LjS2oIJfg==", + "dev": true, + "requires": { + "@jest/schemas": "^29.4.0", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + } + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-util": { + "version": "29.4.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.4.1.tgz", + "integrity": "sha512-bQy9FPGxVutgpN4VRc0hk6w7Hx/m6L53QxpDreTZgJd9gfx/AV2MjyPde9tGyZRINAUrSv57p2inGBu2dRLmkQ==", + "dev": true, + "requires": { + "@jest/types": "^29.4.1", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "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, + "requires": { + "color-name": "~1.1.4" + } + }, + "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 + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "js-sdsl": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.2.0.tgz", + "integrity": "sha512-dyBIzQBDkCqCu+0upx25Y2jGdbTGxE9fshMsCdK0ViOongpV+n5tXRcZY9v7CaVQ79AGS9KA1KHtojxiM7aXSQ==", + "dev": true + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "jsdom": { + "version": "21.1.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-21.1.0.tgz", + "integrity": "sha512-m0lzlP7qOtthD918nenK3hdItSd2I+V3W9IrBcB36sqDwG+KnUs66IF5GY7laGWUnlM9vTsD0W1QwSEBYWWcJg==", + "dev": true, + "requires": { + "abab": "^2.0.6", + "acorn": "^8.8.1", + "acorn-globals": "^7.0.0", + "cssom": "^0.5.0", + "cssstyle": "^2.3.0", + "data-urls": "^3.0.2", + "decimal.js": "^10.4.2", + "domexception": "^4.0.0", + "escodegen": "^2.0.0", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^3.0.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.1", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.2", + "parse5": "^7.1.1", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.1.2", + "w3c-xmlserializer": "^4.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^2.0.0", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^11.0.0", + "ws": "^8.11.0", + "xml-name-validator": "^4.0.0" + } + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" + }, + "json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true + }, + "jsonc-parser": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", + "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", + "dev": true + }, + "jsx-ast-utils": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.3.tgz", + "integrity": "sha512-fYQHZTZ8jSfmWZ0iyzfwiU4WDX4HpHbMCZ3gPlWYiCl3BoeOTsqKBqnTVfH2rYT7eP5c3sVbeSPHnnJOaTrWiw==", + "dev": true, + "requires": { + "array-includes": "^3.1.5", + "object.assign": "^4.1.3" + } + }, + "language-subtag-registry": { + "version": "0.3.22", + "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz", + "integrity": "sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w==", + "dev": true + }, + "language-tags": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.7.tgz", + "integrity": "sha512-bSytju1/657hFjgUzPAPqszxH62ouE8nQFoFaVlIQfne4wO/wXC9A4+m8jYve7YBBvi59eq0SUpcshvG8h5Usw==", + "dev": true, + "requires": { + "language-subtag-registry": "^0.3.20" + } + }, + "levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + } + }, + "lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "local-pkg": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.4.3.tgz", + "integrity": "sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==", + "dev": true + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" + }, + "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==" + }, + "lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "dev": true + }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "long": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", + "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" + }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, + "loupe": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.6.tgz", + "integrity": "sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==", + "dev": true, + "requires": { + "get-func-name": "^2.0.0" + } + }, + "lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "dev": true, + "requires": { + "tslib": "^2.0.3" + }, + "dependencies": { + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "dev": true + } + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "lz-string": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.4.4.tgz", + "integrity": "sha512-0ckx7ZHRPqb0oUm8zNr+90mtf9DQB60H1wMCjBtfi62Kl3a7JbHob6gA2bC+xRvZoOL+1hzUK8jeuEIQE8svEQ==", + "dev": true + }, + "magic-string": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.27.0.tgz", + "integrity": "sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==", + "dev": true, + "requires": { + "@jridgewell/sourcemap-codec": "^1.4.13" + } + }, + "material-colors": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/material-colors/-/material-colors-1.2.6.tgz", + "integrity": "sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg==" + }, + "mdn-data": { + "version": "2.0.30", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", + "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", + "dev": true + }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true + }, + "micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "requires": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + } + }, + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" + }, + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "requires": { + "mime-db": "1.52.0" + } + }, + "min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", + "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", + "dev": true + }, + "mlly": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.1.0.tgz", + "integrity": "sha512-cwzBrBfwGC1gYJyfcy8TcZU1f+dbH/T+TuOhtYP2wLv/Fb51/uV7HJQfBPtEupZ2ORLRU1EKFS/QfS3eo9+kBQ==", + "dev": true, + "requires": { + "acorn": "^8.8.1", + "pathe": "^1.0.0", + "pkg-types": "^1.0.1", + "ufo": "^1.0.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "nanoid": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", + "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", + "dev": true + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", + "dev": true + }, + "no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "dev": true, + "requires": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + }, + "dependencies": { + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "dev": true + } + } + }, + "node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "requires": { + "whatwg-url": "^5.0.0" + }, + "dependencies": { + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "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==" + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + } + } + }, + "node-releases": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", + "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", + "dev": true + }, + "nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "requires": { + "boolbase": "^1.0.0" + } + }, + "nwsapi": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.2.tgz", + "integrity": "sha512-90yv+6538zuvUMnN+zCr8LuV6bPFdq50304114vJYJ8RDyK8D5O9Phpbd6SZWgI7PwzmmfN1upeOJlvybDSgCw==", + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" + }, + "object-inspect": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", + "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", + "dev": true + }, + "object-is": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", + "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + } + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, + "object.assign": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + } + }, + "object.entries": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.6.tgz", + "integrity": "sha512-leTPzo4Zvg3pmbQ3rDK69Rl8GQvIqMWubrkxONG9/ojtFE2rD9fjMKfSI5BxW3osRH1m6VdzmqK8oAY9aT4x5w==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + } + }, + "object.fromentries": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.6.tgz", + "integrity": "sha512-VciD13dswC4j1Xt5394WR4MzmAQmlgN72phd/riNp9vtD7tp4QQWJ0R4wvclXcafgcYK8veHRed2W6XeGBvcfg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + } + }, + "object.hasown": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.2.tgz", + "integrity": "sha512-B5UIT3J1W+WuWIU55h0mjlwaqxiE5vYENJXIXZ4VFe05pNYrkKuK0U/6aFcb0pKywYJh7IhfoqUfKVmrJJHZHw==", + "dev": true, + "requires": { + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + } + }, + "object.values": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz", + "integrity": "sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "open": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "requires": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + } + }, + "optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "requires": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + } + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, + "parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + } + }, + "parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "dev": true, + "requires": { + "entities": "^4.4.0" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true + }, + "pathe": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.0.tgz", + "integrity": "sha512-ODbEPR0KKHqECXW1GoxdDb+AZvULmXjVPy4rt+pGo2+TnjJTIPJQSVS6N63n8T2Ip+syHhbn52OewKicV0373w==", + "dev": true + }, + "pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true + }, + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==" + }, + "pkg-types": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.0.1.tgz", + "integrity": "sha512-jHv9HB+Ho7dj6ItwppRDDl0iZRYBD0jsakHXtFgoLr+cHSF6xC+QL54sJmWxyGxOLYSHm0afhXhXcQDQqH9z8g==", + "dev": true, + "requires": { + "jsonc-parser": "^3.2.0", + "mlly": "^1.0.0", + "pathe": "^1.0.0" + } + }, + "postcss": { + "version": "8.4.20", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.20.tgz", + "integrity": "sha512-6Q04AXR1212bXr5fh03u8aAwbLxAQNGQ/Q1LNa0VfOI06ZAlhPHtQvE4OIdpj4kLThXilalPnmDSOD65DcHt+g==", + "dev": true, + "requires": { + "nanoid": "^3.3.4", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + } + }, + "postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" + }, + "prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true + }, + "prettier": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.1.tgz", + "integrity": "sha512-lqGoSJBQNJidqCHE80vqZJHWHRFoNYsSpP9AjFhlhi9ODCJA541svILes/+/1GM3VaL/abZi7cpFzOpdR9UPKg==", + "dev": true, + "peer": true + }, + "prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "requires": { + "fast-diff": "^1.1.2" + } + }, + "pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + }, + "react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true + } + } + }, + "prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "requires": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + }, + "dependencies": { + "react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + } + } + }, + "protobufjs": { + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.5.tgz", + "integrity": "sha512-gGXRSXvxQ7UiPgfw8gevrfRWcTlSbOFg+p/N+JVJEK5VhueL2miT6qTymqAmjr1Q5WbOCyJbyrk6JfWKwlFn6A==", + "requires": { + "@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" + } + }, + "proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, + "psl": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", + "dev": true + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true + }, + "queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true + }, + "react": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", + "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "requires": { + "loose-envify": "^1.1.0" + } + }, + "react-color": { + "version": "2.19.3", + "resolved": "https://registry.npmjs.org/react-color/-/react-color-2.19.3.tgz", + "integrity": "sha512-LEeGE/ZzNLIsFWa1TMe8y5VYqr7bibneWmvJwm1pCn/eNmrabWDh659JSPn9BuaMpEfU83WTOJfnCcjDZwNQTA==", + "requires": { + "@icons/material": "^0.2.4", + "lodash": "^4.17.15", + "lodash-es": "^4.17.15", + "material-colors": "^1.2.1", + "prop-types": "^15.5.10", + "reactcss": "^1.2.0", + "tinycolor2": "^1.4.1" + } + }, + "react-cookie": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/react-cookie/-/react-cookie-4.1.1.tgz", + "integrity": "sha512-ffn7Y7G4bXiFbnE+dKhHhbP+b8I34mH9jqnm8Llhj89zF4nPxPutxHT1suUqMeCEhLDBI7InYwf1tpaSoK5w8A==", + "requires": { + "@types/hoist-non-react-statics": "^3.0.1", + "hoist-non-react-statics": "^3.0.0", + "universal-cookie": "^4.0.0" + } + }, + "react-dom": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", + "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "requires": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.0" + } + }, + "react-ga4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/react-ga4/-/react-ga4-2.1.0.tgz", + "integrity": "sha512-ZKS7PGNFqqMd3PJ6+C2Jtz/o1iU9ggiy8Y8nUeksgVuvNISbmrQtJiZNvC/TjDsqD0QlU5Wkgs7i+w9+OjHhhQ==" + }, + "react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + }, + "react-lifecycles-compat": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", + "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" + }, + "react-refresh": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz", + "integrity": "sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==", + "dev": true + }, + "react-router": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.5.0.tgz", + "integrity": "sha512-fqqUSU0NC0tSX0sZbyuxzuAzvGqbjiZItBQnyicWlOUmzhAU8YuLgRbaCL2hf3sJdtRy4LP/WBrWtARkMvdGPQ==", + "requires": { + "@remix-run/router": "1.1.0" + } + }, + "react-router-dom": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.5.0.tgz", + "integrity": "sha512-/XzRc5fq80gW1ctiIGilyKFZC/j4kfe75uivMsTChFbkvrK4ZrF3P3cGIc1f/SSkQ4JiJozPrf+AwUHHWVehVg==", + "requires": { + "@remix-run/router": "1.1.0", + "react-router": "6.5.0" + } + }, + "reactcss": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/reactcss/-/reactcss-1.2.3.tgz", + "integrity": "sha512-KiwVUcFu1RErkI97ywr8nvx8dNOpT03rbnma0SSalTYjkrPYaEajR4a/MRt6DZ46K6arDRbWMNHF+xH7G7n/8A==", + "requires": { + "lodash": "^4.0.1" + } + }, + "recoil": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/recoil/-/recoil-0.7.6.tgz", + "integrity": "sha512-hsBEw7jFdpBCY/tu2GweiyaqHKxVj6EqF2/SfrglbKvJHhpN57SANWvPW+gE90i3Awi+A5gssOd3u+vWlT+g7g==", + "requires": { + "hamt_plus": "1.0.2" + } + }, + "recoil-persist": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/recoil-persist/-/recoil-persist-4.2.0.tgz", + "integrity": "sha512-MHVfML9GxJP3RpkKR4F5rp7DtvzIvjWhowtMao/b7h2k4afMio/4sMAdUtltIrDaeVegH0Iga8Sx5XQ3oD7CzA==", + "requires": {} + }, + "redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "requires": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + } + }, + "regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "dev": true + }, + "regenerate-unicode-properties": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.1.tgz", + "integrity": "sha512-X007RyZLsCJVVrjgEFVpLUTZwyOZk3oiL75ZcuYjlIWd6rNJtOjkBwQc5AsRrpbKVkxN6sklw/k/9m2jJYOf8Q==", + "dev": true, + "requires": { + "regenerate": "^1.4.2" + } + }, + "regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "dev": true + }, + "regenerator-transform": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz", + "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==", + "dev": true, + "requires": { + "@babel/runtime": "^7.8.4" + } + }, + "regexp.prototype.flags": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", + "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "functions-have-names": "^1.2.2" + } + }, + "regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true + }, + "regexpu-core": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz", + "integrity": "sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==", + "dev": true, + "requires": { + "@babel/regjsgen": "^0.8.0", + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.1.0", + "regjsparser": "^0.9.1", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.1.0" + } + }, + "regjsparser": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", + "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", + "dev": true, + "requires": { + "jsesc": "~0.5.0" + }, + "dependencies": { + "jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", + "dev": true + } + } + }, + "require-directory": { + "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==" + }, + "requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true + }, + "resolve": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "dev": true, + "requires": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "rollup": { + "version": "3.7.5", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.7.5.tgz", + "integrity": "sha512-z0ZbqHBtS/et2EEUKMrAl2CoSdwN7ZPzL17UMiKN9RjjqHShTlv7F9J6ZJZJNREYjBh3TvBrdfjkFDIXFNeuiQ==", + "devOptional": true, + "requires": { + "fsevents": "~2.3.2" + } + }, + "rollup-plugin-visualizer": { + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/rollup-plugin-visualizer/-/rollup-plugin-visualizer-5.9.0.tgz", + "integrity": "sha512-bbDOv47+Bw4C/cgs0czZqfm8L82xOZssk4ayZjG40y9zbXclNk7YikrZTDao6p7+HDiGxrN0b65SgZiVm9k1Cg==", + "requires": { + "open": "^8.4.0", + "picomatch": "^2.3.1", + "source-map": "^0.7.4", + "yargs": "^17.5.1" + }, + "dependencies": { + "source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==" + } + } + }, + "run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "requires": { + "queue-microtask": "^1.2.2" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, + "safe-regex-test": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", + "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-regex": "^1.1.4" + } + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "requires": { + "xmlchars": "^2.2.0" + } + }, + "scheduler": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", + "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "requires": { + "loose-envify": "^1.1.0" + } + }, + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true + }, + "shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==" + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, + "siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "slice-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", + "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", + "dev": true, + "requires": { + "ansi-styles": "^6.0.0", + "is-fullwidth-code-point": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true + } + } + }, + "snake-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", + "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", + "dev": true, + "requires": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + }, + "dependencies": { + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "dev": true + } + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true + }, + "source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "requires": { + "escape-string-regexp": "^2.0.0" + }, + "dependencies": { + "escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true + } + } + }, + "stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true + }, + "std-env": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.3.1.tgz", + "integrity": "sha512-3H20QlwQsSm2OvAxWIYhs+j01MzzqwMwGiiO1NQaJYZgJZFPuAbf95/DiKRBSTYIJ2FeGUc+B/6mPGcWP9dO3Q==", + "dev": true + }, + "stop-iteration-iterator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz", + "integrity": "sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==", + "dev": true, + "requires": { + "internal-slot": "^1.0.4" + } + }, + "string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "requires": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true + }, + "strip-ansi": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", + "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", + "dev": true, + "requires": { + "ansi-regex": "^6.0.1" + } + } + } + }, + "string.prototype.matchall": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.8.tgz", + "integrity": "sha512-6zOCOcJ+RJAQshcTvXPHoxoQGONa3e/Lqx90wUA+wEzX78sg5Bo+1tQo4N0pohS0erG9qtCqJDjNCQBjeWVxyg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "get-intrinsic": "^1.1.3", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.3", + "regexp.prototype.flags": "^1.4.3", + "side-channel": "^1.0.4" + } + }, + "string.prototype.trimend": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", + "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + } + }, + "string.prototype.trimstart": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz", + "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true + }, + "strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "requires": { + "min-indent": "^1.0.0" + } + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "strip-literal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-1.0.0.tgz", + "integrity": "sha512-5o4LsH1lzBzO9UFH63AJ2ad2/S2AVx6NtjOcaz+VTT2h1RiRvbipW72z8M/lxEhcPHDBQwpDrnTF7sXy/7OwCQ==", + "dev": true, + "requires": { + "acorn": "^8.8.1" + } + }, + "styled-components": { + "version": "5.3.6", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.6.tgz", + "integrity": "sha512-hGTZquGAaTqhGWldX7hhfzjnIYBZ0IXQXkCYdvF1Sq3DsUaLx6+NTHC5Jj1ooM2F68sBiVz3lvhfwQs/S3l6qg==", + "requires": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/traverse": "^7.4.5", + "@emotion/is-prop-valid": "^1.1.0", + "@emotion/stylis": "^0.8.4", + "@emotion/unitless": "^0.7.4", + "babel-plugin-styled-components": ">= 1.12.0", + "css-to-react-native": "^3.0.0", + "hoist-non-react-statics": "^3.0.0", + "shallowequal": "^1.1.0", + "supports-color": "^5.5.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + }, + "supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true + }, + "svg-parser": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz", + "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==", + "dev": true + }, + "svgo": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.0.2.tgz", + "integrity": "sha512-Z706C1U2pb1+JGP48fbazf3KxHrWOsLme6Rv7imFBn5EnuanDW1GPaA/P1/dvObE670JDePC3mnj0k0B7P0jjQ==", + "dev": true, + "requires": { + "@trysound/sax": "0.2.0", + "commander": "^7.2.0", + "css-select": "^5.1.0", + "css-tree": "^2.2.1", + "csso": "^5.0.5", + "picocolors": "^1.0.0" + } + }, + "symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "tinybench": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.3.1.tgz", + "integrity": "sha512-hGYWYBMPr7p4g5IarQE7XhlyWveh1EKhy4wUBS1LrHXCKYgvz+4/jCqgmJqZxxldesn05vccrtME2RLLZNW7iA==", + "dev": true + }, + "tinycolor2": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz", + "integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==" + }, + "tinypool": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.3.1.tgz", + "integrity": "sha512-zLA1ZXlstbU2rlpA4CIeVaqvWq41MTWqLY3FfsAXgC8+f7Pk7zroaJQxDgxn1xNudKW6Kmj4808rPFShUlIRmQ==", + "dev": true + }, + "tinyspy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-1.0.2.tgz", + "integrity": "sha512-bSGlgwLBYf7PnUsQ6WOc6SJ3pGOcd+d8AA6EUnLDDM0kWEstC1JIlSZA3UNliDXhd9ABoS7hiRBDCu+XP/sf1Q==", + "dev": true + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==" + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "tough-cookie": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.2.tgz", + "integrity": "sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==", + "dev": true, + "requires": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + } + }, + "tr46": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "dev": true, + "requires": { + "punycode": "^2.1.1" + } + }, + "tsconfig-paths": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", + "integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==", + "dev": true, + "requires": { + "@types/json5": "^0.0.29", + "json5": "^1.0.1", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + }, + "dependencies": { + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + } + } + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + }, + "type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1" + } + }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true + }, + "typescript": { + "version": "4.9.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz", + "integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==", + "dev": true + }, + "ufo": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.0.1.tgz", + "integrity": "sha512-boAm74ubXHY7KJQZLlXrtMz52qFvpsbOxDcZOnw/Wf+LS4Mmyu7JxmzD4tDLtUQtmZECypJ0FrCz4QIe6dvKRA==", + "dev": true + }, + "unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + } + }, + "unicode-canonical-property-names-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", + "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", + "dev": true + }, + "unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dev": true, + "requires": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + } + }, + "unicode-match-property-value-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", + "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", + "dev": true + }, + "unicode-property-aliases-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", + "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "dev": true + }, + "universal-cookie": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/universal-cookie/-/universal-cookie-4.0.4.tgz", + "integrity": "sha512-lbRVHoOMtItjWbM7TwDLdl8wug7izB0tq3/YVKhT/ahB4VDvWMyvnADfnJI8y6fSvsjh51Ix7lTGC6Tn4rMPhw==", + "requires": { + "@types/cookie": "^0.3.3", + "cookie": "^0.4.0" + } + }, + "universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "dev": true + }, + "update-browserslist-db": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", + "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "dev": true, + "requires": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + } + }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dev": true, + "requires": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "vite": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.0.1.tgz", + "integrity": "sha512-kZQPzbDau35iWOhy3CpkrRC7It+HIHtulAzBhMqzGHKRf/4+vmh8rPDDdv98SWQrFWo6//3ozwsRmwQIPZsK9g==", + "dev": true, + "requires": { + "esbuild": "^0.16.3", + "fsevents": "~2.3.2", + "postcss": "^8.4.20", + "resolve": "^1.22.1", + "rollup": "^3.7.0" + } + }, + "vite-node": { + "version": "0.28.3", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-0.28.3.tgz", + "integrity": "sha512-uJJAOkgVwdfCX8PUQhqLyDOpkBS5+j+FdbsXoPVPDlvVjRkb/W/mLYQPSL6J+t8R0UV8tJSe8c9VyxVQNsDSyg==", + "dev": true, + "requires": { + "cac": "^6.7.14", + "debug": "^4.3.4", + "mlly": "^1.1.0", + "pathe": "^1.1.0", + "picocolors": "^1.0.0", + "source-map": "^0.6.1", + "source-map-support": "^0.5.21", + "vite": "^3.0.0 || ^4.0.0" + } + }, + "vite-plugin-svgr": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/vite-plugin-svgr/-/vite-plugin-svgr-4.1.0.tgz", + "integrity": "sha512-v7Qic+FWmCChgQNGSI4V8X63OEYsdUoLt66iqIcHozq9bfK/Dwmr0V+LBy1NE8CE98Y8HouEBJ+pto4AMfN5xw==", + "dev": true, + "requires": { + "@rollup/pluginutils": "^5.0.4", + "@svgr/core": "^8.1.0", + "@svgr/plugin-jsx": "^8.1.0" + } + }, + "vitest": { + "version": "0.28.3", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-0.28.3.tgz", + "integrity": "sha512-N41VPNf3VGJlWQizGvl1P5MGyv3ZZA2Zvh+2V8L6tYBAAuqqDK4zExunT1Cdb6dGfZ4gr+IMrnG8d4Z6j9ctPw==", + "dev": true, + "requires": { + "@types/chai": "^4.3.4", + "@types/chai-subset": "^1.3.3", + "@types/node": "*", + "@vitest/expect": "0.28.3", + "@vitest/runner": "0.28.3", + "@vitest/spy": "0.28.3", + "@vitest/utils": "0.28.3", + "acorn": "^8.8.1", + "acorn-walk": "^8.2.0", + "cac": "^6.7.14", + "chai": "^4.3.7", + "debug": "^4.3.4", + "local-pkg": "^0.4.2", + "pathe": "^1.1.0", + "picocolors": "^1.0.0", + "source-map": "^0.6.1", + "std-env": "^3.3.1", + "strip-literal": "^1.0.0", + "tinybench": "^2.3.1", + "tinypool": "^0.3.1", + "tinyspy": "^1.0.2", + "vite": "^3.0.0 || ^4.0.0", + "vite-node": "0.28.3", + "why-is-node-running": "^2.2.2" + } + }, + "w3c-xmlserializer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", + "integrity": "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==", + "dev": true, + "requires": { + "xml-name-validator": "^4.0.0" + } + }, + "webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true + }, + "websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "requires": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + } + }, + "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==" + }, + "whatwg-encoding": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", + "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "dev": true, + "requires": { + "iconv-lite": "0.6.3" + } + }, + "whatwg-mimetype": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "dev": true + }, + "whatwg-url": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", + "dev": true, + "requires": { + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "requires": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + } + }, + "which-collection": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", + "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", + "dev": true, + "requires": { + "is-map": "^2.0.1", + "is-set": "^2.0.1", + "is-weakmap": "^2.0.1", + "is-weakset": "^2.0.1" + } + }, + "which-typed-array": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", + "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", + "dev": true, + "requires": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0", + "is-typed-array": "^1.1.10" + } + }, + "why-is-node-running": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.2.2.tgz", + "integrity": "sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==", + "dev": true, + "requires": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + } + }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true + }, + "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==", + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "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==" + }, + "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==" + }, + "is-fullwidth-code-point": { + "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==" + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "ws": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.12.0.tgz", + "integrity": "sha512-kU62emKIdKVeEIOIKVegvqpXMSTAMLJozpHZaJNDYqBjzlSYXQGviYwN1osDLJ9av68qHd4a2oSjd7yD4pacig==", + "dev": true, + "requires": {} + }, + "xml-name-validator": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", + "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", + "dev": true + }, + "xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "requires": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "dependencies": { + "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==" + }, + "is-fullwidth-code-point": { + "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==" + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + } + } + }, + "yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==" + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true + } } } diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 1cfb55dfd..f8a66b315 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -29,11 +29,9 @@ const SearchPage = lazy(() => import("@/Cabinet/pages/admin/SearchPage")); const AdminClubPage = lazy(() => import("@/Cabinet/pages/admin/AdminClubPage")); const AdminLoginFailurePage = lazy( () => import("@/Cabinet/pages/admin/AdminLoginFailurePage") + ); const AdminHomePage = lazy(() => import("@/Cabinet/pages/admin/AdminHomePage")); -const AdminSlackNotiPage = lazy( - () => import("@/Cabinet/pages/admin/AdminSlackNotiPage") -); function App(): React.ReactElement { return ( diff --git a/frontend/src/Cabinet/components/LeftNav/LeftMainNav/LeftMainNav.container.tsx b/frontend/src/Cabinet/components/LeftNav/LeftMainNav/LeftMainNav.container.tsx index 90dfce091..c0f00fee6 100644 --- a/frontend/src/Cabinet/components/LeftNav/LeftMainNav/LeftMainNav.container.tsx +++ b/frontend/src/Cabinet/components/LeftNav/LeftMainNav/LeftMainNav.container.tsx @@ -118,7 +118,7 @@ const LeftMainNavContainer = ({ isAdmin }: { isAdmin?: boolean }) => { navigator("slack-notification"); closeAll(); }; - + const onClickMainClubButton = () => { navigator("clubs"); closeAll(); @@ -152,7 +152,7 @@ const LeftMainNavContainer = ({ isAdmin }: { isAdmin?: boolean }) => { resetCurrentSection(); navigator("/login"); }; - + return ( Date: Sun, 19 May 2024 15:27:17 +0900 Subject: [PATCH 04/17] =?UTF-8?q?[BE]=20HOTFIX=20:=20=EC=88=98=EC=9A=94?= =?UTF-8?q?=EC=A7=80=EC=8B=9D=ED=9A=8C=20=EA=B8=B0=EB=B3=B8=20=EC=9E=A5?= =?UTF-8?q?=EC=86=8C=203=EC=B8=B5=20->=20=EC=A7=80=ED=95=98=EC=8B=A4?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cabinet/presentation/domain/Presentation.java | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/presentation/domain/Presentation.java b/backend/src/main/java/org/ftclub/cabinet/presentation/domain/Presentation.java index 6913b08b5..aa7754973 100644 --- a/backend/src/main/java/org/ftclub/cabinet/presentation/domain/Presentation.java +++ b/backend/src/main/java/org/ftclub/cabinet/presentation/domain/Presentation.java @@ -60,7 +60,7 @@ public class Presentation { private User user; protected Presentation(Category category, LocalDateTime dateTime, - PresentationTime presentationTime, String subject, String summary, String detail) { + PresentationTime presentationTime, String subject, String summary, String detail) { this.category = category; this.dateTime = dateTime; this.presentationTime = presentationTime; @@ -68,18 +68,17 @@ protected Presentation(Category category, LocalDateTime dateTime, this.detail = detail; this.summary = summary; this.presentationStatus = PresentationStatus.EXPECTED; - this.presentationLocation = PresentationLocation.THIRD; + this.presentationLocation = PresentationLocation.BASEMENT; } public static Presentation of(Category category, LocalDateTime dateTime, - PresentationTime presentationTime, String subject, String summary, String detail) { + PresentationTime presentationTime, String subject, String summary, String detail) { - return new Presentation(category, dateTime, presentationTime, subject, - summary, detail); + return new Presentation(category, dateTime, presentationTime, subject, summary, detail); } public void adminUpdate(PresentationStatus newStatus, LocalDateTime newDateTime, - PresentationLocation newLocation) { + PresentationLocation newLocation) { this.presentationStatus = newStatus; this.dateTime = newDateTime; this.presentationLocation = newLocation; From b5b093eb28c1d4fb43ca2a3a2b5366d3057b501f Mon Sep 17 00:00:00 2001 From: jusohn Eddie Sohn Date: Mon, 10 Jun 2024 22:30:25 +0900 Subject: [PATCH 05/17] =?UTF-8?q?[FE]=20FIX:=20Dropdown=20=EC=97=90?= =?UTF-8?q?=EC=84=9C=20selectedIdx=20=EA=B0=80=20=EC=9D=8C=EC=88=98?= =?UTF-8?q?=EC=9D=B8=20=EA=B2=BD=EC=9A=B0=EC=99=80=20options=20=EA=B0=80?= =?UTF-8?q?=20=EB=B9=88=20=EB=B0=B0=EC=97=B4=EC=9D=BC=20=EA=B2=BD=EC=9A=B0?= =?UTF-8?q?=20=EC=98=88=EC=99=B8=EC=B2=98=EB=A6=AC=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?#1626?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Cabinet/components/Common/Dropdown.tsx | 9 ++--- .../EditStatusModal/EditStatusModal.tsx | 34 +++++++++---------- 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/frontend/src/Cabinet/components/Common/Dropdown.tsx b/frontend/src/Cabinet/components/Common/Dropdown.tsx index c1a3f6de0..8b47b4cce 100644 --- a/frontend/src/Cabinet/components/Common/Dropdown.tsx +++ b/frontend/src/Cabinet/components/Common/Dropdown.tsx @@ -10,17 +10,18 @@ export interface IDropdownOptions { imageSrc?: string; } -export interface IDropdown { +export interface IDropdownProps { options: IDropdownOptions[]; defaultValue: string; defaultImageSrc?: string; onChangeValue?: (param: any) => any; } -const Dropdown = ({ options, defaultValue, onChangeValue }: IDropdown) => { +const Dropdown = ({ options, defaultValue, onChangeValue }: IDropdownProps) => { const [currentName, setCurrentName] = useState(defaultValue); const [isOpen, setIsOpen] = useState(false); - const selectedIdx = options.findIndex((op) => op.name === currentName) ?? 0; + const idx: number = options.findIndex((op) => op.name === currentName); + const selectedIdx: number = idx === -1 ? 0 : idx; return ( @@ -30,7 +31,7 @@ const Dropdown = ({ options, defaultValue, onChangeValue }: IDropdown) => { }} isOpen={isOpen} > - {options[selectedIdx].imageSrc?.length && ( + {options[selectedIdx] && options[selectedIdx].imageSrc && ( {options[selectedIdx].value === "PRIVATE" && } {options[selectedIdx].value === "CLUB" && } diff --git a/frontend/src/Presentation/components/Modals/EditStatusModal/EditStatusModal.tsx b/frontend/src/Presentation/components/Modals/EditStatusModal/EditStatusModal.tsx index 54c4a997d..449ccddab 100644 --- a/frontend/src/Presentation/components/Modals/EditStatusModal/EditStatusModal.tsx +++ b/frontend/src/Presentation/components/Modals/EditStatusModal/EditStatusModal.tsx @@ -4,8 +4,8 @@ import { useRecoilState, useSetRecoilState } from "recoil"; import styled from "styled-components"; import Button from "@/Cabinet/components/Common/Button"; import Dropdown, { - IDropdown, IDropdownOptions, + IDropdownProps, } from "@/Cabinet/components/Common/Dropdown"; import ModalPortal from "@/Cabinet/components/Modals/ModalPortal"; import { @@ -69,18 +69,19 @@ const EditStatusModal = ({ closeModal }: EditStatusModalProps) => { PresentationLocation.THIRD ); const [invalidDates, setInvalidDates] = useState([]); - const [statusDropdownProps, setStatusDropdownProps] = useState({ - options: statusOptions, - defaultValue: - statusOptions.find( - (option) => option.value === currentPresentation?.presentationStatus - )?.name ?? "발표 예정", - defaultImageSrc: "", - onChangeValue: (val: PresentationStatusType) => { - setPresentationStatus(val); - }, - }); - const [datesDropdownProps, setDatesDropdownProps] = useState({ + const [statusDropdownProps, setStatusDropdownProps] = + useState({ + options: statusOptions, + defaultValue: + statusOptions.find( + (option) => option.value === currentPresentation?.presentationStatus + )?.name ?? "발표 예정", + defaultImageSrc: "", + onChangeValue: (val: PresentationStatusType) => { + setPresentationStatus(val); + }, + }); + const [datesDropdownProps, setDatesDropdownProps] = useState({ options: [], defaultValue: currentPresentation?.dateTime ? format(currentPresentation?.dateTime.split("T")[0], "M월 d일") @@ -90,8 +91,8 @@ const EditStatusModal = ({ closeModal }: EditStatusModalProps) => { setPresentationDate(val); }, }); - const [locationDropdownProps, setLocationDropdownProps] = useState( - { + const [locationDropdownProps, setLocationDropdownProps] = + useState({ options: floorOptions, defaultValue: floorOptions.find( @@ -101,8 +102,7 @@ const EditStatusModal = ({ closeModal }: EditStatusModalProps) => { onChangeValue: (val: PresentationLocation) => { setLocation(val); }, - } - ); + }); const tryEditPresentationStatus = async (e: React.MouseEvent) => { if (!currentPresentation || !currentPresentation.id) return; From b39a6ff665f7ea623f68f47836271395360c415a Mon Sep 17 00:00:00 2001 From: chyo1 <142678241+chyo1@users.noreply.github.com> Date: Tue, 11 Jun 2024 17:55:36 +0900 Subject: [PATCH 06/17] Common/dev/feat coin#1604 (#1628) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [FE] FIX: 재화 까비상점, 동전줍기 Nav 다크모드 적용 #1604 * [FE] FEAT: /main club section인 곳들도 AlertStyled div 넣어줌#1604 * [FE] FIX: 동전줍기 Nav box-shadow 적용 #1604 * [FE] FEAT: 아이템 사용 내역이 없을 경우 나타나는 문구에 DarkMode 색상 적용 #1604 * [FE] FEAT: StoreMain 페이지 Header 의 border-bottom 색상을 DarkMode 색상으로 변경 #1604 * [FE] REFACT: clubSectionsData를 mapPositionData.ts에 넣음 #1604 * [FE] FIX: WarningNotification svg 파일 사용 구조 변경&텍스트 색상 변경#1604 * [FE] FIX: top nav button myCoinBtn 어드민 페이지에서 안보이게&id, type등 의미에 맞게 이름 변경#1604 * [FE] FIX: SearchBar에 있는 SearchIcon svg 사용 구조 변경&TopNavButton 중 searchButton 크기 조정 방법 변경#1604 * [FE] FIX: SearchItemByIntraId private icon 안나오는거 고침#1604 * [FE] FIX: cabinetIconComponentMap 중 private icon 사용하는 곳 크기 조정#1604 * [FE] FIX: /home에 있는 아이콘 svg 파일 사용방식 변경#1604 * [FE] FIX: privateIcon.svg 사용하는 곳들 모두 크기 조정완료#1604 * [FE] FIX: privateIcon shareIcon clubIcon storeExtension 사용하는 부분 크기 조정#1604 * [FE] FEAT: 페널티 모달 날짜 오류 수정 #1604 * [FE] FEAT: Profile 페이지에서 연장권 Card 삭제 #1604 * [FE] FEAT: 새로고침시 home으로 이동 말고 현재 url로 새로고침 #1604 * [FE] FEAT: 프로필페이지에서 연장권 카드 삭제 후 레이아웃 조정#1604 * [FE] FEAT: ItmeUsageLogPage 첫 랜더링 시 로딩 애니메이션 추가 #1604 * [BE] FIX : 어드민에서 층 전체 사물함 안불러와지는 오류 수정 * [FE] FIX: 확대시 코인 깨짐 방지 및 동전줍기 비활성화 #1604 * [FE] FIX: 알림권 사용 모달 문구 수정 #1604 * [FE] FIX: 메인 컬러값 변경 하면 코인 색상도 같이 변경 #1604 * [FE] FIX: Store 새로고침할 경우 LeftStoreNav 에 해당 페이지 선택되도록 수정 #1604 * [BE] FIX : 동전 줍기 동기 처리 및 DB에 저장 로직 추가 * [BE] FIX: SWAP 아이템 1일 1회 사용 제한 적용 * [FE] FEAT: 프로필 페이지 연장권 삭제 후 레이아웃 조정#1604 * [FE] REFACT: DisplayStyleCard ProfilePage 리팩토링#1604 * [FE] REFACT: extensionTicket.svg, storeExtension.svg extension.svg로 통합#1604 * [FE] FIX: 백엔드가 보내주는 데이터에 맞춰 ALERT->ALARM으로 변경#1604 * [FE] FIX: 알림 등록권 사용 연속 2회이상일때 ui 바로 렌더링되게 변경#1604 * [FE] FEAT: UserInfoArea->UserCabinetInfoArea & UserStoreInfoArea css 완성#1604 * [FE] FEAT: UserInfoArea에서의 로고이미지 svg파일로 변경#1604 * [FE] FEAT: UserStoreInfoArea에 openUserStore,closeUserStore 정의해서 넣고 styled component명 UserStoreInfoArea에 맞게 변경#1604 * [FE] FIX: light-grayLine 사용하는 곳 grayLine으로 변경#1604 * [FE] FEAT: UserInfoArea 대신 UserCabinetInfoArea import #1604 * [FE] FIX: AdminLayout쪽 UserInfoAreaContainer->UserCabinetInfoAreaContainer#1604 * [FE] FEAT: 유지보수 위해 인벤토리, 아이템사용내역, 코인내역에서 데이터 없을때의 문구와 아이콘을 UnavailableDataInfo를 정의해 대체#1604 * [FE] FIX: 사용가능페이지 사물함 없을때 UnavailableDataInfo로 교체#1604 * [FE] FIX: 사물함설정-사물함이름 수정하다 취소 눌렀을때 초기화되게 변경#1604 * [FE] ETC: 어드민 /main 데이터 정상적으로 불러온 후 private,share,club icon 정상적으로 렌더링되는지 확인#1604 * [FE] FIX: getDefaultCabinetInfo title:null->로 수정#1604 * [FE] DOCS: DisplayStyleInitializer.ts->displayStyleInitializer.ts#1604 * [FE] FEAT: admin 페널티 모달 css완성, 코인 다크모드 색상 통일 #1604 * [FE] FIX: 유저 아이템 지급 버튼 생성 #1604 * [FE] FIX: IItemDetail itemName:StoreItemType->string, itemType:string->StoreItemType으로 변경&IItemDetail 사용하는 부분 변경사항 적용#1604 * [FE] FIX: 유저 아이템 지급 nav 수정 #1604 * [FE] FIX: UserStoreInfoArea.tsx에서 ban modal 관련된 이름 아이템 지급 모달 관련되게 변경#1604 * [FE] FIX: ISelectedUserInfo->ISelectedUserCabinetInfo & ISelectedUserStoreInfo 생성 및 사용#1604 * [FE] FIX: UserInfoArea 잘 안열리고 잘 안닫히는 버그 해결#1604 * [FE] REFACT: UserStoreInfo container 삭제#1604 * [FE] FIX: 연장권 사용 모달 아이템 없을 때 처리 #1604 * [FE] FIX: 아이템 사용 모달에서 보유한 아이템만 보이도록 수정, SearchItemByIntraId 리팩토링 #1604 * [FE] FEAT: 코인 통계 axios 작성 #1604 * [FE] FEAT: 코인 통계 CSS#1604 * [FE] FEAT: LogTable 에 들어가 있는 불필요한 padding 삭제 #1604 * [FE] FEAT: media.css 수정 #1604 * [FE] FEAT: API 명세에 맞는 mock data 로 표 구성 완료 #1604 * [FE] FEAT: Admin 아이템 사용 내역 표 구현 #1604 * [FE] FEAT: Admin 아이템 사용 내역 표 세부 사항 구현 #1604 * [FE] FEAT: AdminLentLog 의 margin-top 속성 조정 #1604 * [FE] FEAT: useMenu 호출에 맞는 함수명 변경 #1604 * [FE] FEAT: AdminItemUsageLogPage 구현 #1604 * [FE] FEAT: AdminItemUsageLogPage 추가 #1604 * [FE] FEAT: AdminDto 에 아이템 사용내역 관련 interface 추가 #1604 * [FE] FEAT: mockData로 코인 통계 띄우기#1604 * [FE] FIX: leader.svg, crown.svg 통합#1604 * [FE] FEAT: 유저/사물함 검색 시 그에 대응되는 cabinetInfoArea 문구로 수정#1604 * [FE] FIX: Selector 아이콘 ReactComponent 사용하는 구조로 변경#1604 * [FE] FIX: 연장권 사용 모달 아이템 없을 때 disabled #1604 * [FE] FEAT: admin store 라우터 설정#1604 * [FE] FEAT: *선택해주세요 하는 부분들 SelectInduction 컴포넌트 만들어서 적용#1604 * [FE] FEAT: Dropdown Penalty모달 로직 수정 #1604 * [FE] FIX: 아이템 관리 버튼 눌렀을때 info area 두개 나오는거 해결#1604 * [FE] FEAT: AdminStore 페이지 속성 수정 #1604 * [FE] FEAT: AdminStorePage 에 전체 재화 현황 PieChart 추가 #1604 * [FE] FEAT: 코인 통계 api 받아와서 데이터 띄우기 준비 갈 완료#1604 * [FE] FEAT: 재화 전체 그래프 형식 잡기 #1604 * [FE] FIX: AdminNavType 적용 안된곳 적용시킴#1604 * [FE] FEAT: coinflow 컴포넌트 adminStorePage에 이식 #1604 * [FE] FEAT: 어드민 페이지에서 새로고침했을때 새로고침한 위치로 남아있게 변경#1604 * [FE] FEAT: coinFlow 토글 기능 구현 #1604 * [FE] FIX: CI 깨짐 해결 #1604 * [FE] FEAT: Admin dto 수정 #1604 * [FE] FEAT: 아이템 내역 container 작성 #1604 * [FE] FEAT: AdminItemProvideLog 추가 #1604 * [FE] FEAT: AdminItemProviedTable 추가 #1604 * [FE] FEAT: 아이템 내역, 아이템 지급 기록 toggle 버튼 추가 #1604 * [FE] FEAT: AdminStorePage 에 전체 재화 현황 mock data 적용 완료 #1604 * [FE] FIX: AdminNavType 타입 적용 안된 곳 적용#1604 * [FE] FEAT: 페널티 사용 모달 #1604 * [FE] FIX: 동전 주우러 가기 모달 안나오는 오류 수정 #1604 * [BE] FIX : 아이템 목록 요청 유저 + 어드민 전부 가능하도록 수정 * [FE] FIX: 페널티 모닲 출력시 일수와 시간까지 같이 표현 #1604 * [FE] FEAT: 아이템 지급 모달 아이템 드롭다운 axiosItems로 받은 아이템들로 띄움#1604 * [FE] FEAT: 아이템 클릭할때마다 axiosItems 사용해서 해당 타입들 dropdown options에 넣음#1604 * [FE] FEAT: admin 재화 사용 통계 css 수정 #1604 * [FE] FEAT: CI CD 수정 #1604 * [FE] FIX:CI 해결, 아이템 사용 통계 axios 적용 #1604 * [FE] FIX:CI 해결 #1604 * [FE] FIX: 페널티 모달 문구 수정 #1604 * [FE] FEAT: IDropdownOptions에 hasNoOptions 넣어 옵션이 여러개가 아니면 화살표 안보이게 함#1604 * [FE] FEAT: Dropdown 옵션이 여러개가 아닐때 화살표 보여줌->안보여줌으로 수정#1604 * [FE] FIX: admin 아이템 기록 뒤로가기 안되는 버그 수정(miyu님 암살 실패) #1604 * [FE] FEAT: 아이템 지급 모달 api 적용#1604 * [FE] REFACT: 아이템 정렬하는 함수 sortItems 정의해서 StoreMainPage, AdminItemProvisionModal에 적용#1604 * [BE] FIX: getAllItems 가격 부등호 오류 수정 * [BE] FIX: 아이템 SKU 기준 오름차순 정렬 * [FE] REFACT: getTypeOptions 만들어 중복되는 코드 대체#1604 * [BE] FIX: DB 가격 정책대로 다시 괄호 변경 * [FE] FIX: 연장권 사용 모달 기본값 수정 #1604 * [FE] REFACT: AdminNavType->CabinetDetailAreaType으로 이름 변경#1604 * [FE] DOCS: cabinetDetailAreaType->cabinetDetailArea.type으로 파일명 변경#1604 * [FE] FIX: SearchListItem 다크모드 적용 안된 부분 적용#1604 * [BE] admin controller 생성 * [BE] admin controller 생성 * FEAT: 관리자 계정에서 아이템 지급 목록 조회 컨트롤러 및 dto 작성 * FEAT: ItemAssignResponseDto mapper 작성 * FEAT: 특정 유저의 아이템 이력 조회 기능 * FIX: 음수 가격의 아이템 및 양수 가격의 아이템 모두 조회 * FEAT: 아이템별 구매 인원 조회 컨트롤러 작성 * FEAT: 아이템 id에 대한 구매 횟수 반환 기능 * [FEAT] admin 특정 기간(한 달)동안 코인 줍기 횟수를 횟수 별로 통계 * [BE] FIX : 사물함 목록 조회 시 과거 기록부터 조회되던 현상 수정 * [FEAT] admin 전체 기간동안의 동전 총 발행량 및 총 사용량 기능 * FEAT: 날짜별 코인 사용량 컨트롤러 작성 * FEAT: 날짜별 코인 사용량 조회 기능 * [BE] FIX : redis 동기화 로직 추가 및 연장권 스케줄러에서 재화 발행하도록 변경 * [BE] FIX: admin 전체 기간동안의 동전 총 발행량 및 총 사용량 반환 부분 변경 * [BE] FIX : 동기화 내에서 불필요한 코드 동기화 밖으로 퇴출 * [BE] FIX: 특정 기간동안 재화 사용량 반환시 사용 시점이 아닌 구매시점 확인 * [BE] FIX: 아이템 히스토리 조회시 구매일 순으로 정렬해서 반환 * [FE] FIX: 페널티 dropdown 모달 뺴기 #1604 * [FE] FEAT: 페널티 dropdown 코드 통일화 시킴 #1604 * [BE] FIX: 메서드명 변경 * [BE] FIX: TotalCoinAmountDto 음수 로직 생성자 내부로 변경 * [BE] FIX: ItemHistoryQueryService 메서드명 find로 변경 * [BE] FIX: 동전 줍기 20일 보상 랜덤값 생성 로직 ThreadLocalRandom으로 변경 * [FE] FIX: 아이템 사용 통계 api 적용 #1604 * [FE] FEAT: coinFlow GE오류 해결 #1604 * [FE] FEAT: 아이템 지급 기록 관련 토글 및 표 삭제 #1604 * [BE] FIX: redis coin 양 저장 key 변경 * [BE] FIX : 재화 통계 요청 시 used 조회 안되는 오류 수정 * [FE] FIX: 동전줍기 20회 이상 줍기 시도시 오류 해결 #1604 * [FE] FIX: coinFlow nivo 데이터 유형 맞추기 #1604 * [FE] FEAT: admin axiosGetUserItems 작성 #1604 * [FE] FEAT: mock data 삭제 및 api 연결 #1604 * [FE] FEAT: AdminItemLogTable 수정 #1604 * [BE] FIX : 동전 줍기 보상 금액을 직접 처리에서 조회 후 처리로 변경(정합성 보장 및 유지보수성 개선) * [FE] FIX: 아이템 사용 통계 그래프 색상 변경 및 까비상점 아이템 정렬 버그 해결 #1604 * [FE] FIX: 동전줍기 20회 이상 모달 오류 수정 #1604 * [FE] FIX: 페널티 모달 버튼 글자 수정 #1604 * [FE] FIX: 어드민 store 페이지 그리드 수정 #1604 * [BE] FIX : Item 관련 일부 메서드 이름 변경 * [FE] FIX: 페널티 아이템 없을시 모달에서 store로 이동 url 넣기 #1604 * [BE] FIX : purchase Item 네이밍 변경 * [FE] FIX: coinFlow 날짜 과거부터 표현 #1604 * [FE] FIX: 그래프 색상 수정 #1604 * [BE] DOCS: TODO from sanan * [FE] FIX: 아이템 지급하기 dto 변경사항 적용#1604 * [FE] FEAT: 동전 줍기 통계 표 변경 및 api 적용#1604 * [FE] FEAT: 동전 줍기 통계 표 css#1604 * [FE] FEAT: 동전 줍기 통계 표 다크모드 적용#1604 * [FE] REFACT: padTo2Digits 사용해서 코드 길이 줄임#1604 * [FE] REFACT: AdminStorePage styled components 컨벤션에 맞게 수정#1604 * [FE] REFACT: StoreHorizontalBarChart 성능 좀 더 높이려 push 말고 스프레드 연산자 사용#1604 * [FE] FIX: StoreHorizontalBarChart에서 유저수가 0명일때 숫자 0 안보이게함#1604 * [FE] FIX: 아이템 지급 시 response modal 메시지 수정 #1604 * [FE] FEAT: 전체 재화 현황 api 받아오도록 수정 #1604 * [FE] FEAT: 아이템 지급 모달 특정 드롭다운 클릭시 다른 드롭다운 닫히게 함#1604 * [FE] FIX: 아이템 지급 실패시 상태코드에 따라 modalTitle 처리#1604 * [FE] FIX: coinFlow tooltip 수정 #1604 * [FE] FEAT: StoreHorizontalBarChart 툴팁 공백 추가#1604 * [FE] FIX: CoinFlow 및 변수명 직관적으로 바꿈 #1604 * [FE] FIX: itembarchart 리팩토링 #1604 * [FE] FIX: dataToggleList 밖에다 선언 및 coinFlow이름 변경 #1604 * [FE] FIX: 모든 Dropdown에 바뀐 구조 적용#1604 * [FE] FEAT: Cointainer 을 Wrapper 로 수정 #1604 * [FE] FIX: 코인 내역에서 보유 코인이 10000 이상일 때 단위 세로로 출력되는 문제 해결 #1604 * [FE] FIX: 까비상점 연장권 구매 모달 수정 #1604 * [FE] FIX: 구매 모달 수정 #1604 * [FE] FIX: 수지회 상태 변경 모달 드롭다운 안열리는 문제 해결#1604 * [FE] FIX: dto 코드 정리하기 #1604 * [FE] FIX: 아이템 통계 그래프 수정 #1604 * [FE] FIX: 재화사용통계 모바일뷰 ui배치 #1604 * [FE] FEAT: 동전 줍기 통계 모바일 뷰 css 적용#1604 * [BE] FIX : 재화 통계 수치 안맞는 오류 수정 * [FE] REFACT: AdminItemProvisionModal item, item type에 따라 변수 및 함수명 수정#1604 * [FE] FIX: 아이템 통계 그래프 너비 수정 #1604 * [FE] FIX: section Alert 문구 강조 변경 #1604 * [FE] FIX: 아이템 통계 그래프 너비 다시 수정 #1604 * [BE] FEAT : 층 전체 사물함 조회 로직 및 오픈 예정 로직에 로직 설명 주석 추가 * [FE] FIX: 아이템 구매 모달 오류 수정 #1604 * [BE] FIX : CabinetFacadeService 줄 정리 및 이름 변경 * [FE] FIX: ItemUsageLogPage 에서 더보기 버튼 누른 후 화면 최상단으로 이동하는 문제 해결 #1604 * [FE] FEAT: PieChartCoin 이름 변경 #1604 * [FE] FEAT: AdminItemUsaLog 에 paginatedData 를 paginatedData.data 로 수정 * [FE] FEAT: AdminItemLogTable 에서 잘못 파싱된 값 수정 #1604 * [FE] FEAT: SearchItemByIntraId 에 getData 로직 추가 #1604 * [FE] FEAT: UserStoreInfoArea 의 return 값을 AdminItemUsageLogPage 로 수정 #1604 * [FE] FEAT: top 속성 0px 로 수정 #1604 * [FE] FEAT: AdminItemHistoryDto purchaseAt 이름 변경 #1604 * [FE] FEAT: left section nav에 알림 등록 안했을때 동아리 섹션 제외 빈 하트 띄움#1604 * [BE] FIX : NPE 발생 오류 수정 * [FE] FEAT: 어드민 left section nav에서 하트 아이콘 안띄움#1604 * [BE] FIX : prev_extension 가격 0원으로 변경 * [FE] FIX: CoinLog 에서 더보기 버튼 누른 후 화면 상단으로 올라가는 버그 수정 #1604 * [FE] FIX: 아이템 사용 통계 이름 변경 및 제목 가운데 정렬 #1604 * [BE] FIX : redis 조회 오류로 인해 로직 순서 변경 * [FE] FIX: 동전줍기 문장 변경 #1604 * [FE] FAET: inventory에 사용 안내문구 넣기 #1604 * [FE] FIX: inventory 변수명변경 #1604 * [FE] FIX: 아이템 구매 모달 리팩토링 #1604 * [FE] FIX: 아이템 사용 내역 아이콘 크기 통일 #1604 * [FE] FIX: IDropdown->IDropdownProps 이름 변경#1604 * [FE] FIX: IDropdown->IDropdownProps 이름 변경2#1604 --------- Co-authored-by: jiminChoi Co-authored-by: jnkeniaem Co-authored-by: gykoh42 Co-authored-by: Minkyu01 Co-authored-by: jiwon Co-authored-by: jeekim <80810728+jnkeniaem@users.noreply.github.com> Co-authored-by: space Co-authored-by: saewoo1 Co-authored-by: Jiwon Park <82518170+Z1Park@users.noreply.github.com> --- backend/database/cabi_local.sql | 75 ++++ .../club/service/AdminClubFacadeService.java | 4 +- .../admin/dto/AdminItemHistoryDto.java | 15 + .../dto/AdminItemHistoryPaginationDto.java | 13 + .../item/controller/AdminItemController.java | 57 +++ .../item/service/AdminItemFacadeService.java | 75 ++++ .../AdminItemStatisticsController.java | 84 +++++ .../service/AdminStatisticsFacadeService.java | 106 ++++++ .../cabinet/alarm/config/AlarmProperties.java | 13 + .../cabinet/alarm/domain/AlarmEvent.java | 5 +- .../cabinet/alarm/domain/AlarmItem.java | 14 + .../alarm/domain/AvailableSectionAlarm.java | 14 + .../cabinet/alarm/domain/ExtensionItem.java | 13 + .../cabinet/alarm/domain/ItemUsage.java | 5 + .../cabinet/alarm/domain/PenaltyItem.java | 12 + .../ftclub/cabinet/alarm/domain/SwapItem.java | 13 + ...ntHandler.java => AlarmEventListener.java} | 2 +- .../alarm/handler/EmailAlarmSender.java | 17 +- .../alarm/handler/ItemUsageHandler.java | 43 +++ .../alarm/handler/PushAlarmSender.java | 13 + .../alarm/handler/SectionAlarmManager.java | 71 ++++ .../alarm/handler/SlackAlarmSender.java | 12 +- .../cabinet/controller/CabinetController.java | 6 +- .../cabinet/cabinet/domain/Location.java | 29 +- .../cabinet/repository/CabinetRepository.java | 7 + .../cabinet/service/CabinetFacadeService.java | 82 ++-- .../cabinet/service/CabinetQueryService.java | 18 + .../cabinet/club/domain/ClubRegistration.java | 1 - .../{user => club}/domain/UserRole.java | 2 +- .../repository/ClubRegistrationRepoitory.java | 2 +- .../club/service/ClubFacadeService.java | 4 +- .../club/service/ClubPolicyService.java | 2 +- .../ClubRegistrationCommandService.java | 2 +- .../service/ClubRegistrationQueryService.java | 2 +- .../config/ArgumentResolverConfig.java | 20 + .../cabinet/dto/ActiveCabinetInfoDto.java | 6 +- .../dto/CabinetsPerSectionResponseDto.java | 1 + .../org/ftclub/cabinet/dto/CoinAmountDto.java | 13 + .../cabinet/dto/CoinCollectStatisticsDto.java | 15 + .../cabinet/dto/CoinCollectedCountDto.java | 14 + .../dto/CoinCollectionRewardResponseDto.java | 11 + .../ftclub/cabinet/dto/CoinHistoryDto.java | 19 + .../cabinet/dto/CoinHistoryPaginationDto.java | 15 + .../cabinet/dto/CoinMonthlyCollectionDto.java | 14 + .../ftclub/cabinet/dto/CoinStaticsDto.java | 13 + .../cabinet/dto/ItemAssignPaginationDto.java | 13 + .../cabinet/dto/ItemAssignRequestDto.java | 18 + .../cabinet/dto/ItemAssignResponseDto.java | 16 + .../org/ftclub/cabinet/dto/ItemCreateDto.java | 21 ++ .../ftclub/cabinet/dto/ItemDetailsDto.java | 16 + .../java/org/ftclub/cabinet/dto/ItemDto.java | 21 ++ .../ftclub/cabinet/dto/ItemHistoryDto.java | 17 + .../cabinet/dto/ItemHistoryPaginationDto.java | 15 + .../cabinet/dto/ItemPurchaseCountDto.java | 13 + .../ftclub/cabinet/dto/ItemResponseDto.java | 12 + .../ftclub/cabinet/dto/ItemStatisticsDto.java | 13 + .../org/ftclub/cabinet/dto/ItemStoreDto.java | 26 ++ .../cabinet/dto/ItemStoreResponseDto.java | 16 + .../ftclub/cabinet/dto/ItemUseRequestDto.java | 21 ++ .../ftclub/cabinet/dto/MyItemResponseDto.java | 17 + .../cabinet/dto/MyProfileResponseDto.java | 8 +- .../cabinet/dto/TotalCoinAmountDto.java | 15 + .../cabinet/exception/ExceptionStatus.java | 11 +- .../item/controller/ItemController.java | 140 +++++++ .../cabinet/item/domain/CoinHistoryType.java | 11 + .../org/ftclub/cabinet/item/domain/Item.java | 88 +++++ .../cabinet/item/domain/ItemHistory.java | 111 ++++++ .../ftclub/cabinet/item/domain/ItemType.java | 37 ++ .../cabinet/item/domain/ItemUseValidator.java | 48 +++ .../cabinet/item/domain/SectionAlarm.java | 108 ++++++ .../org/ftclub/cabinet/item/domain/Sku.java | 59 +++ .../cabinet/item/domain/ValidItemUse.java | 21 ++ .../repository/ItemHistoryRepository.java | 93 +++++ .../cabinet/item/repository/ItemRedis.java | 143 +++++++ .../item/repository/ItemRepository.java | 35 ++ .../repository/SectionAlarmRepository.java | 39 ++ .../item/service/ItemCommandService.java | 24 ++ .../item/service/ItemFacadeService.java | 320 ++++++++++++++++ .../service/ItemHistoryCommandService.java | 31 ++ .../item/service/ItemHistoryQueryService.java | 55 +++ .../item/service/ItemPolicyService.java | 67 ++++ .../item/service/ItemQueryService.java | 37 ++ .../item/service/ItemRedisService.java | 120 ++++++ .../service/SectionAlarmCommandService.java | 28 ++ .../service/SectionAlarmQueryService.java | 27 ++ .../cabinet/lent/domain/LentPolicyStatus.java | 7 +- .../cabinet/lent/repository/LentRedis.java | 53 +-- .../lent/repository/LentRepository.java | 9 +- .../lent/service/LentFacadeService.java | 25 ++ .../lent/service/LentPolicyService.java | 10 +- .../lent/service/LentQueryService.java | 3 +- .../lent/service/LentRedisService.java | 51 ++- .../ftclub/cabinet/mapper/CabinetMapper.java | 54 ++- .../org/ftclub/cabinet/mapper/ItemMapper.java | 93 +++++ .../org/ftclub/cabinet/mapper/UserMapper.java | 32 +- .../controller/PresentationController.java | 22 +- .../service/PresentationService.java | 82 ++-- .../cabinet/user/domain/BanHistory.java | 11 +- .../cabinet/user/domain/UserAspect.java | 63 ---- .../domain/UserSessionArgumentResolver.java | 68 ++++ .../service/BanHistoryCommandService.java | 13 + .../user/service/BanHistoryQueryService.java | 15 +- .../service/LentExtensionCommandService.java | 21 +- .../user/service/LentExtensionManager.java | 47 ++- .../user/service/UserFacadeService.java | 30 +- .../utils/annotations/NullableMapper.java | 26 -- .../cabinet/utils/lock/IntegerLock.java | 7 + .../ftclub/cabinet/utils/lock/LockUtil.java | 16 + .../ftclub/cabinet/utils/lock/VoidLock.java | 7 + .../cabinet/utils/release/ReleaseManager.java | 2 +- .../utils/scheduler/SystemScheduler.java | 8 + .../resources/templates/mail/lentsuccess.html | 68 ++-- .../resources/templates/mail/overdue.html | 72 ++-- .../templates/mail/sectionAlarm.html | 31 ++ .../resources/templates/mail/soonoverdue.html | 75 ++-- .../service/CabinetFacadeServiceTest.java | 13 +- .../cabinet/mapper/CabinetMapperTest.java | 63 ++-- .../repository/UserOptionalFetcherTest.java | 2 +- config | 2 +- delete_local_branchs.sh | 20 + frontend/index.html | 2 +- frontend/package-lock.json | 6 +- frontend/package.json | 2 +- frontend/src/App.tsx | 10 + .../src/Cabinet/api/axios/axios.custom.ts | 209 ++++++++++ frontend/src/Cabinet/assets/css/media.css | 30 ++ .../src/Cabinet/assets/data/ColorTheme.ts | 74 ++-- .../src/Cabinet/assets/data/ManualContent.ts | 19 +- .../src/Cabinet/assets/data/SlackAlarm.ts | 2 +- .../Cabinet/assets/data/mapPositionData.ts | 6 + frontend/src/Cabinet/assets/data/maps.ts | 48 ++- .../src/Cabinet/assets/images/clubIcon.svg | 12 +- .../src/Cabinet/assets/images/coinIcon.svg | 5 + frontend/src/Cabinet/assets/images/crown.svg | 4 +- .../Cabinet/assets/images/dollar-circle.svg | 5 + .../Cabinet/assets/images/dropdownChevron.svg | 2 +- .../src/Cabinet/assets/images/extension.svg | 2 +- .../src/Cabinet/assets/images/filledHeart.svg | 3 + frontend/src/Cabinet/assets/images/leader.svg | 3 - .../src/Cabinet/assets/images/lineHeart.svg | 3 + .../src/Cabinet/assets/images/privateIcon.svg | 2 +- .../src/Cabinet/assets/images/shareIcon.svg | 2 +- .../src/Cabinet/assets/images/storeAlarm.svg | 4 + .../src/Cabinet/assets/images/storeCoin.svg | 9 + .../assets/images/storeCoinCheckFin.svg | 11 + .../assets/images/storeCoinCheckOff.svg | 4 + .../assets/images/storeCoinCheckOn.svg | 10 + .../Cabinet/assets/images/storeCoinNav.svg | 5 + .../Cabinet/assets/images/storeExtension.svg | 6 + .../Cabinet/assets/images/storeIconGray.svg | 7 + .../src/Cabinet/assets/images/storeMove.svg | 7 + .../Cabinet/assets/images/storePenalty.svg | 5 + .../AdminInfo/Chart/CoinUseLineChart.tsx | 131 +++++++ .../AdminInfo/Chart/ItemBarChart.tsx | 163 ++++++++ .../AdminInfo/Chart/PieChartCoin.tsx | 117 ++++++ .../Chart/StoreHorizontalBarChart.tsx | 143 +++++++ .../components/AdminInfo/Table/AdminTable.tsx | 2 +- .../components/Available/FloorContainer.tsx | 46 ++- .../CabinetInfoArea/AdminCabinetInfoArea.tsx | 38 +- .../CabinetInfoArea/CabinetInfoArea.tsx | 73 ++-- .../CountTime/CountTime.container.tsx | 11 +- .../CabinetListItem/AdminCabinetListItem.tsx | 11 +- .../CabinetListItem/CabinetListItem.tsx | 6 +- frontend/src/Cabinet/components/Card/Card.tsx | 9 +- .../ClubCabinetInfoCard.tsx | 3 +- .../Card/ClubNoticeCard/ClubNoticeCard.tsx | 2 +- .../ColorTheme/ColorTheme.tsx | 104 ----- .../DisplayStyleCard.container.tsx | 205 +++------- .../DisplayStyleCard/DisplayStyleCard.tsx | 178 +++++---- .../DisplayStyleCard/colorThemeInitializer.ts | 32 -- .../displayStyleInitializer.ts | 33 ++ .../Card/ExtensionCard/ExtensionCard.tsx | 2 +- .../LentInfoCard/LentInfoCard.container.tsx | 46 ++- .../Card/LentInfoCard/LentInfoCard.tsx | 178 +++++---- .../ColorPicker.tsx | 4 +- .../PointColor.tsx | 4 +- .../PointColorCard.container.tsx | 127 +++++++ .../Card/PointColorCard/PointColorCard.tsx | 98 +++++ .../colorInfo.ts | 2 +- .../Card/StoreItemCard/StoreItemCard.tsx | 145 +++++++ .../src/Cabinet/components/Club/ClubInfo.tsx | 39 +- .../ClubMemberInfoArea/ClubMemberInfoArea.tsx | 43 +-- .../Club/ClubMemberList/ClubMemberList.tsx | 5 + .../ClubMemberListItem/ClubMemberListItem.tsx | 1 - .../components/Common/ClubListDropdown.tsx | 4 +- .../Cabinet/components/Common/Dropdown.tsx | 126 ++++--- .../Common/MultiSelectFilterButton.tsx | 2 +- .../components/Common/SelectInduction.tsx | 43 +++ .../Cabinet/components/Common/Selector.tsx | 15 +- .../components/Common/ToggleSwitch.tsx | 4 +- .../components/Common/UnavailableDataInfo.tsx | 68 ++++ .../components/Common/WarningNotification.tsx | 34 +- .../components/Home/ManualContentBox.tsx | 40 +- .../Cabinet/components/Home/ServiceManual.tsx | 2 +- .../ItemLog/AdminItemProvideLog.container.tsx | 112 ++++++ .../ItemLog/AdminItemProvideLog.tsx | 124 ++++++ .../ItemLog/AdminItemUsageLog.container.tsx | 79 ++++ .../components/ItemLog/AdminItemUsageLog.tsx | 124 ++++++ .../ItemLogTable/AdminItemLogTable.tsx | 113 ++++++ .../ItemLogTable/AdminItemProvideTable.tsx | 111 ++++++ .../LeftClubNav.tsx} | 7 +- .../LeftMainNav/LeftMainNav.container.tsx | 19 +- .../LeftNav/LeftMainNav/LeftMainNav.tsx | 52 ++- .../Cabinet/components/LeftNav/LeftNav.tsx | 27 +- .../LeftNav/LeftProfileNav/LeftProfileNav.tsx | 118 ++++++ .../LeftSectionNav.container.tsx | 67 ---- .../LeftNav/LeftSectionNav/LeftSectionNav.tsx | 182 +++------ .../LeftNav/LeftStoreNav/LeftStoreNav.tsx | 152 ++++++++ .../components/LentLog/AdminLentLog.tsx | 3 +- .../components/LentLog/LogTable/LogTable.tsx | 1 - .../components/Login/AdminLoginTemplate.tsx | 2 +- .../components/MapInfo/MapItem/MapItem.tsx | 4 +- .../components/Modals/BanModal/BanModal.tsx | 26 +- .../Modals/ClubModal/AddClubMemberModal.tsx | 2 +- .../components/Modals/ClubModal/ClubModal.tsx | 2 +- .../Modals/ExtendModal/ExtendModal.tsx | 213 +++++++++-- .../Modals/ManualModal/ManualModal.tsx | 21 +- .../components/Modals/MemoModal/MemoModal.tsx | 15 +- .../src/Cabinet/components/Modals/Modal.tsx | 40 +- .../OverduePenaltyModal.tsx | 31 +- .../Modals/ResponseModal/ResponseModal.tsx | 10 +- .../Modals/ReturnModal/AdminReturnModal.tsx | 3 +- .../Modals/ReturnModal/ReturnModal.tsx | 30 +- .../SectionAlertModal/SectionAlertModal.tsx | 113 ++++++ .../Modals/StatusModal/StatusModal.tsx | 13 +- .../StoreModal/AdminItemProvisionModal.tsx | 182 +++++++++ .../Modals/StoreModal/StoreBuyItemModal.tsx | 164 ++++++++ .../StoreModal/StoreBuyPenaltyModal.tsx | 230 +++++++++++ .../components/Modals/SwapModal/SwapModal.tsx | 45 ++- .../components/Search/SearchItemByIntraId.tsx | 236 ++++++++---- .../components/Search/SearchItemByNum.tsx | 89 +++-- .../SectionPagination.container.tsx | 25 +- .../SectionPagination/SectionPagination.tsx | 10 +- .../SlackNoti/SlackNotiSearchBar.tsx | 2 +- .../UserStoreInfoArea/UserStoreInfoArea.tsx | 152 ++++++++ .../components/Store/CoinAnimation.tsx | 31 ++ .../components/Store/CoinLog/CoinLog.tsx | 339 +++++++++++++++++ .../components/Store/Inventory/Inventory.tsx | 68 ++++ .../Store/Inventory/InventoryItem.tsx | 299 +++++++++++++++ .../Store/ItemUsageLog/ItemLogBlock.tsx | 101 +++++ .../components/Store/StoreCoinCheckBox.tsx | 72 ++++ .../components/Store/StoreCoinPick.tsx | 48 +++ .../Cabinet/components/Store/StoreInfo.tsx | 163 ++++++++ .../TopNav/AdminTopNav.container.tsx | 5 +- .../components/TopNav/SearchBar/SearchBar.tsx | 33 +- .../SearchListItem/SearchListItem.tsx | 27 +- .../components/TopNav/TopNav.container.tsx | 5 +- .../TopNavButton/TopNavButton.tsx | 13 +- .../TopNavButtonGroup/TopNavButtonGroup.tsx | 8 +- .../UserCabinetInfoArea.container.tsx} | 15 +- .../UserCabinetInfoArea.tsx} | 39 +- frontend/src/Cabinet/hooks/useMenu.ts | 84 ++++- frontend/src/Cabinet/hooks/useMultiSelect.ts | 8 +- frontend/src/Cabinet/pages/CoinLogPage.tsx | 7 + frontend/src/Cabinet/pages/InventoryPage.tsx | 7 + .../src/Cabinet/pages/ItemUsageLogPage.tsx | 175 +++++++++ frontend/src/Cabinet/pages/Layout.tsx | 14 +- frontend/src/Cabinet/pages/MainPage.tsx | 84 ++++- frontend/src/Cabinet/pages/ProfilePage.tsx | 20 +- frontend/src/Cabinet/pages/StoreMainPage.tsx | 171 +++++++++ .../src/Cabinet/pages/admin/AdminClubPage.tsx | 4 +- .../src/Cabinet/pages/admin/AdminHomePage.tsx | 9 +- .../pages/admin/AdminItemUsageLogPage.tsx | 68 ++++ .../src/Cabinet/pages/admin/AdminLayout.tsx | 22 +- .../src/Cabinet/pages/admin/AdminMainPage.tsx | 15 +- .../pages/admin/AdminSlackNotiPage.tsx | 2 +- .../Cabinet/pages/admin/AdminStorePage.tsx | 357 ++++++++++++++++++ frontend/src/Cabinet/recoil/atoms.ts | 6 +- frontend/src/Cabinet/recoil/selectors.ts | 10 +- frontend/src/Cabinet/types/dto/admin.dto.ts | 46 +++ frontend/src/Cabinet/types/dto/cabinet.dto.ts | 8 +- frontend/src/Cabinet/types/dto/store.dto.ts | 40 ++ frontend/src/Cabinet/types/dto/user.dto.ts | 4 +- .../types/enum/cabinetDetailArea.type.enum.ts | 7 + ...type.enum.ts => displayStyle.type.enum.ts} | 4 +- frontend/src/Cabinet/types/enum/store.enum.ts | 35 ++ frontend/src/Cabinet/utils/dateUtils.ts | 43 ++- .../Details/DetailContent.container.tsx | 5 +- .../LeftNav/LeftMainNav/LeftMainNav.tsx | 2 +- .../components/LeftNav/LeftNav.tsx | 1 - .../EditStatusModal/EditStatusModal.tsx | 93 +++-- .../components/Register/InputField.tsx | 2 +- frontend/src/assets/images/sadCcabi.svg | 8 - frontend/src/custom.d.ts | 16 + frontend/src/index.css | 11 +- frontend/src/main.tsx | 12 +- frontend/stats.json | 12 +- redis/redis_clear.sh | 11 + 288 files changed, 10591 insertions(+), 1933 deletions(-) create mode 100644 backend/src/main/java/org/ftclub/cabinet/admin/dto/AdminItemHistoryDto.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/admin/dto/AdminItemHistoryPaginationDto.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/admin/item/controller/AdminItemController.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/admin/item/service/AdminItemFacadeService.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/admin/statistics/controller/AdminItemStatisticsController.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/alarm/domain/AlarmItem.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/alarm/domain/AvailableSectionAlarm.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/alarm/domain/ExtensionItem.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/alarm/domain/ItemUsage.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/alarm/domain/PenaltyItem.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/alarm/domain/SwapItem.java rename backend/src/main/java/org/ftclub/cabinet/alarm/handler/{AlarmEventHandler.java => AlarmEventListener.java} (98%) create mode 100644 backend/src/main/java/org/ftclub/cabinet/alarm/handler/ItemUsageHandler.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/alarm/handler/SectionAlarmManager.java rename backend/src/main/java/org/ftclub/cabinet/{user => club}/domain/UserRole.java (76%) create mode 100644 backend/src/main/java/org/ftclub/cabinet/config/ArgumentResolverConfig.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/dto/CoinAmountDto.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/dto/CoinCollectStatisticsDto.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/dto/CoinCollectedCountDto.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/dto/CoinCollectionRewardResponseDto.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/dto/CoinHistoryDto.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/dto/CoinHistoryPaginationDto.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/dto/CoinMonthlyCollectionDto.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/dto/CoinStaticsDto.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/dto/ItemAssignPaginationDto.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/dto/ItemAssignRequestDto.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/dto/ItemAssignResponseDto.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/dto/ItemCreateDto.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/dto/ItemDetailsDto.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/dto/ItemDto.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/dto/ItemHistoryDto.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/dto/ItemHistoryPaginationDto.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/dto/ItemPurchaseCountDto.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/dto/ItemResponseDto.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/dto/ItemStatisticsDto.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/dto/ItemStoreDto.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/dto/ItemStoreResponseDto.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/dto/ItemUseRequestDto.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/dto/MyItemResponseDto.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/dto/TotalCoinAmountDto.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/item/controller/ItemController.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/item/domain/CoinHistoryType.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/item/domain/Item.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/item/domain/ItemHistory.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/item/domain/ItemType.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/item/domain/ItemUseValidator.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/item/domain/SectionAlarm.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/item/domain/Sku.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/item/domain/ValidItemUse.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/item/repository/ItemHistoryRepository.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/item/repository/ItemRedis.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/item/repository/ItemRepository.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/item/repository/SectionAlarmRepository.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/item/service/ItemCommandService.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/item/service/ItemFacadeService.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/item/service/ItemHistoryCommandService.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/item/service/ItemHistoryQueryService.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/item/service/ItemPolicyService.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/item/service/ItemQueryService.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/item/service/ItemRedisService.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/item/service/SectionAlarmCommandService.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/item/service/SectionAlarmQueryService.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/mapper/ItemMapper.java delete mode 100644 backend/src/main/java/org/ftclub/cabinet/user/domain/UserAspect.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/user/domain/UserSessionArgumentResolver.java delete mode 100644 backend/src/main/java/org/ftclub/cabinet/utils/annotations/NullableMapper.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/utils/lock/IntegerLock.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/utils/lock/LockUtil.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/utils/lock/VoidLock.java create mode 100644 backend/src/main/resources/templates/mail/sectionAlarm.html create mode 100755 delete_local_branchs.sh create mode 100644 frontend/src/Cabinet/assets/images/coinIcon.svg create mode 100644 frontend/src/Cabinet/assets/images/dollar-circle.svg create mode 100644 frontend/src/Cabinet/assets/images/filledHeart.svg delete mode 100644 frontend/src/Cabinet/assets/images/leader.svg create mode 100644 frontend/src/Cabinet/assets/images/lineHeart.svg create mode 100644 frontend/src/Cabinet/assets/images/storeAlarm.svg create mode 100644 frontend/src/Cabinet/assets/images/storeCoin.svg create mode 100644 frontend/src/Cabinet/assets/images/storeCoinCheckFin.svg create mode 100644 frontend/src/Cabinet/assets/images/storeCoinCheckOff.svg create mode 100644 frontend/src/Cabinet/assets/images/storeCoinCheckOn.svg create mode 100644 frontend/src/Cabinet/assets/images/storeCoinNav.svg create mode 100644 frontend/src/Cabinet/assets/images/storeExtension.svg create mode 100644 frontend/src/Cabinet/assets/images/storeIconGray.svg create mode 100644 frontend/src/Cabinet/assets/images/storeMove.svg create mode 100644 frontend/src/Cabinet/assets/images/storePenalty.svg create mode 100644 frontend/src/Cabinet/components/AdminInfo/Chart/CoinUseLineChart.tsx create mode 100644 frontend/src/Cabinet/components/AdminInfo/Chart/ItemBarChart.tsx create mode 100644 frontend/src/Cabinet/components/AdminInfo/Chart/PieChartCoin.tsx create mode 100644 frontend/src/Cabinet/components/AdminInfo/Chart/StoreHorizontalBarChart.tsx delete mode 100644 frontend/src/Cabinet/components/Card/DisplayStyleCard/ColorTheme/ColorTheme.tsx delete mode 100644 frontend/src/Cabinet/components/Card/DisplayStyleCard/colorThemeInitializer.ts create mode 100644 frontend/src/Cabinet/components/Card/DisplayStyleCard/displayStyleInitializer.ts rename frontend/src/Cabinet/components/Card/{DisplayStyleCard/PointColor => PointColorCard}/ColorPicker.tsx (93%) rename frontend/src/Cabinet/components/Card/{DisplayStyleCard/PointColor => PointColorCard}/PointColor.tsx (91%) create mode 100644 frontend/src/Cabinet/components/Card/PointColorCard/PointColorCard.container.tsx create mode 100644 frontend/src/Cabinet/components/Card/PointColorCard/PointColorCard.tsx rename frontend/src/Cabinet/components/Card/{DisplayStyleCard => PointColorCard}/colorInfo.ts (97%) create mode 100644 frontend/src/Cabinet/components/Card/StoreItemCard/StoreItemCard.tsx create mode 100644 frontend/src/Cabinet/components/Common/SelectInduction.tsx create mode 100644 frontend/src/Cabinet/components/Common/UnavailableDataInfo.tsx create mode 100644 frontend/src/Cabinet/components/ItemLog/AdminItemProvideLog.container.tsx create mode 100644 frontend/src/Cabinet/components/ItemLog/AdminItemProvideLog.tsx create mode 100644 frontend/src/Cabinet/components/ItemLog/AdminItemUsageLog.container.tsx create mode 100644 frontend/src/Cabinet/components/ItemLog/AdminItemUsageLog.tsx create mode 100644 frontend/src/Cabinet/components/ItemLog/ItemLogTable/AdminItemLogTable.tsx create mode 100644 frontend/src/Cabinet/components/ItemLog/ItemLogTable/AdminItemProvideTable.tsx rename frontend/src/Cabinet/components/LeftNav/{LeftSectionNav/LeftSectionNavClubs.tsx => LeftClubNav/LeftClubNav.tsx} (92%) create mode 100644 frontend/src/Cabinet/components/LeftNav/LeftProfileNav/LeftProfileNav.tsx delete mode 100644 frontend/src/Cabinet/components/LeftNav/LeftSectionNav/LeftSectionNav.container.tsx create mode 100644 frontend/src/Cabinet/components/LeftNav/LeftStoreNav/LeftStoreNav.tsx create mode 100644 frontend/src/Cabinet/components/Modals/SectionAlertModal/SectionAlertModal.tsx create mode 100644 frontend/src/Cabinet/components/Modals/StoreModal/AdminItemProvisionModal.tsx create mode 100644 frontend/src/Cabinet/components/Modals/StoreModal/StoreBuyItemModal.tsx create mode 100644 frontend/src/Cabinet/components/Modals/StoreModal/StoreBuyPenaltyModal.tsx create mode 100644 frontend/src/Cabinet/components/Store/Admin/UserStoreInfoArea/UserStoreInfoArea.tsx create mode 100644 frontend/src/Cabinet/components/Store/CoinAnimation.tsx create mode 100644 frontend/src/Cabinet/components/Store/CoinLog/CoinLog.tsx create mode 100644 frontend/src/Cabinet/components/Store/Inventory/Inventory.tsx create mode 100644 frontend/src/Cabinet/components/Store/Inventory/InventoryItem.tsx create mode 100644 frontend/src/Cabinet/components/Store/ItemUsageLog/ItemLogBlock.tsx create mode 100644 frontend/src/Cabinet/components/Store/StoreCoinCheckBox.tsx create mode 100644 frontend/src/Cabinet/components/Store/StoreCoinPick.tsx create mode 100644 frontend/src/Cabinet/components/Store/StoreInfo.tsx rename frontend/src/Cabinet/components/{UserInfoArea/UserInfoArea.container.tsx => UserCabinetInfoArea/UserCabinetInfoArea.container.tsx} (67%) rename frontend/src/Cabinet/components/{UserInfoArea/UserInfoArea.tsx => UserCabinetInfoArea/UserCabinetInfoArea.tsx} (82%) create mode 100644 frontend/src/Cabinet/pages/CoinLogPage.tsx create mode 100644 frontend/src/Cabinet/pages/InventoryPage.tsx create mode 100644 frontend/src/Cabinet/pages/ItemUsageLogPage.tsx create mode 100644 frontend/src/Cabinet/pages/StoreMainPage.tsx create mode 100644 frontend/src/Cabinet/pages/admin/AdminItemUsageLogPage.tsx create mode 100644 frontend/src/Cabinet/pages/admin/AdminStorePage.tsx create mode 100644 frontend/src/Cabinet/types/dto/store.dto.ts create mode 100644 frontend/src/Cabinet/types/enum/cabinetDetailArea.type.enum.ts rename frontend/src/Cabinet/types/enum/{colorTheme.type.enum.ts => displayStyle.type.enum.ts} (73%) create mode 100644 frontend/src/Cabinet/types/enum/store.enum.ts delete mode 100644 frontend/src/assets/images/sadCcabi.svg create mode 100644 redis/redis_clear.sh diff --git a/backend/database/cabi_local.sql b/backend/database/cabi_local.sql index 2268f4a07..fe882bb56 100644 --- a/backend/database/cabi_local.sql +++ b/backend/database/cabi_local.sql @@ -705,6 +705,81 @@ CREATE TABLE `club_registration` COLLATE = utf8mb4_general_ci; +DROP TABLE IF EXISTS `item`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `item` +( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `price` bigint(20) NOT NULL, + `sku` varchar(64) NOT NULL, + `type` varchar(64) NOT NULL, + PRIMARY KEY (`id`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_general_ci; + +LOCK TABLES `item` WRITE; +/*!40000 ALTER TABLE `item` + DISABLE KEYS */; +INSERT INTO `item` +VALUES (1, 0, 'EXTENSION_PREV', 'EXTENSION'), + (2, -2000, 'EXTENSION_31', 'EXTENSION'), + (3, -1200, 'EXTENSION_15', 'EXTENSION'), + (4, -300, 'EXTENSION_3', 'EXTENSION'), + (5, -6200, 'PENALTY_31', 'PENALTY'), + (6, -1400, 'PENALTY_7', 'PENALTY'), + (7, -600, 'PENALTY_3', 'PENALTY'), + (8, -100, 'SWAP', 'SWAP'), + (9, -100, 'ALARM', 'ALARM'), + (10, 10, 'COIN_COLLECT', 'COIN_COLLECT'), + (11, 2000, 'COIN_FULL_TIME', 'COIN_FULL_TIME'), + (12, 200, 'COIN_REWARD_200', 'COIN_REWARD'), + (13, 500, 'COIN_REWARD_500', 'COIN_REWARD'), + (14, 1000, 'COIN_REWARD_1000', 'COIN_REWARD'), + (15, 2000, 'COIN_REWARD_2000', 'COIN_REWARD'); +/*!40000 ALTER TABLE `item` + ENABLE KEYS */; +UNLOCK TABLES; + + +DROP TABLE IF EXISTS `item_history`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `item_history` +( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `item_id` bigint(20) NOT NULL, + `user_id` bigint(20) NOT NULL, + `purchase_at` datetime(6) NOT NULL, + `used_at` datetime(6) DEFAULT NULL, + PRIMARY KEY (`id`), + CONSTRAINT `item_history_item_id` FOREIGN KEY (`item_id`) REFERENCES `item` (`id`), + CONSTRAINT `item_history_user_id` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_general_ci; + + +DROP TABLE IF EXISTS `section_alarm`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `section_alarm` +( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `cabinet_place_id` bigint(20) NOT NULL, + `user_id` bigint(20) NOT NULL, + `registered_at` datetime(6) NOT NULL, + `alarmed_at` datetime(6) DEFAULT NULL, + PRIMARY KEY (`id`), + CONSTRAINT `section_alarm_cabinet_place_id` + FOREIGN KEY (`cabinet_place_id`) REFERENCES `cabinet_place` (`id`), + CONSTRAINT `section_alarm_user_id` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_general_ci; + + /*!40103 SET TIME_ZONE = @OLD_TIME_ZONE */; /*!40101 SET SQL_MODE = @OLD_SQL_MODE */; diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/club/service/AdminClubFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/admin/club/service/AdminClubFacadeService.java index cf2bced7d..ef8b1ffc4 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/club/service/AdminClubFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/club/service/AdminClubFacadeService.java @@ -1,6 +1,6 @@ package org.ftclub.cabinet.admin.club.service; -import static org.ftclub.cabinet.user.domain.UserRole.CLUB_ADMIN; +import static org.ftclub.cabinet.club.domain.UserRole.CLUB_ADMIN; import java.util.List; import java.util.Map; @@ -9,6 +9,7 @@ import lombok.RequiredArgsConstructor; import org.ftclub.cabinet.club.domain.Club; import org.ftclub.cabinet.club.domain.ClubRegistration; +import org.ftclub.cabinet.club.domain.UserRole; import org.ftclub.cabinet.club.service.ClubCommandService; import org.ftclub.cabinet.club.service.ClubQueryService; import org.ftclub.cabinet.club.service.ClubRegistrationCommandService; @@ -19,7 +20,6 @@ import org.ftclub.cabinet.log.Logging; import org.ftclub.cabinet.mapper.ClubMapper; import org.ftclub.cabinet.user.domain.User; -import org.ftclub.cabinet.user.domain.UserRole; import org.ftclub.cabinet.user.service.UserQueryService; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/dto/AdminItemHistoryDto.java b/backend/src/main/java/org/ftclub/cabinet/admin/dto/AdminItemHistoryDto.java new file mode 100644 index 000000000..99bab75df --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/admin/dto/AdminItemHistoryDto.java @@ -0,0 +1,15 @@ +package org.ftclub.cabinet.admin.dto; + +import java.time.LocalDateTime; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class AdminItemHistoryDto { + + private LocalDateTime purchaseAt; + private LocalDateTime usedAt; + private String itemName; + private String itemDetails; +} diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/dto/AdminItemHistoryPaginationDto.java b/backend/src/main/java/org/ftclub/cabinet/admin/dto/AdminItemHistoryPaginationDto.java new file mode 100644 index 000000000..e5710392e --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/admin/dto/AdminItemHistoryPaginationDto.java @@ -0,0 +1,13 @@ +package org.ftclub.cabinet.admin.dto; + +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class AdminItemHistoryPaginationDto { + + private List itemHistories; + private Long totalLength; +} diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/item/controller/AdminItemController.java b/backend/src/main/java/org/ftclub/cabinet/admin/item/controller/AdminItemController.java new file mode 100644 index 000000000..f3932160e --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/admin/item/controller/AdminItemController.java @@ -0,0 +1,57 @@ +package org.ftclub.cabinet.admin.item.controller; + +import lombok.RequiredArgsConstructor; +import org.ftclub.cabinet.admin.dto.AdminItemHistoryPaginationDto; +import org.ftclub.cabinet.admin.item.service.AdminItemFacadeService; +import org.ftclub.cabinet.admin.statistics.service.AdminStatisticsFacadeService; +import org.ftclub.cabinet.auth.domain.AuthGuard; +import org.ftclub.cabinet.auth.domain.AuthLevel; +import org.ftclub.cabinet.dto.ItemAssignRequestDto; +import org.ftclub.cabinet.dto.ItemCreateDto; +import org.ftclub.cabinet.log.Logging; +import org.springframework.data.domain.Pageable; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RequestMapping("/v5/admin/items") +@RequiredArgsConstructor +@RestController +@Logging +public class AdminItemController { + + private final AdminItemFacadeService adminItemFacadeService; + private final AdminStatisticsFacadeService adminStatisticsFacadeService; + + + @PostMapping("") + @AuthGuard(level = AuthLevel.ADMIN_ONLY) + public void createItem(@RequestBody ItemCreateDto itemCreateDto) { + adminItemFacadeService.createItem(itemCreateDto.getPrice(), + itemCreateDto.getSku(), itemCreateDto.getType()); + } + + @PostMapping("/assign") + @AuthGuard(level = AuthLevel.ADMIN_ONLY) + public void assignItem(@RequestBody ItemAssignRequestDto itemAssignRequestDto) { + adminItemFacadeService.assignItem(itemAssignRequestDto.getUserIds(), + itemAssignRequestDto.getItemSku()); + } + + /** + * 특정 유저의 아이템 history 조회 + * + * @param userId + * @param pageable + * @return + */ + @GetMapping("/users/{userId}") + @AuthGuard(level = AuthLevel.ADMIN_ONLY) + public AdminItemHistoryPaginationDto getUserItemHistoryPagination( + @PathVariable(value = "userId") Long userId, Pageable pageable) { + return adminItemFacadeService.getUserItemHistories(userId, pageable); + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/item/service/AdminItemFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/admin/item/service/AdminItemFacadeService.java new file mode 100644 index 000000000..2e98fd711 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/admin/item/service/AdminItemFacadeService.java @@ -0,0 +1,75 @@ +package org.ftclub.cabinet.admin.item.service; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import org.ftclub.cabinet.admin.dto.AdminItemHistoryDto; +import org.ftclub.cabinet.admin.dto.AdminItemHistoryPaginationDto; +import org.ftclub.cabinet.dto.ItemPurchaseCountDto; +import org.ftclub.cabinet.dto.ItemStatisticsDto; +import org.ftclub.cabinet.item.domain.Item; +import org.ftclub.cabinet.item.domain.ItemHistory; +import org.ftclub.cabinet.item.domain.ItemType; +import org.ftclub.cabinet.item.domain.Sku; +import org.ftclub.cabinet.item.service.ItemCommandService; +import org.ftclub.cabinet.item.service.ItemHistoryCommandService; +import org.ftclub.cabinet.item.service.ItemHistoryQueryService; +import org.ftclub.cabinet.item.service.ItemPolicyService; +import org.ftclub.cabinet.item.service.ItemQueryService; +import org.ftclub.cabinet.mapper.ItemMapper; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class AdminItemFacadeService { + + private final ItemQueryService itemQueryService; + private final ItemCommandService itemCommandService; + private final ItemHistoryCommandService itemHistoryCommandService; + private final ItemHistoryQueryService itemHistoryQueryService; + private final ItemPolicyService itemPolicyService; + private final ItemMapper itemMapper; + + @Transactional + public void createItem(Integer Price, Sku sku, ItemType type) { + itemCommandService.createItem(Price, sku, type); + } + + @Transactional + public void assignItem(List userIds, Sku sku) { + Item item = itemQueryService.getBySku(sku); + LocalDateTime now = null; + if (item.getPrice() > 0) { + now = LocalDateTime.now(); + } + itemHistoryCommandService.createItemHistories(userIds, item.getId(), now); + } + + @Transactional(readOnly = true) + public AdminItemHistoryPaginationDto getUserItemHistories(Long userId, Pageable pageable) { + Page itemHistoryWithItem = + itemHistoryQueryService.findItemHistoriesByUserIdWithItem(userId, pageable); + + List result = itemHistoryWithItem.stream() + .map(ih -> itemMapper.toAdminItemHistoryDto(ih, ih.getItem())) + .collect(Collectors.toList()); + + return new AdminItemHistoryPaginationDto(result, itemHistoryWithItem.getTotalElements()); + } + + @Transactional(readOnly = true) + public ItemStatisticsDto getItemPurchaseStatistics() { + List itemsOnSale = itemQueryService.getUseItemIds(); + List result = itemsOnSale.stream() + .map(item -> { + int userCount = itemHistoryQueryService.findPurchaseCountByItemId(item.getId()); + return itemMapper.toItemPurchaseCountDto(item, userCount); + }).collect(Collectors.toList()); + + return new ItemStatisticsDto(result); + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/statistics/controller/AdminItemStatisticsController.java b/backend/src/main/java/org/ftclub/cabinet/admin/statistics/controller/AdminItemStatisticsController.java new file mode 100644 index 000000000..71232d964 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/admin/statistics/controller/AdminItemStatisticsController.java @@ -0,0 +1,84 @@ +package org.ftclub.cabinet.admin.statistics.controller; + +import java.time.LocalDateTime; +import lombok.RequiredArgsConstructor; +import org.ftclub.cabinet.admin.item.service.AdminItemFacadeService; +import org.ftclub.cabinet.admin.statistics.service.AdminStatisticsFacadeService; +import org.ftclub.cabinet.auth.domain.AuthGuard; +import org.ftclub.cabinet.auth.domain.AuthLevel; +import org.ftclub.cabinet.dto.CoinCollectStatisticsDto; +import org.ftclub.cabinet.dto.CoinStaticsDto; +import org.ftclub.cabinet.dto.ItemStatisticsDto; +import org.ftclub.cabinet.dto.TotalCoinAmountDto; +import org.ftclub.cabinet.log.Logging; +import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.format.annotation.DateTimeFormat.ISO; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/v5/admin/statistics") +@RequiredArgsConstructor +@Logging +public class AdminItemStatisticsController { + + private final AdminStatisticsFacadeService adminStatisticsFacadeService; + private final AdminItemFacadeService adminItemFacadeService; + + /** + * 특정 연도, 월의 동전 줍기 횟수를 횟수 별로 통계를 내서 반환 + * + * @param year + * @param month 조회를 원하는 기간 + * @return + */ + @GetMapping("/coins/collect") + @AuthGuard(level = AuthLevel.ADMIN_ONLY) + public CoinCollectStatisticsDto getCoinCollectCountByMonth( + @RequestParam("year") Integer year, + @RequestParam("month") Integer month) { + return adminStatisticsFacadeService.getCoinCollectCountByMonth(year, month); + } + + /** + * 전체 기간동안 동전의 발행량 및 사용량 반환 + * + * @return + */ + @GetMapping("/coins") + @AuthGuard(level = AuthLevel.ADMIN_ONLY) + public TotalCoinAmountDto getTotalCoinAmount() { + return adminStatisticsFacadeService.getTotalCoinAmount(); + } + + /** + * 아이템별 구매 인원 조회 + * + * @return + */ + @GetMapping("/items") + @AuthGuard(level = AuthLevel.ADMIN_ONLY) + public ItemStatisticsDto getItemPurchaseStatistics() { + return adminItemFacadeService.getItemPurchaseStatistics(); + } + + /** + * 특정 기간동안 재화 사용량 및 발행량 조회 + * + * @param startDate + * @param endDate 조회를 원하는 기간 + * @return + */ + @GetMapping("/coins/use") + @AuthGuard(level = AuthLevel.ADMIN_ONLY) + public CoinStaticsDto getCoinStaticsDto( + @RequestParam("startDate") @DateTimeFormat(iso = ISO.DATE_TIME) LocalDateTime startDate, + @RequestParam("endDate") @DateTimeFormat(iso = ISO.DATE_TIME) LocalDateTime endDate) { + return adminStatisticsFacadeService.getCoinStaticsDto(startDate.toLocalDate(), + endDate.toLocalDate()); + + } + +} diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/statistics/service/AdminStatisticsFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/admin/statistics/service/AdminStatisticsFacadeService.java index 4add56569..f3fc592e8 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/statistics/service/AdminStatisticsFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/statistics/service/AdminStatisticsFacadeService.java @@ -1,28 +1,46 @@ package org.ftclub.cabinet.admin.statistics.service; +import static java.util.stream.Collectors.groupingBy; import static org.ftclub.cabinet.cabinet.domain.CabinetStatus.AVAILABLE; import static org.ftclub.cabinet.cabinet.domain.CabinetStatus.BROKEN; import static org.ftclub.cabinet.cabinet.domain.CabinetStatus.FULL; import static org.ftclub.cabinet.cabinet.domain.CabinetStatus.OVERDUE; +import static org.ftclub.cabinet.item.domain.Sku.COIN_COLLECT; +import java.time.LocalDate; import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.Comparator; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; +import java.util.stream.IntStream; import lombok.RequiredArgsConstructor; import org.ftclub.cabinet.cabinet.service.CabinetQueryService; import org.ftclub.cabinet.dto.BlockedUserPaginationDto; import org.ftclub.cabinet.dto.CabinetFloorStatisticsResponseDto; +import org.ftclub.cabinet.dto.CoinAmountDto; +import org.ftclub.cabinet.dto.CoinCollectStatisticsDto; +import org.ftclub.cabinet.dto.CoinCollectedCountDto; +import org.ftclub.cabinet.dto.CoinStaticsDto; import org.ftclub.cabinet.dto.LentsStatisticsResponseDto; import org.ftclub.cabinet.dto.OverdueUserCabinetDto; import org.ftclub.cabinet.dto.OverdueUserCabinetPaginationDto; +import org.ftclub.cabinet.dto.TotalCoinAmountDto; import org.ftclub.cabinet.dto.UserBlockedInfoDto; import org.ftclub.cabinet.exception.ExceptionStatus; import org.ftclub.cabinet.exception.ServiceException; +import org.ftclub.cabinet.item.domain.ItemHistory; +import org.ftclub.cabinet.item.service.ItemHistoryQueryService; +import org.ftclub.cabinet.item.service.ItemQueryService; +import org.ftclub.cabinet.item.service.ItemRedisService; import org.ftclub.cabinet.lent.domain.LentHistory; import org.ftclub.cabinet.lent.service.LentQueryService; import org.ftclub.cabinet.log.LogLevel; import org.ftclub.cabinet.log.Logging; import org.ftclub.cabinet.mapper.CabinetMapper; +import org.ftclub.cabinet.mapper.ItemMapper; import org.ftclub.cabinet.mapper.UserMapper; import org.ftclub.cabinet.user.domain.BanHistory; import org.ftclub.cabinet.user.service.BanHistoryQueryService; @@ -48,6 +66,10 @@ public class AdminStatisticsFacadeService { private final CabinetMapper cabinetMapper; private final UserMapper userMapper; + private final ItemHistoryQueryService itemHistoryQueryService; + private final ItemQueryService itemQueryService; + private final ItemMapper itemMapper; + private final ItemRedisService itemRedisService; /** * 현재 가용중인 모든 사물함의 현황을 반환합니다. @@ -114,4 +136,88 @@ public OverdueUserCabinetPaginationDto getOverdueUsers(Pageable pageable) { ).collect(Collectors.toList()); return cabinetMapper.toOverdueUserCabinetPaginationDto(result, (long) lentHistories.size()); } + + /** + * 특정 연도, 월의 동전 줍기 횟수를 횟수 별로 통계를 내서 반환 + * + * @param year 조회를 원하는 기간 + * @param month 조회를 원하는 기간 + * @return 동전 줍기 횟수 통계 + */ + public CoinCollectStatisticsDto getCoinCollectCountByMonth(Integer year, Integer month) { + Long itemId = itemQueryService.getBySku(COIN_COLLECT).getId(); + List coinCollectedInfoByMonth = + itemHistoryQueryService.findCoinCollectedInfoByMonth(itemId, year, month); + Map coinCollectCountByUser = coinCollectedInfoByMonth.stream() + .collect(groupingBy(ItemHistory::getUserId, Collectors.counting())); + + int[] coinCollectArray = new int[31]; + coinCollectCountByUser.forEach((userId, coinCount) -> + coinCollectArray[coinCount.intValue() - 1]++); + + List coinCollectedCountDto = IntStream.rangeClosed(0, + 30) // 1부터 30까지의 범위로 스트림 생성 + .mapToObj(i -> new CoinCollectedCountDto(i + 1, + coinCollectArray[i])) // 각 인덱스와 해당하는 배열 값으로 CoinCollectedCountDto 생성 + .collect(Collectors.toList()); // 리스트로 변환하여 반환 + + return new CoinCollectStatisticsDto(coinCollectedCountDto); + } + + /** + * 전체 기간동안 동전의 발행량 및 사용량 반환 + * + * @return 동전의 발행량 및 사용량 + */ + public TotalCoinAmountDto getTotalCoinAmount() { + long totalCoinSupply = itemRedisService.getTotalCoinSupply(); + long totalCoinUsage = itemRedisService.getTotalCoinUsage(); + + // 재화 총 사용량, 현재 총 보유량 (총 공급량 - 총 사용량) 반환 + return new TotalCoinAmountDto(totalCoinUsage, totalCoinSupply + totalCoinUsage); + } + + /** + * 특정 기간동안 재화 사용량 및 발행량 조회 + * + * @param startDate 조회를 원하는 기간 + * @param endDate 조회를 원하는 기간 + * @return 재화 사용량 및 발행량 + */ + public CoinStaticsDto getCoinStaticsDto(LocalDate startDate, LocalDate endDate) { + Map issuedAmount = new LinkedHashMap<>(); + Map usedAmount = new LinkedHashMap<>(); + int dayDifference = (int) ChronoUnit.DAYS.between(startDate, endDate) + 1; + IntStream.range(0, dayDifference) + .mapToObj(startDate::plusDays) + .forEach(date -> { + issuedAmount.put(date, 0L); + usedAmount.put(date, 0L); + }); + + List usedCoins = + itemHistoryQueryService.findUsedCoinHistoryBetween(startDate, endDate); + + usedCoins.forEach(ih -> { + Long price = ih.getItem().getPrice(); + LocalDate date = ih.getPurchaseAt().toLocalDate(); + + if (price > 0) { + issuedAmount.put(date, issuedAmount.get(date) + price); + } else { + usedAmount.put(date, usedAmount.get(date) - price); + } + }); + List issueCoin = convertMapToList(issuedAmount); + List usedCoin = convertMapToList(usedAmount); + + return new CoinStaticsDto(issueCoin, usedCoin); + } + + List convertMapToList(Map map) { + return map.entrySet().stream() + .map(entry -> itemMapper.toCoinAmountDto(entry.getKey(), entry.getValue())) + .sorted(Comparator.comparing(CoinAmountDto::getDate)) + .collect(Collectors.toList()); + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/config/AlarmProperties.java b/backend/src/main/java/org/ftclub/cabinet/alarm/config/AlarmProperties.java index 4dd3ae0a1..556f30571 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/config/AlarmProperties.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/config/AlarmProperties.java @@ -95,6 +95,19 @@ public class AlarmProperties { @Value("${cabinet.alarm.slack.announcement.template}") private String announcementSlackTemplate; + /*==================== section alarm ========================*/ + @Value("${cabinet.alarm.mail.sectionAlarm.subject}") + private String sectionAlarmSubject; + + @Value("${cabinet.alarm.mail.sectionAlarm.template}") + private String sectionAlarmMailTemplateUrl; + + @Value("${cabinet.alarm.fcm.sectionAlarm.template}") + private String sectionAlarmFcmTemplate; + + @Value("${cabinet.alarm.slack.sectionAlarm.template}") + private String sectionAlarmSlackTemplate; + /*======================== term =============================*/ @Value("${cabinet.alarm.overdue-term.week-before}") private Long overdueTermWeekBefore; diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/AlarmEvent.java b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/AlarmEvent.java index 4e7d97587..a0100f4e1 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/AlarmEvent.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/AlarmEvent.java @@ -6,6 +6,7 @@ @Getter @ToString public class AlarmEvent { + private final Long receiverId; private final Alarm alarm; @@ -14,7 +15,7 @@ private AlarmEvent(Long receiverId, Alarm alarm) { this.alarm = alarm; } - public static AlarmEvent of(Long id, Alarm alarm) { - return new AlarmEvent(id, alarm); + public static AlarmEvent of(Long receiverId, Alarm alarm) { + return new AlarmEvent(receiverId, alarm); } } diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/AlarmItem.java b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/AlarmItem.java new file mode 100644 index 000000000..e0d73e1dc --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/AlarmItem.java @@ -0,0 +1,14 @@ +package org.ftclub.cabinet.alarm.domain; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.ToString; + +@Getter +@ToString +@RequiredArgsConstructor +public class AlarmItem implements ItemUsage { + + private final Long userId; + private final Long cabinetPlaceId; +} diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/AvailableSectionAlarm.java b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/AvailableSectionAlarm.java new file mode 100644 index 000000000..42e9818fc --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/AvailableSectionAlarm.java @@ -0,0 +1,14 @@ +package org.ftclub.cabinet.alarm.domain; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.ToString; +import org.ftclub.cabinet.cabinet.domain.Location; + +@Getter +@ToString +@AllArgsConstructor +public class AvailableSectionAlarm implements Alarm { + + private final Location location; +} diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/ExtensionItem.java b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/ExtensionItem.java new file mode 100644 index 000000000..76787c888 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/ExtensionItem.java @@ -0,0 +1,13 @@ +package org.ftclub.cabinet.alarm.domain; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public class ExtensionItem implements ItemUsage { + + private final Long userId; + private final Integer days; + +} diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/ItemUsage.java b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/ItemUsage.java new file mode 100644 index 000000000..7aa8d74c2 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/ItemUsage.java @@ -0,0 +1,5 @@ +package org.ftclub.cabinet.alarm.domain; + +public interface ItemUsage { + +} diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/PenaltyItem.java b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/PenaltyItem.java new file mode 100644 index 000000000..32782c51f --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/PenaltyItem.java @@ -0,0 +1,12 @@ +package org.ftclub.cabinet.alarm.domain; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public class PenaltyItem implements ItemUsage { + + private final Long userId; + private final Integer days; +} diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/SwapItem.java b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/SwapItem.java new file mode 100644 index 000000000..621ab18a5 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/SwapItem.java @@ -0,0 +1,13 @@ +package org.ftclub.cabinet.alarm.domain; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public class SwapItem implements ItemUsage { + + private final Long userId; + private final Long newCabinetId; + +} diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/handler/AlarmEventHandler.java b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/AlarmEventListener.java similarity index 98% rename from backend/src/main/java/org/ftclub/cabinet/alarm/handler/AlarmEventHandler.java rename to backend/src/main/java/org/ftclub/cabinet/alarm/handler/AlarmEventListener.java index 135d5c956..77e8cd3e7 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/handler/AlarmEventHandler.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/AlarmEventListener.java @@ -14,7 +14,7 @@ @Component @RequiredArgsConstructor @Log4j2 -public class AlarmEventHandler { +public class AlarmEventListener { private final UserQueryService userQueryService; private final SlackAlarmSender slackAlarmSender; diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/handler/EmailAlarmSender.java b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/EmailAlarmSender.java index c7a2cb890..d8006b6bc 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/handler/EmailAlarmSender.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/EmailAlarmSender.java @@ -9,6 +9,7 @@ import org.ftclub.cabinet.alarm.domain.Alarm; import org.ftclub.cabinet.alarm.domain.AlarmEvent; import org.ftclub.cabinet.alarm.domain.AnnouncementAlarm; +import org.ftclub.cabinet.alarm.domain.AvailableSectionAlarm; import org.ftclub.cabinet.alarm.domain.ExtensionExpirationImminentAlarm; import org.ftclub.cabinet.alarm.domain.ExtensionIssuanceAlarm; import org.ftclub.cabinet.alarm.domain.LentExpirationAlarm; @@ -38,10 +39,6 @@ public class EmailAlarmSender { @Async public void send(User user, AlarmEvent alarmEvent) { log.info("Email Alarm Event : user = {}, alarmEvent = {}", user, alarmEvent); - if (!gmailProperties.getIsProduction()) { - log.debug("개발 환경이므로 메일을 보내지 않습니다."); - return; - } MailDto mailDto = parseMessageToMailDto(user.getName(), alarmEvent.getAlarm()); try { @@ -68,6 +65,8 @@ private MailDto parseMessageToMailDto(String name, Alarm alarm) { (ExtensionExpirationImminentAlarm) alarm, context); } else if (alarm instanceof AnnouncementAlarm) { return generateAnnouncementAlarm((AnnouncementAlarm) alarm, context); + } else if (alarm instanceof AvailableSectionAlarm) { + return generateSectionAlarm((AvailableSectionAlarm) alarm, context); } else { throw ExceptionStatus.NOT_FOUND_ALARM.asServiceException(); } @@ -125,6 +124,16 @@ private MailDto generateLentSuccessAlarm(LentSuccessAlarm alarm, Context context alarmProperties.getLentSuccessMailTemplateUrl(), context); } + @NotNull + private MailDto generateSectionAlarm(AvailableSectionAlarm alarm, Context context) { + String building = alarm.getLocation().getBuilding(); + Integer floor = alarm.getLocation().getFloor(); + String section = alarm.getLocation().getSection(); + context.setVariable("location", building + " " + floor + "층 " + section + "구역"); + return new MailDto(alarmProperties.getSectionAlarmSubject(), + alarmProperties.getSectionAlarmMailTemplateUrl(), context); + } + private void sendMessage(String email, MailDto mailDto) throws MessagingException { log.info("send Message : email = {}, mailDto = {}", email, mailDto); MimeMessage message = javaMailSender.createMimeMessage(); diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/handler/ItemUsageHandler.java b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/ItemUsageHandler.java new file mode 100644 index 000000000..c00c4d387 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/ItemUsageHandler.java @@ -0,0 +1,43 @@ +package org.ftclub.cabinet.alarm.handler; + +import lombok.RequiredArgsConstructor; +import org.ftclub.cabinet.alarm.domain.AlarmItem; +import org.ftclub.cabinet.alarm.domain.ExtensionItem; +import org.ftclub.cabinet.alarm.domain.ItemUsage; +import org.ftclub.cabinet.alarm.domain.PenaltyItem; +import org.ftclub.cabinet.alarm.domain.SwapItem; +import org.ftclub.cabinet.exception.ExceptionStatus; +import org.ftclub.cabinet.item.service.ItemFacadeService; +import org.ftclub.cabinet.lent.service.LentFacadeService; +import org.ftclub.cabinet.user.service.UserFacadeService; +import org.springframework.stereotype.Component; +import org.springframework.transaction.event.TransactionPhase; +import org.springframework.transaction.event.TransactionalEventListener; + +@Component +@RequiredArgsConstructor +public class ItemUsageHandler { + + private final LentFacadeService lentFacadeService; + private final UserFacadeService userFacadeService; + private final ItemFacadeService itemFacadeService; + + @TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT) + public void handleItemUsage(ItemUsage itemUsage) { + if (itemUsage instanceof SwapItem) { + SwapItem swapItem = (SwapItem) itemUsage; + lentFacadeService.swapPrivateCabinet(swapItem.getUserId(), swapItem.getNewCabinetId()); + } else if (itemUsage instanceof PenaltyItem) { + PenaltyItem penaltyItem = (PenaltyItem) itemUsage; + userFacadeService.reduceBanDays(penaltyItem.getUserId(), penaltyItem.getDays()); + } else if (itemUsage instanceof ExtensionItem) { + ExtensionItem extensionItem = (ExtensionItem) itemUsage; + lentFacadeService.plusExtensionDays(extensionItem.getUserId(), extensionItem.getDays()); + } else if (itemUsage instanceof AlarmItem) { + AlarmItem alarmItem = (AlarmItem) itemUsage; + itemFacadeService.addSectionAlarm(alarmItem.getUserId(), alarmItem.getCabinetPlaceId()); + } else { + throw ExceptionStatus.NOT_FOUND_ITEM.asServiceException(); + } + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/handler/PushAlarmSender.java b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/PushAlarmSender.java index de642539f..635425d49 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/handler/PushAlarmSender.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/PushAlarmSender.java @@ -10,6 +10,7 @@ import org.ftclub.cabinet.alarm.domain.Alarm; import org.ftclub.cabinet.alarm.domain.AlarmEvent; import org.ftclub.cabinet.alarm.domain.AnnouncementAlarm; +import org.ftclub.cabinet.alarm.domain.AvailableSectionAlarm; import org.ftclub.cabinet.alarm.domain.ExtensionExpirationImminentAlarm; import org.ftclub.cabinet.alarm.domain.ExtensionIssuanceAlarm; import org.ftclub.cabinet.alarm.domain.LentExpirationAlarm; @@ -60,6 +61,8 @@ private FCMDto parseMessage(Alarm alarm) { (ExtensionExpirationImminentAlarm) alarm); } else if (alarm instanceof AnnouncementAlarm) { return generateAnnouncementAlarm(); + } else if (alarm instanceof AvailableSectionAlarm) { + return generateAvailableSectionAlarm((AvailableSectionAlarm) alarm); } else { throw ExceptionStatus.NOT_FOUND_ALARM.asServiceException(); } @@ -122,6 +125,16 @@ private FCMDto generateLentSuccessAlarm(LentSuccessAlarm alarm) { return new FCMDto(title, body); } + private FCMDto generateAvailableSectionAlarm(AvailableSectionAlarm alarm) { + String building = alarm.getLocation().getBuilding(); + Integer floor = alarm.getLocation().getFloor(); + String section = alarm.getLocation().getSection(); + String title = alarmProperties.getSectionAlarmSubject(); + String body = String.format(alarmProperties.getSectionAlarmSlackTemplate(), + building + " " + floor + "층 " + section + "구역"); + return new FCMDto(title, body); + } + private void sendMessage(String token, FCMDto fcmDto) { log.info("send Message : token = {}, fcmDto = {}", token, fcmDto); Message message = Message.builder() diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/handler/SectionAlarmManager.java b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/SectionAlarmManager.java new file mode 100644 index 000000000..fa43002ca --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/SectionAlarmManager.java @@ -0,0 +1,71 @@ +package org.ftclub.cabinet.alarm.handler; + +import static java.util.stream.Collectors.groupingBy; +import static java.util.stream.Collectors.mapping; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import org.ftclub.cabinet.alarm.domain.AlarmEvent; +import org.ftclub.cabinet.alarm.domain.AvailableSectionAlarm; +import org.ftclub.cabinet.cabinet.domain.Cabinet; +import org.ftclub.cabinet.cabinet.domain.CabinetStatus; +import org.ftclub.cabinet.cabinet.domain.Location; +import org.ftclub.cabinet.cabinet.service.CabinetQueryService; +import org.ftclub.cabinet.item.service.SectionAlarmCommandService; +import org.ftclub.cabinet.item.service.SectionAlarmQueryService; +import org.ftclub.cabinet.lent.service.LentRedisService; +import org.ftclub.cabinet.log.LogLevel; +import org.ftclub.cabinet.log.Logging; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +@Component +@RequiredArgsConstructor +@Logging(level = LogLevel.DEBUG) +public class SectionAlarmManager { + + private final SectionAlarmQueryService sectionAlarmQueryService; + private final SectionAlarmCommandService sectionAlarmCommandService; + private final CabinetQueryService cabinetQueryService; + private final LentRedisService lentRedisService; + private final ApplicationEventPublisher eventPublisher; + + + @Transactional + public void sendSectionAlarm() { + LocalDateTime now = LocalDateTime.now(); + LocalDateTime from = LocalDate.now().atStartOfDay(); + + // 오늘 열리는 모든 사물함 조회 + List allPendingCabinets = + cabinetQueryService.findAllPendingCabinets(CabinetStatus.PENDING); + // 반납일이 오늘 이전인 사물함만 필터링 + Map> locationCabinetMap = allPendingCabinets.stream() + .filter(cabinet -> + lentRedisService.getPreviousEndedAt(cabinet.getId()).isBefore(from)) + .collect(groupingBy(cabinet -> cabinet.getCabinetPlace().getLocation(), + mapping(cabinet -> cabinet, Collectors.toList()))); + + // 사용되지 않은 알람권 조회 + List alarmIds = new ArrayList<>(); + sectionAlarmQueryService.getUnsentAlarms().forEach(alarm -> { + Location location = alarm.getCabinetPlace().getLocation(); + // 오늘 열리는 사물함인 경우에만 알람 이벤트 생성 + if (locationCabinetMap.containsKey(location)) { + alarmIds.add(alarm.getId()); + eventPublisher.publishEvent( + AlarmEvent.of(alarm.getUserId(), new AvailableSectionAlarm(location))); + } + }); + // 사용된 알림권 업데이트 + if (!alarmIds.isEmpty()) { + sectionAlarmCommandService.updateAlarmSend(alarmIds, now); + } + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/handler/SlackAlarmSender.java b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/SlackAlarmSender.java index 3d6a4177f..715f7a99c 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/handler/SlackAlarmSender.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/SlackAlarmSender.java @@ -7,6 +7,7 @@ import org.ftclub.cabinet.alarm.domain.Alarm; import org.ftclub.cabinet.alarm.domain.AlarmEvent; import org.ftclub.cabinet.alarm.domain.AnnouncementAlarm; +import org.ftclub.cabinet.alarm.domain.AvailableSectionAlarm; import org.ftclub.cabinet.alarm.domain.ExtensionExpirationImminentAlarm; import org.ftclub.cabinet.alarm.domain.ExtensionIssuanceAlarm; import org.ftclub.cabinet.alarm.domain.LentExpirationAlarm; @@ -58,6 +59,8 @@ private SlackDto parseMessage(Alarm alarm) { return generateExtensionExpirationImminent((ExtensionExpirationImminentAlarm) alarm); } else if (alarm instanceof AnnouncementAlarm) { return generateAnnouncementAlarm(); + } else if (alarm instanceof AvailableSectionAlarm) { + return generateAvailableSectionAlarm((AvailableSectionAlarm) alarm); } else { throw ExceptionStatus.NOT_FOUND_ALARM.asServiceException(); } @@ -113,5 +116,12 @@ private SlackDto generateAnnouncementAlarm() { return new SlackDto(body); } - + private SlackDto generateAvailableSectionAlarm(AvailableSectionAlarm alarm) { + String building = alarm.getLocation().getBuilding(); + Integer floor = alarm.getLocation().getFloor(); + String section = alarm.getLocation().getSection(); + String body = String.format(alarmProperties.getSectionAlarmSlackTemplate(), + building + " " + floor + "층 " + section + "구역"); + return new SlackDto(body); + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/controller/CabinetController.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/controller/CabinetController.java index 39059a418..0257bf866 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/controller/CabinetController.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/controller/CabinetController.java @@ -9,8 +9,10 @@ import org.ftclub.cabinet.dto.CabinetInfoResponseDto; import org.ftclub.cabinet.dto.CabinetPendingResponseDto; import org.ftclub.cabinet.dto.CabinetsPerSectionResponseDto; +import org.ftclub.cabinet.dto.UserSessionDto; import org.ftclub.cabinet.exception.ControllerException; import org.ftclub.cabinet.log.Logging; +import org.ftclub.cabinet.user.domain.UserSession; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; @@ -49,9 +51,11 @@ public List getBuildingFloorsResponse() { @GetMapping("/buildings/{building}/floors/{floor}") @AuthGuard(level = AuthLevel.USER_OR_ADMIN) public List getCabinetsPerSection( + @UserSession UserSessionDto user, @PathVariable("building") String building, @PathVariable("floor") Integer floor) { - return cabinetFacadeService.getCabinetsPerSection(building, floor); + return cabinetFacadeService.getCabinetsPerSection(building, floor, + (user == null ? null : user.getUserId())); } /** diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/domain/Location.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/domain/Location.java index 1783f1be6..3ef7d20e5 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/domain/Location.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/domain/Location.java @@ -1,5 +1,7 @@ package org.ftclub.cabinet.cabinet.domain; +import javax.persistence.Column; +import javax.persistence.Embeddable; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; @@ -8,9 +10,6 @@ import org.ftclub.cabinet.exception.ExceptionStatus; import org.ftclub.cabinet.utils.ExceptionUtil; -import javax.persistence.Column; -import javax.persistence.Embeddable; - /** * 건물, 층, 구역에 대한 정보입니다. */ @@ -36,11 +35,33 @@ protected Location(String building, Integer floor, String section) { public static Location of(String building, Integer floor, String section) { Location location = new Location(building, floor, section); - ExceptionUtil.throwIfFalse(location.isValid(), new DomainException(ExceptionStatus.INVALID_ARGUMENT)); + ExceptionUtil.throwIfFalse(location.isValid(), + new DomainException(ExceptionStatus.INVALID_ARGUMENT)); return location; } private boolean isValid() { return (this.building != null && this.floor > 0 && this.section != null); } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + Location location = (Location) obj; + return building.equals(location.building) && floor.equals(location.floor) + && section.equals(location.section); + } + + @Override + public int hashCode() { + int hash = 17 * 31 + building.hashCode(); + hash = 31 * hash + floor.hashCode(); + hash = 31 * hash + section.hashCode(); + return hash; + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetRepository.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetRepository.java index 8efe292b2..13cd5d0cd 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetRepository.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetRepository.java @@ -4,6 +4,7 @@ import java.util.Optional; import javax.persistence.LockModeType; import org.ftclub.cabinet.cabinet.domain.Cabinet; +import org.ftclub.cabinet.cabinet.domain.CabinetPlace; import org.ftclub.cabinet.cabinet.domain.CabinetStatus; import org.ftclub.cabinet.cabinet.domain.LentType; import org.springframework.data.domain.Page; @@ -210,4 +211,10 @@ void updateStatusAndTitleAndMemoByCabinetIdsIn(@Param("cabinetIds") List c List findAllByStatus(CabinetStatus cabinetStatus); + + @Query("SELECT p " + + "FROM CabinetPlace p " + + "WHERE p.location.building = :building AND p.location.floor = :floor AND p.location.section = :section ") + Optional findCabinetPlaceInfoByLocation(@Param("building") String building, + @Param("floor") Integer floor, @Param("section") String section); } diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeService.java index 0f6e3b461..f4c38f8b8 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeService.java @@ -6,6 +6,7 @@ import static org.ftclub.cabinet.cabinet.domain.CabinetStatus.AVAILABLE; import static org.ftclub.cabinet.cabinet.domain.CabinetStatus.PENDING; +import java.time.LocalDate; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Collections; @@ -13,15 +14,14 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import org.ftclub.cabinet.cabinet.domain.Cabinet; import org.ftclub.cabinet.cabinet.domain.CabinetStatus; import org.ftclub.cabinet.cabinet.domain.Grid; import org.ftclub.cabinet.cabinet.domain.LentType; -import org.ftclub.cabinet.club.domain.Club; import org.ftclub.cabinet.club.domain.ClubLentHistory; -import org.ftclub.cabinet.club.service.ClubQueryService; import org.ftclub.cabinet.dto.ActiveCabinetInfoEntities; import org.ftclub.cabinet.dto.BuildingFloorsDto; import org.ftclub.cabinet.dto.CabinetDto; @@ -34,6 +34,7 @@ import org.ftclub.cabinet.dto.LentHistoryDto; import org.ftclub.cabinet.dto.LentHistoryPaginationDto; import org.ftclub.cabinet.exception.ExceptionStatus; +import org.ftclub.cabinet.item.service.SectionAlarmQueryService; import org.ftclub.cabinet.lent.domain.LentHistory; import org.ftclub.cabinet.lent.service.ClubLentQueryService; import org.ftclub.cabinet.lent.service.LentQueryService; @@ -59,8 +60,8 @@ public class CabinetFacadeService { private final LentQueryService lentQueryService; private final LentRedisService lentRedisService; private final UserQueryService userQueryService; - private final ClubQueryService clubQueryService; private final ClubLentQueryService clubLentQueryService; + private final SectionAlarmQueryService sectionAlarmQueryService; private final CabinetMapper cabinetMapper; private final LentMapper lentMapper; @@ -75,8 +76,7 @@ public List getBuildingFloorsResponse() { List allBuildings = cabinetQueryService.findAllBuildings(); return allBuildings.stream() .map(building -> cabinetMapper.toBuildingFloorsDto(building, - cabinetQueryService.findAllFloorsByBuilding(building)) - ) + cabinetQueryService.findAllFloorsByBuilding(building))) .collect(Collectors.toList()); } @@ -99,8 +99,8 @@ public CabinetInfoResponseDto getCabinetInfo(Long cabinetId) { LocalDateTime sessionExpiredAt = lentRedisService.getSessionExpired(cabinetId); return cabinetMapper.toCabinetInfoResponseDto(cabinet, lentDtos, sessionExpiredAt); } - List cabinetActiveLentHistories = lentQueryService.findCabinetActiveLentHistories( - cabinetId); + List cabinetActiveLentHistories = + lentQueryService.findCabinetActiveLentHistories(cabinetId); List lentDtos = cabinetActiveLentHistories.stream() .map(lentHistory -> lentMapper.toLentDto(lentHistory.getUser(), lentHistory)) .collect(Collectors.toList()); @@ -109,8 +109,7 @@ public CabinetInfoResponseDto getCabinetInfo(Long cabinetId) { List usersInCabinet = lentRedisService.findUsersInCabinet(cabinetId); List users = userQueryService.findUsers(usersInCabinet); users.forEach(user -> lentDtos.add( - LentDto.builder().userId(user.getId()).name(user.getName()).build() - )); + LentDto.builder().userId(user.getId()).name(user.getName()).build())); } LocalDateTime sessionExpiredAt = lentRedisService.getSessionExpired(cabinetId); @@ -127,29 +126,43 @@ public CabinetInfoResponseDto getCabinetInfo(Long cabinetId) { */ @Transactional(readOnly = true) public List getCabinetsPerSection(String building, - Integer floor) { + Integer floor, Long userId) { + // 건물, 층에 있는 현재 대여 중인 사물함 정보, 해당 사물함에 대여 중인 유저 정보, 해당 대여 기록 조회 List activeCabinetInfos = cabinetQueryService.findActiveCabinetInfoEntities(building, floor); + // 조회한 사물함, 유저, 대여기록 리스트를 사물함 기준으로 대여기록 그룹화(Map) Map> cabinetLentHistories = activeCabinetInfos.stream(). collect(groupingBy(ActiveCabinetInfoEntities::getCabinet, mapping(ActiveCabinetInfoEntities::getLentHistory, Collectors.toList()))); + // 건물, 층에 있는 모든 사물함 정보 조회 List allCabinetsOnSection = cabinetQueryService.findAllCabinetsByBuildingAndFloor(building, floor); + // 동아리 사물함 대여 정보들을 조회하여 사물함 ID를 기준으로 그룹화(Map) Map> clubLentMap = clubLentQueryService.findAllActiveLentHistoriesWithClub().stream() .collect(groupingBy(ClubLentHistory::getCabinetId)); + // 층, 건물에 따른 유저가 알람 등록한 section 조회 + Set unsetAlarmSection = + sectionAlarmQueryService.getUnsentAlarm(userId, building, floor).stream() + .map(alarm -> alarm.getCabinetPlace().getLocation().getSection()) + .collect(Collectors.toSet()); + + // 층, 건물에 있는 사물함을 순회하며, visibleNum으로 정렬하고, 섹션별로 사물함 정보를 그룹화 Map> cabinetPreviewsBySection = new LinkedHashMap<>(); allCabinetsOnSection.stream() .sorted(Comparator.comparing(Cabinet::getVisibleNum)) .forEach(cabinet -> { String section = cabinet.getCabinetPlace().getLocation().getSection(); + // 동아리 사물함이라면, if (cabinet.getLentType().equals(LentType.CLUB)) { + // 동아리 사물함이 대여 중인 아닌 경우 빈 이름으로 Dto 생성, if (!clubLentMap.containsKey(cabinet.getId())) { cabinetPreviewsBySection.computeIfAbsent(section, k -> new ArrayList<>()) .add(cabinetMapper.toCabinetPreviewDto(cabinet, 0, null)); } else { + // 대여 중인 경우 대여기록을 가져와서 사물함 title을 가져와 Dto 생성 clubLentMap.get(cabinet.getId()).stream() .map(c -> c.getClub().getName()) .findFirst().ifPresent(clubName -> cabinetPreviewsBySection @@ -158,18 +171,22 @@ public List getCabinetsPerSection(String building } return; } + // 사물함 대여기록을 조회 및 사물함 제목을 가져옴 List lentHistories = cabinetLentHistories.getOrDefault(cabinet, Collections.emptyList()); String title = getCabinetTitle(cabinet, lentHistories); + // 사물함과 대여기록, title로 Dto 생성 cabinetPreviewsBySection.computeIfAbsent(section, k -> new ArrayList<>()) .add(cabinetMapper.toCabinetPreviewDto(cabinet, lentHistories.size(), title)); }); - + // 생성한 Dto를 섹션별로 묶어서 반환 return cabinetPreviewsBySection.entrySet().stream() - .map(entry -> cabinetMapper.toCabinetsPerSectionResponseDto(entry.getKey(), - entry.getValue())) - .collect(Collectors.toList()); + .map(entry -> { + String section = entry.getKey(); + return cabinetMapper.toCabinetsPerSectionResponseDto(section, entry.getValue(), + unsetAlarmSection.contains(section)); + }).collect(Collectors.toList()); } /** @@ -196,30 +213,41 @@ private String getCabinetTitle(Cabinet cabinet, List lentHistories) */ @Transactional(readOnly = true) public CabinetPendingResponseDto getAvailableCabinets(String building) { + // 현재 시간 및 어제 13시 00분 00초 시간 설정 final LocalDateTime now = LocalDateTime.now(); - final LocalDateTime yesterday = now.minusDays(1).withHour(13).withMinute(0).withSecond(0); + final LocalDate yesterday = now.minusDays(1).toLocalDate(); + + // 빌딩에 있는 CLUB 대여 타입이 아니면서, AVAILABLE, PENDING 상태인 사물함 조회 List availableCabinets = cabinetQueryService.findCabinetsNotLentTypeAndStatus( building, LentType.CLUB, List.of(AVAILABLE, PENDING)); + // 그 중 PENDING 상태인 사물함들만 ID를 리스트로 변환 List cabinetIds = availableCabinets.stream() .filter(cabinet -> cabinet.isStatus(PENDING)) .map(Cabinet::getId).collect(Collectors.toList()); + Map> lentHistoriesMap; + // 현재 시간이 13시 이전이면 어제 반납된 사물함을 조회 if (now.getHour() < 13) { - lentHistoriesMap = lentQueryService.findPendingLentHistoriesOnDate( - yesterday.toLocalDate(), cabinetIds) - .stream().collect(groupingBy(LentHistory::getCabinetId)); + lentHistoriesMap = + lentQueryService.findAvailableLentHistoriesOnDate(yesterday, cabinetIds) + .stream().collect(groupingBy(LentHistory::getCabinetId)); } else { + // 13시 이후면 현재 PENDING 인 사물함들을 조회 lentHistoriesMap = lentQueryService.findCabinetLentHistories(cabinetIds) .stream().collect(groupingBy(LentHistory::getCabinetId)); } + + // 층별로 사물함 정보를 그룹화 Map> cabinetFloorMap = cabinetQueryService.findAllFloorsByBuilding(building).stream() .collect(toMap(key -> key, value -> new ArrayList<>())); availableCabinets.forEach(cabinet -> { Integer floor = cabinet.getCabinetPlace().getLocation().getFloor(); + // AVAILABLE 상태인 사물함은 바로 추가 if (cabinet.isStatus(AVAILABLE)) { cabinetFloorMap.get(floor).add(cabinetMapper.toCabinetPreviewDto(cabinet, 0, null)); } + // PENDING 상태인 사물함이면서 오픈 예정으로 보여주어야 하는 사물함 추가 if (cabinet.isStatus(PENDING) && lentHistoriesMap.containsKey(cabinet.getId())) { lentHistoriesMap.get(cabinet.getId()).stream() .map(LentHistory::getEndedAt) @@ -256,10 +284,9 @@ public CabinetPaginationDto getCabinetPaginationByStatus(CabinetStatus status, */ @Transactional(readOnly = true) public LentHistoryPaginationDto getLentHistoryPagination(Long cabinetId, Pageable pageable) { - Page lentHistories = lentQueryService.findCabinetLentHistoriesWithUserAndCabinet( - cabinetId, pageable); + Page lentHistories = + lentQueryService.findCabinetLentHistoriesWithUserAndCabinet(cabinetId, pageable); List result = lentHistories.stream() - .sorted(Comparator.comparing(LentHistory::getStartedAt).reversed()) .map(lh -> lentMapper.toLentHistoryDto(lh, lh.getUser(), lh.getCabinet())) .collect(Collectors.toList()); return lentMapper.toLentHistoryPaginationDto(result, lentHistories.getTotalElements()); @@ -340,19 +367,6 @@ public void updateCabinetBundleStatus(List cabinetIds, CabinetStatus statu } } - /** - * [ADMIN] 사물함에 동아리 유저를 대여 시킵니다. {inheritDoc} - * - * @param clubId 대여할 유저 ID - * @param cabinetId 대여할 사물함 ID - * @param statusNote 상태 메모 - */ - @Transactional - public void updateClub(Long clubId, Long cabinetId, String statusNote) { - Cabinet cabinet = cabinetQueryService.getUserActiveCabinetForUpdate(cabinetId); - Club club = clubQueryService.getClub(clubId); - } - /** * [ADMIN] 사물함id 로 사물함을 찾아서, 상태를 변경시킵니다 * diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetQueryService.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetQueryService.java index 875b62fb0..122ecdf07 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetQueryService.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetQueryService.java @@ -4,6 +4,7 @@ import java.util.Optional; import lombok.RequiredArgsConstructor; import org.ftclub.cabinet.cabinet.domain.Cabinet; +import org.ftclub.cabinet.cabinet.domain.CabinetPlace; import org.ftclub.cabinet.cabinet.domain.CabinetStatus; import org.ftclub.cabinet.cabinet.domain.LentType; import org.ftclub.cabinet.cabinet.repository.CabinetRepository; @@ -207,4 +208,21 @@ public Page findAllByStatus(CabinetStatus status, Pageable pageable) { public List findAllPendingCabinets(CabinetStatus cabinetStatus) { return cabinetRepository.findAllByStatus(cabinetStatus); } + + + /** + * 사물함 위치 정보로 CabinetPlaceId를 가져옵니다. + * + * @param building + * @param floor + * @param section + * @return + */ + public CabinetPlace getCabinetPlaceInfoByLocation(String building, Integer floor, + String section) { + return cabinetRepository.findCabinetPlaceInfoByLocation(building, floor, section) + .orElseThrow(ExceptionStatus.NOT_FOUND_SECTION::asServiceException); + + } + } diff --git a/backend/src/main/java/org/ftclub/cabinet/club/domain/ClubRegistration.java b/backend/src/main/java/org/ftclub/cabinet/club/domain/ClubRegistration.java index 333800b7a..291435f8a 100644 --- a/backend/src/main/java/org/ftclub/cabinet/club/domain/ClubRegistration.java +++ b/backend/src/main/java/org/ftclub/cabinet/club/domain/ClubRegistration.java @@ -20,7 +20,6 @@ import org.ftclub.cabinet.exception.DomainException; import org.ftclub.cabinet.exception.ExceptionStatus; import org.ftclub.cabinet.user.domain.User; -import org.ftclub.cabinet.user.domain.UserRole; import org.ftclub.cabinet.utils.ExceptionUtil; import org.springframework.data.annotation.CreatedDate; import org.springframework.data.jpa.domain.support.AuditingEntityListener; diff --git a/backend/src/main/java/org/ftclub/cabinet/user/domain/UserRole.java b/backend/src/main/java/org/ftclub/cabinet/club/domain/UserRole.java similarity index 76% rename from backend/src/main/java/org/ftclub/cabinet/user/domain/UserRole.java rename to backend/src/main/java/org/ftclub/cabinet/club/domain/UserRole.java index f377b3669..b05c1c902 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/domain/UserRole.java +++ b/backend/src/main/java/org/ftclub/cabinet/club/domain/UserRole.java @@ -1,4 +1,4 @@ -package org.ftclub.cabinet.user.domain; +package org.ftclub.cabinet.club.domain; public enum UserRole { diff --git a/backend/src/main/java/org/ftclub/cabinet/club/repository/ClubRegistrationRepoitory.java b/backend/src/main/java/org/ftclub/cabinet/club/repository/ClubRegistrationRepoitory.java index 695a083ab..91a997e91 100644 --- a/backend/src/main/java/org/ftclub/cabinet/club/repository/ClubRegistrationRepoitory.java +++ b/backend/src/main/java/org/ftclub/cabinet/club/repository/ClubRegistrationRepoitory.java @@ -4,7 +4,7 @@ import java.util.List; import java.util.Optional; import org.ftclub.cabinet.club.domain.ClubRegistration; -import org.ftclub.cabinet.user.domain.UserRole; +import org.ftclub.cabinet.club.domain.UserRole; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; diff --git a/backend/src/main/java/org/ftclub/cabinet/club/service/ClubFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/club/service/ClubFacadeService.java index 9fc807736..317377294 100644 --- a/backend/src/main/java/org/ftclub/cabinet/club/service/ClubFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/club/service/ClubFacadeService.java @@ -1,6 +1,6 @@ package org.ftclub.cabinet.club.service; -import static org.ftclub.cabinet.user.domain.UserRole.CLUB_ADMIN; +import static org.ftclub.cabinet.club.domain.UserRole.CLUB_ADMIN; import java.util.List; import java.util.Map; @@ -11,6 +11,7 @@ import org.ftclub.cabinet.club.domain.Club; import org.ftclub.cabinet.club.domain.ClubLentHistory; import org.ftclub.cabinet.club.domain.ClubRegistration; +import org.ftclub.cabinet.club.domain.UserRole; import org.ftclub.cabinet.dto.ClubInfoDto; import org.ftclub.cabinet.dto.ClubInfoPaginationDto; import org.ftclub.cabinet.dto.ClubInfoResponseDto; @@ -21,7 +22,6 @@ import org.ftclub.cabinet.log.Logging; import org.ftclub.cabinet.mapper.ClubMapper; import org.ftclub.cabinet.user.domain.User; -import org.ftclub.cabinet.user.domain.UserRole; import org.ftclub.cabinet.user.service.UserQueryService; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; diff --git a/backend/src/main/java/org/ftclub/cabinet/club/service/ClubPolicyService.java b/backend/src/main/java/org/ftclub/cabinet/club/service/ClubPolicyService.java index 0b28c52a1..19e8760a9 100644 --- a/backend/src/main/java/org/ftclub/cabinet/club/service/ClubPolicyService.java +++ b/backend/src/main/java/org/ftclub/cabinet/club/service/ClubPolicyService.java @@ -2,10 +2,10 @@ import java.util.List; import lombok.RequiredArgsConstructor; +import org.ftclub.cabinet.club.domain.UserRole; import org.ftclub.cabinet.exception.ExceptionStatus; import org.ftclub.cabinet.log.LogLevel; import org.ftclub.cabinet.log.Logging; -import org.ftclub.cabinet.user.domain.UserRole; import org.springframework.stereotype.Service; @Service diff --git a/backend/src/main/java/org/ftclub/cabinet/club/service/ClubRegistrationCommandService.java b/backend/src/main/java/org/ftclub/cabinet/club/service/ClubRegistrationCommandService.java index e5c272555..57932098a 100644 --- a/backend/src/main/java/org/ftclub/cabinet/club/service/ClubRegistrationCommandService.java +++ b/backend/src/main/java/org/ftclub/cabinet/club/service/ClubRegistrationCommandService.java @@ -2,10 +2,10 @@ import lombok.RequiredArgsConstructor; import org.ftclub.cabinet.club.domain.ClubRegistration; +import org.ftclub.cabinet.club.domain.UserRole; import org.ftclub.cabinet.club.repository.ClubRegistrationRepoitory; import org.ftclub.cabinet.log.LogLevel; import org.ftclub.cabinet.log.Logging; -import org.ftclub.cabinet.user.domain.UserRole; import org.springframework.stereotype.Service; @Service diff --git a/backend/src/main/java/org/ftclub/cabinet/club/service/ClubRegistrationQueryService.java b/backend/src/main/java/org/ftclub/cabinet/club/service/ClubRegistrationQueryService.java index 3bc38ec8f..700843de0 100644 --- a/backend/src/main/java/org/ftclub/cabinet/club/service/ClubRegistrationQueryService.java +++ b/backend/src/main/java/org/ftclub/cabinet/club/service/ClubRegistrationQueryService.java @@ -1,7 +1,7 @@ package org.ftclub.cabinet.club.service; -import static org.ftclub.cabinet.user.domain.UserRole.CLUB_ADMIN; +import static org.ftclub.cabinet.club.domain.UserRole.CLUB_ADMIN; import java.util.List; import java.util.Optional; diff --git a/backend/src/main/java/org/ftclub/cabinet/config/ArgumentResolverConfig.java b/backend/src/main/java/org/ftclub/cabinet/config/ArgumentResolverConfig.java new file mode 100644 index 000000000..1060e1521 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/config/ArgumentResolverConfig.java @@ -0,0 +1,20 @@ +package org.ftclub.cabinet.config; + +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.ftclub.cabinet.user.domain.UserSessionArgumentResolver; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +@RequiredArgsConstructor +public class ArgumentResolverConfig implements WebMvcConfigurer { + + private final UserSessionArgumentResolver userSessionArgumentResolver; + + @Override + public void addArgumentResolvers(List resolvers) { + resolvers.add(userSessionArgumentResolver); + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/dto/ActiveCabinetInfoDto.java b/backend/src/main/java/org/ftclub/cabinet/dto/ActiveCabinetInfoDto.java index 6bf9a599b..e712e3a8a 100644 --- a/backend/src/main/java/org/ftclub/cabinet/dto/ActiveCabinetInfoDto.java +++ b/backend/src/main/java/org/ftclub/cabinet/dto/ActiveCabinetInfoDto.java @@ -1,5 +1,6 @@ package org.ftclub.cabinet.dto; +import java.time.LocalDateTime; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.ToString; @@ -7,9 +8,6 @@ import org.ftclub.cabinet.cabinet.domain.Grid; import org.ftclub.cabinet.cabinet.domain.LentType; import org.ftclub.cabinet.cabinet.domain.Location; -import org.ftclub.cabinet.user.domain.UserRole; - -import java.time.LocalDateTime; /** * 현재 대여 기록의 모든 연관관계를 담는 내부 계층 간 DTO @@ -18,6 +16,7 @@ @Getter @ToString public class ActiveCabinetInfoDto { + private final Long cabinetId; private final Long lentHistoryId; private final Long userId; @@ -25,7 +24,6 @@ public class ActiveCabinetInfoDto { private final String email; private final LocalDateTime blackholedAt; private final LocalDateTime deletedAt; - private final UserRole role; private final LocalDateTime startedAt; private final LocalDateTime expiredAt; private final LocalDateTime endedAt; diff --git a/backend/src/main/java/org/ftclub/cabinet/dto/CabinetsPerSectionResponseDto.java b/backend/src/main/java/org/ftclub/cabinet/dto/CabinetsPerSectionResponseDto.java index eeec57211..3b14c03b2 100644 --- a/backend/src/main/java/org/ftclub/cabinet/dto/CabinetsPerSectionResponseDto.java +++ b/backend/src/main/java/org/ftclub/cabinet/dto/CabinetsPerSectionResponseDto.java @@ -15,4 +15,5 @@ public class CabinetsPerSectionResponseDto { private final String section; private final List cabinets; + private final boolean alarmRegistered; } diff --git a/backend/src/main/java/org/ftclub/cabinet/dto/CoinAmountDto.java b/backend/src/main/java/org/ftclub/cabinet/dto/CoinAmountDto.java new file mode 100644 index 000000000..ecc45f449 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/dto/CoinAmountDto.java @@ -0,0 +1,13 @@ +package org.ftclub.cabinet.dto; + +import java.time.LocalDate; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class CoinAmountDto { + + private LocalDate date; + private Long amount; +} diff --git a/backend/src/main/java/org/ftclub/cabinet/dto/CoinCollectStatisticsDto.java b/backend/src/main/java/org/ftclub/cabinet/dto/CoinCollectStatisticsDto.java new file mode 100644 index 000000000..13d866fee --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/dto/CoinCollectStatisticsDto.java @@ -0,0 +1,15 @@ +package org.ftclub.cabinet.dto; + +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.ToString; + +@Getter +@ToString +@AllArgsConstructor +public class CoinCollectStatisticsDto { + + private List coinCollectStatistics; + +} diff --git a/backend/src/main/java/org/ftclub/cabinet/dto/CoinCollectedCountDto.java b/backend/src/main/java/org/ftclub/cabinet/dto/CoinCollectedCountDto.java new file mode 100644 index 000000000..d893e604a --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/dto/CoinCollectedCountDto.java @@ -0,0 +1,14 @@ +package org.ftclub.cabinet.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.ToString; + +@Getter +@ToString +@AllArgsConstructor +public class CoinCollectedCountDto { + + private Integer coinCount; + private Integer userCount; +} diff --git a/backend/src/main/java/org/ftclub/cabinet/dto/CoinCollectionRewardResponseDto.java b/backend/src/main/java/org/ftclub/cabinet/dto/CoinCollectionRewardResponseDto.java new file mode 100644 index 000000000..147e9e730 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/dto/CoinCollectionRewardResponseDto.java @@ -0,0 +1,11 @@ +package org.ftclub.cabinet.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +@Getter +public class CoinCollectionRewardResponseDto { + + private Integer reward; +} diff --git a/backend/src/main/java/org/ftclub/cabinet/dto/CoinHistoryDto.java b/backend/src/main/java/org/ftclub/cabinet/dto/CoinHistoryDto.java new file mode 100644 index 000000000..023469920 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/dto/CoinHistoryDto.java @@ -0,0 +1,19 @@ +package org.ftclub.cabinet.dto; + +import java.time.LocalDateTime; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@ToString +@AllArgsConstructor +public class CoinHistoryDto { + + private LocalDateTime date; + private Integer amount; + private String history; + private String itemDetails; +} diff --git a/backend/src/main/java/org/ftclub/cabinet/dto/CoinHistoryPaginationDto.java b/backend/src/main/java/org/ftclub/cabinet/dto/CoinHistoryPaginationDto.java new file mode 100644 index 000000000..a9fb02a14 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/dto/CoinHistoryPaginationDto.java @@ -0,0 +1,15 @@ +package org.ftclub.cabinet.dto; + +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.ToString; + +@Getter +@ToString +@AllArgsConstructor +public class CoinHistoryPaginationDto { + + private List result; + private Long totalLength; +} diff --git a/backend/src/main/java/org/ftclub/cabinet/dto/CoinMonthlyCollectionDto.java b/backend/src/main/java/org/ftclub/cabinet/dto/CoinMonthlyCollectionDto.java new file mode 100644 index 000000000..cf9c35955 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/dto/CoinMonthlyCollectionDto.java @@ -0,0 +1,14 @@ +package org.ftclub.cabinet.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.ToString; + +@Getter +@ToString +@AllArgsConstructor +public class CoinMonthlyCollectionDto { + + private Long monthlyCoinCount; + private boolean todayCoinCollection; +} diff --git a/backend/src/main/java/org/ftclub/cabinet/dto/CoinStaticsDto.java b/backend/src/main/java/org/ftclub/cabinet/dto/CoinStaticsDto.java new file mode 100644 index 000000000..beef66087 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/dto/CoinStaticsDto.java @@ -0,0 +1,13 @@ +package org.ftclub.cabinet.dto; + +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class CoinStaticsDto { + + private List issuedCoin; + private List usedCoin; +} diff --git a/backend/src/main/java/org/ftclub/cabinet/dto/ItemAssignPaginationDto.java b/backend/src/main/java/org/ftclub/cabinet/dto/ItemAssignPaginationDto.java new file mode 100644 index 000000000..a441b91d2 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/dto/ItemAssignPaginationDto.java @@ -0,0 +1,13 @@ +package org.ftclub.cabinet.dto; + +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class ItemAssignPaginationDto { + + Long total; + private List items; +} diff --git a/backend/src/main/java/org/ftclub/cabinet/dto/ItemAssignRequestDto.java b/backend/src/main/java/org/ftclub/cabinet/dto/ItemAssignRequestDto.java new file mode 100644 index 000000000..20770c617 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/dto/ItemAssignRequestDto.java @@ -0,0 +1,18 @@ +package org.ftclub.cabinet.dto; + +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; +import org.ftclub.cabinet.item.domain.Sku; + +@Getter +@ToString +@NoArgsConstructor +@AllArgsConstructor +public class ItemAssignRequestDto { + + private Sku itemSku; + private List userIds; +} diff --git a/backend/src/main/java/org/ftclub/cabinet/dto/ItemAssignResponseDto.java b/backend/src/main/java/org/ftclub/cabinet/dto/ItemAssignResponseDto.java new file mode 100644 index 000000000..d1b104750 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/dto/ItemAssignResponseDto.java @@ -0,0 +1,16 @@ +package org.ftclub.cabinet.dto; + +import java.time.LocalDateTime; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.ftclub.cabinet.item.domain.Sku; + +@Getter +@AllArgsConstructor +public class ItemAssignResponseDto { + + private Sku itemSku; // sku + private String itemName; // itemType + private String itemDetails; // sku.description + private LocalDateTime issuedDate; // itemHistory -> purchasedAt +} diff --git a/backend/src/main/java/org/ftclub/cabinet/dto/ItemCreateDto.java b/backend/src/main/java/org/ftclub/cabinet/dto/ItemCreateDto.java new file mode 100644 index 000000000..63ddfbe05 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/dto/ItemCreateDto.java @@ -0,0 +1,21 @@ +package org.ftclub.cabinet.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; +import org.ftclub.cabinet.item.domain.ItemType; +import org.ftclub.cabinet.item.domain.Sku; + +@Getter +@Setter +@ToString +@NoArgsConstructor +@AllArgsConstructor +public class ItemCreateDto { + + private Integer price; + private Sku sku; + private ItemType type; +} diff --git a/backend/src/main/java/org/ftclub/cabinet/dto/ItemDetailsDto.java b/backend/src/main/java/org/ftclub/cabinet/dto/ItemDetailsDto.java new file mode 100644 index 000000000..08502179d --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/dto/ItemDetailsDto.java @@ -0,0 +1,16 @@ +package org.ftclub.cabinet.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.ToString; +import org.ftclub.cabinet.item.domain.Sku; + +@Getter +@ToString +@AllArgsConstructor +public class ItemDetailsDto { + + private Sku itemSku; + private Integer itemPrice; + private String itemDetails; // 연장권 종류 - 3, 15, 31일, 페널티 종류 - 3, 7, 31일 +} diff --git a/backend/src/main/java/org/ftclub/cabinet/dto/ItemDto.java b/backend/src/main/java/org/ftclub/cabinet/dto/ItemDto.java new file mode 100644 index 000000000..9b128b514 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/dto/ItemDto.java @@ -0,0 +1,21 @@ +package org.ftclub.cabinet.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import org.ftclub.cabinet.item.domain.ItemType; +import org.ftclub.cabinet.item.domain.Sku; + +@Getter +@Setter +@ToString +@AllArgsConstructor +public class ItemDto { + + private Sku itemSku; + private String itemName; + private ItemType itemType; + private Integer itemPrice; + private String itemDetails; +} diff --git a/backend/src/main/java/org/ftclub/cabinet/dto/ItemHistoryDto.java b/backend/src/main/java/org/ftclub/cabinet/dto/ItemHistoryDto.java new file mode 100644 index 000000000..1520360ee --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/dto/ItemHistoryDto.java @@ -0,0 +1,17 @@ +package org.ftclub.cabinet.dto; + +import java.time.LocalDateTime; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@ToString +@AllArgsConstructor +public class ItemHistoryDto { + + private LocalDateTime date; + private ItemDto itemDto; +} diff --git a/backend/src/main/java/org/ftclub/cabinet/dto/ItemHistoryPaginationDto.java b/backend/src/main/java/org/ftclub/cabinet/dto/ItemHistoryPaginationDto.java new file mode 100644 index 000000000..f6ed8f0aa --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/dto/ItemHistoryPaginationDto.java @@ -0,0 +1,15 @@ +package org.ftclub.cabinet.dto; + +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.ToString; + +@Getter +@ToString +@AllArgsConstructor +public class ItemHistoryPaginationDto { + + private List result; + private Long totalLength; +} diff --git a/backend/src/main/java/org/ftclub/cabinet/dto/ItemPurchaseCountDto.java b/backend/src/main/java/org/ftclub/cabinet/dto/ItemPurchaseCountDto.java new file mode 100644 index 000000000..7dc75c7c3 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/dto/ItemPurchaseCountDto.java @@ -0,0 +1,13 @@ +package org.ftclub.cabinet.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class ItemPurchaseCountDto { + + private String itemName; + private String itemDetails; + private int userCount; +} diff --git a/backend/src/main/java/org/ftclub/cabinet/dto/ItemResponseDto.java b/backend/src/main/java/org/ftclub/cabinet/dto/ItemResponseDto.java new file mode 100644 index 000000000..e0b953368 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/dto/ItemResponseDto.java @@ -0,0 +1,12 @@ +package org.ftclub.cabinet.dto; + +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +@Getter +public class ItemResponseDto { + + private final List result; +} diff --git a/backend/src/main/java/org/ftclub/cabinet/dto/ItemStatisticsDto.java b/backend/src/main/java/org/ftclub/cabinet/dto/ItemStatisticsDto.java new file mode 100644 index 000000000..da2dbce66 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/dto/ItemStatisticsDto.java @@ -0,0 +1,13 @@ +package org.ftclub.cabinet.dto; + + +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class ItemStatisticsDto { + + private List items; +} diff --git a/backend/src/main/java/org/ftclub/cabinet/dto/ItemStoreDto.java b/backend/src/main/java/org/ftclub/cabinet/dto/ItemStoreDto.java new file mode 100644 index 000000000..83f45d8b8 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/dto/ItemStoreDto.java @@ -0,0 +1,26 @@ +package org.ftclub.cabinet.dto; + +import java.util.Comparator; +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import org.ftclub.cabinet.item.domain.ItemType; + +@Getter +@Setter +@ToString +@AllArgsConstructor +public class ItemStoreDto { + + private String itemName; + private ItemType itemType; + private String description; + private List items; + + //SKU 기준 오름차순 정렬 + public void sortBySkuASC() { + items.sort(Comparator.comparing(ItemDetailsDto::getItemSku)); + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/dto/ItemStoreResponseDto.java b/backend/src/main/java/org/ftclub/cabinet/dto/ItemStoreResponseDto.java new file mode 100644 index 000000000..e69bfb5d5 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/dto/ItemStoreResponseDto.java @@ -0,0 +1,16 @@ +package org.ftclub.cabinet.dto; + +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; + +@Getter +@ToString +@AllArgsConstructor +@NoArgsConstructor +public class ItemStoreResponseDto { + + private List items; +} diff --git a/backend/src/main/java/org/ftclub/cabinet/dto/ItemUseRequestDto.java b/backend/src/main/java/org/ftclub/cabinet/dto/ItemUseRequestDto.java new file mode 100644 index 000000000..46b3d3db8 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/dto/ItemUseRequestDto.java @@ -0,0 +1,21 @@ +package org.ftclub.cabinet.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import lombok.NoArgsConstructor; +import org.ftclub.cabinet.item.domain.ValidItemUse; + + +@Getter +@AllArgsConstructor +@NoArgsConstructor +@ValidItemUse +public class ItemUseRequestDto { + + private Long newCabinetId; // 이사권 사용 시 + + private String building; + private Integer floor; + private String section; +} diff --git a/backend/src/main/java/org/ftclub/cabinet/dto/MyItemResponseDto.java b/backend/src/main/java/org/ftclub/cabinet/dto/MyItemResponseDto.java new file mode 100644 index 000000000..897e05cc1 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/dto/MyItemResponseDto.java @@ -0,0 +1,17 @@ +package org.ftclub.cabinet.dto; + +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.ToString; + +@Getter +@ToString +@AllArgsConstructor +public class MyItemResponseDto { + + List extensionItems; + List swapItems; + List alarmItems; + List penaltyItems; +} diff --git a/backend/src/main/java/org/ftclub/cabinet/dto/MyProfileResponseDto.java b/backend/src/main/java/org/ftclub/cabinet/dto/MyProfileResponseDto.java index 9a3c12a19..4811ba0ea 100644 --- a/backend/src/main/java/org/ftclub/cabinet/dto/MyProfileResponseDto.java +++ b/backend/src/main/java/org/ftclub/cabinet/dto/MyProfileResponseDto.java @@ -1,12 +1,11 @@ package org.ftclub.cabinet.dto; -import lombok.AllArgsConstructor; -import lombok.Getter; -import org.ftclub.cabinet.alarm.dto.AlarmTypeResponseDto; - import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.Locale; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.ftclub.cabinet.alarm.dto.AlarmTypeResponseDto; /** * 내 프로필 정보와 대여 중인 사물함의 ID를 반환하는 DTO입니다. @@ -25,4 +24,5 @@ public class MyProfileResponseDto { private final LentExtensionResponseDto lentExtensionResponseDto; private final AlarmTypeResponseDto alarmTypes; private final Boolean isDeviceTokenExpired; + private final Long coins; } diff --git a/backend/src/main/java/org/ftclub/cabinet/dto/TotalCoinAmountDto.java b/backend/src/main/java/org/ftclub/cabinet/dto/TotalCoinAmountDto.java new file mode 100644 index 000000000..8ca2d65c4 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/dto/TotalCoinAmountDto.java @@ -0,0 +1,15 @@ +package org.ftclub.cabinet.dto; + +import lombok.Getter; + +@Getter +public class TotalCoinAmountDto { + + private Long used; + private Long unused; + + public TotalCoinAmountDto(Long used, Long unused) { + this.used = -1 * used; + this.unused = unused; + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/exception/ExceptionStatus.java b/backend/src/main/java/org/ftclub/cabinet/exception/ExceptionStatus.java index 26f2fa8ce..55ac3ae7a 100644 --- a/backend/src/main/java/org/ftclub/cabinet/exception/ExceptionStatus.java +++ b/backend/src/main/java/org/ftclub/cabinet/exception/ExceptionStatus.java @@ -17,6 +17,7 @@ public enum ExceptionStatus { NOT_FOUND_CABINET(HttpStatus.NOT_FOUND, "사물함이 존재하지 않습니다."), NOT_FOUND_LENT_HISTORY(HttpStatus.NOT_FOUND, "대여한 사물함이 존재하지 않습니다."), NOT_FOUND_CLUB(HttpStatus.NOT_FOUND, "동아리가 존재하지 않습니다."), + NOT_FOUND_ITEM(HttpStatus.NOT_FOUND, "아이템이 존재하지 않습니다"), LENT_CLUB(HttpStatus.I_AM_A_TEAPOT, "동아리 전용 사물함입니다"), LENT_NOT_CLUB(HttpStatus.I_AM_A_TEAPOT, "동아리 전용 사물함이 아닙니다"), LENT_EXPIRE_IMMINENT(HttpStatus.I_AM_A_TEAPOT, "만료가 임박한 공유 사물함입니다\n해당 사물함은 대여할 수 없습니다"), @@ -32,6 +33,7 @@ public enum ExceptionStatus { LENT_ALREADY_EXISTED(HttpStatus.BAD_REQUEST, "이미 대여중인 사물함이 있습니다"), USER_ALREADY_EXISTED(HttpStatus.BAD_REQUEST, "이미 존재하는 유저입니다"), ADMIN_ALREADY_EXISTED(HttpStatus.BAD_REQUEST, "이미 존재하는 어드민입니다"), + COIN_COLLECTION_ALREADY_EXIST(HttpStatus.BAD_REQUEST, "오늘은 이미 동전줍기를 수행했습니다."), NOT_CLUB_USER(HttpStatus.BAD_REQUEST, "동아리 유저가 아닙니다"), INVALID_ARGUMENT(HttpStatus.BAD_REQUEST, "유효하지 않은 입력입니다"), INVALID_STATUS(HttpStatus.BAD_REQUEST, "유효하지 않은 상태변경입니다"), @@ -63,9 +65,9 @@ public enum ExceptionStatus { INVALID_LENT_TYPE(HttpStatus.BAD_REQUEST, "사물함의 대여 타입이 유효하지 않습니다."), NOT_FOUND_BUILDING(HttpStatus.NOT_FOUND, "빌딩이 존재하지 않습니다."), SWAP_EXPIRE_IMMINENT(HttpStatus.I_AM_A_TEAPOT, "현재 사물함의 대여 기간의 만료가 임박해 사물함을 이동 할 수 없습니다."), - SWAP_LIMIT_EXCEEDED(HttpStatus.I_AM_A_TEAPOT, "사물함 이사 횟수 제한을 초과했습니다.\n 일주일에 1회만 이사할 수 있습니다."), SWAP_RECORD_NOT_FOUND(HttpStatus.NOT_FOUND, "이사하기 기능을 사용한 기록이 없습니다."), SWAP_SAME_CABINET(HttpStatus.BAD_REQUEST, "같은 사물함으로 이사할 수 없습니다."), + SWAP_LIMIT_EXCEEDED(HttpStatus.BAD_REQUEST, "이사하기 기능을 이미 사용했습니다."), INVALID_CLUB(HttpStatus.BAD_REQUEST, "동아리가 맞지 않습니다."), NOT_CLUB_MASTER(HttpStatus.BAD_REQUEST, "동아리 장이 아닙니다."), INVALID_CLUB_MASTER(HttpStatus.BAD_REQUEST, "동아리에 동아리 장이 없습니다."), @@ -76,7 +78,12 @@ public enum ExceptionStatus { NOT_FOUND_FORM(HttpStatus.NOT_FOUND, "신청서가 존재하지 않습니다."), INVALID_FORM_ID(HttpStatus.BAD_REQUEST, "잘못된 신청번호입니다."), INVALID_LOCATION(HttpStatus.BAD_REQUEST, "잘못된 장소입니다."), - ; + INVALID_ITEM_USE_REQUEST(HttpStatus.BAD_REQUEST, "잘못된 아이템 사용 요청입니다."), + ITEM_NOT_ON_SALE(HttpStatus.BAD_REQUEST, "구매할 수 없는 아이템입니다."), + NOT_ENOUGH_COIN(HttpStatus.BAD_REQUEST, "보유한 코인이 아이템 가격보다 적습니다."), + INVALID_JWT_TOKEN(HttpStatus.BAD_REQUEST, "토큰이 없거나, 유효하지 않은 JWT 토큰입니다."), + NOT_FOUND_SECTION(HttpStatus.BAD_REQUEST, "사물함 구역 정보를 찾을 수 없습니다."), + ITEM_NOT_OWNED(HttpStatus.BAD_REQUEST, "해당 아이템을 보유하고 있지 않습니다"); final private int statusCode; final private String message; diff --git a/backend/src/main/java/org/ftclub/cabinet/item/controller/ItemController.java b/backend/src/main/java/org/ftclub/cabinet/item/controller/ItemController.java new file mode 100644 index 000000000..328bede18 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/item/controller/ItemController.java @@ -0,0 +1,140 @@ +package org.ftclub.cabinet.item.controller; + +import javax.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.ftclub.cabinet.auth.domain.AuthGuard; +import org.ftclub.cabinet.auth.domain.AuthLevel; +import org.ftclub.cabinet.dto.CoinCollectionRewardResponseDto; +import org.ftclub.cabinet.dto.CoinHistoryPaginationDto; +import org.ftclub.cabinet.dto.CoinMonthlyCollectionDto; +import org.ftclub.cabinet.dto.ItemHistoryPaginationDto; +import org.ftclub.cabinet.dto.ItemStoreResponseDto; +import org.ftclub.cabinet.dto.ItemUseRequestDto; +import org.ftclub.cabinet.dto.MyItemResponseDto; +import org.ftclub.cabinet.dto.UserSessionDto; +import org.ftclub.cabinet.item.domain.CoinHistoryType; +import org.ftclub.cabinet.item.domain.Sku; +import org.ftclub.cabinet.item.service.ItemFacadeService; +import org.ftclub.cabinet.log.Logging; +import org.ftclub.cabinet.user.domain.UserSession; +import org.springframework.data.domain.Pageable; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/v5/items") +@Logging +public class ItemController { + + private final ItemFacadeService itemFacadeService; + + /** + * 전체 아이템 목록 조회 + * + * @return + */ + @GetMapping("") + @AuthGuard(level = AuthLevel.USER_OR_ADMIN) + public ItemStoreResponseDto getAllItems() { + return itemFacadeService.getAllItems(); + } + + /** + * 특정 아이템 구매 요청 + * + * @param user + * @param sku + */ + @PostMapping("/{sku}/purchase") + @AuthGuard(level = AuthLevel.USER_ONLY) + public void purchaseItem(@UserSession UserSessionDto user, + @PathVariable Sku sku) { + itemFacadeService.purchaseItem(user.getUserId(), sku); + } + + /** + * 유저의 아이템 구매, 사용 내역 목록 조회 + * + * @param user + * @param pageable + * @return + */ + @GetMapping("/history") + @AuthGuard(level = AuthLevel.USER_ONLY) + public ItemHistoryPaginationDto getItemHistory(@UserSession UserSessionDto user, + Pageable pageable) { + return itemFacadeService.getItemHistory(user.getUserId(), pageable); + } + + /** + * 유저가 보유하고 있는 아이템 목록 조회 + * + * @param user + * @return + */ + @GetMapping("/me") + @AuthGuard(level = AuthLevel.USER_ONLY) + public MyItemResponseDto getMyItems(@UserSession UserSessionDto user) { + return itemFacadeService.getMyItems(user); + } + + /** + * 유저의 동전 줍기 내역 반환 + * + * @param user + * @param type ALL, EARN, USE + * @param pageable + * @return + */ + @GetMapping("/coin/history") + @AuthGuard(level = AuthLevel.USER_ONLY) + public CoinHistoryPaginationDto getCoinHistory(@UserSession UserSessionDto user, + @RequestParam CoinHistoryType type, Pageable pageable) { + return itemFacadeService.getCoinHistory(user.getUserId(), type, pageable); + } + + /** + * 한달 간 동전 줍기 횟수, 당일 동전줍기 요청 유무 + * + * @param user 유저 세션 + * @return + */ + @GetMapping("/coin") + @AuthGuard(level = AuthLevel.USER_ONLY) + public CoinMonthlyCollectionDto getCoinMonthlyCollectionCount( + @UserSession UserSessionDto user) { + return itemFacadeService.getCoinCollectionCountInMonth(user.getUserId()); + } + + /** + * 동전 줍기 요청 + * + * @param user 유저 세션 + */ + @PostMapping("/coin") + @AuthGuard(level = AuthLevel.USER_ONLY) + public CoinCollectionRewardResponseDto collectCoin(@UserSession UserSessionDto user) { + return itemFacadeService.collectCoinAndIssueReward(user.getUserId()); + } + + /** + * 아이템 사용 요청 + * + * @param user 유저 세션 + * @param sku 아이템 고유 식별 값 + * @param data sku 에 따라 다르게 필요한 정보 + */ + @PostMapping("{sku}/use") + @AuthGuard(level = AuthLevel.USER_ONLY) + public void useItem(@UserSession UserSessionDto user, + @PathVariable("sku") Sku sku, + @Valid @RequestBody ItemUseRequestDto data) { + itemFacadeService.useItem(user.getUserId(), sku, data); + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/item/domain/CoinHistoryType.java b/backend/src/main/java/org/ftclub/cabinet/item/domain/CoinHistoryType.java new file mode 100644 index 000000000..c4199bae4 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/item/domain/CoinHistoryType.java @@ -0,0 +1,11 @@ +package org.ftclub.cabinet.item.domain; + +public enum CoinHistoryType { + ALL, + EARN, + USE; + + public boolean isValid() { + return this.equals(ALL) || this.equals(EARN) || this.equals(USE); + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/item/domain/Item.java b/backend/src/main/java/org/ftclub/cabinet/item/domain/Item.java new file mode 100644 index 000000000..2b3cd7d4d --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/item/domain/Item.java @@ -0,0 +1,88 @@ +package org.ftclub.cabinet.item.domain; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; +import org.ftclub.cabinet.exception.ExceptionStatus; + +@Entity +@Table(name = "ITEM") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@ToString +@Getter +public class Item { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "ID") + private Long id; + + /** + * 상품 타입 + */ + @Column(name = "Type", nullable = false) + @Enumerated(value = EnumType.STRING) + private ItemType type; + + /** + * 상품 고유 코드 + */ + @Column(name = "SKU", unique = true, nullable = false) + @Enumerated(value = EnumType.STRING) + private Sku sku; + + /** + * 상품 가격 + */ + @Column(name = "PRICE", nullable = false) + private Long price; + + + protected Item(long price, Sku sku, ItemType type) { + this.price = price; + this.sku = sku; + this.type = type; + } + + /** + * @param price 상품 가격 + * @param sku 상품 코드 + * @return 상품 객체 {@link Item} + */ + public static Item of(long price, Sku sku, ItemType type) { + Item item = new Item(price, sku, type); + if (!item.isValid()) { + throw ExceptionStatus.INVALID_ARGUMENT.asDomainException(); + } + return item; + } + + /** + * name, sku, description 의 null 이 아닌지 확인합니다. + * + * @return 유효한 인스턴스 여부 + */ + private boolean isValid() { + return sku.isValid() && type.isValid() && type.isValid(); + } + + @Override + public boolean equals(final Object other) { + if (this == other) { + return true; + } + if (!(other instanceof Item)) { + return false; + } + return (this.id.equals(((Item) other).id)); + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/item/domain/ItemHistory.java b/backend/src/main/java/org/ftclub/cabinet/item/domain/ItemHistory.java new file mode 100644 index 000000000..2784a5f72 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/item/domain/ItemHistory.java @@ -0,0 +1,111 @@ +package org.ftclub.cabinet.item.domain; + +import static javax.persistence.FetchType.LAZY; + +import java.time.LocalDateTime; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EntityListeners; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; +import org.ftclub.cabinet.exception.ExceptionStatus; +import org.ftclub.cabinet.user.domain.User; +import org.hibernate.annotations.BatchSize; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +@Entity +@Table(name = "ITEM_HISTORY") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@ToString(exclude = {"user", "item"}) +@EntityListeners(AuditingEntityListener.class) +@Getter +@BatchSize(size = 25) +public class ItemHistory { + + @Id + @Column(name = "ID") + @GeneratedValue(strategy = GenerationType.IDENTITY) + private long id; + + /** + * 아이템 구매 일자 + */ + @CreatedDate + @Column(name = "PURCHASE_AT", nullable = false, updatable = false) + private LocalDateTime purchaseAt; + + /** + * 아이템 사용 일자 + */ + @Column(name = "USED_AT") + private LocalDateTime usedAt; + + /** + * 아이템 소유자 + */ + @JoinColumn(name = "USER_ID", nullable = false, updatable = false, insertable = false) + @ManyToOne(fetch = LAZY) + private User user; + + /** + * 사용한 아이템 + */ + @JoinColumn(name = "ITEM_ID", nullable = false, updatable = false, insertable = false) + @ManyToOne(fetch = LAZY) + private Item item; + + /** + * 사용한 아이템 ID + */ + @Column(name = "ITEM_ID", nullable = false) + private Long itemId; + + /** + * 사용한 사용자 ID + */ + @Column(name = "USER_ID", nullable = false) + private Long userId; + + + protected ItemHistory(long userId, long itemId, LocalDateTime usedAt) { + this.userId = userId; + this.itemId = itemId; + this.usedAt = usedAt; + } + + /** + * @param userId 아이템을 사용한 유저 ID + * @param itemId 사용된 아이템 ID + * @param usedAt 아이템 사용일자 + * @return 아이템 히스토리 객체 {@link ItemHistory} + */ + public static ItemHistory of(long userId, long itemId, LocalDateTime usedAt) { + ItemHistory itemHistory = new ItemHistory(userId, itemId, usedAt); + if (!itemHistory.isValid()) { + throw ExceptionStatus.INVALID_ARGUMENT.asDomainException(); + } + return itemHistory; + } + + /** + * 사용자 ID, 아이템 ID, 사용일자의 null 이 아닌지 확인합니다. + * + * @return 유효한 인스턴스 여부 + */ + private boolean isValid() { + return this.userId != null && this.itemId != null; + } + + public void updateUsedAt() { + this.usedAt = LocalDateTime.now(); + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/item/domain/ItemType.java b/backend/src/main/java/org/ftclub/cabinet/item/domain/ItemType.java new file mode 100644 index 000000000..d7de1893a --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/item/domain/ItemType.java @@ -0,0 +1,37 @@ +package org.ftclub.cabinet.item.domain; + +import lombok.Getter; + +@Getter +public enum ItemType { + + EXTENSION("연장권", + "현재 대여 중인 사물함의 반납 기한을 3일, 15일 또는 30일 연장할 수 있습니다."), + PENALTY("페널티 감면권", + "사물함 이용 중 발생한 페널티 일수를 감소시켜 사물함 사용 제한 기간을 줄일 수 있습니다."), + SWAP("이사권" + , "현재 대여 중인 사물함의 반납 기한을 유지하면서 다른 사물함으로 이동할 수 있습니다."), + ALARM("알림 등록권", + "원하는 사물함 구역의 개인 사물함 자리가 빈 경우, 다음 날 오픈 5분 전 알림을 받을 수 있습니다."), + + COIN_COLLECT("동전 줍기", + "누군가가 매일 흘리는 동전을 주워보세요\uD83D\uDCB0\n동전은 하루에 한 번씩 획득할 수 있습니다"), + COIN_REWARD("동전 줍기 20일 보상", + "20일 동안 매일 동전을 주웠다면, 보너스 동전을 받습니다\uD83D\uDCB0"), + COIN_FULL_TIME("42 출석 보상", + "42 서울에 열심히 출석했다면, 보상을 받을 수 있습니다\uD83D\uDCB0"), + ; + + private final String name; + private final String description; + + ItemType(String name, String description) { + this.name = name; + this.description = description; + } + + public boolean isValid() { + return this.equals(EXTENSION) || this.equals(PENALTY) || this.equals(SWAP) + || this.equals(ALARM); + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/item/domain/ItemUseValidator.java b/backend/src/main/java/org/ftclub/cabinet/item/domain/ItemUseValidator.java new file mode 100644 index 000000000..8d323913a --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/item/domain/ItemUseValidator.java @@ -0,0 +1,48 @@ +package org.ftclub.cabinet.item.domain; + +import io.netty.util.internal.StringUtil; +import java.util.Map; +import javax.servlet.http.HttpServletRequest; +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; +import org.ftclub.cabinet.dto.ItemUseRequestDto; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; +import org.springframework.web.servlet.HandlerMapping; + +public class ItemUseValidator implements ConstraintValidator { + + private static final String PATH_VARIABLE_NAME = "sku"; + private static final String SKU_SWAP_NAME = "SWAP"; + private static final String SKU_ALARM_NAME = "ALARM"; + + @Override + public boolean isValid(ItemUseRequestDto itemUseRequestDto, + ConstraintValidatorContext constraintValidatorContext) { + + ServletRequestAttributes requestAttributes = + (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + HttpServletRequest request = requestAttributes.getRequest(); + Map pathVariables = + (Map) request.getAttribute( + HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE); + + String sku = pathVariables.get(PATH_VARIABLE_NAME); + if (sku.equals(SKU_SWAP_NAME) && itemUseRequestDto.getNewCabinetId() == null) { + return false; + } + if (sku.equals(SKU_ALARM_NAME) && !isValidAlarmData(itemUseRequestDto)) { + return false; + } + return true; + } + + private boolean isValidAlarmData(ItemUseRequestDto dto) { + if (dto.getFloor() == null + || StringUtil.isNullOrEmpty(dto.getSection()) + || StringUtil.isNullOrEmpty(dto.getBuilding())) { + return false; + } + return true; + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/item/domain/SectionAlarm.java b/backend/src/main/java/org/ftclub/cabinet/item/domain/SectionAlarm.java new file mode 100644 index 000000000..79166cbbb --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/item/domain/SectionAlarm.java @@ -0,0 +1,108 @@ +package org.ftclub.cabinet.item.domain; + + +import static javax.persistence.FetchType.LAZY; + +import java.time.LocalDateTime; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EntityListeners; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; +import org.ftclub.cabinet.cabinet.domain.CabinetPlace; +import org.ftclub.cabinet.exception.ExceptionStatus; +import org.ftclub.cabinet.user.domain.User; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +@Entity +@Table(name = "SECTION_ALARM") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +@ToString(exclude = {"user", "cabinetPlace"}) +@EntityListeners(AuditingEntityListener.class) +public class SectionAlarm { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Long id; + + /** + * 알람 등록 시간 + */ + @CreatedDate + @Column(name = "REGISTERED_AT", nullable = false, updatable = false) + private LocalDateTime registeredAt; + + /** + * 알람 발생 시간 + */ + @Column(name = "ALARMED_AT") + private LocalDateTime alarmedAt; + + /** + * 대여하는 유저 + */ + @Column(name = "USER_ID", nullable = false) + private Long userId; + + /** + * 알람 등록된 관심 사물함 영역 + */ + @Column(name = "CABINET_PLACE_ID", nullable = false) + private Long cabinetPlaceId; + + @JoinColumn(name = "USER_ID", nullable = false, insertable = false, updatable = false) + @ManyToOne(fetch = LAZY) + private User user; + + @JoinColumn(name = "CABINET_PLACE_ID", nullable = false, insertable = false, updatable = false) + @ManyToOne(fetch = LAZY) + private CabinetPlace cabinetPlace; + + protected SectionAlarm(Long userId, Long cabinetPlaceId) { + this.userId = userId; + this.cabinetPlaceId = cabinetPlaceId; + } + + /** + * @param userId 알람 발생 유저 + * @param cabinetPlaceId 알람 발생 사물함 영역 + * @return 인자 정보를 담고있는 {@link SectionAlarm} + */ + public static SectionAlarm of(Long userId, Long cabinetPlaceId) { + SectionAlarm sectionAlarm = new SectionAlarm(userId, cabinetPlaceId); + if (!sectionAlarm.isValid()) { + throw ExceptionStatus.INVALID_ARGUMENT.asDomainException(); + } + return sectionAlarm; + } + + /** + * registeredAt, userId, cabinetPlaceId, alarmType 의 null 이 아닌지 확인합니다. + * + * @return 유효한 인스턴스 여부 + */ + private boolean isValid() { + return this.userId != null && this.cabinetPlaceId != null; + } + + @Override + public boolean equals(final Object other) { + if (this == other) { + return true; + } + if (!(other instanceof SectionAlarm)) { + return false; + } + return (this.id.equals(((SectionAlarm) other).id)); + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/item/domain/Sku.java b/backend/src/main/java/org/ftclub/cabinet/item/domain/Sku.java new file mode 100644 index 000000000..fc8dff04a --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/item/domain/Sku.java @@ -0,0 +1,59 @@ +package org.ftclub.cabinet.item.domain; + +import lombok.Getter; +import org.ftclub.cabinet.exception.ExceptionStatus; + +@Getter +public enum Sku { + + EXTENSION_PREV("출석 연장권 보상"), + EXTENSION_3("3일"), + EXTENSION_15("15일"), + EXTENSION_31("31일"), + + PENALTY_3("3일"), + PENALTY_7("7일"), + PENALTY_31("31일"), + + SWAP("이사권"), + ALARM("알림 등록권"), + + COIN_COLLECT("동전 줍기"), + COIN_FULL_TIME("42 출석 보상"), + COIN_REWARD_200("동전 줍기 20일 보상"), + COIN_REWARD_500("동전 줍기 20일 보상"), + COIN_REWARD_1000("동전 줍기 20일 보상"), + COIN_REWARD_2000("동전 줍기 20일 보상"), + ; + + private final String details; + + Sku(String details) { + this.details = details; + } + + public boolean isValid() { + return this.equals(EXTENSION_3) || this.equals(EXTENSION_15) || this.equals(EXTENSION_31) + || this.equals(PENALTY_3) || this.equals(PENALTY_7) || this.equals(PENALTY_31) + || this.equals(SWAP) || this.equals(ALARM) || this.equals(COIN_COLLECT) + || this.equals(COIN_FULL_TIME) || this.equals(COIN_REWARD_200) + || this.equals(COIN_REWARD_500) || this.equals(COIN_REWARD_1000) + || this.equals(COIN_REWARD_2000); + } + + public Integer getDays() { + if (this.equals(EXTENSION_3) || this.equals(PENALTY_3)) { + return 3; + } + if (this.equals(EXTENSION_31) || this.equals(PENALTY_31)) { + return 31; + } + if (this.equals(EXTENSION_15)) { + return 15; + } + if (this.equals(PENALTY_7)) { + return 7; + } + throw ExceptionStatus.NOT_FOUND_ITEM.asDomainException(); + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/item/domain/ValidItemUse.java b/backend/src/main/java/org/ftclub/cabinet/item/domain/ValidItemUse.java new file mode 100644 index 000000000..6019c186f --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/item/domain/ValidItemUse.java @@ -0,0 +1,21 @@ +package org.ftclub.cabinet.item.domain; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import javax.validation.Constraint; +import javax.validation.Payload; + +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Constraint(validatedBy = ItemUseValidator.class) +public @interface ValidItemUse { + + String message() default "잘못된 아이템 사용 요청입니다"; + + Class[] groups() default {}; + + Class[] payload() default {}; +} + diff --git a/backend/src/main/java/org/ftclub/cabinet/item/repository/ItemHistoryRepository.java b/backend/src/main/java/org/ftclub/cabinet/item/repository/ItemHistoryRepository.java new file mode 100644 index 000000000..8eded2a0f --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/item/repository/ItemHistoryRepository.java @@ -0,0 +1,93 @@ +package org.ftclub.cabinet.item.repository; + +import java.time.LocalDate; +import java.util.List; +import org.ftclub.cabinet.item.domain.ItemHistory; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.EntityGraph; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +@Repository +public interface ItemHistoryRepository extends JpaRepository { + + @Query("SELECT ih " + + "FROM ItemHistory ih " + + "WHERE ih.userId = :userId " + + "AND ih.itemId IN (:itemIds) " + + "ORDER BY ih.purchaseAt DESC") + Page findAllByUserIdAndItemIdIn(@Param("userId") Long userId, + Pageable pageable, @Param("itemIds") List itemIds); + + @Query(value = "SELECT ih " + + "FROM ItemHistory ih " + + "JOIN FETCH ih.item i " + + "WHERE ih.userId = :userId " + + "AND ih.usedAt IS NOT NULL " + + "AND i.price < 0 " + + "ORDER BY ih.usedAt DESC", + countQuery = "SELECT COUNT(ih) " + + "FROM ItemHistory ih " + + "JOIN ih.item i " + + "WHERE ih.userId = :userId " + + "AND ih.usedAt IS NOT NULL " + + "AND i.price < 0") + Page findAllByUserIdOnMinusPriceItemsWithSubQuery( + @Param("userId") Long userId, Pageable pageable); + + @EntityGraph(attributePaths = "item") + Page findAllByUserIdOrderByPurchaseAtDesc(Long userId, Pageable pageable); + + @EntityGraph(attributePaths = "item") + List findAllByUserId(Long userId); + + @Query(value = "SELECT SUM(i.price) " + + "FROM ItemHistory ih " + + "JOIN ih.item i " + + "WHERE i.price < 0") + Long getPriceSumOnMinusPriceItems(); + + @Query(value = "SELECT SUM(i.price) " + + "FROM ItemHistory ih " + + "JOIN ih.item i " + + "WHERE i.price > 0") + Long getPriceSumOnPlusPriceItems(); + + @Query("SELECT ih " + + "FROM ItemHistory ih " + + "JOIN FETCH ih.item " + + "WHERE ih.userId = :userId " + + "AND ih.usedAt IS NULL " + ) + List getAllUnusedItemHistoryByUser(@Param("userId") Long userId); + + List findAllByUserIdAndItemIdAndUsedAtIsNull(Long userId, Long itemId); + + + @Query("SELECT ih " + + "FROM ItemHistory ih " + + "WHERE ih.itemId = :itemId " + + "AND YEAR(ih.purchaseAt) = :year " + + "AND MONTH(ih.purchaseAt) = :month " + ) + List findCoinCollectInfoByIdAtYearAndMonth(@Param("itemId") Long itemId, + @Param("year") Integer year, @Param("month") Integer month); + + @Query("SELECT COUNT(ih) " + + "FROM ItemHistory ih " + + "WHERE ih.itemId = :itemId") + int getCountByItemIds(@Param("itemId") Long itemId); + + @Query("SELECT ih " + + "FROM ItemHistory ih " + + "JOIN FETCH ih.item " + + "WHERE DATE(ih.purchaseAt) >= DATE(:start) " + + "AND DATE(ih.purchaseAt) <= DATE(:end)" + ) + List findAllUsedAtIsNotNullBetween( + @Param("start") LocalDate startDate, + @Param("end") LocalDate endDate); +} diff --git a/backend/src/main/java/org/ftclub/cabinet/item/repository/ItemRedis.java b/backend/src/main/java/org/ftclub/cabinet/item/repository/ItemRedis.java new file mode 100644 index 000000000..49f43a457 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/item/repository/ItemRedis.java @@ -0,0 +1,143 @@ +package org.ftclub.cabinet.item.repository; + + +import java.time.Duration; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.temporal.TemporalAdjusters; +import java.util.Objects; +import java.util.concurrent.TimeUnit; +import org.ftclub.cabinet.log.LogLevel; +import org.ftclub.cabinet.log.Logging; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Component; + +@Component +@Logging(level = LogLevel.DEBUG) +public class ItemRedis { + + private static final String COIN_COUNT_KEY_SUFFIX = ":coinAmount"; + private static final String COIN_COLLECT_KEY_SUFFIX = ":coinCollect"; + private static final String COIN_COLLECT_COUNT_KEY_SUFFIX = ":coinCollectCount"; + private static final String TOTAL_COIN_SUPPLY_KEY_SUFFIX = "totalCoinSupply"; + private static final String TOTAL_COIN_USAGE_KEY_SUFFIX = "totalCoinUsage"; + + + private final RedisTemplate coinTemplate; + + @Autowired + public ItemRedis(RedisTemplate coinTemplate) { + this.coinTemplate = coinTemplate; + } + + public String getCoinAmount(String userId) { + return coinTemplate.opsForValue().get(userId + COIN_COUNT_KEY_SUFFIX); + } + + public void saveCoinAmount(String userId, String coinCount) { + coinTemplate.opsForValue().set(userId + COIN_COUNT_KEY_SUFFIX, coinCount); + } + + /** + * 전체 동전 발행량 반환 + * + * @return + */ + public String getTotalCoinSupply() { + return coinTemplate.opsForValue().get(TOTAL_COIN_SUPPLY_KEY_SUFFIX); + } + + /** + * 전체 동전 사용량 반환 + * + * @return + */ + public String getTotalcoinUsage() { + return coinTemplate.opsForValue().get(TOTAL_COIN_USAGE_KEY_SUFFIX); + } + + /** + * 전체 동전 발행량 저장 + * + * @param coinSupply + */ + + public void saveTotalCoinSupply(String coinSupply) { + coinTemplate.opsForValue().set(TOTAL_COIN_SUPPLY_KEY_SUFFIX, coinSupply); + } + + /** + * 전체 동전 사용량 저장 + * + * @param coinUsage + */ + public void saveTotalCoinUsage(String coinUsage) { + coinTemplate.opsForValue().set(TOTAL_COIN_USAGE_KEY_SUFFIX, coinUsage); + } + + + /** + * 하루동안 유지되는 redis를 탐색하여 동전줍기를 했는지 검수 + * + * @param userId + * @return + */ + public boolean isCoinCollected(String userId) { + Boolean isCollected = coinTemplate.hasKey(userId + COIN_COLLECT_KEY_SUFFIX); + return Objects.nonNull(isCollected) && isCollected; + } + + /** + * 한 달 동안 동전 줍기를 행한 횟수 반환 + * + * @param userId + * @return + */ + public String getCoinCollectionCount(String userId) { + String key = userId + COIN_COLLECT_COUNT_KEY_SUFFIX; + return coinTemplate.opsForValue().get(key); + } + + /** + * 하루동안 유지되는 코인 줍기 + *

+ * 현재 시간부터 자정까지의 남은 시간을 초로 계산하여 expireTime 으로 저장합니다. + * + * @param userId + */ + public void collectCoin(String userId) { + String key = userId + COIN_COLLECT_KEY_SUFFIX; + coinTemplate.opsForValue().set(key, "collected"); + + LocalDateTime todayEnd = LocalDate.now().atStartOfDay().plusDays(1); + LocalDateTime now = LocalDateTime.now(); + Duration duration = Duration.between(now, todayEnd); + + long expireTime = duration.getSeconds(); + coinTemplate.expire(key, expireTime, TimeUnit.SECONDS); + } + + /** + * 한 달 동안 유지되는 코인 줍기 + *

+ * 값이 존재한다면 횟수 증가, 없다면 1로 설정 후 당월의 마지막을 expire 로 설정 + * + * @param userId + */ + public void addCoinCollectionCount(String userId) { + String key = userId + COIN_COLLECT_COUNT_KEY_SUFFIX; + Long currentCount = coinTemplate.opsForValue().increment(key, 1); + + if (currentCount == 1) { + LocalDate today = LocalDate.now(); + LocalDate lastDayOfMonth = today.with(TemporalAdjusters.lastDayOfMonth()); + LocalDateTime endOfMonth = lastDayOfMonth.atTime(23, 59, 59); + LocalDateTime now = LocalDateTime.now(); + + Duration between = Duration.between(now, endOfMonth); + long expireTime = between.getSeconds(); + coinTemplate.expire(key, expireTime, TimeUnit.SECONDS); + } + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/item/repository/ItemRepository.java b/backend/src/main/java/org/ftclub/cabinet/item/repository/ItemRepository.java new file mode 100644 index 000000000..6c2f95377 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/item/repository/ItemRepository.java @@ -0,0 +1,35 @@ +package org.ftclub.cabinet.item.repository; + +import java.util.List; +import java.util.Optional; +import org.ftclub.cabinet.item.domain.Item; +import org.ftclub.cabinet.item.domain.Sku; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +@Repository +public interface ItemRepository extends JpaRepository { + + @Query("SELECT i " + + "FROM Item i " + + "WHERE i.price >= 0") + List findAllByPricePositive(); + + @Query("SELECT i " + + "FROM Item i " + + "WHERE i.price < 0") + List findAllByPriceNegative(); + + /** + * SKU (상품고유번호)로 상품 조회 + * + * @param sku + * @return + */ + @Query("SELECT i " + + "FROM Item i " + + "WHERE i.sku = :sku") + Optional findBySku(@Param("sku") Sku sku); +} diff --git a/backend/src/main/java/org/ftclub/cabinet/item/repository/SectionAlarmRepository.java b/backend/src/main/java/org/ftclub/cabinet/item/repository/SectionAlarmRepository.java new file mode 100644 index 000000000..1077be50f --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/item/repository/SectionAlarmRepository.java @@ -0,0 +1,39 @@ +package org.ftclub.cabinet.item.repository; + +import java.time.LocalDateTime; +import java.util.List; +import org.ftclub.cabinet.item.domain.SectionAlarm; +import org.springframework.data.jpa.repository.EntityGraph; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +@Repository +public interface SectionAlarmRepository extends JpaRepository { + + + @EntityGraph(attributePaths = {"cabinetPlace"}) + List findAllByAlarmedAtIsNull(); + + @Modifying(flushAutomatically = true, clearAutomatically = true) + @Query("UPDATE SectionAlarm s " + + "SET s.alarmedAt = :date " + + "WHERE s.id IN (:ids)") + void updateAlarmedAtBulk(@Param("ids") List ids, @Param("date") LocalDateTime date); + + @Query("SELECT s " + + "FROM SectionAlarm s " + + "JOIN FETCH s.cabinetPlace " + + "WHERE s.userId = :userId " + + "AND s.alarmedAt IS NULL " + + "AND s.cabinetPlaceId in (" + + " SELECT cp FROM CabinetPlace cp " + + " WHERE cp.location.building = :building " + + " AND cp.location.floor = :floor)") + List findAllByUserIdAndCabinetPlaceAndAlarmedAtIsNull( + @Param("userId") Long userId, + @Param("building") String building, + @Param("floor") Integer floor); +} diff --git a/backend/src/main/java/org/ftclub/cabinet/item/service/ItemCommandService.java b/backend/src/main/java/org/ftclub/cabinet/item/service/ItemCommandService.java new file mode 100644 index 000000000..360ba83b4 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/item/service/ItemCommandService.java @@ -0,0 +1,24 @@ +package org.ftclub.cabinet.item.service; + +import lombok.RequiredArgsConstructor; +import org.ftclub.cabinet.item.domain.Item; +import org.ftclub.cabinet.item.domain.ItemType; +import org.ftclub.cabinet.item.domain.Sku; +import org.ftclub.cabinet.item.repository.ItemRepository; +import org.ftclub.cabinet.log.LogLevel; +import org.ftclub.cabinet.log.Logging; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +@Transactional +@Logging(level = LogLevel.DEBUG) +public class ItemCommandService { + + private final ItemRepository itemRepository; + + public void createItem(Integer price, Sku sku, ItemType type) { + itemRepository.save(Item.of(price, sku, type)); + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/item/service/ItemFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/item/service/ItemFacadeService.java new file mode 100644 index 000000000..90ffd825e --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/item/service/ItemFacadeService.java @@ -0,0 +1,320 @@ +package org.ftclub.cabinet.item.service; + +import static java.util.stream.Collectors.groupingBy; +import static java.util.stream.Collectors.mapping; + +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ThreadLocalRandom; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import org.ftclub.cabinet.alarm.domain.AlarmItem; +import org.ftclub.cabinet.alarm.domain.ExtensionItem; +import org.ftclub.cabinet.alarm.domain.ItemUsage; +import org.ftclub.cabinet.alarm.domain.PenaltyItem; +import org.ftclub.cabinet.alarm.domain.SwapItem; +import org.ftclub.cabinet.cabinet.domain.CabinetPlace; +import org.ftclub.cabinet.cabinet.service.CabinetQueryService; +import org.ftclub.cabinet.dto.CoinCollectionRewardResponseDto; +import org.ftclub.cabinet.dto.CoinHistoryDto; +import org.ftclub.cabinet.dto.CoinHistoryPaginationDto; +import org.ftclub.cabinet.dto.CoinMonthlyCollectionDto; +import org.ftclub.cabinet.dto.ItemDetailsDto; +import org.ftclub.cabinet.dto.ItemDto; +import org.ftclub.cabinet.dto.ItemHistoryDto; +import org.ftclub.cabinet.dto.ItemHistoryPaginationDto; +import org.ftclub.cabinet.dto.ItemStoreDto; +import org.ftclub.cabinet.dto.ItemStoreResponseDto; +import org.ftclub.cabinet.dto.ItemUseRequestDto; +import org.ftclub.cabinet.dto.MyItemResponseDto; +import org.ftclub.cabinet.dto.UserBlackHoleEvent; +import org.ftclub.cabinet.dto.UserSessionDto; +import org.ftclub.cabinet.exception.ExceptionStatus; +import org.ftclub.cabinet.item.domain.CoinHistoryType; +import org.ftclub.cabinet.item.domain.Item; +import org.ftclub.cabinet.item.domain.ItemHistory; +import org.ftclub.cabinet.item.domain.ItemType; +import org.ftclub.cabinet.item.domain.Sku; +import org.ftclub.cabinet.log.LogLevel; +import org.ftclub.cabinet.log.Logging; +import org.ftclub.cabinet.mapper.ItemMapper; +import org.ftclub.cabinet.user.domain.User; +import org.ftclub.cabinet.user.service.UserQueryService; +import org.ftclub.cabinet.utils.lock.LockUtil; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + + +@Service +@RequiredArgsConstructor +@Logging(level = LogLevel.DEBUG) +public class ItemFacadeService { + + private final ItemQueryService itemQueryService; + private final ItemHistoryQueryService itemHistoryQueryService; + private final ItemHistoryCommandService itemHistoryCommandService; + private final ItemRedisService itemRedisService; + private final UserQueryService userQueryService; + private final SectionAlarmCommandService sectionAlarmCommandService; + private final CabinetQueryService cabinetQueryService; + private final ItemMapper itemMapper; + private final ItemPolicyService itemPolicyService; + private final ApplicationEventPublisher eventPublisher; + + + /** + * 모든 아이템 리스트 반환 + * + * @return 전체 아이템 리스트 + */ + @Transactional + public ItemStoreResponseDto getAllItems() { + List allItems = itemQueryService.getAllItems(); + Map> itemMap = allItems.stream() + .filter(item -> item.getPrice() < 0) + .collect(groupingBy(Item::getType, + mapping(itemMapper::toItemDetailsDto, Collectors.toList()))); + List result = itemMap.entrySet().stream() + .map(entry -> { + ItemStoreDto itemStoreDto = itemMapper.toItemStoreDto(entry.getKey(), + entry.getValue()); + itemStoreDto.sortBySkuASC(); + return itemStoreDto; + }) + .collect(Collectors.toList()); + return new ItemStoreResponseDto(result); + } + + /** + * 유저의 보유 아이템 반환 + * + * @param user 유저 정보 + * @return 유저의 보유 아이템 + */ + @Transactional(readOnly = true) + public MyItemResponseDto getMyItems(UserSessionDto user) { + List userItemHistories = itemHistoryQueryService.findAllItemHistoryByUser( + user.getUserId()); + + Map> itemMap = userItemHistories.stream() + .map(ItemHistory::getItem) + .filter(item -> item.getPrice() < 0) + .collect(groupingBy(Item::getType, + mapping(itemMapper::toItemDto, Collectors.toList()))); + + List extensionItems = itemMap.getOrDefault(ItemType.EXTENSION, + Collections.emptyList()); + List swapItems = itemMap.getOrDefault(ItemType.SWAP, Collections.emptyList()); + List alarmItems = itemMap.getOrDefault(ItemType.ALARM, Collections.emptyList()); + List penaltyItems = itemMap.getOrDefault(ItemType.PENALTY, + Collections.emptyList()); + + return itemMapper.toMyItemResponseDto(extensionItems, swapItems, alarmItems, penaltyItems); + } + + + @Transactional(readOnly = true) + public ItemHistoryPaginationDto getItemHistory(Long userId, Pageable pageable) { + Page itemHistories = + itemHistoryQueryService.findItemHistoryWithItem(userId, pageable); + List result = itemHistories.stream() + .map(ih -> itemMapper.toItemHistoryDto(ih, itemMapper.toItemDto(ih.getItem()))) + .collect(Collectors.toList()); + return itemMapper.toItemHistoryPaginationDto(result, itemHistories.getTotalElements()); + } + + @Transactional(readOnly = true) + public CoinHistoryPaginationDto getCoinHistory(Long userId, CoinHistoryType type, + Pageable pageable) { + + Set items = new HashSet<>(); + if (type.equals(CoinHistoryType.EARN) || type.equals(CoinHistoryType.ALL)) { + items.addAll(itemQueryService.getEarnItemIds()); + } + if (type.equals(CoinHistoryType.USE) || type.equals(CoinHistoryType.ALL)) { + items.addAll(itemQueryService.getUseItemIds()); + } + List itemIds = items.stream().map(Item::getId).collect(Collectors.toList()); + Page coinHistories = + itemHistoryQueryService.findCoinHistory(userId, pageable, itemIds); + + Map itemMap = items.stream() + .collect(Collectors.toMap(Item::getId, item -> item)); + List result = coinHistories.stream() + .map(ih -> itemMapper.toCoinHistoryDto(ih, itemMap.get(ih.getItemId()))) + .sorted(Comparator.comparing(CoinHistoryDto::getDate, Comparator.reverseOrder())) + .collect(Collectors.toList()); + return itemMapper.toCoinHistoryPaginationDto(result, coinHistories.getTotalElements()); + } + + /** + * itemRedisService 를 통해 동전 줍기 정보 생성 + * + * @param userId redis 의 고유 key 를 만들 userId + * @return 동전 줍기 정보 + */ + @Transactional(readOnly = true) + public CoinMonthlyCollectionDto getCoinCollectionCountInMonth(Long userId) { + Long coinCollectionCountInMonth = + itemRedisService.getCoinCollectionCountInMonth(userId); + boolean isCollectedInToday = itemRedisService.isCoinCollected(userId); + + return itemMapper.toCoinMonthlyCollectionDto(coinCollectionCountInMonth, + isCollectedInToday); + } + + /** + * 당일 중복해서 동전줍기를 요청했는지 검수 후 + *

+ * 당일 동전 줍기 체크 및 한 달 동전줍기 횟수 증가 + *

+ * 당일 동전 줍기 리워드 지급 및 지정된 출석 일수 달성 시 랜덤 리워드 지급 + * + * @param userId redis 의 고유 key 를 만들 userId + */ + @Transactional + public CoinCollectionRewardResponseDto collectCoinAndIssueReward(Long userId) { + + // 코인 줍기 횟수 (당일, 한 달) 갱신 + boolean isChecked = itemRedisService.isCoinCollected(userId); + itemPolicyService.verifyIsAlreadyCollectedCoin(isChecked); + itemRedisService.collectCoin(userId); + + // DB에 코인 저장 + Item coinCollect = itemQueryService.getBySku(Sku.COIN_COLLECT); + int reward = (int) (coinCollect.getPrice().longValue()); + itemHistoryCommandService.createItemHistory(userId, coinCollect.getId()); + + // 출석 일자에 따른 랜덤 리워드 지급 + Long coinCollectionCountInMonth = + itemRedisService.getCoinCollectionCountInMonth(userId); + if (itemPolicyService.isRewardable(coinCollectionCountInMonth)) { + ThreadLocalRandom random = ThreadLocalRandom.current(); + int randomPercentage = random.nextInt(100); + Sku coinSku = itemPolicyService.getRewardSku(randomPercentage); + Item coinReward = itemQueryService.getBySku(coinSku); + + itemHistoryCommandService.createItemHistory(userId, coinReward.getId()); + reward += coinReward.getPrice(); + } + + // Redis에 코인 변화량 저장 + saveCoinChangeOnRedis(userId, reward); + + return new CoinCollectionRewardResponseDto(reward); + } + + private void saveCoinChangeOnRedis(Long userId, final int reward) { + LockUtil.lockRedisCoin(userId, () -> { + // Redis에 유저 리워드 저장 + long coins = itemRedisService.getCoinCount(userId); + itemRedisService.saveCoinCount(userId, coins + reward); + + // Redis에 전체 코인 발행량 저장 + long totalCoinSupply = itemRedisService.getTotalCoinSupply(); + itemRedisService.saveTotalCoinSupply(totalCoinSupply + reward); + }); + } + + + /** + * 아이템 사용 + * + * @param userId 사용자 아이디 + * @param sku 아이템 sku + * @param data 아이템 사용 요청 데이터 + */ + @Transactional + public void useItem(Long userId, Sku sku, ItemUseRequestDto data) { + itemPolicyService.verifyDataFieldBySku(sku, data); + User user = userQueryService.getUser(userId); + if (user.isBlackholed()) { + // 이벤트를 발생시켰는데 동기로직이다..? + // TODO: 근데 그 이벤트가 뭘 하는지 이 코드 흐름에서는 알 수 없다..? + eventPublisher.publishEvent(UserBlackHoleEvent.of(user)); + } + Item item = itemQueryService.getBySku(sku); + List itemInInventory = + itemHistoryQueryService.findUnusedItemsInUserInventory(user.getId(), item.getId()); + ItemHistory oldestItemHistory = + itemPolicyService.verifyNotEmptyAndFindOldest(itemInInventory); + ItemUsage itemUsage = getItemUsage(userId, item, data); + + eventPublisher.publishEvent(itemUsage); + oldestItemHistory.updateUsedAt(); + } + + /** + * itemType 에 따른 구현체 반환 + * + * @param userId 사용자 아이디 + * @param item 아이템 + * @param data 아이템 사용 요청 데이터 + * @return 아이템 사용 구현체 + */ + private ItemUsage getItemUsage(Long userId, Item item, ItemUseRequestDto data) { + // 연장권, 이사권, 페널티 + if (item.getType().equals(ItemType.SWAP)) { + return new SwapItem(userId, data.getNewCabinetId()); + } + if (item.getType().equals(ItemType.EXTENSION)) { + return new ExtensionItem(userId, item.getSku().getDays()); + } + if (item.getType().equals(ItemType.PENALTY)) { + return new PenaltyItem(userId, item.getSku().getDays()); + } + if (item.getType().equals(ItemType.ALARM)) { + CabinetPlace cabinetPlaceInfo = cabinetQueryService.getCabinetPlaceInfoByLocation( + data.getBuilding(), data.getFloor(), data.getSection()); + return new AlarmItem(userId, cabinetPlaceInfo.getId()); + } + throw ExceptionStatus.NOT_FOUND_ITEM.asServiceException(); + } + + /** + * user가 아이템 구매 요청 + * + * @param userId 사용자 아이디 + * @param sku 아이템 sku + */ + @Transactional + public void purchaseItem(Long userId, Sku sku) { + // 유저가 블랙홀인지 확인 + User user = userQueryService.getUser(userId); + if (user.isBlackholed()) { + eventPublisher.publishEvent(UserBlackHoleEvent.of(user)); + } + + Item item = itemQueryService.getBySku(sku); + long price = item.getPrice(); + long userCoin = itemRedisService.getCoinCount(userId); + + // 아이템 Policy 검증 + itemPolicyService.verifyOnSale(price); + itemPolicyService.verifyIsAffordable(userCoin, price); + + // 아이템 구매 처리 + itemHistoryCommandService.createItemHistory(user.getId(), item.getId()); + + LockUtil.lockRedisCoin(userId, () -> { + // 코인 차감 + itemRedisService.saveCoinCount(userId, userCoin + price); + + // 전체 코인 사용량 저장 + long totalCoinUsage = itemRedisService.getTotalCoinUsage(); + itemRedisService.saveTotalCoinUsage(totalCoinUsage + price); + }); + } + + @Transactional + public void addSectionAlarm(Long userId, Long cabinetPlaceId) { + sectionAlarmCommandService.addSectionAlarm(userId, cabinetPlaceId); + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/item/service/ItemHistoryCommandService.java b/backend/src/main/java/org/ftclub/cabinet/item/service/ItemHistoryCommandService.java new file mode 100644 index 000000000..e375a8518 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/item/service/ItemHistoryCommandService.java @@ -0,0 +1,31 @@ +package org.ftclub.cabinet.item.service; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import org.ftclub.cabinet.item.domain.ItemHistory; +import org.ftclub.cabinet.item.repository.ItemHistoryRepository; +import org.ftclub.cabinet.log.LogLevel; +import org.ftclub.cabinet.log.Logging; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +@Logging(level = LogLevel.DEBUG) +public class ItemHistoryCommandService { + + private final ItemHistoryRepository itemHistoryRepository; + + public void createItemHistory(Long userId, Long itemId) { + ItemHistory itemHistory = ItemHistory.of(userId, itemId, null); + itemHistoryRepository.save(itemHistory); + } + + public void createItemHistories(List userIds, Long itemId, LocalDateTime usedAt) { + List itemHistories = userIds.stream() + .map(userId -> ItemHistory.of(userId, itemId, usedAt)) + .collect(Collectors.toList()); + itemHistoryRepository.saveAll(itemHistories); + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/item/service/ItemHistoryQueryService.java b/backend/src/main/java/org/ftclub/cabinet/item/service/ItemHistoryQueryService.java new file mode 100644 index 000000000..4409404d6 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/item/service/ItemHistoryQueryService.java @@ -0,0 +1,55 @@ +package org.ftclub.cabinet.item.service; + +import java.time.LocalDate; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.ftclub.cabinet.item.domain.ItemHistory; +import org.ftclub.cabinet.item.repository.ItemHistoryRepository; +import org.ftclub.cabinet.log.LogLevel; +import org.ftclub.cabinet.log.Logging; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +@Logging(level = LogLevel.DEBUG) +public class ItemHistoryQueryService { + + private final ItemHistoryRepository itemHistoryRepository; + + public List findAllItemHistoryByUser(Long userId) { + return itemHistoryRepository.getAllUnusedItemHistoryByUser(userId); + } + + public Page findItemHistoryWithItem(Long userId, Pageable pageable) { + return itemHistoryRepository.findAllByUserIdOnMinusPriceItemsWithSubQuery(userId, pageable); + } + + public Page findItemHistoriesByUserIdWithItem(Long userId, Pageable pageable) { + return itemHistoryRepository.findAllByUserIdOrderByPurchaseAtDesc(userId, pageable); + } + + public Page findCoinHistory(Long userId, Pageable pageable, List itemIds) { + return itemHistoryRepository.findAllByUserIdAndItemIdIn(userId, pageable, itemIds); + } + + public List findUnusedItemsInUserInventory(Long userId, Long itemId) { + return itemHistoryRepository.findAllByUserIdAndItemIdAndUsedAtIsNull(userId, itemId); + } + + public int findPurchaseCountByItemId(Long itemId) { + return itemHistoryRepository.getCountByItemIds(itemId); + } + + public List findUsedCoinHistoryBetween( + LocalDate startDate, + LocalDate endDate) { + return itemHistoryRepository.findAllUsedAtIsNotNullBetween(startDate, endDate); + } + + public List findCoinCollectedInfoByMonth(Long itemId, Integer year, + Integer month) { + return itemHistoryRepository.findCoinCollectInfoByIdAtYearAndMonth(itemId, year, month); + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/item/service/ItemPolicyService.java b/backend/src/main/java/org/ftclub/cabinet/item/service/ItemPolicyService.java new file mode 100644 index 000000000..f27d4bf75 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/item/service/ItemPolicyService.java @@ -0,0 +1,67 @@ +package org.ftclub.cabinet.item.service; + +import java.util.Comparator; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.ftclub.cabinet.dto.ItemUseRequestDto; +import org.ftclub.cabinet.exception.ExceptionStatus; +import org.ftclub.cabinet.item.domain.ItemHistory; +import org.ftclub.cabinet.item.domain.Sku; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class ItemPolicyService { + + private static final Long REWARD_COUNT = 20L; + + public void verifyIsAffordable(long userCoin, long itemPrice) { + if (userCoin <= 0 || userCoin < -itemPrice) { + throw ExceptionStatus.NOT_ENOUGH_COIN.asServiceException(); + } + } + + public void verifyIsAlreadyCollectedCoin(boolean isChecked) { + if (isChecked) { + throw ExceptionStatus.COIN_COLLECTION_ALREADY_EXIST.asServiceException(); + } + } + + public ItemHistory verifyNotEmptyAndFindOldest(List itemInInventory) { + return itemInInventory.stream() + .min(Comparator.comparing(ItemHistory::getPurchaseAt)) + .orElseThrow(ExceptionStatus.ITEM_NOT_OWNED::asServiceException); + } + + public void verifyOnSale(long price) { + if (price >= 0) { + throw ExceptionStatus.ITEM_NOT_ON_SALE.asServiceException(); + } + } + + public void verifyDataFieldBySku(Sku sku, ItemUseRequestDto data) { + if (sku.equals(Sku.ALARM) && (data.getBuilding() == null || data.getFloor() == null + || data.getSection() == null)) { + throw ExceptionStatus.INVALID_ITEM_USE_REQUEST.asServiceException(); + } + if (sku.equals(Sku.SWAP) && data.getNewCabinetId() == null) { + throw ExceptionStatus.INVALID_ITEM_USE_REQUEST.asServiceException(); + } + } + + public boolean isRewardable(Long monthlyCoinCount) { + return monthlyCoinCount.equals(REWARD_COUNT); + } + + public Sku getRewardSku(int randomPercentage) { + if (randomPercentage < 50) { + return Sku.COIN_REWARD_200; + } else if (randomPercentage < 80) { + return Sku.COIN_REWARD_500; + } else if (randomPercentage < 95) { + return Sku.COIN_REWARD_1000; + } else { + return Sku.COIN_REWARD_2000; + } + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/item/service/ItemQueryService.java b/backend/src/main/java/org/ftclub/cabinet/item/service/ItemQueryService.java new file mode 100644 index 000000000..3a746a767 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/item/service/ItemQueryService.java @@ -0,0 +1,37 @@ +package org.ftclub.cabinet.item.service; + +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.ftclub.cabinet.exception.ExceptionStatus; +import org.ftclub.cabinet.item.domain.Item; +import org.ftclub.cabinet.item.domain.Sku; +import org.ftclub.cabinet.item.repository.ItemRepository; +import org.ftclub.cabinet.log.LogLevel; +import org.ftclub.cabinet.log.Logging; +import org.springframework.stereotype.Service; + + +@Service +@RequiredArgsConstructor +@Logging(level = LogLevel.DEBUG) +public class ItemQueryService { + + private final ItemRepository itemRepository; + + public List getAllItems() { + return itemRepository.findAll(); + } + + public List getEarnItemIds() { + return itemRepository.findAllByPricePositive(); + } + + public List getUseItemIds() { + return itemRepository.findAllByPriceNegative(); + } + + public Item getBySku(Sku sku) { + return itemRepository.findBySku(sku) + .orElseThrow(ExceptionStatus.NOT_FOUND_ITEM::asServiceException); + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/item/service/ItemRedisService.java b/backend/src/main/java/org/ftclub/cabinet/item/service/ItemRedisService.java new file mode 100644 index 000000000..b7487a74b --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/item/service/ItemRedisService.java @@ -0,0 +1,120 @@ +package org.ftclub.cabinet.item.service; + +import lombok.RequiredArgsConstructor; +import org.ftclub.cabinet.item.repository.ItemHistoryRepository; +import org.ftclub.cabinet.item.repository.ItemRedis; +import org.ftclub.cabinet.log.LogLevel; +import org.ftclub.cabinet.log.Logging; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +@Logging(level = LogLevel.DEBUG) +public class ItemRedisService { + + private final ItemRedis itemRedis; + private final ItemHistoryRepository itemHistoryRepository; + + public long getCoinCount(Long userId) { + String userIdString = userId.toString(); + String coinCount = itemRedis.getCoinAmount(userIdString); + if (coinCount == null) { + long coin = itemHistoryRepository.findAllByUserId(userId).stream() + .mapToLong(ih -> ih.getItem().getPrice()) + .reduce(Long::sum).orElse(0L); + itemRedis.saveCoinAmount(userIdString, Long.toString(coin)); + return coin; + } + return Integer.parseInt(coinCount); + } + + public void saveCoinCount(Long userId, long coinCount) { + itemRedis.saveCoinAmount(userId.toString(), String.valueOf(coinCount)); + } + + public boolean isCoinCollected(Long userId) { + return itemRedis.isCoinCollected(userId.toString()); + } + + /** + * 전체 기간 동전 발행량 반환 + * + * @return 전체 기간 동전 발행량 + */ + public long getTotalCoinSupply() { + String totalCoinSupply = itemRedis.getTotalCoinSupply(); + if (totalCoinSupply == null) { + Long coin = itemHistoryRepository.getPriceSumOnPlusPriceItems(); + if (coin == null) { + coin = 0L; + } + itemRedis.saveTotalCoinSupply(Long.toString(coin)); + return coin; + } + return Long.parseLong(totalCoinSupply); + } + + /** + * 전체 기간 동전 사용량 반환 + * + * @return 전체 기간 동전 사용량 + */ + public long getTotalCoinUsage() { + String totalCoinUsage = itemRedis.getTotalcoinUsage(); + if (totalCoinUsage == null) { + Long coin = itemHistoryRepository.getPriceSumOnMinusPriceItems(); + if (coin == null) { + coin = 0L; + } + itemRedis.saveTotalCoinUsage(Long.toString(coin)); + return coin; + } + return Long.parseLong(totalCoinUsage); + } + + /** + * 전체 기간 동전 발행량 저장 + * + * @param coin 동전 발행량 + */ + public void saveTotalCoinSupply(long coin) { + itemRedis.saveTotalCoinSupply(String.valueOf(coin)); + } + + /** + * 전체 기간 동전 사용량 저장 + * + * @param coin 동전 사용량 + */ + public void saveTotalCoinUsage(long coin) { + itemRedis.saveTotalCoinUsage(String.valueOf(coin)); + } + + /** + * 당일 동전 줍기 처리 및 한 달 동안 유지되는 동전 줍기 횟수 증가 기능 + * + * @param userId 유저 아이디 + */ + public void collectCoin(Long userId) { + itemRedis.collectCoin(userId.toString()); + itemRedis.addCoinCollectionCount(userId.toString()); + } + + /** + * userId를 문자열로 변경하여 redis 내에서 조회 + *

+ * 존재하지 않는다면 캐싱 처리 없이 0을 반환, 존재한다면 Long 으로 변환하여 반환합니다. + * + * @param userId 유저 아이디 + * @return 동전 줍기 횟수 + */ + public Long getCoinCollectionCountInMonth(Long userId) { + String userIdToString = userId.toString(); + String coinCollectionCount = itemRedis.getCoinCollectionCount(userIdToString); + + if (coinCollectionCount == null) { + return 0L; + } + return Long.parseLong(coinCollectionCount); + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/item/service/SectionAlarmCommandService.java b/backend/src/main/java/org/ftclub/cabinet/item/service/SectionAlarmCommandService.java new file mode 100644 index 000000000..e6772ad8b --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/item/service/SectionAlarmCommandService.java @@ -0,0 +1,28 @@ +package org.ftclub.cabinet.item.service; + +import java.time.LocalDateTime; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.ftclub.cabinet.item.domain.SectionAlarm; +import org.ftclub.cabinet.item.repository.SectionAlarmRepository; +import org.ftclub.cabinet.log.LogLevel; +import org.ftclub.cabinet.log.Logging; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +@Logging(level = LogLevel.DEBUG) +public class SectionAlarmCommandService { + + private final SectionAlarmRepository sectionAlarmRepository; + + + public void addSectionAlarm(Long userId, Long cabinetPlaceId) { + SectionAlarm sectionAlarm = SectionAlarm.of(userId, cabinetPlaceId); + sectionAlarmRepository.save(sectionAlarm); + } + + public void updateAlarmSend(List sectionAlarmIds, LocalDateTime sentDate) { + sectionAlarmRepository.updateAlarmedAtBulk(sectionAlarmIds, sentDate); + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/item/service/SectionAlarmQueryService.java b/backend/src/main/java/org/ftclub/cabinet/item/service/SectionAlarmQueryService.java new file mode 100644 index 000000000..131370b8c --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/item/service/SectionAlarmQueryService.java @@ -0,0 +1,27 @@ +package org.ftclub.cabinet.item.service; + +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.ftclub.cabinet.item.domain.SectionAlarm; +import org.ftclub.cabinet.item.repository.SectionAlarmRepository; +import org.ftclub.cabinet.log.LogLevel; +import org.ftclub.cabinet.log.Logging; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +@Logging(level = LogLevel.DEBUG) +public class SectionAlarmQueryService { + + private final SectionAlarmRepository sectionAlarmRepository; + + + public List getUnsentAlarms() { + return sectionAlarmRepository.findAllByAlarmedAtIsNull(); + } + + public List getUnsentAlarm(Long userId, String building, Integer floor) { + return sectionAlarmRepository.findAllByUserIdAndCabinetPlaceAndAlarmedAtIsNull( + userId, building, floor); + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/domain/LentPolicyStatus.java b/backend/src/main/java/org/ftclub/cabinet/lent/domain/LentPolicyStatus.java index 2cc8af857..947fd636b 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/domain/LentPolicyStatus.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/domain/LentPolicyStatus.java @@ -3,8 +3,7 @@ /** * 대여 상태 */ -public enum -LentPolicyStatus { +public enum LentPolicyStatus { /** * 빌릴 수 있음 */ @@ -56,6 +55,8 @@ BLACKHOLED_USER, SWAP_EXPIREDAT_IMMINENT, INVALID_LENT_TYPE, INVALID_ARGUMENT, - INVALID_EXPIREDAT, SWAP_SAME_CABINET, SWAP_LIMIT_EXCEEDED, LENT_NOT_CLUB, + INVALID_EXPIREDAT, SWAP_SAME_CABINET, LENT_NOT_CLUB, + + SWAP_LIMIT_EXCEEDED } \ No newline at end of file diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRedis.java b/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRedis.java index c335511ca..d623372c0 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRedis.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRedis.java @@ -28,14 +28,18 @@ public class LentRedis { private static final String SHADOW_KEY_SUFFIX = ":shadow"; private static final String CABINET_KEY_SUFFIX = ":cabinetSession"; private static final String VALUE_KEY_SUFFIX = ":userSession"; + private static final String PREVIOUS_USER_SUFFIX = ":previousUser"; + private static final String PREVIOUS_ENDED_AT_SUFFIX = ":previousEndedAt"; private static final String SWAP_KEY_SUFFIX = ":swap"; private final HashOperations shareCabinetTemplate; private final ValueOperations userCabinetTemplate; private final RedisTemplate shadowKeyTemplate; //조금 더 많은 기능을 지원 - private final ValueOperations previousUserTemplate; // 조회랑 생성 한정 기능 + + private final ValueOperations previousTemplate; // 조회랑 생성 한정 기능 + private final RedisTemplate swapTemplate; private final CabinetProperties cabinetProperties; @@ -44,13 +48,13 @@ public class LentRedis { public LentRedis(RedisTemplate valueHashRedisTemplate, RedisTemplate valueRedisTemplate, RedisTemplate shadowKeyTemplate, - RedisTemplate previousUserTemplate, + RedisTemplate previousTemplate, RedisTemplate swapTemplate, CabinetProperties cabinetProperties) { this.userCabinetTemplate = valueRedisTemplate.opsForValue(); this.shareCabinetTemplate = valueHashRedisTemplate.opsForHash(); this.shadowKeyTemplate = shadowKeyTemplate; - this.previousUserTemplate = previousUserTemplate.opsForValue(); + this.previousTemplate = previousTemplate.opsForValue(); this.swapTemplate = swapTemplate; this.cabinetProperties = cabinetProperties; } @@ -238,7 +242,7 @@ public LocalDateTime getCabinetExpiredAt(String cabinetId) { * @param userName 유저 이름 */ public void setPreviousUserName(String cabinetId, String userName) { - previousUserTemplate.set(cabinetId + PREVIOUS_USER_SUFFIX, userName); + previousTemplate.set(cabinetId + PREVIOUS_USER_SUFFIX, userName); } /** @@ -248,30 +252,38 @@ public void setPreviousUserName(String cabinetId, String userName) { * @return 유저 이름 */ public String getPreviousUserName(String cabinetId) { - return previousUserTemplate.get(cabinetId + PREVIOUS_USER_SUFFIX); + return previousTemplate.get(cabinetId + PREVIOUS_USER_SUFFIX); } - /*---------------------------------------- Swap -----------------------------------------*/ - /** - * swap 하려는 유저가 이전에 swap 한 이력의 여부를 조회합니다. + * 특정 사물함에 대한 이전 대여 종료 시각을 설정합니다. * - * @param userId 유저 ID - * @return true or false + * @param cabinetId 사물함 id + * @param endedAt 종료 시각 */ - public boolean isExistPreviousSwap(String userId) { - Boolean isExist = swapTemplate.hasKey(userId + SWAP_KEY_SUFFIX); - return Objects.nonNull(isExist) && isExist; + public void setPreviousEndedAt(String cabinetId, String endedAt) { + previousTemplate.set(cabinetId + PREVIOUS_ENDED_AT_SUFFIX, endedAt); } /** - * 유저가 swap 가능한 시각을 조회합니다. + * 특정 사물함에 대한 이전 대여 종료 시각을 가져옵니다. * - * @param userId 유저 ID - * @return swap 가능한 시각 + * @param cabinetId 사물함 id + * @return 종료 시각 */ - public LocalDateTime getSwapExpiredTime(String userId) { - if (!this.isExistPreviousSwap(userId)) { + public String getPreviousEndedAt(String cabinetId) { + return previousTemplate.get(cabinetId + PREVIOUS_ENDED_AT_SUFFIX); + } + + /*-----------------------------------SWAP-----------------------------------*/ + + public boolean isExistSwapRecord(String userId) { + Boolean isExist = swapTemplate.hasKey(userId + SWAP_KEY_SUFFIX); + return Objects.nonNull(isExist) && isExist; + } + + public LocalDateTime getSwapExpiredAt(String userId) { + if (!this.isExistSwapRecord(userId)) { return null; } String swapKey = userId + SWAP_KEY_SUFFIX; @@ -280,11 +292,6 @@ public LocalDateTime getSwapExpiredTime(String userId) { return LocalDateTime.now().plusSeconds(expire); } - /** - * swap 하는 유저의 swap 이력을 저장합니다. 기한을 설정합니다 - * - * @param userId 유저 ID - */ public void setSwap(String userId) { final String swapKey = userId + SWAP_KEY_SUFFIX; swapTemplate.opsForValue().set(swapKey, USER_SWAPPED); diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java b/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java index b4ba806bc..5063ad871 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java @@ -174,7 +174,8 @@ int countReturnFromStartDateToEndDate(@Param("startDate") LocalDateTime startDat + "LEFT JOIN FETCH lh.user u " + "LEFT JOIN FETCH lh.cabinet c " + "LEFT JOIN FETCH c.cabinetPlace cp " - + "WHERE lh.cabinetId = :cabinetId ", + + "WHERE lh.cabinetId = :cabinetId " + + "ORDER BY lh.startedAt DESC", countQuery = "SELECT count(lh) " + "FROM LentHistory lh " + "WHERE lh.cabinetId = :cabinetId ") @@ -255,8 +256,8 @@ List findAllByCabinetIdsEndedAtEqualDate(@Param("date") LocalDate d * cabinet 정보를 Join하여 가져옵니다. *

* - * @param userId - * @return + * @param userId 찾으려는 user id + * @return 반납하지 않은 {@link LentHistory}의 {@link Optional} */ @Query("SELECT lh " + "FROM LentHistory lh " @@ -265,6 +266,8 @@ List findAllByCabinetIdsEndedAtEqualDate(@Param("date") LocalDate d + "WHERE lh.userId = :userId AND lh.endedAt IS NULL") Optional findByUserIdAndEndedAtIsNullJoinCabinet(@Param("userId") Long userId); + Optional findFirstByCabinetIdOrderByEndedAtDesc(Long cabinetId); + /** * 특정 유저들의 아직 반납하지 않은 대여 기록을 가져옵니다. *

diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java index 9050ea7a7..62a615afd 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java @@ -31,10 +31,13 @@ import org.ftclub.cabinet.mapper.LentMapper; import org.ftclub.cabinet.user.domain.BanHistory; import org.ftclub.cabinet.user.domain.BanType; +import org.ftclub.cabinet.user.domain.LentExtension; +import org.ftclub.cabinet.user.domain.LentExtensionType; import org.ftclub.cabinet.user.domain.User; import org.ftclub.cabinet.user.service.BanHistoryCommandService; import org.ftclub.cabinet.user.service.BanHistoryQueryService; import org.ftclub.cabinet.user.service.BanPolicyService; +import org.ftclub.cabinet.user.service.LentExtensionCommandService; import org.ftclub.cabinet.user.service.UserQueryService; import org.springframework.context.ApplicationEventPublisher; import org.springframework.data.domain.Page; @@ -65,6 +68,7 @@ public class LentFacadeService { private final LentMapper lentMapper; private final CabinetMapper cabinetMapper; + private final LentExtensionCommandService lentExtensionCommandService; /** @@ -418,4 +422,25 @@ public void swapPrivateCabinet(Long userId, Long newCabinetId) { lentRedisService.setSwapRecord(userId); } + + /** + * 아이템 사용 로직 + *

+ * 연체중이지 않고, 대여중인 상태라면 일자만큼 연장 수치를 늘려줍니다. + *

+ * 살 때 발급시키고 쓸 때 사용하는게 맞나, 쓸 때 바로 발급시키고 사용하는게 맞나.. + * + * @param userId + * @param days + */ + @Transactional + public void plusExtensionDays(Long userId, Integer days) { + Cabinet cabinet = cabinetQueryService.getUserActiveCabinet(userId); + + LentExtension lentExtensionByItem = lentExtensionCommandService.createLentExtensionByItem( + userId, LentExtensionType.ALL, days); + List lentHistories = lentQueryService.findCabinetActiveLentHistories( + cabinet.getId()); + lentExtensionCommandService.useLentExtension(lentExtensionByItem, lentHistories); + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentPolicyService.java b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentPolicyService.java index eeaa438f5..7fe2abbbb 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentPolicyService.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentPolicyService.java @@ -63,11 +63,6 @@ private void handlePolicyStatus(LentPolicyStatus status, LocalDateTime policyDat DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")); throw new CustomExceptionStatus(ExceptionStatus.SHARE_CODE_TRIAL_EXCEEDED, unbannedAtString).asCustomServiceException(); - case SWAP_LIMIT_EXCEEDED: - unbannedAtString = policyDate.format( - DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")); - throw new CustomExceptionStatus(ExceptionStatus.SWAP_LIMIT_EXCEEDED, - unbannedAtString).asCustomServiceException(); case BLACKHOLED_USER: throw ExceptionStatus.BLACKHOLED_USER.asServiceException(); case PENDING_CABINET: @@ -80,6 +75,11 @@ private void handlePolicyStatus(LentPolicyStatus status, LocalDateTime policyDat throw ExceptionStatus.INVALID_ARGUMENT.asServiceException(); case SWAP_SAME_CABINET: throw ExceptionStatus.SWAP_SAME_CABINET.asServiceException(); + case SWAP_LIMIT_EXCEEDED: + unbannedAtString = policyDate.format( + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")); + throw new CustomExceptionStatus(ExceptionStatus.SWAP_LIMIT_EXCEEDED, + unbannedAtString).asCustomServiceException(); case NOT_USER: case INTERNAL_ERROR: default: diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentQueryService.java b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentQueryService.java index 442d123a2..3a594f1e5 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentQueryService.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentQueryService.java @@ -193,7 +193,8 @@ public List findOverdueLentHistories(LocalDateTime now, Pageable pa * @param cabinetIds 찾으려는 cabinet id {@link List} * @return 기준 날짜보다 반납 기한이 나중인 대여 기록 {@link List} */ - public List findPendingLentHistoriesOnDate(LocalDate date, List cabinetIds) { + public List findAvailableLentHistoriesOnDate(LocalDate date, + List cabinetIds) { return lentRepository.findAllByCabinetIdsEndedAtEqualDate(date, cabinetIds); } diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentRedisService.java b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentRedisService.java index 1d019f73d..b65a4c2c0 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentRedisService.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentRedisService.java @@ -1,9 +1,11 @@ package org.ftclub.cabinet.lent.service; import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; import java.util.Comparator; import java.util.List; import java.util.Objects; +import java.util.Optional; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import org.ftclub.cabinet.config.CabinetProperties; @@ -225,31 +227,48 @@ public void setPreviousUserName(Long cabinetId, String userName) { } /** - * 정책 기한 이내에 swap 기록이 있는지 확인합니다. + * 사물함의 이전 대여 종료 시간을 가져옵니다. * - * @param userId 유저 이름 - * @return + * @param cabinetId 사물함 id + * @return 이전 대여 종료 시간 */ - public boolean isExistSwapRecord(Long userId) { - return lentRedis.isExistPreviousSwap(String.valueOf(userId)); + public LocalDateTime getPreviousEndedAt(Long cabinetId) { + LocalDateTime previousEndedAt; + String previousEndedAtString = lentRedis.getPreviousEndedAt(cabinetId.toString()); + if (Objects.isNull(previousEndedAtString)) { + Optional cabinetLastLentHistory = + lentRepository.findFirstByCabinetIdOrderByEndedAtDesc(cabinetId); + previousEndedAt = cabinetLastLentHistory.map(LentHistory::getEndedAt).orElse(null); + if (Objects.nonNull(previousEndedAt)) { + lentRedis.setPreviousEndedAt(cabinetId.toString(), previousEndedAt.toString()); + } + } else { + DateTimeFormatter dateFormatter = + DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSSSS"); + previousEndedAt = LocalDateTime.parse(previousEndedAtString, dateFormatter); + } + return previousEndedAt; } /** - * swap에 성공한 유저를 등록합니다. + * 사물함의 이전 대여 종료 시간을 설정합니다. * - * @param userId swap 기능을 사용한 유저 이름 + * @param cabinetId 사물함 id + * @param endedAt 이전 대여 종료 시간 */ - public void setSwapRecord(Long userId) { - lentRedis.setSwap(String.valueOf(userId)); + public void setPreviousEndedAt(Long cabinetId, LocalDateTime endedAt) { + lentRedis.setPreviousEndedAt(cabinetId.toString(), endedAt.toString()); } - /** - * swap 기능이 사용가능한 시간을 리턴합니다. - * - * @param userId 유저 이름 - * @return 사용 가능한 시간 - */ public LocalDateTime getSwapExpiredAt(Long userId) { - return lentRedis.getSwapExpiredTime(String.valueOf(userId)); + return lentRedis.getSwapExpiredAt(String.valueOf(userId)); + } + + public boolean isExistSwapRecord(Long userId) { + return lentRedis.isExistSwapRecord(String.valueOf(userId)); + } + + public void setSwapRecord(Long userId) { + lentRedis.setSwap(String.valueOf(userId)); } } diff --git a/backend/src/main/java/org/ftclub/cabinet/mapper/CabinetMapper.java b/backend/src/main/java/org/ftclub/cabinet/mapper/CabinetMapper.java index da0075454..d2c61098d 100644 --- a/backend/src/main/java/org/ftclub/cabinet/mapper/CabinetMapper.java +++ b/backend/src/main/java/org/ftclub/cabinet/mapper/CabinetMapper.java @@ -1,7 +1,32 @@ package org.ftclub.cabinet.mapper; +import static org.mapstruct.NullValueMappingStrategy.RETURN_DEFAULT; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; import org.ftclub.cabinet.cabinet.domain.Cabinet; -import org.ftclub.cabinet.dto.*; +import org.ftclub.cabinet.dto.ActiveCabinetInfoDto; +import org.ftclub.cabinet.dto.ActiveCabinetInfoEntities; +import org.ftclub.cabinet.dto.BuildingFloorsDto; +import org.ftclub.cabinet.dto.CabinetDto; +import org.ftclub.cabinet.dto.CabinetFloorStatisticsResponseDto; +import org.ftclub.cabinet.dto.CabinetInfoPaginationDto; +import org.ftclub.cabinet.dto.CabinetInfoResponseDto; +import org.ftclub.cabinet.dto.CabinetPaginationDto; +import org.ftclub.cabinet.dto.CabinetPendingResponseDto; +import org.ftclub.cabinet.dto.CabinetPreviewDto; +import org.ftclub.cabinet.dto.CabinetSimpleDto; +import org.ftclub.cabinet.dto.CabinetSimplePaginationDto; +import org.ftclub.cabinet.dto.CabinetsPerSectionResponseDto; +import org.ftclub.cabinet.dto.LentDto; +import org.ftclub.cabinet.dto.LentsStatisticsResponseDto; +import org.ftclub.cabinet.dto.MyCabinetResponseDto; +import org.ftclub.cabinet.dto.OverdueUserCabinetDto; +import org.ftclub.cabinet.dto.OverdueUserCabinetPaginationDto; +import org.ftclub.cabinet.dto.UserBlockedInfoDto; +import org.ftclub.cabinet.dto.UserCabinetDto; +import org.ftclub.cabinet.dto.UserCabinetPaginationDto; import org.ftclub.cabinet.lent.domain.LentHistory; import org.ftclub.cabinet.user.domain.User; import org.mapstruct.Mapper; @@ -9,12 +34,6 @@ import org.mapstruct.factory.Mappers; import org.springframework.stereotype.Component; -import java.time.LocalDateTime; -import java.util.List; -import java.util.Map; - -import static org.mapstruct.NullValueMappingStrategy.RETURN_DEFAULT; - //@NullableMapper @Mapper(componentModel = "spring", nullValueMappingStrategy = RETURN_DEFAULT, @@ -34,7 +53,7 @@ public interface CabinetMapper { @Mapping(target = "cabinetId", source = "lentHistory.cabinetId") @Mapping(target = "location", source = "cabinet.cabinetPlace.location") OverdueUserCabinetDto toOverdueUserCabinetDto(LentHistory lentHistory, User user, - Cabinet cabinet, Long overdueDays); + Cabinet cabinet, Long overdueDays); UserCabinetDto toUserCabinetDto(UserBlockedInfoDto userInfo, CabinetDto cabinetInfo); @@ -46,40 +65,41 @@ OverdueUserCabinetDto toOverdueUserCabinetDto(LentHistory lentHistory, User user @Mapping(target = "userId", source = "lentHistory.userId") @Mapping(target = "location", source = "cabinet.cabinetPlace.location") ActiveCabinetInfoDto toActiveCabinetInfoDto(Cabinet cabinet, LentHistory lentHistory, - User user); + User user); @Mapping(target = "cabinet", source = "cabinet") @Mapping(target = "lentHistory", source = "lentHistory") @Mapping(target = "user", source = "user") ActiveCabinetInfoEntities toActiveCabinetInfoEntitiesDto(Cabinet cabinet, - LentHistory lentHistory, User user); + LentHistory lentHistory, User user); /*--------------------------------Wrapped DTO--------------------------------*/ //TO do : cabinetPlace러 바꾸기 CabinetsPerSectionResponseDto toCabinetsPerSectionResponseDto(String section, - List cabinets); + List cabinets, + boolean alarmRegistered); @Mapping(target = "cabinetId", source = "cabinet.id") @Mapping(target = "location", source = "cabinet.cabinetPlace.location") CabinetInfoResponseDto toCabinetInfoResponseDto(Cabinet cabinet, List lents, - LocalDateTime sessionExpiredAt); + LocalDateTime sessionExpiredAt); @Mapping(target = "totalLength", source = "totalLength") CabinetPaginationDto toCabinetPaginationDtoList(List result, - Long totalLength); + Long totalLength); OverdueUserCabinetPaginationDto toOverdueUserCabinetPaginationDto( List result, Long totalLength); UserCabinetPaginationDto toUserCabinetPaginationDto(List result, - Long totalLength); + Long totalLength); @Mapping(target = "cabinetId", source = "cabinet.id") @Mapping(target = "location", source = "cabinet.cabinetPlace.location") @Mapping(target = "shareCode", source = "sessionShareCode") MyCabinetResponseDto toMyCabinetResponseDto(Cabinet cabinet, List lents, - String sessionShareCode, LocalDateTime sessionExpiredAt, String previousUserName); + String sessionShareCode, LocalDateTime sessionExpiredAt, String previousUserName); @Mapping(target = "cabinetId", source = "cabinet.id") CabinetPreviewDto toCabinetPreviewDto(Cabinet cabinet, Integer userCount, String name); @@ -99,8 +119,8 @@ CabinetPendingResponseDto toCabinetPendingResponseDto( Map> cabinetInfoResponseDtos); CabinetFloorStatisticsResponseDto toCabinetFloorStatisticsResponseDto(Integer floor, - Integer total, Integer used, Integer overdue, Integer unused, Integer disabled); + Integer total, Integer used, Integer overdue, Integer unused, Integer disabled); LentsStatisticsResponseDto toLentsStatisticsResponseDto(LocalDateTime startDate, - LocalDateTime endDate, int lentStartCount, int lentEndCount); + LocalDateTime endDate, int lentStartCount, int lentEndCount); } diff --git a/backend/src/main/java/org/ftclub/cabinet/mapper/ItemMapper.java b/backend/src/main/java/org/ftclub/cabinet/mapper/ItemMapper.java new file mode 100644 index 000000000..71939ee98 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/mapper/ItemMapper.java @@ -0,0 +1,93 @@ +package org.ftclub.cabinet.mapper; + +import static org.mapstruct.NullValueMappingStrategy.RETURN_DEFAULT; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; +import org.ftclub.cabinet.admin.dto.AdminItemHistoryDto; +import org.ftclub.cabinet.dto.CoinAmountDto; +import org.ftclub.cabinet.dto.CoinHistoryDto; +import org.ftclub.cabinet.dto.CoinHistoryPaginationDto; +import org.ftclub.cabinet.dto.CoinMonthlyCollectionDto; +import org.ftclub.cabinet.dto.ItemAssignResponseDto; +import org.ftclub.cabinet.dto.ItemDetailsDto; +import org.ftclub.cabinet.dto.ItemDto; +import org.ftclub.cabinet.dto.ItemHistoryDto; +import org.ftclub.cabinet.dto.ItemHistoryPaginationDto; +import org.ftclub.cabinet.dto.ItemPurchaseCountDto; +import org.ftclub.cabinet.dto.ItemStoreDto; +import org.ftclub.cabinet.dto.MyItemResponseDto; +import org.ftclub.cabinet.item.domain.Item; +import org.ftclub.cabinet.item.domain.ItemHistory; +import org.ftclub.cabinet.item.domain.ItemType; +import org.ftclub.cabinet.item.domain.Sku; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; +import org.springframework.stereotype.Component; + +@Mapper(componentModel = "spring", + nullValueMappingStrategy = RETURN_DEFAULT, + nullValueMapMappingStrategy = RETURN_DEFAULT, + nullValueIterableMappingStrategy = RETURN_DEFAULT) +@Component +public interface ItemMapper { + + ItemMapper INSTANCE = Mappers.getMapper(ItemMapper.class); + + @Mapping(target = "date", source = "itemHistory.purchaseAt") + @Mapping(target = "amount", source = "item.price") + @Mapping(target = "history", source = "item.type.name") + @Mapping(target = "itemDetails", source = "item.sku.details") + CoinHistoryDto toCoinHistoryDto(ItemHistory itemHistory, Item item); + + @Mapping(target = "itemSku", source = "item.sku") + @Mapping(target = "itemName", source = "item.type.name") + @Mapping(target = "itemType", source = "item.type") + @Mapping(target = "itemPrice", source = "item.price") + @Mapping(target = "itemDetails", source = "item.sku.details") + ItemDto toItemDto(Item item); + + @Mapping(target = "itemName", source = "item.type.name") + @Mapping(target = "itemDetails", source = "item.sku.details") + AdminItemHistoryDto toAdminItemHistoryDto(ItemHistory itemHistory, Item item); + + @Mapping(target = "itemSku", source = "item.sku") + @Mapping(target = "itemPrice", source = "item.price") + @Mapping(target = "itemDetails", source = "item.sku.details") + ItemDetailsDto toItemDetailsDto(Item item); + + @Mapping(target = "date", source = "itemHistory.usedAt") + ItemHistoryDto toItemHistoryDto(ItemHistory itemHistory, ItemDto itemDto); + + + MyItemResponseDto toMyItemResponseDto(List extensionItems, List swapItems, + List alarmItems, List penaltyItems); + + @Mapping(target = "itemName", source = "itemType.name") + @Mapping(target = "itemType", source = "itemType") + @Mapping(target = "description", source = "itemType.description") + ItemStoreDto toItemStoreDto(ItemType itemType, List items); + + ItemHistoryPaginationDto toItemHistoryPaginationDto(List result, + Long totalLength); + + CoinHistoryPaginationDto toCoinHistoryPaginationDto(List result, + Long totalLength); + + CoinMonthlyCollectionDto toCoinMonthlyCollectionDto(Long monthlyCoinCount, + boolean todayCoinCollection); + + @Mapping(target = "itemSku", source = "sku") + @Mapping(target = "itemDetails", source = "sku.details") + @Mapping(target = "itemName", source = "itemType.name") + ItemAssignResponseDto toItemAssignResponseDto(Sku sku, ItemType itemType, + LocalDateTime issuedDate); + + @Mapping(target = "itemName", source = "item.type.name") + @Mapping(target = "itemDetails", source = "item.sku.details") + ItemPurchaseCountDto toItemPurchaseCountDto(Item item, int userCount); + + CoinAmountDto toCoinAmountDto(LocalDate date, Long amount); +} diff --git a/backend/src/main/java/org/ftclub/cabinet/mapper/UserMapper.java b/backend/src/main/java/org/ftclub/cabinet/mapper/UserMapper.java index f1f7fdd4e..e4720701c 100644 --- a/backend/src/main/java/org/ftclub/cabinet/mapper/UserMapper.java +++ b/backend/src/main/java/org/ftclub/cabinet/mapper/UserMapper.java @@ -1,8 +1,20 @@ package org.ftclub.cabinet.mapper; +import static org.mapstruct.NullValueMappingStrategy.RETURN_DEFAULT; +import static org.mapstruct.NullValueMappingStrategy.RETURN_NULL; + +import java.util.List; import org.ftclub.cabinet.alarm.dto.AlarmTypeResponseDto; import org.ftclub.cabinet.cabinet.domain.Cabinet; -import org.ftclub.cabinet.dto.*; +import org.ftclub.cabinet.dto.BlockedUserPaginationDto; +import org.ftclub.cabinet.dto.ClubUserListDto; +import org.ftclub.cabinet.dto.LentExtensionPaginationDto; +import org.ftclub.cabinet.dto.LentExtensionResponseDto; +import org.ftclub.cabinet.dto.MyProfileResponseDto; +import org.ftclub.cabinet.dto.UserBlockedInfoDto; +import org.ftclub.cabinet.dto.UserProfileDto; +import org.ftclub.cabinet.dto.UserProfilePaginationDto; +import org.ftclub.cabinet.dto.UserSessionDto; import org.ftclub.cabinet.user.domain.BanHistory; import org.ftclub.cabinet.user.domain.LentExtension; import org.ftclub.cabinet.user.domain.User; @@ -10,11 +22,6 @@ import org.mapstruct.Mapping; import org.springframework.stereotype.Component; -import java.util.List; - -import static org.mapstruct.NullValueMappingStrategy.RETURN_DEFAULT; -import static org.mapstruct.NullValueMappingStrategy.RETURN_NULL; - //@NullableMapper @Mapper(componentModel = "spring", nullValueMappingStrategy = RETURN_NULL, @@ -34,15 +41,16 @@ public interface UserMapper { @Mapping(target = "userId", source = "user.userId") @Mapping(target = "name", source = "user.name") @Mapping(target = "cabinetId", source = "cabinet.id") - MyProfileResponseDto toMyProfileResponseDto(UserSessionDto user, Cabinet cabinet, - BanHistory banHistory, LentExtensionResponseDto lentExtensionResponseDto, - AlarmTypeResponseDto alarmTypes, boolean isDeviceTokenExpired); + MyProfileResponseDto toMyProfileResponseDto(UserSessionDto user, + Cabinet cabinet, BanHistory banHistory, + LentExtensionResponseDto lentExtensionResponseDto, + AlarmTypeResponseDto alarmTypes, boolean isDeviceTokenExpired, Long coins); BlockedUserPaginationDto toBlockedUserPaginationDto(List result, - Long totalLength); + Long totalLength); UserProfilePaginationDto toUserProfilePaginationDto(List result, - Long totalLength); + Long totalLength); ClubUserListDto toClubUserListDto(List result, Long totalLength); @@ -50,5 +58,5 @@ UserProfilePaginationDto toUserProfilePaginationDto(List result, LentExtensionResponseDto toLentExtensionResponseDto(LentExtension lentExtension); LentExtensionPaginationDto toLentExtensionPaginationDto(List result, - Long totalLength); + Long totalLength); } diff --git a/backend/src/main/java/org/ftclub/cabinet/presentation/controller/PresentationController.java b/backend/src/main/java/org/ftclub/cabinet/presentation/controller/PresentationController.java index 971665968..3bb1902e3 100644 --- a/backend/src/main/java/org/ftclub/cabinet/presentation/controller/PresentationController.java +++ b/backend/src/main/java/org/ftclub/cabinet/presentation/controller/PresentationController.java @@ -34,9 +34,9 @@ public class PresentationController { @PostMapping("/form") @AuthGuard(level = AuthLevel.USER_ONLY) public void createPresentationForm( - @UserSession UserSessionDto user, - @Valid @RequestBody PresentationFormRequestDto dto) { - presentationService.createPresentationFrom(user.getUserId(), dto); + @UserSession UserSessionDto user, + @Valid @RequestBody PresentationFormRequestDto dto) { + presentationService.createPresentationForm(user.getUserId(), dto); } @GetMapping("/form/invalid-date") @@ -47,17 +47,17 @@ public InvalidDateResponseDto getInvalidDate() { @GetMapping("") @AuthGuard(level = AuthLevel.USER_ONLY) public PresentationMainData getMainData( - @RequestParam(value = "pastFormCount") Integer pastFormCount, - @RequestParam(value = "upcomingFormCount") Integer upcomingFormCount) { + @RequestParam(value = "pastFormCount") Integer pastFormCount, + @RequestParam(value = "upcomingFormCount") Integer upcomingFormCount) { return presentationService.getPastAndUpcomingPresentations(pastFormCount, - upcomingFormCount); + upcomingFormCount); } @GetMapping("/schedule") public PresentationFormResponseDto getPresentationSchedule( - @RequestParam(value = "yearMonth") - @DateTimeFormat(pattern = "yyyy-MM") - YearMonth yearMonth) { + @RequestParam(value = "yearMonth") + @DateTimeFormat(pattern = "yyyy-MM") + YearMonth yearMonth) { return presentationService.getUserPresentationSchedule(yearMonth); } @@ -71,8 +71,8 @@ public PresentationFormResponseDto getPresentationSchedule( @GetMapping("/me/histories") @AuthGuard(level = AuthLevel.USER_ONLY) public PresentationMyPagePaginationDto getUserPresentation( - @UserSession UserSessionDto user, - Pageable pageable + @UserSession UserSessionDto user, + Pageable pageable ) { return presentationService.getUserPresentations(user.getUserId(), pageable); } diff --git a/backend/src/main/java/org/ftclub/cabinet/presentation/service/PresentationService.java b/backend/src/main/java/org/ftclub/cabinet/presentation/service/PresentationService.java index 4c08f928f..0f1457070 100644 --- a/backend/src/main/java/org/ftclub/cabinet/presentation/service/PresentationService.java +++ b/backend/src/main/java/org/ftclub/cabinet/presentation/service/PresentationService.java @@ -18,7 +18,6 @@ import org.ftclub.cabinet.exception.ExceptionStatus; import org.ftclub.cabinet.mapper.PresentationMapper; import org.ftclub.cabinet.presentation.domain.Presentation; -import org.ftclub.cabinet.presentation.domain.PresentationLocation; import org.ftclub.cabinet.presentation.domain.PresentationStatus; import org.ftclub.cabinet.presentation.repository.PresentationRepository; import org.ftclub.cabinet.user.domain.User; @@ -54,13 +53,13 @@ public class PresentationService { * @param dto 신청서 작성에 필요한 정보들 */ @Transactional - public void createPresentationFrom(Long userId, PresentationFormRequestDto dto) { + public void createPresentationForm(Long userId, PresentationFormRequestDto dto) { presentationPolicyService.verifyReservationDate(dto.getDateTime()); Presentation presentation = - Presentation.of(dto.getCategory(), dto.getDateTime(), - dto.getPresentationTime(), dto.getSubject(), dto.getSummary(), - dto.getDetail()); + Presentation.of(dto.getCategory(), dto.getDateTime(), + dto.getPresentationTime(), dto.getSubject(), dto.getSummary(), + dto.getDetail()); User user = userQueryService.getUser(userId); presentation.setUser(user); @@ -79,10 +78,10 @@ public InvalidDateResponseDto getInvalidDate() { LocalDateTime end = start.plusMonths(MAX_MONTH); List invalidDates = - presentationQueryService.getRegisteredPresentations(start, end) - .stream() - .map(Presentation::getDateTime) - .collect(Collectors.toList()); + presentationQueryService.getRegisteredPresentations(start, end) + .stream() + .map(Presentation::getDateTime) + .collect(Collectors.toList()); return new InvalidDateResponseDto(invalidDates); } @@ -98,16 +97,16 @@ public List getLatestPastPresentations(int count) { LocalDateTime limit = now.atStartOfDay(); LocalDateTime start = limit.minusYears(10); PageRequest pageRequest = PageRequest.of(DEFAULT_PAGE, count, - Sort.by(DATE_TIME).descending()); + Sort.by(DATE_TIME).descending()); List presentations = - presentationQueryService.getPresentationsBetweenWithPageRequest(start, limit, - pageRequest); + presentationQueryService.getPresentationsBetweenWithPageRequest(start, limit, + pageRequest); return presentations.stream() - .filter(presentation -> - presentation.getPresentationStatus().equals(PresentationStatus.DONE)) - .collect(Collectors.toList()); + .filter(presentation -> + presentation.getPresentationStatus().equals(PresentationStatus.DONE)) + .collect(Collectors.toList()); } /** @@ -121,15 +120,15 @@ public List getLatestUpcomingPresentations(int count) { LocalDateTime start = now.atStartOfDay(); LocalDateTime end = start.plusMonths(MAX_MONTH); PageRequest pageRequest = PageRequest.of(DEFAULT_PAGE, count, - Sort.by(DATE_TIME).ascending()); + Sort.by(DATE_TIME).ascending()); List presentations = presentationQueryService. - getPresentationsBetweenWithPageRequest(start, end, pageRequest); + getPresentationsBetweenWithPageRequest(start, end, pageRequest); return presentations.stream() - .filter(presentation -> - presentation.getPresentationStatus().equals(PresentationStatus.EXPECTED)) - .collect(Collectors.toList()); + .filter(presentation -> + presentation.getPresentationStatus().equals(PresentationStatus.EXPECTED)) + .collect(Collectors.toList()); } /** @@ -140,17 +139,17 @@ public List getLatestUpcomingPresentations(int count) { * @return */ public PresentationMainData getPastAndUpcomingPresentations( - int pastFormCount, int upcomingFormCount) { + int pastFormCount, int upcomingFormCount) { List pastPresentations = getLatestPastPresentations(pastFormCount); List upcomingPresentations = getLatestUpcomingPresentations( - upcomingFormCount); + upcomingFormCount); List past = pastPresentations.stream() - .map(presentationMapper::toPresentationFormDataDto) - .collect(Collectors.toList()); + .map(presentationMapper::toPresentationFormDataDto) + .collect(Collectors.toList()); List upcoming = upcomingPresentations.stream() - .map(presentationMapper::toPresentationFormDataDto) - .collect(Collectors.toList()); + .map(presentationMapper::toPresentationFormDataDto) + .collect(Collectors.toList()); return presentationMapper.toPresentationMainData(past, upcoming); } @@ -164,12 +163,13 @@ public PresentationMainData getPastAndUpcomingPresentations( public PresentationFormResponseDto getUserPresentationSchedule(YearMonth yearMonth) { List result = - presentationQueryService.getPresentationsByYearMonth(yearMonth) - .stream() - .filter(presentation -> - !presentation.getPresentationStatus().equals(PresentationStatus.CANCEL)) - .map(presentationMapper::toPresentationFormDataDto) - .collect(Collectors.toList()); + presentationQueryService.getPresentationsByYearMonth(yearMonth) + .stream() + .filter(presentation -> + !presentation.getPresentationStatus() + .equals(PresentationStatus.CANCEL)) + .map(presentationMapper::toPresentationFormDataDto) + .collect(Collectors.toList()); return new PresentationFormResponseDto(result); } @@ -177,10 +177,10 @@ public PresentationFormResponseDto getUserPresentationSchedule(YearMonth yearMon public PresentationFormResponseDto getAdminPresentationSchedule(YearMonth yearMonth) { List result = - presentationQueryService.getPresentationsByYearMonth(yearMonth) - .stream() - .map(presentationMapper::toPresentationFormDataDto) - .collect(Collectors.toList()); + presentationQueryService.getPresentationsByYearMonth(yearMonth) + .stream() + .map(presentationMapper::toPresentationFormDataDto) + .collect(Collectors.toList()); return new PresentationFormResponseDto(result); } @@ -199,8 +199,8 @@ public PresentationFormResponseDto getAdminPresentationSchedule(YearMonth yearMo @Transactional public void updatePresentationByFormId(Long formId, PresentationUpdateDto dto) { Presentation presentationToUpdate = - presentationRepository.findById(formId) - .orElseThrow(ExceptionStatus.INVALID_FORM_ID::asServiceException); + presentationRepository.findById(formId) + .orElseThrow(ExceptionStatus.INVALID_FORM_ID::asServiceException); //날짜 변경시에만 유효성 검증 if (!presentationToUpdate.getDateTime().isEqual(dto.getDateTime())) { presentationPolicyService.verifyReservationDate(dto.getDateTime()); @@ -218,11 +218,11 @@ public void updatePresentationByFormId(Long formId, PresentationUpdateDto dto) { */ public PresentationMyPagePaginationDto getUserPresentations(Long userId, Pageable pageable) { Page presentations = presentationQueryService.getPresentationsById(userId, - pageable); + pageable); List result = presentations.stream() - .map(presentationMapper::toPresentationMyPageDto) - .collect(Collectors.toList()); + .map(presentationMapper::toPresentationMyPageDto) + .collect(Collectors.toList()); return new PresentationMyPagePaginationDto(result, presentations.getTotalElements()); } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/domain/BanHistory.java b/backend/src/main/java/org/ftclub/cabinet/user/domain/BanHistory.java index 9a5b32928..0e6b3b04e 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/domain/BanHistory.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/domain/BanHistory.java @@ -15,7 +15,6 @@ import javax.persistence.ManyToOne; import javax.persistence.Table; import javax.validation.constraints.NotNull; - import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; @@ -56,7 +55,7 @@ public class BanHistory { private User user; protected BanHistory(LocalDateTime bannedAt, LocalDateTime unbannedAt, BanType banType, - Long userId) { + Long userId) { this.bannedAt = bannedAt; this.unbannedAt = unbannedAt; this.banType = banType; @@ -64,10 +63,10 @@ protected BanHistory(LocalDateTime bannedAt, LocalDateTime unbannedAt, BanType b } public static BanHistory of(LocalDateTime bannedAt, LocalDateTime unbannedAt, BanType banType, - Long userId) { + Long userId) { BanHistory banHistory = new BanHistory(bannedAt, unbannedAt, banType, userId); ExceptionUtil.throwIfFalse(banHistory.isValid(), - new DomainException(ExceptionStatus.INVALID_ARGUMENT)); + new DomainException(ExceptionStatus.INVALID_ARGUMENT)); return banHistory; } @@ -90,4 +89,8 @@ public boolean equals(Object o) { public boolean isBanned(LocalDateTime date) { return date.isBefore(unbannedAt); } + + public void updateUnbannedAt(LocalDateTime localDateTime) { + this.unbannedAt = localDateTime; + } } \ No newline at end of file diff --git a/backend/src/main/java/org/ftclub/cabinet/user/domain/UserAspect.java b/backend/src/main/java/org/ftclub/cabinet/user/domain/UserAspect.java deleted file mode 100644 index db4698ac8..000000000 --- a/backend/src/main/java/org/ftclub/cabinet/user/domain/UserAspect.java +++ /dev/null @@ -1,63 +0,0 @@ -package org.ftclub.cabinet.user.domain; - -import com.fasterxml.jackson.core.JsonProcessingException; -import java.time.LocalDateTime; -import javax.servlet.http.HttpServletRequest; -import lombok.RequiredArgsConstructor; -import lombok.extern.log4j.Log4j2; -import org.aspectj.lang.ProceedingJoinPoint; -import org.aspectj.lang.annotation.Around; -import org.aspectj.lang.annotation.Aspect; -import org.ftclub.cabinet.auth.domain.CookieManager; -import org.ftclub.cabinet.auth.service.TokenValidator; -import org.ftclub.cabinet.config.JwtProperties; -import org.ftclub.cabinet.dto.UserSessionDto; -import org.ftclub.cabinet.exception.ExceptionStatus; -import org.ftclub.cabinet.user.service.UserQueryService; -import org.springframework.stereotype.Component; -import org.springframework.web.context.request.RequestContextHolder; -import org.springframework.web.context.request.ServletRequestAttributes; - -@Component -@Aspect -@RequiredArgsConstructor -@Log4j2 -public class UserAspect { - - //private final UserMapper ... - //private final UserService ... - //컨트롤러가 아니므로 Facade를 주입받지는 않지만, 서비스와 매퍼를 주입받아서 UserSessionDto를 생성해 줌. - private final CookieManager cookieManager; - private final TokenValidator tokenValidator; - private final JwtProperties jwtProperties; - private final UserQueryService userQueryService; - - @Around("execution(* *(.., @UserSession (*), ..))") - public Object setUserSessionDto(ProceedingJoinPoint joinPoint) - throws Throwable { - HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()) - .getRequest(); - //@User를 쓰려면 반드시 첫 매개변수에 UserSessionDto로 설정해주어야 함. - Object[] args = joinPoint.getArgs(); - if (!args[0].getClass().equals(UserSessionDto.class)) { - log.error("User not found"); - throw ExceptionStatus.UNAUTHORIZED.asControllerException(); - } - args[0] = getUserSessionDtoByRequest(request); - return joinPoint.proceed(args); - } - - // ToDo: 수정 필요 - public UserSessionDto getUserSessionDtoByRequest(HttpServletRequest req) - throws JsonProcessingException { - String name = tokenValidator.getPayloadJson( - cookieManager.getCookieValue(req, jwtProperties.getMainTokenName())).get("name") - .asText(); - User user = userQueryService.findUserByName(name) - .orElseThrow(ExceptionStatus.NOT_FOUND_USER::asServiceException); - //ToDo: name을 기준으로 service에게 정보를 받고, 매핑한다. - // name과 email은 우선 구현했으나 수정이 필요함. - return new UserSessionDto(user.getId(), name, user.getEmail(), 1, 1, LocalDateTime.now(), - true); - } -} diff --git a/backend/src/main/java/org/ftclub/cabinet/user/domain/UserSessionArgumentResolver.java b/backend/src/main/java/org/ftclub/cabinet/user/domain/UserSessionArgumentResolver.java new file mode 100644 index 000000000..616ff2be2 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/user/domain/UserSessionArgumentResolver.java @@ -0,0 +1,68 @@ +package org.ftclub.cabinet.user.domain; + +import io.netty.util.internal.StringUtil; +import javax.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.ftclub.cabinet.auth.domain.CookieManager; +import org.ftclub.cabinet.auth.service.TokenValidator; +import org.ftclub.cabinet.config.JwtProperties; +import org.ftclub.cabinet.dto.UserSessionDto; +import org.ftclub.cabinet.exception.ExceptionStatus; +import org.ftclub.cabinet.user.service.UserQueryService; +import org.jetbrains.annotations.NotNull; +import org.springframework.core.MethodParameter; +import org.springframework.stereotype.Component; +import org.springframework.web.bind.support.WebDataBinderFactory; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.method.support.ModelAndViewContainer; + +@Slf4j +@Component +@RequiredArgsConstructor +public class UserSessionArgumentResolver implements HandlerMethodArgumentResolver { + + private final TokenValidator tokenValidator; + private final UserQueryService userQueryService; + private final CookieManager cookieManager; + private final JwtProperties jwtProperties; + + + @Override + public boolean supportsParameter(MethodParameter parameter) { + boolean hasUserSessionAnnotation = parameter.hasParameterAnnotation(UserSession.class); + boolean hasUserSessionType = + UserSessionDto.class.isAssignableFrom(parameter.getParameterType()); + + return hasUserSessionAnnotation && hasUserSessionType; + } + + @Override + public Object resolveArgument(@NotNull MethodParameter parameter, + ModelAndViewContainer mavContainer, + @NotNull NativeWebRequest webRequest, + WebDataBinderFactory binderFactory) throws Exception { + + HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest(); + + String token = cookieManager.getCookieValue(request, jwtProperties.getMainTokenName()); + if (StringUtil.isNullOrEmpty(token)) { + token = cookieManager.getCookieValue(request, jwtProperties.getAdminTokenName()); + if (token != null) { + return null; + } else { + throw ExceptionStatus.INVALID_JWT_TOKEN.asControllerException(); + } + } + String name = tokenValidator.getPayloadJson(token).get("name").asText(); + if (StringUtil.isNullOrEmpty(name)) { + throw ExceptionStatus.INVALID_JWT_TOKEN.asControllerException(); + } + User user = userQueryService.findUserByName(name) + .orElseThrow(ExceptionStatus.NOT_FOUND_USER::asServiceException); + + return new UserSessionDto(user.getId(), name, user.getEmail(), 1, 1, + user.getBlackholedAt(), false); + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/user/service/BanHistoryCommandService.java b/backend/src/main/java/org/ftclub/cabinet/user/service/BanHistoryCommandService.java index 802beac2c..adfb3b7bd 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/service/BanHistoryCommandService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/service/BanHistoryCommandService.java @@ -42,4 +42,17 @@ public void deleteRecentBanHistory(BanHistory banHistory, LocalDateTime now) { banHistoryRepository.delete(banHistory); } } + + @Transactional + public void updateBanDate(BanHistory recentBanHistory, LocalDateTime reducedBanDate) { + LocalDateTime newUnbannedAt = findMaxUnbannedAt(reducedBanDate, LocalDateTime.now()); + recentBanHistory.updateUnbannedAt(newUnbannedAt); + } + + private LocalDateTime findMaxUnbannedAt(LocalDateTime reducedBanDate, LocalDateTime now) { + if (reducedBanDate.isAfter(now)) { + return reducedBanDate; + } + return now; + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/service/BanHistoryQueryService.java b/backend/src/main/java/org/ftclub/cabinet/user/service/BanHistoryQueryService.java index 7ec65e770..ca248339c 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/service/BanHistoryQueryService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/service/BanHistoryQueryService.java @@ -5,6 +5,7 @@ import java.util.List; import java.util.Optional; import lombok.RequiredArgsConstructor; +import org.ftclub.cabinet.exception.ExceptionStatus; import org.ftclub.cabinet.log.LogLevel; import org.ftclub.cabinet.log.Logging; import org.ftclub.cabinet.user.domain.BanHistory; @@ -23,9 +24,9 @@ public class BanHistoryQueryService { public Optional findRecentActiveBanHistory(Long userId, LocalDateTime now) { List banHistories = banHistoryRepository.findByUserId(userId); return banHistories.stream() - .filter(history -> history.getUnbannedAt().isAfter(now)) - .sorted(Comparator.comparing(BanHistory::getUnbannedAt, Comparator.reverseOrder())) - .findFirst(); + .filter(history -> history.getUnbannedAt().isAfter(now)) + .sorted(Comparator.comparing(BanHistory::getUnbannedAt, Comparator.reverseOrder())) + .findFirst(); } public List findActiveBanHistories(Long userId, LocalDateTime date) { @@ -39,4 +40,12 @@ public List findActiveBanHistories(List userIds, LocalDateTime public Page findActiveBanHistories(LocalDateTime now, Pageable pageable) { return banHistoryRepository.findPaginationActiveBanHistoriesJoinUser(pageable, now); } + + public BanHistory getRecentBanHistory(Long userId) { + List activeBanHistories = + banHistoryRepository.findByUserIdAndUnbannedAt(userId, LocalDateTime.now()); + return activeBanHistories.stream() + .max(Comparator.comparing(BanHistory::getBannedAt)) + .orElseThrow(ExceptionStatus.NOT_FOUND_BAN_HISTORY::asServiceException); + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/service/LentExtensionCommandService.java b/backend/src/main/java/org/ftclub/cabinet/user/service/LentExtensionCommandService.java index 41ddff61f..044cc03fb 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/service/LentExtensionCommandService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/service/LentExtensionCommandService.java @@ -19,6 +19,7 @@ @Transactional public class LentExtensionCommandService { + private static final String LENT_EXTENSION_ITEM = "lentExtensionItem"; private final LentExtensionRepository lentExtensionRepository; private final LentExtensionPolicy policy; @@ -31,11 +32,19 @@ public class LentExtensionCommandService { * @return 생성된 연장권 */ public LentExtension createLentExtension(Long userId, LentExtensionType type, - LocalDateTime expiredAt) { + LocalDateTime expiredAt) { LentExtension lentExtension = LentExtension.of(policy.getDefaultName(), - policy.getDefaultExtensionTerm(), - policy.getExpiry(expiredAt), - type, userId); + policy.getDefaultExtensionTerm(), + policy.getExpiry(expiredAt), + type, userId); + return lentExtensionRepository.save(lentExtension); + } + + public LentExtension createLentExtensionByItem(Long userId, LentExtensionType type, + Integer extensionDay) { + LentExtension lentExtension = LentExtension.of(LENT_EXTENSION_ITEM, extensionDay, + LocalDateTime.now().plusYears(100), + type, userId); return lentExtensionRepository.save(lentExtension); } @@ -48,7 +57,7 @@ public LentExtension createLentExtension(Long userId, LentExtensionType type, public void useLentExtension(LentExtension lentExtension, List lentHistories) { lentExtension.use(); lentHistories.forEach(lentHistory -> - lentHistory.setExpiredAt( - lentHistory.getExpiredAt().plusDays(lentExtension.getExtensionPeriod()))); + lentHistory.setExpiredAt( + lentHistory.getExpiredAt().plusDays(lentExtension.getExtensionPeriod()))); } } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/service/LentExtensionManager.java b/backend/src/main/java/org/ftclub/cabinet/user/service/LentExtensionManager.java index 75778b2a1..40b618173 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/service/LentExtensionManager.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/service/LentExtensionManager.java @@ -1,16 +1,20 @@ package org.ftclub.cabinet.user.service; -import java.time.LocalDateTime; import java.util.List; import java.util.stream.Collectors; import javax.transaction.Transactional; import lombok.RequiredArgsConstructor; import org.ftclub.cabinet.dto.UserMonthDataDto; +import org.ftclub.cabinet.item.domain.Item; +import org.ftclub.cabinet.item.domain.Sku; +import org.ftclub.cabinet.item.service.ItemHistoryCommandService; +import org.ftclub.cabinet.item.service.ItemQueryService; +import org.ftclub.cabinet.item.service.ItemRedisService; import org.ftclub.cabinet.log.LogLevel; import org.ftclub.cabinet.log.Logging; import org.ftclub.cabinet.occupiedtime.OccupiedTimeManager; -import org.ftclub.cabinet.user.domain.LentExtensionType; import org.ftclub.cabinet.user.domain.User; +import org.ftclub.cabinet.utils.lock.LockUtil; import org.springframework.stereotype.Service; @Service @@ -18,7 +22,9 @@ @Logging(level = LogLevel.DEBUG) public class LentExtensionManager { - private final LentExtensionCommandService lentExtensionCommandService; + private final ItemHistoryCommandService itemHistoryCommandService; + private final ItemQueryService itemQueryService; + private final ItemRedisService itemRedisService; private final OccupiedTimeManager occupiedTimeManager; private final UserQueryService userQueryService; @@ -28,17 +34,36 @@ public class LentExtensionManager { */ @Transactional public void issueLentExtension() { - UserMonthDataDto[] userLastMonthOccupiedTime = occupiedTimeManager.getUserLastMonthOccupiedTime(); - List userMonthDataDtos = occupiedTimeManager.filterToMetUserMonthlyTime( - userLastMonthOccupiedTime); + UserMonthDataDto[] userLastMonthOccupiedTime = + occupiedTimeManager.getUserLastMonthOccupiedTime(); + List userMonthDataDtos = + occupiedTimeManager.filterToMetUserMonthlyTime(userLastMonthOccupiedTime); List userNames = userMonthDataDtos.stream().map(UserMonthDataDto::getLogin) .collect(Collectors.toList()); - LocalDateTime now = LocalDateTime.now(); List users = userQueryService.findAllUsersByNames(userNames); - users.forEach(user -> lentExtensionCommandService.createLentExtension(user.getId(), - LentExtensionType.ALL, - LocalDateTime.of(now.getYear(), now.getMonth(), - now.getMonth().length(now.toLocalDate().isLeapYear()), 23, 59, 0))); + Item coinRewardItem = itemQueryService.getBySku(Sku.COIN_FULL_TIME); + users.forEach(user -> { + Long userId = user.getId(); + LockUtil.lockRedisCoin(userId, () -> + saveCoinChangeOnRedis(userId, coinRewardItem.getPrice())); + itemHistoryCommandService.createItemHistory(userId, coinRewardItem.getId()); + }); + } + + /** + * 재화 변동량을 Redis에 저장합니다. + * + * @param userId 유저 아이디 + * @param price 변동량 + */ + private void saveCoinChangeOnRedis(Long userId, long price) { + // 유저 재화 변동량 Redis에 저장 + long userCoinCount = itemRedisService.getCoinCount(userId); + itemRedisService.saveCoinCount(userId, userCoinCount + price); + + // 전체 재화 변동량 Redis에 저장 + long totalCoinSupply = itemRedisService.getTotalCoinSupply(); + itemRedisService.saveTotalCoinSupply(totalCoinSupply + price); } } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeService.java index 8aa3e22aa..e1d7767af 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeService.java @@ -17,6 +17,7 @@ import org.ftclub.cabinet.dto.UpdateDeviceTokenRequestDto; import org.ftclub.cabinet.dto.UserSessionDto; import org.ftclub.cabinet.exception.ExceptionStatus; +import org.ftclub.cabinet.item.service.ItemRedisService; import org.ftclub.cabinet.lent.domain.LentHistory; import org.ftclub.cabinet.lent.service.LentQueryService; import org.ftclub.cabinet.log.LogLevel; @@ -45,6 +46,9 @@ public class UserFacadeService { private final UserMapper userMapper; private final FCMTokenRedisService fcmTokenRedisService; private final FirebaseConfig firebaseConfig; + private final BanHistoryCommandService banHistoryCommandService; + private final ItemRedisService itemRedisService; + /** * 유저의 프로필을 가져옵니다. @@ -54,19 +58,21 @@ public class UserFacadeService { */ @Transactional(readOnly = true) public MyProfileResponseDto getProfile(UserSessionDto user) { - Cabinet cabinet = cabinetQueryService.findUserActiveCabinet(user.getUserId()); - BanHistory banHistory = banHistoryQueryService.findRecentActiveBanHistory(user.getUserId(), + Long userId = user.getUserId(); + Cabinet cabinet = cabinetQueryService.findUserActiveCabinet(userId); + BanHistory banHistory = banHistoryQueryService.findRecentActiveBanHistory(userId, LocalDateTime.now()).orElse(null); LentExtension lentExtension = lentExtensionQueryService.findActiveLentExtension( - user.getUserId()); + userId); LentExtensionResponseDto lentExtensionResponseDto = userMapper.toLentExtensionResponseDto( lentExtension); - User currentUser = userQueryService.getUser(user.getUserId()); + User currentUser = userQueryService.getUser(userId); AlarmTypeResponseDto userAlarmTypes = currentUser.getAlarmTypes(); boolean isDeviceTokenExpired = userAlarmTypes.isPush() && fcmTokenRedisService.findByUserName(user.getName()).isEmpty(); + Long coins = itemRedisService.getCoinCount(userId); return userMapper.toMyProfileResponseDto(user, cabinet, banHistory, - lentExtensionResponseDto, userAlarmTypes, isDeviceTokenExpired); + lentExtensionResponseDto, userAlarmTypes, isDeviceTokenExpired, coins); } /** @@ -135,5 +141,19 @@ public void updateDeviceToken(UserSessionDto userSessionDto, Duration.ofDays(firebaseConfig.getDeviceTokenExpiryDays()) ); } + + /** + * 가장 최근 밴 당한 날짜에서 일자만큼 차감 후 업데이트 + * + * @param userId + * @param days + */ + @Transactional + public void reduceBanDays(Long userId, Integer days) { + BanHistory recentBanHistory = banHistoryQueryService.getRecentBanHistory(userId); + LocalDateTime reducedUnbannedAt = recentBanHistory.getUnbannedAt().minusDays(days); + banHistoryCommandService.updateBanDate(recentBanHistory, reducedUnbannedAt); + } + } diff --git a/backend/src/main/java/org/ftclub/cabinet/utils/annotations/NullableMapper.java b/backend/src/main/java/org/ftclub/cabinet/utils/annotations/NullableMapper.java deleted file mode 100644 index ecfd121ac..000000000 --- a/backend/src/main/java/org/ftclub/cabinet/utils/annotations/NullableMapper.java +++ /dev/null @@ -1,26 +0,0 @@ -package org.ftclub.cabinet.utils.annotations; - -import static org.mapstruct.NullValueMappingStrategy.RETURN_DEFAULT; -import static org.mapstruct.NullValueMappingStrategy.RETURN_NULL; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; -import org.mapstruct.Mapper; - -/** - * NPE를 방지하고, 빈 값들을 매핑할 수 있는 mapper 입니다. - *

- * 단일 객체가 null인 경우, null을 매핑합니다. - *

- * Map, Iterable가 비어있거나, null인 경우 빈 Map 또는 Iterable을 매핑합니다. - */ -@Mapper(componentModel = "spring", - nullValueMappingStrategy = RETURN_NULL, - nullValueMapMappingStrategy = RETURN_DEFAULT, - nullValueIterableMappingStrategy = RETURN_DEFAULT) -@Target({java.lang.annotation.ElementType.TYPE}) -@Retention(RetentionPolicy.RUNTIME) -public @interface NullableMapper { - -} diff --git a/backend/src/main/java/org/ftclub/cabinet/utils/lock/IntegerLock.java b/backend/src/main/java/org/ftclub/cabinet/utils/lock/IntegerLock.java new file mode 100644 index 000000000..538465ab1 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/utils/lock/IntegerLock.java @@ -0,0 +1,7 @@ +package org.ftclub.cabinet.utils.lock; + +@FunctionalInterface +public interface IntegerLock { + + int invoke(); +} diff --git a/backend/src/main/java/org/ftclub/cabinet/utils/lock/LockUtil.java b/backend/src/main/java/org/ftclub/cabinet/utils/lock/LockUtil.java new file mode 100644 index 000000000..52817601c --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/utils/lock/LockUtil.java @@ -0,0 +1,16 @@ +package org.ftclub.cabinet.utils.lock; + +import java.util.concurrent.ConcurrentHashMap; + +public abstract class LockUtil { + + private final static ConcurrentHashMap redisCoinLock = new ConcurrentHashMap<>(); + + public static void lockRedisCoin(Long userId, VoidLock functionalInterface) { + Object lock = redisCoinLock.computeIfAbsent(userId, v -> new Object()); + synchronized (lock) { + functionalInterface.invoke(); + } + redisCoinLock.remove(userId); + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/utils/lock/VoidLock.java b/backend/src/main/java/org/ftclub/cabinet/utils/lock/VoidLock.java new file mode 100644 index 000000000..16b31d8d5 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/utils/lock/VoidLock.java @@ -0,0 +1,7 @@ +package org.ftclub.cabinet.utils.lock; + +@FunctionalInterface +public interface VoidLock { + + void invoke(); +} diff --git a/backend/src/main/java/org/ftclub/cabinet/utils/release/ReleaseManager.java b/backend/src/main/java/org/ftclub/cabinet/utils/release/ReleaseManager.java index 8c8e760a9..6f4bf2778 100644 --- a/backend/src/main/java/org/ftclub/cabinet/utils/release/ReleaseManager.java +++ b/backend/src/main/java/org/ftclub/cabinet/utils/release/ReleaseManager.java @@ -35,7 +35,7 @@ private Set getAllPendedYesterdayCabinet() { .map(Cabinet::getId).collect(Collectors.toList()); Set todayReturnedSet = lentQueryService.findCabinetLentHistories(cabinetIds) .stream() - .filter(lh -> lh.getEndedAt().isAfter(from)) + .filter(lh -> lh.getEndedAt() != null && lh.getEndedAt().isAfter(from)) .map(LentHistory::getCabinetId) .collect(Collectors.toSet()); diff --git a/backend/src/main/java/org/ftclub/cabinet/utils/scheduler/SystemScheduler.java b/backend/src/main/java/org/ftclub/cabinet/utils/scheduler/SystemScheduler.java index dce80e8d8..24d3fa8cb 100644 --- a/backend/src/main/java/org/ftclub/cabinet/utils/scheduler/SystemScheduler.java +++ b/backend/src/main/java/org/ftclub/cabinet/utils/scheduler/SystemScheduler.java @@ -7,6 +7,7 @@ import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.alarm.discord.DiscordScheduleAlarmMessage; import org.ftclub.cabinet.alarm.discord.DiscordWebHookMessenger; +import org.ftclub.cabinet.alarm.handler.SectionAlarmManager; import org.ftclub.cabinet.dto.ActiveLentHistoryDto; import org.ftclub.cabinet.dto.UserBlackHoleEvent; import org.ftclub.cabinet.exception.FtClubCabinetException; @@ -48,6 +49,7 @@ private void errorHandle(Exception e, DiscordScheduleAlarmMessage message) { } } + private final SectionAlarmManager sectionAlarmManager; /** * 매일 자정마다 대여 기록을 확인하여, 연체 메일 발송 및 휴학생 처리를 트리거 @@ -176,4 +178,10 @@ public void lentExtensionIssue() { // List userMonthDataDtos = occupiedTimeManager.metLimitTimeUser(occupiedTimeManager.getUserLastMonthOccupiedTime()); // userService.updateUserExtensible(userMonthDataDtos); // } + + @Scheduled(cron = "${cabinet.schedule.cron.section-alarm-time}") + public void sectionAlarm() { + log.info("called sectionAlarm"); + sectionAlarmManager.sendSectionAlarm(); + } } diff --git a/backend/src/main/resources/templates/mail/lentsuccess.html b/backend/src/main/resources/templates/mail/lentsuccess.html index 1be585ffd..dbe1068c1 100644 --- a/backend/src/main/resources/templates/mail/lentsuccess.html +++ b/backend/src/main/resources/templates/mail/lentsuccess.html @@ -1,33 +1,41 @@ - + - - - - - Document - - + + + + + Document + + +

+

🚨 사물함 대여 성공 알림 🚨

+
+
+
+ 님, 의 사물함 대여에 + 성공했습니다.

-
-

🚨 사물함 대여 성공 알림 🚨

-
-
-
- 님, 의 사물함 대여에 성공했습니다.

+ 대여 기간은 까지 이며 기한 만료 전 반납 + 부탁드립니다.

- 대여 기간은 까지 이며 기한 만료 전 반납 부탁드립니다.

- - 연체한 일 수의 제곱 일수 만큼 패널티가 주어집니다.
- 패널티 일수 만큼 사물함 이용이 불가합니다.
- 반복적인 연체 발생 시 TIG가 부여될 수 있습니다.
-
-
-
- 사물함 대여 서비스 바로가기 ➡ https://cabi.42seoul.io
- 사물함 서비스 관련 문의사항 ➡ https://42born2code.slack.com/archives/C02V6GE8LD7 -
-
- - - \ No newline at end of file + 연체한 일 수의 제곱 일수 만큼 페널티가 주어집니다.
+ 페널티 일수 만큼 사물함 이용이 불가합니다.
+ 반복적인 연체 발생 시 TIG가 부여될 수 있습니다.
+
+
+
+ 사물함 대여 서비스 바로가기 ➡ + https://cabi.42seoul.io
+ 사물함 서비스 관련 문의사항 ➡ + https://42born2code.slack.com/archives/C02V6GE8LD7 +
+
+ + diff --git a/backend/src/main/resources/templates/mail/overdue.html b/backend/src/main/resources/templates/mail/overdue.html index ef1010c4b..7d9068712 100644 --- a/backend/src/main/resources/templates/mail/overdue.html +++ b/backend/src/main/resources/templates/mail/overdue.html @@ -1,35 +1,43 @@ - + - - - - - Document - - + + + + + Document + + +
+

🚨 사물함 대여 연체 알림 🚨

+
+
+
+ 님, 이용 중인 사물함이 연체되었습니다. +

-
-

🚨 사물함 대여 연체 알림 🚨

-
-
-
- 님, 이용 중인 사물함이 연체되었습니다.

+ 현재 일 연체 되었으며, 확인 후 반납 + 부탁드립니다.

+ 짐을 비우지 않을 경우, 해당 짐은 분실물 처리가 될 수 있습니다.
+ 이 과정에서 발생하는 분실물에 대한 책임은 사용자 본인에게 있습니다.

- 현재 일 연체 되었으며, 확인 후 반납 부탁드립니다.

- 짐을 비우지 않을 경우, 해당 짐은 분실물 처리가 될 수 있습니다.
- 이 과정에서 발생하는 분실물에 대한 책임은 사용자 본인에게 있습니다.

- - 연체한 일 수의 제곱 일수 만큼 패널티가 주어집니다.
- 패널티 일수 만큼 사물함 이용이 불가합니다.
- 반복적인 연체 발생 시 TIG가 부여될 수 있습니다.
-
-
-
- 사물함 대여 서비스 바로가기 ➡ https://cabi.42seoul.io
- 사물함 서비스 관련 문의사항 ➡ https://42born2code.slack.com/archives/C02V6GE8LD7 -
-
- - - \ No newline at end of file + 연체한 일 수의 제곱 일수 만큼 페널티가 주어집니다.
+ 페널티 일수 만큼 사물함 이용이 불가합니다.
+ 반복적인 연체 발생 시 TIG가 부여될 수 있습니다.
+
+
+
+ 사물함 대여 서비스 바로가기 ➡ + https://cabi.42seoul.io
+ 사물함 서비스 관련 문의사항 ➡ + https://42born2code.slack.com/archives/C02V6GE8LD7 +
+
+ + diff --git a/backend/src/main/resources/templates/mail/sectionAlarm.html b/backend/src/main/resources/templates/mail/sectionAlarm.html new file mode 100644 index 000000000..9699eb3f1 --- /dev/null +++ b/backend/src/main/resources/templates/mail/sectionAlarm.html @@ -0,0 +1,31 @@ + + + + + + + Document + + + +
+

🚨 알림 등록 영역 사물함 오픈 예정 알림 🚨

+
+
+
+ 님, 알림 등록하신 의 사물함이 오픈 예정입니다.

+ + 사물함 오픈 예정 시간은 매일 13시입니다.
+ 이용에 참고 바랍니다.

+
+
+
+ 사물함 대여 서비스 바로가기 ➡ https://cabi.42seoul.io
+ 사물함 서비스 관련 문의사항 ➡ https://42born2code.slack.com/archives/C02V6GE8LD7 +
+
+ + + \ No newline at end of file diff --git a/backend/src/main/resources/templates/mail/soonoverdue.html b/backend/src/main/resources/templates/mail/soonoverdue.html index 23acc4676..8b71b62f3 100644 --- a/backend/src/main/resources/templates/mail/soonoverdue.html +++ b/backend/src/main/resources/templates/mail/soonoverdue.html @@ -1,36 +1,45 @@ - + - - - - - Document - - + + + + + Document + + +
+

🚨 사물함 대여 기간 만료 예정 알림 🚨

+
+
+
+ 님, 사물함 사용 기간이 곧 만료될 예정입니다.

-
-

🚨 사물함 대여 기간 만료 예정 알림 🚨

-
-
-
- 님, 사물함 사용 기간이 곧 만료될 예정입니다.

+ 대여 기간은 까지 이며 기한 만료 전 반납 + 부탁드립니다.

+ 반납 전, 사물함의 짐을 반드시 비운 후에 반납해주시기 바랍니다.
+ 짐을 비우지 않을 경우, 해당 짐은 분실물 처리가 될 수 있습니다.
+ 이 과정에서 발생하는 분실물에 대한 책임은 사용자 본인에게 있습니다.

- 대여 기간은 까지 이며 기한 만료 전 반납 부탁드립니다.

- 반납 전, 사물함의 짐을 반드시 비운 후에 반납해주시기 바랍니다.
- 짐을 비우지 않을 경우, 해당 짐은 분실물 처리가 될 수 있습니다.
- 이 과정에서 발생하는 분실물에 대한 책임은 사용자 본인에게 있습니다.

- - 연체한 일 수의 제곱 일수 만큼 패널티가 주어집니다.
- 패널티 일수 만큼 사물함 이용이 불가합니다.
- 반복적인 연체 발생 시 TIG가 부여될 수 있습니다.
- 직접 반납하기 어려우신 경우 지인을 통해서라도 꼭 연체 전 반납 부탁드립니다!

-
-
-
- 사물함 대여 서비스 바로가기 ➡ https://cabi.42seoul.io
- 사물함 서비스 관련 문의사항 ➡ https://42born2code.slack.com/archives/C02V6GE8LD7 -
-
- - \ No newline at end of file + 연체한 일 수의 제곱 일수 만큼 페널티가 주어집니다.
+ 페널티 일수 만큼 사물함 이용이 불가합니다.
+ 반복적인 연체 발생 시 TIG가 부여될 수 있습니다.
+ 직접 반납하기 어려우신 경우 지인을 통해서라도 꼭 연체 전 반납 + 부탁드립니다!

+
+
+
+ 사물함 대여 서비스 바로가기 ➡ + https://cabi.42seoul.io
+ 사물함 서비스 관련 문의사항 ➡ + https://42born2code.slack.com/archives/C02V6GE8LD7 +
+
+ + diff --git a/backend/src/test/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeServiceTest.java b/backend/src/test/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeServiceTest.java index ea4d780bd..ca6f1239f 100644 --- a/backend/src/test/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeServiceTest.java +++ b/backend/src/test/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeServiceTest.java @@ -3,6 +3,7 @@ import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; import java.util.List; import javax.transaction.Transactional; @@ -39,7 +40,7 @@ public class CabinetFacadeServiceTest { assertEquals(LentType.PRIVATE, cabinet.getLentType()); assertEquals(1, cabinet.getVisibleNum().intValue()); assertEquals(Location.of("새롬관", 2, "Oasis"), cabinet.getLocation()); - assertEquals(null, cabinet.getTitle()); + assertNull(cabinet.getTitle()); } @Test @@ -65,9 +66,9 @@ public class CabinetFacadeServiceTest { assertEquals("user7", lentsOfCabinet.get(0).getName()); } - @Test - public void 사물함_섹션_정보_가져오기() { - System.out.println( - "cabinetFacadeService = " + cabinetFacadeService.getCabinetsPerSection("새롬관", 2)); - } +// @Test +// public void 사물함_섹션_정보_가져오기() { +// System.out.println( +// "cabinetFacadeService = " + cabinetFacadeService.getCabinetsPerSection("새롬관", 2)); +// } } diff --git a/backend/src/test/java/org/ftclub/cabinet/mapper/CabinetMapperTest.java b/backend/src/test/java/org/ftclub/cabinet/mapper/CabinetMapperTest.java index 73b1c6da4..143d7d3bc 100644 --- a/backend/src/test/java/org/ftclub/cabinet/mapper/CabinetMapperTest.java +++ b/backend/src/test/java/org/ftclub/cabinet/mapper/CabinetMapperTest.java @@ -1,18 +1,27 @@ package org.ftclub.cabinet.mapper; -import org.ftclub.cabinet.cabinet.domain.*; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.time.LocalDateTime; +import java.util.List; +import org.ftclub.cabinet.cabinet.domain.Cabinet; +import org.ftclub.cabinet.cabinet.domain.CabinetPlace; +import org.ftclub.cabinet.cabinet.domain.CabinetStatus; +import org.ftclub.cabinet.cabinet.domain.Grid; +import org.ftclub.cabinet.cabinet.domain.LentType; +import org.ftclub.cabinet.cabinet.domain.Location; +import org.ftclub.cabinet.cabinet.domain.MapArea; +import org.ftclub.cabinet.cabinet.domain.SectionFormation; import org.ftclub.cabinet.cabinet.repository.CabinetRepository; -import org.ftclub.cabinet.dto.*; +import org.ftclub.cabinet.dto.BuildingFloorsDto; +import org.ftclub.cabinet.dto.CabinetDto; +import org.ftclub.cabinet.dto.CabinetInfoResponseDto; +import org.ftclub.cabinet.dto.LentDto; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; -import java.time.LocalDateTime; -import java.util.List; - -import static org.junit.jupiter.api.Assertions.assertArrayEquals; -import static org.junit.jupiter.api.Assertions.assertEquals; - @SpringBootTest class CabinetMapperTest { @@ -71,23 +80,23 @@ void toCabinetInfoResponseDto() { assertEquals(lentDtos, cabinetInfoResponseDto.getLents()); } - @Test - void toCabinetsPerSectionResponseDto() { - String section = "testSection"; - CabinetPreviewDto cabinetPreviewDto1 = new CabinetPreviewDto(1L, 2, "title", - LentType.SHARE, 3, CabinetStatus.AVAILABLE, 1, null); - CabinetPreviewDto cabinetPreviewDto2 = new CabinetPreviewDto(2L, 5, "title", - LentType.SHARE, 3, CabinetStatus.AVAILABLE, 1, null); - CabinetPreviewDto cabinetPreviewDto3 = new CabinetPreviewDto(3L, 6, "title", - LentType.SHARE, 3, CabinetStatus.AVAILABLE, 1, null); - CabinetPreviewDto cabinetPreviewDto4 = new CabinetPreviewDto(4L, 7, "title", - LentType.SHARE, 3, CabinetStatus.AVAILABLE, 1, null); - List cabinetPreviewDtos = List.of(cabinetPreviewDto1, cabinetPreviewDto2, - cabinetPreviewDto3, cabinetPreviewDto4); - CabinetsPerSectionResponseDto cabinetsPerSectionResponseDto = cabinetMapper.toCabinetsPerSectionResponseDto( - section, cabinetPreviewDtos); - System.out.println("cabinetsPerSectionResponseDto = " + cabinetsPerSectionResponseDto); - assertEquals(section, cabinetsPerSectionResponseDto.getSection()); - assertEquals(cabinetPreviewDtos, cabinetsPerSectionResponseDto.getCabinets()); - } +// @Test +// void toCabinetsPerSectionResponseDto() { +// String section = "testSection"; +// CabinetPreviewDto cabinetPreviewDto1 = new CabinetPreviewDto(1L, 2, "title", +// LentType.SHARE, 3, CabinetStatus.AVAILABLE, 1, null); +// CabinetPreviewDto cabinetPreviewDto2 = new CabinetPreviewDto(2L, 5, "title", +// LentType.SHARE, 3, CabinetStatus.AVAILABLE, 1, null); +// CabinetPreviewDto cabinetPreviewDto3 = new CabinetPreviewDto(3L, 6, "title", +// LentType.SHARE, 3, CabinetStatus.AVAILABLE, 1, null); +// CabinetPreviewDto cabinetPreviewDto4 = new CabinetPreviewDto(4L, 7, "title", +// LentType.SHARE, 3, CabinetStatus.AVAILABLE, 1, null); +// List cabinetPreviewDtos = List.of(cabinetPreviewDto1, cabinetPreviewDto2, +// cabinetPreviewDto3, cabinetPreviewDto4); +// CabinetsPerSectionResponseDto cabinetsPerSectionResponseDto = cabinetMapper.toCabinetsPerSectionResponseDto( +// section, cabinetPreviewDtos); +// System.out.println("cabinetsPerSectionResponseDto = " + cabinetsPerSectionResponseDto); +// assertEquals(section, cabinetsPerSectionResponseDto.getSection()); +// assertEquals(cabinetPreviewDtos, cabinetsPerSectionResponseDto.getCabinets()); +// } } \ No newline at end of file diff --git a/backend/src/test/java/org/ftclub/cabinet/user/repository/UserOptionalFetcherTest.java b/backend/src/test/java/org/ftclub/cabinet/user/repository/UserOptionalFetcherTest.java index c1e39c2b6..375a2fb2c 100644 --- a/backend/src/test/java/org/ftclub/cabinet/user/repository/UserOptionalFetcherTest.java +++ b/backend/src/test/java/org/ftclub/cabinet/user/repository/UserOptionalFetcherTest.java @@ -6,7 +6,7 @@ //import java.time.LocalDateTime; // //import org.ftclub.cabinet.user.domain.User; -//import org.ftclub.cabinet.user.domain.UserRole; +//import org.ftclub.cabinet.club.domain.UserRole; //import org.junit.jupiter.api.DisplayName; //import org.junit.jupiter.api.Test; //import org.springframework.beans.factory.annotation.Autowired; diff --git a/config b/config index 8d4f9e4f0..2f4778c4b 160000 --- a/config +++ b/config @@ -1 +1 @@ -Subproject commit 8d4f9e4f0f297a73ea6676c582267a265715ce5d +Subproject commit 2f4778c4b0932e488f7e63cd5893e7244b0d7e32 diff --git a/delete_local_branchs.sh b/delete_local_branchs.sh new file mode 100755 index 000000000..6170ac32b --- /dev/null +++ b/delete_local_branchs.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +# 기본 브랜치 이름 (필요에 따라 변경) +default_branch="main" + +# 원격 브랜치 목록 가져오기 +remote_branches=$(git branch -r | sed 's/ *origin\///') + +# 로컬 브랜치 목록 가져오기 (기본 브랜치 제외) +local_branches=$(git branch | grep -v " $default_branch$" | grep -v "\*") + +# 로컬 브랜치 중 원격에 없는 브랜치 찾기 및 삭제 +for branch in $local_branches; { + branch_name=$(echo $branch | sed 's/ //g') # 앞뒤 공백 제거 + if ! echo "$remote_branches" | grep -qw "$branch_name"; then + echo "Deleting local branch: $branch_name" + git branch -d "$branch_name" + fi +} + diff --git a/frontend/index.html b/frontend/index.html index d46975a5f..768d40f31 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -3,7 +3,7 @@ } /> } /> } /> + } /> + } /> + } /> + } /> }> } /> @@ -67,6 +76,7 @@ function App(): React.ReactElement { } /> } /> } /> + } /> => { } }; +const axiosCoinCheck = "/v5/items/coin"; +export const axiosCoinCheckGet = async (): Promise => { + try { + const response = await instance.get(axiosCoinCheck); + return response; + } catch (error) { + throw error; + } +}; + +export const axiosCoinCheckPost = async (): Promise => { + try { + const response = await instance.post(axiosCoinCheck); + return response; + } catch (error) { + throw error; + } +}; + +const axiosCoinLogURL = "/v5/items/coin/history"; +export const axiosCoinLog = async ( + type: CoinLogToggleType, + page: number, + size: number +): Promise => { + try { + const response = await instance.get(axiosCoinLogURL, { + params: { type: type, page: page, size: size }, + }); + return response; + } catch (error) { + throw error; + } +}; + +const axiosMyItemsURL = "/v5/items/me"; +export const axiosMyItems = async (): Promise => { + try { + const response = await instance.get(axiosMyItemsURL); + return response; + } catch (error) { + throw error; + } +}; + +const axiosItemHistoryURL = "/v5/items/history"; +export const axiosGetItemUsageHistory = async ( + page: number, + size: number +): Promise => { + if (page === null || size === null) return; + try { + const response = await instance.get(axiosItemHistoryURL, { + params: { page: page, size: size }, + }); + return response.data; + } catch (error) { + throw error; + } +}; + +const axiosItemsURL = "/v5/items"; +export const axiosItems = async (): Promise => { + try { + const response = await instance.get(axiosItemsURL); + return response; + } catch (error) { + throw error; + } +}; + +const axiosBuyItemURL = "/v5/items/"; +export const axiosBuyItem = async (sku: String): Promise => { + try { + const response = await instance.post(axiosBuyItemURL + sku + "/purchase"); + return response; + } catch (error) { + throw error; + } +}; + +export const axiosUseItem = async ( + sku: String, + newCabinetId: number | null, + building: string | null, + floor: number | null, + section: string | null +): Promise => { + try { + const response = await instance.post(axiosBuyItemURL + sku + "/use", { + newCabinetId: newCabinetId, + building: building, + floor: floor, + section: section, + }); + return response; + } catch (error) { + throw error; + } +}; + // Admin API const axiosAdminAuthLoginURL = "/v4/admin/auth/login"; export const axiosAdminAuthLogin = async ( @@ -665,6 +767,65 @@ export const axiosLentClubCabinet = async ( } }; +const axiosStatisticsCoinURL = "/v5/admin/statistics/coins"; +export const axiosStatisticsCoin = async () => { + try { + const response = await instance.get(axiosStatisticsCoinURL); + return response; + } catch (error) { + throw error; + } +}; + +const axiosCoinUseStatisticsURL = "/v5/admin/statistics/coins/use"; +export const axiosCoinUseStatistics = async ( + startDate: Date, + endDate: Date +): Promise => { + try { + const response = await instance.get(axiosCoinUseStatisticsURL, { + params: { startDate, endDate }, + }); + return response; + } catch (error) { + throw error; + } +}; + +const axiosStatisticsItemURL = "/v5/admin/statistics/coins"; +export const axiosStatisticsItem = async () => { + try { + const response = await instance.get(axiosStatisticsItemURL); + return response; + } catch (error) { + throw error; + } +}; +const axiosStatisticsTotalItemUseURL = "/v5/admin/statistics/items"; +export const axiosStatisticsTotalItemUse = async () => { + try { + const response = await instance.get(axiosStatisticsTotalItemUseURL); + return response; + } catch (error) { + throw error; + } +}; + +const axiosCoinCollectStatisticsURL = "/v5/admin/statistics/coins/collect"; +export const axiosCoinCollectStatistics = async ( + year: number, + month: number +): Promise => { + try { + const response = await instance.get(axiosCoinCollectStatisticsURL, { + params: { year: year, month: month }, + }); + return response; + } catch (error) { + throw error; + } +}; + const axiosLentShareIdURL = "/v4/lent/cabinets/share/"; export const axiosLentShareId = async ( cabinetId: number | null, @@ -734,3 +895,51 @@ export const axiosSendSlackNotificationToChannel = async ( throw error; } }; + +const axiosItemAssignURL = "v5/admin/items/assign"; +export const axiosItemAssign = async ( + itemSku: string, + userIds: number[] +): Promise => { + try { + const response = await instance.post(axiosItemAssignURL, { + itemSku, + userIds, + }); + return response; + } catch (error) { + throw error; + } +}; + +const axiosItemAssignListURL = "/v5/admin/items/assign"; +export const axiosItemAssignList = async ( + page: number, + size: number +): Promise => { + if (page === null || size === null) return; + try { + const response = await instance.get(axiosItemAssignListURL, { + params: { page: page, size: size }, + }); + return response.data; + } catch (error) { + throw error; + } +}; + +const axiosGetUserItemsURL = "/v5/admin/items/users/"; +export const axiosGetUserItems = async ( + userId: number, + page: number, + size: number +): Promise => { + try { + const response = await instance.get(`${axiosGetUserItemsURL}${userId}`, { + params: { page, size }, + }); + return response; + } catch (error) { + throw error; + } +}; diff --git a/frontend/src/Cabinet/assets/css/media.css b/frontend/src/Cabinet/assets/css/media.css index 967c663b5..9d6b56011 100644 --- a/frontend/src/Cabinet/assets/css/media.css +++ b/frontend/src/Cabinet/assets/css/media.css @@ -45,6 +45,36 @@ } } +/* mapInfo */ +#mapInfo.on { + transform: translateX(0%); +} + +/* storeInfo */ +#storeInfo.on { + transform: translateX(0%); +} + +/* itemInfo */ +#itemInfo.on { + transform: translateX(0%); +} + +/* ClubMemberInfoArea(rightSection) */ +/* #clubMemberInfoArea { + position: fixed; + top: 120px; + right: 0; + height: calc(100% - 120px); + z-index: 9; + transform: translateX(120%); + transition: transform 0.3s ease-in-out; + box-shadow: 0 0 40px 0 var(--bg-shadow); + } */ +#clubMemberInfoArea.on { + transform: translateX(0%); +} + /* search */ @media (max-width: 768px) { #searchBar { diff --git a/frontend/src/Cabinet/assets/data/ColorTheme.ts b/frontend/src/Cabinet/assets/data/ColorTheme.ts index 7df79f904..36aeb2958 100644 --- a/frontend/src/Cabinet/assets/data/ColorTheme.ts +++ b/frontend/src/Cabinet/assets/data/ColorTheme.ts @@ -8,29 +8,30 @@ const lightValues = css` --sys-presentation-main-color: var(--ref-blue-400); /* component variable */ + --white-text-with-bg-color: var(--ref-white); + --card-content-bg-color: var(--ref-white); --bg-color: var(--ref-white); + --card-bg-color: var(--ref-gray-100); + --color-picker-hash-bg-color: var(--ref-gray-200); + --capsule-btn-border-color: var(--ref-gray-200); + --map-floor-color: var(--ref-gray-200); + --presentation-no-event-past-color: var(--ref-gray-200); + --inventory-item-title-border-btm-color: var(--ref-gray-300); + --service-man-title-border-btm-color: var(--ref-gray-300); --line-color: var(--ref-gray-400); + --light-gray-line-btn-color: var(--ref-gray-400); + --gray-line-btn-color: var(--ref-gray-500); + --coin-log-date-color: var(--ref-gray-530); + --pie-chart-label-text-color: var(--ref-gray-600); + --notion-btn-text-color: var(--ref-gray-800); --normal-text-color: var(--ref-black); - --white-text-with-bg-color: var(--ref-white); - --card-content-bg-color: var(--ref-white); + --button-line-color: var(--sys-main-color); - --capsule-btn-border-color: var(--ref-gray-200); --capsule-btn-hover-bg-color: var(--ref-transparent-purple-100); - --presentation-no-event-past-color: var(--ref-gray-200); --presentation-no-event-cur-color: var(--bg-color); --color-picker-bg-color: var(--bg-color); - --color-picker-hash-bg-color: var(--ref-gray-200); - --color-picker-input-color: var(--ref-gray-500); --extension-card-active-btn-color: var(--sys-main-color); - --light-gray-line-btn-color: var(--ref-gray-400); - --card-bg-color: var(--ref-gray-100); - --map-floor-color: var(--ref-gray-200); - --service-man-title-border-btm-color: var(--ref-gray-300); - --toggle-switch-off-bg-color: var(--ref-gray-400); --presentation-card-speaker-name-color: var(--ref-gray-450); - --gray-line-btn-color: var(--ref-gray-500); - --pie-chart-label-text-color: var(--ref-gray-600); - --notion-btn-text-color: var(--ref-gray-800); --table-even-row-bg-color: var(--ref-purple-100); --presentation-table-even-row-bg-color: var(--ref-blue-100); --presentation-dropdown-select-color: var(--ref-blue-150); @@ -57,43 +58,48 @@ const lightValues = css` // 다크모드 변수 const darkValues = css` + /* system variable */ + --sys-main-color: var(--ref-purple-600); + --sys-default-main-color: var(--ref-purple-600); + --sys-default-mine-color: var(--ref-green-200); + --sys-presentation-main-color: var(--ref-blue-430); + + /* component variable */ + --white-text-with-bg-color: var(--ref-gray-100); + --card-content-bg-color: var(--ref-gray-550); --bg-color: var(--ref-gray-900); + --card-bg-color: var(--ref-gray-700); + --color-picker-hash-bg-color: var(--ref-gray-550); + --capsule-btn-border-color: var(--ref-gray-600); + --map-floor-color: var(--ref-gray-700); + --presentation-no-event-past-color: var(--ref-gray-900); + --inventory-item-title-border-btm-color: var(--ref-gray-500); --line-color: var(--ref-gray-500); + --service-man-title-border-btm-color: var(--ref-gray-600); + --light-gray-line-btn-color: var(--ref-gray-600); + --gray-line-btn-color: var(--ref-gray-400); + --coin-log-date-color: var(--ref-gray-400); + --pie-chart-label-text-color: var(--ref-gray-300); + --notion-btn-text-color: var(--ref-gray-200); --normal-text-color: var(--ref-gray-100); - --white-text-with-bg-color: var(--ref-gray-100); - --card-content-bg-color: var(--ref-gray-550); + --button-line-color: var(--sys-main-color); - --capsule-btn-border-color: var(--ref-gray-600); --capsule-btn-hover-bg-color: var(--ref-transparent-purple-200); - --presentation-no-event-past-color: var(--bg-color); --presentation-no-event-cur-color: var(--ref-gray-600); --color-picker-bg-color: var(--ref-gray-530); - --color-picker-hash-bg-color: var(--ref-gray-550); - --color-picker-input-color: var(--ref-gray-400); --extension-card-active-btn-color: var(--gray-line-btn-color); - --light-gray-line-btn-color: var(--service-man-title-border-btm-color); - --card-bg-color: var(--ref-gray-700); - --map-floor-color: var(--ref-gray-700); - --service-man-title-border-btm-color: var(--ref-gray-600); --presentation-card-speaker-name-color: var(--ref-gray-450); - --toggle-switch-off-bg-color: var(--ref-gray-500); - --gray-line-btn-color: var(--ref-gray-400); - --pie-chart-label-text-color: var(--ref-gray-300); - --notion-btn-text-color: var(--ref-gray-200); --table-even-row-bg-color: var(--ref-purple-700); --presentation-table-even-row-bg-color: var(--ref-blue-500); --presentation-dropdown-select-color: var(--ref-blue-470); - --sys-main-color: var(--ref-purple-600); - --sys-default-main-color: var(--ref-purple-600); - --sys-default-mine-color: var(--ref-green-200); - --sys-presentation-main-color: var(--ref-blue-430); - + /* cabinet */ --mine-color: var(--ref-green-200); --available-color: var(--sys-main-color); --pending-color: var(--sys-main-color); --expired-color: var(--ref-red-200); + /* shadow */ --modal-bg-shadow-color: var(--ref-black-shadow-300); --hover-box-shadow-color: var(--ref-black-shadow-400); --login-card-border-shadow-color: var(--ref-black-shadow-300); @@ -110,7 +116,7 @@ const darkValues = css` export const GlobalStyle = createGlobalStyle` :root { ${lightValues} - [color-theme="DARK"] { + [display-style="DARK"] { ${darkValues} } } diff --git a/frontend/src/Cabinet/assets/data/ManualContent.ts b/frontend/src/Cabinet/assets/data/ManualContent.ts index 7e2867301..7e92e6370 100644 --- a/frontend/src/Cabinet/assets/data/ManualContent.ts +++ b/frontend/src/Cabinet/assets/data/ManualContent.ts @@ -1,8 +1,13 @@ +import { ReactComponent as ClockImg } from "@/Cabinet/assets/images/clock.svg"; +import { ReactComponent as ClubIcon } from "@/Cabinet/assets/images/clubIcon.svg"; +import { ReactComponent as ExtensionIcon } from "@/Cabinet/assets/images/extension.svg"; +import { ReactComponent as PrivateIcon } from "@/Cabinet/assets/images/privateIcon.svg"; +import { ReactComponent as ShareIcon } from "@/Cabinet/assets/images/shareIcon.svg"; import ContentStatus from "@/Cabinet/types/enum/content.status.enum"; interface ContentStatusData { contentTitle: string; - imagePath: string; + iconComponent: React.FunctionComponent> | null; background: string; rentalPeriod?: string; capacity?: string; @@ -13,7 +18,7 @@ interface ContentStatusData { export const manualContentData: Record = { [ContentStatus.PRIVATE]: { contentTitle: "개인 사물함", - imagePath: "/src/Cabinet/assets/images/privateIcon.svg", + iconComponent: PrivateIcon, background: "linear-gradient(to bottom, var(--ref-purple-400), var(--ref-purple-600))", rentalPeriod: `${import.meta.env.VITE_PRIVATE_LENT_PERIOD}일`, @@ -35,7 +40,7 @@ export const manualContentData: Record = { }, [ContentStatus.SHARE]: { contentTitle: "공유 사물함", - imagePath: "/src/Cabinet/assets/images/shareIcon.svg", + iconComponent: ShareIcon, background: "linear-gradient(to bottom, var(--ref-blue-200), var(--ref-blue-300))", rentalPeriod: `${import.meta.env.VITE_SHARE_LENT_PERIOD}일 + n * ${ @@ -65,7 +70,7 @@ export const manualContentData: Record = { }, [ContentStatus.CLUB]: { contentTitle: "동아리 사물함", - imagePath: "/src/Cabinet/assets/images/clubIcon.svg", + iconComponent: ClubIcon, background: "linear-gradient(to bottom, var(--ref-pink-100), var(--ref-pink-200))", rentalPeriod: "상세내용 참조", @@ -93,7 +98,7 @@ export const manualContentData: Record = { }, [ContentStatus.PENDING]: { contentTitle: "오픈예정", - imagePath: "", + iconComponent: null, background: "var(--sys-main-color)", contentText: `◦ 상세 내용
사물함 반납 시, 해당 사물함은 오픈예정 상태로 변경됩니다.
@@ -109,7 +114,7 @@ export const manualContentData: Record = { }, [ContentStatus.IN_SESSION]: { contentTitle: "대기중", - imagePath: "/src/Cabinet/assets/images/clock.svg", + iconComponent: ClockImg, background: "var(--card-bg-color)", contentText: `◦ 상세 내용
공유 사물함 대여 시 10분간의 대기 시간이 발생합니다.
@@ -129,7 +134,7 @@ export const manualContentData: Record = { }, [ContentStatus.EXTENSION]: { contentTitle: "연장권 이용방법 안내서", - imagePath: "/src/Cabinet/assets/images/extension.svg", + iconComponent: ExtensionIcon, background: "var(--card-bg-color)", contentText: `◦ 연장권 취득 조건
diff --git a/frontend/src/Cabinet/assets/data/SlackAlarm.ts b/frontend/src/Cabinet/assets/data/SlackAlarm.ts index 123470f87..c89bb73cf 100644 --- a/frontend/src/Cabinet/assets/data/SlackAlarm.ts +++ b/frontend/src/Cabinet/assets/data/SlackAlarm.ts @@ -68,7 +68,7 @@ export const SlackAlarmTemplates: ISlackAlarmTemplate[] = [ :happy_ccabi: 이전 사용자의 짐과 관련한 문의는 데스크에 문의 부탁드립니다! :pushpin: 공유 사물함을 대여했는데 비밀번호는 어디서 알 수 있을까요? :happy_ccabi: 같이 사용하는 사람이 있다면 대여 내역에서 공유 메모에 적혀 있을 수 있습니다. 또는 함께 사용하는 분에게 여쭤보세요! - :pushpin: 사물함을 연체 했는데 패널티는 무엇인가요? + :pushpin: 사물함을 연체 했는데 페널티는 무엇인가요? :happy_ccabi: 연체일만큼 누적 연체일이 증가하고, 누적일 만큼 대여가 불가능합니다:face_holding_back_tears:`, }, { diff --git a/frontend/src/Cabinet/assets/data/mapPositionData.ts b/frontend/src/Cabinet/assets/data/mapPositionData.ts index 72287ecd8..542e63253 100644 --- a/frontend/src/Cabinet/assets/data/mapPositionData.ts +++ b/frontend/src/Cabinet/assets/data/mapPositionData.ts @@ -297,3 +297,9 @@ export const mapPostionData: IFloorSectionsInfo = { }, ], }; + +export const clubSectionsData = [ + "Cluster X - 1", + "Cluster X - 2", + "Cluster X - 3", +]; diff --git a/frontend/src/Cabinet/assets/data/maps.ts b/frontend/src/Cabinet/assets/data/maps.ts index 41f6f425e..8706697ba 100644 --- a/frontend/src/Cabinet/assets/data/maps.ts +++ b/frontend/src/Cabinet/assets/data/maps.ts @@ -1,8 +1,17 @@ import { ReactComponent as ClubIcon } from "@/Cabinet/assets/images/clubIcon.svg"; +import { ReactComponent as ExtensionImg } from "@/Cabinet/assets/images/extension.svg"; import { ReactComponent as PrivateIcon } from "@/Cabinet/assets/images/privateIcon.svg"; import { ReactComponent as ShareIcon } from "@/Cabinet/assets/images/shareIcon.svg"; +import { ReactComponent as AlarmImg } from "@/Cabinet/assets/images/storeAlarm.svg"; +import { ReactComponent as SwapImg } from "@/Cabinet/assets/images/storeMove.svg"; +import { ReactComponent as PenaltyImg } from "@/Cabinet/assets/images/storePenalty.svg"; import CabinetStatus from "@/Cabinet/types/enum/cabinet.status.enum"; import CabinetType from "@/Cabinet/types/enum/cabinet.type.enum"; +import { + StoreExtensionType, + StoreItemType, + StorePenaltyType, +} from "@/Cabinet/types/enum/store.enum"; export enum additionalModalType { MODAL_RETURN = "MODAL_RETURN", @@ -141,7 +150,7 @@ export const modalPropsMap = { }, MODAL_OVERDUE_PENALTY: { type: "error", - title: "패널티 안내", + title: "페널티 안내", confirmMessage: "오늘 하루동안 보지않기", }, MODAL_INVITATION_CODE: { @@ -169,6 +178,7 @@ export const modalPropsMap = { title: "이사하기", confirmMessage: "네, 이사할게요", }, + MODAL_CLUB_ADD_MEM: { type: "confirm", title: "동아리 멤버 추가", @@ -194,6 +204,16 @@ export const modalPropsMap = { title: "비밀번호 수적", confirmMessage: "입력", }, + MODAL_STORE_SWAP: { + type: "confirm", + title: "이사권 사용 안내", + confirmMessage: "네, 사용할게요", + }, + MODAL_SECTION_ALERT: { + type: "confirm", + title: "알림 등록권 사용 안내", + confirmMessage: "네, 사용할게요", + }, }; export const cabinetFilterMap = { @@ -221,3 +241,29 @@ export const cabinetTypeLabelMap = { [CabinetType.PRIVATE]: "개인 사물함", [CabinetType.SHARE]: "공유 사물함", }; + +export const ItemIconMap = { + [StoreItemType.EXTENSION]: ExtensionImg, + [StoreItemType.SWAP]: SwapImg, + [StoreItemType.ALARM]: AlarmImg, + [StoreItemType.PENALTY]: PenaltyImg, +}; + +export const ItemTypeLabelMap = { + [StoreItemType.EXTENSION]: "연장권", + [StoreItemType.SWAP]: "이사권", + [StoreItemType.ALARM]: "알림 등록권", + [StoreItemType.PENALTY]: "페널티 감면권", +}; + +export const ItemTypePenaltyMap = { + [StorePenaltyType.PENALTY_3]: "3일", + [StorePenaltyType.PENALTY_7]: "7일", + [StorePenaltyType.PENALTY_31]: "31일", +}; + +export const ItemTypeExtensionMap = { + [StoreExtensionType.EXTENSION_3]: "3일", + [StoreExtensionType.EXTENSION_15]: "15일", + [StoreExtensionType.EXTENSION_31]: "31일", +}; diff --git a/frontend/src/Cabinet/assets/images/clubIcon.svg b/frontend/src/Cabinet/assets/images/clubIcon.svg index 2f9b7a73d..c5b48c59a 100644 --- a/frontend/src/Cabinet/assets/images/clubIcon.svg +++ b/frontend/src/Cabinet/assets/images/clubIcon.svg @@ -1,7 +1,7 @@ - - - - - - + + + + + + diff --git a/frontend/src/Cabinet/assets/images/coinIcon.svg b/frontend/src/Cabinet/assets/images/coinIcon.svg new file mode 100644 index 000000000..7e1fb4f12 --- /dev/null +++ b/frontend/src/Cabinet/assets/images/coinIcon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/frontend/src/Cabinet/assets/images/crown.svg b/frontend/src/Cabinet/assets/images/crown.svg index 9bed5a2d3..537db45f4 100644 --- a/frontend/src/Cabinet/assets/images/crown.svg +++ b/frontend/src/Cabinet/assets/images/crown.svg @@ -1,3 +1,3 @@ - - + + diff --git a/frontend/src/Cabinet/assets/images/dollar-circle.svg b/frontend/src/Cabinet/assets/images/dollar-circle.svg new file mode 100644 index 000000000..3fc1d9cf2 --- /dev/null +++ b/frontend/src/Cabinet/assets/images/dollar-circle.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/frontend/src/Cabinet/assets/images/dropdownChevron.svg b/frontend/src/Cabinet/assets/images/dropdownChevron.svg index 2917ac0ff..efcdd6b14 100644 --- a/frontend/src/Cabinet/assets/images/dropdownChevron.svg +++ b/frontend/src/Cabinet/assets/images/dropdownChevron.svg @@ -1,3 +1,3 @@ - + diff --git a/frontend/src/Cabinet/assets/images/extension.svg b/frontend/src/Cabinet/assets/images/extension.svg index 3611f0a48..478a4c1f0 100644 --- a/frontend/src/Cabinet/assets/images/extension.svg +++ b/frontend/src/Cabinet/assets/images/extension.svg @@ -1,4 +1,4 @@ - + diff --git a/frontend/src/Cabinet/assets/images/filledHeart.svg b/frontend/src/Cabinet/assets/images/filledHeart.svg new file mode 100644 index 000000000..d2a85e863 --- /dev/null +++ b/frontend/src/Cabinet/assets/images/filledHeart.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/src/Cabinet/assets/images/leader.svg b/frontend/src/Cabinet/assets/images/leader.svg deleted file mode 100644 index 627d7d003..000000000 --- a/frontend/src/Cabinet/assets/images/leader.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/src/Cabinet/assets/images/lineHeart.svg b/frontend/src/Cabinet/assets/images/lineHeart.svg new file mode 100644 index 000000000..fd45be7a7 --- /dev/null +++ b/frontend/src/Cabinet/assets/images/lineHeart.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/src/Cabinet/assets/images/privateIcon.svg b/frontend/src/Cabinet/assets/images/privateIcon.svg index 90935b75a..4517cc27a 100644 --- a/frontend/src/Cabinet/assets/images/privateIcon.svg +++ b/frontend/src/Cabinet/assets/images/privateIcon.svg @@ -1,4 +1,4 @@ - + diff --git a/frontend/src/Cabinet/assets/images/shareIcon.svg b/frontend/src/Cabinet/assets/images/shareIcon.svg index 6ecff41ea..1bdfd8f4d 100644 --- a/frontend/src/Cabinet/assets/images/shareIcon.svg +++ b/frontend/src/Cabinet/assets/images/shareIcon.svg @@ -1,4 +1,4 @@ - + diff --git a/frontend/src/Cabinet/assets/images/storeAlarm.svg b/frontend/src/Cabinet/assets/images/storeAlarm.svg new file mode 100644 index 000000000..fdec88cdb --- /dev/null +++ b/frontend/src/Cabinet/assets/images/storeAlarm.svg @@ -0,0 +1,4 @@ + + + + diff --git a/frontend/src/Cabinet/assets/images/storeCoin.svg b/frontend/src/Cabinet/assets/images/storeCoin.svg new file mode 100644 index 000000000..2fe40696a --- /dev/null +++ b/frontend/src/Cabinet/assets/images/storeCoin.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/frontend/src/Cabinet/assets/images/storeCoinCheckFin.svg b/frontend/src/Cabinet/assets/images/storeCoinCheckFin.svg new file mode 100644 index 000000000..2e5c1f371 --- /dev/null +++ b/frontend/src/Cabinet/assets/images/storeCoinCheckFin.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/frontend/src/Cabinet/assets/images/storeCoinCheckOff.svg b/frontend/src/Cabinet/assets/images/storeCoinCheckOff.svg new file mode 100644 index 000000000..557b98e8e --- /dev/null +++ b/frontend/src/Cabinet/assets/images/storeCoinCheckOff.svg @@ -0,0 +1,4 @@ + + + + diff --git a/frontend/src/Cabinet/assets/images/storeCoinCheckOn.svg b/frontend/src/Cabinet/assets/images/storeCoinCheckOn.svg new file mode 100644 index 000000000..51cb824a0 --- /dev/null +++ b/frontend/src/Cabinet/assets/images/storeCoinCheckOn.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/frontend/src/Cabinet/assets/images/storeCoinNav.svg b/frontend/src/Cabinet/assets/images/storeCoinNav.svg new file mode 100644 index 000000000..206c5d49b --- /dev/null +++ b/frontend/src/Cabinet/assets/images/storeCoinNav.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/frontend/src/Cabinet/assets/images/storeExtension.svg b/frontend/src/Cabinet/assets/images/storeExtension.svg new file mode 100644 index 000000000..91c16096c --- /dev/null +++ b/frontend/src/Cabinet/assets/images/storeExtension.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/frontend/src/Cabinet/assets/images/storeIconGray.svg b/frontend/src/Cabinet/assets/images/storeIconGray.svg new file mode 100644 index 000000000..5400d92fb --- /dev/null +++ b/frontend/src/Cabinet/assets/images/storeIconGray.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/frontend/src/Cabinet/assets/images/storeMove.svg b/frontend/src/Cabinet/assets/images/storeMove.svg new file mode 100644 index 000000000..4500e4867 --- /dev/null +++ b/frontend/src/Cabinet/assets/images/storeMove.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/frontend/src/Cabinet/assets/images/storePenalty.svg b/frontend/src/Cabinet/assets/images/storePenalty.svg new file mode 100644 index 000000000..158760b33 --- /dev/null +++ b/frontend/src/Cabinet/assets/images/storePenalty.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/frontend/src/Cabinet/components/AdminInfo/Chart/CoinUseLineChart.tsx b/frontend/src/Cabinet/components/AdminInfo/Chart/CoinUseLineChart.tsx new file mode 100644 index 000000000..80eb28c0b --- /dev/null +++ b/frontend/src/Cabinet/components/AdminInfo/Chart/CoinUseLineChart.tsx @@ -0,0 +1,131 @@ +import { ResponsiveLine } from "@nivo/line"; +import styled from "styled-components"; +import { ICoinStatisticsDto } from "@/Cabinet/types/dto/admin.dto"; +import { CoinUseDateType, CoinUseType } from "@/Cabinet/types/enum/store.enum"; + +const CoinUseLineChart = ({ + toggleType, + coinToggleType, + totalCoinUseData, +}: { + toggleType: CoinUseDateType; + coinToggleType: CoinUseType; + totalCoinUseData: ICoinStatisticsDto | undefined; +}) => { + if (totalCoinUseData === undefined) { + return null; + } + const formattedData = [ + { + id: "issuedCoin", + data: + totalCoinUseData?.issuedCoin?.map((item) => ({ + x: item.date, + y: item.amount, + })) || [], + }, + { + id: "usedCoin", + data: + totalCoinUseData?.usedCoin?.map((item) => ({ + x: item.date, + y: item.amount, + })) || [], + }, + ]; + + // 발행코인, 미사용 코인, 사용코인 나눠서 보내주는 함수 + const filteredData = formattedData.filter( + (data) => data.id === coinToggleType + ); + + return ( + <> + + { + return ( + + +

+ date : {point.point.data.xFormatted} +

+

+ , coin : {point.point.data.yFormatted} +

+ + ); + }} + isInteractive={true} + animate={true} + data={filteredData} + margin={{ top: 50, right: 60, bottom: 50, left: 60 }} + // xFormat="time:%b %d" + // %b -> 영어로 달 표시 + xFormat="time:%m.%d" + xScale={{ + format: "%Y-%m-%d", + precision: "day", + type: "time", + useUTC: false, + }} + yScale={{ + type: "linear", + min: 0, + max: "auto", + }} + yFormat=" >0" + // curve="cardinal" + curve="monotoneX" + axisTop={null} + colors={["var(--sys-main-color)"]} + axisRight={null} + axisBottom={{ + format: "%m.%d", + legendOffset: -12, + tickValues: `every 1 ${toggleType.toLowerCase()}`, + }} + axisLeft={{ + legendOffset: 12, + }} + enableGridX={false} + pointSize={0} + enableArea={true} + useMesh={true} + /> + + + ); +}; + +const LineChartStyled = styled.div` + height: 90%; + width: 90%; + display: flex; + justify-content: center; + align-items: center; +`; + +const ToolTipStyled = styled.div<{ color: string }>` + height: 24px; + background-color: var(--bg-color); + box-shadow: var(--left-nav-border-shadow-color) 0 1px 2px; + color: var(--normal-text-color); + display: flex; + align-items: center; + padding: 5px 9px; + border-radius: 2px; + + & > span { + display: block; + width: 12px; + height: 12px; + background-color: ${(props) => props.color}; + margin-right: 8px; + } +`; + +export default CoinUseLineChart; diff --git a/frontend/src/Cabinet/components/AdminInfo/Chart/ItemBarChart.tsx b/frontend/src/Cabinet/components/AdminInfo/Chart/ItemBarChart.tsx new file mode 100644 index 000000000..a85bfb3c7 --- /dev/null +++ b/frontend/src/Cabinet/components/AdminInfo/Chart/ItemBarChart.tsx @@ -0,0 +1,163 @@ +import { ResponsiveBar } from "@nivo/bar"; +import { useEffect, useState } from "react"; +import styled from "styled-components"; +import { IItemUseCountDto } from "@/Cabinet/types/dto/admin.dto"; + +interface ITransformedItem { + item: string; + [key: string]: number | string; +} + +const transformData = (itemArr: IItemUseCountDto[]): ITransformedItem[] => { + const transformedData: ITransformedItem[] = []; + + itemArr.forEach((item) => { + const { itemName, itemDetails, userCount } = item; + const existingItem = transformedData.find( + (transformed) => transformed.item === itemName + ); + + if (existingItem) { + existingItem[`${itemName}-${itemDetails}`] = userCount; + } else { + const newItem: ITransformedItem = { + item: itemName, + }; + + if (itemName === itemDetails) { + newItem[itemDetails] = userCount; + } else { + newItem[`${itemName}-${itemDetails}`] = userCount; + } + + transformedData.push(newItem); + } + }); + + return transformedData; +}; + +const ItemBarChart = ({ data }: { data: IItemUseCountDto[] }) => { + const itemData: ITransformedItem[] = transformData(data); + return ( + + + + e.id + ": " + e.formattedValue + " in item: " + e.indexValue + } + minValue={0} + /> + + + + + ); +}; + +const ItemBarChartStyled = styled.div` + height: 90%; + width: 100%; + display: flex; + justify-content: center; + align-items: center; + @media screen and (min-width: 768px) { + padding-right: 60px; + padding-left: 60px; + } +`; + +const ResponsiveBarStyled = styled.div` + height: 100%; + width: 100%; +`; + +const ItemLegendsStyled = styled.div` + height: 100%; + width: 0%; + background-color: #d5d5ff; +`; + +export default ItemBarChart; + +// const itemList: IItemUseCountDto[] = [ +// { itemName: "연장권", itemDetails: "출석 연장권 보상", userCount: 0 }, +// { itemName: "연장권", itemDetails: "31일", userCount: 530 }, +// { itemName: "연장권", itemDetails: "15일", userCount: 22 }, +// { itemName: "연장권", itemDetails: "3일", userCount: 3}, +// { itemName: "페널티 감면권", itemDetails: "31일", userCount: 1 }, +// { itemName: "페널티 감면권", itemDetails: "7일", userCount: 30 }, +// { itemName: "페널티 감면권", itemDetails: "3일", userCount: 10 }, +// { itemName: "이사권", itemDetails: "이사권", userCount: 60 }, +// { itemName: "알림 등록권", itemDetails: "알림 등록권", userCount: 100 }, +// ]; diff --git a/frontend/src/Cabinet/components/AdminInfo/Chart/PieChartCoin.tsx b/frontend/src/Cabinet/components/AdminInfo/Chart/PieChartCoin.tsx new file mode 100644 index 000000000..5ee1ccfbb --- /dev/null +++ b/frontend/src/Cabinet/components/AdminInfo/Chart/PieChartCoin.tsx @@ -0,0 +1,117 @@ +import { ResponsivePie } from "@nivo/pie"; +import styled from "styled-components"; + +// make sure parent container have a defined height when using +// responsive component, otherwise height will be 0 and +// no chart will be rendered. +// website examples showcase many properties, +// you'll often use just a few of them. + +interface ICoinInfo { + used: number; + unused: number; +} + +type CoinStatus = { + [used: string]: number; + unused: number; +}; + +type TextMap = { + [used: string]: string; + unused: string; +}; + +const convert = (data: ICoinInfo[]) => { + const textMap: TextMap = { + used: "사용", + unused: "보유", + }; + const obj: CoinStatus = data.reduce( + (acc, cur) => { + acc.used += cur.used; + acc.unused += cur.unused; + return acc; + }, + { used: 0, unused: 0 } as CoinStatus + ); + return Object.keys(obj).map((key: string) => ({ + id: textMap[key], + value: obj[key], + })); +}; + +const PieChartCoin = ({ data }: { data: ICoinInfo[] }) => { + return ( + + + + ); +}; + +const PieChartStyled = styled.div` + height: 90%; + width: 90%; + display: flex; + justify-content: center; + align-items: center; +`; +export default PieChartCoin; diff --git a/frontend/src/Cabinet/components/AdminInfo/Chart/StoreHorizontalBarChart.tsx b/frontend/src/Cabinet/components/AdminInfo/Chart/StoreHorizontalBarChart.tsx new file mode 100644 index 000000000..b35a86480 --- /dev/null +++ b/frontend/src/Cabinet/components/AdminInfo/Chart/StoreHorizontalBarChart.tsx @@ -0,0 +1,143 @@ +import { ResponsiveBar } from "@nivo/bar"; +import styled from "styled-components"; +import { ICoinCollectInfoDto } from "@/Cabinet/types/dto/store.dto"; + +const keys = ["5회", "10회", "15회", "20회", "전체"]; + +interface IConvertedData { + cnt: string; + [key: string]: number | string; +} + +const convert = (data: ICoinCollectInfoDto[]) => { + let userTotalPerCnt = 0; + let userTotal = 0; + let ary: IConvertedData[] = []; + + data.forEach((cur) => { + userTotalPerCnt += cur.userCount; + + // NOTE : 정책에 따라 변경 + // 현재 정책 - 0~5, 6~10, 11~15, 16~20회 + if ( + cur.coinCount % 5 === 0 && + cur.coinCount / 5 >= 1 && + cur.coinCount / 5 <= 4 + ) { + ary = [ + ...ary, + { + cnt: cur.coinCount + "회", + [cur.coinCount + "회"]: userTotalPerCnt, + }, + ]; + userTotal += userTotalPerCnt; + userTotalPerCnt = 0; + } + }); + + return [...ary, { cnt: "전체", ["전체"]: userTotal }]; +}; + +const StoreHorizontalBarChart = ({ data }: { data: ICoinCollectInfoDto[] }) => { + return ( + + { + return ( + + + {point.data.cnt + ":"} {point.value}명 + + ); + }} + theme={{ + legends: { text: { fontSize: "14px" } }, + labels: { text: { fontSize: "14px", fill: "var(--ref-gray-500)" } }, + axis: { ticks: { text: { fontSize: "14px" } } }, + textColor: "var(--normal-text-color)", + }} + margin={{ top: 20, right: 70, bottom: 80, left: 80 }} + colors={[ + "var(--ref-purple-200)", + "var(--ref-purple-300)", + "var(--sys-main-color)", + "var(--ref-purple-650)", + "var(--ref-purple-690)", + "var(--ref-purple-200)", + ]} + legends={[ + { + dataFrom: "keys", + anchor: "bottom", + direction: "row", + itemWidth: 56, + itemHeight: 0, + itemTextColor: "var(--gray-line-btn-color)", + translateX: 0, + translateY: 44, + itemsSpacing: 2, + itemDirection: "top-to-bottom", + symbolSize: 12, + effects: [ + { + on: "hover", + style: { + itemTextColor: "var(--normal-text-color)", + }, + }, + ], + }, + ]} + labelSkipWidth={1} + /> + + ); +}; + +const HalfPieChartStyled = styled.div` + height: 80%; + width: 90%; + display: flex; + justify-content: center; + align-items: center; + + @media screen and (max-width: 768px) { + width: 100%; + } +`; + +const ToolTipStyled = styled.div<{ color: string }>` + height: 24px; + background-color: var(--bg-color); + box-shadow: var(--left-nav-border-shadow-color) 0 1px 2px; + color: var(--normal-text-color); + display: flex; + align-items: center; + padding: 5px 9px; + border-radius: 2px; + + & > span { + display: block; + width: 12px; + height: 12px; + background-color: ${(props) => props.color}; + margin-right: 8px; + } + & > strong { + padding-left: 4px; + } +`; + +export default StoreHorizontalBarChart; diff --git a/frontend/src/Cabinet/components/AdminInfo/Table/AdminTable.tsx b/frontend/src/Cabinet/components/AdminInfo/Table/AdminTable.tsx index 68bc71ed0..968e7e752 100644 --- a/frontend/src/Cabinet/components/AdminInfo/Table/AdminTable.tsx +++ b/frontend/src/Cabinet/components/AdminInfo/Table/AdminTable.tsx @@ -104,7 +104,7 @@ const TheadStyled = styled.thead` height: 45px; line-height: 45px; background-color: var(--sys-main-color); - color: var(--bg-color); + color: var(--white-text-with-bg-color); `; const TbodyStyled = styled.tbody` diff --git a/frontend/src/Cabinet/components/Available/FloorContainer.tsx b/frontend/src/Cabinet/components/Available/FloorContainer.tsx index 0f91d0992..875309a41 100644 --- a/frontend/src/Cabinet/components/Available/FloorContainer.tsx +++ b/frontend/src/Cabinet/components/Available/FloorContainer.tsx @@ -3,8 +3,9 @@ import { useLocation } from "react-router-dom"; import styled from "styled-components"; import AdminCabinetListItem from "@/Cabinet/components/CabinetList/CabinetListItem/AdminCabinetListItem"; import CabinetListItem from "@/Cabinet/components/CabinetList/CabinetListItem/CabinetListItem"; -import { CabinetPreviewInfo } from "@/Cabinet/types/dto/cabinet.dto"; +import UnavailableDataInfo from "@/Cabinet/components/Common/UnavailableDataInfo"; import { ReactComponent as SelectImg } from "@/Cabinet/assets/images/select.svg"; +import { CabinetPreviewInfo } from "@/Cabinet/types/dto/cabinet.dto"; // 하나의 층에 대한 타이틀과 캐비넷 리스트를 담고 있는 컴포넌트 const FloorContainer = ({ @@ -21,13 +22,12 @@ const FloorContainer = ({ const toggle = () => { setIsToggled(!isToggled); }; - return (

{floorNumber}층

{pendingCabinetsList.length !== 0 ? ( @@ -41,13 +41,14 @@ const FloorContainer = ({ )} ) : ( - -

해당 층에는 사용 가능한 사물함이 없습니다

- noAvailable -
+ !isToggled && ( + + + + ) )}
); @@ -67,6 +68,7 @@ const FloorTitleStyled = styled.div<{ isToggled: boolean }>` padding-right: 5px; border-bottom: 1.5px solid var(--service-man-title-border-btm-color); cursor: pointer; + button { all: initial; cursor: inherit; @@ -75,6 +77,10 @@ const FloorTitleStyled = styled.div<{ isToggled: boolean }>` transform: ${(props) => props.isToggled ? "rotate(180deg)" : "rotate(0deg)"}; } + + & > button > svg > path { + stroke: var(--gray-line-btn-color); + } `; const FloorCabinetsContainerStyled = styled.div<{ isToggled: boolean }>` @@ -89,21 +95,11 @@ const FloorCabinetsContainerStyled = styled.div<{ isToggled: boolean }>` } `; -const NoAvailableCabinetMessageStyled = styled.div<{ isToggled: boolean }>` - display: ${(props) => (props.isToggled ? "none" : "flex")}; - align-items: center; - margin-top: 20px; - margin-left: 5px; - p { - color: var(--gray-line-btn-color); - line-height: 1.5; - word-break: keep-all; - } - img { - width: 30px; - aspect-ratio: 1 / 1; - margin-left: 8px; - } +const UnavailableCabinetMsgWrapperStyled = styled.div` + width: 100%; + display: flex; + padding-top: 20px; + padding-left: 5px; `; export default FloorContainer; diff --git a/frontend/src/Cabinet/components/CabinetInfoArea/AdminCabinetInfoArea.tsx b/frontend/src/Cabinet/components/CabinetInfoArea/AdminCabinetInfoArea.tsx index 8caf51754..018fb5d33 100644 --- a/frontend/src/Cabinet/components/CabinetInfoArea/AdminCabinetInfoArea.tsx +++ b/frontend/src/Cabinet/components/CabinetInfoArea/AdminCabinetInfoArea.tsx @@ -12,6 +12,7 @@ import { TAdminModalState, } from "@/Cabinet/components/CabinetInfoArea/CabinetInfoArea.container"; import ButtonContainer from "@/Cabinet/components/Common/Button"; +import SelectInduction from "@/Cabinet/components/Common/SelectInduction"; import ClubLentModal from "@/Cabinet/components/Modals/LentModal/ClubLentModal"; import AdminReturnModal from "@/Cabinet/components/Modals/ReturnModal/AdminReturnModal"; import StatusModalContainer from "@/Cabinet/components/Modals/StatusModal/StatusModal.container"; @@ -66,15 +67,10 @@ const AdminCabinetInfoArea: React.FC<{ (multiSelectTargetInfo && targetCabinetInfoList!.length < 1) ) return ( - - - - - - 사물함/유저를
- 선택해주세요 -
-
+ ); // 다중 선택 모드 진입 후 캐비넷을 하나 이상 선택했을 시 if (multiSelectTargetInfo) { @@ -196,14 +192,6 @@ const AdminCabinetInfoArea: React.FC<{ ); }; -const NotSelectedStyled = styled.div` - height: 100%; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; -`; - const CabinetDetailAreaStyled = styled.div` height: 100%; display: flex; @@ -212,17 +200,6 @@ const CabinetDetailAreaStyled = styled.div` align-items: center; `; -const CabiLogoStyled = styled.div` - width: 35px; - height: 35px; - margin-bottom: 10px; - svg { - .logo_svg__currentPath { - fill: var(--sys-main-color); - } - } -`; - const CabinetTypeIconStyled = styled.div` width: 24px; height: 24px; @@ -233,6 +210,11 @@ const CabinetTypeIconStyled = styled.div` & path { stroke: var(--normal-text-color); } + + & > svg { + width: 24px; + height: 24px; + } `; const LinkTextStyled = styled.div` diff --git a/frontend/src/Cabinet/components/CabinetInfoArea/CabinetInfoArea.tsx b/frontend/src/Cabinet/components/CabinetInfoArea/CabinetInfoArea.tsx index 09be27808..8ec38283a 100644 --- a/frontend/src/Cabinet/components/CabinetInfoArea/CabinetInfoArea.tsx +++ b/frontend/src/Cabinet/components/CabinetInfoArea/CabinetInfoArea.tsx @@ -7,6 +7,7 @@ import { } from "@/Cabinet/components/CabinetInfoArea/CabinetInfoArea.container"; import CountTimeContainer from "@/Cabinet/components/CabinetInfoArea/CountTime/CountTime.container"; import ButtonContainer from "@/Cabinet/components/Common/Button"; +import SelectInduction from "@/Cabinet/components/Common/SelectInduction"; import CancelModal from "@/Cabinet/components/Modals/CancelModal/CancelModal"; import ExtendModal from "@/Cabinet/components/Modals/ExtendModal/ExtendModal"; import InvitationCodeModalContainer from "@/Cabinet/components/Modals/InvitationCodeModal/InvitationCodeModal.container"; @@ -23,7 +24,7 @@ import { cabinetStatusColorMap, } from "@/Cabinet/assets/data/maps"; import alertImg from "@/Cabinet/assets/images/cautionSign.svg"; -import { ReactComponent as ExtensionImg } from "@/Cabinet/assets/images/extensionTicket.svg"; +import { ReactComponent as ExtensionImg } from "@/Cabinet/assets/images/extension.svg"; import { ReactComponent as LogoImg } from "@/Cabinet/assets/images/logo.svg"; import CabinetStatus from "@/Cabinet/types/enum/cabinet.status.enum"; @@ -50,11 +51,8 @@ const CabinetInfoArea: React.FC<{ closeModal, isSwappable, }) => { - const isExtensionVisible = - isMine && - isExtensible && - selectedCabinetInfo && - selectedCabinetInfo.status !== "IN_SESSION"; + const isExtensionVisible = isMine && selectedCabinetInfo; + // selectedCabinetInfo.status !== "IN_SESSION"; const isHoverBoxVisible = selectedCabinetInfo && selectedCabinetInfo.lentsLength <= 1 && @@ -64,15 +62,10 @@ const CabinetInfoArea: React.FC<{ : null; return selectedCabinetInfo === null ? ( - - - - - - 사물함을
- 선택해주세요 -
-
+ ) : ( @@ -193,13 +186,10 @@ const CabinetInfoArea: React.FC<{ selectedCabinetInfo.lentType === "SHARE" } > - - {"연장권 사용하기"} + + + + 연장권 사용하기 )} {isExtensionVisible && isHoverBoxVisible && ( @@ -272,14 +262,6 @@ const CabinetInfoArea: React.FC<{ ); }; -const NotSelectedStyled = styled.div` - height: 100%; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; -`; - const CabinetDetailAreaStyled = styled.div` height: 100%; max-width: 330px; @@ -289,17 +271,6 @@ const CabinetDetailAreaStyled = styled.div` align-items: center; `; -const CabiLogoStyled = styled.div` - width: 35px; - height: 35px; - margin-bottom: 10px; - svg { - .logo_svg__currentPath { - fill: var(--sys-main-color); - } - } -`; - const CabinetTypeIconStyled = styled.div` width: 24px; height: 24px; @@ -310,6 +281,11 @@ const CabinetTypeIconStyled = styled.div` & path { stroke: var(--normal-text-color); } + + & > svg { + width: 24px; + height: 24px; + } `; const TextStyled = styled.p<{ fontSize: string; fontColor: string }>` @@ -460,6 +436,21 @@ const ButtonContainerStyled = styled.button` @media (max-height: 745px) { margin-bottom: 8px; } + + & > span { + height: 20px; + line-height: 18px; + } +`; + +const ExtensionImgStyled = styled.div` + width: 24px; + height: 24px; + margin-right: 10px; + + & > svg > path { + stroke: var(--sys-main-color); + } `; export default CabinetInfoArea; diff --git a/frontend/src/Cabinet/components/CabinetInfoArea/CountTime/CountTime.container.tsx b/frontend/src/Cabinet/components/CabinetInfoArea/CountTime/CountTime.container.tsx index b69de2ce9..55ae40cf3 100644 --- a/frontend/src/Cabinet/components/CabinetInfoArea/CountTime/CountTime.container.tsx +++ b/frontend/src/Cabinet/components/CabinetInfoArea/CountTime/CountTime.container.tsx @@ -12,14 +12,13 @@ import { axiosCabinetById, axiosMyLentInfo, } from "@/Cabinet/api/axios/axios.custom"; +import { padTo2Digits } from "@/Cabinet/utils/dateUtils"; const returnCountTime = (countDown: number) => { - const minutes = Math.floor((countDown % (1000 * 60 * 60)) / (1000 * 60)) - .toString() - .padStart(2, "0"); - const seconds = Math.floor((countDown % (1000 * 60)) / 1000) - .toString() - .padStart(2, "0"); + const minutes = padTo2Digits( + Math.floor((countDown % (1000 * 60 * 60)) / (1000 * 60)) + ); + const seconds = padTo2Digits(Math.floor((countDown % (1000 * 60)) / 1000)); return [minutes, seconds]; }; diff --git a/frontend/src/Cabinet/components/CabinetList/CabinetListItem/AdminCabinetListItem.tsx b/frontend/src/Cabinet/components/CabinetList/CabinetListItem/AdminCabinetListItem.tsx index 5fba4ff25..186e0732b 100644 --- a/frontend/src/Cabinet/components/CabinetList/CabinetListItem/AdminCabinetListItem.tsx +++ b/frontend/src/Cabinet/components/CabinetList/CabinetListItem/AdminCabinetListItem.tsx @@ -17,6 +17,7 @@ import { } from "@/Cabinet/types/dto/cabinet.dto"; import CabinetStatus from "@/Cabinet/types/enum/cabinet.status.enum"; import CabinetType from "@/Cabinet/types/enum/cabinet.type.enum"; +import CabinetDetailAreaType from "@/Cabinet/types/enum/cabinetDetailArea.type.enum"; import { axiosCabinetById } from "@/Cabinet/api/axios/axios.custom"; import useMenu from "@/Cabinet/hooks/useMenu"; import useMultiSelect from "@/Cabinet/hooks/useMultiSelect"; @@ -28,7 +29,7 @@ const AdminCabinetListItem = (props: CabinetPreviewInfo): JSX.Element => { const setTargetCabinetInfo = useSetRecoilState( targetCabinetInfoState ); - const setSelectedTypeOnSearch = useSetRecoilState( + const setSelectedTypeOnSearch = useSetRecoilState( selectedTypeOnSearchState ); const { openCabinet, closeCabinet } = useMenu(); @@ -64,7 +65,7 @@ const AdminCabinetListItem = (props: CabinetPreviewInfo): JSX.Element => { } setCurrentCabinetId(cabinetId); - setSelectedTypeOnSearch("CABINET"); + setSelectedTypeOnSearch(CabinetDetailAreaType.CABINET); async function getData(cabinetId: number) { try { const { data } = await axiosCabinetById(cabinetId); @@ -253,7 +254,11 @@ const CabinetIconContainerStyled = styled.div<{ & > svg > path { stroke: ${(props) => cabinetFilterMap[props.status]}; - transform: scale(0.7); + } + + & > svg { + width: 16px; + height: 16px; } `; diff --git a/frontend/src/Cabinet/components/CabinetList/CabinetListItem/CabinetListItem.tsx b/frontend/src/Cabinet/components/CabinetList/CabinetListItem/CabinetListItem.tsx index 4aaac3ccd..f65ec3cba 100644 --- a/frontend/src/Cabinet/components/CabinetList/CabinetListItem/CabinetListItem.tsx +++ b/frontend/src/Cabinet/components/CabinetList/CabinetListItem/CabinetListItem.tsx @@ -292,7 +292,11 @@ const CabinetIconContainerStyled = styled.div<{ & > svg > path { stroke: ${(props) => cabinetFilterMap[props.status]}; - transform: scale(0.7); + } + + & > svg { + width: 16px; + height: 16px; } `; diff --git a/frontend/src/Cabinet/components/Card/Card.tsx b/frontend/src/Cabinet/components/Card/Card.tsx index e5357800a..17bb10f24 100644 --- a/frontend/src/Cabinet/components/Card/Card.tsx +++ b/frontend/src/Cabinet/components/Card/Card.tsx @@ -19,6 +19,7 @@ interface CardProps { gridArea: string; width?: string; height?: string; + cardType?: string; } const Card = ({ @@ -29,11 +30,12 @@ const Card = ({ height = "163px", buttons = ([] = []), children, + cardType, }: CardProps) => { return ( {(title || buttons.length > 0) && ( - + {title && {title}} {onClickToolTip && } @@ -77,12 +79,13 @@ export const CardStyled = styled.div<{ grid-area: ${(props) => props.gridArea}; `; -export const CardHeaderStyled = styled.div` +export const CardHeaderStyled = styled.div<{ cardType?: string }>` width: 100%; display: flex; justify-content: space-between; align-items: center; - padding: 20px 20px 10px 30px; + padding: ${(props) => + props.cardType === "store" ? "12px 18px 12px 18px" : "20px 20px 10px 30px"}; `; const CardTitleWrapperStyled = styled.div` diff --git a/frontend/src/Cabinet/components/Card/ClubCabinetInfoCard/ClubCabinetInfoCard.tsx b/frontend/src/Cabinet/components/Card/ClubCabinetInfoCard/ClubCabinetInfoCard.tsx index 6a421da3c..51b4f4a3e 100644 --- a/frontend/src/Cabinet/components/Card/ClubCabinetInfoCard/ClubCabinetInfoCard.tsx +++ b/frontend/src/Cabinet/components/Card/ClubCabinetInfoCard/ClubCabinetInfoCard.tsx @@ -6,7 +6,7 @@ import { ContentDetailStyled, } from "@/Cabinet/components/Card/CardStyles"; import ClubPasswordModalContainer from "@/Cabinet/components/Modals/ClubModal/ClubPasswordModal.container"; -import { ReactComponent as LeaderIcon } from "@/Cabinet/assets/images/leader.svg"; +import { ReactComponent as LeaderIcon } from "@/Cabinet/assets/images/crown.svg"; import { ReactComponent as LockIcon } from "@/Cabinet/assets/images/lock.svg"; import { ClubInfoResponseDto } from "@/Cabinet/types/dto/club.dto"; @@ -165,7 +165,6 @@ const CabinetIconStyled = styled.div` & > svg > path { stroke: var(--normal-text-color); - transform: scale(1.1); } `; diff --git a/frontend/src/Cabinet/components/Card/ClubNoticeCard/ClubNoticeCard.tsx b/frontend/src/Cabinet/components/Card/ClubNoticeCard/ClubNoticeCard.tsx index c64f5b266..0d733e3dc 100644 --- a/frontend/src/Cabinet/components/Card/ClubNoticeCard/ClubNoticeCard.tsx +++ b/frontend/src/Cabinet/components/Card/ClubNoticeCard/ClubNoticeCard.tsx @@ -81,7 +81,7 @@ const ClubNoticeTextStyled = styled.div` } ::-webkit-scrollbar-thumb { - background: var(--toggle-switch-off-bg-color); + background: var(--line-color); border-radius: 50px; border: 6px solid transparent; background-clip: padding-box; diff --git a/frontend/src/Cabinet/components/Card/DisplayStyleCard/ColorTheme/ColorTheme.tsx b/frontend/src/Cabinet/components/Card/DisplayStyleCard/ColorTheme/ColorTheme.tsx deleted file mode 100644 index bc2284cd3..000000000 --- a/frontend/src/Cabinet/components/Card/DisplayStyleCard/ColorTheme/ColorTheme.tsx +++ /dev/null @@ -1,104 +0,0 @@ -import styled from "styled-components"; -import { ReactComponent as MonitorMobileIcon } from "@/Cabinet/assets/images/monitorMobile.svg"; -import { ReactComponent as MoonIcon } from "@/Cabinet/assets/images/moon.svg"; -import { ReactComponent as SunIcon } from "@/Cabinet/assets/images/sun.svg"; -import { ColorThemeToggleType } from "@/Cabinet/types/enum/colorTheme.type.enum"; - -interface ItoggleItemSeparated { - name: string; - key: string; - icon: React.ComponentType>; -} - -const toggleList: ItoggleItemSeparated[] = [ - { - name: "라이트", - key: ColorThemeToggleType.LIGHT, - icon: SunIcon, - }, - { - name: "다크", - key: ColorThemeToggleType.DARK, - icon: MoonIcon, - }, - { - name: "기기설정", - key: ColorThemeToggleType.DEVICE, - icon: MonitorMobileIcon, - }, -]; - -const ColorTheme = ({ - colorThemeToggle, - handleColorThemeButtonClick, -}: { - colorThemeToggle: ColorThemeToggleType; - handleColorThemeButtonClick: (colorThemeToggleType: string) => void; -}) => { - return ( - <> - - {toggleList.map((item) => { - const ColorThemeIcon = item.icon; - return ( - handleColorThemeButtonClick(item.key)} - > - {ColorThemeIcon && } - {item.name} - - ); - })} - - - ); -}; - -const ButtonsWrapperStyled = styled.div` - display: flex; - justify-content: center; - align-items: center; - border-radius: 10px; - justify-content: space-between; - padding: 0 16px; -`; - -const ButtonStyled = styled.button<{ - isClicked: boolean; -}>` - display: flex; - justify-content: space-between; - align-items: center; - flex-direction: column; - min-width: 50px; - width: 90px; - min-width: 50px; - border-radius: 10px; - font-size: 1rem; - height: 90px; - font-weight: 500; - background-color: ${(props) => - props.isClicked ? "var(--sys-main-color)" : "var(--card-bg-color)"}; - color: ${(props) => - props.isClicked - ? "var(--white-text-with-bg-color)" - : "var(--normal-text-color)"}; - padding: 12px 0 16px 0; - - & > svg { - width: 30px; - height: 30px; - } - - & > svg > path { - stroke: ${(props) => - props.isClicked - ? "var(--white-text-with-bg-color)" - : "var(--normal-text-color)"}; - } -`; - -export default ColorTheme; diff --git a/frontend/src/Cabinet/components/Card/DisplayStyleCard/DisplayStyleCard.container.tsx b/frontend/src/Cabinet/components/Card/DisplayStyleCard/DisplayStyleCard.container.tsx index 256acd97a..472e4251f 100644 --- a/frontend/src/Cabinet/components/Card/DisplayStyleCard/DisplayStyleCard.container.tsx +++ b/frontend/src/Cabinet/components/Card/DisplayStyleCard/DisplayStyleCard.container.tsx @@ -1,205 +1,84 @@ import { useEffect, useState } from "react"; import DisplayStyleCard from "@/Cabinet/components/Card/DisplayStyleCard/DisplayStyleCard"; -import ColorType from "@/Cabinet/types/enum/color.type.enum"; import { - ColorThemeToggleType, - ColorThemeType, -} from "@/Cabinet/types/enum/colorTheme.type.enum"; + DisplayStyleToggleType, + DisplayStyleType, +} from "@/Cabinet/types/enum/displayStyle.type.enum"; -// 로컬스토리지의 color-theme-toggle 값에 따라 ColorThemeType 반환 -export const getInitialColorTheme = ( - savedColorThemeToggle: ColorThemeToggleType, +// 로컬스토리지의 display-style-toggle 값에 따라 DisplayStyleType 반환 +export const getInitialDisplayStyle = ( + savedDisplayStyleToggle: DisplayStyleToggleType, darkModeQuery: MediaQueryList ) => { // 라이트 / 다크 버튼 - if (savedColorThemeToggle === ColorThemeToggleType.LIGHT) - return ColorThemeType.LIGHT; - else if (savedColorThemeToggle === ColorThemeToggleType.DARK) - return ColorThemeType.DARK; + if (savedDisplayStyleToggle === DisplayStyleToggleType.LIGHT) + return DisplayStyleType.LIGHT; + else if (savedDisplayStyleToggle === DisplayStyleToggleType.DARK) + return DisplayStyleType.DARK; // 디바이스 버튼 if (darkModeQuery.matches) { - return ColorThemeType.DARK; + return DisplayStyleType.DARK; } - return ColorThemeType.LIGHT; + return DisplayStyleType.LIGHT; }; const DisplayStyleCardContainer = () => { - const savedMainColor = - localStorage.getItem("main-color") || "var(--sys-default-main-color)"; - const savedSubColor = - localStorage.getItem("sub-color") || "var(--sys-default-sub-color)"; - const savedMineColor = - localStorage.getItem("mine-color") || "var(--sys-default-mine-color)"; - - const [mainColor, setMainColor] = useState(savedMainColor); - const [subColor, setSubColor] = useState(savedSubColor); - const [mineColor, setMineColor] = useState(savedMineColor); - - const [showColorPicker, setShowColorPicker] = useState(false); - const body: HTMLElement = document.body; - const root: HTMLElement = document.documentElement; - - const [selectedColorType, setSelectedColorType] = useState( - ColorType.MAIN + const savedDisplayStyleToggle = + (localStorage.getItem("display-style-toggle") as DisplayStyleToggleType) || + DisplayStyleToggleType.DEVICE; + var darkModeQuery = window.matchMedia("(prefers-color-scheme: dark)"); + const initialDisplayStyle = getInitialDisplayStyle( + savedDisplayStyleToggle, + darkModeQuery + ); + const [darkMode, setDarkMode] = useState( + initialDisplayStyle as DisplayStyleType + ); + const [toggleType, setToggleType] = useState( + savedDisplayStyleToggle ); - const handlePointColorChange = ( - mainColor: { hex: string }, - colorType: string - ) => { - const selectedColor: string = mainColor.hex; - if (colorType === ColorType.MAIN) { - setMainColor(selectedColor); - } else if (colorType === ColorType.SUB) { - setSubColor(selectedColor); - } else if (colorType === ColorType.MINE) { - setMineColor(selectedColor); - } - }; - - const setColorsAndLocalStorage = ( - main: string, - sub: string, - mine: string, - toggleType: ColorThemeToggleType - ) => { - setMainColor(main); - setSubColor(sub); - setMineColor(mine); - body.style.setProperty("--sys-main-color", main); - body.style.setProperty("--sys-sub-color", sub); - body.style.setProperty("--mine-color", mine); - root.style.setProperty("--sys-main-color", main); - root.style.setProperty("--sys-sub-color", sub); - root.style.setProperty("--mine-color", mine); - localStorage.setItem("main-color", main); - localStorage.setItem("sub-color", sub); - localStorage.setItem("mine-color", mine); - + const setColorsAndLocalStorage = (toggleType: DisplayStyleToggleType) => { setToggleType(toggleType); - localStorage.setItem("color-theme-toggle", toggleType); - }; - - const handleReset = () => { - setColorsAndLocalStorage( - "var(--sys-default-main-color)", - "var(--sys-default-sub-color)", - "var(--sys-default-mine-color)", - ColorThemeToggleType.DEVICE - ); - }; - - const handleSave = () => { - setColorsAndLocalStorage(mainColor, subColor, mineColor, toggleType); - setShowColorPicker(!showColorPicker); - }; - - const handleCancel = () => { - setColorsAndLocalStorage( - savedMainColor, - savedSubColor, - savedMineColor, - savedColorThemeToggle - ); - setShowColorPicker(!showColorPicker); + localStorage.setItem("display-style-toggle", toggleType); }; - const handlePointColorButtonClick = (pointColorType: string) => { - setSelectedColorType(pointColorType); - setShowColorPicker(true); - }; - - const handleColorThemeButtonClick = (colorThemeToggleType: string) => { - if (toggleType === colorThemeToggleType) return; + const handleDisplayStyleButtonClick = (displayStyleToggleType: string) => { + if (toggleType === displayStyleToggleType) return; setToggleType( - colorThemeToggleType as React.SetStateAction + displayStyleToggleType as React.SetStateAction ); - setShowColorPicker(true); + setColorsAndLocalStorage(displayStyleToggleType as DisplayStyleToggleType); }; - const savedColorThemeToggle = - (localStorage.getItem("color-theme-toggle") as ColorThemeToggleType) || - ColorThemeToggleType.DEVICE; - var darkModeQuery = window.matchMedia("(prefers-color-scheme: dark)"); - const initialColorTheme = getInitialColorTheme( - savedColorThemeToggle, - darkModeQuery - ); - const [darkMode, setDarkMode] = useState( - initialColorTheme as ColorThemeType - ); - const [toggleType, setToggleType] = useState( - savedColorThemeToggle - ); - useEffect(() => { darkModeQuery.addEventListener("change", (event) => - setDarkMode(event.matches ? ColorThemeType.DARK : ColorThemeType.LIGHT) + setDarkMode( + event.matches ? DisplayStyleType.DARK : DisplayStyleType.LIGHT + ) ); }, []); useEffect(() => { - document.body.setAttribute("color-theme", darkMode); + document.body.setAttribute("display-style", darkMode); }, [darkMode]); useEffect(() => { - if (toggleType === ColorThemeToggleType.LIGHT) { - setDarkMode(ColorThemeType.LIGHT); - } else if (toggleType === ColorThemeToggleType.DARK) { - setDarkMode(ColorThemeType.DARK); + if (toggleType === DisplayStyleToggleType.LIGHT) { + setDarkMode(DisplayStyleType.LIGHT); + } else if (toggleType === DisplayStyleToggleType.DARK) { + setDarkMode(DisplayStyleType.DARK); } else { setDarkMode( - darkModeQuery.matches ? ColorThemeType.DARK : ColorThemeType.LIGHT + darkModeQuery.matches ? DisplayStyleType.DARK : DisplayStyleType.LIGHT ); } }, [toggleType]); - useEffect(() => { - body.style.setProperty("--sys-main-color", mainColor); - body.style.setProperty("--sys-sub-color", subColor); - body.style.setProperty("--mine-color", mineColor); - root.style.setProperty("--sys-main-color", mainColor); - root.style.setProperty("--sys-sub-color", subColor); - root.style.setProperty("--mine-color", mineColor); - const confirmBeforeUnload = (e: BeforeUnloadEvent) => { - if ( - mainColor !== savedMainColor || - subColor !== savedSubColor || - mineColor !== savedMineColor || - toggleType !== savedColorThemeToggle - ) { - e.returnValue = - "변경된 색상이 저장되지 않을 수 있습니다. 페이지를 나가시겠습니까?"; - } - }; - window.addEventListener("beforeunload", confirmBeforeUnload); - return () => { - window.removeEventListener("beforeunload", confirmBeforeUnload); - }; - }, [ - mainColor, - mineColor, - savedMainColor, - savedMineColor, - subColor, - savedSubColor, - toggleType, - ]); - return ( ); }; diff --git a/frontend/src/Cabinet/components/Card/DisplayStyleCard/DisplayStyleCard.tsx b/frontend/src/Cabinet/components/Card/DisplayStyleCard/DisplayStyleCard.tsx index 541975511..f24601027 100644 --- a/frontend/src/Cabinet/components/Card/DisplayStyleCard/DisplayStyleCard.tsx +++ b/frontend/src/Cabinet/components/Card/DisplayStyleCard/DisplayStyleCard.tsx @@ -1,110 +1,126 @@ import styled from "styled-components"; import Card from "@/Cabinet/components/Card/Card"; import { CardContentWrapper } from "@/Cabinet/components/Card/CardStyles"; -import ColorTheme from "@/Cabinet/components/Card/DisplayStyleCard/ColorTheme/ColorTheme"; -import PointColor from "@/Cabinet/components/Card/DisplayStyleCard/PointColor/PointColor"; -import { ColorThemeToggleType } from "@/Cabinet/types/enum/colorTheme.type.enum"; +import { ReactComponent as MonitorMobileIcon } from "@/Cabinet/assets/images/monitorMobile.svg"; +import { ReactComponent as MoonIcon } from "@/Cabinet/assets/images/moon.svg"; +import { ReactComponent as SunIcon } from "@/Cabinet/assets/images/sun.svg"; +import { DisplayStyleToggleType } from "@/Cabinet/types/enum/displayStyle.type.enum"; interface DisplayStyleProps { - showColorPicker: boolean; - handlePointColorChange: (mainColor: { hex: string }, type: string) => void; - handleReset: () => void; - handleSave: () => void; - handleCancel: () => void; - mainColor: string; - subColor: string; - mineColor: string; - handlePointColorButtonClick: (colorType: string) => void; - selectedColorType: string; - colorThemeToggle: ColorThemeToggleType; - handleColorThemeButtonClick: (colorThemeToggleType: string) => void; + displayStyleToggle: DisplayStyleToggleType; + handleDisplayStyleButtonClick: (DisplayStyleToggleType: string) => void; } +interface IToggleItemSeparated { + name: string; + key: string; + icon: React.ComponentType>; +} + +const toggleList: IToggleItemSeparated[] = [ + { + name: "라이트", + key: DisplayStyleToggleType.LIGHT, + icon: SunIcon, + }, + { + name: "다크", + key: DisplayStyleToggleType.DARK, + icon: MoonIcon, + }, + { + name: "기기설정", + key: DisplayStyleToggleType.DEVICE, + icon: MonitorMobileIcon, + }, +]; + const DisplayStyleCard = ({ - showColorPicker, - handlePointColorChange, - handleReset, - handleSave, - handleCancel, - mainColor, - subColor, - mineColor, - handlePointColorButtonClick, - selectedColorType, - colorThemeToggle, - handleColorThemeButtonClick, + displayStyleToggle, + handleDisplayStyleButtonClick, }: DisplayStyleProps) => { return ( <> - {showColorPicker && } - + <> - - - - + + {toggleList.map((item) => { + const DisplayStyleIcon = item.icon; + return ( + handleDisplayStyleButtonClick(item.key)} + > + {DisplayStyleIcon && } + {item.name} + + ); + })} + - + ); }; -const BackgroundOverlayStyled = styled.div` - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - background: var(--modal-bg-shadow-color); -`; - -const ThemeColorCardWrapper = styled.div` +const DisplayStyleCardWrapper = styled.div` z-index: 1; align-self: start; `; +const ButtonsWrapperStyled = styled.div` + display: flex; + justify-content: center; + align-items: center; + border-radius: 10px; + justify-content: space-between; + padding: 0 16px; +`; + +const ButtonStyled = styled.button<{ + isClicked: boolean; +}>` + display: flex; + justify-content: space-between; + align-items: center; + flex-direction: column; + min-width: 50px; + width: 90px; + min-width: 50px; + border-radius: 10px; + font-size: 1rem; + height: 90px; + font-weight: 500; + background-color: ${(props) => + props.isClicked ? "var(--sys-main-color)" : "var(--card-bg-color)"}; + color: ${(props) => + props.isClicked + ? "var(--white-text-with-bg-color)" + : "var(--normal-text-color)"}; + padding: 12px 0 16px 0; + + & > svg { + width: 30px; + height: 30px; + } + + & > svg > path { + stroke: ${(props) => + props.isClicked + ? "var(--white-text-with-bg-color)" + : "var(--normal-text-color)"}; + } +`; + export default DisplayStyleCard; diff --git a/frontend/src/Cabinet/components/Card/DisplayStyleCard/colorThemeInitializer.ts b/frontend/src/Cabinet/components/Card/DisplayStyleCard/colorThemeInitializer.ts deleted file mode 100644 index 6a1fa6574..000000000 --- a/frontend/src/Cabinet/components/Card/DisplayStyleCard/colorThemeInitializer.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { getInitialColorTheme } from "@/Cabinet/components/Card/DisplayStyleCard/DisplayStyleCard.container"; -import { - ColorThemeToggleType, - ColorThemeType, -} from "@/Cabinet/types/enum/colorTheme.type.enum"; - -(function () { - const isClient = typeof window !== "undefined"; - if (isClient) { - const savedColorThemeToggle = - (localStorage.getItem("color-theme-toggle") as ColorThemeToggleType) || - ColorThemeToggleType.DEVICE; - - const darkModeQuery = window.matchMedia("(prefers-color-scheme: dark)"); - - const colorMode = getInitialColorTheme( - savedColorThemeToggle, - darkModeQuery - ); - - document.documentElement.style.setProperty( - "background-color", - colorMode === ColorThemeType.DARK ? "#1f1f1f" : "#ffffff" - ); - // NOTE : 새로고침 깜박임 현상 방지 - // 이 코드가 실행중일땐 전역변수가 아직 정의가 안된 상태라 전역변수 대신 hex code 사용 - - document.addEventListener("DOMContentLoaded", function () { - document.body.setAttribute("color-theme", colorMode); - }); - } -})(); diff --git a/frontend/src/Cabinet/components/Card/DisplayStyleCard/displayStyleInitializer.ts b/frontend/src/Cabinet/components/Card/DisplayStyleCard/displayStyleInitializer.ts new file mode 100644 index 000000000..3302a8b48 --- /dev/null +++ b/frontend/src/Cabinet/components/Card/DisplayStyleCard/displayStyleInitializer.ts @@ -0,0 +1,33 @@ +import { getInitialDisplayStyle } from "@/Cabinet/components/Card/DisplayStyleCard/DisplayStyleCard.container"; +import { + DisplayStyleToggleType, + DisplayStyleType, +} from "@/Cabinet/types/enum/displayStyle.type.enum"; + +(function () { + const isClient = typeof window !== "undefined"; + if (isClient) { + const savedDisplayStyleToggle = + (localStorage.getItem( + "display-style-toggle" + ) as DisplayStyleToggleType) || DisplayStyleToggleType.DEVICE; + + const darkModeQuery = window.matchMedia("(prefers-color-scheme: dark)"); + + const colorMode = getInitialDisplayStyle( + savedDisplayStyleToggle, + darkModeQuery + ); + + document.documentElement.style.setProperty( + "background-color", + colorMode === DisplayStyleType.DARK ? "#1f1f1f" : "#ffffff" + ); + // NOTE : 새로고침 깜박임 현상 방지 + // 이 코드가 실행중일땐 전역변수가 아직 정의가 안된 상태라 전역변수 대신 hex code 사용 + + document.addEventListener("DOMContentLoaded", function () { + document.body.setAttribute("display-style", colorMode); + }); + } +})(); diff --git a/frontend/src/Cabinet/components/Card/ExtensionCard/ExtensionCard.tsx b/frontend/src/Cabinet/components/Card/ExtensionCard/ExtensionCard.tsx index 1aec4650e..b65e7153a 100644 --- a/frontend/src/Cabinet/components/Card/ExtensionCard/ExtensionCard.tsx +++ b/frontend/src/Cabinet/components/Card/ExtensionCard/ExtensionCard.tsx @@ -1,3 +1,4 @@ +import { useState } from "react"; import Card, { IButtonProps } from "@/Cabinet/components/Card/Card"; import { CardContentStyled, @@ -8,7 +9,6 @@ import { import { NotificationModal } from "@/Cabinet/components/Modals/NotificationModal/NotificationModal"; import { LentExtensionDto } from "@/Cabinet/types/dto/lent.dto"; import { formatDate } from "@/Cabinet/utils/dateUtils"; -import { useState } from "react"; interface ExtensionProps { extensionInfo: LentExtensionDto | null; diff --git a/frontend/src/Cabinet/components/Card/LentInfoCard/LentInfoCard.container.tsx b/frontend/src/Cabinet/components/Card/LentInfoCard/LentInfoCard.container.tsx index c0f48e9d4..3dca53ca1 100644 --- a/frontend/src/Cabinet/components/Card/LentInfoCard/LentInfoCard.container.tsx +++ b/frontend/src/Cabinet/components/Card/LentInfoCard/LentInfoCard.container.tsx @@ -1,12 +1,14 @@ +import { useEffect, useState } from "react"; +import { useRecoilValue, useSetRecoilState } from "recoil"; +import { myCabinetInfoState, userState } from "@/Cabinet/recoil/atoms"; import LentInfoCard from "@/Cabinet/components/Card/LentInfoCard/LentInfoCard"; import { getDefaultCabinetInfo } from "@/Cabinet/components/TopNav/TopNavButtonGroup/TopNavButtonGroup"; -import { myCabinetInfoState } from "@/Cabinet/recoil/atoms"; import { CabinetInfo } from "@/Cabinet/types/dto/cabinet.dto"; import { LentDto } from "@/Cabinet/types/dto/lent.dto"; +import { IItemTimeRemaining } from "@/Cabinet/types/dto/store.dto"; import CabinetStatus from "@/Cabinet/types/enum/cabinet.status.enum"; import CabinetType from "@/Cabinet/types/enum/cabinet.type.enum"; -import { getRemainingTime } from "@/Cabinet/utils/dateUtils"; -import { useRecoilValue } from "recoil"; +import { getRemainingTime, getTimeRemaining } from "@/Cabinet/utils/dateUtils"; export interface MyCabinetInfo { name: string | null; @@ -73,6 +75,10 @@ const LentInfoCardContainer = ({ unbannedAt: Date | null | undefined; }) => { const myCabinetInfo = useRecoilValue(myCabinetInfoState); + const [isPenaltyUser, setIsPenaltyUser] = useState(true); + const [remainPenaltyPeriod, setRemainPenaltyPeriod] = + useState(null); + const [isModalOpen, setIsModalOpen] = useState(false); let dateUsed, dateLeft, expireDate; if (name && myCabinetInfo.lents) { @@ -102,7 +108,39 @@ const LentInfoCardContainer = ({ status: myCabinetInfo.status || "", }; - return ; + const onCLickPenaltyButton = () => { + setIsModalOpen(true); + }; + const handleCloseModal = () => { + setIsModalOpen(false); + }; + + useEffect(() => { + if (unbannedAt == null) { + setIsPenaltyUser(false); + } else { + setRemainPenaltyPeriod(getTimeRemaining(unbannedAt)); + } + }, [unbannedAt]); + + return ( + + ); }; export default LentInfoCardContainer; diff --git a/frontend/src/Cabinet/components/Card/LentInfoCard/LentInfoCard.tsx b/frontend/src/Cabinet/components/Card/LentInfoCard/LentInfoCard.tsx index 527b46c00..3c88ed753 100644 --- a/frontend/src/Cabinet/components/Card/LentInfoCard/LentInfoCard.tsx +++ b/frontend/src/Cabinet/components/Card/LentInfoCard/LentInfoCard.tsx @@ -1,5 +1,5 @@ import styled from "styled-components"; -import Card from "@/Cabinet/components/Card/Card"; +import Card, { IButtonProps } from "@/Cabinet/components/Card/Card"; import { CardContentStyled, CardContentWrapper, @@ -8,8 +8,10 @@ import { } from "@/Cabinet/components/Card/CardStyles"; import { MyCabinetInfo } from "@/Cabinet/components/Card/LentInfoCard/LentInfoCard.container"; import { cabinetIconComponentMap } from "@/Cabinet/assets/data/maps"; +import { IItemTimeRemaining } from "@/Cabinet/types/dto/store.dto"; import CabinetStatus from "@/Cabinet/types/enum/cabinet.status.enum"; import { formatDate } from "@/Cabinet/utils/dateUtils"; +import StoreBuyPenalty from "../../Modals/StoreModal/StoreBuyPenaltyModal"; const calculateFontSize = (userCount: number): string => { const baseSize = 1; @@ -26,94 +28,111 @@ const calculateFontSize = (userCount: number): string => { const LentInfoCard = ({ cabinetInfo, unbannedAt, + button, + isModalOpen, + remainPenaltyPeriod, + onClose, }: { cabinetInfo: MyCabinetInfo; unbannedAt: Date | null | undefined; + button: IButtonProps | undefined; + isModalOpen: boolean; + remainPenaltyPeriod: IItemTimeRemaining | null; + onClose: () => void; }) => { const CabinetIcon = cabinetIconComponentMap[cabinetInfo.lentType]; return ( - - <> - - - {cabinetInfo.visibleNum !== 0 - ? cabinetInfo.visibleNum - : !!unbannedAt - ? "!" - : "-"} - - - + + <> + + - {cabinetInfo.floor !== 0 - ? cabinetInfo.floor + "층 - " + cabinetInfo.section - : "대여 중이 아닌 사용자"} - - - - - - + {cabinetInfo.visibleNum !== 0 + ? cabinetInfo.visibleNum + : !!unbannedAt + ? "!" + : "-"} + + - {cabinetInfo.userNameList} + {cabinetInfo.floor !== 0 + ? cabinetInfo.floor + "층 - " + cabinetInfo.section + : "대여 중이 아닌 사용자"} - - - - - - 사용 기간 - - {cabinetInfo?.isLented && cabinetInfo.status != "IN_SESSION" - ? `${cabinetInfo.dateUsed}일` - : "-"} - - - - - {cabinetInfo?.status === "OVERDUE" ? "연체 기간" : "남은 기간"} - - - {cabinetInfo?.expireDate ? `${cabinetInfo.dateLeft}일` : "-"} - - - - - {!!unbannedAt ? "패널티 종료 일자" : "종료 일자"} - - - {!!unbannedAt - ? formatDate(new Date(unbannedAt), ".") - : cabinetInfo?.expireDate - ? formatDate(new Date(cabinetInfo?.expireDate), ".") - : "-"} - - - - - - 이전 대여자 - - {cabinetInfo?.previousUserName || "-"} - - - - - + + + + + + + {cabinetInfo.userNameList} + + + + + + + 사용 기간 + + {cabinetInfo?.isLented && cabinetInfo.status != "IN_SESSION" + ? `${cabinetInfo.dateUsed}일` + : "-"} + + + + + {cabinetInfo?.status === "OVERDUE" ? "연체 기간" : "남은 기간"} + + + {cabinetInfo?.expireDate ? `${cabinetInfo.dateLeft}일` : "-"} + + + + + {!!unbannedAt ? "페널티 종료 일자" : "종료 일자"} + + + {!!unbannedAt + ? formatDate(new Date(unbannedAt), ".") + : cabinetInfo?.expireDate + ? formatDate(new Date(cabinetInfo?.expireDate), ".") + : "-"} + + + + + + 이전 대여자 + + {cabinetInfo?.previousUserName || "-"} + + + + + + {isModalOpen && ( + + )} + ); }; @@ -188,7 +207,6 @@ const CabinetIconStyled = styled.div` & > svg > path { stroke: var(--normal-text-color); - transform: scale(0.8); } `; diff --git a/frontend/src/Cabinet/components/Card/DisplayStyleCard/PointColor/ColorPicker.tsx b/frontend/src/Cabinet/components/Card/PointColorCard/ColorPicker.tsx similarity index 93% rename from frontend/src/Cabinet/components/Card/DisplayStyleCard/PointColor/ColorPicker.tsx rename to frontend/src/Cabinet/components/Card/PointColorCard/ColorPicker.tsx index 0227cec50..9cef69a2a 100644 --- a/frontend/src/Cabinet/components/Card/DisplayStyleCard/PointColor/ColorPicker.tsx +++ b/frontend/src/Cabinet/components/Card/PointColorCard/ColorPicker.tsx @@ -1,6 +1,6 @@ import { TwitterPicker } from "react-color"; import styled from "styled-components"; -import { GetCustomColorsValues } from "@/Cabinet/components/Card/DisplayStyleCard/colorInfo"; +import { GetCustomColorsValues } from "@/Cabinet/components/Card/PointColorCard/colorInfo"; interface ColorPickerProps { color: string; @@ -24,7 +24,7 @@ const ColorPicker = ({ color, onChange }: ColorPickerProps) => { input: { boxShadow: "var(--color-picker-hash-bg-color) 0px 0px 0px 1px inset", - color: "var(--color-picker-input-color)", + color: "var(--gray-line-btn-color)", }, hash: { background: "var(--color-picker-hash-bg-color)", diff --git a/frontend/src/Cabinet/components/Card/DisplayStyleCard/PointColor/PointColor.tsx b/frontend/src/Cabinet/components/Card/PointColorCard/PointColor.tsx similarity index 91% rename from frontend/src/Cabinet/components/Card/DisplayStyleCard/PointColor/PointColor.tsx rename to frontend/src/Cabinet/components/Card/PointColorCard/PointColor.tsx index fd7bb11e7..eae1723e1 100644 --- a/frontend/src/Cabinet/components/Card/DisplayStyleCard/PointColor/PointColor.tsx +++ b/frontend/src/Cabinet/components/Card/PointColorCard/PointColor.tsx @@ -3,8 +3,8 @@ import { CardContentStyled, ContentInfoStyled, } from "@/Cabinet/components/Card/CardStyles"; -import ColorPicker from "@/Cabinet/components/Card/DisplayStyleCard/PointColor/ColorPicker"; -import { pointColorData } from "@/Cabinet/components/Card/DisplayStyleCard/colorInfo"; +import ColorPicker from "@/Cabinet/components/Card/PointColorCard/ColorPicker"; +import { pointColorData } from "@/Cabinet/components/Card/PointColorCard/colorInfo"; interface PointColorProps { showColorPicker: boolean; diff --git a/frontend/src/Cabinet/components/Card/PointColorCard/PointColorCard.container.tsx b/frontend/src/Cabinet/components/Card/PointColorCard/PointColorCard.container.tsx new file mode 100644 index 000000000..034fd2d49 --- /dev/null +++ b/frontend/src/Cabinet/components/Card/PointColorCard/PointColorCard.container.tsx @@ -0,0 +1,127 @@ +import { useEffect, useState } from "react"; +import PointColorCard from "@/Cabinet/components/Card/PointColorCard/PointColorCard"; +import ColorType from "@/Cabinet/types/enum/color.type.enum"; + +const PointColorCardContainer = () => { + const savedMainColor = + localStorage.getItem("main-color") || "var(--sys-default-main-color)"; + const savedSubColor = + localStorage.getItem("sub-color") || "var(--sys-default-sub-color)"; + const savedMineColor = + localStorage.getItem("mine-color") || "var(--sys-default-mine-color)"; + + const [mainColor, setMainColor] = useState(savedMainColor); + const [subColor, setSubColor] = useState(savedSubColor); + const [mineColor, setMineColor] = useState(savedMineColor); + + const [showColorPicker, setShowColorPicker] = useState(false); + const body: HTMLElement = document.body; + const root: HTMLElement = document.documentElement; + + const [selectedColorType, setSelectedColorType] = useState( + ColorType.MAIN + ); + + const handlePointColorChange = ( + mainColor: { hex: string }, + colorType: string + ) => { + const selectedColor: string = mainColor.hex; + if (colorType === ColorType.MAIN) { + setMainColor(selectedColor); + } else if (colorType === ColorType.SUB) { + setSubColor(selectedColor); + } else if (colorType === ColorType.MINE) { + setMineColor(selectedColor); + } + }; + + const setColorsAndLocalStorage = ( + main: string, + sub: string, + mine: string + ) => { + setMainColor(main); + setSubColor(sub); + setMineColor(mine); + body.style.setProperty("--sys-main-color", main); + body.style.setProperty("--sys-sub-color", sub); + body.style.setProperty("--mine-color", mine); + root.style.setProperty("--sys-main-color", main); + root.style.setProperty("--sys-sub-color", sub); + root.style.setProperty("--mine-color", mine); + localStorage.setItem("main-color", main); + localStorage.setItem("sub-color", sub); + localStorage.setItem("mine-color", mine); + }; + + const handleReset = () => { + setColorsAndLocalStorage( + "var(--sys-default-main-color)", + "var(--sys-default-sub-color)", + "var(--sys-default-mine-color)" + ); + }; + + const handleSave = () => { + setColorsAndLocalStorage(mainColor, subColor, mineColor); + setShowColorPicker(!showColorPicker); + }; + + const handleCancel = () => { + setColorsAndLocalStorage(savedMainColor, savedSubColor, savedMineColor); + setShowColorPicker(!showColorPicker); + }; + + const handlePointColorButtonClick = (pointColorType: string) => { + setSelectedColorType(pointColorType); + setShowColorPicker(true); + }; + + useEffect(() => { + body.style.setProperty("--sys-main-color", mainColor); + body.style.setProperty("--sys-sub-color", subColor); + body.style.setProperty("--mine-color", mineColor); + root.style.setProperty("--sys-main-color", mainColor); + root.style.setProperty("--sys-sub-color", subColor); + root.style.setProperty("--mine-color", mineColor); + const confirmBeforeUnload = (e: BeforeUnloadEvent) => { + if ( + mainColor !== savedMainColor || + subColor !== savedSubColor || + mineColor !== savedMineColor + ) { + e.returnValue = + "변경된 색상이 저장되지 않을 수 있습니다. 페이지를 나가시겠습니까?"; + } + }; + window.addEventListener("beforeunload", confirmBeforeUnload); + return () => { + window.removeEventListener("beforeunload", confirmBeforeUnload); + }; + }, [ + mainColor, + mineColor, + savedMainColor, + savedMineColor, + subColor, + savedSubColor, + ]); + + return ( + + ); +}; + +export default PointColorCardContainer; diff --git a/frontend/src/Cabinet/components/Card/PointColorCard/PointColorCard.tsx b/frontend/src/Cabinet/components/Card/PointColorCard/PointColorCard.tsx new file mode 100644 index 000000000..a95e4af09 --- /dev/null +++ b/frontend/src/Cabinet/components/Card/PointColorCard/PointColorCard.tsx @@ -0,0 +1,98 @@ +import styled from "styled-components"; +import Card from "@/Cabinet/components/Card/Card"; +import { CardContentWrapper } from "@/Cabinet/components/Card/CardStyles"; +import PointColor from "@/Cabinet/components/Card/PointColorCard/PointColor"; + +interface PointColorProps { + showColorPicker: boolean; + handlePointColorChange: (mainColor: { hex: string }, type: string) => void; + handleReset: () => void; + handleSave: () => void; + handleCancel: () => void; + mainColor: string; + subColor: string; + mineColor: string; + handlePointColorButtonClick: (colorType: string) => void; + selectedColorType: string; +} + +const PointColorCard = ({ + showColorPicker, + handlePointColorChange, + handleReset, + handleSave, + handleCancel, + mainColor, + subColor, + mineColor, + handlePointColorButtonClick, + selectedColorType, +}: PointColorProps) => { + return ( + <> + {showColorPicker && } + + + <> + + + + + + + + ); +}; + +const BackgroundOverlayStyled = styled.div` + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: var(--modal-bg-shadow-color); +`; + +const PointColorCardWrapper = styled.div` + z-index: 1; + align-self: start; +`; + +export default PointColorCard; diff --git a/frontend/src/Cabinet/components/Card/DisplayStyleCard/colorInfo.ts b/frontend/src/Cabinet/components/Card/PointColorCard/colorInfo.ts similarity index 97% rename from frontend/src/Cabinet/components/Card/DisplayStyleCard/colorInfo.ts rename to frontend/src/Cabinet/components/Card/PointColorCard/colorInfo.ts index b301c1157..f4ad1336a 100644 --- a/frontend/src/Cabinet/components/Card/DisplayStyleCard/colorInfo.ts +++ b/frontend/src/Cabinet/components/Card/PointColorCard/colorInfo.ts @@ -29,7 +29,7 @@ export const pointColorData: ColorData[] = [ ]; export const customColors = [ - "--custom-pink", + "--custom-pink-200", "--custom-orange", "--custom-yellow", "--custom-green-100", diff --git a/frontend/src/Cabinet/components/Card/StoreItemCard/StoreItemCard.tsx b/frontend/src/Cabinet/components/Card/StoreItemCard/StoreItemCard.tsx new file mode 100644 index 000000000..717e0c2df --- /dev/null +++ b/frontend/src/Cabinet/components/Card/StoreItemCard/StoreItemCard.tsx @@ -0,0 +1,145 @@ +import styled from "styled-components"; +import Card from "@/Cabinet/components/Card/Card"; +import type { IButtonProps } from "@/Cabinet/components/Card/Card"; +import { ItemIconMap } from "@/Cabinet/assets/data/maps"; +import { ReactComponent as CoinImg } from "@/Cabinet/assets/images/coinIcon.svg"; +import { IItemDetail } from "@/Cabinet/types/dto/store.dto"; +import { StoreItemType } from "@/Cabinet/types/enum/store.enum"; + +const convertToItemType = (itemType: string) => { + switch (itemType) { + case "EXTENSION": + return StoreItemType.EXTENSION; + case "SWAP": + return StoreItemType.SWAP; + case "ALARM": + return StoreItemType.ALARM; + case "PENALTY": + return StoreItemType.PENALTY; + default: + return StoreItemType.EXTENSION; + } +}; + +const StoreItemCard = ({ + item, + button, +}: { + item: IItemDetail; + button: IButtonProps; +}) => { + const ItemIcon = ItemIconMap[convertToItemType(item.itemType)]; + return ( + + + + + + + + + + + + {item.items[0].itemPrice * -1} + + + {item.description} + + + + ); +}; + +const WrapperStyled = styled.div` + font-size: 15px; +`; + +const SectionStyled = styled.div` + display: flex; + height: 80px; + width: 90%; + align-items: center; +`; + +const BlockStyled = styled.div` + display: flex; + flex-direction: column; + margin-right: 15px; + justify-content: center; +`; + +const IconBlockStyled = styled.div` + width: 53px; + height: 53px; + border-radius: 10px; + background-color: var(--sys-main-color); + margin-bottom: 5px; + display: flex; + justify-content: center; + align-items: center; +`; + +const PriseBlockStyled = styled.div` + width: 53px; + height: 22px; + background-color: var(--card-content-bg-color); + border-radius: 5px; + font-size: 12px; + color: var(--sys-main-color); + display: flex; + justify-content: center; + align-items: center; + > span { + margin-left: 3px; + font-weight: 600; + } + + & > svg { + width: 14px; + height: 14px; + } + & > svg > path { + stroke: var(--sys-main-color); + stroke-width: 2px; + } +`; + +const ItemDetailStyled = styled.div` + width: 100%; + height: 100%; + background-color: var(--card-content-bg-color); + font-size: var(--size-base); + word-wrap: normal; + padding: 10px 16px; + line-height: 1.4; + border-radius: 10px; +`; + +const ItemIconStyled = styled.div<{ itemType: StoreItemType }>` + display: flex; + justify-content: center; + align-items: center; + width: 32px; + height: 32px; + + & > svg { + width: 32px; + height: 32px; + } + + & > svg > path { + stroke: var(--white-text-with-bg-color); + stroke-width: ${(props) => + props.itemType === StoreItemType.EXTENSION ? "2.8px" : "1.5px"}; + } +`; + +export default StoreItemCard; diff --git a/frontend/src/Cabinet/components/Club/ClubInfo.tsx b/frontend/src/Cabinet/components/Club/ClubInfo.tsx index 9e373495c..d26f04cbc 100644 --- a/frontend/src/Cabinet/components/Club/ClubInfo.tsx +++ b/frontend/src/Cabinet/components/Club/ClubInfo.tsx @@ -6,7 +6,7 @@ import ClubCabinetInfoCard from "@/Cabinet/components/Card/ClubCabinetInfoCard/C import ClubNoticeCard from "@/Cabinet/components/Card/ClubNoticeCard/ClubNoticeCard"; import ClubMemberListContainer from "@/Cabinet/components/Club/ClubMemberList/ClubMemberList.container"; import LoadingAnimation from "@/Cabinet/components/Common/LoadingAnimation"; -import { ReactComponent as SadCcabi } from "@/Cabinet/assets/images/sadCcabi.svg"; +import UnavailableDataInfo from "@/Cabinet/components/Common/UnavailableDataInfo"; import { ClubInfoResponseDto } from "@/Cabinet/types/dto/club.dto"; import useClubInfo from "@/Cabinet/hooks/useClubInfo"; import useMenu from "@/Cabinet/hooks/useMenu"; @@ -31,12 +31,9 @@ const ClubInfo = () => { {clubInfo === undefined ? ( ) : clubInfo === STATUS_400_BAD_REQUEST ? ( - - 동아리 사물함이 없어요 - - - - + <> + + ) : ( 동아리 정보 @@ -55,34 +52,6 @@ const ClubInfo = () => { ); }; -const EmptyClubCabinetTextStyled = styled.div` - width: 100%; - height: 100%; - display: flex; - justify-content: center; - align-items: center; - font-size: 1.125rem; - color: var(--gray-line-btn-color); -`; - -const SadCcabiStyled = styled.div` - display: flex; - margin-left: 5px; - width: 30px; - height: 30px; - margin-left: 8px; - padding-top: 3px; - - & > svg { - width: 30px; - height: 30px; - } - - & > svg > path { - fill: var(--gray-line-btn-color); - } -`; - const ClubInfoWrapperStyled = styled.div` display: flex; flex-direction: column; diff --git a/frontend/src/Cabinet/components/Club/ClubMemberInfoArea/ClubMemberInfoArea.tsx b/frontend/src/Cabinet/components/Club/ClubMemberInfoArea/ClubMemberInfoArea.tsx index cf41d2628..7aaa96f81 100644 --- a/frontend/src/Cabinet/components/Club/ClubMemberInfoArea/ClubMemberInfoArea.tsx +++ b/frontend/src/Cabinet/components/Club/ClubMemberInfoArea/ClubMemberInfoArea.tsx @@ -4,15 +4,14 @@ import { TClubModalState, } from "@/Cabinet/components/Club/ClubMemberInfoArea/ClubMemberInfoArea.container"; import Button from "@/Cabinet/components/Common/Button"; +import SelectInduction from "@/Cabinet/components/Common/SelectInduction"; import DeleteClubMemberModal from "@/Cabinet/components/Modals/ClubModal/DeleteClubMemberModal"; import MandateClubMemberModal from "@/Cabinet/components/Modals/ClubModal/MandateClubMemberModal"; import { - cabinetIconSrcMap, cabinetLabelColorMap, cabinetStatusColorMap, } from "@/Cabinet/assets/data/maps"; -import { ReactComponent as LeaderIcon } from "@/Cabinet/assets/images/leader.svg"; -import { ReactComponent as LogoImg } from "@/Cabinet/assets/images/logo.svg"; +import { ReactComponent as LeaderIcon } from "@/Cabinet/assets/images/crown.svg"; import { ReactComponent as UserImg } from "@/Cabinet/assets/images/privateIcon.svg"; import { ClubCabinetInfo, @@ -51,18 +50,10 @@ const ClubMemberInfoArea = ({ <> {selectedClubCabinetInfo === null ? ( - - - - - - 동아리를
- 선택해주세요 -
-
+ ) : ( <> @@ -120,24 +111,6 @@ const ClubMemberInfoArea = ({ ); }; -const NotSelectedStyled = styled.div` - height: 100%; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; -`; - -const CabiLogoStyled = styled.div` - width: 35px; - height: 35px; - margin-bottom: 10px; - svg { - .logo_svg__currentPath { - fill: var(--sys-main-color); - } - } -`; const ClubMemberInfoAreaStyled = styled.div` position: fixed; top: 120px; @@ -215,13 +188,11 @@ const ClubMemberIconStyled = styled.div<{ isMasterSelected: boolean }>` & > svg { width: 24px; - height: 24px; + height: ${(props) => (props.isMasterSelected ? "20px" : "24px")}; } & > svg > path { stroke: var(--normal-text-color); - transform: ${(props) => - props.isMasterSelected ? "scale(1.3)" : "scale(1.0)"}; } `; diff --git a/frontend/src/Cabinet/components/Club/ClubMemberList/ClubMemberList.tsx b/frontend/src/Cabinet/components/Club/ClubMemberList/ClubMemberList.tsx index 69c1c0179..1d772bb24 100644 --- a/frontend/src/Cabinet/components/Club/ClubMemberList/ClubMemberList.tsx +++ b/frontend/src/Cabinet/components/Club/ClubMemberList/ClubMemberList.tsx @@ -152,6 +152,11 @@ const UserCountIconStyled = styled.div` & > svg > path { stroke: var(--normal-text-color); } + + & > svg { + width: 24px; + height: 24px; + } `; const AddMemberCardStyled = styled.div` diff --git a/frontend/src/Cabinet/components/Club/ClubMemberList/ClubMemberListItem/ClubMemberListItem.tsx b/frontend/src/Cabinet/components/Club/ClubMemberList/ClubMemberListItem/ClubMemberListItem.tsx index be6e04df5..8c2e2d9ac 100644 --- a/frontend/src/Cabinet/components/Club/ClubMemberList/ClubMemberListItem/ClubMemberListItem.tsx +++ b/frontend/src/Cabinet/components/Club/ClubMemberList/ClubMemberListItem/ClubMemberListItem.tsx @@ -86,7 +86,6 @@ const MemberListItemStyled = styled.div<{ isMaster: boolean }>` props.isMaster ? "var(--white-text-with-bg-color)" : "var(--normal-text-color)"}; - transform: ${(props) => (props.isMaster ? "" : "scale(0.7)")}; } `; diff --git a/frontend/src/Cabinet/components/Common/ClubListDropdown.tsx b/frontend/src/Cabinet/components/Common/ClubListDropdown.tsx index 1c1e16001..8ae9c35a8 100644 --- a/frontend/src/Cabinet/components/Common/ClubListDropdown.tsx +++ b/frontend/src/Cabinet/components/Common/ClubListDropdown.tsx @@ -71,7 +71,7 @@ const ClubListDropdSelectionBoxStyled = styled.div<{ isOpen: boolean }>` position: relative; display: flex; align-items: center; - border: 1px solid var(--toggle-switch-off-bg-color); + border: 1px solid var(--line-color); width: 100%; height: 60px; border-radius: 10px; @@ -114,7 +114,7 @@ const ClubListDropdItemStyled = styled.div<{ isSelected: boolean }>` align-items: center; background-color: ${({ isSelected }) => isSelected ? "var(--map-floor-color)" : "var(--bg-color)"}; - border: 1px solid var(--toggle-switch-off-bg-color); + border: 1px solid var(--line-color); border-width: 0px 1px 1px 1px; width: 100%; height: 60px; diff --git a/frontend/src/Cabinet/components/Common/Dropdown.tsx b/frontend/src/Cabinet/components/Common/Dropdown.tsx index 8b47b4cce..c9eb3f121 100644 --- a/frontend/src/Cabinet/components/Common/Dropdown.tsx +++ b/frontend/src/Cabinet/components/Common/Dropdown.tsx @@ -1,13 +1,15 @@ -import { useState } from "react"; +import { useEffect, useState } from "react"; import styled, { css } from "styled-components"; -import { ReactComponent as ClubIcon } from "@/Cabinet/assets/images/clubIcon.svg"; -import { ReactComponent as PrivateIcon } from "@/Cabinet/assets/images/privateIcon.svg"; -import { ReactComponent as ShareIcon } from "@/Cabinet/assets/images/shareIcon.svg"; +import { cabinetIconComponentMap } from "@/Cabinet/assets/data/maps"; +import { ReactComponent as DropdownChevronIcon } from "@/Cabinet/assets/images/dropdownChevron.svg"; +import CabinetType from "@/Cabinet/types/enum/cabinet.type.enum"; export interface IDropdownOptions { name: string; value: any; imageSrc?: string; + isDisabled?: boolean; + hasNoOptions?: boolean; } export interface IDropdownProps { @@ -15,51 +17,72 @@ export interface IDropdownProps { defaultValue: string; defaultImageSrc?: string; onChangeValue?: (param: any) => any; + isOpen: boolean; + setIsOpen: React.Dispatch>; + closeOtherDropdown?: () => void; } -const Dropdown = ({ options, defaultValue, onChangeValue }: IDropdownProps) => { +const Dropdown = ({ + options, + defaultValue, + onChangeValue, + defaultImageSrc, + isOpen, + setIsOpen, + closeOtherDropdown, +}: IDropdownProps) => { const [currentName, setCurrentName] = useState(defaultValue); - const [isOpen, setIsOpen] = useState(false); const idx: number = options.findIndex((op) => op.name === currentName); const selectedIdx: number = idx === -1 ? 0 : idx; + const DefaultOptionIcon = + defaultImageSrc && + cabinetIconComponentMap[options[selectedIdx].value as CabinetType]; + useEffect(() => { + setCurrentName(defaultValue); + }, [defaultValue]); return ( { + if (options[selectedIdx].isDisabled) return; setIsOpen(!isOpen); + closeOtherDropdown && closeOtherDropdown(); }} - isOpen={isOpen} > - {options[selectedIdx] && options[selectedIdx].imageSrc && ( + {DefaultOptionIcon && ( - {options[selectedIdx].value === "PRIVATE" && } - {options[selectedIdx].value === "CLUB" && } - {options[selectedIdx].value === "SHARE" && } + )}

{currentName}

- + + +
- {options?.map((option) => { + {options.map((option) => { + const OptionIcon = + cabinetIconComponentMap[option.value as CabinetType]; return ( { - setCurrentName(option.name); - setIsOpen(false); - if (onChangeValue) { - onChangeValue(option.value); + if (!option.isDisabled) { + setCurrentName(option.name); + setIsOpen(false); + if (onChangeValue) { + onChangeValue(option.value); + } } }} isSelected={option.name === currentName} + isDisabled={option.isDisabled} + hasNoOptions={option.hasNoOptions} > {option.imageSrc && ( - {option.value === "PRIVATE" && } - {option.value === "CLUB" && } - {option.value === "SHARE" && } + )}

{option.name}

@@ -76,13 +99,14 @@ const DropdownContainerStyled = styled.div` flex-direction: column; width: 100%; position: relative; + cursor: pointer; `; -const DropdownSelectionBoxStyled = styled.div<{ isOpen: boolean }>` +const DropdownSelectionBoxStyled = styled.div` position: relative; display: flex; align-items: center; - border: 1px solid var(--toggle-switch-off-bg-color); + border: 1px solid var(--line-color); width: 100%; height: 60px; border-radius: 10px; @@ -90,19 +114,6 @@ const DropdownSelectionBoxStyled = styled.div<{ isOpen: boolean }>` padding-left: 20px; font-size: 1.125rem; color: var(--sys-main-color); - & > img { - filter: contrast(0.6); - width: 14px; - height: 8px; - position: absolute; - top: 45%; - left: 85%; - ${({ isOpen }) => - isOpen === true && - css` - transform: scaleY(-1); - `} - } `; const DropdownItemContainerStyled = styled.div<{ isVisible: boolean }>` @@ -119,29 +130,41 @@ const DropdownItemContainerStyled = styled.div<{ isVisible: boolean }>` `} `; -const DropdownItemStyled = styled.div<{ isSelected: boolean }>` +const DropdownItemStyled = styled.div<{ + isSelected: boolean; + isDisabled?: boolean; + hasNoOptions?: boolean; +}>` position: relative; display: flex; align-items: center; background-color: ${({ isSelected }) => isSelected ? "var(--map-floor-color)" : "var(--bg-color)"}; - border: 1px solid var(--toggle-switch-off-bg-color); + border: 1px solid var(--line-color); border-width: 0px 1px 1px 1px; width: 100%; height: 60px; text-align: start; padding-left: 20px; font-size: 1.125rem; - color: ${({ isSelected }) => - isSelected ? "var(--sys-main-color)" : "var(--normal-text-color)"}; - cursor: pointer; + color: ${( + { isSelected, isDisabled } // 비활성화 된 항목은 --capsule-btn-border-color 로 띄우고 클릭 못하게 + ) => + isDisabled + ? "var(--capsule-btn-border-color)" + : isSelected + ? "var(--sys-main-color)" + : "var(--normal-text-color)"}; + cursor: ${({ isDisabled }) => (isDisabled ? "not-allowed" : "pointer")}; &:first-child { border-radius: 10px 10px 0px 0px; border-width: 1px 1px 1px 1px; } &:last-child { - border-radius: 0px 0px 10px 10px; + border-radius: ${(props) => + props.hasNoOptions ? "10px" : "0px 0px 10px 10px"}; } + &:hover { background-color: var(--map-floor-color); } @@ -155,9 +178,28 @@ const OptionsImgStyled = styled.div<{ isSelected?: boolean }>` width: 18px; height: 18px; } + & > svg > path { stroke: var(--normal-text-color); - transform: scale(0.8); } `; + +const DropdownSelectionBoxIconStyled = styled.div<{ isOpen: boolean }>` + width: 14px; + height: 8px; + display: flex; + position: absolute; + top: 45%; + left: 85%; + ${({ isOpen }) => + isOpen === true && + css` + transform: scaleY(-1); + `} + + & > svg > path { + stroke: var(--line-color); + } +`; + export default Dropdown; diff --git a/frontend/src/Cabinet/components/Common/MultiSelectFilterButton.tsx b/frontend/src/Cabinet/components/Common/MultiSelectFilterButton.tsx index 9a2a71785..76355fa3b 100644 --- a/frontend/src/Cabinet/components/Common/MultiSelectFilterButton.tsx +++ b/frontend/src/Cabinet/components/Common/MultiSelectFilterButton.tsx @@ -50,7 +50,7 @@ const FilterTextWrapperStyled = styled.div<{ isClicked: boolean }>` justify-content: center; align-items: center; color: ${({ isClicked }) => - isClicked ? "var(--sys-main-color)" : "var(--toggle-switch-off-bg-color)"}; + isClicked ? "var(--sys-main-color)" : "var(--line-color)"}; font-size: 1rem; `; diff --git a/frontend/src/Cabinet/components/Common/SelectInduction.tsx b/frontend/src/Cabinet/components/Common/SelectInduction.tsx new file mode 100644 index 000000000..e6c36903b --- /dev/null +++ b/frontend/src/Cabinet/components/Common/SelectInduction.tsx @@ -0,0 +1,43 @@ +import styled from "styled-components"; +import { ReactComponent as LogoImg } from "@/Cabinet/assets/images/logo.svg"; + +const SelectInduction = ({ msg }: { msg: string }) => { + return ( + + + + + {msg} + + ); +}; + +const WrapperStyled = styled.div` + height: 100%; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +`; + +const CabiLogoStyled = styled.div` + width: 35px; + height: 35px; + margin-bottom: 10px; + svg { + .logo_svg__currentPath { + fill: var(--sys-main-color); + } + } +`; + +const MsgStyled = styled.p` + font-size: 1.125rem; + font-weight: 400; + line-height: 28px; + color: var(--gray-line-btn-color); + text-align: center; + white-space: pre-line; +`; + +export default SelectInduction; diff --git a/frontend/src/Cabinet/components/Common/Selector.tsx b/frontend/src/Cabinet/components/Common/Selector.tsx index 170761990..ea90c4b35 100644 --- a/frontend/src/Cabinet/components/Common/Selector.tsx +++ b/frontend/src/Cabinet/components/Common/Selector.tsx @@ -1,18 +1,17 @@ -import PillButton from "@/Cabinet/components/Common/PillButton"; import styled from "styled-components"; +import PillButton from "@/Cabinet/components/Common/PillButton"; interface ISelectorProps { - iconSrc?: string; + icon?: React.FunctionComponent>; selectList: { key: number; value: string }[]; onClickSelect: any; } -const Selector = ({ iconSrc, selectList, onClickSelect }: ISelectorProps) => { +const Selector = ({ icon, selectList, onClickSelect }: ISelectorProps) => { + const Icon = icon; return ( - - - + {Icon && } {selectList && selectList.map((elem) => { return ( @@ -42,6 +41,10 @@ const IconWrapperStyled = styled.div` width: 24px; height: 24px; margin-bottom: 12px; + + & > svg > path { + stroke: var(--normal-text-color); + } `; export default Selector; diff --git a/frontend/src/Cabinet/components/Common/ToggleSwitch.tsx b/frontend/src/Cabinet/components/Common/ToggleSwitch.tsx index 8d50baf2d..49c068d39 100644 --- a/frontend/src/Cabinet/components/Common/ToggleSwitch.tsx +++ b/frontend/src/Cabinet/components/Common/ToggleSwitch.tsx @@ -57,9 +57,7 @@ const ToggleSwitchStyled = styled.label<{ display: inline-block; position: relative; background: ${(props) => - props.checked - ? "var(--sys-main-color)" - : "var(--toggle-switch-off-bg-color)"}; + props.checked ? "var(--sys-main-color)" : "var(--line-color)"}; width: 56px; height: 28px; border-radius: 50px; diff --git a/frontend/src/Cabinet/components/Common/UnavailableDataInfo.tsx b/frontend/src/Cabinet/components/Common/UnavailableDataInfo.tsx new file mode 100644 index 000000000..b5f703049 --- /dev/null +++ b/frontend/src/Cabinet/components/Common/UnavailableDataInfo.tsx @@ -0,0 +1,68 @@ +import styled from "styled-components"; +import { ReactComponent as SadCabiIcon } from "@/Cabinet/assets/images/sadCcabi.svg"; + +const UnavailableDataInfo = ({ + msg, + height, + fontSize, + iconWidth, + iconHeight, +}: { + msg: string; + height?: string; + fontSize?: string; + iconWidth?: string; + iconHeight?: string; +}) => { + return ( + + + {msg} + + + + + + ); +}; + +const EmptyWrapperStyled = styled.div` + display: flex; + justify-content: center; + align-items: center; + height: 100%; +`; + +const EmptyItemUsageLogTextStyled = styled.div<{ + height: string | undefined; + fontSize: string | undefined; +}>` + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + font-size: ${(props) => (props.fontSize ? props.fontSize : "1.125rem")}; + line-height: 1.75rem; + color: var(--gray-line-btn-color); + height: ${(props) => props.height && props.height}; +`; + +const SadCcabiIconStyled = styled.div<{ + width: string | undefined; + height: string | undefined; +}>` + width: ${(props) => (props.width ? props.width : "30px")}; + height: ${(props) => (props.height ? props.height : "30px")}; + margin-left: 10px; + + & > svg { + width: ${(props) => (props.width ? props.width : "30px")}; + height: ${(props) => (props.height ? props.height : "30px")}; + } + + & > svg > path { + fill: var(--normal-text-color); + } +`; + +export default UnavailableDataInfo; diff --git a/frontend/src/Cabinet/components/Common/WarningNotification.tsx b/frontend/src/Cabinet/components/Common/WarningNotification.tsx index 6e00574f1..455d9c8dc 100644 --- a/frontend/src/Cabinet/components/Common/WarningNotification.tsx +++ b/frontend/src/Cabinet/components/Common/WarningNotification.tsx @@ -1,5 +1,6 @@ import React from "react"; import styled from "styled-components"; +import { ReactComponent as WarningIcon } from "@/Cabinet/assets/images/warningTriangleIcon.svg"; export interface WarningNotificationProps { isVisible: boolean; @@ -12,25 +13,14 @@ const WarningNotification: React.FC = ({ }: WarningNotificationProps) => { return ( - + + + {message} ); }; -const WarningIcon = styled.div<{ isVisible: boolean }>` - display: ${({ isVisible }) => (isVisible ? "block" : "none")}; - background-image: url("/src/Cabinet/assets/images/warningTriangleIcon.svg"); - width: 24px; - height: 24px; - margin: 0px auto; - opacity: 0.6; - cursor: pointer; - &:hover { - opacity: 1; - } -`; - const WarningBox = styled.div` position: relative; margin: 10px auto; @@ -50,15 +40,27 @@ const WarningBox = styled.div` padding 0.5s ease-in-out; `; +const IconWrapperStyled = styled.div<{ isVisible: boolean }>` + display: ${({ isVisible }) => (isVisible ? "block" : "none")}; + width: 24px; + height: 24px; + cursor: pointer; + margin: 0px auto; + opacity: 0.6; + &:hover { + opacity: 1; + } +`; + const WarningWrapper = styled.div<{ isVisible: boolean }>` display: ${({ isVisible }) => (isVisible ? "block" : "none")}; position: relative; width: 100%; height: 24px; justify-content: center; - & ${WarningIcon}:hover + ${WarningBox} { + & ${IconWrapperStyled}:hover + ${WarningBox} { visibility: visible; - color: var(--normal-text-color); + color: var(--white-text-with-bg-color); background-color: var(--tooltip-shadow-color); &:before { border-color: transparent transparent var(--tooltip-shadow-color) diff --git a/frontend/src/Cabinet/components/Home/ManualContentBox.tsx b/frontend/src/Cabinet/components/Home/ManualContentBox.tsx index 1af0d4d15..5f4885e30 100644 --- a/frontend/src/Cabinet/components/Home/ManualContentBox.tsx +++ b/frontend/src/Cabinet/components/Home/ManualContentBox.tsx @@ -1,12 +1,7 @@ import styled, { css, keyframes } from "styled-components"; import { manualContentData } from "@/Cabinet/assets/data/ManualContent"; -import { ReactComponent as ClockImg } from "@/Cabinet/assets/images/clock.svg"; -import { ReactComponent as ClubIcon } from "@/Cabinet/assets/images/clubIcon.svg"; -import { ReactComponent as ExtensionIcon } from "@/Cabinet/assets/images/extension.svg"; import { ReactComponent as ManualPeopleImg } from "@/Cabinet/assets/images/manualPeople.svg"; import { ReactComponent as MoveBtnImg } from "@/Cabinet/assets/images/moveButton.svg"; -import { ReactComponent as PrivateIcon } from "@/Cabinet/assets/images/privateIcon.svg"; -import { ReactComponent as ShareIcon } from "@/Cabinet/assets/images/shareIcon.svg"; import ContentStatus from "@/Cabinet/types/enum/content.status.enum"; interface MaunalContentBoxProps { @@ -22,24 +17,17 @@ const MaunalContentBox = ({ contentStatus }: MaunalContentBoxProps) => { contentStatus={contentStatus} > {contentStatus === ContentStatus.EXTENSION && ( - + )} - {contentStatus === ContentStatus.PRIVATE && ( - - )} - {contentStatus === ContentStatus.SHARE && ( - - )} - {contentStatus === ContentStatus.CLUB && ( - - )} - {contentStatus === ContentStatus.EXTENSION && ( - - )} - - {contentStatus === ContentStatus.IN_SESSION && ( - + {contentStatus !== ContentStatus.IN_SESSION && + contentData.iconComponent && ( + )} + + {contentStatus === ContentStatus.IN_SESSION && + contentData.iconComponent && ( + + )}

{contentData.contentTitle}

@@ -49,9 +37,9 @@ const MaunalContentBox = ({ contentStatus }: MaunalContentBoxProps) => { const Rotation = keyframes` to { - transform : rotate(360deg) + transform : rotate(360deg) } -`; + `; const MaunalContentBoxStyled = styled.div<{ background: string; @@ -76,6 +64,7 @@ const MaunalContentBoxStyled = styled.div<{ margin-right: 10px; margin-top: 160px; animation: ${Rotation} 1s linear infinite; + stroke: var(--sys-main-color); } .contentImg { @@ -87,10 +76,6 @@ const MaunalContentBoxStyled = styled.div<{ props.contentStatus === ContentStatus.EXTENSION ? "var(--normal-text-color)" : "var(--white-text-with-bg-color)"}; - transform: ${(props) => - props.contentStatus === ContentStatus.EXTENSION - ? "scale(1.4)" - : "scale(3.3)"}; } } @@ -101,6 +86,7 @@ const MaunalContentBoxStyled = styled.div<{ position: absolute; right: 100px; bottom: 30px; + fill: var(--sys-main-color); } ${({ contentStatus }) => diff --git a/frontend/src/Cabinet/components/Home/ServiceManual.tsx b/frontend/src/Cabinet/components/Home/ServiceManual.tsx index 29da0ccf1..94f766f70 100644 --- a/frontend/src/Cabinet/components/Home/ServiceManual.tsx +++ b/frontend/src/Cabinet/components/Home/ServiceManual.tsx @@ -147,7 +147,7 @@ const NotionBtn = styled.button` font-size: 0.875rem; color: var(--notion-btn-text-color); background: var(--bg-color); - border: 1px solid var(--toggle-switch-off-bg-color); + border: 1px solid var(--line-color); :hover { color: var(--normal-text-color); font-weight: 400; diff --git a/frontend/src/Cabinet/components/ItemLog/AdminItemProvideLog.container.tsx b/frontend/src/Cabinet/components/ItemLog/AdminItemProvideLog.container.tsx new file mode 100644 index 000000000..1cca6cf96 --- /dev/null +++ b/frontend/src/Cabinet/components/ItemLog/AdminItemProvideLog.container.tsx @@ -0,0 +1,112 @@ +import { useEffect, useState } from "react"; +import AdminItemProvideLog from "@/Cabinet/components/ItemLog/AdminItemProvideLog"; +import { ItemLogResponseType } from "@/Cabinet/types/dto/admin.dto"; +import useMenu from "@/Cabinet/hooks/useMenu"; +import { STATUS_400_BAD_REQUEST } from "@/Cabinet/constants/StatusCode"; + +const AdminItemProvideLogContainer = () => { + const { closeStore } = useMenu(); + const [logs, setLogs] = useState({ + itemHistories: [], + totalLength: 0, + }); + const [page, setPage] = useState(0); + const [totalPage, setTotalPage] = useState(-1); + const [needsUpdate, setNeedsUpdate] = useState(false); + const size = 8; + + const mockItemHistories = [ + { + items: [ + { + itemSku: "SKU1001", + itemName: "이사권", + itemDetails: "이사권", + issuedDate: "2024-05-28T10:00:00", + }, + { + itemSku: "SKU1002", + itemName: "알림 등록권", + itemDetails: "알림 등록권", + issuedDate: "2024-05-28T11:00:00", + }, + { + itemSku: "SKU1003", + itemName: "페널티권", + itemDetails: "31일", + issuedDate: "2024-05-28T12:00:00", + }, + { + itemSku: "SKU1004", + itemName: "연장권", + itemDetails: "15일", + issuedDate: "2024-05-28T13:00:00", + }, + ], + }, + ]; + + async function getData(page: number) { + try { + const items = mockItemHistories[0].items; + const startIndex = page * size; + const endIndex = startIndex + size; + const paginatedItems = items.slice(startIndex, endIndex); + + const paginatedData = { + itemHistories: paginatedItems, + totalLength: items.length, + }; + + setLogs(paginatedData); + setTotalPage(Math.ceil(paginatedData.totalLength / size)); + } catch (error) { + console.error("Failed to fetch data:", error); + setLogs({ itemHistories: [], totalLength: 0 }); + } + } + + useEffect(() => { + getData(page); + }, [page]); + + useEffect(() => { + if (needsUpdate) { + getData(page); + setNeedsUpdate(false); + } + }, [needsUpdate, page]); + + const onClickPrev = () => { + if (page > 0) { + setPage((prev) => prev - 1); + setNeedsUpdate(true); + } + }; + + const onClickNext = () => { + if (page < totalPage - 1) { + setPage((prev) => prev + 1); + setNeedsUpdate(true); + } + }; + + const closeAndResetLogPage = () => { + closeStore(); + setPage(0); + setNeedsUpdate(true); + }; + + return ( + + ); +}; + +export default AdminItemProvideLogContainer; diff --git a/frontend/src/Cabinet/components/ItemLog/AdminItemProvideLog.tsx b/frontend/src/Cabinet/components/ItemLog/AdminItemProvideLog.tsx new file mode 100644 index 000000000..b97f6dc4a --- /dev/null +++ b/frontend/src/Cabinet/components/ItemLog/AdminItemProvideLog.tsx @@ -0,0 +1,124 @@ +import styled, { css } from "styled-components"; +import AdminItemProvideTable from "@/Cabinet/components/ItemLog/ItemLogTable/AdminItemProvideTable"; +import { IItemLog } from "@/Cabinet/types/dto/admin.dto"; + +const AdminItemProvideLog = ({ + closeItem, + logs, + page, + totalPage, + onClickPrev, + onClickNext, +}: IItemLog) => { + return ( + + + + + + + + + + + + + + + + + + + + ); +}; + +const AdminItemUsageLogStyled = styled.div` + width: 100%; + position: relative; +`; + +const ButtonContainerStyled = styled.div` + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + opacity: 0.3; + display: flex; + justify-content: space-between; + align-items: center; + margin: 0 auto; + overflow: hidden; + &:hover > .logPageButton { + opacity: 1; + } +`; + +const PageButtonStyled = styled.div<{ + page: number; + totalPage: number; + type: string; +}>` + cursor: pointer; + width: 40px; + height: 100%; + border-radius: 10px; + position: absolute; + opacity: 0.5; + transition: opacity 0.5s; + background: linear-gradient( + to left, + transparent, + var(--page-btn-shadow-color) + ); + display: ${({ page, totalPage, type }) => { + if (type == "prev" && page == 0) return "none"; + if (type == "next" && (totalPage == 0 || page == totalPage - 1)) + return "none"; + return "block"; + }}; + ${({ type }) => + type === "prev" + ? css` + left: 0; + ` + : css` + right: 0; + transform: rotate(-180deg); + `} +`; + +const ImgCenterStyled = styled.div` + display: flex; + align-items: center; + justify-content: center; + height: 100%; +`; + +const ImageStyled = styled.div` + width: 40px; + margin-right: 4px; + filter: brightness(0%); + border-radius: 50%; +`; + +export default AdminItemProvideLog; diff --git a/frontend/src/Cabinet/components/ItemLog/AdminItemUsageLog.container.tsx b/frontend/src/Cabinet/components/ItemLog/AdminItemUsageLog.container.tsx new file mode 100644 index 000000000..ff26aba0c --- /dev/null +++ b/frontend/src/Cabinet/components/ItemLog/AdminItemUsageLog.container.tsx @@ -0,0 +1,79 @@ +import { useEffect, useState } from "react"; +import { useRecoilState } from "recoil"; +import { targetUserInfoState } from "@/Cabinet/recoil/atoms"; +import AdminItemUsageLog from "@/Cabinet/components/ItemLog/AdminItemUsageLog"; +import { ItemLogResponseType } from "@/Cabinet/types/dto/admin.dto"; +import { axiosGetUserItems } from "@/Cabinet/api/axios/axios.custom"; +import useMenu from "@/Cabinet/hooks/useMenu"; + +const AdminItemUsageLogContainer = () => { + const { closeStore } = useMenu(); + const [userId, setUserId] = useState(0); + const [targetUserInfo] = useRecoilState(targetUserInfoState); + const [logs, setLogs] = useState({ + itemHistories: [], + totalLength: 0, + }); + const [page, setPage] = useState(0); + const [totalPage, setTotalPage] = useState(-1); + const [needsUpdate, setNeedsUpdate] = useState(true); + const size = 8; + + async function getData(page: number) { + try { + const paginatedData = await axiosGetUserItems( + targetUserInfo.userId!, + page, + size + ); + setLogs({ + itemHistories: paginatedData.data.itemHistories, + totalLength: paginatedData.data.totalLength, + }); + setTotalPage(Math.ceil(paginatedData.data.totalLength / size)); + } catch { + setLogs({ itemHistories: [], totalLength: 0 }); + setTotalPage(1); + } + } + + useEffect(() => { + if (needsUpdate) { + getData(page); + setNeedsUpdate(false); + } + }, [needsUpdate, page]); + + const onClickPrev = () => { + if (page > 0) { + setPage((prev) => prev - 1); + setNeedsUpdate(true); + } + }; + + const onClickNext = () => { + if (page < totalPage - 1) { + setPage((prev) => prev + 1); + setNeedsUpdate(true); + } + }; + + const closeAndResetLogPage = () => { + closeStore(); + setPage(0); + setNeedsUpdate(true); + }; + + return ( + + ); +}; + +export default AdminItemUsageLogContainer; diff --git a/frontend/src/Cabinet/components/ItemLog/AdminItemUsageLog.tsx b/frontend/src/Cabinet/components/ItemLog/AdminItemUsageLog.tsx new file mode 100644 index 000000000..164924250 --- /dev/null +++ b/frontend/src/Cabinet/components/ItemLog/AdminItemUsageLog.tsx @@ -0,0 +1,124 @@ +import styled, { css } from "styled-components"; +import AdminItemLogTable from "@/Cabinet/components/ItemLog/ItemLogTable/AdminItemLogTable"; +import { IItemLog } from "@/Cabinet/types/dto/admin.dto"; + +const AdminItemUsageLog = ({ + closeItem, + logs, + page, + totalPage, + onClickPrev, + onClickNext, +}: IItemLog) => { + return ( + + + + + + + + + + + + + + + + + + + + ); +}; + +const AdminItemUsageLogStyled = styled.div` + width: 100%; + position: relative; +`; + +const ButtonWrapperStyled = styled.div` + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + opacity: 0.3; + display: flex; + justify-content: space-between; + align-items: center; + margin: 0 auto; + overflow: hidden; + &:hover > .logPageButton { + opacity: 1; + } +`; + +const PageButtonStyled = styled.div<{ + page: number; + totalPage: number; + type: string; +}>` + cursor: pointer; + width: 40px; + height: 100%; + border-radius: 10px; + position: absolute; + opacity: 0.5; + transition: opacity 0.5s; + background: linear-gradient( + to left, + transparent, + var(--page-btn-shadow-color) + ); + display: ${({ page, totalPage, type }) => { + if (type == "prev" && page == 0) return "none"; + if (type == "next" && (totalPage == 0 || page == totalPage - 1)) + return "none"; + return "block"; + }}; + ${({ type }) => + type === "prev" + ? css` + left: 0; + ` + : css` + right: 0; + transform: rotate(-180deg); + `} +`; + +const ImgCenterStyled = styled.div` + display: flex; + align-items: center; + justify-content: center; + height: 100%; +`; + +const ImageStyled = styled.div` + width: 40px; + margin-right: 4px; + filter: brightness(0%); + border-radius: 50%; +`; + +export default AdminItemUsageLog; diff --git a/frontend/src/Cabinet/components/ItemLog/ItemLogTable/AdminItemLogTable.tsx b/frontend/src/Cabinet/components/ItemLog/ItemLogTable/AdminItemLogTable.tsx new file mode 100644 index 000000000..07a754416 --- /dev/null +++ b/frontend/src/Cabinet/components/ItemLog/ItemLogTable/AdminItemLogTable.tsx @@ -0,0 +1,113 @@ +import styled from "styled-components"; +import LoadingAnimation from "@/Cabinet/components/Common/LoadingAnimation"; +import { ItemLogResponseType } from "@/Cabinet/types/dto/admin.dto"; +import { STATUS_400_BAD_REQUEST } from "@/Cabinet/constants/StatusCode"; + +const dateOptions: Intl.DateTimeFormatOptions = { + year: "2-digit", + month: "2-digit", + day: "2-digit", +}; + +const AdminItemLogTable = ({ itemLog }: { itemLog: ItemLogResponseType }) => { + if (!itemLog) return ; + return ( + + + + + 발급일 + 아이템 + 사용일 + + + {itemLog !== STATUS_400_BAD_REQUEST && + Array.isArray(itemLog.itemHistories) && ( + + {itemLog.itemHistories.map( + ({ purchaseAt, itemName, itemDetails, usedAt }, idx) => ( + + + {new Date(purchaseAt ?? "").toLocaleString( + "ko-KR", + dateOptions + )} + + + {itemName !== itemDetails + ? `${itemName} - ${itemDetails}` + : itemName} + + + {usedAt + ? new Date(usedAt).toLocaleString("ko-KR", dateOptions) + : "-"} + + + ) + )} + + )} + + {(itemLog === STATUS_400_BAD_REQUEST || + itemLog.totalLength === undefined || + itemLog.totalLength === 0) && ( + 아이템 내역이 없습니다. + )} + + ); +}; + +const LogTableWrapperstyled = styled.div` + width: 100%; + max-width: 800px; + border-radius: 10px; + overflow: hidden; + margin: 0 auto; + box-shadow: 0 0 10px 0 var(--table-border-shadow-color-100); +`; + +const LogTableStyled = styled.table` + width: 100%; + background: var(--bg-color); + overflow: scroll; +`; + +const TheadStyled = styled.thead` + width: 100%; + height: 50px; + line-height: 50px; + background-color: var(--sys-main-color); + color: var(--white-text-with-bg-color); +`; + +const TbodyStyled = styled.tbody` + & > tr { + font-size: 11px; + text-align: center; + height: 50px; + } + & > tr > td { + height: 50px; + line-height: 50px; + width: 33.3%; + } + & > tr:nth-child(2n) { + background: var(--table-even-row-bg-color); + } +`; + +const EmptyLogStyled = styled.div` + width: 100%; + text-align: center; + font-size: 1rem; + padding: 20px 0; +`; + +export default AdminItemLogTable; diff --git a/frontend/src/Cabinet/components/ItemLog/ItemLogTable/AdminItemProvideTable.tsx b/frontend/src/Cabinet/components/ItemLog/ItemLogTable/AdminItemProvideTable.tsx new file mode 100644 index 000000000..d9d5c2930 --- /dev/null +++ b/frontend/src/Cabinet/components/ItemLog/ItemLogTable/AdminItemProvideTable.tsx @@ -0,0 +1,111 @@ +import styled from "styled-components"; +import LoadingAnimation from "@/Cabinet/components/Common/LoadingAnimation"; +import { ItemLogResponseType } from "@/Cabinet/types/dto/admin.dto"; +import { STATUS_400_BAD_REQUEST } from "@/Cabinet/constants/StatusCode"; + +const dateOptions: Intl.DateTimeFormatOptions = { + year: "2-digit", + month: "2-digit", + day: "2-digit", +}; + +const AdminItemProvideTable = ({ + itemLog, +}: { + itemLog: ItemLogResponseType; +}) => { + if (itemLog === undefined) return ; + + return ( + + + + + 지급일 + 아이템 + + + {itemLog !== STATUS_400_BAD_REQUEST && ( + + {itemLog.itemHistories.map( + ({ issuedDate, itemName, itemDetails, usedAt }, idx) => ( + + + {issuedDate + ? new Date(issuedDate).toLocaleString( + "ko-KR", + dateOptions + ) + : ""} + + + {itemName !== itemDetails + ? `${itemName} - ${itemDetails}` + : itemName} + + + ) + )} + + )} + + {itemLog === STATUS_400_BAD_REQUEST && ( + 아이템 사용기록이 없습니다. + )} + + ); +}; + +const LogTableWrapperstyled = styled.div` + width: 100%; + max-width: 800px; + border-radius: 10px; + overflow: hidden; + margin: 0 auto; + box-shadow: 0 0 10px 0 var(--table-border-shadow-color-100); +`; + +const LogTableStyled = styled.table` + width: 100%; + background: var(--bg-color); + overflow: scroll; +`; + +const TheadStyled = styled.thead` + width: 100%; + height: 50px; + line-height: 50px; + background-color: var(--sys-main-color); + color: var(--white-text-with-bg-color); +`; + +const TbodyStyled = styled.tbody` + & > tr { + font-size: small; + text-align: center; + height: 50px; + } + & > tr > td { + height: 50px; + line-height: 50px; + width: 33.3%; + } + & > tr:nth-child(2n) { + background: var(--table-even-row-bg-color); + } +`; + +const EmptyLogStyled = styled.div` + width: 100%; + text-align: center; + font-size: 1rem; + padding: 20px 0; +`; + +export default AdminItemProvideTable; diff --git a/frontend/src/Cabinet/components/LeftNav/LeftSectionNav/LeftSectionNavClubs.tsx b/frontend/src/Cabinet/components/LeftNav/LeftClubNav/LeftClubNav.tsx similarity index 92% rename from frontend/src/Cabinet/components/LeftNav/LeftSectionNav/LeftSectionNavClubs.tsx rename to frontend/src/Cabinet/components/LeftNav/LeftClubNav/LeftClubNav.tsx index 5224eb6a8..09d363114 100644 --- a/frontend/src/Cabinet/components/LeftNav/LeftSectionNav/LeftSectionNavClubs.tsx +++ b/frontend/src/Cabinet/components/LeftNav/LeftClubNav/LeftClubNav.tsx @@ -6,13 +6,11 @@ import { ClubPaginationResponseDto, ClubResponseDto, } from "@/Cabinet/types/dto/club.dto"; -import useMenu from "@/Cabinet/hooks/useMenu"; -const LeftSectionNavClubs = () => { +const LeftClubNav = ({ closeLeftNav }: { closeLeftNav: () => void }) => { const clubList = useRecoilValue(myClubListState); const [targetClubInfo, setTargetClubInfo] = useRecoilState(targetClubInfoState); - const { closeLeftNav } = useMenu(); return ( <> @@ -48,6 +46,7 @@ const ClubLeftNavOptionStyled = styled.div` padding: 32px 10px 32px; border-right: 1px solid var(--line-color); font-weight: 300; + font-size: var(--size-base); position: relative; font-size: var(--size-base); & hr { @@ -67,4 +66,4 @@ const ListTitleStyled = styled.div` font-weight: 500; `; -export default LeftSectionNavClubs; +export default LeftClubNav; diff --git a/frontend/src/Cabinet/components/LeftNav/LeftMainNav/LeftMainNav.container.tsx b/frontend/src/Cabinet/components/LeftNav/LeftMainNav/LeftMainNav.container.tsx index 90dfce091..f5ed614ab 100644 --- a/frontend/src/Cabinet/components/LeftNav/LeftMainNav/LeftMainNav.container.tsx +++ b/frontend/src/Cabinet/components/LeftNav/LeftMainNav/LeftMainNav.container.tsx @@ -12,8 +12,10 @@ import { currentFloorNumberState, currentMapFloorState, currentSectionNameState, + isCurrentSectionRenderState, myCabinetInfoState, numberOfAdminWorkState, + selectedTypeOnSearchState, } from "@/Cabinet/recoil/atoms"; import { currentBuildingFloorState } from "@/Cabinet/recoil/selectors"; import LeftMainNav from "@/Cabinet/components/LeftNav/LeftMainNav/LeftMainNav"; @@ -21,6 +23,7 @@ import { CabinetInfoByBuildingFloorDto, MyCabinetInfoResponseDto, } from "@/Cabinet/types/dto/cabinet.dto"; +import CabinetDetailAreaType from "@/Cabinet/types/enum/cabinetDetailArea.type.enum"; import { axiosCabinetByBuildingFloor } from "@/Cabinet/api/axios/axios.custom"; import { removeCookie } from "@/Cabinet/api/react_cookie/cookies"; import useMenu from "@/Cabinet/hooks/useMenu"; @@ -45,6 +48,10 @@ const LeftMainNavContainer = ({ isAdmin }: { isAdmin?: boolean }) => { const numberOfAdminWork = useRecoilValue(numberOfAdminWorkState); const navigator = useNavigate(); const { pathname } = useLocation(); + const [isCurrentSectionRender] = useRecoilState(isCurrentSectionRenderState); + const setSelectedTypeOnSearch = useSetRecoilState( + selectedTypeOnSearchState + ); useEffect(() => { if (currentFloor === undefined) { @@ -79,11 +86,13 @@ const LeftMainNavContainer = ({ isAdmin }: { isAdmin?: boolean }) => { myCabinetInfo?.cabinetId, numberOfAdminWork, myCabinetInfo?.status, + isCurrentSectionRender, ]); const onClickFloorButton = (floor: number) => { setCurrentFloor(floor); setCurrentMapFloor(floor); + setSelectedTypeOnSearch(CabinetDetailAreaType.CABINET); if (!pathname.includes("main")) { if (floor === currentFloor) { axiosCabinetByBuildingFloor(currentBuilding, currentFloor).then( @@ -118,7 +127,12 @@ const LeftMainNavContainer = ({ isAdmin }: { isAdmin?: boolean }) => { navigator("slack-notification"); closeAll(); }; - + + const onClickStoreButton = (): void => { + navigator("store"); + closeAll(); + }; + const onClickMainClubButton = () => { navigator("clubs"); closeAll(); @@ -152,7 +166,7 @@ const LeftMainNavContainer = ({ isAdmin }: { isAdmin?: boolean }) => { resetCurrentSection(); navigator("/login"); }; - + return ( { onClickMainClubButton={onClickMainClubButton} onClickProfileButton={onClickProfileButton} onClickAvailableButton={onClickAvailableButton} + onClickStoreButton={onClickStoreButton} isAdmin={isAdmin} /> ); diff --git a/frontend/src/Cabinet/components/LeftNav/LeftMainNav/LeftMainNav.tsx b/frontend/src/Cabinet/components/LeftNav/LeftMainNav/LeftMainNav.tsx index 548466036..2251e3ca4 100644 --- a/frontend/src/Cabinet/components/LeftNav/LeftMainNav/LeftMainNav.tsx +++ b/frontend/src/Cabinet/components/LeftNav/LeftMainNav/LeftMainNav.tsx @@ -1,10 +1,10 @@ import styled from "styled-components"; import { ReactComponent as LogoutImg } from "@/Cabinet/assets/images/close-square.svg"; -import { ReactComponent as CulbImg } from "@/Cabinet/assets/images/clubIconGray.svg"; +import { ReactComponent as ClubImg } from "@/Cabinet/assets/images/clubIconGray.svg"; import { ReactComponent as ProfileImg } from "@/Cabinet/assets/images/profile-circle.svg"; -import { ReactComponent as SearchImg } from "@/Cabinet/assets/images/search.svg"; import { ReactComponent as SlackNotiImg } from "@/Cabinet/assets/images/slack-notification.svg"; import { ReactComponent as SlackImg } from "@/Cabinet/assets/images/slack.svg"; +import { ReactComponent as StoreImg } from "@/Cabinet/assets/images/storeIconGray.svg"; interface ILeftMainNav { pathname: string; @@ -20,6 +20,7 @@ interface ILeftMainNav { onClickMainClubButton: React.MouseEventHandler; onClickProfileButton: React.MouseEventHandler; onClickAvailableButton: React.MouseEventHandler; + onClickStoreButton: React.MouseEventHandler; isAdmin?: boolean; } @@ -32,11 +33,11 @@ const LeftMainNav = ({ onClickFloorButton, onClickLogoutButton, onClickSlackNotiButton, - onClickSearchButton, onClickAdminClubButton, onClickMainClubButton, onClickProfileButton, onClickAvailableButton, + onClickStoreButton, isAdmin, }: ILeftMainNav) => { return ( @@ -89,25 +90,25 @@ const LeftMainNav = ({ <> - - Noti + + Store - - Search + + Noti - + Club - + Logout )} {!isAdmin && currentBuildingName === "새롬관" && ( <> + + + Store + - + Clubs - + Profile @@ -198,6 +210,8 @@ const TopBtnStyled = styled.li` width: 100%; height: 48px; line-height: 48px; + /* font-size: var(--size-base); */ + font-size: var(--size-base); font-weight: 300; margin-bottom: 2.5vh; border-radius: 10px; @@ -227,7 +241,7 @@ const BottomSectionStyled = styled.section` margin: 0 auto; width: 56px; height: 1px; - background-color: var(--toggle-switch-off-bg-color); + background-color: var(--line-color); } `; @@ -240,6 +254,7 @@ const BottomBtnStyled = styled.li` width: 100%; min-height: 48px; line-height: 1.125rem; + font-size: var(--size-base); font-weight: 300; margin-top: 2.5vh; border-radius: 10px; @@ -268,8 +283,11 @@ const BottomBtnStyled = styled.li` stroke: var(--button-line-color); } } - svg { + & > svg { margin: 0 auto; + width: 24px; + height: 24px; + stroke: var(--gray-line-btn-color); } @media (hover: hover) and (pointer: fine) { &:hover { diff --git a/frontend/src/Cabinet/components/LeftNav/LeftNav.tsx b/frontend/src/Cabinet/components/LeftNav/LeftNav.tsx index e7f68f4ce..409446e82 100644 --- a/frontend/src/Cabinet/components/LeftNav/LeftNav.tsx +++ b/frontend/src/Cabinet/components/LeftNav/LeftNav.tsx @@ -1,15 +1,38 @@ +import { useNavigate } from "react-router-dom"; import styled from "styled-components"; +import LeftClubNav from "@/Cabinet/components/LeftNav/LeftClubNav/LeftClubNav"; import LeftMainNavContainer from "@/Cabinet/components/LeftNav/LeftMainNav/LeftMainNav.container"; -import LeftSectionNavContainer from "@/Cabinet/components/LeftNav/LeftSectionNav/LeftSectionNav.container"; +import LeftProfileNav from "@/Cabinet/components/LeftNav/LeftProfileNav/LeftProfileNav"; +import LeftSectionNav from "@/Cabinet/components/LeftNav/LeftSectionNav/LeftSectionNav"; +import LeftStoreNav from "@/Cabinet/components/LeftNav/LeftStoreNav/LeftStoreNav"; +import useMenu from "@/Cabinet/hooks/useMenu"; const LeftNav: React.FC<{ isVisible: boolean; isAdmin?: boolean; }> = ({ isAdmin, isVisible }) => { + const navigator = useNavigate(); + const { closeLeftNav } = useMenu(); + const isProfilePage: boolean = location.pathname.includes("profile"); + const isMainClubPage: boolean = location.pathname === "/clubs"; + const isMainStorePage: boolean = location.pathname.includes("store"); + + const onClickRedirectButton = (location: string) => { + closeLeftNav(); + navigator(location); + }; + return ( - + {isVisible && } + {isProfilePage && ( + + )} + {isMainClubPage && } + {isMainStorePage && !isAdmin && ( + + )} ); }; diff --git a/frontend/src/Cabinet/components/LeftNav/LeftProfileNav/LeftProfileNav.tsx b/frontend/src/Cabinet/components/LeftNav/LeftProfileNav/LeftProfileNav.tsx new file mode 100644 index 000000000..cc18f9c29 --- /dev/null +++ b/frontend/src/Cabinet/components/LeftNav/LeftProfileNav/LeftProfileNav.tsx @@ -0,0 +1,118 @@ +import { useLocation } from "react-router-dom"; +import styled from "styled-components"; +import { FloorSectionStyled } from "@/Cabinet/components/LeftNav/LeftSectionNav/LeftSectionNav"; +import { ReactComponent as LinkImg } from "@/Cabinet/assets/images/link.svg"; + +const LeftProfileNav = ({ + onClickRedirectButton, +}: { + onClickRedirectButton: (location: string) => void; +}) => { + const { pathname } = useLocation(); + + const onClickSlack = () => { + window.open( + "https://42born2code.slack.com/archives/C02V6GE8LD7", + "_blank", + "noopener noreferrer" + ); + }; + + const onClickClubForm = () => { + window.open( + "https://docs.google.com/forms/d/e/1FAIpQLSfp-d7qq8gTvmQe5i6Gtv_mluNSICwuv5pMqeTBqt9NJXXP7w/closedform", + "_blank", + "noopener noreferrer" + ); + }; + + return ( + + onClickRedirectButton("profile")} + > + 내 정보 + + onClickRedirectButton("profile/log")} + > + 대여 기록 + +
+ onClickSlack()} + title="슬랙 캐비닛 채널 새창으로 열기" + > + 문의하기 + + + onClickClubForm()} + title="동아리 사물함 사용 신청서 새창으로 열기" + > + 동아리 신청서 + + +
+ ); +}; + +const ProfileLeftNavOptionStyled = styled.div` + display: block; + min-width: 240px; + height: 100%; + padding: 32px 10px; + border-right: 1px solid var(--line-color); + font-weight: 300; + font-size: var(--size-base); + position: relative; + & hr { + width: 80%; + height: 1px; + background-color: var(--inventory-item-title-border-btm-color); + border: 0; + margin-top: 20px; + margin-bottom: 20px; + } +`; + +const SectionLinkStyled = styled.div` + width: 100%; + height: 40px; + line-height: 40px; + text-indent: 20px; + margin: 2px 0; + padding-right: 30px; + cursor: pointer; + display: flex; + align-items: center; + color: var(--gray-line-btn-color); + + #linknImg { + width: 15px; + height: 15px; + margin-left: auto; + } + + @media (hover: hover) and (pointer: fine) { + &:hover { + color: var(--sys-main-color); + } + &:hover img { + filter: invert(33%) sepia(55%) saturate(3554%) hue-rotate(230deg) + brightness(99%) contrast(107%); + } + } +`; + +export default LeftProfileNav; diff --git a/frontend/src/Cabinet/components/LeftNav/LeftSectionNav/LeftSectionNav.container.tsx b/frontend/src/Cabinet/components/LeftNav/LeftSectionNav/LeftSectionNav.container.tsx deleted file mode 100644 index ee4171ec2..000000000 --- a/frontend/src/Cabinet/components/LeftNav/LeftSectionNav/LeftSectionNav.container.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import { useLocation, useNavigate } from "react-router-dom"; -import { useRecoilState, useRecoilValue } from "recoil"; -import { currentSectionNameState } from "@/Cabinet/recoil/atoms"; -import { currentFloorSectionState } from "@/Cabinet/recoil/selectors"; -import LeftSectionNav from "@/Cabinet/components/LeftNav/LeftSectionNav/LeftSectionNav"; -import useMenu from "@/Cabinet/hooks/useMenu"; - -const LeftSectionNavContainer = ({ isVisible }: { isVisible: boolean }) => { - const floorSection = useRecoilValue>(currentFloorSectionState); - const [currentFloorSection, setCurrentFloorSection] = useRecoilState( - currentSectionNameState - ); - const navigator = useNavigate(); - const { pathname } = useLocation(); - const { closeLeftNav } = useMenu(); - const isProfilePage: boolean = location.pathname.includes("profile"); - const isMainClubPage: boolean = location.pathname === "/clubs"; - - const onClickSection = (section: string) => { - closeLeftNav(); - setCurrentFloorSection(section); - }; - - const onClickProfile = () => { - closeLeftNav(); - navigator("profile"); - }; - - const onClickLentLogButton = () => { - closeLeftNav(); - navigator("profile/log"); - }; - - const onClickSlack = () => { - window.open( - "https://42born2code.slack.com/archives/C02V6GE8LD7", - "_blank", - "noopener noreferrer" - ); - }; - - const onClickClubForm = () => { - window.open( - "https://docs.google.com/forms/d/e/1FAIpQLSfp-d7qq8gTvmQe5i6Gtv_mluNSICwuv5pMqeTBqt9NJXXP7w/closedform", - "_blank", - "noopener noreferrer" - ); - }; - - return ( - - ); -}; - -export default LeftSectionNavContainer; diff --git a/frontend/src/Cabinet/components/LeftNav/LeftSectionNav/LeftSectionNav.tsx b/frontend/src/Cabinet/components/LeftNav/LeftSectionNav/LeftSectionNav.tsx index 7deadb65b..5939a9405 100644 --- a/frontend/src/Cabinet/components/LeftNav/LeftSectionNav/LeftSectionNav.tsx +++ b/frontend/src/Cabinet/components/LeftNav/LeftSectionNav/LeftSectionNav.tsx @@ -1,113 +1,67 @@ +import { useLocation } from "react-router-dom"; +import { useRecoilValue } from "recoil"; +import { useRecoilState } from "recoil"; import styled from "styled-components"; +import { currentSectionNameState } from "@/Cabinet/recoil/atoms"; +import { currentFloorSectionState } from "@/Cabinet/recoil/selectors"; import CabinetColorTable from "@/Cabinet/components/LeftNav/CabinetColorTable/CabinetColorTable"; -import LeftSectionNavClubs from "@/Cabinet/components/LeftNav/LeftSectionNav/LeftSectionNavClubs"; -import { ReactComponent as LinkImg } from "@/Cabinet/assets/images/link.svg"; +import { clubSectionsData } from "@/Cabinet/assets/data/mapPositionData"; +import { ReactComponent as FilledHeartIcon } from "@/Cabinet/assets/images/filledHeart.svg"; +import { ReactComponent as LineHeartIcon } from "@/Cabinet/assets/images/lineHeart.svg"; +import { ICurrentSectionInfo } from "@/Cabinet/types/dto/cabinet.dto"; -interface ILeftSectionNav { - isVisible: boolean; - onClickSection: Function; - currentFloorSection: string; - floorSection: string[]; - isProfile: boolean; - onClickProfile: Function; - pathname: string; - onClickLentLogButton: Function; - onClickSlack: Function; - onClickClubForm: Function; - isClub: boolean; -} +const LeftSectionNav = ({ closeLeftNav }: { closeLeftNav: () => void }) => { + const floorSection = useRecoilValue>( + currentFloorSectionState + ); + const [currentFloorSection, setCurrentFloorSection] = useRecoilState( + currentSectionNameState + ); + const { pathname } = useLocation(); + const isAdmin = pathname.includes("admin"); -const LeftSectionNav = ({ - isVisible, - currentFloorSection, - onClickSection, - floorSection, - isProfile, - onClickProfile, - pathname, - onClickLentLogButton, - onClickSlack, - onClickClubForm, - isClub, -}: ILeftSectionNav) => { return ( - <> - - {floorSection.map((section: string, index: number) => ( + + {floorSection.map((section: ICurrentSectionInfo, index: number) => { + const isClubSection = clubSectionsData.find((clubSection) => { + return clubSection === section.sectionName; + }) + ? true + : false; + return ( onClickSection(section)} + onClick={() => { + closeLeftNav(); + setCurrentFloorSection(section.sectionName); + }} > - {section} + {section.sectionName} + + {!isAdmin && + !isClubSection && + (section.alarmRegistered ? ( + + ) : ( + + ))} + - ))} - - - - - onClickProfile()} - > - 내 정보 - - onClickLentLogButton()} - > - 대여 기록 - -
- onClickSlack()} - title="슬랙 캐비닛 채널 새창으로 열기" - > - 문의하기 - - - onClickClubForm()} - title="동아리 사물함 사용 신청서 새창으로 열기" - > - 동아리 신청서 - - -
- {isClub && } - + ); + })} + +
); }; -const LeftNavOptionStyled = styled.div<{ - isVisible: boolean; -}>` - display: ${(props) => (props.isVisible ? "block" : "none")}; - min-width: 240px; - height: 100%; - padding: 32px 10px; - border-right: 1px solid var(--line-color); - font-weight: 300; - position: relative; +const LeftNavOptionStyled = styled.div` font-size: var(--size-base); -`; - -const ProfileLeftNavOptionStyled = styled.div<{ - isProfile: boolean; -}>` - display: ${(props) => (props.isProfile ? "block" : "none")}; + display: block; min-width: 240px; height: 100%; padding: 32px 10px; @@ -115,14 +69,6 @@ const ProfileLeftNavOptionStyled = styled.div<{ font-weight: 300; position: relative; font-size: var(--size-base); - & hr { - width: 80%; - height: 1px; - background-color: var(--service-man-title-border-btm-color); - border: 0; - margin-top: 20px; - margin-bottom: 20px; - } `; export const FloorSectionStyled = styled.div` @@ -134,6 +80,9 @@ export const FloorSectionStyled = styled.div` color: var(--gray-line-btn-color); margin: 2px 0; cursor: pointer; + display: flex; + justify-content: space-between; + align-items: center; @media (hover: hover) and (pointer: fine) { &:hover { background-color: var(--sys-main-color); @@ -142,32 +91,15 @@ export const FloorSectionStyled = styled.div` } `; -const SectionLinkStyled = styled.div` - width: 100%; - height: 40px; - line-height: 40px; - text-indent: 20px; - margin: 2px 0; - padding-right: 30px; - cursor: pointer; +const IconWrapperStyled = styled.div` + height: 14px; + width: 14px; + margin-right: 12px; display: flex; - align-items: center; - color: var(--gray-line-btn-color); - - #linknImg { - width: 15px; - height: 15px; - margin-left: auto; - } - @media (hover: hover) and (pointer: fine) { - &:hover { - color: var(--button-line-color); - - svg { - stroke: var(--button-line-color); - } - } + & > svg { + height: 14px; + width: 14px; } `; diff --git a/frontend/src/Cabinet/components/LeftNav/LeftStoreNav/LeftStoreNav.tsx b/frontend/src/Cabinet/components/LeftNav/LeftStoreNav/LeftStoreNav.tsx new file mode 100644 index 000000000..15bfbcebb --- /dev/null +++ b/frontend/src/Cabinet/components/LeftNav/LeftStoreNav/LeftStoreNav.tsx @@ -0,0 +1,152 @@ +import { useEffect, useState } from "react"; +import { useLocation } from "react-router-dom"; +import { useRecoilState } from "recoil"; +import styled from "styled-components"; +import { userState } from "@/Cabinet/recoil/atoms"; +import { ReactComponent as CoinIcon } from "@/Cabinet/assets/images/coinIcon.svg"; + +interface IStorePageItem { + name: string; + route: string; +} + +const storePages: IStorePageItem[] = [ + { name: "까비상점", route: "/store" }, + { name: "인벤토리", route: "/store/inventory" }, + { name: "아이템 사용내역", route: "/store/item-use-log" }, + { name: "코인 내역", route: "/store/coin-log" }, +]; + +const LeftStoreNav = ({ + onClickRedirectButton, +}: { + onClickRedirectButton: (location: string) => void; +}) => { + const location = useLocation(); + const [userInfo] = useRecoilState(userState); + + const getCurrentPageName = () => { + const matchingPage = storePages.find( + (page) => page.route === location.pathname + ); + return matchingPage ? matchingPage.name : storePages[0].name; + }; + + const [currentPage, setCurrentPage] = useState(getCurrentPageName()); + + useEffect(() => { + const handleRouteChange = () => { + setCurrentPage(getCurrentPageName()); + }; + + handleRouteChange(); + window.addEventListener("popstate", handleRouteChange); + + return () => { + window.removeEventListener("popstate", handleRouteChange); + }; + }, [location]); + + return ( + <> + + + 코인 + + + + + + {userInfo.coins} 까비 + + + +
+ {storePages.map((item: IStorePageItem) => ( + { + setCurrentPage(item.name); + onClickRedirectButton(item.route); + }} + > + {item.name} + + ))} +
+ + ); +}; + +const StoreLeftNavOptionStyled = styled.div` + min-width: 240px; + height: 100%; + padding: 32px 10px; + border-right: 1px solid var(--line-color); + font-weight: 300; + font-size: var(--size-base); + position: relative; + & hr { + width: 80%; + height: 1px; + background-color: var(--inventory-item-title-border-btm-color); + border: 0; + margin-top: 20px; + margin-bottom: 20px; + } +`; + +const CoinCountStyled = styled.div` + display: flex; + width: 100%; + padding: 0px 20px 0px 20px; + align-items: center; + justify-content: space-between; + color: var(--gray-line-btn-color); +`; + +const UserCoinsWrapperStyled = styled.div` + display: flex; + align-items: center; +`; + +const CoinTextStyled = styled.span` + margin-left: 5px; + & span { + font-weight: 800; + } +`; + +const StoreSectionStyled = styled.div` + width: 100%; + height: 40px; + line-height: 40px; + border-radius: 10px; + text-indent: 20px; + color: var(--gray-line-btn-color); + margin: 2px 0; + cursor: pointer; + &.leftNavButtonActive { + background-color: var(--sys-main-color); + color: var(--white-text-with-bg-color); + } + &:hover { + background-color: var(--sys-main-color); + color: var(--white-text-with-bg-color); + } +`; + +const CoinIconStyled = styled.div` + width: 20px; + height: 20px; + & > svg > path { + stroke: var(--sys-main-color); + } +`; + +export default LeftStoreNav; diff --git a/frontend/src/Cabinet/components/LentLog/AdminLentLog.tsx b/frontend/src/Cabinet/components/LentLog/AdminLentLog.tsx index ee0942a27..142b25c0f 100644 --- a/frontend/src/Cabinet/components/LentLog/AdminLentLog.tsx +++ b/frontend/src/Cabinet/components/LentLog/AdminLentLog.tsx @@ -70,7 +70,8 @@ const TitleContainer = styled.div` display: flex; justify-content: space-between; align-items: flex-start; - margin-bottom: 30px; + margin-top: 15px; + margin-bottom: 25px; `; const TitleStyled = styled.h1<{ isClick: boolean }>` diff --git a/frontend/src/Cabinet/components/LentLog/LogTable/LogTable.tsx b/frontend/src/Cabinet/components/LentLog/LogTable/LogTable.tsx index 4faea564f..87f822758 100644 --- a/frontend/src/Cabinet/components/LentLog/LogTable/LogTable.tsx +++ b/frontend/src/Cabinet/components/LentLog/LogTable/LogTable.tsx @@ -61,7 +61,6 @@ const LogTableWrapperstyled = styled.div` max-width: 800px; border-radius: 10px; overflow: hidden; - margin: 60px 0 0 0; box-shadow: 0 0 10px 0 var(--table-border-shadow-color-100); `; diff --git a/frontend/src/Cabinet/components/Login/AdminLoginTemplate.tsx b/frontend/src/Cabinet/components/Login/AdminLoginTemplate.tsx index a0962fddc..38c54f415 100644 --- a/frontend/src/Cabinet/components/Login/AdminLoginTemplate.tsx +++ b/frontend/src/Cabinet/components/Login/AdminLoginTemplate.tsx @@ -258,7 +258,7 @@ const CardInputStyled = styled.input<{ isFocus: boolean }>` border: ${(props) => props.isFocus ? "1px solid var(--sys-main-color)" - : "1px solid var(--toggle-switch-off-bg-color)"}; + : "1px solid var(--line-color)"}; color: var(--normal-text-color); `; diff --git a/frontend/src/Cabinet/components/MapInfo/MapItem/MapItem.tsx b/frontend/src/Cabinet/components/MapInfo/MapItem/MapItem.tsx index 804d812eb..deb52f4a2 100644 --- a/frontend/src/Cabinet/components/MapInfo/MapItem/MapItem.tsx +++ b/frontend/src/Cabinet/components/MapInfo/MapItem/MapItem.tsx @@ -56,7 +56,7 @@ const ItemStyled = styled.div<{ cursor: ${({ info }) => (info.type === "floorInfo" ? "default" : "pointer")}; color: ${({ info }) => info.type === "floorInfo" - ? "var(--toggle-switch-off-bg-color)" + ? "var(--line-color)" : "var(--white-text-with-bg-color)"}; display: flex; justify-content: center; @@ -72,7 +72,7 @@ const ItemStyled = styled.div<{ ? "var(--sys-main-color)" : info.type === "floorInfo" ? "transparent" - : "var(--toggle-switch-off-bg-color)"}; + : "var(--line-color)"}; &:hover { opacity: ${({ info }) => (info.type === "cabinet" ? 0.9 : 1)}; } diff --git a/frontend/src/Cabinet/components/Modals/BanModal/BanModal.tsx b/frontend/src/Cabinet/components/Modals/BanModal/BanModal.tsx index 838f8fb59..5690e0184 100644 --- a/frontend/src/Cabinet/components/Modals/BanModal/BanModal.tsx +++ b/frontend/src/Cabinet/components/Modals/BanModal/BanModal.tsx @@ -1,24 +1,24 @@ +import React, { useState } from "react"; +import { useSetRecoilState } from "recoil"; import { - axiosDeleteCurrentBanLog, - axiosGetBannedUserList, -} from "@/Cabinet/api/axios/axios.custom"; -import { additionalModalType, modalPropsMap } from "@/Cabinet/assets/data/maps"; + bannedUserListState, + isCurrentSectionRenderState, + numberOfAdminWorkState, + targetUserInfoState, +} from "@/Cabinet/recoil/atoms"; import Modal, { IModalContents } from "@/Cabinet/components/Modals/Modal"; import ModalPortal from "@/Cabinet/components/Modals/ModalPortal"; import { FailResponseModal, SuccessResponseModal, } from "@/Cabinet/components/Modals/ResponseModal/ResponseModal"; -import { - bannedUserListState, - isCurrentSectionRenderState, - numberOfAdminWorkState, - targetUserInfoState, -} from "@/Cabinet/recoil/atoms"; +import { additionalModalType, modalPropsMap } from "@/Cabinet/assets/data/maps"; import IconType from "@/Cabinet/types/enum/icon.type.enum"; +import { + axiosDeleteCurrentBanLog, + axiosGetBannedUserList, +} from "@/Cabinet/api/axios/axios.custom"; import { handleBannedUserList } from "@/Cabinet/utils/tableUtils"; -import React, { useState } from "react"; -import { useSetRecoilState } from "recoil"; const BanModal: React.FC<{ userId: number | null; @@ -40,7 +40,7 @@ const BanModal: React.FC<{ 해제하시겠습니까?`; const tryReturnRequest = async (e: React.MouseEvent) => { try { - // 패널티 해제 API 호출 + // 페널티 해제 API 호출 await axiosDeleteCurrentBanLog(props.userId); setIsCurrentSectionRender(true); setModalTitle("해제되었습니다"); diff --git a/frontend/src/Cabinet/components/Modals/ClubModal/AddClubMemberModal.tsx b/frontend/src/Cabinet/components/Modals/ClubModal/AddClubMemberModal.tsx index c65d3b549..0d46eba64 100644 --- a/frontend/src/Cabinet/components/Modals/ClubModal/AddClubMemberModal.tsx +++ b/frontend/src/Cabinet/components/Modals/ClubModal/AddClubMemberModal.tsx @@ -113,7 +113,7 @@ const ContentItemInputStyled = styled.input` cursor: "input"; color: "var(--normal-text-color)"; &::placeholder { - color: "var(--toggle-switch-off-bg-color)"; + color: "var(--line-color)"; } `; diff --git a/frontend/src/Cabinet/components/Modals/ClubModal/ClubModal.tsx b/frontend/src/Cabinet/components/Modals/ClubModal/ClubModal.tsx index 43faeca45..e6976a345 100644 --- a/frontend/src/Cabinet/components/Modals/ClubModal/ClubModal.tsx +++ b/frontend/src/Cabinet/components/Modals/ClubModal/ClubModal.tsx @@ -282,7 +282,7 @@ const ContentItemInputStyled = styled.input` color: var(--normal-text-color); &::placeholder { - color: var(--toggle-switch-off-bg-color); + color: var(--line-color); } `; diff --git a/frontend/src/Cabinet/components/Modals/ExtendModal/ExtendModal.tsx b/frontend/src/Cabinet/components/Modals/ExtendModal/ExtendModal.tsx index 60ebafbe3..7e243fbfe 100644 --- a/frontend/src/Cabinet/components/Modals/ExtendModal/ExtendModal.tsx +++ b/frontend/src/Cabinet/components/Modals/ExtendModal/ExtendModal.tsx @@ -1,15 +1,6 @@ -import { - axiosCabinetById, - axiosMyLentInfo, - axiosUseExtension, // axiosExtend, // TODO: 연장권 api 생성 후 연결해야 함 -} from "@/Cabinet/api/axios/axios.custom"; -import { additionalModalType, modalPropsMap } from "@/Cabinet/assets/data/maps"; -import Modal, { IModalContents } from "@/Cabinet/components/Modals/Modal"; -import ModalPortal from "@/Cabinet/components/Modals/ModalPortal"; -import { - FailResponseModal, - SuccessResponseModal, -} from "@/Cabinet/components/Modals/ResponseModal/ResponseModal"; +import React, { useEffect, useState } from "react"; +import { useRecoilState, useSetRecoilState } from "recoil"; +import styled from "styled-components"; import { currentCabinetIdState, isCurrentSectionRenderState, @@ -17,11 +8,30 @@ import { targetCabinetInfoState, userState, } from "@/Cabinet/recoil/atoms"; +import Dropdown from "@/Cabinet/components/Common/Dropdown"; +import Modal, { IModalContents } from "@/Cabinet/components/Modals/Modal"; +import ModalPortal from "@/Cabinet/components/Modals/ModalPortal"; +import { + FailResponseModal, + SuccessResponseModal, +} from "@/Cabinet/components/Modals/ResponseModal/ResponseModal"; +import { IInventoryInfo } from "@/Cabinet/components/Store/Inventory/Inventory"; +import { additionalModalType, modalPropsMap } from "@/Cabinet/assets/data/maps"; import { MyCabinetInfoResponseDto } from "@/Cabinet/types/dto/cabinet.dto"; import IconType from "@/Cabinet/types/enum/icon.type.enum"; +import { + axiosCabinetById, + axiosMyItems, + axiosMyLentInfo, + axiosUseItem, +} from "@/Cabinet/api/axios/axios.custom"; import { getExtendedDateString } from "@/Cabinet/utils/dateUtils"; -import React, { useState } from "react"; -import { useRecoilState, useRecoilValue, useSetRecoilState } from "recoil"; + +const extensionPeriod = [ + { sku: "EXTENSION_3", period: "3일", day: 3 }, + { sku: "EXTENSION_15", period: "15일", day: 15 }, + { sku: "EXTENSION_31", period: "31일", day: 31 }, +]; const ExtendModal: React.FC<{ onClose: () => void; @@ -30,7 +40,10 @@ const ExtendModal: React.FC<{ const [showResponseModal, setShowResponseModal] = useState(false); const [hasErrorOnResponse, setHasErrorOnResponse] = useState(false); const [modalTitle, setModalTitle] = useState(""); - const currentCabinetId = useRecoilValue(currentCabinetIdState); + const [modalContents, setModalContents] = useState(null); + const [extensionDate, setExtensionDate] = useState(3); + const [isOpen, setIsOpen] = useState(false); + const [currentCabinetId] = useRecoilState(currentCabinetIdState); const [myInfo, setMyInfo] = useRecoilState(userState); const [myLentInfo, setMyLentInfo] = useRecoilState(myCabinetInfoState); @@ -40,7 +53,9 @@ const ExtendModal: React.FC<{ ); const formattedExtendedDate = getExtendedDateString( myLentInfo.lents[0].expiredAt, - myInfo.lentExtensionResponseDto?.extensionPeriod + // myInfo.lentExtensionResponseDto?.extensionPeriod + extensionDate + //내가 선택한 옵션의 연장 기간을 number 로 넘겨주기 ); const extensionExpiredDate = getExtendedDateString( myInfo.lentExtensionResponseDto?.expiredAt, @@ -49,24 +64,123 @@ const ExtendModal: React.FC<{ const extendDetail = `사물함 연장권 사용 시, 대여 기간이 ${formattedExtendedDate} 23:59으로 연장됩니다. - 연장권 사용은 취소할 수 없습니다. - 연장권을 사용하시겠습니까?`; + 연장권 사용은 취소할 수 없습니다.`; const extendInfoDetail = `사물함을 대여하시면 연장권 사용이 가능합니다. 연장권은 ${extensionExpiredDate} 23:59 이후 만료됩니다.`; + const getModalTitle = (cabinetId: number | null) => { return cabinetId === null ? modalPropsMap[additionalModalType.MODAL_OWN_EXTENSION].title : modalPropsMap[additionalModalType.MODAL_USE_EXTENSION].title; }; + const getModalDetail = (cabinetId: number | null) => { return cabinetId === null ? extendInfoDetail : extendDetail; }; + const getModalProceedBtnText = (cabinetId: number | null) => { return cabinetId === null ? modalPropsMap[additionalModalType.MODAL_OWN_EXTENSION].confirmMessage : modalPropsMap[additionalModalType.MODAL_USE_EXTENSION].confirmMessage; }; - const tryExtendRequest = async (e: React.MouseEvent) => { + + // 연장권 보유 여부 확인하는 부분 + const [myItems, setMyItems] = useState(null); + const [selectedOption, setSelectedOption] = useState(0); + + const findMyExtension = (period: string) => { + return !myItems?.extensionItems.some((item) => item.itemDetails === period); + }; + + // 연장권이 하나라도 없다면 true + const checkExtension = () => { + return ( + findMyExtension("3일") && + findMyExtension("15일") && + findMyExtension("31일") + ); + }; + + const getDefault = () => { + if (!findMyExtension(extensionPeriod[0].period)) return 3; + if (!findMyExtension(extensionPeriod[1].period)) return 15; + if (!findMyExtension(extensionPeriod[2].period)) return 31; + else return 0; + }; + const getDefaultOption = (option: number) => { + if (option == 3) return 0; + else if (option == 15) return 1; + else return 2; + }; + + const getMyItems = async () => { + try { + const response = await axiosMyItems(); + setMyItems(response.data); + } catch (error: any) { + console.error("Error getting inventory:", error); + } + }; + + useEffect(() => { + getMyItems(); + }, []); + + useEffect(() => { + if (checkExtension() == true) { + setShowResponseModal(true); + setHasErrorOnResponse(true); + setModalContents( + `현재 연장권을 보유하고 있지 않습니다. +연장권은 까비 상점에서 구매하실 수 있습니다.` + ); + } else { + setShowResponseModal(false); + setHasErrorOnResponse(false); + } + setExtensionDate(getDefault()); + }, [myItems]); + + useEffect(() => { + setSelectedOption(getDefaultOption(extensionDate)); + }, [extensionDate]); + + + const handleDropdownChange = (option: number) => { + setSelectedOption(option); + setExtensionDate(extensionPeriod[option].day); + }; + + const extensionDropdownProps = { + options: [ + { + name: extensionPeriod[0].period, + value: 0, + isDisabled: findMyExtension(extensionPeriod[0].period), + }, + { + name: extensionPeriod[1].period, + value: 1, + isDisabled: findMyExtension(extensionPeriod[1].period), + }, + { + name: extensionPeriod[2].period, + value: 2, + isDisabled: findMyExtension(extensionPeriod[2].period), + }, + ], + defaultValue: findMyExtension(extensionPeriod[0].period) + ? findMyExtension(extensionPeriod[1].period) + ? extensionPeriod[2].period + : extensionPeriod[1].period + : extensionPeriod[0].period, + onChangeValue: handleDropdownChange, + isOpen: isOpen, + setIsOpen: setIsOpen, + }; + + const extensionItemUse = async (item: string) => { + // 아이템 사용 if (currentCabinetId === 0 || myInfo.cabinetId === null) { setHasErrorOnResponse(true); setModalTitle("현재 대여중인 사물함이 없습니다."); @@ -74,14 +188,17 @@ const ExtendModal: React.FC<{ return; } try { - await axiosUseExtension(); + await axiosUseItem(item, null, null, null, null); setMyInfo({ ...myInfo, cabinetId: currentCabinetId, lentExtensionResponseDto: null, }); setIsCurrentSectionRender(true); - setModalTitle("연장되었습니다"); + setModalTitle("연장권 사용완료"); + setModalContents( + `대여 기간이 ${formattedExtendedDate}으로 연장되었습니다.` + ); try { const { data } = await axiosCabinetById(currentCabinetId); setTargetCabinetInfo(data); @@ -96,14 +213,23 @@ const ExtendModal: React.FC<{ } } catch (error: any) { setHasErrorOnResponse(true); - error.response - ? setModalTitle(error.response.data.message) - : setModalTitle(error.data.message); + if (error.response.status === 400) { + setModalTitle("연장권 사용실패"); + setModalContents( + `현재 연장권을 보유하고 있지 않습니다. + 연장권은 까비 상점에서 구매하실 수 있습니다.` + ); + } else + error.response + ? setModalTitle(error.response.data.message) + : setModalTitle(error.data.message); } finally { setShowResponseModal(true); } }; + + const extendModalContents: IModalContents = { type: myInfo.cabinetId === null ? "penaltyBtn" : "hasProceedBtn", title: getModalTitle(myInfo.cabinetId), @@ -114,9 +240,20 @@ const ExtendModal: React.FC<{ ? async (e: React.MouseEvent) => { props.onClose(); } - : tryExtendRequest, + : async () => { + extensionItemUse(extensionPeriod[selectedOption].sku); + }, closeModal: props.onClose, iconType: IconType.CHECKICON, + renderAdditionalComponent: () => ( + <> + + 연장권 타입 + + + + + ), }; return ( @@ -126,11 +263,15 @@ const ExtendModal: React.FC<{ (hasErrorOnResponse ? ( ) : ( ))} @@ -138,4 +279,26 @@ const ExtendModal: React.FC<{ ); }; +const ModalContainerStyled = styled.div` + padding: 10px 20px 0 20px; +`; + +const ModalDropdownNameStyled = styled.div` + display: flex; + margin: 10px 10px 15px 5px; + font-size: 18px; +`; + +const ModalDetailStyled = styled.div` + width: 100%; + height: 100%; + margin-top: 30px; + > p { + margin: 10px; + > span { + font-weight: 600; + } + } +`; + export default ExtendModal; diff --git a/frontend/src/Cabinet/components/Modals/ManualModal/ManualModal.tsx b/frontend/src/Cabinet/components/Modals/ManualModal/ManualModal.tsx index 2acebafdd..494d11383 100644 --- a/frontend/src/Cabinet/components/Modals/ManualModal/ManualModal.tsx +++ b/frontend/src/Cabinet/components/Modals/ManualModal/ManualModal.tsx @@ -2,11 +2,7 @@ import React from "react"; import { useState } from "react"; import styled, { keyframes } from "styled-components"; import { manualContentData } from "@/Cabinet/assets/data/ManualContent"; -import { ReactComponent as ClubIcon } from "@/Cabinet/assets/images/clubIcon.svg"; -import { ReactComponent as ExtensionIcon } from "@/Cabinet/assets/images/extension.svg"; import { ReactComponent as MoveBtnImg } from "@/Cabinet/assets/images/moveButton.svg"; -import { ReactComponent as PrivateIcon } from "@/Cabinet/assets/images/privateIcon.svg"; -import { ReactComponent as ShareIcon } from "@/Cabinet/assets/images/shareIcon.svg"; import ContentStatus from "@/Cabinet/types/enum/content.status.enum"; interface ModalProps { @@ -60,17 +56,8 @@ const ManualModal: React.FC = ({ {hasImage && ( - {contentStatus === ContentStatus.PRIVATE && ( - - )} - {contentStatus === ContentStatus.SHARE && ( - - )} - {contentStatus === ContentStatus.CLUB && ( - - )} - {contentStatus === ContentStatus.EXTENSION && ( - + {contentData.iconComponent && ( + )} {isCabinetType && ( @@ -339,10 +326,6 @@ const ContentImgStyled = styled.div<{ props.contentStatus === ContentStatus.EXTENSION ? "var(--normal-text-color)" : "var(--white-text-with-bg-color)"}; - transform: ${(props) => - props.contentStatus === ContentStatus.EXTENSION - ? "scale(1.4)" - : "scale(3.3)"}; } } `; diff --git a/frontend/src/Cabinet/components/Modals/MemoModal/MemoModal.tsx b/frontend/src/Cabinet/components/Modals/MemoModal/MemoModal.tsx index aaec64b5d..9d90cb626 100644 --- a/frontend/src/Cabinet/components/Modals/MemoModal/MemoModal.tsx +++ b/frontend/src/Cabinet/components/Modals/MemoModal/MemoModal.tsx @@ -6,14 +6,14 @@ import CabinetType from "@/Cabinet/types/enum/cabinet.type.enum"; export interface MemoModalInterface { cabinetType: CabinetType; - cabinetTitle: string | null; + cabinetTitle: string; cabinetMemo: string; } interface MemoModalContainerInterface { memoModalObj: MemoModalInterface; onClose: React.MouseEventHandler; - onSave: (newTitle: string | null, newMemo: string) => void; + onSave: (newTitle: string, newMemo: string) => void; } const MAX_INPUT_LENGTH = 14; @@ -33,6 +33,7 @@ const MemoModal = ({ newTitle.current.select(); } }; + const handleClickSave = (e: React.MouseEvent) => { //사물함 제목, 사물함 비밀메모 update api 호출 // onClose(e); @@ -40,7 +41,7 @@ const MemoModal = ({ if (newTitle.current!.value) { onSave(newTitle.current!.value, newMemo.current!.value); } else { - onSave(null, newMemo.current!.value); + onSave("", newMemo.current!.value); } setMode("read"); }; @@ -100,12 +101,12 @@ const MemoModal = ({ ? onClose : () => { setMode("read"); - if (cabinetTitle) newTitle.current!.value = cabinetTitle; + newTitle.current!.value = cabinetTitle; newMemo.current!.value = cabinetMemo; } } text={mode === "read" ? "닫기" : "취소"} - theme={mode === "read" ? "light-grayLine" : "line"} + theme={mode === "read" ? "grayLine" : "line"} /> @@ -183,9 +184,7 @@ const ContentItemInputStyled = styled.input<{ mode === "read" ? "var(--sys-main-color)" : "var(--normal-text-color)"}; &::placeholder { color: ${({ mode }) => - mode === "read" - ? "var(--sys-main-color)" - : "var(--toggle-switch-off-bg-color)"}; + mode === "read" ? "var(--sys-main-color)" : "var(--line-color)"}; } `; diff --git a/frontend/src/Cabinet/components/Modals/Modal.tsx b/frontend/src/Cabinet/components/Modals/Modal.tsx index a2256af07..a854f03a8 100644 --- a/frontend/src/Cabinet/components/Modals/Modal.tsx +++ b/frontend/src/Cabinet/components/Modals/Modal.tsx @@ -1,11 +1,11 @@ import React, { ReactElement } from "react"; +import { useNavigate } from "react-router-dom"; import styled, { css } from "styled-components"; import AdminClubLogContainer from "@/Cabinet/components/Club/AdminClubLog.container"; import Button from "@/Cabinet/components/Common/Button"; import { ReactComponent as CheckIcon } from "@/Cabinet/assets/images/checkIcon.svg"; import { ReactComponent as ErrorIcon } from "@/Cabinet/assets/images/errorIcon.svg"; import { ReactComponent as NotificationIcon } from "@/Cabinet/assets/images/notificationSign.svg"; -import IconType from "@/Cabinet/types/enum/icon.type.enum"; import useMultiSelect from "@/Cabinet/hooks/useMultiSelect"; /** @@ -24,6 +24,8 @@ import useMultiSelect from "@/Cabinet/hooks/useMultiSelect"; * @property {boolean} isClubLentModal : 동아리 (CLUB) 대여 모달인지 여부 * @property {boolean} isLoading : 로딩중 요청 버튼 비활성화 감지를 위한 변수 * @property {boolean} isCheckIcon : checkIcon인지 errorIcon인지 감지를 위한 변수 + * @property {string} urlTitle : 모달에서 링크로 이동할 url의 제목 + * @property {string} url : 모달에서 링크로 이동할 url 값 */ export interface IModalContents { type: string; @@ -38,6 +40,8 @@ export interface IModalContents { isClubLentModal?: boolean; isLoading?: boolean; iconType?: string; + urlTitle?: string | null; + url?: string | null; } const Modal: React.FC<{ modalContents: IModalContents }> = (props) => { @@ -54,8 +58,11 @@ const Modal: React.FC<{ modalContents: IModalContents }> = (props) => { isClubLentModal, isLoading, iconType, + urlTitle, + url, } = props.modalContents; const { isMultiSelect, closeMultiSelectMode } = useMultiSelect(); + const navigator = useNavigate(); return ( <> @@ -91,11 +98,6 @@ const Modal: React.FC<{ modalContents: IModalContents }> = (props) => { {renderAdditionalComponent && renderAdditionalComponent()} {type === "hasProceedBtn" && ( -