From 90145c2449e1146e510683113ffaeedcd71c11c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=84=9C=EC=A0=9C=EA=B2=BD?= Date: Wed, 13 Aug 2025 23:47:10 +0900 Subject: [PATCH 01/11] =?UTF-8?q?feat:=20=EC=84=A4=EC=A0=95API=20=EC=97=B0?= =?UTF-8?q?=EB=8F=99=20=EB=AF=B8=EC=99=84=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 890 ++++++++++++++++++++- src/api/auth/account.ts | 15 + src/api/faq/faq.keys.ts | 5 + src/api/faq/faq.ts | 28 + src/api/notice/notice.keys.ts | 20 + src/api/notice/notice.ts | 35 +- src/api/settingAlarm/alarm.ts | 15 + src/components/common/EditableInputBox.tsx | 1 + src/components/common/PasswordEdit.tsx | 124 +-- src/components/faq/FAQItem.tsx | 34 +- src/components/settingTab/AlarmSetting.tsx | 54 +- src/components/settingTab/InfoSetting.tsx | 70 +- src/hooks/alarm/useDeviceToken.ts | 64 +- src/hooks/auth/useAccount.ts | 28 + src/hooks/auth/useEmail.ts | 15 + src/hooks/faq/useFaq.ts | 34 + src/hooks/notice/useNotice.ts | 58 ++ src/hooks/settingAlarm/useAlarms.ts | 25 + src/pages/home/HomePage.tsx | 3 +- src/pages/notice/Notice.tsx | 123 ++- src/pages/notice/NoticeDetail.tsx | 65 +- src/pages/question/Question.tsx | 127 +-- src/types/auth/account.ts | 26 + src/types/faq/faq.ts | 17 + src/types/notice/notice.ts | 23 + src/types/settingAlarm/alarm.ts | 15 + src/utils/date.ts | 11 + yarn.lock | 200 +++-- 28 files changed, 1688 insertions(+), 437 deletions(-) create mode 100644 src/api/auth/account.ts create mode 100644 src/api/faq/faq.keys.ts create mode 100644 src/api/faq/faq.ts create mode 100644 src/api/notice/notice.keys.ts create mode 100644 src/api/settingAlarm/alarm.ts create mode 100644 src/hooks/auth/useAccount.ts create mode 100644 src/hooks/auth/useEmail.ts create mode 100644 src/hooks/faq/useFaq.ts create mode 100644 src/hooks/notice/useNotice.ts create mode 100644 src/hooks/settingAlarm/useAlarms.ts create mode 100644 src/types/auth/account.ts create mode 100644 src/types/faq/faq.ts create mode 100644 src/types/settingAlarm/alarm.ts create mode 100644 src/utils/date.ts diff --git a/package-lock.json b/package-lock.json index ee1a1a2..cfedfc3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "chart.js": "^4.5.0", "chartjs-plugin-datalabels": "^2.2.0", "clsx": "^2.1.1", + "firebase": "^12.0.0", "lodash": "^4.17.21", "lodash.throttle": "^4.1.1", "path": "^0.12.7", @@ -25,7 +26,9 @@ "react-chartjs-2": "^5.3.0", "react-dom": "^19.1.0", "react-hook-form": "^7.60.0", + "react-intersection-observer": "^9.16.0", "react-router-dom": "^7.6.0", + "react-spinners": "^0.17.0", "sonner": "^2.0.3", "tailwindcss": "^4.1.7", "wordcloud": "^1.2.3", @@ -909,6 +912,645 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@firebase/ai": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@firebase/ai/-/ai-2.1.0.tgz", + "integrity": "sha512-4HvFr4YIzNFh0MowJLahOjJDezYSTjQar0XYVu/sAycoxQ+kBsfXuTPRLVXCYfMR5oNwQgYe4Q2gAOYKKqsOyA==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/app-check-interop-types": "0.3.3", + "@firebase/component": "0.7.0", + "@firebase/logger": "0.5.0", + "@firebase/util": "1.13.0", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@firebase/app": "0.x", + "@firebase/app-types": "0.x" + } + }, + "node_modules/@firebase/analytics": { + "version": "0.10.18", + "resolved": "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.10.18.tgz", + "integrity": "sha512-iN7IgLvM06iFk8BeFoWqvVpRFW3Z70f+Qe2PfCJ7vPIgLPjHXDE774DhCT5Y2/ZU/ZbXPDPD60x/XPWEoZLNdg==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.0", + "@firebase/installations": "0.6.19", + "@firebase/logger": "0.5.0", + "@firebase/util": "1.13.0", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/analytics-compat": { + "version": "0.2.24", + "resolved": "https://registry.npmjs.org/@firebase/analytics-compat/-/analytics-compat-0.2.24.tgz", + "integrity": "sha512-jE+kJnPG86XSqGQGhXXYt1tpTbCTED8OQJ/PQ90SEw14CuxRxx/H+lFbWA1rlFtFSsTCptAJtgyRBwr/f00vsw==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/analytics": "0.10.18", + "@firebase/analytics-types": "0.8.3", + "@firebase/component": "0.7.0", + "@firebase/util": "1.13.0", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/analytics-types": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/@firebase/analytics-types/-/analytics-types-0.8.3.tgz", + "integrity": "sha512-VrIp/d8iq2g501qO46uGz3hjbDb8xzYMrbu8Tp0ovzIzrvJZ2fvmj649gTjge/b7cCCcjT0H37g1gVtlNhnkbg==", + "license": "Apache-2.0" + }, + "node_modules/@firebase/app": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.14.1.tgz", + "integrity": "sha512-jxTrDbxnGoX7cGz7aP9E7v9iKvBbQfZ8Gz4TH3SfrrkcyIojJM3+hJnlbGnGxHrABts844AxRcg00arMZEyA6Q==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.0", + "@firebase/logger": "0.5.0", + "@firebase/util": "1.13.0", + "idb": "7.1.1", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@firebase/app-check": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@firebase/app-check/-/app-check-0.11.0.tgz", + "integrity": "sha512-XAvALQayUMBJo58U/rxW02IhsesaxxfWVmVkauZvGEz3vOAjMEQnzFlyblqkc2iAaO82uJ2ZVyZv9XzPfxjJ6w==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.0", + "@firebase/logger": "0.5.0", + "@firebase/util": "1.13.0", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/app-check-compat": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@firebase/app-check-compat/-/app-check-compat-0.4.0.tgz", + "integrity": "sha512-UfK2Q8RJNjYM/8MFORltZRG9lJj11k0nW84rrffiKvcJxLf1jf6IEjCIkCamykHE73C6BwqhVfhIBs69GXQV0g==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/app-check": "0.11.0", + "@firebase/app-check-types": "0.5.3", + "@firebase/component": "0.7.0", + "@firebase/logger": "0.5.0", + "@firebase/util": "1.13.0", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/app-check-interop-types": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@firebase/app-check-interop-types/-/app-check-interop-types-0.3.3.tgz", + "integrity": "sha512-gAlxfPLT2j8bTI/qfe3ahl2I2YcBQ8cFIBdhAQA4I2f3TndcO+22YizyGYuttLHPQEpWkhmpFW60VCFEPg4g5A==", + "license": "Apache-2.0" + }, + "node_modules/@firebase/app-check-types": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/@firebase/app-check-types/-/app-check-types-0.5.3.tgz", + "integrity": "sha512-hyl5rKSj0QmwPdsAxrI5x1otDlByQ7bvNvVt8G/XPO2CSwE++rmSVf3VEhaeOR4J8ZFaF0Z0NDSmLejPweZ3ng==", + "license": "Apache-2.0" + }, + "node_modules/@firebase/app-compat": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.5.1.tgz", + "integrity": "sha512-BEy1L6Ufd85ZSP79HDIv0//T9p7d5Bepwy+2mKYkgdXBGKTbFm2e2KxyF1nq4zSQ6RRBxWi0IY0zFVmoBTZlUA==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/app": "0.14.1", + "@firebase/component": "0.7.0", + "@firebase/logger": "0.5.0", + "@firebase/util": "1.13.0", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@firebase/app-types": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.3.tgz", + "integrity": "sha512-kRVpIl4vVGJ4baogMDINbyrIOtOxqhkZQg4jTq3l8Lw6WSk0xfpEYzezFu+Kl4ve4fbPl79dvwRtaFqAC/ucCw==", + "license": "Apache-2.0" + }, + "node_modules/@firebase/auth": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-1.11.0.tgz", + "integrity": "sha512-5j7+ua93X+IRcJ1oMDTClTo85l7Xe40WSkoJ+shzPrX7OISlVWLdE1mKC57PSD+/LfAbdhJmvKixINBw2ESK6w==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.0", + "@firebase/logger": "0.5.0", + "@firebase/util": "1.13.0", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@firebase/app": "0.x", + "@react-native-async-storage/async-storage": "^1.18.1" + }, + "peerDependenciesMeta": { + "@react-native-async-storage/async-storage": { + "optional": true + } + } + }, + "node_modules/@firebase/auth-compat": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@firebase/auth-compat/-/auth-compat-0.6.0.tgz", + "integrity": "sha512-J0lGSxXlG/lYVi45wbpPhcWiWUMXevY4fvLZsN1GHh+po7TZVng+figdHBVhFheaiipU8HZyc7ljw1jNojM2nw==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/auth": "1.11.0", + "@firebase/auth-types": "0.13.0", + "@firebase/component": "0.7.0", + "@firebase/util": "1.13.0", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/auth-interop-types": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.2.4.tgz", + "integrity": "sha512-JPgcXKCuO+CWqGDnigBtvo09HeBs5u/Ktc2GaFj2m01hLarbxthLNm7Fk8iOP1aqAtXV+fnnGj7U28xmk7IwVA==", + "license": "Apache-2.0" + }, + "node_modules/@firebase/auth-types": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@firebase/auth-types/-/auth-types-0.13.0.tgz", + "integrity": "sha512-S/PuIjni0AQRLF+l9ck0YpsMOdE8GO2KU6ubmBB7P+7TJUCQDa3R1dlgYm9UzGbbePMZsp0xzB93f2b/CgxMOg==", + "license": "Apache-2.0", + "peerDependencies": { + "@firebase/app-types": "0.x", + "@firebase/util": "1.x" + } + }, + "node_modules/@firebase/component": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.7.0.tgz", + "integrity": "sha512-wR9En2A+WESUHexjmRHkqtaVH94WLNKt6rmeqZhSLBybg4Wyf0Umk04SZsS6sBq4102ZsDBFwoqMqJYj2IoDSg==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/util": "1.13.0", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@firebase/data-connect": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@firebase/data-connect/-/data-connect-0.3.11.tgz", + "integrity": "sha512-G258eLzAD6im9Bsw+Qm1Z+P4x0PGNQ45yeUuuqe5M9B1rn0RJvvsQCRHXgE52Z+n9+WX1OJd/crcuunvOGc7Vw==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/auth-interop-types": "0.2.4", + "@firebase/component": "0.7.0", + "@firebase/logger": "0.5.0", + "@firebase/util": "1.13.0", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/database": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-1.1.0.tgz", + "integrity": "sha512-gM6MJFae3pTyNLoc9VcJNuaUDej0ctdjn3cVtILo3D5lpp0dmUHHLFN/pUKe7ImyeB1KAvRlEYxvIHNF04Filg==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/app-check-interop-types": "0.3.3", + "@firebase/auth-interop-types": "0.2.4", + "@firebase/component": "0.7.0", + "@firebase/logger": "0.5.0", + "@firebase/util": "1.13.0", + "faye-websocket": "0.11.4", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@firebase/database-compat": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-2.1.0.tgz", + "integrity": "sha512-8nYc43RqxScsePVd1qe1xxvWNf0OBnbwHxmXJ7MHSuuTVYFO3eLyLW3PiCKJ9fHnmIz4p4LbieXwz+qtr9PZDg==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.0", + "@firebase/database": "1.1.0", + "@firebase/database-types": "1.0.16", + "@firebase/logger": "0.5.0", + "@firebase/util": "1.13.0", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@firebase/database-types": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-1.0.16.tgz", + "integrity": "sha512-xkQLQfU5De7+SPhEGAXFBnDryUWhhlFXelEg2YeZOQMCdoe7dL64DDAd77SQsR+6uoXIZY5MB4y/inCs4GTfcw==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/app-types": "0.9.3", + "@firebase/util": "1.13.0" + } + }, + "node_modules/@firebase/firestore": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-4.9.0.tgz", + "integrity": "sha512-5zl0+/h1GvlCSLt06RMwqFsd7uqRtnNZt4sW99k2rKRd6k/ECObIWlEnvthm2cuOSnUmwZknFqtmd1qyYSLUuQ==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.0", + "@firebase/logger": "0.5.0", + "@firebase/util": "1.13.0", + "@firebase/webchannel-wrapper": "1.0.4", + "@grpc/grpc-js": "~1.9.0", + "@grpc/proto-loader": "^0.7.8", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/firestore-compat": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@firebase/firestore-compat/-/firestore-compat-0.4.0.tgz", + "integrity": "sha512-4O7v4VFeSEwAZtLjsaj33YrMHMRjplOIYC2CiYsF6o/MboOhrhe01VrTt8iY9Y5EwjRHuRz4pS6jMBT8LfQYJA==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.0", + "@firebase/firestore": "4.9.0", + "@firebase/firestore-types": "3.0.3", + "@firebase/util": "1.13.0", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/firestore-types": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@firebase/firestore-types/-/firestore-types-3.0.3.tgz", + "integrity": "sha512-hD2jGdiWRxB/eZWF89xcK9gF8wvENDJkzpVFb4aGkzfEaKxVRD1kjz1t1Wj8VZEp2LCB53Yx1zD8mrhQu87R6Q==", + "license": "Apache-2.0", + "peerDependencies": { + "@firebase/app-types": "0.x", + "@firebase/util": "1.x" + } + }, + "node_modules/@firebase/functions": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@firebase/functions/-/functions-0.13.0.tgz", + "integrity": "sha512-2/LH5xIbD8aaLOWSFHAwwAybgSzHIM0dB5oVOL0zZnxFG1LctX2bc1NIAaPk1T+Zo9aVkLKUlB5fTXTkVUQprQ==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/app-check-interop-types": "0.3.3", + "@firebase/auth-interop-types": "0.2.4", + "@firebase/component": "0.7.0", + "@firebase/messaging-interop-types": "0.2.3", + "@firebase/util": "1.13.0", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/functions-compat": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@firebase/functions-compat/-/functions-compat-0.4.0.tgz", + "integrity": "sha512-VPgtvoGFywWbQqtvgJnVWIDFSHV1WE6Hmyi5EGI+P+56EskiGkmnw6lEqc/MEUfGpPGdvmc4I9XMU81uj766/g==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.0", + "@firebase/functions": "0.13.0", + "@firebase/functions-types": "0.6.3", + "@firebase/util": "1.13.0", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/functions-types": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@firebase/functions-types/-/functions-types-0.6.3.tgz", + "integrity": "sha512-EZoDKQLUHFKNx6VLipQwrSMh01A1SaL3Wg6Hpi//x6/fJ6Ee4hrAeswK99I5Ht8roiniKHw4iO0B1Oxj5I4plg==", + "license": "Apache-2.0" + }, + "node_modules/@firebase/installations": { + "version": "0.6.19", + "resolved": "https://registry.npmjs.org/@firebase/installations/-/installations-0.6.19.tgz", + "integrity": "sha512-nGDmiwKLI1lerhwfwSHvMR9RZuIH5/8E3kgUWnVRqqL7kGVSktjLTWEMva7oh5yxQ3zXfIlIwJwMcaM5bK5j8Q==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.0", + "@firebase/util": "1.13.0", + "idb": "7.1.1", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/installations-compat": { + "version": "0.2.19", + "resolved": "https://registry.npmjs.org/@firebase/installations-compat/-/installations-compat-0.2.19.tgz", + "integrity": "sha512-khfzIY3EI5LePePo7vT19/VEIH1E3iYsHknI/6ek9T8QCozAZshWT9CjlwOzZrKvTHMeNcbpo/VSOSIWDSjWdQ==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.0", + "@firebase/installations": "0.6.19", + "@firebase/installations-types": "0.5.3", + "@firebase/util": "1.13.0", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/installations-types": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/@firebase/installations-types/-/installations-types-0.5.3.tgz", + "integrity": "sha512-2FJI7gkLqIE0iYsNQ1P751lO3hER+Umykel+TkLwHj6plzWVxqvfclPUZhcKFVQObqloEBTmpi2Ozn7EkCABAA==", + "license": "Apache-2.0", + "peerDependencies": { + "@firebase/app-types": "0.x" + } + }, + "node_modules/@firebase/logger": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.5.0.tgz", + "integrity": "sha512-cGskaAvkrnh42b3BA3doDWeBmuHFO/Mx5A83rbRDYakPjO9bJtRL3dX7javzc2Rr/JHZf4HlterTW2lUkfeN4g==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@firebase/messaging": { + "version": "0.12.23", + "resolved": "https://registry.npmjs.org/@firebase/messaging/-/messaging-0.12.23.tgz", + "integrity": "sha512-cfuzv47XxqW4HH/OcR5rM+AlQd1xL/VhuaeW/wzMW1LFrsFcTn0GND/hak1vkQc2th8UisBcrkVcQAnOnKwYxg==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.0", + "@firebase/installations": "0.6.19", + "@firebase/messaging-interop-types": "0.2.3", + "@firebase/util": "1.13.0", + "idb": "7.1.1", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/messaging-compat": { + "version": "0.2.23", + "resolved": "https://registry.npmjs.org/@firebase/messaging-compat/-/messaging-compat-0.2.23.tgz", + "integrity": "sha512-SN857v/kBUvlQ9X/UjAqBoQ2FEaL1ZozpnmL1ByTe57iXkmnVVFm9KqAsTfmf+OEwWI4kJJe9NObtN/w22lUgg==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.0", + "@firebase/messaging": "0.12.23", + "@firebase/util": "1.13.0", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/messaging-interop-types": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@firebase/messaging-interop-types/-/messaging-interop-types-0.2.3.tgz", + "integrity": "sha512-xfzFaJpzcmtDjycpDeCUj0Ge10ATFi/VHVIvEEjDNc3hodVBQADZ7BWQU7CuFpjSHE+eLuBI13z5F/9xOoGX8Q==", + "license": "Apache-2.0" + }, + "node_modules/@firebase/performance": { + "version": "0.7.9", + "resolved": "https://registry.npmjs.org/@firebase/performance/-/performance-0.7.9.tgz", + "integrity": "sha512-UzybENl1EdM2I1sjYm74xGt/0JzRnU/0VmfMAKo2LSpHJzaj77FCLZXmYQ4oOuE+Pxtt8Wy2BVJEENiZkaZAzQ==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.0", + "@firebase/installations": "0.6.19", + "@firebase/logger": "0.5.0", + "@firebase/util": "1.13.0", + "tslib": "^2.1.0", + "web-vitals": "^4.2.4" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/performance-compat": { + "version": "0.2.22", + "resolved": "https://registry.npmjs.org/@firebase/performance-compat/-/performance-compat-0.2.22.tgz", + "integrity": "sha512-xLKxaSAl/FVi10wDX/CHIYEUP13jXUjinL+UaNXT9ByIvxII5Ne5150mx6IgM8G6Q3V+sPiw9C8/kygkyHUVxg==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.0", + "@firebase/logger": "0.5.0", + "@firebase/performance": "0.7.9", + "@firebase/performance-types": "0.2.3", + "@firebase/util": "1.13.0", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/performance-types": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@firebase/performance-types/-/performance-types-0.2.3.tgz", + "integrity": "sha512-IgkyTz6QZVPAq8GSkLYJvwSLr3LS9+V6vNPQr0x4YozZJiLF5jYixj0amDtATf1X0EtYHqoPO48a9ija8GocxQ==", + "license": "Apache-2.0" + }, + "node_modules/@firebase/remote-config": { + "version": "0.6.6", + "resolved": "https://registry.npmjs.org/@firebase/remote-config/-/remote-config-0.6.6.tgz", + "integrity": "sha512-Yelp5xd8hM4NO1G1SuWrIk4h5K42mNwC98eWZ9YLVu6Z0S6hFk1mxotAdCRmH2luH8FASlYgLLq6OQLZ4nbnCA==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.0", + "@firebase/installations": "0.6.19", + "@firebase/logger": "0.5.0", + "@firebase/util": "1.13.0", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/remote-config-compat": { + "version": "0.2.19", + "resolved": "https://registry.npmjs.org/@firebase/remote-config-compat/-/remote-config-compat-0.2.19.tgz", + "integrity": "sha512-y7PZAb0l5+5oIgLJr88TNSelxuASGlXyAKj+3pUc4fDuRIdPNBoONMHaIUa9rlffBR5dErmaD2wUBJ7Z1a513Q==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.0", + "@firebase/logger": "0.5.0", + "@firebase/remote-config": "0.6.6", + "@firebase/remote-config-types": "0.4.0", + "@firebase/util": "1.13.0", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/remote-config-types": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@firebase/remote-config-types/-/remote-config-types-0.4.0.tgz", + "integrity": "sha512-7p3mRE/ldCNYt8fmWMQ/MSGRmXYlJ15Rvs9Rk17t8p0WwZDbeK7eRmoI1tvCPaDzn9Oqh+yD6Lw+sGLsLg4kKg==", + "license": "Apache-2.0" + }, + "node_modules/@firebase/storage": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@firebase/storage/-/storage-0.14.0.tgz", + "integrity": "sha512-xWWbb15o6/pWEw8H01UQ1dC5U3rf8QTAzOChYyCpafV6Xki7KVp3Yaw2nSklUwHEziSWE9KoZJS7iYeyqWnYFA==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.0", + "@firebase/util": "1.13.0", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/storage-compat": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@firebase/storage-compat/-/storage-compat-0.4.0.tgz", + "integrity": "sha512-vDzhgGczr1OfcOy285YAPur5pWDEvD67w4thyeCUh6Ys0izN9fNYtA1MJERmNBfqjqu0lg0FM5GLbw0Il21M+g==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.0", + "@firebase/storage": "0.14.0", + "@firebase/storage-types": "0.8.3", + "@firebase/util": "1.13.0", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/storage-types": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/@firebase/storage-types/-/storage-types-0.8.3.tgz", + "integrity": "sha512-+Muk7g9uwngTpd8xn9OdF/D48uiQ7I1Fae7ULsWPuKoCH3HU7bfFPhxtJYzyhjdniowhuDpQcfPmuNRAqZEfvg==", + "license": "Apache-2.0", + "peerDependencies": { + "@firebase/app-types": "0.x", + "@firebase/util": "1.x" + } + }, + "node_modules/@firebase/util": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.13.0.tgz", + "integrity": "sha512-0AZUyYUfpMNcztR5l09izHwXkZpghLgCUaAGjtMwXnCg3bj4ml5VgiwqOMOxJ+Nw4qN/zJAaOQBcJ7KGkWStqQ==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@firebase/webchannel-wrapper": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-1.0.4.tgz", + "integrity": "sha512-6m8+P+dE/RPl4OPzjTxcTbQ0rGeRyeTvAi9KwIffBVCiAMKrfXfLZaqD1F+m8t4B5/Q5aHsMozOgirkH1F5oMQ==", + "license": "Apache-2.0" + }, + "node_modules/@grpc/grpc-js": { + "version": "1.9.15", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.9.15.tgz", + "integrity": "sha512-nqE7Hc0AzI+euzUwDAy0aY5hCp10r734gMGRdU+qOPX0XSceI2ULrcXB5U2xSc5VkWwalCj4M7GzCAygZl2KoQ==", + "license": "Apache-2.0", + "dependencies": { + "@grpc/proto-loader": "^0.7.8", + "@types/node": ">=12.12.47" + }, + "engines": { + "node": "^8.13.0 || >=10.10.0" + } + }, + "node_modules/@grpc/proto-loader": { + "version": "0.7.15", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.15.tgz", + "integrity": "sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ==", + "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/@hookform/resolvers": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-5.1.1.tgz", @@ -1123,6 +1765,70 @@ "url": "https://opencollective.com/pkgr" } }, + "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/@rolldown/pluginutils": { "version": "1.0.0-beta.11", "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.11.tgz", @@ -1687,7 +2393,6 @@ "version": "24.0.7", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.7.tgz", "integrity": "sha512-YIEUUr4yf8q8oQoXPpSlnvKNVKDQlPMWrmOcgzoduo7kvA2UF0/BwJ/eMKFTiTtkNL17I0M6Xe2tvwFU7be6iw==", - "dev": true, "license": "MIT", "dependencies": { "undici-types": "~7.8.0" @@ -2070,7 +2775,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" @@ -2673,7 +3377,6 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, "license": "ISC", "dependencies": { "string-width": "^4.2.0", @@ -2688,7 +3391,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" @@ -2704,7 +3406,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", @@ -2731,7 +3432,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" @@ -2744,7 +3444,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/colorette": { @@ -3138,7 +3837,6 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, "license": "MIT" }, "node_modules/enhanced-resolve": { @@ -3419,7 +4117,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" @@ -3997,6 +4694,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/fdir": { "version": "6.4.6", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", @@ -4055,6 +4764,42 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/firebase": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/firebase/-/firebase-12.1.0.tgz", + "integrity": "sha512-oZucxvfWKuAW4eHHRqGKzC43fLiPqPwHYBHPRNsnkgonqYaq0VurYgqgBosRlEulW+TWja/5Tpo2FpUU+QrfEQ==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/ai": "2.1.0", + "@firebase/analytics": "0.10.18", + "@firebase/analytics-compat": "0.2.24", + "@firebase/app": "0.14.1", + "@firebase/app-check": "0.11.0", + "@firebase/app-check-compat": "0.4.0", + "@firebase/app-compat": "0.5.1", + "@firebase/app-types": "0.9.3", + "@firebase/auth": "1.11.0", + "@firebase/auth-compat": "0.6.0", + "@firebase/data-connect": "0.3.11", + "@firebase/database": "1.1.0", + "@firebase/database-compat": "2.1.0", + "@firebase/firestore": "4.9.0", + "@firebase/firestore-compat": "0.4.0", + "@firebase/functions": "0.13.0", + "@firebase/functions-compat": "0.4.0", + "@firebase/installations": "0.6.19", + "@firebase/installations-compat": "0.2.19", + "@firebase/messaging": "0.12.23", + "@firebase/messaging-compat": "0.2.23", + "@firebase/performance": "0.7.9", + "@firebase/performance-compat": "0.2.22", + "@firebase/remote-config": "0.6.6", + "@firebase/remote-config-compat": "0.2.19", + "@firebase/storage": "0.14.0", + "@firebase/storage-compat": "0.4.0", + "@firebase/util": "1.13.0" + } + }, "node_modules/flat-cache": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", @@ -4210,7 +4955,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, "license": "ISC", "engines": { "node": "6.* || 8.* || >= 10.*" @@ -4478,6 +5222,12 @@ "node": ">= 0.4" } }, + "node_modules/http-parser-js": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.10.tgz", + "integrity": "sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==", + "license": "MIT" + }, "node_modules/husky": { "version": "9.1.7", "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", @@ -4494,6 +5244,12 @@ "url": "https://github.com/sponsors/typicode" } }, + "node_modules/idb": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz", + "integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==", + "license": "ISC" + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -4745,7 +5501,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -5349,7 +6104,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", - "dev": true, "license": "MIT" }, "node_modules/lodash.isplainobject": { @@ -5496,6 +6250,12 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "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", @@ -6199,6 +6959,30 @@ "react-is": "^16.13.1" } }, + "node_modules/protobufjs": { + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.3.tgz", + "integrity": "sha512-sildjKwVqOI2kmFDiXQ6aEB0fjYTafpEvIBs8tOR8qI4spuL9OPROLVu2qZqi/xgCfsHIwVqlaF8JBjWFHnKbw==", + "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/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -6283,6 +7067,21 @@ "react": "^16.8.0 || ^17 || ^18 || ^19" } }, + "node_modules/react-intersection-observer": { + "version": "9.16.0", + "resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-9.16.0.tgz", + "integrity": "sha512-w9nJSEp+DrW9KmQmeWHQyfaP6b03v+TdXynaoA964Wxt7mdR3An11z4NNCQgL4gKSK7y1ver2Fq+JKH6CWEzUA==", + "license": "MIT", + "peerDependencies": { + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -6328,6 +7127,16 @@ "react-dom": ">=18" } }, + "node_modules/react-spinners": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-spinners/-/react-spinners-0.17.0.tgz", + "integrity": "sha512-L/8HTylaBmIWwQzIjMq+0vyaRXuoAevzWoD35wKpNTxxtYXWZp+xtgkfD7Y4WItuX0YvdxMPU79+7VhhmbmuTQ==", + "license": "MIT", + "peerDependencies": { + "react": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", @@ -6376,7 +7185,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -6539,6 +7347,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-push-apply": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", @@ -6856,7 +7684,6 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -6984,7 +7811,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -7186,7 +8012,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/type-check": { @@ -7340,7 +8165,6 @@ "version": "7.8.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz", "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==", - "dev": true, "license": "MIT" }, "node_modules/unicorn-magic": { @@ -7496,6 +8320,35 @@ "vite": ">=2.6.0" } }, + "node_modules/web-vitals": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-4.2.4.tgz", + "integrity": "sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw==", + "license": "Apache-2.0" + }, + "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/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -7693,7 +8546,6 @@ "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, "license": "ISC", "engines": { "node": ">=10" @@ -7726,7 +8578,6 @@ "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, "license": "MIT", "dependencies": { "cliui": "^8.0.1", @@ -7745,7 +8596,6 @@ "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, "license": "ISC", "engines": { "node": ">=12" diff --git a/src/api/auth/account.ts b/src/api/auth/account.ts new file mode 100644 index 0000000..06a8b24 --- /dev/null +++ b/src/api/auth/account.ts @@ -0,0 +1,15 @@ +import type { TChangeNicknamePayload, TChangeNicknameResponse, TChangePasswordPayload } from '@/types/auth/account'; + +import { axiosInstance } from '@/api/axiosInstance'; + +// 비밀번호 변경 +export async function changePassword(payload: TChangePasswordPayload): Promise { + const body = { nowPassword: payload.currentPassword, newPassword: payload.newPassword }; + await axiosInstance.patch('/api/v1/members/passwords', body, { withCredentials: true }); +} + +// 닉네임 변경 +export async function changeNickname(payload: TChangeNicknamePayload): Promise { + const { data } = await axiosInstance.patch('/api/v1/members/infos', payload, { withCredentials: true }); + return data; +} diff --git a/src/api/faq/faq.keys.ts b/src/api/faq/faq.keys.ts new file mode 100644 index 0000000..d18bb0f --- /dev/null +++ b/src/api/faq/faq.keys.ts @@ -0,0 +1,5 @@ +export const faqKeys = { + all: ['faqs'] as const, + list: (p: { category: string; page: number; size: number }) => [...faqKeys.all, 'list', p] as const, + search: (p: { keyword: string; category?: string; page: number; size: number }) => [...faqKeys.all, 'search', p] as const, +}; diff --git a/src/api/faq/faq.ts b/src/api/faq/faq.ts new file mode 100644 index 0000000..98bc9d2 --- /dev/null +++ b/src/api/faq/faq.ts @@ -0,0 +1,28 @@ +import type { TFaqCategory, TFetchFaqsResponse } from '@/types/faq/faq'; + +import { axiosInstance } from '@/api/axiosInstance'; + +// FAQ 목록 조회 +export const getFaqs = async (params: { category: TFaqCategory; page: number; size: number }) => { + const { data } = await axiosInstance.get('/api/v1/faqs', { + params: { + faqCategory: params.category, + page: params.page, + size: params.size, + }, + }); + return data; +}; + +// FAQ 검색 +export const searchFaqs = async (params: { keyword: string; category?: TFaqCategory; page: number; size: number }) => { + const { data } = await axiosInstance.get('/api/v1/faqs/search', { + params: { + keyword: params.keyword, + faqCategory: params.category, + page: params.page, + size: params.size, + }, + }); + return data; +}; diff --git a/src/api/notice/notice.keys.ts b/src/api/notice/notice.keys.ts new file mode 100644 index 0000000..12c3470 --- /dev/null +++ b/src/api/notice/notice.keys.ts @@ -0,0 +1,20 @@ +// React Query 캐시 키 관리 전용 파일 + +// 목록 쿼리 파라미터 타입 +type TListParams = { + category: 'SERVICE' | 'SYSTEM'; + page: number; + size: number; +}; + +export const noticeKeys = { + all: ['notices'] as const, + // 목록 키 - 카테고리/페이지/사이즈별로 캐시 분리 + list: (params: TListParams) => [...noticeKeys.all, 'list', params.category, params.page, params.size] as const, + + // 상세 키 - ID별로 캐시 분리 + detail: (id: number) => [...noticeKeys.all, 'detail', id] as const, + + // 검색 키 + search: (p: { keyword: string; page: number; size: number; category?: string }) => [...noticeKeys.all, 'search', p] as const, +}; diff --git a/src/api/notice/notice.ts b/src/api/notice/notice.ts index b2ce1c0..a3d8ccf 100644 --- a/src/api/notice/notice.ts +++ b/src/api/notice/notice.ts @@ -1,17 +1,36 @@ -import type { TFetchNoticeDetailResponse, TFetchNoticesResponse, TRequestGetNoticeRequest } from '@/types/notice/notice'; +import type { TFetchNoticeDetailResponse, TFetchNoticesResponse } from '@/types/notice/notice'; import { axiosInstance } from '@/api/axiosInstance'; -// 공지사항 전체 조회 API -export const fetchNotices = async ({ noticeCategory = 'SERVICE', page, size }: TRequestGetNoticeRequest): Promise => { - const { data } = await axiosInstance.get('/api/v1/notices', { - params: { noticeCategory: noticeCategory, page, size }, +// 공지사항 전체 조회 +export const getNotices = async (params: { category: 'SERVICE' | 'SYSTEM'; page: number; size: number }) => { + const { data } = await axiosInstance.get('/api/v1/notices', { + params: { + noticeCategory: params.category, + page: params.page, + size: params.size, + }, }); return data; }; -// 공지사항 상세 조회 API -export const fetchNoticeDetail = async (noticeId: number): Promise => { - const { data } = await axiosInstance.get(`/api/v1/notices/${noticeId}`); +// 상세 조회 +export const getNoticeDetail = async (noticeId: number) => { + const { data } = await axiosInstance.get(`/api/v1/notices/${noticeId}`); + return data; +}; + +// 공지 검색 +export const searchNotices = async (params: { keyword: string; page: number; size: number; category?: 'SERVICE' | 'SYSTEM' }) => { + const { keyword, page, size, category } = params; + + const { data } = await axiosInstance.get('/api/v1/notices/search', { + params: { + keyword, + page, + size, + ...(category && { noticeCategory: category }), + }, + }); return data; }; diff --git a/src/api/settingAlarm/alarm.ts b/src/api/settingAlarm/alarm.ts new file mode 100644 index 0000000..faf8830 --- /dev/null +++ b/src/api/settingAlarm/alarm.ts @@ -0,0 +1,15 @@ +import type { TAlarmSettings, TGetAlarmSettingsResp, TPatchAlarmSettingsResp } from '@/types/settingAlarm/alarm'; + +import { axiosInstance } from '@/api/axiosInstance'; + +// 조회 +export async function getAlarmSettings(): Promise { + const { data } = await axiosInstance.get('/api/v1/alarms/settings', { withCredentials: true }); + return data; +} + +// 업데이트 +export async function patchAlarmSettings(payload: TAlarmSettings): Promise { + const { data } = await axiosInstance.patch('/api/v1/alarms/settings', payload, { withCredentials: true }); + return data; +} diff --git a/src/components/common/EditableInputBox.tsx b/src/components/common/EditableInputBox.tsx index 1c65e89..b70503b 100644 --- a/src/components/common/EditableInputBox.tsx +++ b/src/components/common/EditableInputBox.tsx @@ -15,6 +15,7 @@ interface IEditableInputBoxProps { onSearchClick?: () => void; className?: string; placeholder?: string; + readOnly?: boolean; } export default function EditableInputBox({ diff --git a/src/components/common/PasswordEdit.tsx b/src/components/common/PasswordEdit.tsx index 19bd430..b5e34cb 100644 --- a/src/components/common/PasswordEdit.tsx +++ b/src/components/common/PasswordEdit.tsx @@ -1,28 +1,25 @@ import { useState } from 'react'; import { z } from 'zod'; +import { useAccount } from '@/hooks/auth/useAccount'; + import AlertCircle from '@/assets/icons/alert-circle_Fill.svg?react'; -// 유효성 스키마 +// 비밀번호 유효성 스키마 const passwordSchema = z.string().min(8, '8자 이상이어야 합니다.').max(32, '32자 이하로 입력해주세요.'); export default function PasswordEditSection() { - // 상태 : 편집 여부 - const [isEditing, setIsEditing] = useState(false); + const { useChangePassword } = useAccount(); - // 상태 : 비밀번호 입력값 + const [isEditing, setIsEditing] = useState(false); const [currentPw, setCurrentPw] = useState(''); const [newPw, setNewPw] = useState(''); const [confirmPw, setConfirmPw] = useState(''); + const [errors, setErrors] = useState<{ currentPw?: string; newPw?: string; confirmPw?: string }>({}); - // 상태 : 각 필드별 에러 메시지 - const [errors, setErrors] = useState<{ - currentPw?: string; - newPw?: string; - confirmPw?: string; - }>({}); + const MASK = '********'; - // 취소 버튼 + // 입력값 초기화 const handleCancel = () => { setCurrentPw(''); setNewPw(''); @@ -31,39 +28,43 @@ export default function PasswordEditSection() { setIsEditing(false); }; - // 저장하기 버튼 + const { mutate: changePw, isPending } = useChangePassword({ + onSuccess: () => { + alert('비밀번호가 변경되었습니다.'); + handleCancel(); + }, + onError: (err) => { + const msg = (err as any)?.response?.data?.message ?? '비밀번호 변경에 실패했습니다.'; + alert(msg); + }, + }); + + // 제출 const handleSubmit = () => { - const newErrors: typeof errors = {}; + const nextErrors: typeof errors = {}; - // 현재 비밀번호 검사 - if (!currentPw || currentPw.trim().length < 8) { - newErrors.currentPw = '8자 이상이어야 합니다.'; - } + if (!currentPw) nextErrors.currentPw = '현재 비밀번호를 입력하세요.'; - // 새 비밀번호 검사 - const result = passwordSchema.safeParse(newPw); - if (!result.success) { - newErrors.newPw = result.error.errors[0].message; - } else if (newPw === currentPw) { - newErrors.newPw = '이전에 사용한 적이 없는 비밀번호여야 합니다.'; + const newPwCheck = passwordSchema.safeParse(newPw); + if (!newPwCheck.success) { + nextErrors.newPw = newPwCheck.error.issues[0]?.message ?? '유효하지 않은 비밀번호입니다.'; } - // 비밀번호 확인 일치 if (newPw !== confirmPw) { - newErrors.confirmPw = '비밀번호가 일치하지 않습니다'; + nextErrors.confirmPw = '비밀번호가 일치하지 않습니다.'; } - if (Object.keys(newErrors).length > 0) { - setErrors(newErrors); - return; - } + setErrors(nextErrors); + if (Object.keys(nextErrors).length > 0) return; - console.log('비밀번호 변경 요청:', { currentPw, newPw }); - setIsEditing(false); - setErrors({}); + // 제출 + changePw({ + currentPassword: currentPw, + newPassword: newPw, + }); }; - // input 스타일 + // 공통 인풋 스타일 const inputClass = (hasError: boolean) => `w-full h-12 pl-4 pr-10 font-body1 text-black placeholder:text-default-gray-500 rounded-2xl border ${ hasError ? 'border-warning' : 'border-primary-500' @@ -71,22 +72,28 @@ export default function PasswordEditSection() { return (
- {/* 헤더 - 비밀번호 / 취소 */} + {/* 헤더 */}

