diff --git a/FE/package-lock.json b/FE/package-lock.json index 76198c2..762e3fb 100644 --- a/FE/package-lock.json +++ b/FE/package-lock.json @@ -14,7 +14,7 @@ "react": "^18.3.1", "react-dom": "^18.3.1", "react-error-boundary": "^4.1.2", - "react-helmet": "^6.1.0", + "react-helmet-async": "^2.0.5", "react-router-dom": "^6.27.0", "react-toastify": "^10.0.6", "socket.io-client": "^4.8.1", @@ -26,6 +26,7 @@ "@types/node": "^22.9.1", "@types/react": "^18.3.12", "@types/react-dom": "^18.3.1", + "@types/react-helmet": "^6.1.11", "@vitejs/plugin-react": "^4.3.3", "autoprefixer": "^10.4.20", "eslint": "^9.13.0", @@ -1384,6 +1385,16 @@ "@types/react": "*" } }, + "node_modules/@types/react-helmet": { + "version": "6.1.11", + "resolved": "https://registry.npmjs.org/@types/react-helmet/-/react-helmet-6.1.11.tgz", + "integrity": "sha512-0QcdGLddTERotCXo3VFlUSWO3ztraw8nZ6e3zJSgG7apwV5xt+pJUS8ewPBqT4NYB1optGLprNQzFleIY84u/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.12.2", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.12.2.tgz", @@ -2615,6 +2626,15 @@ "node": ">=0.8.19" } }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -2978,6 +2998,7 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -3395,17 +3416,6 @@ } } }, - "node_modules/prop-types": { - "version": "15.8.1", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", - "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" - } - }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -3475,27 +3485,20 @@ "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==", "license": "MIT" }, - "node_modules/react-helmet": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/react-helmet/-/react-helmet-6.1.0.tgz", - "integrity": "sha512-4uMzEY9nlDlgxr61NL3XbKRy1hEkXmKNXhjbAIOVw5vcFrsdYbH2FEwcNyWvWinl103nXgzYNlns9ca+8kFiWw==", - "license": "MIT", + "node_modules/react-helmet-async": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/react-helmet-async/-/react-helmet-async-2.0.5.tgz", + "integrity": "sha512-rYUYHeus+i27MvFE+Jaa4WsyBKGkL6qVgbJvSBoX8mbsWoABJXdEO0bZyi0F6i+4f0NuIb8AvqPMj3iXFHkMwg==", + "license": "Apache-2.0", "dependencies": { - "object-assign": "^4.1.1", - "prop-types": "^15.7.2", - "react-fast-compare": "^3.1.1", - "react-side-effect": "^2.1.0" + "invariant": "^2.2.4", + "react-fast-compare": "^3.2.2", + "shallowequal": "^1.1.0" }, "peerDependencies": { - "react": ">=16.3.0" + "react": "^16.6.0 || ^17.0.0 || ^18.0.0" } }, - "node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "license": "MIT" - }, "node_modules/react-refresh": { "version": "0.14.2", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", @@ -3535,15 +3538,6 @@ "react-dom": ">=16.8" } }, - "node_modules/react-side-effect": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/react-side-effect/-/react-side-effect-2.1.2.tgz", - "integrity": "sha512-PVjOcvVOyIILrYoyGEpDN3vmYNLdy1CajSFNt4TDsVQC5KpTijDvWVoR+/7Rz2xT978D8/ZtFceXxzsPwZEDvw==", - "license": "MIT", - "peerDependencies": { - "react": "^16.3.0 || ^17.0.0 || ^18.0.0" - } - }, "node_modules/react-toastify": { "version": "10.0.6", "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-10.0.6.tgz", @@ -3695,6 +3689,12 @@ "semver": "bin/semver.js" } }, + "node_modules/shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==", + "license": "MIT" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", diff --git a/FE/package.json b/FE/package.json index cd6f913..683aacf 100644 --- a/FE/package.json +++ b/FE/package.json @@ -16,7 +16,7 @@ "react": "^18.3.1", "react-dom": "^18.3.1", "react-error-boundary": "^4.1.2", - "react-helmet": "^6.1.0", + "react-helmet-async": "^2.0.5", "react-router-dom": "^6.27.0", "react-toastify": "^10.0.6", "socket.io-client": "^4.8.1", @@ -28,6 +28,7 @@ "@types/node": "^22.9.1", "@types/react": "^18.3.12", "@types/react-dom": "^18.3.1", + "@types/react-helmet": "^6.1.11", "@vitejs/plugin-react": "^4.3.3", "autoprefixer": "^10.4.20", "eslint": "^9.13.0", diff --git a/FE/public/robots.txt b/FE/public/robots.txt new file mode 100644 index 0000000..f5a734c --- /dev/null +++ b/FE/public/robots.txt @@ -0,0 +1,2 @@ +user-agent: * +allow: / \ No newline at end of file diff --git a/FE/src/App.tsx b/FE/src/App.tsx index 9939f1f..30064b0 100644 --- a/FE/src/App.tsx +++ b/FE/src/App.tsx @@ -14,6 +14,9 @@ import MyPage from 'page/MyPage'; import Rank from 'page/Rank.tsx'; import { ToastContainer } from 'react-toastify'; import 'react-toastify/dist/ReactToastify.css'; +import { Helmet } from 'react-helmet-async'; +import { Suspense } from 'react'; +import { RankingSkeleton } from './components/Rank/RankingSkeleton.tsx'; function App() { return ( @@ -23,7 +26,14 @@ function App() { } /> } /> } /> - } /> + }> + + + } + /> @@ -35,6 +45,25 @@ export default App; function Layout() { return ( <> + + + + + + + + + JuGa +
diff --git a/FE/src/components/GlobalErrorFallback.tsx b/FE/src/components/GlobalErrorFallback.tsx new file mode 100644 index 0000000..b1c9905 --- /dev/null +++ b/FE/src/components/GlobalErrorFallback.tsx @@ -0,0 +1,31 @@ +import { FallbackProps } from 'react-error-boundary'; +import logoPng from 'assets/logo.png'; +import logoWebp from 'assets/logo.webp'; + +export default function GlobalErrorFallback({ error }: FallbackProps) { + return ( +
+
+ + + Logo + +
+

오류가 발생했습니다!

+

+ 문제가 지속적으로 발생하면 관리자에게 문의해주세요. +

+
+        {error.message}
+      
+ +
+ ); +} diff --git a/FE/src/components/Header.tsx b/FE/src/components/Header.tsx index a4d65d5..b958950 100644 --- a/FE/src/components/Header.tsx +++ b/FE/src/components/Header.tsx @@ -7,6 +7,7 @@ import logoPng from 'assets/logo.png'; import logoWebp from 'assets/logo.webp'; import { checkAuth, logout } from 'service/auth.ts'; import { useEffect } from 'react'; +import Toast from './Toast'; export default function Header() { const { toggleModal } = useLoginModalStore(); @@ -29,6 +30,7 @@ export default function Header() { const handleLogout = () => { logout().then(() => { setIsLogin(false); + Toast({ message: '로그아웃 되었습니다!', type: 'success' }); }); }; @@ -49,7 +51,7 @@ export default function Header() { type='image/webp' className={'h-[32px]'} /> - + {'Logo'}

JuGa

@@ -58,20 +60,20 @@ export default function Header() {