diff --git a/package-lock.json b/package-lock.json index 234d4ba..86c3c64 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "@mui/x-date-pickers": "^7.22.3", "axios": "^1.7.8", "dayjs": "^1.11.13", + "firebase": "^9.20.0", "react": "^18.3.1", "react-dom": "^18.3.1", "react-hot-toast": "^2.4.1", @@ -1085,6 +1086,526 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@firebase/analytics": { + "version": "0.9.5", + "resolved": "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.9.5.tgz", + "integrity": "sha512-hJTVs2jLxPXE7hs7D/jaEsgGivrm7tSEl65kb5NkDBWV7QQBUnRfVML/xra9nTFLLJhAdbExZPHg6HfIuMSYEQ==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/installations": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/analytics-compat": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@firebase/analytics-compat/-/analytics-compat-0.2.5.tgz", + "integrity": "sha512-ohKUrwSoXvyUJdSLuDr82mOqrzgWKyHMUt9/TfYKkyDXnFjNlBcFBpkpl/UHMAOJe0M60YYXiVCZoGQYldCslA==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/analytics": "0.9.5", + "@firebase/analytics-types": "0.8.0", + "@firebase/component": "0.6.4", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/analytics-types": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@firebase/analytics-types/-/analytics-types-0.8.0.tgz", + "integrity": "sha512-iRP+QKI2+oz3UAh4nPEq14CsEjrjD6a5+fuypjScisAh9kXKFvdJOZJDwk7kikLvWVLGEs9+kIUS4LPQV7VZVw==", + "license": "Apache-2.0" + }, + "node_modules/@firebase/app": { + "version": "0.9.8", + "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.9.8.tgz", + "integrity": "sha512-mYoH/aT4Dx6szBBnO7qcEr5ieJRnWU9TENgPiZI5DtkrIDTpW9540KMn996176PkR4GbLKto6rtvUX5P7ii+KQ==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "idb": "7.0.1", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/app-check": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/@firebase/app-check/-/app-check-0.6.5.tgz", + "integrity": "sha512-TCHJ+kghqDiNWCXAsPnHaE98CxBfEW9D16CIC3gYVaXrh3w42UNWqbR+S+ggSc7xN+vP9QRhCOY5pvr7rBEEUg==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/app-check-compat": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@firebase/app-check-compat/-/app-check-compat-0.3.5.tgz", + "integrity": "sha512-ji+LxuM2AyFCaJCBfJllnQ1OIedMq+iMwzABlfP9yVrhcR6ZSdCLLhDGMyoENyoPiZo6av+5b3acYUTYrffFeQ==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/app-check": "0.6.5", + "@firebase/app-check-types": "0.5.0", + "@firebase/component": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/app-check-interop-types": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@firebase/app-check-interop-types/-/app-check-interop-types-0.2.0.tgz", + "integrity": "sha512-+3PQIeX6/eiVK+x/yg8r6xTNR97fN7MahFDm+jiQmDjcyvSefoGuTTNQuuMScGyx3vYUBeZn+Cp9kC0yY/9uxQ==", + "license": "Apache-2.0" + }, + "node_modules/@firebase/app-check-types": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@firebase/app-check-types/-/app-check-types-0.5.0.tgz", + "integrity": "sha512-uwSUj32Mlubybw7tedRzR24RP8M8JUVR3NPiMk3/Z4bCmgEKTlQBwMXrehDAZ2wF+TsBq0SN1c6ema71U/JPyQ==", + "license": "Apache-2.0" + }, + "node_modules/@firebase/app-compat": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.2.8.tgz", + "integrity": "sha512-aG9juNXD+m8gWs6VnrLUWWV1LtJu8W0+uyX5u+Sz6YjDO69JN2jEaxCsb7Wr1egXKKJN1YrhoS+0kQqWakp61Q==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/app": "0.9.8", + "@firebase/component": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/app-types": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.0.tgz", + "integrity": "sha512-AeweANOIo0Mb8GiYm3xhTEBVCmPwTYAu9Hcd2qSkLuga/6+j9b1Jskl5bpiSQWy9eJ/j5pavxj6eYogmnuzm+Q==", + "license": "Apache-2.0" + }, + "node_modules/@firebase/auth": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-0.23.0.tgz", + "integrity": "sha512-OzDs1osO8R/9BIgKLoJCRoDdR4sM/MUVu2mNhMya2qJVH00I1fYqrmGeV3jUH5vcy0MYkJvxa2J7oXetaoKcCg==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "node-fetch": "2.6.7", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/auth-compat": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@firebase/auth-compat/-/auth-compat-0.4.0.tgz", + "integrity": "sha512-MS4S90rOjv9/DkumQkKbQs84YgRVHLFQKI+UI3PRdbPO+50Bl3MNXtTyGlLKSIdMjMISeX8IbyBmCdOOTQZmLw==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/auth": "0.23.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" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/auth-interop-types": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.2.1.tgz", + "integrity": "sha512-VOaGzKp65MY6P5FI84TfYKBXEPi6LmOCSMMzys6o2BN2LOsqy7pCuZCup7NYnfbk5OkkQKzvIfHOzTm0UDpkyg==", + "license": "Apache-2.0" + }, + "node_modules/@firebase/auth-types": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@firebase/auth-types/-/auth-types-0.12.0.tgz", + "integrity": "sha512-pPwaZt+SPOshK8xNoiQlK5XIrS97kFYc3Rc7xmy373QsOJ9MmqXxLaYssP5Kcds4wd2qK//amx/c+A8O2fVeZA==", + "license": "Apache-2.0", + "peerDependencies": { + "@firebase/app-types": "0.x", + "@firebase/util": "1.x" + } + }, + "node_modules/@firebase/component": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.6.4.tgz", + "integrity": "sha512-rLMyrXuO9jcAUCaQXCMjCMUsWrba5fzHlNK24xz5j2W6A/SRmK8mZJ/hn7V0fViLbxC0lPMtrK1eYzk6Fg03jA==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/database": { + "version": "0.14.4", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.14.4.tgz", + "integrity": "sha512-+Ea/IKGwh42jwdjCyzTmeZeLM3oy1h0mFPsTy6OqCWzcu/KFqRAr5Tt1HRCOBlNOdbh84JPZC47WLU18n2VbxQ==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/auth-interop-types": "0.2.1", + "@firebase/component": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "faye-websocket": "0.11.4", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/database-compat": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-0.3.4.tgz", + "integrity": "sha512-kuAW+l+sLMUKBThnvxvUZ+Q1ZrF/vFJ58iUY9kAcbX48U03nVzIF6Tmkf0p3WVQwMqiXguSgtOPIB6ZCeF+5Gg==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/database": "0.14.4", + "@firebase/database-types": "0.10.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/database-types": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.10.4.tgz", + "integrity": "sha512-dPySn0vJ/89ZeBac70T+2tWWPiJXWbmRygYv0smT5TfE3hDrQ09eKMF3Y+vMlTdrMWq7mUdYW5REWPSGH4kAZQ==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/app-types": "0.9.0", + "@firebase/util": "1.9.3" + } + }, + "node_modules/@firebase/firestore": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-3.10.1.tgz", + "integrity": "sha512-p+WQMLkuHECVjB6zoyZYF4OjudquW9IlHsBx7eIfyvOZyOtTEmbSmNrJaWsqCZ/9kDo94XYJx/eZQ2Y4WBAV4A==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "@firebase/webchannel-wrapper": "0.9.0", + "@grpc/grpc-js": "~1.7.0", + "@grpc/proto-loader": "^0.6.13", + "node-fetch": "2.6.7", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=10.10.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/firestore-compat": { + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/@firebase/firestore-compat/-/firestore-compat-0.3.7.tgz", + "integrity": "sha512-pwSEh75e0WIQjU6UdZJcdP0AO1Tj2P7r1aIWcBf7kdqTOwZmplxhJ/rXNL6IaKo2fP+/9osXaLZiBH6WWrSbfQ==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/firestore": "3.10.1", + "@firebase/firestore-types": "2.5.1", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/firestore-types": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@firebase/firestore-types/-/firestore-types-2.5.1.tgz", + "integrity": "sha512-xG0CA6EMfYo8YeUxC8FeDzf6W3FX1cLlcAGBYV6Cku12sZRI81oWcu61RSKM66K6kUENP+78Qm8mvroBcm1whw==", + "license": "Apache-2.0", + "peerDependencies": { + "@firebase/app-types": "0.x", + "@firebase/util": "1.x" + } + }, + "node_modules/@firebase/functions": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/@firebase/functions/-/functions-0.9.4.tgz", + "integrity": "sha512-3H2qh6U+q+nepO5Hds+Ddl6J0pS+zisuBLqqQMRBHv9XpWfu0PnDHklNmE8rZ+ccTEXvBj6zjkPfdxt6NisvlQ==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/app-check-interop-types": "0.2.0", + "@firebase/auth-interop-types": "0.2.1", + "@firebase/component": "0.6.4", + "@firebase/messaging-interop-types": "0.2.0", + "@firebase/util": "1.9.3", + "node-fetch": "2.6.7", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/functions-compat": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@firebase/functions-compat/-/functions-compat-0.3.4.tgz", + "integrity": "sha512-kxVxTGyLV1MBR3sp3mI+eQ6JBqz0G5bk310F8eX4HzDFk4xjk5xY0KdHktMH+edM2xs1BOg0vwvvsAHczIjB+w==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/functions": "0.9.4", + "@firebase/functions-types": "0.6.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/functions-types": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@firebase/functions-types/-/functions-types-0.6.0.tgz", + "integrity": "sha512-hfEw5VJtgWXIRf92ImLkgENqpL6IWpYaXVYiRkFY1jJ9+6tIhWM7IzzwbevwIIud/jaxKVdRzD7QBWfPmkwCYw==", + "license": "Apache-2.0" + }, + "node_modules/@firebase/installations": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/@firebase/installations/-/installations-0.6.4.tgz", + "integrity": "sha512-u5y88rtsp7NYkCHC3ElbFBrPtieUybZluXyzl7+4BsIz4sqb4vSAuwHEUgCgCeaQhvsnxDEU6icly8U9zsJigA==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/util": "1.9.3", + "idb": "7.0.1", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/installations-compat": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@firebase/installations-compat/-/installations-compat-0.2.4.tgz", + "integrity": "sha512-LI9dYjp0aT9Njkn9U4JRrDqQ6KXeAmFbRC0E7jI7+hxl5YmRWysq5qgQl22hcWpTk+cm3es66d/apoDU/A9n6Q==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/installations": "0.6.4", + "@firebase/installations-types": "0.5.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/installations-types": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@firebase/installations-types/-/installations-types-0.5.0.tgz", + "integrity": "sha512-9DP+RGfzoI2jH7gY4SlzqvZ+hr7gYzPODrbzVD82Y12kScZ6ZpRg/i3j6rleto8vTFC8n6Len4560FnV1w2IRg==", + "license": "Apache-2.0", + "peerDependencies": { + "@firebase/app-types": "0.x" + } + }, + "node_modules/@firebase/logger": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.4.0.tgz", + "integrity": "sha512-eRKSeykumZ5+cJPdxxJRgAC3G5NknY2GwEbKfymdnXtnT0Ucm4pspfR6GT4MUQEDuJwRVbVcSx85kgJulMoFFA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/messaging": { + "version": "0.12.4", + "resolved": "https://registry.npmjs.org/@firebase/messaging/-/messaging-0.12.4.tgz", + "integrity": "sha512-6JLZct6zUaex4g7HI3QbzeUrg9xcnmDAPTWpkoMpd/GoSVWH98zDoWXMGrcvHeCAIsLpFMe4MPoZkJbrPhaASw==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/installations": "0.6.4", + "@firebase/messaging-interop-types": "0.2.0", + "@firebase/util": "1.9.3", + "idb": "7.0.1", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/messaging-compat": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@firebase/messaging-compat/-/messaging-compat-0.2.4.tgz", + "integrity": "sha512-lyFjeUhIsPRYDPNIkYX1LcZMpoVbBWXX4rPl7c/rqc7G+EUea7IEtSt4MxTvh6fDfPuzLn7+FZADfscC+tNMfg==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/messaging": "0.12.4", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/messaging-interop-types": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@firebase/messaging-interop-types/-/messaging-interop-types-0.2.0.tgz", + "integrity": "sha512-ujA8dcRuVeBixGR9CtegfpU4YmZf3Lt7QYkcj693FFannwNuZgfAYaTmbJ40dtjB81SAu6tbFPL9YLNT15KmOQ==", + "license": "Apache-2.0" + }, + "node_modules/@firebase/performance": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/@firebase/performance/-/performance-0.6.4.tgz", + "integrity": "sha512-HfTn/bd8mfy/61vEqaBelNiNnvAbUtME2S25A67Nb34zVuCSCRIX4SseXY6zBnOFj3oLisaEqhVcJmVPAej67g==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/installations": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/performance-compat": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@firebase/performance-compat/-/performance-compat-0.2.4.tgz", + "integrity": "sha512-nnHUb8uP9G8islzcld/k6Bg5RhX62VpbAb/Anj7IXs/hp32Eb2LqFPZK4sy3pKkBUO5wcrlRWQa6wKOxqlUqsg==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/performance": "0.6.4", + "@firebase/performance-types": "0.2.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/performance-types": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@firebase/performance-types/-/performance-types-0.2.0.tgz", + "integrity": "sha512-kYrbr8e/CYr1KLrLYZZt2noNnf+pRwDq2KK9Au9jHrBMnb0/C9X9yWSXmZkFt4UIdsQknBq8uBB7fsybZdOBTA==", + "license": "Apache-2.0" + }, + "node_modules/@firebase/remote-config": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/@firebase/remote-config/-/remote-config-0.4.4.tgz", + "integrity": "sha512-x1ioTHGX8ZwDSTOVp8PBLv2/wfwKzb4pxi0gFezS5GCJwbLlloUH4YYZHHS83IPxnua8b6l0IXUaWd0RgbWwzQ==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/installations": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/remote-config-compat": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@firebase/remote-config-compat/-/remote-config-compat-0.2.4.tgz", + "integrity": "sha512-FKiki53jZirrDFkBHglB3C07j5wBpitAaj8kLME6g8Mx+aq7u9P7qfmuSRytiOItADhWUj7O1JIv7n9q87SuwA==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/remote-config": "0.4.4", + "@firebase/remote-config-types": "0.3.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/remote-config-types": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@firebase/remote-config-types/-/remote-config-types-0.3.0.tgz", + "integrity": "sha512-RtEH4vdcbXZuZWRZbIRmQVBNsE7VDQpet2qFvq6vwKLBIQRQR5Kh58M4ok3A3US8Sr3rubYnaGqZSurCwI8uMA==", + "license": "Apache-2.0" + }, + "node_modules/@firebase/storage": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/@firebase/storage/-/storage-0.11.2.tgz", + "integrity": "sha512-CtvoFaBI4hGXlXbaCHf8humajkbXhs39Nbh6MbNxtwJiCqxPy9iH3D3CCfXAvP0QvAAwmJUTK3+z9a++Kc4nkA==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/util": "1.9.3", + "node-fetch": "2.6.7", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/storage-compat": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@firebase/storage-compat/-/storage-compat-0.3.2.tgz", + "integrity": "sha512-wvsXlLa9DVOMQJckbDNhXKKxRNNewyUhhbXev3t8kSgoCotd1v3MmqhKKz93ePhDnhHnDs7bYHy+Qa8dRY6BXw==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/storage": "0.11.2", + "@firebase/storage-types": "0.8.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/storage-types": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@firebase/storage-types/-/storage-types-0.8.0.tgz", + "integrity": "sha512-isRHcGrTs9kITJC0AVehHfpraWFui39MPaU7Eo8QfWlqW7YPymBmRgjDrlOgFdURh6Cdeg07zmkLP5tzTKRSpg==", + "license": "Apache-2.0", + "peerDependencies": { + "@firebase/app-types": "0.x", + "@firebase/util": "1.x" + } + }, + "node_modules/@firebase/util": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.9.3.tgz", + "integrity": "sha512-DY02CRhOZwpzO36fHpuVysz6JZrscPiBXD0fXp6qSrL9oNOx5KWICKdR95C0lSITzxp0TZosVyHqzatE8JbcjA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/webchannel-wrapper": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-0.9.0.tgz", + "integrity": "sha512-BpiZLBWdLFw+qFel9p3Zs1jD6QmH7Ii4aTDu6+vx8ShdidChZUXqDhYJly4ZjSgQh54miXbBgBrk0S+jTIh/Qg==", + "license": "Apache-2.0" + }, "node_modules/@fontsource/material-icons": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/@fontsource/material-icons/-/material-icons-5.1.0.tgz", @@ -1106,6 +1627,139 @@ "node": ">=6" } }, + "node_modules/@grpc/grpc-js": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.7.3.tgz", + "integrity": "sha512-H9l79u4kJ2PVSxUNA08HMYAnUBLj9v6KjYQ7SQ71hOZcEXhShE/y5iQCesP8+6/Ik/7i2O0a10bPquIcYfufog==", + "license": "Apache-2.0", + "dependencies": { + "@grpc/proto-loader": "^0.7.0", + "@types/node": ">=12.12.47" + }, + "engines": { + "node": "^8.13.0 || >=10.10.0" + } + }, + "node_modules/@grpc/grpc-js/node_modules/@grpc/proto-loader": { + "version": "0.7.13", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.13.tgz", + "integrity": "sha512-AiXO/bfe9bmxBjxxtYxFAXGZvMaN5s8kO+jBHAJCON8rJoB5YS/D6X7ZNc6XQkuHNmyl4CYaMI1fJ/Gn27RGGw==", + "license": "Apache-2.0", + "dependencies": { + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", + "protobufjs": "^7.2.5", + "yargs": "^17.7.2" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@grpc/grpc-js/node_modules/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==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@grpc/grpc-js/node_modules/long": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", + "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==", + "license": "Apache-2.0" + }, + "node_modules/@grpc/grpc-js/node_modules/protobufjs": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.4.0.tgz", + "integrity": "sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@grpc/grpc-js/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@grpc/grpc-js/node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@grpc/grpc-js/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/@grpc/proto-loader": { + "version": "0.6.13", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.6.13.tgz", + "integrity": "sha512-FjxPYDRTn6Ec3V0arm1FtSpmP6V50wuph2yILpyvTKzjc76oDdoihXqM1DzOW5ubvCC8GivfCnNtfaRE8myJ7g==", + "license": "Apache-2.0", + "dependencies": { + "@types/long": "^4.0.1", + "lodash.camelcase": "^4.3.0", + "long": "^4.0.0", + "protobufjs": "^6.11.3", + "yargs": "^16.2.0" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -1637,6 +2291,70 @@ "url": "https://opencollective.com/popperjs" } }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", + "license": "BSD-3-Clause" + }, "node_modules/@rollup/pluginutils": { "version": "5.1.3", "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.3.tgz", @@ -2649,6 +3367,12 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/long": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", + "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==", + "license": "MIT" + }, "node_modules/@types/mdx": { "version": "2.0.13", "resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.13.tgz", @@ -2667,7 +3391,6 @@ "version": "22.9.4", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.9.4.tgz", "integrity": "sha512-d9RWfoR7JC/87vj7n+PVTzGg9hDyuFjir3RxUHbjFSKNd9mpxbxwMEyaCim/ddCmy4IuW7HjTzF3g9p3EtWEOg==", - "dev": true, "license": "MIT", "dependencies": { "undici-types": "~6.19.8" @@ -3034,7 +3757,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -3044,7 +3766,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -3488,6 +4209,29 @@ "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", "license": "MIT" }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/clsx": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", @@ -3501,7 +4245,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -3514,7 +4257,6 @@ "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, "license": "MIT" }, "node_modules/combined-stream": { @@ -3792,6 +4534,12 @@ "dev": true, "license": "ISC" }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, "node_modules/enquire.js": { "version": "2.1.6", "resolved": "https://registry.npmjs.org/enquire.js/-/enquire.js-2.1.6.tgz", @@ -4031,7 +4779,6 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -4390,6 +5137,18 @@ "reusify": "^1.0.4" } }, + "node_modules/faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "license": "Apache-2.0", + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", @@ -4449,6 +5208,40 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/firebase": { + "version": "9.20.0", + "resolved": "https://registry.npmjs.org/firebase/-/firebase-9.20.0.tgz", + "integrity": "sha512-qkSfdotdgZuMNCi4ddMLT+Pr18m021SkzImgplpihZOSx0utWdO39TgHivMfurlgp0fp4eQ2U4YEDPfP+Iq4bw==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/analytics": "0.9.5", + "@firebase/analytics-compat": "0.2.5", + "@firebase/app": "0.9.8", + "@firebase/app-check": "0.6.5", + "@firebase/app-check-compat": "0.3.5", + "@firebase/app-compat": "0.2.8", + "@firebase/app-types": "0.9.0", + "@firebase/auth": "0.23.0", + "@firebase/auth-compat": "0.4.0", + "@firebase/database": "0.14.4", + "@firebase/database-compat": "0.3.4", + "@firebase/firestore": "3.10.1", + "@firebase/firestore-compat": "0.3.7", + "@firebase/functions": "0.9.4", + "@firebase/functions-compat": "0.3.4", + "@firebase/installations": "0.6.4", + "@firebase/installations-compat": "0.2.4", + "@firebase/messaging": "0.12.4", + "@firebase/messaging-compat": "0.2.4", + "@firebase/performance": "0.6.4", + "@firebase/performance-compat": "0.2.4", + "@firebase/remote-config": "0.4.4", + "@firebase/remote-config-compat": "0.2.4", + "@firebase/storage": "0.11.2", + "@firebase/storage-compat": "0.3.2", + "@firebase/util": "1.9.3" + } + }, "node_modules/flat-cache": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", @@ -4584,6 +5377,15 @@ "node": ">=6.9.0" } }, + "node_modules/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==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, "node_modules/get-intrinsic": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", @@ -4839,6 +5641,18 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "license": "MIT" }, + "node_modules/http-parser-js": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", + "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==", + "license": "MIT" + }, + "node_modules/idb": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/idb/-/idb-7.0.1.tgz", + "integrity": "sha512-UUxlE7vGWK5RfB/fDwEGgRf84DY/ieqNha6msMV99UsEMQhJ1RwbCd8AYBj3QMgnE3VZnfQvm4oKVCJTYlqIgg==", + "license": "ISC" + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -5107,6 +5921,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/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==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/is-generator-function": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", @@ -5548,6 +6371,12 @@ "dev": true, "license": "MIT" }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "license": "MIT" + }, "node_modules/lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", @@ -5561,6 +6390,12 @@ "dev": true, "license": "MIT" }, + "node_modules/long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==", + "license": "Apache-2.0" + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -5750,6 +6585,26 @@ "dev": true, "license": "MIT" }, + "node_modules/node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/node-releases": { "version": "2.0.18", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", @@ -6182,6 +7037,32 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "license": "MIT" }, + "node_modules/protobufjs": { + "version": "6.11.4", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.4.tgz", + "integrity": "sha512-5kQWPaJHi1WoCpjTGszzQ32PG2F4+wRY6BmAT4Vfw56Q2FZ4YZzK20xUYQH4YkfehY1e6QSICrJquM6xXZNcrw==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/long": "^4.0.1", + "@types/node": ">=13.7.0", + "long": "^4.0.0" + }, + "bin": { + "pbjs": "bin/pbjs", + "pbts": "bin/pbts" + } + }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -6501,6 +7382,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/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==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/resize-observer-polyfill": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", @@ -6625,6 +7515,26 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/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==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/safe-regex-test": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", @@ -6806,6 +7716,32 @@ "integrity": "sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A==", "license": "MIT" }, + "node_modules/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==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/string.prototype.matchall": { "version": "4.0.11", "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.11.tgz", @@ -7035,6 +7971,12 @@ "node": ">=8.0" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, "node_modules/ts-api-utils": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.1.tgz", @@ -7077,7 +8019,6 @@ "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, "license": "0BSD" }, "node_modules/turbo-stream": { @@ -7232,7 +8173,6 @@ "version": "6.19.8", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", - "dev": true, "license": "MIT" }, "node_modules/universalify": { @@ -7818,6 +8758,12 @@ "@esbuild/win32-x64": "0.21.5" } }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, "node_modules/webpack-virtual-modules": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", @@ -7825,6 +8771,39 @@ "dev": true, "license": "MIT" }, + "node_modules/websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "license": "Apache-2.0", + "dependencies": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -7935,6 +8914,35 @@ "node": ">=0.10.0" } }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -7964,6 +8972,15 @@ } } }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", @@ -7980,6 +8997,33 @@ "node": ">= 6" } }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "license": "MIT", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index cc4f6ed..9a8afa2 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "@mui/x-date-pickers": "^7.22.3", "axios": "^1.7.8", "dayjs": "^1.11.13", + "firebase": "^9.20.0", "react": "^18.3.1", "react-dom": "^18.3.1", "react-hot-toast": "^2.4.1", diff --git a/public/_redirects b/public/_redirects index f824337..08664fd 100644 --- a/public/_redirects +++ b/public/_redirects @@ -1 +1,3 @@ -/* /index.html 200 \ No newline at end of file +/* /index.html 200 + +https://dangdangsalon.netlify.app/* https://client.dangdang-salon.com/:s 301! diff --git a/public/firebase-messaging-sw.js b/public/firebase-messaging-sw.js new file mode 100644 index 0000000..aa96c1e --- /dev/null +++ b/public/firebase-messaging-sw.js @@ -0,0 +1,32 @@ +self.importScripts( + 'https://www.gstatic.com/firebasejs/9.20.0/firebase-app-compat.js' +); +self.importScripts( + 'https://www.gstatic.com/firebasejs/9.20.0/firebase-messaging-compat.js' +); + +const firebaseConfig = { + apiKey: 'AIzaSyDV1rn-AOUbRKnUrlZTWxs7DRmpLd7ZfY0', + authDomain: 'dangdangsalon-50432.firebaseapp.com', + projectId: 'dangdangsalon-50432', + storageBucket: 'dangdangsalon-50432.firebasestorage.app', + messagingSenderId: '441665534881', + appId: '1:441665534881:web:442db19619f35ba4f6a9e0', + measurementId: 'G-23L8ZGFYT0', +}; + +firebase.initializeApp(firebaseConfig); + +const messaging = firebase.messaging(); + +messaging.onBackgroundMessage((payload) => { + console.log('Background message received:', payload); + + const notificationTitle = payload.notification.title || 'Default Title'; + const notificationOptions = { + body: payload.notification.body || 'Default Body', + icon: payload.notification.icon || '/default-icon.png', + }; + + self.registration.showNotification(notificationTitle, notificationOptions); +}); diff --git a/src/api/apiClient.js b/src/api/apiClient.js index e4cfcbe..e8e12ca 100644 --- a/src/api/apiClient.js +++ b/src/api/apiClient.js @@ -1,6 +1,11 @@ import axios from 'axios'; +import { handleError } from './handleError'; export const apiClient = axios.create({ baseURL: import.meta.env.VITE_BASE_URL, withCredentials: true, }); + +apiClient.interceptors.response.use((response) => { + return response; +}, handleError); diff --git a/src/api/auth.js b/src/api/auth.js index e0ce59c..6950d79 100644 --- a/src/api/auth.js +++ b/src/api/auth.js @@ -15,11 +15,44 @@ export const join = async (role, district_id) => { } }; +export const tokenRefresh = async () => { + try { + const { data } = await apiClient.post(AuthController.refresh); + if (data.response === '액세스 토큰 갱신에 성공했습니다.') return true; + else return false; + } catch (e) { + console.error(e); + return false; + } +}; + export const loginCheck = async () => { try { const { data } = await apiClient.get(AuthController.checkLogin); if (data.response === '로그인이 되어있지 않습니다.') return false; - return !!data.response?.login; + return data.response; + } catch (e) { + console.log(e); + return false; + } +}; + +export const logout = async () => { + try { + const { data } = await apiClient.post(AuthController.logout); + if (data.response === '로그아웃에 성공했습니다.') return true; + else return false; + } catch (e) { + console.log(e); + return false; + } +}; + +export const deleteAccount = async () => { + try { + const { data } = await apiClient.delete(AuthController.deleteAccount); + if (data.response === '회원탈퇴에 성공했습니다.') return true; + else return false; } catch (e) { console.log(e); return false; diff --git a/src/api/contest.js b/src/api/contest.js new file mode 100644 index 0000000..42cbd10 --- /dev/null +++ b/src/api/contest.js @@ -0,0 +1,12 @@ +import { apiClient } from './apiClient'; +import { ContestController } from './requestUrls'; + +export const getContestRanking = async () => { + try { + const { data } = await apiClient.get(ContestController.rank); + return data.response || false; + } catch (e) { + console.log(e); + return false; + } +}; diff --git a/src/api/contestApi.js b/src/api/contestApi.js new file mode 100644 index 0000000..d11ef92 --- /dev/null +++ b/src/api/contestApi.js @@ -0,0 +1,87 @@ +import { apiClient } from './apiClient'; + +export const fetchCurrentContest = async () => { + try { + const { data } = await apiClient.get('/api/contests'); + return data.response; + } catch (error) { + console.error(error); + return null; + } +}; + +export const fetchContestDetails = async (contestId) => { + try { + const { data } = await apiClient.get(`/api/contests/${contestId}`); + return data.response; + } catch (error) { + console.error(error); + throw error; + } +}; + +export const fetchContestPayments = async (startDate, endDate) => { + try { + const { data } = await apiClient.post('/api/contests/payment', { + startDate, + endDate, + }); + return data.response; + } catch (error) { + console.error(error); + return []; + } +}; + +export const postContestEntry = async (data) => { + try { + const res = await apiClient.post('/api/posts', data); + return res.response; + } catch (error) { + console.error(error); + throw error; + } +}; + +export const checkContestParticipation = async (contestId) => { + try { + const { data } = await apiClient.get(`/api/contests/${contestId}/check`); + return data.response; + } catch (error) { + console.error(error); + throw error; + } +}; + +export const getContestPosts = async (contestId, page, size) => { + try { + const { data } = await apiClient.get(`/api/contests/${contestId}/posts`, { + params: { page, size }, + }); + return data.response; + } catch (error) { + console.error(error); + throw error; + } +}; + +export const deletePost = async (postId) => { + try { + const { data } = await apiClient.delete(`/api/posts/${postId}`); + return data.response; + } catch (error) { + console.error(error); + throw error; + } +}; + +export const likePost = async (postId) => { + const { data } = await apiClient.post(`/api/posts/${postId}/like`); + return data.response; +}; + +export const unlikePost = async (postId) => { + const { data } = await apiClient.delete(`/api/posts/${postId}/like`); + return data.response; +}; + diff --git a/src/api/dogProfile.js b/src/api/dogProfile.js new file mode 100644 index 0000000..2ab7817 --- /dev/null +++ b/src/api/dogProfile.js @@ -0,0 +1,76 @@ +import { apiClient } from './apiClient'; +import { ProfileController } from './requestUrls'; + +export const dogProfile = async (id) => { + try { + const url = `${ProfileController.dogProfile}/${id}`; + const { data } = await apiClient.get(url); + console.log(data); + return data.response; + } catch (e) { + console.log(e); + return false; + } +}; + +export const postDogProfile = async (petInfo) => { + try { + const { data } = await apiClient.post(ProfileController.dogProfile, { + name: petInfo.name, + profileImage: petInfo.profileImage, + species: petInfo.species, + ageYear: petInfo.ageYear, + ageMonth: petInfo.ageMonth, + gender: petInfo.gender, + neutering: petInfo.neutering, + weight: petInfo.weight, + featureIds: petInfo.featureIds, + additionalFeature: petInfo.additionalFeature, + }); + if (data.response === '반려견 프로필 등록이 완료되었습니다.') return true; + else return false; + } catch (e) { + console.log(e); + return false; + } +}; + +export const updateDogProfile = async ( + newData, + id, + featureIds, + additionalFeature +) => { + try { + const url = `${ProfileController.dogProfile}/${id}`; + const { data } = await apiClient.put(url, { + name: newData.name, + profileImage: newData.profileImage, + species: newData.species, + ageYear: newData.ageYear, + ageMonth: newData.ageMonth, + gender: newData.gender, + neutering: newData.neutering, + weight: newData.weight, + featureIds: featureIds, + additionalFeature: additionalFeature, + }); + if (data.response === '반려견 프로필 수정이 완료되었습니다.') return true; + else return false; + } catch (e) { + console.log(e); + return false; + } +}; + +export const deleteDogProfile = async (id) => { + try { + const url = `${ProfileController.dogProfile}/${id}`; + const { data } = await apiClient.delete(url); + if (data.response === '반려견 프로필 삭제가 완료되었습니다.') return true; + else return false; + } catch (e) { + console.log(e); + return false; + } +}; diff --git a/src/api/groomerProfile.js b/src/api/groomerProfile.js new file mode 100644 index 0000000..a6fb02b --- /dev/null +++ b/src/api/groomerProfile.js @@ -0,0 +1,104 @@ +import { apiClient } from './apiClient'; +import { ProfileController } from './requestUrls'; + +export const groomerProfile = async () => { + try { + const { data } = await apiClient.get(ProfileController.groomerProfile); + return data.response.groomerProfile; + } catch (e) { + console.log(e); + return false; + } +}; + +export const postGroomerProfile = async (groomerInfo) => { + try { + const { data } = await apiClient.post(ProfileController.groomerProfile, { + name: groomerInfo.name, + phone: groomerInfo.phone, + contactHours: groomerInfo.contactHours, + serviceType: groomerInfo.serviceType, + servicesOfferedId: groomerInfo.servicesOfferedId, + servicesDistrictIds: groomerInfo.servicesDistrictIds, + }); + if (data.response === '미용사 프로필 등록이 완료되었습니다.') return true; + else return false; + } catch (e) { + console.log(e); + return false; + } +}; + +export const postAddGroomerProfile = async (businessInfo) => { + try { + const { data } = await apiClient.post( + ProfileController.detailGroomerProfile, + { + imageKey: businessInfo.imageKey, + businessNumber: businessInfo.businessNumber, + address: businessInfo.address, + experience: businessInfo.experience, + certifications: businessInfo.certifications, + description: businessInfo.description, + startMessage: businessInfo.startMessage, + faq: businessInfo.faq, + } + ); + if (data.response === '미용사 프로필 상세 정보 등록이 완료되었습니다.') + return true; + else return false; + } catch (e) { + console.log(e); + return false; + } +}; + +export const groomerPublicProfile = async (id) => { + try { + const url = `${ProfileController.groomerProfile}/${id}`; + const { data } = await apiClient.get(url); + return data.response; + } catch (e) { + console.log(e); + return false; + } +}; + +export const updateGroomerProfile = async (newData) => { + try { + const url = `${ProfileController.groomerProfile}/${newData.profileId}`; + const { data } = await apiClient.put(url, { + imageKey: newData.imageKey, + name: newData.name, + phone: newData.phone, + servicesDistrictIds: newData.servicesDistrictIds, + contactHours: newData.contactHours, + servicesOfferedId: newData.servicesOfferedId, + serviceType: newData.serviceType, + businessNumber: newData.businessNumber, + address: newData.address, + experience: newData.experience, + certifications: newData.certifications, + description: newData.description, + startMessage: newData.startMessage, + faq: newData.faq, + }); + if (data.response === '미용사 프로필 수정이 완료되었습니다.') return true; + else return false; + } catch (e) { + console.log(e); + return false; + } +}; + +export const deleteGroomerProfile = async (id) => { + try { + const url = `${ProfileController.groomerProfile}/${id}`; + const { data } = await apiClient.delete(url); + if (data.response === '미용사 프로필 삭제가 완료되었습니다.') return true; + else return false; + } catch (e) { + console.log(e); + return false; + } +}; diff --git a/src/api/handleError.js b/src/api/handleError.js new file mode 100644 index 0000000..3da1a92 --- /dev/null +++ b/src/api/handleError.js @@ -0,0 +1,29 @@ +import { apiClient } from './apiClient'; +import { tokenRefresh } from './auth'; +import { AuthController } from './requestUrls'; + +export const handleError = async (error) => { + const originalRequest = error.config; + + if (originalRequest.url === AuthController.refresh) { + window.location.href = '/'; + return Promise.reject(error); + } + + if ( + error.response && + (error.response.status === 401 || error.response.status === 403) && + !originalRequest.__isRetryRequest + ) { + originalRequest.__isRetryRequest = true; + try { + const res = await tokenRefresh(); + if (res) return apiClient(originalRequest); + else return Promise.reject(error); + } catch (e) { + window.location.href = '/'; + return Promise.reject(e); + } + } + return Promise.reject(error); +}; diff --git a/src/api/home.js b/src/api/home.js new file mode 100644 index 0000000..1c4e421 --- /dev/null +++ b/src/api/home.js @@ -0,0 +1,22 @@ +import { apiClient } from './apiClient'; +import { HomeController } from './requestUrls'; + +export const getGroomerProfileMainPage = async () => { + try { + const { data } = await apiClient.get(HomeController.homegroomerProfile); + return data.response || false; + } catch (e) { + console.log(e); + return false; + } +}; + +export const getContestWinner = async () => { + try { + const { data } = await apiClient.get(HomeController.winnerProfile); + return data.response || false; + } catch (e) { + console.log(e); + return false; + } +}; diff --git a/src/api/image.js b/src/api/image.js new file mode 100644 index 0000000..06f4d2b --- /dev/null +++ b/src/api/image.js @@ -0,0 +1,22 @@ +import { apiClient } from './apiClient'; +import { ImageController } from './requestUrls'; + +export const uploadImage = async (file) => { + try { + const formData = new FormData(); + formData.append('file', file); + + const { data } = await apiClient.post( + ImageController.uploadImage, + formData, + { + headers: { 'Content-Type': 'multipart/form-data' }, + } + ); + + return data.response; + } catch (e) { + console.log(e); + return false; + } +}; diff --git a/src/api/notification.js b/src/api/notification.js new file mode 100644 index 0000000..62d25ff --- /dev/null +++ b/src/api/notification.js @@ -0,0 +1,76 @@ +import { apiClient } from './apiClient'; +import { NotificationController } from './requestUrls'; + +export const postFcmToken = async (token) => { + try { + const { data } = await apiClient.post(NotificationController.fcmToken, { + fcmToken: token, + }); + if (data.response === 'FCM 토큰이 성공적으로 등록되었습니다.') return true; + else return false; + } catch (e) { + console.log(e); + return false; + } +}; + +export const getNotification = async () => { + try { + const { data } = await apiClient.get( + NotificationController.getNotification + ); + + console.log(data.response); + return data.response; + } catch (e) { + console.log(e); + return false; + } +}; + +export const updateSetting = async (enabled) => { + try { + const { data } = await apiClient.post( + `${NotificationController.updateSetting}/${enabled}` + ); + if (data.response === '알림 설정이 업데이트 되었습니다.') return true; + else return false; + } catch (e) { + console.log(e); + return false; + } +}; + +export const markAsRead = async (id) => { + try { + const { data } = await apiClient.post( + `${NotificationController.markRead}${id}` + ); + if (data.response === '알림이 성공적으로 읽음 처리되었습니다.') return true; + else return false; + } catch (e) { + console.log(e); + return false; + } +}; + +export const unreadCount = async () => { + try { + const { data } = await apiClient.get(NotificationController.unreadCount); + return data.response; + } catch (e) { + console.log(e); + return false; + } +}; + +export const deleteAll = async () => { + try { + const { data } = await apiClient.post(NotificationController.markAllRead); + if (data.response === '모든 알림이 읽음 처리되었습니다.') return true; + else return false; + } catch (e) { + console.log(e); + return false; + } +}; diff --git a/src/api/profile.js b/src/api/profile.js deleted file mode 100644 index ecde865..0000000 --- a/src/api/profile.js +++ /dev/null @@ -1,66 +0,0 @@ -import { apiClient } from './apiClient'; -import { MyController } from './requestUrls'; - -export const postDogProfile = async (petInfo) => { - try { - const { data } = await apiClient.post(MyController.dogProfile, { - name: petInfo.name, - profileImage: petInfo.profileImage, - species: petInfo.species, - ageYear: petInfo.ageYear, - ageMonth: petInfo.ageMonth, - gender: petInfo.gender, - neutering: petInfo.neutering, - weight: petInfo.weight, - featureIds: petInfo.featureIds, - additionalFeature: petInfo.additionalFeature, - }); - if (data.response === '반려견 프로필 등록이 완료되었습니다.') return true; - else return false; - } catch (e) { - console.log(e); - return false; - } -}; - -export const postGroomerProfile = async (groomerInfo) => { - try { - const { data } = await apiClient.post(MyController.groomerProfile, { - name: groomerInfo.name, - phone: groomerInfo.phone, - contactHours: groomerInfo.contactHours, - serviceType: groomerInfo.serviceType, - servicesOfferedId: groomerInfo.servicesOfferedId, - servicesDistrictIds: groomerInfo.servicesDistrictIds, - }); - if (data.response === '미용사 프로필 등록이 완료되었습니다.') return true; - else return false; - } catch (e) { - console.log(e); - return false; - } -}; - -export const postAddGroomerProfile = async (businessInfo) => { - try { - const { data } = await apiClient.post( - MyController.groomerProfile + '/detail', - { - imageKey: businessInfo.imageKey, - businessNumber: businessInfo.businessNumber, - address: businessInfo.address, - experience: businessInfo.experience, - certifications: businessInfo.certifications, - description: businessInfo.description, - startMessage: businessInfo.startMessage, - faq: businessInfo.faq, - } - ); - if (data.response === '미용사 프로필 상세 정보 등록이 완료되었습니다.') - return true; - else return false; - } catch (e) { - console.log(e); - return false; - } -}; diff --git a/src/api/requestUrls.js b/src/api/requestUrls.js index 4233bad..7bc3ecc 100644 --- a/src/api/requestUrls.js +++ b/src/api/requestUrls.js @@ -2,9 +2,41 @@ export const AuthController = { join: '/api/auth/join', refresh: '/api/auth/refresh', checkLogin: '/api/auth/check/login', + logout: '/api/auth/logout', + deleteAccount: '/api/auth/delete', }; -export const MyController = { - dogProfile: '/api/dogprofile', +export const ProfileController = { + socialProfile: '/api/common', + userProfile: '/api/userprofile', groomerProfile: '/api/groomerprofile', + detailGroomerProfile: '/api/groomerprofile/detail', + dogProfile: '/api/dogprofile', +}; + +export const ImageController = { + uploadImage: '/api/images', +}; + +export const ContestController = { + rank: '/api/contests/winner/rank', +}; + +export const HomeController = { + homegroomerProfile: '/api/groomerprofile/main', + winnerProfile: '/api/contests/winner/last', +}; +export const ReviewController = { + review: '/api/review', +}; + +export const NotificationController = { + fcmToken: '/api/notification/fcm-token', + getNotification: '/api/notification/list', + markRead: '/api/notification/read?uuid=', + unreadCount: '/api/notification/unread-count', + markAllRead: '/api/notification/read-all', + updateSetting: '/api/notification/update', + subscribe: '/api/notification/subscribe', + unsubscribe: '/api/notification/unsubscribe', }; diff --git a/src/api/review.js b/src/api/review.js new file mode 100644 index 0000000..b62804c --- /dev/null +++ b/src/api/review.js @@ -0,0 +1,67 @@ +import { apiClient } from './apiClient'; +import { ReviewController } from './requestUrls'; + +export const postReview = async (newData, groomerId) => { + try { + const url = `${ReviewController.review}/${groomerId}`; + const { data } = await apiClient.post(url, { + text: newData.text, + starScore: newData.starScore, + imageKey: newData.imageKey, + }); + if (data.response === '리뷰 등록이 완료되었습니다.') return true; + else return false; + } catch (e) { + console.log(e); + return false; + } +}; + +export const myReviews = async () => { + try { + const { data } = await apiClient.get(ReviewController.review); + return data.response; + } catch (e) { + console.log(e); + return false; + } +}; + +export const receivedReviews = async (groomerId) => { + try { + const url = `${ReviewController.review}/${groomerId}`; + const { data } = await apiClient.get(url); + console.log(data); + return data.response; + } catch (e) { + console.log(e); + return false; + } +}; + +export const deleteReview = async (reviewId) => { + try { + const url = `${ReviewController.review}/${reviewId}`; + const { data } = await apiClient.delete(url); + if (data.response === '리뷰 삭제가 완료되었습니다.') return true; + else return false; + } catch (e) { + console.log(e); + return false; + } +}; + +export const updateReview = async (reviewId, newData) => { + try { + const url = `${ReviewController.review}/${reviewId}`; + const { data } = await apiClient.put(url, { + text: newData.text, + reviewImages: newData.reviewImages, + }); + if (data.response === '리뷰 수정이 완료되었습니다.') return true; + else return false; + } catch (e) { + console.log(e); + return false; + } +}; diff --git a/src/api/socialProfile.js b/src/api/socialProfile.js new file mode 100644 index 0000000..434e05f --- /dev/null +++ b/src/api/socialProfile.js @@ -0,0 +1,25 @@ +import { apiClient } from './apiClient'; +import { ProfileController } from './requestUrls'; + +export const socialProfile = async () => { + try { + const { data } = await apiClient.get(ProfileController.socialProfile); + return data.response; + } catch (e) { + console.log(e); + return false; + } +}; + +export const updateSocialProfile = async (district_id) => { + try { + const { data } = await apiClient.put(ProfileController.socialProfile, { + districtId: district_id, + }); + if (data.response === '유저 정보가 변경되었습니다.') return true; + else return false; + } catch (e) { + console.log(e); + return false; + } +}; diff --git a/src/api/userProfile.js b/src/api/userProfile.js new file mode 100644 index 0000000..8130b88 --- /dev/null +++ b/src/api/userProfile.js @@ -0,0 +1,12 @@ +import { apiClient } from './apiClient'; +import { ProfileController } from './requestUrls'; + +export const userProfile = async () => { + try { + const { data } = await apiClient.get(ProfileController.userProfile); + return data.response; + } catch (e) { + console.log(e); + return false; + } +}; diff --git a/src/components/Chatting/atoms/MyRequestItem.jsx b/src/components/Chatting/atoms/MyRequestItem.jsx index 2dc5722..e59595c 100644 --- a/src/components/Chatting/atoms/MyRequestItem.jsx +++ b/src/components/Chatting/atoms/MyRequestItem.jsx @@ -38,9 +38,9 @@ const MyRequestItem = ({ deadline }) => { ) : ( )} diff --git a/src/components/Chatting/modules/ChatRoomHeader.jsx b/src/components/Chatting/modules/ChatRoomHeader.jsx index 339ca7b..2169a95 100644 --- a/src/components/Chatting/modules/ChatRoomHeader.jsx +++ b/src/components/Chatting/modules/ChatRoomHeader.jsx @@ -48,9 +48,9 @@ const ChatRoomHeader = ({ userName }) => { diff --git a/src/components/Chatting/modules/ListItem.jsx b/src/components/Chatting/modules/ListItem.jsx index 221761f..028c88b 100644 --- a/src/components/Chatting/modules/ListItem.jsx +++ b/src/components/Chatting/modules/ListItem.jsx @@ -82,9 +82,9 @@ const ListItem = ({ content }) => { diff --git a/src/components/Common/Header/Header.jsx b/src/components/Common/Header/Header.jsx index 6327f0f..33af33e 100644 --- a/src/components/Common/Header/Header.jsx +++ b/src/components/Common/Header/Header.jsx @@ -1,36 +1,43 @@ -import PropTypes from 'prop-types'; import './Header.css'; import NotificationsNoneRoundedIcon from '@mui/icons-material/NotificationsNoneRounded'; import { IconButton, Badge } from '@mui/material'; import paths from '@/routes/paths'; +import { unreadCount } from '@/api/notification'; +import { useEffect, useState } from 'react'; -export const Header = ({ invisible }) => ( -
- - header-logo - +export const Header = () => { + const [unreadNotification, setUnreadNotification] = useState(0); -
- - - - - -
-
-); + useEffect(() => { + const getUnreadCount = async () => { + const res = await unreadCount(); + setUnreadNotification(res); + }; + getUnreadCount(); + }, []); -Header.propTypes = { - invisible: PropTypes.bool.isRequired, + return ( +
+ + header-logo + + +
+ + + + + +
+
+ ); }; diff --git a/src/components/Common/InputText/InputText.jsx b/src/components/Common/InputText/InputText.jsx index 5104472..2577dc0 100644 --- a/src/components/Common/InputText/InputText.jsx +++ b/src/components/Common/InputText/InputText.jsx @@ -16,7 +16,7 @@ const InputText = ({ value, onChange, placeholder, disabled }) => { InputText.propTypes = { value: PropTypes.string.isRequired, - onChange: PropTypes.func.isRequired, + onChange: PropTypes.func, placeholder: PropTypes.string, disabled: PropTypes.bool, }; diff --git a/src/components/Common/Modal/Modal.jsx b/src/components/Common/Modal/Modal.jsx index d8fb925..5c38c04 100644 --- a/src/components/Common/Modal/Modal.jsx +++ b/src/components/Common/Modal/Modal.jsx @@ -3,11 +3,11 @@ import PropTypes from 'prop-types'; import { Box, Button, DialogTitle, Dialog, DialogActions } from '@mui/material'; export const Modal = ({ - openLabel, + openModalButton, buttonColor, title, - leftLabel, - rightLabel, + secondaryButton, + primaryButton, action, variant, buttonSx, @@ -39,7 +39,7 @@ export const Modal = ({ sx={{ borderRadius: '10px', minWidth: '40px', ...buttonSx }} variant={variant} > - {openLabel} + {openModalButton} - {leftLabel} + {secondaryButton} @@ -97,11 +97,11 @@ export const Modal = ({ }; Modal.propTypes = { - openLabel: PropTypes.string, + openModalButton: PropTypes.string, buttonColor: PropTypes.string, title: PropTypes.string.isRequired, - leftLabel: PropTypes.string, - rightLabel: PropTypes.string, + secondaryButton: PropTypes.string, + primaryButton: PropTypes.string, action: PropTypes.func, variant: PropTypes.string, buttonSx: PropTypes.object, diff --git a/src/components/Common/Modal/Modal.stories.jsx b/src/components/Common/Modal/Modal.stories.jsx index 466c364..c27f19e 100644 --- a/src/components/Common/Modal/Modal.stories.jsx +++ b/src/components/Common/Modal/Modal.stories.jsx @@ -14,11 +14,11 @@ export default { export const MainModal = { args: { - openLabel: '삭제', + openModalButton: '삭제', buttonColor: 'delete', title: '정말 삭제하시겠습니까? 이 과정은 돌이킬 수 없습니다.', - leftLabel: '취소', - rightLabel: '삭제', + secondaryButton: '취소', + primaryButton: '삭제', action: () => alert('삭제 완료'), }, }; diff --git a/src/components/Common/NumberPicker/NumberPicker.css b/src/components/Common/NumberPicker/NumberPicker.css index 57949b1..1041767 100644 --- a/src/components/Common/NumberPicker/NumberPicker.css +++ b/src/components/Common/NumberPicker/NumberPicker.css @@ -81,7 +81,7 @@ border-radius: 4px; padding: 8px; width: 60px; - text-align: center; + text-align: left; font-size: 16px; font-weight: bold; color: #3b3b3b; diff --git a/src/components/Common/RegionModal/RegionModal.jsx b/src/components/Common/RegionModal/RegionModal.jsx index 14a4db1..51fac04 100644 --- a/src/components/Common/RegionModal/RegionModal.jsx +++ b/src/components/Common/RegionModal/RegionModal.jsx @@ -16,7 +16,7 @@ import { koreaRegions } from './KoreaRegions'; export const RegionModal = ({ setLocation, open, setOpen }) => { const [selectedCity, setSelectedCity] = useState(null); const [selectedRegion, setSelectedRegion] = useState(null); - const [regionId, setRegionId] = useState(-1); + const [regionId, setRegionId] = useState(0); const handleClose = () => { setOpen(false); diff --git a/src/components/Contest/Feed.jsx b/src/components/Contest/Feed.jsx index 3092a89..add6d0b 100644 --- a/src/components/Contest/Feed.jsx +++ b/src/components/Contest/Feed.jsx @@ -1,6 +1,7 @@ import { Box, Typography, IconButton } from '@mui/material'; import FavoriteBorderIcon from '@mui/icons-material/FavoriteBorder'; import FavoriteIcon from '@mui/icons-material/Favorite'; +import CloseIcon from '@mui/icons-material/Close'; const Feed = ({ imageUrl, @@ -8,18 +9,38 @@ const Feed = ({ nickname, explanation, isLiked, - onClick, + onClick, deleteButton, + onLikeToggle, }) => { - return ( - + return ( + + {/* 삭제 버튼 */} + {deleteButton && ( + { + e.stopPropagation(); + deleteButton(); + }} + > + + + )} {/* 상단 사용자 정보 */} { e.stopPropagation(); // 좋아요 토글 로직 + onLikeToggle(); }} > {isLiked ? ( diff --git a/src/components/Features/ImageSelector.jsx b/src/components/Features/ImageSelector.jsx index e15f346..dd0d9c1 100644 --- a/src/components/Features/ImageSelector.jsx +++ b/src/components/Features/ImageSelector.jsx @@ -4,8 +4,9 @@ import AddRoundedIcon from '@mui/icons-material/AddRounded'; import HighlightOffRoundedIcon from '@mui/icons-material/HighlightOffRounded'; import toast from 'react-hot-toast'; import SubTitle from '@components/NewRequest/atoms/SubTitle'; +import { uploadImage } from '@/api/image'; -const ImageSelector = ({ maxImages, images = [], onChange }) => { +const ImageSelector = ({ maxImages, images, onChange }) => { const fileInputRef = useRef(null); const maxImagesReached = () => @@ -17,7 +18,7 @@ const ImageSelector = ({ maxImages, images = [], onChange }) => { } }; - const handleFileChange = (event) => { + const handleFileChange = async (event) => { const files = event.target.files; if (files) { const fileArray = Array.from(files); @@ -27,10 +28,12 @@ const ImageSelector = ({ maxImages, images = [], onChange }) => { return; } - const newImages = fileArray.map((file) => ({ - file, - preview: URL.createObjectURL(file), - })); + const newImages = await Promise.all( + fileArray.map(async (file) => { + const res = await uploadImage(file); + return res; + }) + ); onChange([...images, ...newImages]); } @@ -49,11 +52,11 @@ const ImageSelector = ({ maxImages, images = [], onChange }) => { /> {images.map((image, index) => ( - <> + {`Uploaded { handleRemoveImage(index)}> - + ))} {images.length < maxImages && ( diff --git a/src/components/Features/ProfileSelector.jsx b/src/components/Features/ProfileSelector.jsx index 55faa56..bd428ee 100644 --- a/src/components/Features/ProfileSelector.jsx +++ b/src/components/Features/ProfileSelector.jsx @@ -1,55 +1,49 @@ import { useRef, useState } from 'react'; import { Box, Button, DialogTitle, Dialog, DialogActions } from '@mui/material'; +import { uploadImage } from '@/api/image'; + +const ProfileSelector = ({ defaultImage, image, onChange }) => { + const defaultImgPath = + defaultImage === 'human' + ? '/images/default-groomer-profile.png' + : '/images/default-dog-profile.png'; -const ProfileSelector = ({ defaultImage, image = null, onChange }) => { const fileInputRef = useRef(null); const [open, setOpen] = useState(false); - const [selectedImage, setSelectedImage] = useState(image); + const [selectedImage, setSelectedImage] = useState(image ? image : null); const handleOpenFileInput = () => { if (fileInputRef.current) fileInputRef.current.click(); setOpen(false); }; - const handleFileChange = (event) => { + const handleFileChange = async (event) => { const file = event.target.files[0]; if (file) { - const preview = URL.createObjectURL(file); - setSelectedImage(preview); - onChange(preview); + const res = await uploadImage(file); + setSelectedImage(res); + onChange(res); } setOpen(false); }; - const handleRemoveImage = () => { - setSelectedImage(null); - onChange(null); - }; - - const defaultImgPath = - defaultImage === 'human' - ? '/images/default-groomer-profile.png' - : '/images/default-dog-profile.png'; - return ( - - setOpen(true)} - > + + setOpen(true)}> {selectedImage @@ -83,7 +77,8 @@ const ProfileSelector = ({ defaultImage, image = null, onChange }) => { + deleteReview(review.reviewId)} + /> + + )} + + {Array(fullStars) + .fill(0) + .map((_, index) => ( + + ))} + {hasHalfStar && ( + + )} + {Array(emptyStars) + .fill(0) + .map((_, index) => ( + + ))} + + + {review.text} + + + {open && ( + + {review.reviewImages.map((image, index) => ( + {`Review handleImageClick(image)} + /> + ))} + + )} + + + + + {modalOpen && ( + + + Selected Review Image + + + + + + )} + + ); +}; + +export default ReviewAccordion; diff --git a/src/components/Features/ReviewStars.jsx b/src/components/Features/ReviewStars.jsx index 7d5ce72..5dde792 100644 --- a/src/components/Features/ReviewStars.jsx +++ b/src/components/Features/ReviewStars.jsx @@ -1,27 +1,26 @@ -import { Box, Button, Typography } from '@mui/material'; +import { Box, Typography } from '@mui/material'; const ReviewStars = (props) => { - const totalStars = 5; - const fullStars = Math.floor(props.averageReview); - const hasHalfStar = props.averageReview % 1 != 0; - const emptyStars = totalStars - fullStars - (hasHalfStar ? 1 : 0); + const TOTAL_STARS = 5; + const fullStars = Math.floor(props.starScore); + const hasHalfStar = props.starScore % 1 != 0; + const emptyStars = TOTAL_STARS - fullStars - (hasHalfStar ? 1 : 0); return ( <> - {Array(fullStars) - .fill(0) - .map((_, index) => ( + {fullStars > 0 && + Array.from({ length: fullStars }).map((_, index) => ( ))} + {hasHalfStar && } - {Array(emptyStars) - .fill(0) - .map((_, index) => ( + {emptyStars > 0 && + Array.from({ length: emptyStars }).map((_, index) => ( { /> ))} - {props.averageReview} + {props.starScore} / 5 - - - ); }; diff --git a/src/components/Features/ServiceRegionForm.jsx b/src/components/Features/ServiceRegionForm.jsx index bd64c59..2cbb94e 100644 --- a/src/components/Features/ServiceRegionForm.jsx +++ b/src/components/Features/ServiceRegionForm.jsx @@ -7,10 +7,14 @@ import { RegionModal } from '@components/Common/RegionModal/RegionModal'; const ServiceRegionForm = ({ regions, setServiceAreas }) => { const [isModalOpen, setIsModalOpen] = useState(false); - const handleSetLocation = (selectedCity, selectedDistrict) => { + const handleSetLocation = (selectedCity, selectedDistrict, selectedId) => { setServiceAreas((prev) => [ ...prev, - { city: selectedCity, district: selectedDistrict }, + { + city: selectedCity, + district: selectedDistrict, + districtId: selectedId, + }, ]); setIsModalOpen(false); }; diff --git a/src/components/Layout/NotFound.jsx b/src/components/Layout/NotFound.jsx index 6259ded..6773cff 100644 --- a/src/components/Layout/NotFound.jsx +++ b/src/components/Layout/NotFound.jsx @@ -1,29 +1,33 @@ import Button from '@components/Common/Button/Button'; +import { Header } from '@components/Common/Header/Header'; import { Box, Typography } from '@mui/material'; import { useNavigate } from 'react-router-dom'; const NotFound = () => { const navigate = useNavigate(); return ( - - dog img - 멍! 찾으시는 페이지가 사라진 모양이에요. - + + + )} - {data.description} + {data?.description} { flexDirection="column" sx={{ cursor: 'pointer', + '&:hover': { color: 'secondary.main' }, }} > - + 결제 - 11 + {detail?.estimateRequestCount} @@ -102,58 +124,74 @@ const SalonProfile = () => { flexDirection="column" sx={{ cursor: 'pointer', + '&:hover': { color: 'secondary.main' }, }} + onClick={() => + navigate(paths.salonReviews, { + state: { profileId: data.profileId }, + }) + } > - + 리뷰 - 10 + {data?.reviewCount || 0} - {data.experienceYears}년 경력 + {data?.experience} 📞전화번호: - {data.phone} + {data?.phone} 🧑연락 가능 시간: - {data.contactHours} + {data?.contactHours} 📍서비스 지역: - {data.serviceLocation} + + {detail?.servicesDistricts?.map((item, index) => ( + + {item.city} {item.district} + + ))} + 🚙서비스 형태: - {data.serviceType == 'VISIT' + {data?.serviceType == 'VISIT' ? '방문' - : data.serviceType == 'SHOP' + : data?.serviceType == 'SHOP' ? '매장' : '방문, 매장'} ✂제공 서비스: - {data.services} + + {detail?.servicesOffered?.map((item, index) => ( + + {item} + {index < detail.servicesOffered.length - 1 && ', '} + + ))} + 🪪자격증: - {data.certification.map((cert, index) => { + {detail?.certifications?.map((cert, index) => { return
  • {cert}
  • ; })}
    💼사업자 번호: - {data.businessNumber} + {data?.businessNumber} 📍가게 위치 정보: - {data.address} + {data?.address}
    FAQ - {data.faq} + {data?.faq}
    diff --git a/src/pages/SalonReviews.jsx b/src/pages/SalonReviews.jsx new file mode 100644 index 0000000..ca8812d --- /dev/null +++ b/src/pages/SalonReviews.jsx @@ -0,0 +1,48 @@ +import { myReviews, receivedReviews } from '@/api/review'; +import useUserStore from '@/store/useUserStore'; +import { DetailHeader } from '@components/Common/DetailHeader/DetailHeader'; +import ReviewAccordion from '@components/Features/ReviewAccordion'; +import EmptyContent from '@components/Layout/EmptyContent'; +import { Box, Typography } from '@mui/material'; +import { useState, useEffect } from 'react'; +import { useLocation } from 'react-router-dom'; + +const SalonReviews = () => { + const location = useLocation(); + const [id, setId] = useState(location.state?.profileId); + const [allReviews, setAllReviews] = useState([]); + const [loading, setLoading] = useState(true); + + useEffect(() => { + const getReviews = async () => { + const res = await receivedReviews(id); + setAllReviews(res); + setLoading(false); + }; + getReviews(); + }, []); + + if (loading) return LOADING; + + return ( + + + + {!allReviews.length ? ( + + ) : ( + + {allReviews?.map((review) => ( + + ))} + + )} + + ); +}; + +export default SalonReviews; diff --git a/src/pages/Survey/AddDogProfile.jsx b/src/pages/Survey/AddDogProfile.jsx new file mode 100644 index 0000000..d442f43 --- /dev/null +++ b/src/pages/Survey/AddDogProfile.jsx @@ -0,0 +1,119 @@ +import { Container, Box } from '@mui/material'; +import { useNavigate } from 'react-router-dom'; +import { SurveyHeader } from '@/components/Common/SurveyHeader/SurveyHeader'; +import Button from '@/components/Common/Button/Button'; +import Step1 from '@/components/Survey/UserSteps/Step1'; +import Step2 from '@/components/Survey/UserSteps/Step2'; +import Step3 from '@/components/Survey/UserSteps/Step3'; +import Step4 from '@/components/Survey/UserSteps/Step4'; +import Step5 from '@/components/Survey/UserSteps/Step5'; +import Step6 from '@/components/Survey/UserSteps/Step6'; +import Step7 from '@/components/Survey/UserSteps/Step7'; +import useSurveyUserStore from '@/store/useSurveyUserStore'; +import { postDogProfile } from '@/api/dogProfile'; +import paths from '@/routes/paths'; + +const AddDogProfile = () => { + const navigate = useNavigate(); + const { step, setStep, petInfo, characteristics } = useSurveyUserStore(); + + const isStepValid = () => { + switch (step) { + case 1: + return petInfo.name.trim() !== ''; + case 2: + return petInfo.ageYear > 0 || petInfo.ageMonth > 0; + case 3: + return petInfo.weight > 0; + case 4: + return petInfo.species !== '' && petInfo.species !== null; + case 5: + return ( + ['MALE', 'FEMALE'].includes(petInfo.gender) && + ['Y', 'N'].includes(petInfo.neutering) + ); + case 6: + return ( + petInfo.featureIds.length !== 0 || + characteristics.없음 === true || + petInfo.additionalFeature.trim() !== '' + ); + case 7: + return true; + default: + return false; + } + }; + + const handleSaveProfile = async () => { + const res = await postDogProfile(petInfo); + return res; + }; + + const handleSubmit = async () => { + if (!isStepValid()) return ''; + if (await handleSaveProfile()) navigate(paths.mypage); + }; + + const handleNextStep = () => { + if (!isStepValid()) return ''; + setStep(step + 1); + }; + + const handleBack = () => { + if (step > 1) setStep(step - 1); + else navigate(-1); + }; + + return ( + <> + + + + {step === 1 && } + {step === 2 && } + {step === 3 && } + {step === 4 && } + {step === 5 && } + {step === 6 && } + {step === 7 && } + + + {step === 7 ? ( +