diff --git a/package.json b/package.json index 0fda0a7..c5f4747 100644 --- a/package.json +++ b/package.json @@ -26,14 +26,15 @@ "react-dom": "^18.2.0", "react-hook-form": "^7.50.1", "react-router-dom": "^6.22.1", - "react-spinners": "^0.13.8", "react-toastify": "^10.0.4", - "zustand": "^4.5.0", + "wowds-icons": "^0.1.0", "wowds-tokens": "^0.0.9", - "wowds-ui": "^0.1.7", - "wowds-icons": "^0.1.0" + "zustand": "^4.5.0", + "wowds-ui": "^0.1.9" }, "devDependencies": { + "@sentry/react": "^8.22.0", + "@sentry/vite-plugin": "^2.21.1", "@storybook/addon-essentials": "^7.6.14", "@storybook/addon-interactions": "^7.6.14", "@storybook/addon-links": "^7.6.14", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4687c3b..dc189e9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -44,9 +44,6 @@ dependencies: react-router-dom: specifier: ^6.22.1 version: 6.22.1(react-dom@18.2.0)(react@18.2.0) - react-spinners: - specifier: ^0.13.8 - version: 0.13.8(react-dom@18.2.0)(react@18.2.0) react-toastify: specifier: ^10.0.4 version: 10.0.4(react-dom@18.2.0)(react@18.2.0) @@ -57,13 +54,19 @@ dependencies: specifier: ^0.0.9 version: 0.0.9 wowds-ui: - specifier: ^0.1.7 - version: 0.1.7(next@14.2.4)(react@18.2.0) + specifier: ^0.1.9 + version: 0.1.9(next@14.2.4)(react-dom@18.2.0)(react@18.2.0) zustand: specifier: ^4.5.0 version: 4.5.1(@types/react@18.2.58)(react@18.2.0) devDependencies: + '@sentry/react': + specifier: ^8.22.0 + version: 8.22.0(react@18.2.0) + '@sentry/vite-plugin': + specifier: ^2.21.1 + version: 2.21.1 '@storybook/addon-essentials': specifier: ^7.6.14 version: 7.6.17(@types/react-dom@18.2.19)(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0) @@ -3010,6 +3013,210 @@ packages: dev: true optional: true + /@sentry-internal/browser-utils@8.22.0: + resolution: {integrity: sha512-R0u8KPaSivueIwUOhmYxcisKaJq3gx+I0xOcWoluDB3OI1Ds/QOSP/vmTsMg/mjwG/nUJ8RRM8pj0s8vlqCrjg==} + engines: {node: '>=14.18'} + dependencies: + '@sentry/core': 8.22.0 + '@sentry/types': 8.22.0 + '@sentry/utils': 8.22.0 + dev: true + + /@sentry-internal/feedback@8.22.0: + resolution: {integrity: sha512-Sy2+v0xBmVnZ5LQ48603CvLy5vVQvAZ+hc9xQSAHexts07NkvApMU1qv26YNwxlAWfDha1wXiW6ryd4YDzaoVA==} + engines: {node: '>=14.18'} + dependencies: + '@sentry/core': 8.22.0 + '@sentry/types': 8.22.0 + '@sentry/utils': 8.22.0 + dev: true + + /@sentry-internal/replay-canvas@8.22.0: + resolution: {integrity: sha512-/gV8qN3JqWw0LXTMuCGB8RDI8Bx1VESNRBdh/7Cmc5+hxYBfcketuix3S8mHWcE/JO+Ed9g1Abzys6GphTB9LA==} + engines: {node: '>=14.18'} + dependencies: + '@sentry-internal/replay': 8.22.0 + '@sentry/core': 8.22.0 + '@sentry/types': 8.22.0 + '@sentry/utils': 8.22.0 + dev: true + + /@sentry-internal/replay@8.22.0: + resolution: {integrity: sha512-sF8RyMPJP1fSIyyBDAbtybvKCu0dy8ZAfMwLP7ZqEnWrhZqktVuqM7/++EAFMlD5YaWJXm1IDuOXjgSQjUtSIQ==} + engines: {node: '>=14.18'} + dependencies: + '@sentry-internal/browser-utils': 8.22.0 + '@sentry/core': 8.22.0 + '@sentry/types': 8.22.0 + '@sentry/utils': 8.22.0 + dev: true + + /@sentry/babel-plugin-component-annotate@2.21.1: + resolution: {integrity: sha512-u1L8gZ4He0WdyiIsohYkA/YOY1b6Oa5yIMRtfZZ9U5TiWYLgOfMWyb88X0GotZeghSbgxrse/yI4WeHnhAUQDQ==} + engines: {node: '>= 14'} + dev: true + + /@sentry/browser@8.22.0: + resolution: {integrity: sha512-t3b+/9WWcP9SQTWwrHrB57B33ENgmUjyFlW2+JSlCXkSJBSmAoquPZ/GPjOuPaSr3HIA0mu9uEr4A41d5diASQ==} + engines: {node: '>=14.18'} + dependencies: + '@sentry-internal/browser-utils': 8.22.0 + '@sentry-internal/feedback': 8.22.0 + '@sentry-internal/replay': 8.22.0 + '@sentry-internal/replay-canvas': 8.22.0 + '@sentry/core': 8.22.0 + '@sentry/types': 8.22.0 + '@sentry/utils': 8.22.0 + dev: true + + /@sentry/bundler-plugin-core@2.21.1: + resolution: {integrity: sha512-F8FdL/bS8cy1SY1Gw0Mfo3ROTqlrq9Lvt5QGvhXi22dpVcDkWmoTWE2k+sMEnXOa8SdThMc/gyC8lMwHGd3kFQ==} + engines: {node: '>= 14'} + dependencies: + '@babel/core': 7.23.9 + '@sentry/babel-plugin-component-annotate': 2.21.1 + '@sentry/cli': 2.33.1 + dotenv: 16.4.5 + find-up: 5.0.0 + glob: 9.3.5 + magic-string: 0.30.8 + unplugin: 1.0.1 + transitivePeerDependencies: + - encoding + - supports-color + dev: true + + /@sentry/cli-darwin@2.33.1: + resolution: {integrity: sha512-+4/VIx/E1L2hChj5nGf5MHyEPHUNHJ/HoG5RY+B+vyEutGily1c1+DM2bum7RbD0xs6wKLIyup5F02guzSzG8A==} + engines: {node: '>=10'} + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@sentry/cli-linux-arm64@2.33.1: + resolution: {integrity: sha512-DbGV56PRKOLsAZJX27Jt2uZ11QfQEMmWB4cIvxkKcFVE+LJP4MVA+MGGRUL6p+Bs1R9ZUuGbpKGtj0JiG6CoXw==} + engines: {node: '>=10'} + cpu: [arm64] + os: [linux, freebsd] + requiresBuild: true + dev: true + optional: true + + /@sentry/cli-linux-arm@2.33.1: + resolution: {integrity: sha512-zbxEvQju+tgNvzTOt635le4kS/Fbm2XC2RtYbCTs034Vb8xjrAxLnK0z1bQnStUV8BkeBHtsNVrG+NSQDym2wg==} + engines: {node: '>=10'} + cpu: [arm] + os: [linux, freebsd] + requiresBuild: true + dev: true + optional: true + + /@sentry/cli-linux-i686@2.33.1: + resolution: {integrity: sha512-g2LS4oPXkPWOfKWukKzYp4FnXVRRSwBxhuQ9eSw2peeb58ZIObr4YKGOA/8HJRGkooBJIKGaAR2mH2Pk1TKaiA==} + engines: {node: '>=10'} + cpu: [x86, ia32] + os: [linux, freebsd] + requiresBuild: true + dev: true + optional: true + + /@sentry/cli-linux-x64@2.33.1: + resolution: {integrity: sha512-IV3dcYV/ZcvO+VGu9U6kuxSdbsV2kzxaBwWUQxtzxJ+cOa7J8Hn1t0koKGtU53JVZNBa06qJWIcqgl4/pCuKIg==} + engines: {node: '>=10'} + cpu: [x64] + os: [linux, freebsd] + requiresBuild: true + dev: true + optional: true + + /@sentry/cli-win32-i686@2.33.1: + resolution: {integrity: sha512-F7cJySvkpzIu7fnLKNHYwBzZYYwlhoDbAUnaFX0UZCN+5DNp/5LwTp37a5TWOsmCaHMZT4i9IO4SIsnNw16/zQ==} + engines: {node: '>=10'} + cpu: [x86, ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@sentry/cli-win32-x64@2.33.1: + resolution: {integrity: sha512-8VyRoJqtb2uQ8/bFRKNuACYZt7r+Xx0k2wXRGTyH05lCjAiVIXn7DiS2BxHFty7M1QEWUCMNsb/UC/x/Cu2wuA==} + engines: {node: '>=10'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@sentry/cli@2.33.1: + resolution: {integrity: sha512-dUlZ4EFh98VFRPJ+f6OW3JEYQ7VvqGNMa0AMcmvk07ePNeK/GicAWmSQE4ZfJTTl80ul6HZw1kY01fGQOQlVRA==} + engines: {node: '>= 10'} + hasBin: true + requiresBuild: true + dependencies: + https-proxy-agent: 5.0.1 + node-fetch: 2.7.0 + progress: 2.0.3 + proxy-from-env: 1.1.0 + which: 2.0.2 + optionalDependencies: + '@sentry/cli-darwin': 2.33.1 + '@sentry/cli-linux-arm': 2.33.1 + '@sentry/cli-linux-arm64': 2.33.1 + '@sentry/cli-linux-i686': 2.33.1 + '@sentry/cli-linux-x64': 2.33.1 + '@sentry/cli-win32-i686': 2.33.1 + '@sentry/cli-win32-x64': 2.33.1 + transitivePeerDependencies: + - encoding + - supports-color + dev: true + + /@sentry/core@8.22.0: + resolution: {integrity: sha512-fYPnxp7UkY2tckaOtivIySxnJvlbekuxs+Qi6rkUv9JpF+TYKpt7OPNUAbgVIhS0xazAEN6iKTfmnmpUbFRLmQ==} + engines: {node: '>=14.18'} + dependencies: + '@sentry/types': 8.22.0 + '@sentry/utils': 8.22.0 + dev: true + + /@sentry/react@8.22.0(react@18.2.0): + resolution: {integrity: sha512-LcO8SPfjYsx3Zvg1mQwjreVvtriVxde+6njIJyXU9TArB0e8bFexvd4MGXdBExgW9aY449hNaStgKRWMNHeVHQ==} + engines: {node: '>=14.18'} + peerDependencies: + react: ^16.14.0 || 17.x || 18.x || 19.x + dependencies: + '@sentry/browser': 8.22.0 + '@sentry/core': 8.22.0 + '@sentry/types': 8.22.0 + '@sentry/utils': 8.22.0 + hoist-non-react-statics: 3.3.2 + react: 18.2.0 + dev: true + + /@sentry/types@8.22.0: + resolution: {integrity: sha512-1MLK3xO+uF2oJaa+M98aLIrQsEHzV7xnVWPfE3MhejYLNQebj4rQnQKTut/xZNIF9W0Q+bRcakLarC3ce2a74g==} + engines: {node: '>=14.18'} + dev: true + + /@sentry/utils@8.22.0: + resolution: {integrity: sha512-0ITG2+3EtyMtyc/nQG8aB9z9eIQ4L43nM/KuNgYSnM1vPl/zegbaLT0Ek/xkQB1OLIOLkEPQ6x9GWe+248/n3g==} + engines: {node: '>=14.18'} + dependencies: + '@sentry/types': 8.22.0 + dev: true + + /@sentry/vite-plugin@2.21.1: + resolution: {integrity: sha512-i2PqeLafGBcSROnmr9mS0dL2/JBJji/4rJZ2U2A+tqtAhDAAaCUNalbn6xLp/hawLExt/wRuBj1J7j46sGDOaA==} + engines: {node: '>= 14'} + dependencies: + '@sentry/bundler-plugin-core': 2.21.1 + unplugin: 1.0.1 + transitivePeerDependencies: + - encoding + - supports-color + dev: true + /@sinclair/typebox@0.27.8: resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} dev: true @@ -4655,6 +4862,15 @@ packages: engines: {node: '>= 6.0.0'} dev: true + /agent-base@6.0.2: + resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} + engines: {node: '>= 6.0.0'} + dependencies: + debug: 4.3.4 + transitivePeerDependencies: + - supports-color + dev: true + /aggregate-error@3.1.0: resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} engines: {node: '>=8'} @@ -6764,6 +6980,16 @@ packages: path-is-absolute: 1.0.1 dev: true + /glob@9.3.5: + resolution: {integrity: sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==} + engines: {node: '>=16 || 14 >=14.17'} + dependencies: + fs.realpath: 1.0.0 + minimatch: 8.0.4 + minipass: 4.2.8 + path-scurry: 1.10.1 + dev: true + /globals@11.12.0: resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} engines: {node: '>=4'} @@ -6882,7 +7108,6 @@ packages: resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} dependencies: react-is: 16.13.1 - dev: false /hosted-git-info@2.8.9: resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} @@ -6914,6 +7139,16 @@ packages: - supports-color dev: true + /https-proxy-agent@5.0.1: + resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} + engines: {node: '>= 6'} + dependencies: + agent-base: 6.0.2 + debug: 4.3.4 + transitivePeerDependencies: + - supports-color + dev: true + /human-signals@2.1.0: resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} engines: {node: '>=10.17.0'} @@ -7574,6 +7809,21 @@ packages: dependencies: js-tokens: 4.0.0 + /lottie-react@2.4.0(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-pDJGj+AQlnlyHvOHFK7vLdsDcvbuqvwPZdMlJ360wrzGFurXeKPr8SiRCjLf3LrNYKANQtSsh5dz9UYQHuqx4w==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + dependencies: + lottie-web: 5.12.2 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + + /lottie-web@5.12.2: + resolution: {integrity: sha512-uvhvYPC8kGPjXT3MyKMrL3JitEAmDMp30lVkuq/590Mw9ok6pWcFCwXJveo0t5uqYw1UREQHofD+jVpdjBv8wg==} + dev: false + /loupe@2.3.7: resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==} dependencies: @@ -7609,6 +7859,13 @@ packages: '@jridgewell/sourcemap-codec': 1.4.15 dev: true + /magic-string@0.30.8: + resolution: {integrity: sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==} + engines: {node: '>=12'} + dependencies: + '@jridgewell/sourcemap-codec': 1.4.15 + dev: true + /make-dir@2.1.0: resolution: {integrity: sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==} engines: {node: '>=6'} @@ -7740,6 +7997,13 @@ packages: brace-expansion: 2.0.1 dev: true + /minimatch@8.0.4: + resolution: {integrity: sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA==} + engines: {node: '>=16 || 14 >=14.17'} + dependencies: + brace-expansion: 2.0.1 + dev: true + /minimatch@9.0.3: resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} engines: {node: '>=16 || 14 >=14.17'} @@ -7765,6 +8029,11 @@ packages: yallist: 4.0.0 dev: true + /minipass@4.2.8: + resolution: {integrity: sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==} + engines: {node: '>=8'} + dev: true + /minipass@5.0.0: resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} engines: {node: '>=8'} @@ -8627,16 +8896,6 @@ packages: react: 18.2.0 dev: false - /react-spinners@0.13.8(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-3e+k56lUkPj0vb5NDXPVFAOkPC//XyhKPJjvcGjyMNPWsBKpplfeyialP74G7H7+It7KzhtET+MvGqbKgAqpZA==} - peerDependencies: - react: ^16.0.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 - dependencies: - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: false - /react-style-singleton@2.2.1(@types/react@18.2.58)(react@18.2.0): resolution: {integrity: sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==} engines: {node: '>=10'} @@ -9720,6 +9979,15 @@ packages: engines: {node: '>= 0.8'} dev: true + /unplugin@1.0.1: + resolution: {integrity: sha512-aqrHaVBWW1JVKBHmGo33T5TxeL0qWzfvjWokObHA9bYmN7eNDkwOxmLjhioHl9878qDFMAaT51XNroRyuz7WxA==} + dependencies: + acorn: 8.12.0 + chokidar: 3.6.0 + webpack-sources: 3.2.3 + webpack-virtual-modules: 0.5.0 + dev: true + /unplugin@1.7.1: resolution: {integrity: sha512-JqzORDAPxxs8ErLV4x+LL7bk5pk3YlcWqpSNsIkAZj972KzFZLClc/ekppahKkOczGkwIG6ElFgdOgOlK4tXZw==} dependencies: @@ -9918,6 +10186,10 @@ packages: engines: {node: '>=10.13.0'} dev: true + /webpack-virtual-modules@0.5.0: + resolution: {integrity: sha512-kyDivFZ7ZM0BVOUteVbDFhlRt7Ah/CSPwJdi8hBpkK7QLumUqdLtVfm/PX/hkcnrvr0i77fO5+TjZ94Pe+C9iw==} + dev: true + /webpack-virtual-modules@0.6.1: resolution: {integrity: sha512-poXpCylU7ExuvZK8z+On3kX+S8o/2dQ/SVYueKA0D4WEMXROXgY8Ez50/bQEUmvoSMMrWcrJqCHuhAbsiwg7Dg==} dev: true @@ -10001,15 +10273,18 @@ packages: resolution: {integrity: sha512-fMGyb92sZ1iSf0TGSabQVym/MWsYyfetgBBNbZ0K6eGfESKtZF8AM1aDYRODmwyPkPSnt0SmOtRWMDxnPsnCqg==} dev: false - /wowds-ui@0.1.7(next@14.2.4)(react@18.2.0): - resolution: {integrity: sha512-ls2SnsTVfZpF2IOVu1N0FSo+XdGtlxtp+YOTwdd/l2lLX+Mh99/HKLsH6Q36p+yRF9zu0iiHv5dIlX790jvkOg==} + /wowds-ui@0.1.9(next@14.2.4)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-MDH0HqUCoRlHF1jtl+S6h+vusy5Lli+SVdu2SLhTaxlWJ6JQIqubesvNa16vaq/hRpn4WByX6x+aHrtM57cz3A==} peerDependencies: next: ^14.1.1 react: ^18.2.0 dependencies: + lottie-react: 2.4.0(react-dom@18.2.0)(react@18.2.0) next: 14.2.4(@babel/core@7.23.9)(react-dom@18.2.0)(react@18.2.0)(sass@1.71.1) react: 18.2.0 wowds-icons: 0.1.1 + transitivePeerDependencies: + - react-dom dev: false /wrap-ansi@7.0.0: diff --git a/src/App.tsx b/src/App.tsx index be0eca6..b952095 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -12,9 +12,8 @@ import styled from '@emotion/styled'; import { css } from '@emotion/react'; import GlobalSize from '@/constants/globalSize'; import { useNavigate } from 'react-router-dom'; -import { getAuthRedirectPath } from '@/utils/auth'; import 'react-toastify/dist/ReactToastify.css'; -import useLandingStatus from '@/hooks/zustand/useLandingStatus'; +import RoutePath from './routes/routePath'; const IMG_SRC = [ '/onboarding/1.png', @@ -29,7 +28,6 @@ const IMG_SRC = [ function App() { const navigate = useNavigate(); - const { landingStatus } = useLandingStatus(); return ( @@ -162,12 +160,8 @@ function App() { - navigate(getAuthRedirectPath(landingStatus))}> - {landingStatus === 'ONBOARDING_CLOSED' - ? '지금은 지원 기간이 아니에요' - : '가입하기'} + navigate(RoutePath.Dashboard)}> + 가입하기 diff --git a/src/apis/auth/authApi.ts b/src/apis/auth/authApi.ts new file mode 100644 index 0000000..150379e --- /dev/null +++ b/src/apis/auth/authApi.ts @@ -0,0 +1,9 @@ +import apiClient from '..'; + +const authApi = { + LOGOUT: async () => { + const response = await apiClient.get(`/auth/logout`); + return response.data; + } +}; +export default authApi; diff --git a/src/apis/discord/discordApi.ts b/src/apis/discord/discordApi.ts index 5a795d6..40810f3 100644 --- a/src/apis/discord/discordApi.ts +++ b/src/apis/discord/discordApi.ts @@ -25,7 +25,7 @@ const discordApi = { }); return response.data; }, - GET_DISCORD_JOIN: async (name: string) => { + GET_DISCORD_JOIN: async (name: string): Promise<{ isJoined: boolean }> => { const response = await apiClient.get('/onboarding/check-discord-join', { params: { username: name diff --git a/src/apis/index.ts b/src/apis/index.ts index a09ed77..ff10199 100644 --- a/src/apis/index.ts +++ b/src/apis/index.ts @@ -1,5 +1,4 @@ import { BASE_URL, DEV_AUTH_TOKEN } from '@/constants/environment'; -import useAuthToken from '@/hooks/auth/useAuthToken'; import axios from 'axios'; const apiClient = axios.create({ @@ -8,8 +7,12 @@ const apiClient = axios.create({ withCredentials: true }); -apiClient.defaults.headers.common['Authorization'] = DEV_AUTH_TOKEN - ? `${DEV_AUTH_TOKEN}` - : `Bearer ${useAuthToken().accessToken}`; +export function setAuthHeader() { + if (DEV_AUTH_TOKEN) { + apiClient.defaults.headers.common['Authorization'] = DEV_AUTH_TOKEN; + } +} + +setAuthHeader(); export default apiClient; diff --git a/src/apis/member/memberType.ts b/src/apis/member/memberType.ts index 13f24eb..abd5ae0 100644 --- a/src/apis/member/memberType.ts +++ b/src/apis/member/memberType.ts @@ -1,6 +1,5 @@ -import { Status } from '@/types/status'; import { User } from '@/types/user'; - +import { Status } from '@/types/status'; export interface MemberInfoResponse { member: User; currentRecruitmentRound: CurrentRecruitmentType; diff --git a/src/components/ApiErrorBoundary.tsx b/src/components/ApiErrorBoundary.tsx index 29bb051..d0efcd9 100644 --- a/src/components/ApiErrorBoundary.tsx +++ b/src/components/ApiErrorBoundary.tsx @@ -31,7 +31,8 @@ export default function ApiErrorBoundary({ children }: PropsWithChildren) { case 401: case 403: toast.error(message); - redirect(RoutePath.Index); + sessionStorage.setItem('isLogin', 'false'); + redirect(RoutePath.Home); break; default: toast.error(message); diff --git a/src/components/auth/guard/AuthAccessGuard.tsx b/src/components/auth/guard/AuthAccessGuard.tsx index a147080..9ea265b 100644 --- a/src/components/auth/guard/AuthAccessGuard.tsx +++ b/src/components/auth/guard/AuthAccessGuard.tsx @@ -1,13 +1,25 @@ -import useLandingStatus from '@/hooks/zustand/useLandingStatus'; -import { useEffect } from 'react'; -import { Outlet } from 'react-router-dom'; +import RoutePath from '@/routes/routePath'; +import { isAuthenticated } from '@/utils/auth'; +import { toast } from 'react-toastify'; +import { useNavigate, Outlet } from 'react-router-dom'; +import { useEffect, useState } from 'react'; export default function AuthAccessGuard() { - const { clearLandingStatus } = useLandingStatus(); + const navigate = useNavigate(); + const [redirect, setRedirect] = useState(false); useEffect(() => { - clearLandingStatus(); + if (!isAuthenticated()) { + toast.error('로그인이 필요한 서비스예요.'); + setRedirect(true); + } }, []); - return ; + useEffect(() => { + if (redirect) { + navigate(RoutePath.Home); + } + }, [redirect, navigate]); + + return isAuthenticated() ? : null; } diff --git a/src/components/auth/guard/MypageAccessGuard.tsx b/src/components/auth/guard/MypageAccessGuard.tsx deleted file mode 100644 index c0ff901..0000000 --- a/src/components/auth/guard/MypageAccessGuard.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import LandingStatus from '@/constants/landingStatus'; -import useLandingStatus from '@/hooks/zustand/useLandingStatus'; -import { getAuthRedirectPath } from '@/utils/auth'; -import { Navigate, Outlet } from 'react-router-dom'; - -export default function MypageAccessGuard() { - const { landingStatus } = useLandingStatus(); - - if (landingStatus !== LandingStatus.Dashboard) { - return ; - } - - return ; -} diff --git a/src/components/auth/guard/OnboardingClosedAccessGuard.tsx b/src/components/auth/guard/OnboardingClosedAccessGuard.tsx deleted file mode 100644 index a4f8916..0000000 --- a/src/components/auth/guard/OnboardingClosedAccessGuard.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import LandingStatus from '@/constants/landingStatus'; -import useLandingStatus from '@/hooks/zustand/useLandingStatus'; -import { getAuthRedirectPath } from '@/utils/auth'; -import { Navigate, Outlet } from 'react-router-dom'; - -export default function OnboardingClosedAccessGuard() { - const { landingStatus } = useLandingStatus(); - - if (landingStatus !== LandingStatus.OnboardingClosed) { - return ; - } - - return ; -} diff --git a/src/components/auth/guard/OnboardingNotOpenedAccessGuard.tsx b/src/components/auth/guard/OnboardingNotOpenedAccessGuard.tsx deleted file mode 100644 index 09a6899..0000000 --- a/src/components/auth/guard/OnboardingNotOpenedAccessGuard.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import LandingStatus from '@/constants/landingStatus'; -import useLandingStatus from '@/hooks/zustand/useLandingStatus'; -import { getAuthRedirectPath } from '@/utils/auth'; -import { Navigate, Outlet } from 'react-router-dom'; - -export default function OnboardingNotOpenedAccessGuard() { - const { landingStatus } = useLandingStatus(); - - if (landingStatus !== LandingStatus.OnboardingNotOpened) { - return ; - } - - return ; -} diff --git a/src/components/auth/guard/PaymentAccessGuard.tsx b/src/components/auth/guard/PaymentAccessGuard.tsx new file mode 100644 index 0000000..0337722 --- /dev/null +++ b/src/components/auth/guard/PaymentAccessGuard.tsx @@ -0,0 +1,58 @@ +import { useQuery } from '@tanstack/react-query'; +import memberApi from '@/apis/member/memberApi'; +import { useState, useEffect } from 'react'; +import { Outlet, useNavigate } from 'react-router-dom'; +import RoutePath from '@/routes/routePath'; +import { toast } from 'react-toastify'; +import LoadingSpinner from '@/components/common/LoadingSpinner'; + +const PaymentAccessGuard = () => { + const [redirect, setRedirect] = useState(false); + const navigate = useNavigate(); + const { data, isLoading } = useQuery({ + queryKey: ['member'], + queryFn: memberApi.GET_DASHBOARD + }); + + useEffect(() => { + if (!data) return; + + if (data.member.role !== 'ASSOCIATE') { + toast.error('준회원 조건을 충족해주세요.'); + setRedirect(true); + } + + if (!data.currentMembership) { + toast.error('정회원 지원 이후 학회비를 결제할 수 있어요.'); + setRedirect(true); + } + + if (!data.currentRecruitmentRound) { + toast.error('지금은 정회원 모집 기간이 아니에요.'); + setRedirect(true); + } + }, []); + + useEffect(() => { + if (redirect) { + navigate(RoutePath.Dashboard); + } + }, [redirect, navigate]); + + if (isLoading) { + return ; + } + + if (!data) return; + + if ( + data.member.role !== 'ASSOCIATE' || + !data.currentRecruitmentRound || + !data.currentMembership + ) { + navigate(RoutePath.Dashboard); + return null; + } else return ; +}; + +export default PaymentAccessGuard; diff --git a/src/components/auth/guard/PaymentSuccessAccessGuard.tsx b/src/components/auth/guard/PaymentSuccessAccessGuard.tsx new file mode 100644 index 0000000..d651bdc --- /dev/null +++ b/src/components/auth/guard/PaymentSuccessAccessGuard.tsx @@ -0,0 +1,49 @@ +import { useQuery } from '@tanstack/react-query'; +import memberApi from '@/apis/member/memberApi'; +import { useState, useEffect } from 'react'; +import { Outlet, useNavigate } from 'react-router-dom'; +import RoutePath from '@/routes/routePath'; +import { toast } from 'react-toastify'; +import LoadingSpinner from '@/components/common/LoadingSpinner'; + +const PaymentSuccessAccessGuard = () => { + const [redirect, setRedirect] = useState(false); + const navigate = useNavigate(); + const { data, isLoading } = useQuery({ + queryKey: ['member'], + queryFn: memberApi.GET_DASHBOARD + }); + + useEffect(() => { + if (!data) return; + + if (data.member.role !== 'REGULAR') { + toast.error('토스페이먼츠 결제를 완료해주세요.'); + setRedirect(true); + } + + if (!data.currentRecruitmentRound) { + toast.error('지금은 정회원 모집 기간이 아니에요.'); + setRedirect(true); + } + }, []); + + useEffect(() => { + if (redirect) { + navigate(RoutePath.Dashboard); + } + }, [redirect, navigate]); + + if (isLoading) { + return ; + } + + if (!data) return; + + if (data.member.role !== 'REGULAR' || !data.currentRecruitmentRound) { + navigate(RoutePath.Dashboard); + return null; + } else return ; +}; + +export default PaymentSuccessAccessGuard; diff --git a/src/components/auth/guard/SignupAccessGuard.tsx b/src/components/auth/guard/SignupAccessGuard.tsx deleted file mode 100644 index 94c42ac..0000000 --- a/src/components/auth/guard/SignupAccessGuard.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import { Outlet } from 'react-router-dom'; - -//deprecated: 추후 삭제 필요한 파일임 -export default function SignupAccessGuard() { - //TODO: 추후 보안 정책에 따라 수정 필요 - // if (landingStatus !== LandingStatus.Signup) { - // return ; - // } - - return ; -} diff --git a/src/components/auth/guard/StudentVerificationAccessGuard.tsx b/src/components/auth/guard/StudentVerificationAccessGuard.tsx deleted file mode 100644 index 7ec9007..0000000 --- a/src/components/auth/guard/StudentVerificationAccessGuard.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import { Outlet } from 'react-router-dom'; - -export default function StudentVerificationAccessGuard() { - // const { landingStatus } = useLandingStatus(); - - // if (landingStatus !== LandingStatus.StudentAuthentication) { - // return ; - // } - - return ; -} diff --git a/src/components/auth/guard/VerificationGuard.tsx b/src/components/auth/guard/VerificationGuard.tsx new file mode 100644 index 0000000..223dc83 --- /dev/null +++ b/src/components/auth/guard/VerificationGuard.tsx @@ -0,0 +1,63 @@ +import RoutePath from '@/routes/routePath'; +import { toast } from 'react-toastify'; +import { useNavigate } from 'react-router-dom'; +import { PropsWithChildren, useEffect } from 'react'; +import memberApi from '@/apis/member/memberApi'; +import { useQuery } from '@tanstack/react-query'; + +type GuardType = 'StudentVerification' | 'Discord' | 'SignUp' | 'Bevy'; + +interface VerificationGuardProps extends PropsWithChildren { + guardType: GuardType; +} +/** 기본 정보, 디스코드 정보, 이메일 정보가 인증되어 있는지에 대한 가드 */ + +export default function VerificationGuard({ + guardType, + children +}: VerificationGuardProps) { + const navigate = useNavigate(); + const { data } = useQuery({ + queryKey: ['member'], + queryFn: memberApi.GET_DASHBOARD + }); + + useEffect(() => { + if (!data) return; + + if ( + guardType === 'SignUp' && + data.member.associateRequirement.infoStatus === 'SATISFIED' + ) { + toast.error('기본 정보를 이미 입력했습니다.'); + navigate(RoutePath.Dashboard); + return; + } + if ( + guardType === 'Discord' && + data.member.associateRequirement.discordStatus === 'SATISFIED' + ) { + toast.error('디스코드 연동을 이미 완료했습니다.'); + navigate(RoutePath.Dashboard); + return; + } + if ( + guardType === 'StudentVerification' && + data.member.associateRequirement.univStatus === 'SATISFIED' + ) { + toast.error('재학생 인증을 이미 완료했습니다.'); + navigate(RoutePath.Dashboard); + return; + } + if ( + guardType === 'Bevy' && + data.member.associateRequirement.bevyStatus === 'SATISFIED' + ) { + toast.error('bevy 가입을 이미 완료했습니다.'); + navigate(RoutePath.Dashboard); + return; + } + }, [data, guardType, navigate]); + + return children; +} diff --git a/src/components/auth/guard/index.ts b/src/components/auth/guard/index.ts deleted file mode 100644 index 2d1665d..0000000 --- a/src/components/auth/guard/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export { default as AuthAccessGuard } from './AuthAccessGuard'; -export { default as MypageAccessGuard } from './MypageAccessGuard'; -export { default as SignupAccessGuard } from './SignupAccessGuard'; -export { default as StudentVerificationAccessGuard } from './StudentVerificationAccessGuard'; -export { default as OnboardingNotOpenedAccessGuard } from './OnboardingNotOpenedAccessGuard'; -export { default as OnboardingClosedAccessGuard } from './OnboardingClosedAccessGuard'; diff --git a/src/components/bottomsheet/JoinRegularMemberBottomSheet.tsx b/src/components/bottomsheet/JoinRegularMemberBottomSheet.tsx index ad1ea79..b8b4ca6 100644 --- a/src/components/bottomsheet/JoinRegularMemberBottomSheet.tsx +++ b/src/components/bottomsheet/JoinRegularMemberBottomSheet.tsx @@ -19,7 +19,10 @@ const JoinRegularMemberBottomSheet = ({ currentRecruitment: CurrentRecruitmentType; }) => { const { joinRegularMember } = useJoinRegularMember(); - const bottomSheetTitle = convertRecruitmentName(currentRecruitment.name); + const bottomSheetTitle = convertRecruitmentName( + currentRecruitment.name, + currentRecruitment.roundTypeValue + ); const recruitmentPeriod = convertRecruitmentPeriod(currentRecruitment.period); return ( diff --git a/src/components/chatbot/ChannelService.ts b/src/components/chatbot/ChannelService.ts deleted file mode 100644 index 70e5326..0000000 --- a/src/components/chatbot/ChannelService.ts +++ /dev/null @@ -1,64 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -declare global { - interface Window { - ChannelIO: (arg0: string, arg1?: unknown, arg2?: unknown) => void; - ChannelIOInitialized: boolean; - } -} - -class ChannelService { - constructor() { - this.loadScript(); - } - - loadScript() { - const w = window; - if (w.ChannelIOInitialized) { - return; - } - const channelIO = function () { - // eslint-disable-next-line prefer-rest-params - channelIO.c(arguments); - }; - - channelIO.q = [] as any[]; - channelIO.c = function (args: any) { - channelIO.q.push(args); - }; - w.ChannelIO = channelIO; - function initializeChannelIO() { - if (w.ChannelIOInitialized) { - return; - } - w.ChannelIOInitialized = true; - const script = document.createElement("script"); - script.type = "text/javascript"; - script.async = true; - script.src = "https://cdn.channel.io/plugin/ch-plugin-web.js"; - const firstScript = document.getElementsByTagName("script")[0]; - if (firstScript?.parentNode) { - firstScript.parentNode.insertBefore(script, firstScript); - } - } - if (document.readyState === "complete") { - initializeChannelIO(); - } else { - w.addEventListener("DOMContentLoaded", initializeChannelIO); - w.addEventListener("load", initializeChannelIO); - } - } - boot(settings: any, callback: any) { - window.ChannelIO("boot", settings, callback); - } - onBadgeChanged(callback: (number: number) => void) { - window.ChannelIO("onBadgeChanged", callback); - } - addTags(tags: string[]) { - window.ChannelIO("addTags", tags); - } - shutdown() { - window.ChannelIO("shutdown"); - } -} - -export default ChannelService; diff --git a/src/components/chatbot/Chatbot.tsx b/src/components/chatbot/Chatbot.tsx deleted file mode 100644 index 422ef9e..0000000 --- a/src/components/chatbot/Chatbot.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { useEffect } from 'react'; - -import { CHANNELIO_PLUGIN_KEY } from '@constants/environment'; - -import ChannelService from '@/components/chatbot/ChannelService'; - -export default function Chatbot() { - useEffect(() => { - const channelTalk = new ChannelService(); - - channelTalk.boot({ pluginKey: CHANNELIO_PLUGIN_KEY }, () => {}); - - return () => channelTalk.shutdown(); - }, []); - - return