비밀번호

{isEditing && ( - )}
- {/* 비편집 모드 - 현재 비밀번호 + 수정 */} + + {/* 비편집 모드 */} {!isEditing ? (
) : ( - // 편집 모드 - 현재/새/확인 비밀번호 + // 편집 모드
{/* 현재 비밀번호 */}
@@ -107,13 +114,16 @@ export default function PasswordEditSection() { value={currentPw} onChange={(e) => setCurrentPw(e.target.value)} className={inputClass(!!errors.currentPw)} + disabled={isPending} /> {errors.currentPw && ( -
- -
+ <> +
+ +
+

{errors.currentPw}

+ )} - {errors.currentPw &&

{errors.currentPw}

}
{/* 새 비밀번호 */} @@ -124,13 +134,16 @@ export default function PasswordEditSection() { value={newPw} onChange={(e) => setNewPw(e.target.value)} className={inputClass(!!errors.newPw)} + disabled={isPending} /> {errors.newPw && ( -
- -
+ <> +
+ +
+

{errors.newPw}

+ )} - {errors.newPw &&

{errors.newPw}

}
{/* 비밀번호 확인 */} @@ -141,19 +154,26 @@ export default function PasswordEditSection() { value={confirmPw} onChange={(e) => setConfirmPw(e.target.value)} className={inputClass(!!errors.confirmPw)} + disabled={isPending} /> {errors.confirmPw && ( -
- -
+ <> +
+ +
+

{errors.confirmPw}

+ )} - {errors.confirmPw &&

{errors.confirmPw}

}
{/* 저장 버튼 */}
-
diff --git a/src/components/faq/FAQItem.tsx b/src/components/faq/FAQItem.tsx index a7fb2f3..0962932 100644 --- a/src/components/faq/FAQItem.tsx +++ b/src/components/faq/FAQItem.tsx @@ -1,10 +1,8 @@ -//Question - 질문 토글 컴포넌트 import ChevronDown from '@/assets/icons/default_arrows/chevron_down.svg?react'; -//props 타입 정의 interface IFAQItemProps { item: { - category: string; + category?: string; question: string; answer: string; }; @@ -13,22 +11,38 @@ interface IFAQItemProps { } export default function FAQItem({ item, isOpen, onToggle }: IFAQItemProps) { + const contentId = `faq-answer-${item.question}-${item.category ?? 'none'}`; + return (
  • {/* 질문 영역 - 클릭 시 토글 */} - {/* 답변 영역 */} - {isOpen &&
    {item.answer}
    } +
    +
    {item.answer}
    +
  • ); } diff --git a/src/components/settingTab/AlarmSetting.tsx b/src/components/settingTab/AlarmSetting.tsx index e6ccc61..f650568 100644 --- a/src/components/settingTab/AlarmSetting.tsx +++ b/src/components/settingTab/AlarmSetting.tsx @@ -1,11 +1,11 @@ -import { useState } from 'react'; +import { useEffect, useState } from 'react'; + +import { useGetAlarmSettings, usePatchAlarmSettings } from '@/hooks/settingAlarm/useAlarms'; import ToggleSwitch from '@/components/common/ToggleSwitch'; -// 알람 타입 정의 type TAlarmType = 'email' | 'push' | 'sms'; -// 알람 설정 상태 구조 interface IAlarmSettingState { email: boolean; push: boolean; @@ -13,23 +13,46 @@ interface IAlarmSettingState { } export default function AlarmSetting() { - // 상태 관리 + // 서버 데이터 + const { data: serverSettings } = useGetAlarmSettings(); + const { mutate: patchAlarm } = usePatchAlarmSettings(); + + // 초기 값 const [alarmSetting, setAlarmSetting] = useState({ email: true, push: true, - sms: false, + sms: true, }); - // 토글 변경 핸들러 - 이전 값 기준으로 반전 - const handleToggle = (type: TAlarmType) => { - setAlarmSetting((prev) => ({ - ...prev, - [type]: !prev[type], - })); + // 서버 값 수신 -> UI 상태 매핑 + useEffect(() => { + if (!serverSettings) return; + setAlarmSetting({ + email: !!serverSettings.emailAlarm, + push: !!serverSettings.pushAlarm, + sms: !!serverSettings.smsAlarm, + }); + }, [serverSettings]); + + // 토글 핸들러 + const handleToggle = (key: TAlarmType) => { + const prev = alarmSetting; + const next = { ...prev, [key]: !prev[key] }; + setAlarmSetting(next); + + patchAlarm( + { + emailAlarm: next.email, + pushAlarm: next.push, + smsAlarm: next.sms, + }, + { + onError: () => setAlarmSetting(prev), + }, + ); }; - // UI 표시할 항목 배열 - const alarmItems: { label: string; key: TAlarmType }[] = [ + const items: { label: string; key: TAlarmType }[] = [ { label: 'Email 알람', key: 'email' }, { label: '푸쉬 알람', key: 'push' }, { label: 'SMS 알람', key: 'sms' }, @@ -37,12 +60,9 @@ export default function AlarmSetting() { return (
    - {alarmItems.map(({ label, key }) => ( + {items.map(({ label, key }) => (
    - {/* 텍스트 */}

    {label}

    - - {/* 토글 */} handleToggle(key)} onLabel="ON" offLabel="OFF" />
    ))} diff --git a/src/components/settingTab/InfoSetting.tsx b/src/components/settingTab/InfoSetting.tsx index aa29684..d83e6c4 100644 --- a/src/components/settingTab/InfoSetting.tsx +++ b/src/components/settingTab/InfoSetting.tsx @@ -1,6 +1,9 @@ import { useEffect, useState } from 'react'; import { Link } from 'react-router-dom'; +import { useAccount } from '@/hooks/auth/useAccount'; +import { useUserEmail } from '@/hooks/auth/useEmail'; + import EditableInputBox from '../common/EditableInputBox'; import PasswordEditSection from '../common/PasswordEdit'; @@ -8,55 +11,80 @@ import ChevronForward from '@/assets/icons/default_arrows/chevron_forward.svg?re export default function InfoSetting() { const [nickname, setNickname] = useState(''); - const [email, setEmail] = useState(''); + const [initialNickname, setInitialNickname] = useState(''); + + // 이메일(읽기 전용) + const { email } = useUserEmail(); + + // 닉네임/비밀번호 변경 훅 + const { useChangeNickname } = useAccount(); + const { mutate: changeNickname, isPending: nickPending } = useChangeNickname({ + onSuccess: (res) => { + if (res.isSuccess) { + setNickname(res.result.username); + setInitialNickname(res.result.username); + localStorage.setItem('nickname', res.result.username); + alert('닉네임이 변경되었습니다.'); + } else { + alert(res.message ?? '닉네임 변경에 실패했습니다.'); + } + }, + onError: (err: any) => { + const msg = err?.response?.data?.message ?? (err?.response?.status === 401 ? '로그인이 필요합니다.' : '닉네임 변경에 실패했습니다.'); + alert(msg); + }, + }); + + // 초기 닉네임 세팅 useEffect(() => { - // 예시: 로컬에 저장된 이메일 가져옴 - const storedEmail = localStorage.getItem('userEmail'); - if (storedEmail) { - setEmail(storedEmail); + const stored = localStorage.getItem('nickname'); + if (stored) { + setNickname(stored); + setInitialNickname(stored); } }, []); + // 닉네임 저장 + const handleSubmitNickname = () => { + if (nickname === initialNickname || nickPending) return; + changeNickname({ username: nickname }); + }; + + // 닉네임 취소 + const handleCancelNickname = () => setNickname(initialNickname); + return (
    - {/* 닉네임 - 수정가능 */} + {/* 닉네임 */} setNickname(e.target.value)} - onCancel={() => setNickname('')} - onSubmit={() => console.log('닉네임 저장:', nickname)} + onCancel={handleCancelNickname} + onSubmit={handleSubmitNickname} /> - {/* 이메일 - 수정 불가능 */} - {}} // 읽기 전용 - className="pointer-events-none" // 수정도 불가능 - placeholder="이메일" - /> - {/* 비밀번호 - 수정 가능 */} + + {/* 이메일 (읽기 전용) */} + {}} className="pointer-events-none" placeholder="이메일" /> + + {/* 비밀번호 변경 섹션 */} - {/* 취향 초기화 버튼 */}
    - {/* 기타 링크 */}
    - - 탈퇴하기 diff --git a/src/hooks/alarm/useDeviceToken.ts b/src/hooks/alarm/useDeviceToken.ts index 73523c4..2fd48c0 100644 --- a/src/hooks/alarm/useDeviceToken.ts +++ b/src/hooks/alarm/useDeviceToken.ts @@ -1,39 +1,39 @@ -// src/hooks/alarm/useDeviceToken.ts -import { useEffect } from 'react'; -import { isSupported } from 'firebase/messaging'; +// // src/hooks/alarm/useDeviceToken.ts +// import { useEffect } from 'react'; +// import { isSupported } from 'firebase/messaging'; -import { postDeviceToken } from '@/api/alarm/alarm'; // 서버에 FCM 토큰 전송하는 API 함수 -import { generateToken, registerServiceWorker } from '@/firebase/firebase'; +// import { postDeviceToken } from '@/api/alarm/alarm'; // 서버에 FCM 토큰 전송하는 API 함수 +// import { generateToken, registerServiceWorker } from '@/firebase/firebase'; -export const useDeviceToken = () => { - useEffect(() => { - const setupFCM = async () => { - if (!(await isSupported())) { - console.warn('FCM은 현재 브라우저에서 지원되지 않습니다.'); - return; - } +// export const useDeviceToken = () => { +// useEffect(() => { +// const setupFCM = async () => { +// if (!(await isSupported())) { +// console.warn('FCM은 현재 브라우저에서 지원되지 않습니다.'); +// return; +// } - await registerServiceWorker(); - const token = await generateToken(); +// await registerServiceWorker(); +// const token = await generateToken(); - if (token) { - try { - await postDeviceToken({ deviceToken: token }); // 서버에 전송 - } catch (err) { - console.error('디바이스 토큰 서버 전송 실패:', err); - } - } - }; +// if (token) { +// try { +// await postDeviceToken({ deviceToken: token }); // 서버에 전송 +// } catch (err) { +// console.error('디바이스 토큰 서버 전송 실패:', err); +// } +// } +// }; - const handleClick = () => { - setupFCM(); - window.removeEventListener('click', handleClick); - }; +// const handleClick = () => { +// setupFCM(); +// window.removeEventListener('click', handleClick); +// }; - window.addEventListener('click', handleClick); +// window.addEventListener('click', handleClick); - return () => { - window.removeEventListener('click', handleClick); - }; - }, []); -}; +// return () => { +// window.removeEventListener('click', handleClick); +// }; +// }, []); +// }; diff --git a/src/hooks/auth/useAccount.ts b/src/hooks/auth/useAccount.ts new file mode 100644 index 0000000..9ad8825 --- /dev/null +++ b/src/hooks/auth/useAccount.ts @@ -0,0 +1,28 @@ +import type { + TChangeNicknameMutationOptions, + TChangeNicknameMutationResult, + TChangeNicknamePayload, + TChangeNicknameResponse, + TChangePasswordMutationOptions, + TChangePasswordMutationResult, + TChangePasswordPayload, + TChangePasswordResponse, +} from '@/types/auth/account'; + +import { useCoreMutation } from '@/hooks/customQuery'; + +import { changeNickname, changePassword } from '@/api/auth/account'; + +export function useAccount() { + // 비밀번호 변경 + function useChangePassword(options?: TChangePasswordMutationOptions): TChangePasswordMutationResult { + return useCoreMutation(changePassword, options); + } + + // 닉네임 변경 + function useChangeNickname(options?: TChangeNicknameMutationOptions): TChangeNicknameMutationResult { + return useCoreMutation(changeNickname, options); + } + + return { useChangePassword, useChangeNickname }; +} diff --git a/src/hooks/auth/useEmail.ts b/src/hooks/auth/useEmail.ts new file mode 100644 index 0000000..28a5a3a --- /dev/null +++ b/src/hooks/auth/useEmail.ts @@ -0,0 +1,15 @@ +import { useEffect, useState } from 'react'; + +export function useUserEmail() { + const [email, setEmail] = useState(() => localStorage.getItem('userEmail') || ''); + + useEffect(() => { + const onStorage = (e: StorageEvent) => { + if (e.key === 'userEmail') setEmail(e.newValue || ''); + }; + window.addEventListener('storage', onStorage); + return () => window.removeEventListener('storage', onStorage); + }, []); + + return { email }; +} diff --git a/src/hooks/faq/useFaq.ts b/src/hooks/faq/useFaq.ts new file mode 100644 index 0000000..2195e22 --- /dev/null +++ b/src/hooks/faq/useFaq.ts @@ -0,0 +1,34 @@ +import { useMemo } from 'react'; +import { keepPreviousData } from '@tanstack/react-query'; + +import type { TFaqCategory, TFetchFaqsResponse } from '@/types/faq/faq'; + +import { useCoreQuery } from '@/hooks/customQuery'; + +import { getFaqs, searchFaqs } from '@/api/faq/faq'; +import { faqKeys } from '@/api/faq/faq.keys'; + +type TFaqListParams = { category: TFaqCategory; page: number; size: number }; +type TFaqSearchParams = { keyword: string; category?: TFaqCategory; page: number; size: number }; + +export function useFaq() { + const useGetFaqs = (params: TFaqListParams, options?: Parameters>[2]) => { + const stable = useMemo(() => params, [params.category, params.page, params.size]); + return useCoreQuery(faqKeys.list(stable), () => getFaqs(stable), { + placeholderData: keepPreviousData, + ...options, + }); + }; + + const useSearchFaqs = (params: TFaqSearchParams, options?: Parameters>[2]) => { + const stable = useMemo(() => ({ ...params, keyword: params.keyword.trim() }), [params.keyword, params.category, params.page, params.size]); + + return useCoreQuery(faqKeys.search(stable), () => searchFaqs(stable), { + enabled: stable.keyword.length > 0, + placeholderData: keepPreviousData, + ...options, + }); + }; + + return { useGetFaqs, useSearchFaqs }; +} diff --git a/src/hooks/notice/useNotice.ts b/src/hooks/notice/useNotice.ts new file mode 100644 index 0000000..ce38663 --- /dev/null +++ b/src/hooks/notice/useNotice.ts @@ -0,0 +1,58 @@ +import { useMemo } from 'react'; +import { keepPreviousData } from '@tanstack/react-query'; + +import type { TFetchNoticeDetailResponse, TFetchNoticesResponse } from '@/types/notice/notice'; + +import { useCoreQuery } from '@/hooks/customQuery'; + +import { getNoticeDetail, getNotices, searchNotices } from '@/api/notice/notice'; +import { noticeKeys } from '@/api/notice/notice.keys'; + +type TListParams = { + category: 'SERVICE' | 'SYSTEM'; + page: number; + size: number; +}; + +type TSearchParams = { + keyword: string; + page: number; + size: number; + category?: 'SERVICE' | 'SYSTEM'; +}; + +export function useNotice() { + // 공지 목록 + const useGetNotices = (params: TListParams, options?: Parameters>[2]) => { + const stable = useMemo(() => params, [params.category, params.page, params.size]); + + return useCoreQuery(noticeKeys.list(stable), () => getNotices(stable), { + placeholderData: keepPreviousData, + ...options, + }); + }; + + // 공지 상세 + const useGetNoticeDetail = (id: number, options?: Parameters>[2]) => + useCoreQuery(noticeKeys.detail(id), () => getNoticeDetail(id), { + enabled: Number.isFinite(id), + ...options, + }); + + // 공지 검색 + const useSearchNotices = (params: TSearchParams, options?: Parameters>[2]) => { + const stable = useMemo(() => ({ ...params, keyword: params.keyword.trim() }), [params.keyword, params.page, params.size, params.category]); + + return useCoreQuery(noticeKeys.search(stable), () => searchNotices(stable), { + enabled: stable.keyword.length > 0, + placeholderData: keepPreviousData, + ...options, + }); + }; + + return { + useGetNotices, + useGetNoticeDetail, + useSearchNotices, + }; +} diff --git a/src/hooks/settingAlarm/useAlarms.ts b/src/hooks/settingAlarm/useAlarms.ts new file mode 100644 index 0000000..cd741ed --- /dev/null +++ b/src/hooks/settingAlarm/useAlarms.ts @@ -0,0 +1,25 @@ +import { useQueryClient } from '@tanstack/react-query'; + +import type { AlarmSettings, GetAlarmSettingsResp, PatchAlarmSettingsResp } from '@/types/settingAlarm/alarm'; + +import { useCoreMutation, useCoreQuery } from '@/hooks/customQuery'; + +import { getAlarmSettings, patchAlarmSettings } from '@/api/settingAlarm/alarm'; + +// 알림 설정 조회 +export function useGetAlarmSettings() { + return useCoreQuery(['alarmSettings'], getAlarmSettings, { + select: (resp: GetAlarmSettingsResp) => resp.result, + refetchOnWindowFocus: false, + }); +} + +// 알림 설정 업데이트 +export function usePatchAlarmSettings() { + const qc = useQueryClient(); + return useCoreMutation(patchAlarmSettings, { + onSuccess: () => { + qc.invalidateQueries({ queryKey: ['alarmSettings'] }); + }, + }); +} diff --git a/src/pages/home/HomePage.tsx b/src/pages/home/HomePage.tsx index 90838f0..6f239c7 100644 --- a/src/pages/home/HomePage.tsx +++ b/src/pages/home/HomePage.tsx @@ -1,7 +1,6 @@ import { Navigate } from 'react-router-dom'; import ClipLoader from 'react-spinners/ClipLoader'; -import { useDeviceToken } from '@/hooks/alarm/useDeviceToken'; import { useUserGrade } from '@/hooks/home/useUserGrade'; import Banner from '@/components/home/banner'; @@ -14,7 +13,7 @@ import Level from '@/components/home/level'; import WordCloudCard from '@/components/home/wordCloud'; function Home() { - useDeviceToken(); + // useDeviceToken(); const { data: gradeData, isLoading, error } = useUserGrade(); if (error) return ; diff --git a/src/pages/notice/Notice.tsx b/src/pages/notice/Notice.tsx index 1fe1f4c..637715b 100644 --- a/src/pages/notice/Notice.tsx +++ b/src/pages/notice/Notice.tsx @@ -1,119 +1,98 @@ -import { useEffect, useState } from 'react'; +import { useMemo, useState } from 'react'; import { Link } from 'react-router-dom'; import type { TNoticeItem } from '@/types/notice/notice'; +import { formatDateDot } from '@/utils/date'; + +import { useNotice } from '@/hooks/notice/useNotice'; + import EditableInputBox from '@/components/common/EditableInputBox'; import Navigator from '@/components/common/navigator'; -import { fetchNotices } from '@/api/notice/notice'; - -const categories = ['서비스 안내', '시스템 안내']; +const categories = ['서비스 안내', '시스템 안내'] as const; export default function Notice() { - const [searchValue, setSearchValue] = useState(''); //검색어 상태 - const [activeCategory, setActiveCategory] = useState(categories[0]); //선택된 카테고리 - const [currentPage, setCurrentPage] = useState(1); + const [searchValue, setSearchValue] = useState(''); + const [keyword, setKeyword] = useState(''); - const [noticeList, setNoticeList] = useState([]); // 공지사항 리스트 - const [totalPages, setTotalPages] = useState(1); - const [loading, setLoading] = useState(false); - const [error, setError] = useState(''); + const [activeCategory, setActiveCategory] = useState<(typeof categories)[number]>('서비스 안내'); + const [currentPage, setCurrentPage] = useState(1); const itemsPerPage = 10; - // 백엔드에 넘길 카테고리 키 - 영어 변환 - const categoryKey = activeCategory === '서비스 안내' ? 'SERVICE' : 'SYSTEM'; - - // 컴포넌트 마운트 시, 카테고리/페이지 변경 시 -> API 호출 - useEffect(() => { - const getNotices = async () => { - setLoading(true); - try { - // 공지사항 목록 요청 - const response = await fetchNotices({ - noticeCategory: categoryKey, - page: currentPage - 1, - size: itemsPerPage, - }); - - console.log('API 응답:', response); - - // 공지 목록과 페이지 수 설정 (빈 배열도 허용) - setNoticeList(response.result.noticeList ?? []); - setTotalPages(response.result.totalPages ?? 1); - } catch (err) { - // 오류 처리 - setError('공지사항을 불러오는 데 실패했습니다.'); - console.log(err); - } finally { - setLoading(false); - } - }; - - getNotices(); // 함수 실행 - }, [activeCategory, currentPage]); // 의존성 배열 - 카테고리/페이지 변경 시마다 재호출 - - // 검색어 필터링 적용된 공지사항 - const filteredNotices = noticeList.filter((notice) => notice.title.toLowerCase().includes(searchValue.toLowerCase())); + const categoryKey: 'SERVICE' | 'SYSTEM' = activeCategory === '서비스 안내' ? 'SERVICE' : 'SYSTEM'; + + // 검색 여부 + const isSearching = keyword.trim().length > 0; + + const listParams = useMemo(() => ({ category: categoryKey, page: currentPage - 1, size: itemsPerPage }), [categoryKey, currentPage]); + const searchParams = useMemo(() => ({ keyword, category: categoryKey, page: currentPage - 1, size: itemsPerPage }), [keyword, categoryKey, currentPage]); + + const { useGetNotices, useSearchNotices } = useNotice(); + const listQuery = useGetNotices(listParams, { enabled: !isSearching }); + const searchQuery = useSearchNotices(searchParams, { enabled: isSearching }); + + const { data, isLoading, isError } = isSearching ? searchQuery : listQuery; + + const list = (data?.result?.noticeList ?? []) as TNoticeItem[]; + const totalPages = data?.result?.totalPages ?? 1; return ( -
    +

    공지사항

    - {/* 검색 */} + {/* 검색창 */} setSearchValue(e.target.value)} - onSearchClick={() => console.log('검색 실행:', searchValue)} - placeholder="찾으시는 내용을 입력해주세요." + onSearchClick={() => { + setKeyword(searchValue.trim()); + setCurrentPage(1); + }} + placeholder="검색어를 입력하세요" className="mb-8" /> - {/* 카테고리 */} + {/* 카테고리 버튼 */}
    - {categories.map((category) => ( + {categories.map((c) => ( ))}
    - {/* 공지 없을 때 메시지 */} - {!loading && !error && filteredNotices.length === 0 &&

    공지사항이 없습니다.

    } + {/* 상태 */} + {isLoading &&

    로딩 중

    } + {isError &&

    불러오기에 실패했습니다.

    } + {!isLoading && !isError && list.length === 0 &&

    등록된 공지사항이 없습니다.

    } - {/* 공지 리스트 */} + {/* 목록 */}
      - {filteredNotices.map((notice) => ( -
    • - - {notice.title} - {new Date(notice.createdAt).toLocaleDateString()} + {list.map((n) => ( +
    • + + {n.title} +

      {formatDateDot(n.createdAt)}

    • ))}
    {/* 페이지네이션 */} - {totalPages > 1 && ( - { - setCurrentPage(page); //페이지 변경 - }} - /> - )} + {totalPages > 1 && setCurrentPage(p)} />}
    ); } diff --git a/src/pages/notice/NoticeDetail.tsx b/src/pages/notice/NoticeDetail.tsx index a2020a0..dbb907b 100644 --- a/src/pages/notice/NoticeDetail.tsx +++ b/src/pages/notice/NoticeDetail.tsx @@ -1,64 +1,39 @@ -import { useEffect, useState } from 'react'; -import { useLocation, useNavigate } from 'react-router-dom'; +import { useNavigate, useParams } from 'react-router-dom'; -import type { TNoticeDetail } from '@/types/notice/notice'; - -import { fetchNoticeDetail } from '@/api/notice/notice'; +import { useNotice } from '@/hooks/notice/useNotice'; export default function NoticeDetail() { const navigate = useNavigate(); - const location = useLocation(); - const noticeId = location.state?.noticeId; - - const [notice, setNotice] = useState(null); - const [loading, setLoading] = useState(false); - const [error, setError] = useState(''); - - useEffect(() => { - if (!noticeId) { - setError('공지사항 ID가 없습니다.'); - return; - } - - const loadNotice = async () => { - setLoading(true); - try { - const res = await fetchNoticeDetail(noticeId); - - if (!res.isSuccess || !res.result) { - throw new Error(res.message); - } + const { noticeId } = useParams<{ noticeId: string }>(); // URL에서 noticeId 추출 + const id = Number(noticeId); - setNotice(res.result); - } catch (err) { - console.error('📛 공지사항 상세 조회 오류:', err); - setError('공지사항을 불러오는 데 실패했습니다.'); - } finally { - setLoading(false); - } - }; + const { useGetNoticeDetail } = useNotice(); + const { data, isLoading, isError } = useGetNoticeDetail(id); - loadNotice(); - }, [noticeId]); + const notice = data?.result ?? null; - if (loading) return
    로딩 중...
    ; - if (error) return
    {error}
    ; + if (!Number.isFinite(id)) { + return
    잘못된 공지사항 ID입니다.
    ; + } + if (isLoading) return
    로딩 중
    ; + if (isError || !notice) { + return
    공지사항을 불러오는 데 실패했습니다.
    ; + } return (
    - {/* 타이틀 */}

    공지사항

    - {/* 공지 제목 + 날짜 */} + {/* 제목 + 작성일 */}
    -

    {notice?.title}

    -

    {new Date(notice?.createdAt || '').toLocaleDateString()}

    +

    {notice.title}

    +

    {new Date(notice.createdAt).toLocaleDateString()}

    - {/* 본문 */} -
    {notice?.content || '내용이 없습니다.'}
    + {/* 내용 */} +
    {notice.content || '내용이 없습니다.'}
    - {/* 목록으로 돌아가기 */} + {/* 목록으로 돌아가기 버튼 */}
    ))}
    - {/* 질문 리스트 */} + {/* 상태 표시 */} + {isLoading &&

    로딩 중

    } + {isError &&

    불러오기에 실패했습니다.

    } + {!isLoading && !isError && normalized.length === 0 &&

    등록된 질문이 없습니다.

    } + + {/* 리스트 */}
      - {paginatedQuestions.map((item, index) => ( - setOpenedIndex(openedIndex === index ? null : index)} /> + {normalized.map((item, index) => ( + setOpenedIndex(openedIndex === index ? null : index)} + /> ))}
    @@ -103,9 +122,9 @@ export default function Question() { { - setCurrentPage(page); - setOpenedIndex(null); // 페이지 전환할 때 펼친 질문 초기화 + onClick={(p) => { + setCurrentPage(p); + setOpenedIndex(null); }} /> )} diff --git a/src/types/auth/account.ts b/src/types/auth/account.ts new file mode 100644 index 0000000..af2ab8b --- /dev/null +++ b/src/types/auth/account.ts @@ -0,0 +1,26 @@ +import type { UseMutationResult } from '@tanstack/react-query'; +import type { AxiosError } from 'axios'; + +import type { TUseMutationCustomOptions } from '@/types/common/common'; + +export type TChangePasswordPayload = { + currentPassword: string; + newPassword: string; +}; +export type TChangePasswordResponse = void; + +export type TChangeNicknamePayload = { username: string }; +export type TChangeNicknameResponse = { + isSuccess: boolean; + code: string; + message: string; + result: { username: string }; +}; + +// 비밀번호 변경 훅 타입 +export type TChangePasswordMutationOptions = TUseMutationCustomOptions; +export type TChangePasswordMutationResult = UseMutationResult; + +// 닉네임 변경 훅 타입 +export type TChangeNicknameMutationOptions = TUseMutationCustomOptions; +export type TChangeNicknameMutationResult = UseMutationResult; diff --git a/src/types/faq/faq.ts b/src/types/faq/faq.ts new file mode 100644 index 0000000..552188c --- /dev/null +++ b/src/types/faq/faq.ts @@ -0,0 +1,17 @@ +import type { TCommonResponse } from '@/types/common/common'; + +export type TFaqItem = { + faqId: number; + title: string; + content: string; +}; + +export type TFetchFaqsResponse = TCommonResponse<{ + faqList: TFaqItem[]; + totalPages: number; + currentPage: number; + currentSize: number; + hasNextPage: boolean; +}>; + +export type TFaqCategory = 'USAGE' | 'ALGORITHM' | 'FEATURE' | 'SCHEDULE' | 'ERROR' | 'ACCOUNT'; diff --git a/src/types/notice/notice.ts b/src/types/notice/notice.ts index c52ef05..e1634c9 100644 --- a/src/types/notice/notice.ts +++ b/src/types/notice/notice.ts @@ -32,3 +32,26 @@ export type TNoticeDetail = { }; export type TFetchNoticeDetailResponse = TCommonResponse; + +// 생성 요청 +export type TCreateNoticePayload = { + title: string; + content: string; + isPinned: boolean; + noticeCategory: 'SERVICE' | 'SYSTEM'; +}; + +// 수정 요청 +export type TUpdateNoticePayload = { + title: string; + content: string; + isPinned: boolean; +}; + +// 공통 응답 (이미 TCommonResponse가 있다면 생략 가능) +export type TNoticeCommonResponse = { + isSuccess: boolean; + code: string; + message: string; + result: string; +}; diff --git a/src/types/settingAlarm/alarm.ts b/src/types/settingAlarm/alarm.ts new file mode 100644 index 0000000..9cc04e4 --- /dev/null +++ b/src/types/settingAlarm/alarm.ts @@ -0,0 +1,15 @@ +export type TAlarmSettings = { + emailAlarm: boolean; + pushAlarm: boolean; + smsAlarm: boolean; +}; + +export type TApiEnvelope = { + isSuccess: boolean; + code: string; + message: string; + result: T; +}; + +export type TGetAlarmSettingsResp = TApiEnvelope; +export type TPatchAlarmSettingsResp = TApiEnvelope; diff --git a/src/utils/date.ts b/src/utils/date.ts new file mode 100644 index 0000000..66e49ae --- /dev/null +++ b/src/utils/date.ts @@ -0,0 +1,11 @@ +export const formatDateDot = (dateStr?: string): string => { + if (!dateStr) return ''; + const date = new Date(dateStr); + if (isNaN(date.getTime())) return ''; + return date.toLocaleDateString('ko-KR', { + year: 'numeric', + month: '2-digit', + day: '2-digit', + timeZone: 'Asia/Seoul', + }); +}; diff --git a/yarn.lock b/yarn.lock index 809543e..4e4a7bc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -519,10 +519,10 @@ "@eslint/core" "^0.15.1" levn "^0.4.1" -"@firebase/ai@2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@firebase/ai/-/ai-2.0.0.tgz#c0324b48c2451b28d621c96517e056ab67576dc4" - integrity sha512-N/aSHjqOpU+KkYU3piMkbcuxzvqsOvxflLUXBAkYAPAz8wjE2Ye3BQDgKHEYuhMmEWqj6LFgEBUN8wwc6dfMTw== +"@firebase/ai@2.1.0": + version "2.1.0" + resolved "https://registry.npmjs.org/@firebase/ai/-/ai-2.1.0.tgz" + integrity sha512-4HvFr4YIzNFh0MowJLahOjJDezYSTjQar0XYVu/sAycoxQ+kBsfXuTPRLVXCYfMR5oNwQgYe4Q2gAOYKKqsOyA== dependencies: "@firebase/app-check-interop-types" "0.3.3" "@firebase/component" "0.7.0" @@ -532,7 +532,7 @@ "@firebase/analytics-compat@0.2.24": version "0.2.24" - resolved "https://registry.yarnpkg.com/@firebase/analytics-compat/-/analytics-compat-0.2.24.tgz#806c34ddd5c4869006eead08bfde575972d73ce2" + resolved "https://registry.npmjs.org/@firebase/analytics-compat/-/analytics-compat-0.2.24.tgz" integrity sha512-jE+kJnPG86XSqGQGhXXYt1tpTbCTED8OQJ/PQ90SEw14CuxRxx/H+lFbWA1rlFtFSsTCptAJtgyRBwr/f00vsw== dependencies: "@firebase/analytics" "0.10.18" @@ -543,12 +543,12 @@ "@firebase/analytics-types@0.8.3": version "0.8.3" - resolved "https://registry.yarnpkg.com/@firebase/analytics-types/-/analytics-types-0.8.3.tgz#d08cd39a6209693ca2039ba7a81570dfa6c1518f" + resolved "https://registry.npmjs.org/@firebase/analytics-types/-/analytics-types-0.8.3.tgz" integrity sha512-VrIp/d8iq2g501qO46uGz3hjbDb8xzYMrbu8Tp0ovzIzrvJZ2fvmj649gTjge/b7cCCcjT0H37g1gVtlNhnkbg== "@firebase/analytics@0.10.18": version "0.10.18" - resolved "https://registry.yarnpkg.com/@firebase/analytics/-/analytics-0.10.18.tgz#930d43504a02fe0128a8d82f8c5361911b0dbd04" + resolved "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.10.18.tgz" integrity sha512-iN7IgLvM06iFk8BeFoWqvVpRFW3Z70f+Qe2PfCJ7vPIgLPjHXDE774DhCT5Y2/ZU/ZbXPDPD60x/XPWEoZLNdg== dependencies: "@firebase/component" "0.7.0" @@ -559,7 +559,7 @@ "@firebase/app-check-compat@0.4.0": version "0.4.0" - resolved "https://registry.yarnpkg.com/@firebase/app-check-compat/-/app-check-compat-0.4.0.tgz#94ac0cf9f66cab1d81a7b14e0c151dcc2684bc95" + resolved "https://registry.npmjs.org/@firebase/app-check-compat/-/app-check-compat-0.4.0.tgz" integrity sha512-UfK2Q8RJNjYM/8MFORltZRG9lJj11k0nW84rrffiKvcJxLf1jf6IEjCIkCamykHE73C6BwqhVfhIBs69GXQV0g== dependencies: "@firebase/app-check" "0.11.0" @@ -571,17 +571,17 @@ "@firebase/app-check-interop-types@0.3.3": version "0.3.3" - resolved "https://registry.yarnpkg.com/@firebase/app-check-interop-types/-/app-check-interop-types-0.3.3.tgz#ed9c4a4f48d1395ef378f007476db3940aa5351a" + resolved "https://registry.npmjs.org/@firebase/app-check-interop-types/-/app-check-interop-types-0.3.3.tgz" integrity sha512-gAlxfPLT2j8bTI/qfe3ahl2I2YcBQ8cFIBdhAQA4I2f3TndcO+22YizyGYuttLHPQEpWkhmpFW60VCFEPg4g5A== "@firebase/app-check-types@0.5.3": version "0.5.3" - resolved "https://registry.yarnpkg.com/@firebase/app-check-types/-/app-check-types-0.5.3.tgz#38ba954acf4bffe451581a32fffa20337f11d8e5" + resolved "https://registry.npmjs.org/@firebase/app-check-types/-/app-check-types-0.5.3.tgz" integrity sha512-hyl5rKSj0QmwPdsAxrI5x1otDlByQ7bvNvVt8G/XPO2CSwE++rmSVf3VEhaeOR4J8ZFaF0Z0NDSmLejPweZ3ng== "@firebase/app-check@0.11.0": version "0.11.0" - resolved "https://registry.yarnpkg.com/@firebase/app-check/-/app-check-0.11.0.tgz#a7e1d1e3f5ae36eabed1455db937114fe869ce8f" + resolved "https://registry.npmjs.org/@firebase/app-check/-/app-check-0.11.0.tgz" integrity sha512-XAvALQayUMBJo58U/rxW02IhsesaxxfWVmVkauZvGEz3vOAjMEQnzFlyblqkc2iAaO82uJ2ZVyZv9XzPfxjJ6w== dependencies: "@firebase/component" "0.7.0" @@ -589,12 +589,12 @@ "@firebase/util" "1.13.0" tslib "^2.1.0" -"@firebase/app-compat@0.5.0": - version "0.5.0" - resolved "https://registry.yarnpkg.com/@firebase/app-compat/-/app-compat-0.5.0.tgz#7463e9fb8d84706787773d3660accedff4479d05" - integrity sha512-nUnNpOeRj0KZzVzHsyuyrmZKKHfykZ8mn40FtG28DeSTWeM5b/2P242Va4bmQpJsy5y32vfv50+jvdckrpzy7Q== +"@firebase/app-compat@0.5.1": + version "0.5.1" + resolved "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.5.1.tgz" + integrity sha512-BEy1L6Ufd85ZSP79HDIv0//T9p7d5Bepwy+2mKYkgdXBGKTbFm2e2KxyF1nq4zSQ6RRBxWi0IY0zFVmoBTZlUA== dependencies: - "@firebase/app" "0.14.0" + "@firebase/app" "0.14.1" "@firebase/component" "0.7.0" "@firebase/logger" "0.5.0" "@firebase/util" "1.13.0" @@ -602,13 +602,13 @@ "@firebase/app-types@0.9.3": version "0.9.3" - resolved "https://registry.yarnpkg.com/@firebase/app-types/-/app-types-0.9.3.tgz#8408219eae9b1fb74f86c24e7150a148460414ad" + resolved "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.3.tgz" integrity sha512-kRVpIl4vVGJ4baogMDINbyrIOtOxqhkZQg4jTq3l8Lw6WSk0xfpEYzezFu+Kl4ve4fbPl79dvwRtaFqAC/ucCw== -"@firebase/app@0.14.0": - version "0.14.0" - resolved "https://registry.yarnpkg.com/@firebase/app/-/app-0.14.0.tgz#7132e96df95e85783922fd09750f08ab2ebfd699" - integrity sha512-APIAeKvRNFWKJLjIL8wLDjh7u8g6ZjaeVmItyqSjCdEkJj14UuVlus74D8ofsOMWh45HEwxwkd96GYbi+CImEg== +"@firebase/app@0.14.1": + version "0.14.1" + resolved "https://registry.npmjs.org/@firebase/app/-/app-0.14.1.tgz" + integrity sha512-jxTrDbxnGoX7cGz7aP9E7v9iKvBbQfZ8Gz4TH3SfrrkcyIojJM3+hJnlbGnGxHrABts844AxRcg00arMZEyA6Q== dependencies: "@firebase/component" "0.7.0" "@firebase/logger" "0.5.0" @@ -618,7 +618,7 @@ "@firebase/auth-compat@0.6.0": version "0.6.0" - resolved "https://registry.yarnpkg.com/@firebase/auth-compat/-/auth-compat-0.6.0.tgz#1464ea6049b2ad0aae83b4fdcd5e5e5aba6b1c50" + resolved "https://registry.npmjs.org/@firebase/auth-compat/-/auth-compat-0.6.0.tgz" integrity sha512-J0lGSxXlG/lYVi45wbpPhcWiWUMXevY4fvLZsN1GHh+po7TZVng+figdHBVhFheaiipU8HZyc7ljw1jNojM2nw== dependencies: "@firebase/auth" "1.11.0" @@ -629,17 +629,17 @@ "@firebase/auth-interop-types@0.2.4": version "0.2.4" - resolved "https://registry.yarnpkg.com/@firebase/auth-interop-types/-/auth-interop-types-0.2.4.tgz#176a08686b0685596ff03d7879b7e4115af53de0" + resolved "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.2.4.tgz" integrity sha512-JPgcXKCuO+CWqGDnigBtvo09HeBs5u/Ktc2GaFj2m01hLarbxthLNm7Fk8iOP1aqAtXV+fnnGj7U28xmk7IwVA== "@firebase/auth-types@0.13.0": version "0.13.0" - resolved "https://registry.yarnpkg.com/@firebase/auth-types/-/auth-types-0.13.0.tgz#ae6e0015e3bd4bfe18edd0942b48a0a118a098d9" + resolved "https://registry.npmjs.org/@firebase/auth-types/-/auth-types-0.13.0.tgz" integrity sha512-S/PuIjni0AQRLF+l9ck0YpsMOdE8GO2KU6ubmBB7P+7TJUCQDa3R1dlgYm9UzGbbePMZsp0xzB93f2b/CgxMOg== "@firebase/auth@1.11.0": version "1.11.0" - resolved "https://registry.yarnpkg.com/@firebase/auth/-/auth-1.11.0.tgz#81a4f77b16d97c502e493b2a14a97443e243a2a0" + resolved "https://registry.npmjs.org/@firebase/auth/-/auth-1.11.0.tgz" integrity sha512-5j7+ua93X+IRcJ1oMDTClTo85l7Xe40WSkoJ+shzPrX7OISlVWLdE1mKC57PSD+/LfAbdhJmvKixINBw2ESK6w== dependencies: "@firebase/component" "0.7.0" @@ -649,7 +649,7 @@ "@firebase/component@0.7.0": version "0.7.0" - resolved "https://registry.yarnpkg.com/@firebase/component/-/component-0.7.0.tgz#3736644fdb6d3572dceae7fdc1c35a8bd3819adc" + resolved "https://registry.npmjs.org/@firebase/component/-/component-0.7.0.tgz" integrity sha512-wR9En2A+WESUHexjmRHkqtaVH94WLNKt6rmeqZhSLBybg4Wyf0Umk04SZsS6sBq4102ZsDBFwoqMqJYj2IoDSg== dependencies: "@firebase/util" "1.13.0" @@ -657,7 +657,7 @@ "@firebase/data-connect@0.3.11": version "0.3.11" - resolved "https://registry.yarnpkg.com/@firebase/data-connect/-/data-connect-0.3.11.tgz#60a7a9649e4aedd005546032466ef9abc0a544c1" + resolved "https://registry.npmjs.org/@firebase/data-connect/-/data-connect-0.3.11.tgz" integrity sha512-G258eLzAD6im9Bsw+Qm1Z+P4x0PGNQ45yeUuuqe5M9B1rn0RJvvsQCRHXgE52Z+n9+WX1OJd/crcuunvOGc7Vw== dependencies: "@firebase/auth-interop-types" "0.2.4" @@ -668,7 +668,7 @@ "@firebase/database-compat@2.1.0": version "2.1.0" - resolved "https://registry.yarnpkg.com/@firebase/database-compat/-/database-compat-2.1.0.tgz#c64488d741c6da2ed8dcf02f2e433089dae2f590" + resolved "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-2.1.0.tgz" integrity sha512-8nYc43RqxScsePVd1qe1xxvWNf0OBnbwHxmXJ7MHSuuTVYFO3eLyLW3PiCKJ9fHnmIz4p4LbieXwz+qtr9PZDg== dependencies: "@firebase/component" "0.7.0" @@ -680,7 +680,7 @@ "@firebase/database-types@1.0.16": version "1.0.16" - resolved "https://registry.yarnpkg.com/@firebase/database-types/-/database-types-1.0.16.tgz#262f54b8dbebbc46259757b3ba384224fb2ede48" + resolved "https://registry.npmjs.org/@firebase/database-types/-/database-types-1.0.16.tgz" integrity sha512-xkQLQfU5De7+SPhEGAXFBnDryUWhhlFXelEg2YeZOQMCdoe7dL64DDAd77SQsR+6uoXIZY5MB4y/inCs4GTfcw== dependencies: "@firebase/app-types" "0.9.3" @@ -688,7 +688,7 @@ "@firebase/database@1.1.0": version "1.1.0" - resolved "https://registry.yarnpkg.com/@firebase/database/-/database-1.1.0.tgz#bdf60f1605079a87ceb2b5e30d90846e0bde294b" + resolved "https://registry.npmjs.org/@firebase/database/-/database-1.1.0.tgz" integrity sha512-gM6MJFae3pTyNLoc9VcJNuaUDej0ctdjn3cVtILo3D5lpp0dmUHHLFN/pUKe7ImyeB1KAvRlEYxvIHNF04Filg== dependencies: "@firebase/app-check-interop-types" "0.3.3" @@ -701,7 +701,7 @@ "@firebase/firestore-compat@0.4.0": version "0.4.0" - resolved "https://registry.yarnpkg.com/@firebase/firestore-compat/-/firestore-compat-0.4.0.tgz#911cf956e489fa8335ed2f2ace14a74909bcd94d" + resolved "https://registry.npmjs.org/@firebase/firestore-compat/-/firestore-compat-0.4.0.tgz" integrity sha512-4O7v4VFeSEwAZtLjsaj33YrMHMRjplOIYC2CiYsF6o/MboOhrhe01VrTt8iY9Y5EwjRHuRz4pS6jMBT8LfQYJA== dependencies: "@firebase/component" "0.7.0" @@ -712,12 +712,12 @@ "@firebase/firestore-types@3.0.3": version "3.0.3" - resolved "https://registry.yarnpkg.com/@firebase/firestore-types/-/firestore-types-3.0.3.tgz#7d0c3dd8850c0193d8f5ee0cc8f11961407742c1" + resolved "https://registry.npmjs.org/@firebase/firestore-types/-/firestore-types-3.0.3.tgz" integrity sha512-hD2jGdiWRxB/eZWF89xcK9gF8wvENDJkzpVFb4aGkzfEaKxVRD1kjz1t1Wj8VZEp2LCB53Yx1zD8mrhQu87R6Q== "@firebase/firestore@4.9.0": version "4.9.0" - resolved "https://registry.yarnpkg.com/@firebase/firestore/-/firestore-4.9.0.tgz#753d73c002b4c0ae639437b049ef0086791a0cf3" + resolved "https://registry.npmjs.org/@firebase/firestore/-/firestore-4.9.0.tgz" integrity sha512-5zl0+/h1GvlCSLt06RMwqFsd7uqRtnNZt4sW99k2rKRd6k/ECObIWlEnvthm2cuOSnUmwZknFqtmd1qyYSLUuQ== dependencies: "@firebase/component" "0.7.0" @@ -730,7 +730,7 @@ "@firebase/functions-compat@0.4.0": version "0.4.0" - resolved "https://registry.yarnpkg.com/@firebase/functions-compat/-/functions-compat-0.4.0.tgz#aa63dea248053e9c06904605704662ea550e50ed" + resolved "https://registry.npmjs.org/@firebase/functions-compat/-/functions-compat-0.4.0.tgz" integrity sha512-VPgtvoGFywWbQqtvgJnVWIDFSHV1WE6Hmyi5EGI+P+56EskiGkmnw6lEqc/MEUfGpPGdvmc4I9XMU81uj766/g== dependencies: "@firebase/component" "0.7.0" @@ -741,12 +741,12 @@ "@firebase/functions-types@0.6.3": version "0.6.3" - resolved "https://registry.yarnpkg.com/@firebase/functions-types/-/functions-types-0.6.3.tgz#f5faf770248b13f45d256f614230da6a11bfb654" + resolved "https://registry.npmjs.org/@firebase/functions-types/-/functions-types-0.6.3.tgz" integrity sha512-EZoDKQLUHFKNx6VLipQwrSMh01A1SaL3Wg6Hpi//x6/fJ6Ee4hrAeswK99I5Ht8roiniKHw4iO0B1Oxj5I4plg== "@firebase/functions@0.13.0": version "0.13.0" - resolved "https://registry.yarnpkg.com/@firebase/functions/-/functions-0.13.0.tgz#91685a59589b3a00f6c48faf383acd28a35800c2" + resolved "https://registry.npmjs.org/@firebase/functions/-/functions-0.13.0.tgz" integrity sha512-2/LH5xIbD8aaLOWSFHAwwAybgSzHIM0dB5oVOL0zZnxFG1LctX2bc1NIAaPk1T+Zo9aVkLKUlB5fTXTkVUQprQ== dependencies: "@firebase/app-check-interop-types" "0.3.3" @@ -758,7 +758,7 @@ "@firebase/installations-compat@0.2.19": version "0.2.19" - resolved "https://registry.yarnpkg.com/@firebase/installations-compat/-/installations-compat-0.2.19.tgz#4bc57c8c57d241eeca95900ff3033d6ec3dbcc7c" + resolved "https://registry.npmjs.org/@firebase/installations-compat/-/installations-compat-0.2.19.tgz" integrity sha512-khfzIY3EI5LePePo7vT19/VEIH1E3iYsHknI/6ek9T8QCozAZshWT9CjlwOzZrKvTHMeNcbpo/VSOSIWDSjWdQ== dependencies: "@firebase/component" "0.7.0" @@ -769,12 +769,12 @@ "@firebase/installations-types@0.5.3": version "0.5.3" - resolved "https://registry.yarnpkg.com/@firebase/installations-types/-/installations-types-0.5.3.tgz#cac8a14dd49f09174da9df8ae453f9b359c3ef2f" + resolved "https://registry.npmjs.org/@firebase/installations-types/-/installations-types-0.5.3.tgz" integrity sha512-2FJI7gkLqIE0iYsNQ1P751lO3hER+Umykel+TkLwHj6plzWVxqvfclPUZhcKFVQObqloEBTmpi2Ozn7EkCABAA== "@firebase/installations@0.6.19": version "0.6.19" - resolved "https://registry.yarnpkg.com/@firebase/installations/-/installations-0.6.19.tgz#93c569321f6fb399f4f1a197efc0053ce6452c7c" + resolved "https://registry.npmjs.org/@firebase/installations/-/installations-0.6.19.tgz" integrity sha512-nGDmiwKLI1lerhwfwSHvMR9RZuIH5/8E3kgUWnVRqqL7kGVSktjLTWEMva7oh5yxQ3zXfIlIwJwMcaM5bK5j8Q== dependencies: "@firebase/component" "0.7.0" @@ -784,14 +784,14 @@ "@firebase/logger@0.5.0": version "0.5.0" - resolved "https://registry.yarnpkg.com/@firebase/logger/-/logger-0.5.0.tgz#a9e55b1c669a0983dc67127fa4a5964ce8ed5e1b" + resolved "https://registry.npmjs.org/@firebase/logger/-/logger-0.5.0.tgz" integrity sha512-cGskaAvkrnh42b3BA3doDWeBmuHFO/Mx5A83rbRDYakPjO9bJtRL3dX7javzc2Rr/JHZf4HlterTW2lUkfeN4g== dependencies: tslib "^2.1.0" "@firebase/messaging-compat@0.2.23": version "0.2.23" - resolved "https://registry.yarnpkg.com/@firebase/messaging-compat/-/messaging-compat-0.2.23.tgz#2ca6b36ea238fae4dff53bf85442c4a2af516224" + resolved "https://registry.npmjs.org/@firebase/messaging-compat/-/messaging-compat-0.2.23.tgz" integrity sha512-SN857v/kBUvlQ9X/UjAqBoQ2FEaL1ZozpnmL1ByTe57iXkmnVVFm9KqAsTfmf+OEwWI4kJJe9NObtN/w22lUgg== dependencies: "@firebase/component" "0.7.0" @@ -801,12 +801,12 @@ "@firebase/messaging-interop-types@0.2.3": version "0.2.3" - resolved "https://registry.yarnpkg.com/@firebase/messaging-interop-types/-/messaging-interop-types-0.2.3.tgz#e647c9cd1beecfe6a6e82018a6eec37555e4da3e" + resolved "https://registry.npmjs.org/@firebase/messaging-interop-types/-/messaging-interop-types-0.2.3.tgz" integrity sha512-xfzFaJpzcmtDjycpDeCUj0Ge10ATFi/VHVIvEEjDNc3hodVBQADZ7BWQU7CuFpjSHE+eLuBI13z5F/9xOoGX8Q== "@firebase/messaging@0.12.23": version "0.12.23" - resolved "https://registry.yarnpkg.com/@firebase/messaging/-/messaging-0.12.23.tgz#71f932a521ac39d9f036175672e37897531010eb" + resolved "https://registry.npmjs.org/@firebase/messaging/-/messaging-0.12.23.tgz" integrity sha512-cfuzv47XxqW4HH/OcR5rM+AlQd1xL/VhuaeW/wzMW1LFrsFcTn0GND/hak1vkQc2th8UisBcrkVcQAnOnKwYxg== dependencies: "@firebase/component" "0.7.0" @@ -816,27 +816,27 @@ idb "7.1.1" tslib "^2.1.0" -"@firebase/performance-compat@0.2.21": - version "0.2.21" - resolved "https://registry.yarnpkg.com/@firebase/performance-compat/-/performance-compat-0.2.21.tgz#60f04ecb5ff98b5d84a7a932f2e8294aae711c72" - integrity sha512-OQfYRsIQiEf9ez1SOMLb5TRevBHNIyA2x1GI1H10lZ432W96AK5r4LTM+SNApg84dxOuHt6RWSQWY7TPWffKXg== +"@firebase/performance-compat@0.2.22": + version "0.2.22" + resolved "https://registry.npmjs.org/@firebase/performance-compat/-/performance-compat-0.2.22.tgz" + integrity sha512-xLKxaSAl/FVi10wDX/CHIYEUP13jXUjinL+UaNXT9ByIvxII5Ne5150mx6IgM8G6Q3V+sPiw9C8/kygkyHUVxg== dependencies: "@firebase/component" "0.7.0" "@firebase/logger" "0.5.0" - "@firebase/performance" "0.7.8" + "@firebase/performance" "0.7.9" "@firebase/performance-types" "0.2.3" "@firebase/util" "1.13.0" tslib "^2.1.0" "@firebase/performance-types@0.2.3": version "0.2.3" - resolved "https://registry.yarnpkg.com/@firebase/performance-types/-/performance-types-0.2.3.tgz#5ce64e90fa20ab5561f8b62a305010cf9fab86fb" + resolved "https://registry.npmjs.org/@firebase/performance-types/-/performance-types-0.2.3.tgz" integrity sha512-IgkyTz6QZVPAq8GSkLYJvwSLr3LS9+V6vNPQr0x4YozZJiLF5jYixj0amDtATf1X0EtYHqoPO48a9ija8GocxQ== -"@firebase/performance@0.7.8": - version "0.7.8" - resolved "https://registry.yarnpkg.com/@firebase/performance/-/performance-0.7.8.tgz#a3e5ff36070e0f26e59f84fcd8faf7a8ee77c677" - integrity sha512-k6xfNM/CdTl4RaV4gT/lH53NU+wP33JiN0pUeNBzGVNvfXZ3HbCkoISE3M/XaiOwHgded1l6XfLHa4zHgm0Wyg== +"@firebase/performance@0.7.9": + version "0.7.9" + resolved "https://registry.npmjs.org/@firebase/performance/-/performance-0.7.9.tgz" + integrity sha512-UzybENl1EdM2I1sjYm74xGt/0JzRnU/0VmfMAKo2LSpHJzaj77FCLZXmYQ4oOuE+Pxtt8Wy2BVJEENiZkaZAzQ== dependencies: "@firebase/component" "0.7.0" "@firebase/installations" "0.6.19" @@ -847,7 +847,7 @@ "@firebase/remote-config-compat@0.2.19": version "0.2.19" - resolved "https://registry.yarnpkg.com/@firebase/remote-config-compat/-/remote-config-compat-0.2.19.tgz#10cfd804f65c5ca80a4d40994bc853ca6d1f7307" + resolved "https://registry.npmjs.org/@firebase/remote-config-compat/-/remote-config-compat-0.2.19.tgz" integrity sha512-y7PZAb0l5+5oIgLJr88TNSelxuASGlXyAKj+3pUc4fDuRIdPNBoONMHaIUa9rlffBR5dErmaD2wUBJ7Z1a513Q== dependencies: "@firebase/component" "0.7.0" @@ -859,12 +859,12 @@ "@firebase/remote-config-types@0.4.0": version "0.4.0" - resolved "https://registry.yarnpkg.com/@firebase/remote-config-types/-/remote-config-types-0.4.0.tgz#91b9a836d5ca30ced68c1516163b281fbb544537" + resolved "https://registry.npmjs.org/@firebase/remote-config-types/-/remote-config-types-0.4.0.tgz" integrity sha512-7p3mRE/ldCNYt8fmWMQ/MSGRmXYlJ15Rvs9Rk17t8p0WwZDbeK7eRmoI1tvCPaDzn9Oqh+yD6Lw+sGLsLg4kKg== "@firebase/remote-config@0.6.6": version "0.6.6" - resolved "https://registry.yarnpkg.com/@firebase/remote-config/-/remote-config-0.6.6.tgz#50eae3d2d71791d76fb6521971bb646d6628805e" + resolved "https://registry.npmjs.org/@firebase/remote-config/-/remote-config-0.6.6.tgz" integrity sha512-Yelp5xd8hM4NO1G1SuWrIk4h5K42mNwC98eWZ9YLVu6Z0S6hFk1mxotAdCRmH2luH8FASlYgLLq6OQLZ4nbnCA== dependencies: "@firebase/component" "0.7.0" @@ -875,7 +875,7 @@ "@firebase/storage-compat@0.4.0": version "0.4.0" - resolved "https://registry.yarnpkg.com/@firebase/storage-compat/-/storage-compat-0.4.0.tgz#a09bd33c262123e7e3ed0cd590b4c6e2ce4a8902" + resolved "https://registry.npmjs.org/@firebase/storage-compat/-/storage-compat-0.4.0.tgz" integrity sha512-vDzhgGczr1OfcOy285YAPur5pWDEvD67w4thyeCUh6Ys0izN9fNYtA1MJERmNBfqjqu0lg0FM5GLbw0Il21M+g== dependencies: "@firebase/component" "0.7.0" @@ -886,12 +886,12 @@ "@firebase/storage-types@0.8.3": version "0.8.3" - resolved "https://registry.yarnpkg.com/@firebase/storage-types/-/storage-types-0.8.3.tgz#2531ef593a3452fc12c59117195d6485c6632d3d" + resolved "https://registry.npmjs.org/@firebase/storage-types/-/storage-types-0.8.3.tgz" integrity sha512-+Muk7g9uwngTpd8xn9OdF/D48uiQ7I1Fae7ULsWPuKoCH3HU7bfFPhxtJYzyhjdniowhuDpQcfPmuNRAqZEfvg== "@firebase/storage@0.14.0": version "0.14.0" - resolved "https://registry.yarnpkg.com/@firebase/storage/-/storage-0.14.0.tgz#01acb97d413ada7c91de860fb260623468baa25d" + resolved "https://registry.npmjs.org/@firebase/storage/-/storage-0.14.0.tgz" integrity sha512-xWWbb15o6/pWEw8H01UQ1dC5U3rf8QTAzOChYyCpafV6Xki7KVp3Yaw2nSklUwHEziSWE9KoZJS7iYeyqWnYFA== dependencies: "@firebase/component" "0.7.0" @@ -900,19 +900,19 @@ "@firebase/util@1.13.0": version "1.13.0" - resolved "https://registry.yarnpkg.com/@firebase/util/-/util-1.13.0.tgz#2e9e7569722a1e3fc86b1b4076d5cbfbfa7265d6" + resolved "https://registry.npmjs.org/@firebase/util/-/util-1.13.0.tgz" integrity sha512-0AZUyYUfpMNcztR5l09izHwXkZpghLgCUaAGjtMwXnCg3bj4ml5VgiwqOMOxJ+Nw4qN/zJAaOQBcJ7KGkWStqQ== dependencies: tslib "^2.1.0" "@firebase/webchannel-wrapper@1.0.4": version "1.0.4" - resolved "https://registry.yarnpkg.com/@firebase/webchannel-wrapper/-/webchannel-wrapper-1.0.4.tgz#9d5b4b6f23309260a12856cb574c5e64e6c133f7" + resolved "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-1.0.4.tgz" integrity sha512-6m8+P+dE/RPl4OPzjTxcTbQ0rGeRyeTvAi9KwIffBVCiAMKrfXfLZaqD1F+m8t4B5/Q5aHsMozOgirkH1F5oMQ== "@grpc/grpc-js@~1.9.0": version "1.9.15" - resolved "https://registry.yarnpkg.com/@grpc/grpc-js/-/grpc-js-1.9.15.tgz#433d7ac19b1754af690ea650ab72190bd700739b" + resolved "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.9.15.tgz" integrity sha512-nqE7Hc0AzI+euzUwDAy0aY5hCp10r734gMGRdU+qOPX0XSceI2ULrcXB5U2xSc5VkWwalCj4M7GzCAygZl2KoQ== dependencies: "@grpc/proto-loader" "^0.7.8" @@ -920,7 +920,7 @@ "@grpc/proto-loader@^0.7.8": version "0.7.15" - resolved "https://registry.yarnpkg.com/@grpc/proto-loader/-/proto-loader-0.7.15.tgz#4cdfbf35a35461fc843abe8b9e2c0770b5095e60" + resolved "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.15.tgz" integrity sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ== dependencies: lodash.camelcase "^4.3.0" @@ -1049,27 +1049,27 @@ "@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": version "1.1.2" - resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" + resolved "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz" integrity sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ== "@protobufjs/base64@^1.1.2": version "1.1.2" - resolved "https://registry.yarnpkg.com/@protobufjs/base64/-/base64-1.1.2.tgz#4c85730e59b9a1f1f349047dbf24296034bb2735" + resolved "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz" integrity sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg== "@protobufjs/codegen@^2.0.4": version "2.0.4" - resolved "https://registry.yarnpkg.com/@protobufjs/codegen/-/codegen-2.0.4.tgz#7ef37f0d010fb028ad1ad59722e506d9262815cb" + resolved "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz" integrity sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg== "@protobufjs/eventemitter@^1.1.0": version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz#355cbc98bafad5978f9ed095f397621f1d066b70" + resolved "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz" integrity sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q== "@protobufjs/fetch@^1.1.0": version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/fetch/-/fetch-1.1.0.tgz#ba99fb598614af65700c1619ff06d454b0d84c45" + resolved "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz" integrity sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ== dependencies: "@protobufjs/aspromise" "^1.1.1" @@ -1077,27 +1077,27 @@ "@protobufjs/float@^1.0.2": version "1.0.2" - resolved "https://registry.yarnpkg.com/@protobufjs/float/-/float-1.0.2.tgz#5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1" + resolved "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz" integrity sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ== "@protobufjs/inquire@^1.1.0": version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/inquire/-/inquire-1.1.0.tgz#ff200e3e7cf2429e2dcafc1140828e8cc638f089" + resolved "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz" integrity sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q== "@protobufjs/path@^1.1.2": version "1.1.2" - resolved "https://registry.yarnpkg.com/@protobufjs/path/-/path-1.1.2.tgz#6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d" + resolved "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz" integrity sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA== "@protobufjs/pool@^1.1.0": version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/pool/-/pool-1.1.0.tgz#09fd15f2d6d3abfa9b65bc366506d6ad7846ff54" + resolved "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz" integrity sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw== "@protobufjs/utf8@^1.1.0": version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" + resolved "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz" integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw== "@rolldown/pluginutils@1.0.0-beta.11": @@ -1570,20 +1570,17 @@ resolved "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.20.tgz" integrity sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA== +<<<<<<< HEAD "@types/node@*": +======= +"@types/node@*", "@types/node@>=12.12.47", "@types/node@>=13.7.0": +>>>>>>> 761a53f (feat: 설정API 연동 미완성) version "24.0.7" resolved "https://registry.npmjs.org/@types/node/-/node-24.0.7.tgz" integrity sha512-YIEUUr4yf8q8oQoXPpSlnvKNVKDQlPMWrmOcgzoduo7kvA2UF0/BwJ/eMKFTiTtkNL17I0M6Xe2tvwFU7be6iw== dependencies: undici-types "~7.8.0" -"@types/node@>=12.12.47", "@types/node@>=13.7.0": - version "24.2.0" - resolved "https://registry.yarnpkg.com/@types/node/-/node-24.2.0.tgz#cde712f88c5190006d6b069232582ecd1f94a760" - integrity sha512-3xyG3pMCq3oYCNg7/ZP+E1ooTaGB4cG8JWRsqqOYQdbWNY4zbaV0Ennrd7stjiJEFZCaybcIgpTjJWHRfBSIDw== - dependencies: - undici-types "~7.10.0" - "@types/react-dom@^19.1.2": version "19.1.6" resolved "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.6.tgz" @@ -2788,7 +2785,7 @@ fastq@^1.6.0: faye-websocket@0.11.4: version "0.11.4" - resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.4.tgz#7f0d9275cfdd86a1c963dc8b65fcc451edcbb1da" + resolved "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz" integrity sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g== dependencies: websocket-driver ">=0.5.1" @@ -2830,17 +2827,17 @@ find-up@^7.0.0: unicorn-magic "^0.1.0" firebase@^12.0.0: - version "12.0.0" - resolved "https://registry.yarnpkg.com/firebase/-/firebase-12.0.0.tgz#436ee1b97b64265f3e7d3b6b0ce4419cc9fe5ae9" - integrity sha512-KV+OrMJpi2uXlqL2zaCcXb7YuQbY/gMIWT1hf8hKeTW1bSumWaHT5qfmn0WTpHwKQa3QEVOtZR2ta9EchcmYuw== + version "12.1.0" + resolved "https://registry.npmjs.org/firebase/-/firebase-12.1.0.tgz" + integrity sha512-oZucxvfWKuAW4eHHRqGKzC43fLiPqPwHYBHPRNsnkgonqYaq0VurYgqgBosRlEulW+TWja/5Tpo2FpUU+QrfEQ== dependencies: - "@firebase/ai" "2.0.0" + "@firebase/ai" "2.1.0" "@firebase/analytics" "0.10.18" "@firebase/analytics-compat" "0.2.24" - "@firebase/app" "0.14.0" + "@firebase/app" "0.14.1" "@firebase/app-check" "0.11.0" "@firebase/app-check-compat" "0.4.0" - "@firebase/app-compat" "0.5.0" + "@firebase/app-compat" "0.5.1" "@firebase/app-types" "0.9.3" "@firebase/auth" "1.11.0" "@firebase/auth-compat" "0.6.0" @@ -2855,8 +2852,8 @@ firebase@^12.0.0: "@firebase/installations-compat" "0.2.19" "@firebase/messaging" "0.12.23" "@firebase/messaging-compat" "0.2.23" - "@firebase/performance" "0.7.8" - "@firebase/performance-compat" "0.2.21" + "@firebase/performance" "0.7.9" + "@firebase/performance-compat" "0.2.22" "@firebase/remote-config" "0.6.6" "@firebase/remote-config-compat" "0.2.19" "@firebase/storage" "0.14.0" @@ -3092,7 +3089,7 @@ hasown@^2.0.2: http-parser-js@>=0.5.1: version "0.5.10" - resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.10.tgz#b3277bd6d7ed5588e20ea73bf724fcbe44609075" + resolved "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.10.tgz" integrity sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA== husky@^9.1.7: @@ -3102,7 +3099,7 @@ husky@^9.1.7: idb@7.1.1: version "7.1.1" - resolved "https://registry.yarnpkg.com/idb/-/idb-7.1.1.tgz#d910ded866d32c7ced9befc5bfdf36f572ced72b" + resolved "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz" integrity sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ== ignore@^5.2.0: @@ -3679,7 +3676,7 @@ log-update@^6.1.0: long@^5.0.0: version "5.3.2" - resolved "https://registry.yarnpkg.com/long/-/long-5.3.2.tgz#1d84463095999262d7d7b7f8bfd4a8cc55167f83" + resolved "https://registry.npmjs.org/long/-/long-5.3.2.tgz" integrity sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA== loose-envify@^1.4.0: @@ -4068,7 +4065,7 @@ prop-types@^15.8.1: protobufjs@^7.2.5: version "7.5.3" - resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.5.3.tgz#13f95a9e3c84669995ec3652db2ac2fb00b89363" + resolved "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.3.tgz" integrity sha512-sildjKwVqOI2kmFDiXQ6aEB0fjYTafpEvIBs8tOR8qI4spuL9OPROLVu2qZqi/xgCfsHIwVqlaF8JBjWFHnKbw== dependencies: "@protobufjs/aspromise" "^1.1.2" @@ -4118,7 +4115,7 @@ react-hook-form@^7.60.0: react-intersection-observer@^9.16.0: version "9.16.0" - resolved "https://registry.yarnpkg.com/react-intersection-observer/-/react-intersection-observer-9.16.0.tgz#7376d54edc47293300961010844d53b273ee0fb9" + resolved "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-9.16.0.tgz" integrity sha512-w9nJSEp+DrW9KmQmeWHQyfaP6b03v+TdXynaoA964Wxt7mdR3An11z4NNCQgL4gKSK7y1ver2Fq+JKH6CWEzUA== react-is@^16.13.1: @@ -4143,7 +4140,7 @@ react-router@7.6.3: react-spinners@^0.17.0: version "0.17.0" - resolved "https://registry.yarnpkg.com/react-spinners/-/react-spinners-0.17.0.tgz#d518913e2192afaae76c5b781929ea3bd7b1910e" + resolved "https://registry.npmjs.org/react-spinners/-/react-spinners-0.17.0.tgz" integrity sha512-L/8HTylaBmIWwQzIjMq+0vyaRXuoAevzWoD35wKpNTxxtYXWZp+xtgkfD7Y4WItuX0YvdxMPU79+7VhhmbmuTQ== react@^19.1.0: @@ -4282,7 +4279,7 @@ safe-array-concat@^1.1.3: safe-buffer@>=5.1.0: version "5.2.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== safe-push-apply@^1.0.0: @@ -4744,11 +4741,6 @@ unbox-primitive@^1.1.0: has-symbols "^1.1.0" which-boxed-primitive "^1.1.1" -undici-types@~7.10.0: - version "7.10.0" - resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.10.0.tgz#4ac2e058ce56b462b056e629cc6a02393d3ff350" - integrity sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag== - undici-types@~7.8.0: version "7.8.0" resolved "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz" @@ -4806,12 +4798,12 @@ vite@^7.0.2: web-vitals@^4.2.4: version "4.2.4" - resolved "https://registry.yarnpkg.com/web-vitals/-/web-vitals-4.2.4.tgz#1d20bc8590a37769bd0902b289550936069184b7" + resolved "https://registry.npmjs.org/web-vitals/-/web-vitals-4.2.4.tgz" integrity sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw== websocket-driver@>=0.5.1: version "0.7.4" - resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.4.tgz#89ad5295bbf64b480abcba31e4953aca706f5760" + resolved "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz" integrity sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg== dependencies: http-parser-js ">=0.5.1" @@ -4820,7 +4812,7 @@ websocket-driver@>=0.5.1: websocket-extensions@>=0.1.1: version "0.1.4" - resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42" + resolved "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz" integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg== which-boxed-primitive@^1.1.0, which-boxed-primitive@^1.1.1: From 1ff3cf95de4fda7495c70e0bd4c0e83a8e80728c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=84=9C=EC=A0=9C=EA=B2=BD?= Date: Tue, 12 Aug 2025 21:02:17 +0900 Subject: [PATCH 02/11] =?UTF-8?q?fix:=20CodeRabbit=20=ED=94=BC=EB=93=9C?= =?UTF-8?q?=EB=B0=B1=20=EC=9D=BC=EB=B6=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/auth/account.ts | 4 +- src/api/notice/notice.keys.ts | 5 ++- src/api/settingAlarm/alarm.ts | 4 +- src/components/common/EditableInputBox.tsx | 3 +- src/components/common/PasswordEdit.tsx | 2 + src/components/faq/FAQItem.tsx | 32 ++++++--------- src/components/settingTab/InfoSetting.tsx | 34 +++++++++++++--- src/hooks/faq/useFaq.ts | 35 ++++++++-------- src/hooks/notice/useNotice.ts | 11 +++++- src/hooks/settingAlarm/useAlarms.ts | 13 +++--- src/pages/notice/NoticeDetail.tsx | 13 +++--- src/pages/question/Question.tsx | 46 ++++++++++++---------- src/pages/setting/DeleteReasonPage.tsx.tsx | 2 +- src/pages/setting/PaymentHistory.tsx | 2 +- src/pages/setting/SettingEntryPage.tsx | 30 -------------- src/routes/routes.tsx | 5 --- src/types/notice/notice.ts | 30 +++----------- 17 files changed, 127 insertions(+), 144 deletions(-) delete mode 100644 src/pages/setting/SettingEntryPage.tsx diff --git a/src/api/auth/account.ts b/src/api/auth/account.ts index 06a8b24..2948775 100644 --- a/src/api/auth/account.ts +++ b/src/api/auth/account.ts @@ -5,11 +5,11 @@ import { axiosInstance } from '@/api/axiosInstance'; // 비밀번호 변경 export async function changePassword(payload: TChangePasswordPayload): Promise { const body = { nowPassword: payload.currentPassword, newPassword: payload.newPassword }; - await axiosInstance.patch('/api/v1/members/passwords', body, { withCredentials: true }); + await axiosInstance.patch('/api/v1/members/passwords', body); } // 닉네임 변경 export async function changeNickname(payload: TChangeNicknamePayload): Promise { - const { data } = await axiosInstance.patch('/api/v1/members/infos', payload, { withCredentials: true }); + const { data } = await axiosInstance.patch('/api/v1/members/infos', payload); return data; } diff --git a/src/api/notice/notice.keys.ts b/src/api/notice/notice.keys.ts index 12c3470..ab20d36 100644 --- a/src/api/notice/notice.keys.ts +++ b/src/api/notice/notice.keys.ts @@ -15,6 +15,7 @@ export const noticeKeys = { // 상세 키 - ID별로 캐시 분리 detail: (id: number) => [...noticeKeys.all, 'detail', id] as const, - // 검색 키 - search: (p: { keyword: string; page: number; size: number; category?: string }) => [...noticeKeys.all, 'search', p] as const, + // 검색 키 - 개별 파라미터로 분리 + search: (keyword: string, page: number, size: number, category?: string) => + [...noticeKeys.all, 'search', keyword, page, size, ...(category ? [category] : [])] as const, }; diff --git a/src/api/settingAlarm/alarm.ts b/src/api/settingAlarm/alarm.ts index faf8830..5974469 100644 --- a/src/api/settingAlarm/alarm.ts +++ b/src/api/settingAlarm/alarm.ts @@ -4,12 +4,12 @@ import { axiosInstance } from '@/api/axiosInstance'; // 조회 export async function getAlarmSettings(): Promise { - const { data } = await axiosInstance.get('/api/v1/alarms/settings', { withCredentials: true }); + const { data } = await axiosInstance.get('/api/v1/alarms/settings'); return data; } // 업데이트 export async function patchAlarmSettings(payload: TAlarmSettings): Promise { - const { data } = await axiosInstance.patch('/api/v1/alarms/settings', payload, { withCredentials: true }); + const { data } = await axiosInstance.patch('/api/v1/alarms/settings', payload); return data; } diff --git a/src/components/common/EditableInputBox.tsx b/src/components/common/EditableInputBox.tsx index b70503b..9701de3 100644 --- a/src/components/common/EditableInputBox.tsx +++ b/src/components/common/EditableInputBox.tsx @@ -29,6 +29,7 @@ export default function EditableInputBox({ onSearchClick, className = '', placeholder = '', + readOnly = false, }: IEditableInputBoxProps) { const [isEditing, setIsEditing] = useState(false); @@ -78,7 +79,7 @@ export default function EditableInputBox({ type="text" value={value} onChange={onChange} - readOnly={isNickname ? !isEditing : false} + readOnly={readOnly || (isNickname ? !isEditing : false)} placeholder={placeholder} maxLength={maxLength} onKeyDown={handleKeyDown} diff --git a/src/components/common/PasswordEdit.tsx b/src/components/common/PasswordEdit.tsx index b5e34cb..b1c53ce 100644 --- a/src/components/common/PasswordEdit.tsx +++ b/src/components/common/PasswordEdit.tsx @@ -97,6 +97,7 @@ export default function PasswordEditSection() { className="w-full h-12 pl-4 pr-20 border border-primary-500 rounded-[16px] font-body1 text-black bg-white" /> - {/* 답변 영역 */} -
    -
    {item.answer}
    -
    +
    {item.content}
    + ); } diff --git a/src/components/settingTab/InfoSetting.tsx b/src/components/settingTab/InfoSetting.tsx index d83e6c4..e575a43 100644 --- a/src/components/settingTab/InfoSetting.tsx +++ b/src/components/settingTab/InfoSetting.tsx @@ -13,6 +13,9 @@ export default function InfoSetting() { const [nickname, setNickname] = useState(''); const [initialNickname, setInitialNickname] = useState(''); + const TERMS_URL = 'https://continuous-headphones-f4c.notion.site/1ece4447020b8049a727d11c3f853a46?source=copy_link'; + const PRIVACY_URL = 'https://www.notion.so/1ece4447020b80c8befcd2f3886a0350?source=copy_link'; + // 이메일(읽기 전용) const { email } = useUserEmail(); @@ -73,18 +76,39 @@ export default function InfoSetting() {
    - +
    - - + + + {/* 탈퇴하기 */} 탈퇴하기 diff --git a/src/hooks/faq/useFaq.ts b/src/hooks/faq/useFaq.ts index 2195e22..90f6771 100644 --- a/src/hooks/faq/useFaq.ts +++ b/src/hooks/faq/useFaq.ts @@ -1,3 +1,4 @@ +// hooks/faq/useFaq.ts import { useMemo } from 'react'; import { keepPreviousData } from '@tanstack/react-query'; @@ -11,24 +12,22 @@ import { faqKeys } from '@/api/faq/faq.keys'; type TFaqListParams = { category: TFaqCategory; page: number; size: number }; type TFaqSearchParams = { keyword: string; category?: TFaqCategory; page: number; size: number }; -export function useFaq() { - const useGetFaqs = (params: TFaqListParams, options?: Parameters>[2]) => { - const stable = useMemo(() => params, [params.category, params.page, params.size]); - return useCoreQuery(faqKeys.list(stable), () => getFaqs(stable), { - placeholderData: keepPreviousData, - ...options, - }); - }; +//조회 +export function useGetFaqs(params: TFaqListParams, options?: Parameters>[2]) { + const stable = useMemo(() => params, [params.category, params.page, params.size]); - const useSearchFaqs = (params: TFaqSearchParams, options?: Parameters>[2]) => { - const stable = useMemo(() => ({ ...params, keyword: params.keyword.trim() }), [params.keyword, params.category, params.page, params.size]); - - return useCoreQuery(faqKeys.search(stable), () => searchFaqs(stable), { - enabled: stable.keyword.length > 0, - placeholderData: keepPreviousData, - ...options, - }); - }; + return useCoreQuery(faqKeys.list(stable), () => getFaqs(stable), { + placeholderData: keepPreviousData, + ...options, + }); +} - return { useGetFaqs, useSearchFaqs }; +// 검색 +export function useSearchFaqs(params: TFaqSearchParams, options?: Parameters>[2]) { + const stable = useMemo(() => ({ ...params, keyword: params.keyword.trim() }), [params.keyword, params.category, params.page, params.size]); + return useCoreQuery(faqKeys.search(stable), () => searchFaqs(stable), { + enabled: stable.keyword.length > 0, + placeholderData: keepPreviousData, + ...options, + }); } diff --git a/src/hooks/notice/useNotice.ts b/src/hooks/notice/useNotice.ts index ce38663..7aba991 100644 --- a/src/hooks/notice/useNotice.ts +++ b/src/hooks/notice/useNotice.ts @@ -24,7 +24,14 @@ type TSearchParams = { export function useNotice() { // 공지 목록 const useGetNotices = (params: TListParams, options?: Parameters>[2]) => { - const stable = useMemo(() => params, [params.category, params.page, params.size]); + const stable = useMemo( + () => ({ + category: params.category, + page: params.page, + size: params.size, + }), + [params.category, params.page, params.size], + ); return useCoreQuery(noticeKeys.list(stable), () => getNotices(stable), { placeholderData: keepPreviousData, @@ -35,7 +42,7 @@ export function useNotice() { // 공지 상세 const useGetNoticeDetail = (id: number, options?: Parameters>[2]) => useCoreQuery(noticeKeys.detail(id), () => getNoticeDetail(id), { - enabled: Number.isFinite(id), + enabled: Number.isFinite(id) && id > 0, ...options, }); diff --git a/src/hooks/settingAlarm/useAlarms.ts b/src/hooks/settingAlarm/useAlarms.ts index cd741ed..bac45af 100644 --- a/src/hooks/settingAlarm/useAlarms.ts +++ b/src/hooks/settingAlarm/useAlarms.ts @@ -1,23 +1,24 @@ +// hooks/settingAlarm/useAlarms.ts import { useQueryClient } from '@tanstack/react-query'; -import type { AlarmSettings, GetAlarmSettingsResp, PatchAlarmSettingsResp } from '@/types/settingAlarm/alarm'; +import type { TAlarmSettings, TGetAlarmSettingsResp, TPatchAlarmSettingsResp } from '@/types/settingAlarm/alarm'; import { useCoreMutation, useCoreQuery } from '@/hooks/customQuery'; import { getAlarmSettings, patchAlarmSettings } from '@/api/settingAlarm/alarm'; -// 알림 설정 조회 +// 조회 export function useGetAlarmSettings() { - return useCoreQuery(['alarmSettings'], getAlarmSettings, { - select: (resp: GetAlarmSettingsResp) => resp.result, + return useCoreQuery(['alarmSettings'], getAlarmSettings, { + select: (resp) => resp.result, refetchOnWindowFocus: false, }); } -// 알림 설정 업데이트 +// 업데이트 export function usePatchAlarmSettings() { const qc = useQueryClient(); - return useCoreMutation(patchAlarmSettings, { + return useCoreMutation(patchAlarmSettings, { onSuccess: () => { qc.invalidateQueries({ queryKey: ['alarmSettings'] }); }, diff --git a/src/pages/notice/NoticeDetail.tsx b/src/pages/notice/NoticeDetail.tsx index dbb907b..441f57e 100644 --- a/src/pages/notice/NoticeDetail.tsx +++ b/src/pages/notice/NoticeDetail.tsx @@ -6,19 +6,22 @@ export default function NoticeDetail() { const navigate = useNavigate(); const { noticeId } = useParams<{ noticeId: string }>(); // URL에서 noticeId 추출 const id = Number(noticeId); + const isValidId = Number.isInteger(id) && id > 0; // Id 유효성 계산 const { useGetNoticeDetail } = useNotice(); const { data, isLoading, isError } = useGetNoticeDetail(id); - const notice = data?.result ?? null; - - if (!Number.isFinite(id)) { + // Id 유효성 검사 -> 훅 호출 + if (!isValidId) { return
    잘못된 공지사항 ID입니다.
    ; } - if (isLoading) return
    로딩 중
    ; - if (isError || !notice) { + if (isLoading) { + return
    로딩 중
    ; + } + if (isError || !data?.result) { return
    공지사항을 불러오는 데 실패했습니다.
    ; } + const notice = data?.result ?? null; return (
    diff --git a/src/pages/question/Question.tsx b/src/pages/question/Question.tsx index 34e7d92..6a8f464 100644 --- a/src/pages/question/Question.tsx +++ b/src/pages/question/Question.tsx @@ -1,8 +1,9 @@ import { useMemo, useState } from 'react'; +import { keepPreviousData } from '@tanstack/react-query'; import type { TFaqCategory, TFaqItem } from '@/types/faq/faq'; -import { useFaq } from '@/hooks/faq/useFaq'; +import { useGetFaqs, useSearchFaqs } from '@/hooks/faq/useFaq'; import EditableInputBox from '@/components/common/EditableInputBox'; import Navigator from '@/components/common/navigator'; @@ -19,49 +20,51 @@ const CATEGORY_MAP = [ export default function Question() { const [searchValue, setSearchValue] = useState(''); - const [active, setActive] = useState<(typeof CATEGORY_MAP)[number]>(CATEGORY_MAP[0]); const [currentPage, setCurrentPage] = useState(1); const [openedIndex, setOpenedIndex] = useState(null); const size = 10; + const hasKeyword = searchValue.trim().length > 0; - // 목록 API 파라미터 + // 목록 API const paramsList = useMemo( () => ({ category: active.value as TFaqCategory, page: currentPage - 1, size, }), - [active.value, currentPage, size], + [active.value, currentPage], ); - // 검색 API 파라미터 + // 검색 API const paramsSearch = useMemo( () => ({ keyword: searchValue, - category: active.value as TFaqCategory, // 서버가 선택 카테고리를 받음 + category: active.value as TFaqCategory, page: currentPage - 1, size, }), - [searchValue, active.value, currentPage, size], + [searchValue, active.value, currentPage], ); - // FAQ 훅 - const { useGetFaqs, useSearchFaqs } = useFaq(); + // 두 훅을 항상 호출 - enabled로 제어 + const listQuery = useGetFaqs(paramsList, { + enabled: !hasKeyword, + placeholderData: keepPreviousData, + }); - // 키워드 있으면 검색 API / 없으면 목록 API - const { data, isLoading, isError } = searchValue.trim().length > 0 ? useSearchFaqs(paramsSearch) : useGetFaqs(paramsList); + const searchQuery = useSearchFaqs(paramsSearch, { + enabled: hasKeyword, + placeholderData: keepPreviousData, + }); + + const activeQuery = hasKeyword ? searchQuery : listQuery; + const { data, isLoading, isError } = activeQuery; const serverList = (data?.result?.faqList ?? []) as TFaqItem[]; const totalPages = data?.result?.totalPages ?? 1; - const normalized = serverList.map((f) => ({ - category: active.label, - question: f.title, - answer: f.content, - })); - return (
    {/* 제목 */} @@ -85,6 +88,7 @@ export default function Question() {
    {CATEGORY_MAP.map((c) => (
    diff --git a/src/pages/setting/PaymentHistory.tsx b/src/pages/setting/PaymentHistory.tsx index 5befc8e..9a03efb 100644 --- a/src/pages/setting/PaymentHistory.tsx +++ b/src/pages/setting/PaymentHistory.tsx @@ -43,7 +43,7 @@ export default function PaymentHistory() {
    {/* 뒤로가기 버튼 */} - diff --git a/src/pages/setting/SettingEntryPage.tsx b/src/pages/setting/SettingEntryPage.tsx deleted file mode 100644 index 4ddca46..0000000 --- a/src/pages/setting/SettingEntryPage.tsx +++ /dev/null @@ -1,30 +0,0 @@ -// 뒤로 가기 시 SettingModal 띄우기 위한 페이지 -import { useEffect, useState } from 'react'; -import { useLocation } from 'react-router-dom'; - -import Header from '@/components/layout/Header'; -import SettingsModal from '@/components/modal/SettingModal'; - -export default function SettingEntryPage() { - const location = useLocation(); - const [isModalOpen, setIsModalOpen] = useState(false); - - const [defaultTab, setDefaultTab] = useState<'알람' | '멤버십' | '정보'>('알람'); - - useEffect(() => { - const requestedTab = location.state?.openSettingTab; - - // 세 탭 중 하나인지 체크 후 적용 - if (['알람', '멤버십', '정보'].includes(requestedTab)) { - setDefaultTab(requestedTab); - setIsModalOpen(true); - } - }, [location.state]); - - return ( - <> -
    - {isModalOpen && setIsModalOpen(false)} />} - - ); -} diff --git a/src/routes/routes.tsx b/src/routes/routes.tsx index 7694176..f6b9445 100644 --- a/src/routes/routes.tsx +++ b/src/routes/routes.tsx @@ -28,7 +28,6 @@ import Question from '@/pages/question/Question'; import DeleteConfirmPage from '@/pages/setting/DeleteConfirmPage'; import DeleteReasonPage from '@/pages/setting/DeleteReasonPage.tsx'; import PaymentHistory from '@/pages/setting/PaymentHistory'; -import SettingEntryPage from '@/pages/setting/SettingEntryPage'; function ProtectedRoute({ children }: PropsWithChildren) { //추후 실제 로그인 여부로 대체 필요 @@ -139,10 +138,6 @@ const router = createBrowserRouter([ }, // Setting page 연결 - { - path: '/setting', - element: , - }, { path: '/paymentHistory', element: , // 결제 내역 확인 diff --git a/src/types/notice/notice.ts b/src/types/notice/notice.ts index e1634c9..1355f94 100644 --- a/src/types/notice/notice.ts +++ b/src/types/notice/notice.ts @@ -1,6 +1,6 @@ import type { TCommonResponse } from '../common/common'; -// 공지사항 전체 조회 +// 공지 항목 export type TNoticeItem = { noticeId: number; title: string; @@ -8,12 +8,16 @@ export type TNoticeItem = { createdAt: string; }; +<<<<<<< HEAD export type TRequestGetNoticeRequest = { size?: number; noticeCategory: 'SERVICE' | 'SYSTEM'; page: number; }; +======= +// 공지사항 목록 조회 +>>>>>>> f8aebe8 (fix: CodeRabbit 피드백 일부 수정) export type TFetchNoticesResponse = TCommonResponse<{ noticeList: TNoticeItem[]; totalPages: number; @@ -31,27 +35,5 @@ export type TNoticeDetail = { createdAt: string; }; +// 공지 상세 조회 응답 export type TFetchNoticeDetailResponse = TCommonResponse; - -// 생성 요청 -export type TCreateNoticePayload = { - title: string; - content: string; - isPinned: boolean; - noticeCategory: 'SERVICE' | 'SYSTEM'; -}; - -// 수정 요청 -export type TUpdateNoticePayload = { - title: string; - content: string; - isPinned: boolean; -}; - -// 공통 응답 (이미 TCommonResponse가 있다면 생략 가능) -export type TNoticeCommonResponse = { - isSuccess: boolean; - code: string; - message: string; - result: string; -}; From 4e9367e3abe8ca87c885cc12a89384c60cadd575 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=84=9C=EC=A0=9C=EA=B2=BD?= Date: Wed, 13 Aug 2025 00:04:38 +0900 Subject: [PATCH 03/11] =?UTF-8?q?fix:=20api=20=EC=97=B0=EA=B2=B0=20?= =?UTF-8?q?=EC=9E=84=EC=8B=9C=20=EC=A0=80=EC=9E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/auth/account.ts | 13 +++++- src/api/notice/notice.keys.ts | 25 ++++------- src/hooks/auth/useAccount.ts | 18 ++++++-- src/hooks/auth/useEmail.ts | 8 +++- src/hooks/customQuery.ts | 3 +- src/hooks/notice/useNotice.ts | 8 +++- src/pages/notice/NoticeDetail.tsx | 14 +++--- src/pages/setting/DeleteConfirmPage.tsx | 59 +++++++++++++++++++------ src/routes/routes.tsx | 4 +- src/types/auth/account.ts | 14 ++++++ 10 files changed, 119 insertions(+), 47 deletions(-) diff --git a/src/api/auth/account.ts b/src/api/auth/account.ts index 2948775..c78280b 100644 --- a/src/api/auth/account.ts +++ b/src/api/auth/account.ts @@ -1,4 +1,5 @@ -import type { TChangeNicknamePayload, TChangeNicknameResponse, TChangePasswordPayload } from '@/types/auth/account'; +import type { TChangeNicknamePayload, TChangeNicknameResponse, TChangePasswordPayload, TMemberInfo } from '@/types/auth/account'; +import type { TCommonResponse } from '@/types/common/common'; import { axiosInstance } from '@/api/axiosInstance'; @@ -13,3 +14,13 @@ export async function changeNickname(payload: TChangeNicknamePayload): Promise('/api/v1/members/infos', payload); return data; } + +// 탈퇴 +export async function deleteMember(): Promise { + await axiosInstance.delete('/api/v1/members'); +} + +export async function getMemberInfo(): Promise> { + const { data } = await axiosInstance.get>('/api/v1/members/infos'); + return data; +} diff --git a/src/api/notice/notice.keys.ts b/src/api/notice/notice.keys.ts index ab20d36..1351713 100644 --- a/src/api/notice/notice.keys.ts +++ b/src/api/notice/notice.keys.ts @@ -1,21 +1,12 @@ -// React Query 캐시 키 관리 전용 파일 - -// 목록 쿼리 파라미터 타입 -type TListParams = { - category: 'SERVICE' | 'SYSTEM'; - page: number; - size: number; -}; - export const noticeKeys = { - all: ['notices'] as const, - // 목록 키 - 카테고리/페이지/사이즈별로 캐시 분리 - list: (params: TListParams) => [...noticeKeys.all, 'list', params.category, params.page, params.size] as const, + root: ['notice'] as const, // 루트 키 + + // 목록 조회 키 + list: (p: { category: 'SERVICE' | 'SYSTEM'; page: number; size: number }) => [...noticeKeys.root, 'list', p] as const, - // 상세 키 - ID별로 캐시 분리 - detail: (id: number) => [...noticeKeys.all, 'detail', id] as const, + // 상세 조회 키 + detail: (id: number) => [...noticeKeys.root, 'detail', id] as const, - // 검색 키 - 개별 파라미터로 분리 - search: (keyword: string, page: number, size: number, category?: string) => - [...noticeKeys.all, 'search', keyword, page, size, ...(category ? [category] : [])] as const, + // 검색 조회 키 + search: (p: { keyword: string; page: number; size: number; category?: 'SERVICE' | 'SYSTEM' }) => [...noticeKeys.root, 'search', p] as const, }; diff --git a/src/hooks/auth/useAccount.ts b/src/hooks/auth/useAccount.ts index 9ad8825..92d2da0 100644 --- a/src/hooks/auth/useAccount.ts +++ b/src/hooks/auth/useAccount.ts @@ -1,3 +1,4 @@ +// src/hooks/auth/useAccount.ts import type { TChangeNicknameMutationOptions, TChangeNicknameMutationResult, @@ -8,10 +9,11 @@ import type { TChangePasswordPayload, TChangePasswordResponse, } from '@/types/auth/account'; +import type { TUseMutationCustomOptions } from '@/types/common/common'; -import { useCoreMutation } from '@/hooks/customQuery'; +import { useCoreMutation, useCoreQuery } from '@/hooks/customQuery'; -import { changeNickname, changePassword } from '@/api/auth/account'; +import { changeNickname, changePassword, deleteMember, getMemberInfo } from '@/api/auth/account'; export function useAccount() { // 비밀번호 변경 @@ -24,5 +26,15 @@ export function useAccount() { return useCoreMutation(changeNickname, options); } - return { useChangePassword, useChangeNickname }; + // 회원 탈퇴 + function useDeleteMember(options?: TUseMutationCustomOptions) { + return useCoreMutation(deleteMember, options); + } + + // 사용자 정보 가져오기 + function useGetMemberInfo() { + return useCoreQuery(['memberInfo'], getMemberInfo); + } + + return { useChangePassword, useChangeNickname, useDeleteMember, useGetMemberInfo }; } diff --git a/src/hooks/auth/useEmail.ts b/src/hooks/auth/useEmail.ts index 28a5a3a..f31d5c8 100644 --- a/src/hooks/auth/useEmail.ts +++ b/src/hooks/auth/useEmail.ts @@ -1,7 +1,13 @@ import { useEffect, useState } from 'react'; export function useUserEmail() { - const [email, setEmail] = useState(() => localStorage.getItem('userEmail') || ''); + const [email, setEmail] = useState(() => { + try { + return typeof window !== 'undefined' ? localStorage.getItem('userEmail') || '' : ''; + } catch { + return ''; + } + }); useEffect(() => { const onStorage = (e: StorageEvent) => { diff --git a/src/hooks/customQuery.ts b/src/hooks/customQuery.ts index 2ab4371..6df4461 100644 --- a/src/hooks/customQuery.ts +++ b/src/hooks/customQuery.ts @@ -17,7 +17,8 @@ export function useCoreQuery( }); } -export function useCoreMutation(mutation: MutationFunction, options?: TUseMutationCustomOptions) { +//options 타입을 제네릭 변경 +export function useCoreMutation(mutation: MutationFunction, options?: TUseMutationCustomOptions) { return useMutation({ mutationFn: mutation, onError: (error) => { diff --git a/src/hooks/notice/useNotice.ts b/src/hooks/notice/useNotice.ts index 7aba991..6ea1fe5 100644 --- a/src/hooks/notice/useNotice.ts +++ b/src/hooks/notice/useNotice.ts @@ -8,12 +8,14 @@ import { useCoreQuery } from '@/hooks/customQuery'; import { getNoticeDetail, getNotices, searchNotices } from '@/api/notice/notice'; import { noticeKeys } from '@/api/notice/notice.keys'; +// 목록 조회 시 넘길 파라미터 타입 type TListParams = { category: 'SERVICE' | 'SYSTEM'; page: number; size: number; }; +// 검색 시 넘길 파라미터 타입 type TSearchParams = { keyword: string; page: number; @@ -22,8 +24,9 @@ type TSearchParams = { }; export function useNotice() { - // 공지 목록 + // 공지 목록 조회 훅 const useGetNotices = (params: TListParams, options?: Parameters>[2]) => { + // 파라미터 안정화 - 의존성이 변할 때만 새 객체 생성 const stable = useMemo( () => ({ category: params.category, @@ -42,12 +45,13 @@ export function useNotice() { // 공지 상세 const useGetNoticeDetail = (id: number, options?: Parameters>[2]) => useCoreQuery(noticeKeys.detail(id), () => getNoticeDetail(id), { - enabled: Number.isFinite(id) && id > 0, + enabled: Number.isFinite(id) && id > 0, // 유효한 id일때만 실행 ...options, }); // 공지 검색 const useSearchNotices = (params: TSearchParams, options?: Parameters>[2]) => { + // keyword를 전처리하고, 파라미터 객체를 안정화 const stable = useMemo(() => ({ ...params, keyword: params.keyword.trim() }), [params.keyword, params.page, params.size, params.category]); return useCoreQuery(noticeKeys.search(stable), () => searchNotices(stable), { diff --git a/src/pages/notice/NoticeDetail.tsx b/src/pages/notice/NoticeDetail.tsx index 441f57e..f2497b7 100644 --- a/src/pages/notice/NoticeDetail.tsx +++ b/src/pages/notice/NoticeDetail.tsx @@ -1,5 +1,7 @@ import { useNavigate, useParams } from 'react-router-dom'; +import { formatDateDot } from '@/utils/date'; + import { useNotice } from '@/hooks/notice/useNotice'; export default function NoticeDetail() { @@ -13,13 +15,13 @@ export default function NoticeDetail() { // Id 유효성 검사 -> 훅 호출 if (!isValidId) { - return
    잘못된 공지사항 ID입니다.
    ; + return
    잘못된 공지사항 ID입니다.
    ; } if (isLoading) { - return
    로딩 중
    ; + return
    로딩 중
    ; } if (isError || !data?.result) { - return
    공지사항을 불러오는 데 실패했습니다.
    ; + return
    공지사항을 불러오는 데 실패했습니다.
    ; } const notice = data?.result ?? null; @@ -29,8 +31,8 @@ export default function NoticeDetail() { {/* 제목 + 작성일 */}
    -

    {notice.title}

    -

    {new Date(notice.createdAt).toLocaleDateString()}

    +

    {notice.title}

    +

    {formatDateDot(notice.createdAt)}

    {/* 내용 */} @@ -38,7 +40,7 @@ export default function NoticeDetail() { {/* 목록으로 돌아가기 버튼 */}
    -
    diff --git a/src/pages/setting/DeleteConfirmPage.tsx b/src/pages/setting/DeleteConfirmPage.tsx index a1eea24..6b4fd1a 100644 --- a/src/pages/setting/DeleteConfirmPage.tsx +++ b/src/pages/setting/DeleteConfirmPage.tsx @@ -1,6 +1,8 @@ -import { useEffect, useState } from 'react'; +import { useState } from 'react'; import { useNavigate } from 'react-router-dom'; +import { useAccount } from '@/hooks/auth/useAccount'; + import CommonAuthInput from '@/components/auth/commonAuthInput'; import Header from '@/components/layout/Header'; @@ -23,16 +25,34 @@ const withdrawAgreements = [ export default function DeleteConfirmPage() { const navigate = useNavigate(); - const [userEmail, setUserEmail] = useState(''); const [checked, setChecked] = useState(Array(withdrawAgreements.length).fill(false)); - // 사용자 이메일 localStorage에서 가져오기 - useEffect(() => { - const storedEmail = localStorage.getItem('userEmail'); - if (storedEmail) { - setUserEmail(storedEmail); - } - }, []); + // 회원정보 조회 + 탈퇴 훅 + const { useGetMemberInfo, useDeleteMember } = useAccount(); + + // 사용자 정보 가져오기 + const { data: memberData, isLoading: infoLoading, isError: infoError } = useGetMemberInfo(); + const userEmail = memberData?.result?.email ?? ''; // ← 여기서 이메일 사용 + + const { mutate: deleteAccount, isPending } = useDeleteMember({ + onSuccess: () => { + alert('회원 탈퇴가 완료되었습니다.'); + localStorage.removeItem('accessToken'); + navigate('/', { replace: true }); + }, + onError: (e) => { + const msg = (e as any)?.response?.data?.message || '회원 탈퇴에 실패했습니다.'; + alert(msg); + }, + }); + + const allAgreed = checked.every(Boolean); + + const handleDelete = () => { + if (!allAgreed) return alert('유의사항에 모두 동의해 주세요.'); + if (!confirm('정말 탈퇴하시겠습니까?')) return; + deleteAccount(); + }; // 체크박스 토글 const toggleCheckbox = (index: number) => { @@ -56,8 +76,14 @@ export default function DeleteConfirmPage() { {/* 타이틀 */}

    회원 탈퇴 전 아래 유의사항을 확인해주세요

    - {/* 아이디 */} - + {/* 이메일 표시 */} + {/* 탈퇴 설명 */}
    @@ -87,9 +113,16 @@ export default function DeleteConfirmPage() { ))}
    - {/* 탈퇴 버튼 */} + {/* 탈퇴하기 */}
    - +
    diff --git a/src/routes/routes.tsx b/src/routes/routes.tsx index f6b9445..83b745b 100644 --- a/src/routes/routes.tsx +++ b/src/routes/routes.tsx @@ -95,7 +95,7 @@ const router = createBrowserRouter([ element: , }, { - path: 'notice/:id', + path: '/notice/:noticeId', element: , }, { @@ -136,8 +136,6 @@ const router = createBrowserRouter([ }, ], }, - - // Setting page 연결 { path: '/paymentHistory', element: , // 결제 내역 확인 diff --git a/src/types/auth/account.ts b/src/types/auth/account.ts index af2ab8b..27c1b69 100644 --- a/src/types/auth/account.ts +++ b/src/types/auth/account.ts @@ -24,3 +24,17 @@ export type TChangePasswordMutationResult = UseMutationResult; export type TChangeNicknameMutationResult = UseMutationResult; + +// 사용자 정보 타입 +export type TMemberInfo = { + id: number; + email: string; + username: string; + userRank: string; + phoneNumber: string; + isAuthPayment: boolean; + gender: string; + birth: string; + role: string; + point: number; +}; From 2eb294ec7762d556d8e9a6e8dbfd55b3658c5401 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=84=9C=EC=A0=9C=EA=B2=BD?= Date: Wed, 13 Aug 2025 18:04:12 +0900 Subject: [PATCH 04/11] =?UTF-8?q?feat:=20api=20=EC=97=B0=EA=B2=B0=20?= =?UTF-8?q?=EB=AF=B8=EC=99=84=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/auth/account.ts | 1 + src/components/settingTab/InfoSetting.tsx | 71 +++++++++++-------- .../settingTab/MembershipSetting.tsx | 26 +++---- src/hooks/auth/useEmail.ts | 21 ------ src/hooks/notice/useNotice.ts | 2 +- 5 files changed, 54 insertions(+), 67 deletions(-) delete mode 100644 src/hooks/auth/useEmail.ts diff --git a/src/api/auth/account.ts b/src/api/auth/account.ts index c78280b..ccca411 100644 --- a/src/api/auth/account.ts +++ b/src/api/auth/account.ts @@ -20,6 +20,7 @@ export async function deleteMember(): Promise { await axiosInstance.delete('/api/v1/members'); } +// 사용자 정보 조회 export async function getMemberInfo(): Promise> { const { data } = await axiosInstance.get>('/api/v1/members/infos'); return data; diff --git a/src/components/settingTab/InfoSetting.tsx b/src/components/settingTab/InfoSetting.tsx index e575a43..cdad810 100644 --- a/src/components/settingTab/InfoSetting.tsx +++ b/src/components/settingTab/InfoSetting.tsx @@ -1,8 +1,8 @@ import { useEffect, useState } from 'react'; import { Link } from 'react-router-dom'; +import { useQueryClient } from '@tanstack/react-query'; import { useAccount } from '@/hooks/auth/useAccount'; -import { useUserEmail } from '@/hooks/auth/useEmail'; import EditableInputBox from '../common/EditableInputBox'; import PasswordEditSection from '../common/PasswordEdit'; @@ -10,24 +10,38 @@ import PasswordEditSection from '../common/PasswordEdit'; import ChevronForward from '@/assets/icons/default_arrows/chevron_forward.svg?react'; export default function InfoSetting() { - const [nickname, setNickname] = useState(''); - const [initialNickname, setInitialNickname] = useState(''); - const TERMS_URL = 'https://continuous-headphones-f4c.notion.site/1ece4447020b8049a727d11c3f853a46?source=copy_link'; const PRIVACY_URL = 'https://www.notion.so/1ece4447020b80c8befcd2f3886a0350?source=copy_link'; - // 이메일(읽기 전용) - const { email } = useUserEmail(); + const qc = useQueryClient(); + + // 회원정보 조회 + const { useGetMemberInfo, useChangeNickname } = useAccount(); + const { data: memberData, isLoading: infoLoading, isError: infoError } = useGetMemberInfo(); - // 닉네임/비밀번호 변경 훅 - const { useChangeNickname } = useAccount(); + const email = memberData?.result?.email ?? ''; + const apiNickname = memberData?.result?.username ?? ''; + // 닉네임 편집 상태 + const [nickname, setNickname] = useState(''); + const [initialNickname, setInitialNickname] = useState(''); + + useEffect(() => { + if (apiNickname) { + setNickname(apiNickname); + setInitialNickname(apiNickname); + } + }, [apiNickname]); + + // 닉네임 변경 const { mutate: changeNickname, isPending: nickPending } = useChangeNickname({ onSuccess: (res) => { if (res.isSuccess) { - setNickname(res.result.username); - setInitialNickname(res.result.username); - localStorage.setItem('nickname', res.result.username); + const next = res.result.username; + setNickname(next); + setInitialNickname(next); + localStorage.setItem('nickname', next); + qc.invalidateQueries({ queryKey: ['memberInfo'] }); alert('닉네임이 변경되었습니다.'); } else { alert(res.message ?? '닉네임 변경에 실패했습니다.'); @@ -39,22 +53,13 @@ export default function InfoSetting() { }, }); - // 초기 닉네임 세팅 - useEffect(() => { - const stored = localStorage.getItem('nickname'); - if (stored) { - setNickname(stored); - setInitialNickname(stored); - } - }, []); - - // 닉네임 저장 const handleSubmitNickname = () => { - if (nickname === initialNickname || nickPending) return; - changeNickname({ username: nickname }); + const trimmed = nickname.trim(); + if (!trimmed) return alert('닉네임을 입력해 주세요.'); + if (trimmed === initialNickname || nickPending) return; + changeNickname({ username: trimmed }); }; - // 닉네임 취소 const handleCancelNickname = () => setNickname(initialNickname); return ( @@ -67,14 +72,23 @@ export default function InfoSetting() { onChange={(e) => setNickname(e.target.value)} onCancel={handleCancelNickname} onSubmit={handleSubmitNickname} + placeholder={infoLoading ? '불러오는 중' : '닉네임'} /> - {/* 이메일 (읽기 전용) */} - {}} className="pointer-events-none" placeholder="이메일" /> + {/* 이메일 */} + {}} + className="pointer-events-none" + placeholder="이메일" + /> - {/* 비밀번호 변경 섹션 */} + {/* 비밀번호 변경 */} + {/* 취향 데이터 초기화 버튼 */}