From a381672b8ec8427b669b5ceb61fb8ce07e10ec13 Mon Sep 17 00:00:00 2001 From: VishakhaSainani-Josh Date: Sun, 27 Jul 2025 00:25:41 +0530 Subject: [PATCH] implement dashboard page --- frontend/.eslintrc.js | 100 +-- frontend/.prettierrc | 31 +- frontend/README.md | 69 -- frontend/components.json | 12 +- frontend/eslint.config.js | 34 +- frontend/package-lock.json | 690 +++++++++++------- frontend/package.json | 9 +- frontend/src/App.tsx | 12 - frontend/src/{utils => api}/axios.ts | 11 +- frontend/src/api/queries/Leaderboard.ts | 38 + frontend/src/api/queries/Overview.ts | 32 + frontend/src/api/queries/RecentActivities.ts | 22 + frontend/src/api/queries/UserBadges.ts | 22 + .../src/api/queries/UserProfileDetails.ts | 22 + frontend/src/{utils => api}/react-query.ts | 2 +- frontend/src/assets/default-profile-pic.svg | 3 + frontend/src/assets/react.svg | 1 - frontend/src/context/AuthProvider.tsx | 53 -- .../Login/components/LoginComponent.tsx | 51 ++ frontend/src/features/Login/index.tsx | 12 + .../src/features/MyContributions/index.tsx | 7 + .../UserDashboard/components/Leaderboard.tsx | 87 +++ .../components/LeaderboardCard.tsx | 46 ++ .../UserDashboard/components/Overview.tsx | 118 +++ .../UserDashboard/components/OverviewCard.tsx | 32 + .../components/RecentActivities.tsx | 89 +++ .../components/UserDashboardComponent.tsx | 17 + frontend/src/features/UserDashboard/index.tsx | 12 + frontend/src/index.css | 58 +- frontend/src/lib/utils.ts | 6 - frontend/src/main.tsx | 22 +- frontend/src/pages/Login/index.tsx | 13 - frontend/src/pages/Login/loginComponent.tsx | 45 -- frontend/src/root/PrivateRoutes.tsx | 22 - frontend/src/root/Router.tsx | 9 +- frontend/src/root/routeConstants.ts | 4 - frontend/src/root/routes-config.tsx | 34 + frontend/src/root/routesConfig.tsx | 25 - frontend/src/shared/HOC/WithAuth.tsx | 29 + frontend/src/shared/components/AuthLayout.tsx | 87 --- .../components/UserDashboard/Navbar.tsx | 30 + .../components/UserDashboard/UserBadges.tsx | 50 ++ .../components/UserDashboard/UserGoals.tsx | 42 ++ .../UserDashboard/UserProfileCard.tsx | 28 + .../UserDashboard/UserProfileDetails.tsx | 62 ++ .../shared/components/common/ActivityCard.tsx | 58 ++ .../src/shared/components/common/Coin.tsx | 9 + .../src/{ => shared}/components/ui/button.tsx | 36 +- .../src/{ => shared}/components/ui/card.tsx | 28 +- .../shared/components/ui/dropdown-menu.tsx | 255 +++++++ .../src/shared/components/ui/progress.tsx | 35 + .../src/shared/components/ui/separator.tsx | 26 + frontend/src/shared/components/ui/sonner.tsx | 23 + frontend/src/shared/constants/endpoints.ts | 3 + .../src/shared/constants/local-storage.ts | 1 + frontend/src/shared/constants/query-keys.ts | 6 + frontend/src/shared/constants/routes.ts | 4 + frontend/src/shared/context/AuthProvider.tsx | 53 ++ frontend/src/shared/layout/AuthLayout.tsx | 86 +++ .../src/shared/layout/UserDashboardLayout.tsx | 24 + frontend/src/shared/lib/constants.ts | 2 - frontend/src/shared/types/api.ts | 4 + frontend/src/shared/types/navbar.ts | 6 + frontend/src/shared/types/types.ts | 58 ++ frontend/src/shared/utils/local-storage.ts | 20 +- frontend/src/shared/utils/tailwindcss.ts | 6 + frontend/src/utils/endpoints.ts | 5 - frontend/tailwind.config.js | 68 +- frontend/tsconfig.app.json | 4 +- frontend/tsconfig.json | 2 +- frontend/vite.config.ts | 14 +- 71 files changed, 2212 insertions(+), 824 deletions(-) delete mode 100644 frontend/README.md delete mode 100644 frontend/src/App.tsx rename frontend/src/{utils => api}/axios.ts (51%) create mode 100644 frontend/src/api/queries/Leaderboard.ts create mode 100644 frontend/src/api/queries/Overview.ts create mode 100644 frontend/src/api/queries/RecentActivities.ts create mode 100644 frontend/src/api/queries/UserBadges.ts create mode 100644 frontend/src/api/queries/UserProfileDetails.ts rename frontend/src/{utils => api}/react-query.ts (76%) create mode 100644 frontend/src/assets/default-profile-pic.svg delete mode 100644 frontend/src/assets/react.svg delete mode 100644 frontend/src/context/AuthProvider.tsx create mode 100644 frontend/src/features/Login/components/LoginComponent.tsx create mode 100644 frontend/src/features/Login/index.tsx create mode 100644 frontend/src/features/MyContributions/index.tsx create mode 100644 frontend/src/features/UserDashboard/components/Leaderboard.tsx create mode 100644 frontend/src/features/UserDashboard/components/LeaderboardCard.tsx create mode 100644 frontend/src/features/UserDashboard/components/Overview.tsx create mode 100644 frontend/src/features/UserDashboard/components/OverviewCard.tsx create mode 100644 frontend/src/features/UserDashboard/components/RecentActivities.tsx create mode 100644 frontend/src/features/UserDashboard/components/UserDashboardComponent.tsx create mode 100644 frontend/src/features/UserDashboard/index.tsx delete mode 100644 frontend/src/lib/utils.ts delete mode 100644 frontend/src/pages/Login/index.tsx delete mode 100644 frontend/src/pages/Login/loginComponent.tsx delete mode 100644 frontend/src/root/PrivateRoutes.tsx delete mode 100644 frontend/src/root/routeConstants.ts create mode 100644 frontend/src/root/routes-config.tsx delete mode 100644 frontend/src/root/routesConfig.tsx create mode 100644 frontend/src/shared/HOC/WithAuth.tsx delete mode 100644 frontend/src/shared/components/AuthLayout.tsx create mode 100644 frontend/src/shared/components/UserDashboard/Navbar.tsx create mode 100644 frontend/src/shared/components/UserDashboard/UserBadges.tsx create mode 100644 frontend/src/shared/components/UserDashboard/UserGoals.tsx create mode 100644 frontend/src/shared/components/UserDashboard/UserProfileCard.tsx create mode 100644 frontend/src/shared/components/UserDashboard/UserProfileDetails.tsx create mode 100644 frontend/src/shared/components/common/ActivityCard.tsx create mode 100644 frontend/src/shared/components/common/Coin.tsx rename frontend/src/{ => shared}/components/ui/button.tsx (70%) rename frontend/src/{ => shared}/components/ui/card.tsx (93%) create mode 100644 frontend/src/shared/components/ui/dropdown-menu.tsx create mode 100644 frontend/src/shared/components/ui/progress.tsx create mode 100644 frontend/src/shared/components/ui/separator.tsx create mode 100644 frontend/src/shared/components/ui/sonner.tsx create mode 100644 frontend/src/shared/constants/endpoints.ts create mode 100644 frontend/src/shared/constants/local-storage.ts create mode 100644 frontend/src/shared/constants/query-keys.ts create mode 100644 frontend/src/shared/constants/routes.ts create mode 100644 frontend/src/shared/context/AuthProvider.tsx create mode 100644 frontend/src/shared/layout/AuthLayout.tsx create mode 100644 frontend/src/shared/layout/UserDashboardLayout.tsx delete mode 100644 frontend/src/shared/lib/constants.ts create mode 100644 frontend/src/shared/types/api.ts create mode 100644 frontend/src/shared/types/navbar.ts create mode 100644 frontend/src/shared/types/types.ts create mode 100644 frontend/src/shared/utils/tailwindcss.ts delete mode 100644 frontend/src/utils/endpoints.ts diff --git a/frontend/.eslintrc.js b/frontend/.eslintrc.js index 74cf844..0130136 100644 --- a/frontend/.eslintrc.js +++ b/frontend/.eslintrc.js @@ -1,56 +1,56 @@ -import globals from "globals"; import pluginJs from "@eslint/js"; -import tseslint from "typescript-eslint"; -import pluginReact from "eslint-plugin-react"; -import pluginPrettier from "eslint-plugin-prettier"; -import pluginA11y from "eslint-plugin-jsx-a11y"; import pluginImport from "eslint-plugin-import"; +import pluginA11y from "eslint-plugin-jsx-a11y"; +import pluginPrettier from "eslint-plugin-prettier"; +import pluginReact from "eslint-plugin-react"; +import globals from "globals"; +import tseslint from "typescript-eslint"; /** @type {import('eslint').Linter.FlatConfig[]} */ export default [ - { - files: ["**/*.{js,mjs,cjs,ts,jsx,tsx}"], - languageOptions: { - globals: globals.browser, - ecmaVersion: "latest", - sourceType: "module", - parser: tseslint.parser - }, - plugins: { - react: pluginReact, - "@typescript-eslint": tseslint.plugin, - prettier: pluginPrettier, - "jsx-a11y": pluginA11y, - import: pluginImport, - }, - rules: { - ...pluginJs.configs.recommended.rules, - ...tseslint.configs.recommended.rules, - ...pluginReact.configs.recommended.rules, - "prettier/prettier": "error", - "react/display-name": "off", - "jsx-a11y/anchor-is-valid": "off", - "jsx-a11y/label-has-for": "off", - "camelcase": "off", - "func-names": ["error", "never"], - "import/prefer-default-export": "off", - "import/no-anonymous-default-export": "off", - "import/no-extraneous-dependencies": "off", - "no-multi-spaces": "off", - "class-methods-use-this": "off", - "no-class-assign": "off", - "key-spacing": "off", - "lines-between-class-members": "off", - "no-param-reassign": "off", - "consistent-return": "off", - "jsx-a11y/href-no-hash": "off", - "import/no-unresolved": "off", - "no-tabs": "off", - "react/react-in-jsx-scope": "off", - "no-use-before-define": "off", - "@typescript-eslint/no-use-before-define": "error", - "react/jsx-filename-extension": ["error", { "extensions": [".tsx"] }], - "react/prop-types": "off" - }, + { + files: ["**/*.{js,mjs,cjs,ts,jsx,tsx}"], + languageOptions: { + globals: globals.browser, + ecmaVersion: "latest", + sourceType: "module", + parser: tseslint.parser + }, + plugins: { + react: pluginReact, + "@typescript-eslint": tseslint.plugin, + prettier: pluginPrettier, + "jsx-a11y": pluginA11y, + import: pluginImport }, -]; \ No newline at end of file + rules: { + ...pluginJs.configs.recommended.rules, + ...tseslint.configs.recommended.rules, + ...pluginReact.configs.recommended.rules, + "prettier/prettier": "error", + "react/display-name": "off", + "jsx-a11y/anchor-is-valid": "off", + "jsx-a11y/label-has-for": "off", + camelcase: "off", + "func-names": ["error", "never"], + "import/prefer-default-export": "off", + "import/no-anonymous-default-export": "off", + "import/no-extraneous-dependencies": "off", + "no-multi-spaces": "off", + "class-methods-use-this": "off", + "no-class-assign": "off", + "key-spacing": "off", + "lines-between-class-members": "off", + "no-param-reassign": "off", + "consistent-return": "off", + "jsx-a11y/href-no-hash": "off", + "import/no-unresolved": "off", + "no-tabs": "off", + "react/react-in-jsx-scope": "off", + "no-use-before-define": "off", + "@typescript-eslint/no-use-before-define": "error", + "react/jsx-filename-extension": ["error", { extensions: [".tsx"] }], + "react/prop-types": "off" + } + } +]; diff --git a/frontend/.prettierrc b/frontend/.prettierrc index 93c3b9e..3c92e9e 100644 --- a/frontend/.prettierrc +++ b/frontend/.prettierrc @@ -1,16 +1,17 @@ { - "semi": true, - "tabWidth": 2, - "printWidth": 85, - "singleQuote": true, - "trailingComma": "none", - "arrowParens": "avoid", - "endOfLine": "lf", - "htmlWhitespaceSensitivity": "css", - "insertPragma": false, - "jsxSingleQuote": false, - "proseWrap": "always", - "quoteProps": "as-needed", - "requirePragma": false, - "useTabs": false -} \ No newline at end of file + "semi": true, + "tabWidth": 2, + "printWidth": 80, + "singleQuote": false, + "trailingComma": "none", + "arrowParens": "avoid", + "endOfLine": "lf", + "htmlWhitespaceSensitivity": "css", + "insertPragma": false, + "jsxSingleQuote": false, + "proseWrap": "always", + "quoteProps": "as-needed", + "requirePragma": false, + "useTabs": false, + "plugins": ["prettier-plugin-tailwindcss"] +} diff --git a/frontend/README.md b/frontend/README.md deleted file mode 100644 index 7959ce4..0000000 --- a/frontend/README.md +++ /dev/null @@ -1,69 +0,0 @@ -# React + TypeScript + Vite - -This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. - -Currently, two official plugins are available: - -- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) for Fast Refresh -- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh - -## Expanding the ESLint configuration - -If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules: - -```js -export default tseslint.config([ - globalIgnores(['dist']), - { - files: ['**/*.{ts,tsx}'], - extends: [ - // Other configs... - - // Remove tseslint.configs.recommended and replace with this - ...tseslint.configs.recommendedTypeChecked, - // Alternatively, use this for stricter rules - ...tseslint.configs.strictTypeChecked, - // Optionally, add this for stylistic rules - ...tseslint.configs.stylisticTypeChecked, - - // Other configs... - ], - languageOptions: { - parserOptions: { - project: ['./tsconfig.node.json', './tsconfig.app.json'], - tsconfigRootDir: import.meta.dirname, - }, - // other options... - }, - }, -]) -``` - -You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules: - -```js -// eslint.config.js -import reactX from 'eslint-plugin-react-x' -import reactDom from 'eslint-plugin-react-dom' - -export default tseslint.config([ - globalIgnores(['dist']), - { - files: ['**/*.{ts,tsx}'], - extends: [ - // Other configs... - // Enable lint rules for React - reactX.configs['recommended-typescript'], - // Enable lint rules for React DOM - reactDom.configs.recommended, - ], - languageOptions: { - parserOptions: { - project: ['./tsconfig.node.json', './tsconfig.app.json'], - tsconfigRootDir: import.meta.dirname, - }, - // other options... - }, - }, -]) -``` diff --git a/frontend/components.json b/frontend/components.json index 1d282e6..c2eba99 100644 --- a/frontend/components.json +++ b/frontend/components.json @@ -11,11 +11,11 @@ "prefix": "" }, "aliases": { - "components": "@/components", - "utils": "@/lib/utils", - "ui": "@/components/ui", - "lib": "@/lib", - "hooks": "@/hooks" + "components": "@/shared/components", + "utils": "@/shared/utils/tailwindcss", + "ui": "@/shared/components/ui", + "lib": "@/shared/lib", + "hooks": "@/shared/hooks" }, "iconLibrary": "lucide" -} \ No newline at end of file +} diff --git a/frontend/eslint.config.js b/frontend/eslint.config.js index 092408a..c1b016b 100644 --- a/frontend/eslint.config.js +++ b/frontend/eslint.config.js @@ -1,28 +1,28 @@ -import js from '@eslint/js' -import globals from 'globals' -import reactHooks from 'eslint-plugin-react-hooks' -import reactRefresh from 'eslint-plugin-react-refresh' -import tseslint from 'typescript-eslint' +import js from "@eslint/js"; +import reactHooks from "eslint-plugin-react-hooks"; +import reactRefresh from "eslint-plugin-react-refresh"; +import globals from "globals"; +import tseslint from "typescript-eslint"; export default tseslint.config( - { ignores: ['dist'] }, + { ignores: ["dist"] }, { extends: [js.configs.recommended, ...tseslint.configs.recommended], - files: ['**/*.{ts,tsx}'], + files: ["**/*.{ts,tsx}"], languageOptions: { ecmaVersion: 2020, - globals: globals.browser, + globals: globals.browser }, plugins: { - 'react-hooks': reactHooks, - 'react-refresh': reactRefresh, + "react-hooks": reactHooks, + "react-refresh": reactRefresh }, rules: { ...reactHooks.configs.recommended.rules, - 'react-refresh/only-export-components': [ - 'warn', - { allowConstantExport: true }, - ], - }, - }, -) + "react-refresh/only-export-components": [ + "warn", + { allowConstantExport: true } + ] + } + } +); diff --git a/frontend/package-lock.json b/frontend/package-lock.json index f66cef2..8ead3f3 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -8,6 +8,8 @@ "name": "code-curiosity-frontend", "version": "0.0.0", "dependencies": { + "@radix-ui/react-progress": "^1.1.7", + "@radix-ui/react-separator": "^1.1.7", "@radix-ui/react-slot": "^1.2.3", "@tailwindcss/vite": "^4.1.11", "@tanstack/react-query": "^5.83.0", @@ -15,17 +17,20 @@ "axios": "^1.10.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "date-fns": "^4.1.0", "dotenv": "^17.2.0", "lucide-react": "^0.525.0", + "next-themes": "^0.4.6", "react": "^19.1.0", "react-dom": "^19.1.0", "react-router-dom": "^7.7.0", - "react-toastify": "^11.0.5", + "sonner": "^2.0.6", "tailwind-merge": "^3.3.1", "tailwindcss": "^4.1.11" }, "devDependencies": { "@eslint/js": "^9.29.0", + "@types/date-fns": "^2.5.3", "@types/node": "^24.0.15", "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", @@ -39,6 +44,7 @@ "husky": "^8.0.0", "lint-staged": "^16.1.2", "prettier": "^3.6.2", + "prettier-plugin-tailwindcss": "^0.6.14", "tw-animate-css": "^1.3.5", "typescript": "~5.8.3", "typescript-eslint": "^8.34.1", @@ -231,14 +237,14 @@ } }, "node_modules/@babel/helpers": { - "version": "7.27.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.6.tgz", - "integrity": "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==", + "version": "7.28.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.2.tgz", + "integrity": "sha512-/V9771t+EgXz62aCcyofnQhGM8DQACbRhvzKFsXKC9QM+5MadF8ZmIm0crDMaz3+o0h0zXfJnd4EhbYbxsrcFw==", "dev": true, "license": "MIT", "dependencies": { "@babel/template": "^7.27.2", - "@babel/types": "^7.27.6" + "@babel/types": "^7.28.2" }, "engines": { "node": ">=6.9.0" @@ -327,9 +333,9 @@ } }, "node_modules/@babel/types": { - "version": "7.28.1", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.1.tgz", - "integrity": "sha512-x0LvFTekgSX+83TI28Y9wYPUfzrnl2aT5+5QLnO6v7mSJYtEEevuDRN0F0uSHRk1G1IWZC43o00Y0xDDrpBGPQ==", + "version": "7.28.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz", + "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==", "dev": true, "license": "MIT", "dependencies": { @@ -341,9 +347,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.6.tgz", - "integrity": "sha512-ShbM/3XxwuxjFiuVBHA+d3j5dyac0aEVVq1oluIDf71hUw0aRF59dV/efUsIwFnR6m8JNM2FjZOzmaZ8yG61kw==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.8.tgz", + "integrity": "sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==", "cpu": [ "ppc64" ], @@ -357,9 +363,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.6.tgz", - "integrity": "sha512-S8ToEOVfg++AU/bHwdksHNnyLyVM+eMVAOf6yRKFitnwnbwwPNqKr3srzFRe7nzV69RQKb5DgchIX5pt3L53xg==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.8.tgz", + "integrity": "sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==", "cpu": [ "arm" ], @@ -373,9 +379,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.6.tgz", - "integrity": "sha512-hd5zdUarsK6strW+3Wxi5qWws+rJhCCbMiC9QZyzoxfk5uHRIE8T287giQxzVpEvCwuJ9Qjg6bEjcRJcgfLqoA==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.8.tgz", + "integrity": "sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==", "cpu": [ "arm64" ], @@ -389,9 +395,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.6.tgz", - "integrity": "sha512-0Z7KpHSr3VBIO9A/1wcT3NTy7EB4oNC4upJ5ye3R7taCc2GUdeynSLArnon5G8scPwaU866d3H4BCrE5xLW25A==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.8.tgz", + "integrity": "sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==", "cpu": [ "x64" ], @@ -405,9 +411,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.6.tgz", - "integrity": "sha512-FFCssz3XBavjxcFxKsGy2DYK5VSvJqa6y5HXljKzhRZ87LvEi13brPrf/wdyl/BbpbMKJNOr1Sd0jtW4Ge1pAA==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.8.tgz", + "integrity": "sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==", "cpu": [ "arm64" ], @@ -421,9 +427,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.6.tgz", - "integrity": "sha512-GfXs5kry/TkGM2vKqK2oyiLFygJRqKVhawu3+DOCk7OxLy/6jYkWXhlHwOoTb0WqGnWGAS7sooxbZowy+pK9Yg==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.8.tgz", + "integrity": "sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==", "cpu": [ "x64" ], @@ -437,9 +443,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.6.tgz", - "integrity": "sha512-aoLF2c3OvDn2XDTRvn8hN6DRzVVpDlj2B/F66clWd/FHLiHaG3aVZjxQX2DYphA5y/evbdGvC6Us13tvyt4pWg==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.8.tgz", + "integrity": "sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==", "cpu": [ "arm64" ], @@ -453,9 +459,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.6.tgz", - "integrity": "sha512-2SkqTjTSo2dYi/jzFbU9Plt1vk0+nNg8YC8rOXXea+iA3hfNJWebKYPs3xnOUf9+ZWhKAaxnQNUf2X9LOpeiMQ==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.8.tgz", + "integrity": "sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==", "cpu": [ "x64" ], @@ -469,9 +475,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.6.tgz", - "integrity": "sha512-SZHQlzvqv4Du5PrKE2faN0qlbsaW/3QQfUUc6yO2EjFcA83xnwm91UbEEVx4ApZ9Z5oG8Bxz4qPE+HFwtVcfyw==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.8.tgz", + "integrity": "sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==", "cpu": [ "arm" ], @@ -485,9 +491,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.6.tgz", - "integrity": "sha512-b967hU0gqKd9Drsh/UuAm21Khpoh6mPBSgz8mKRq4P5mVK8bpA+hQzmm/ZwGVULSNBzKdZPQBRT3+WuVavcWsQ==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.8.tgz", + "integrity": "sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==", "cpu": [ "arm64" ], @@ -501,9 +507,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.6.tgz", - "integrity": "sha512-aHWdQ2AAltRkLPOsKdi3xv0mZ8fUGPdlKEjIEhxCPm5yKEThcUjHpWB1idN74lfXGnZ5SULQSgtr5Qos5B0bPw==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.8.tgz", + "integrity": "sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==", "cpu": [ "ia32" ], @@ -517,9 +523,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.6.tgz", - "integrity": "sha512-VgKCsHdXRSQ7E1+QXGdRPlQ/e08bN6WMQb27/TMfV+vPjjTImuT9PmLXupRlC90S1JeNNW5lzkAEO/McKeJ2yg==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.8.tgz", + "integrity": "sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==", "cpu": [ "loong64" ], @@ -533,9 +539,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.6.tgz", - "integrity": "sha512-WViNlpivRKT9/py3kCmkHnn44GkGXVdXfdc4drNmRl15zVQ2+D2uFwdlGh6IuK5AAnGTo2qPB1Djppj+t78rzw==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.8.tgz", + "integrity": "sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==", "cpu": [ "mips64el" ], @@ -549,9 +555,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.6.tgz", - "integrity": "sha512-wyYKZ9NTdmAMb5730I38lBqVu6cKl4ZfYXIs31Baf8aoOtB4xSGi3THmDYt4BTFHk7/EcVixkOV2uZfwU3Q2Jw==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.8.tgz", + "integrity": "sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==", "cpu": [ "ppc64" ], @@ -565,9 +571,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.6.tgz", - "integrity": "sha512-KZh7bAGGcrinEj4qzilJ4hqTY3Dg2U82c8bv+e1xqNqZCrCyc+TL9AUEn5WGKDzm3CfC5RODE/qc96OcbIe33w==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.8.tgz", + "integrity": "sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==", "cpu": [ "riscv64" ], @@ -581,9 +587,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.6.tgz", - "integrity": "sha512-9N1LsTwAuE9oj6lHMyyAM+ucxGiVnEqUdp4v7IaMmrwb06ZTEVCIs3oPPplVsnjPfyjmxwHxHMF8b6vzUVAUGw==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.8.tgz", + "integrity": "sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==", "cpu": [ "s390x" ], @@ -597,9 +603,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.6.tgz", - "integrity": "sha512-A6bJB41b4lKFWRKNrWoP2LHsjVzNiaurf7wyj/XtFNTsnPuxwEBWHLty+ZE0dWBKuSK1fvKgrKaNjBS7qbFKig==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.8.tgz", + "integrity": "sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==", "cpu": [ "x64" ], @@ -613,9 +619,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.6.tgz", - "integrity": "sha512-IjA+DcwoVpjEvyxZddDqBY+uJ2Snc6duLpjmkXm/v4xuS3H+3FkLZlDm9ZsAbF9rsfP3zeA0/ArNDORZgrxR/Q==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.8.tgz", + "integrity": "sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==", "cpu": [ "arm64" ], @@ -629,9 +635,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.6.tgz", - "integrity": "sha512-dUXuZr5WenIDlMHdMkvDc1FAu4xdWixTCRgP7RQLBOkkGgwuuzaGSYcOpW4jFxzpzL1ejb8yF620UxAqnBrR9g==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.8.tgz", + "integrity": "sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==", "cpu": [ "x64" ], @@ -645,9 +651,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.6.tgz", - "integrity": "sha512-l8ZCvXP0tbTJ3iaqdNf3pjaOSd5ex/e6/omLIQCVBLmHTlfXW3zAxQ4fnDmPLOB1x9xrcSi/xtCWFwCZRIaEwg==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.8.tgz", + "integrity": "sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==", "cpu": [ "arm64" ], @@ -661,9 +667,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.6.tgz", - "integrity": "sha512-hKrmDa0aOFOr71KQ/19JC7az1P0GWtCN1t2ahYAf4O007DHZt/dW8ym5+CUdJhQ/qkZmI1HAF8KkJbEFtCL7gw==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.8.tgz", + "integrity": "sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==", "cpu": [ "x64" ], @@ -677,9 +683,9 @@ } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.6.tgz", - "integrity": "sha512-+SqBcAWoB1fYKmpWoQP4pGtx+pUUC//RNYhFdbcSA16617cchuryuhOCRpPsjCblKukAckWsV+aQ3UKT/RMPcA==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.8.tgz", + "integrity": "sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==", "cpu": [ "arm64" ], @@ -693,9 +699,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.6.tgz", - "integrity": "sha512-dyCGxv1/Br7MiSC42qinGL8KkG4kX0pEsdb0+TKhmJZgCUDBGmyo1/ArCjNGiOLiIAgdbWgmWgib4HoCi5t7kA==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.8.tgz", + "integrity": "sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==", "cpu": [ "x64" ], @@ -709,9 +715,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.6.tgz", - "integrity": "sha512-42QOgcZeZOvXfsCBJF5Afw73t4veOId//XD3i+/9gSkhSV6Gk3VPlWncctI+JcOyERv85FUo7RxuxGy+z8A43Q==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.8.tgz", + "integrity": "sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==", "cpu": [ "arm64" ], @@ -725,9 +731,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.6.tgz", - "integrity": "sha512-4AWhgXmDuYN7rJI6ORB+uU9DHLq/erBbuMoAuB4VWJTu5KtCgcKYPynF0YI1VkBNuEfjNlLrFr9KZPJzrtLkrQ==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.8.tgz", + "integrity": "sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==", "cpu": [ "ia32" ], @@ -741,9 +747,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.6.tgz", - "integrity": "sha512-NgJPHHbEpLQgDH2MjQu90pzW/5vvXIZ7KOnPyNBm92A6WgZ/7b6fJyUBjoumLqeOQQGqY2QjQxRo97ah4Sj0cA==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.8.tgz", + "integrity": "sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==", "cpu": [ "x64" ], @@ -874,9 +880,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.31.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.31.0.tgz", - "integrity": "sha512-LOm5OVt7D4qiKCqoiPbA7LWmI+tbw1VbTUowBcUMgQSuM6poJufkFkYDcQpo5KfgD39TnNySV26QjOh7VFpSyw==", + "version": "9.32.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.32.0.tgz", + "integrity": "sha512-BBpRFZK3eX6uMLKz8WxFOBIFFcGFJ/g8XuwjTHCqHROSIsopI+ddn/d5Cfh36+7+e5edVS8dbSHnBNhrLEX0zg==", "dev": true, "license": "MIT", "engines": { @@ -897,9 +903,9 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.3.tgz", - "integrity": "sha512-1+WqvgNMhmlAambTvT3KPtCl/Ibr68VldY2XY40SL1CE0ZXiakFR/cbTspaF5HsnpDMvcYYoJHfl4980NBjGag==", + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.4.tgz", + "integrity": "sha512-Ul5l+lHEcw3L5+k8POx6r74mxEYKG5kOb6Xpy2gCRW6zweT6TEhAf8vhxGgjhqrd/VO/Dirhsb+1hNpD1ue9hw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -1062,9 +1068,9 @@ } }, "node_modules/@pkgr/core": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.7.tgz", - "integrity": "sha512-YLT9Zo3oNPJoBjBc4q8G2mjU4tqIbf5CEOORbUUr48dCD9q3umJ3IPlVqOqDakPfd2HuwccBaqlGhN4Gmr5OWg==", + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", + "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", "dev": true, "license": "MIT", "engines": { @@ -1089,6 +1095,91 @@ } } }, + "node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-progress": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.7.tgz", + "integrity": "sha512-vPdg/tF6YC/ynuBIJlk1mm7Le0VgW6ub6J2UWnTQ7/D23KXcPI1qy+0vBkgKgd38RCMJavBXpB83HPNFMTb0Fg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-separator": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.7.tgz", + "integrity": "sha512-0HEb8R9E8A+jZjvmFCy/J4xhbXy3TV+9XSnGJ3KvTtjlIUy/YQ/p6UYZvi7YbeoeXdyU9+Y3scizK6hkY37baA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-slot": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", @@ -1734,6 +1825,13 @@ "@babel/types": "^7.20.7" } }, + "node_modules/@types/date-fns": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/@types/date-fns/-/date-fns-2.5.3.tgz", + "integrity": "sha512-4KVPD3g5RjSgZtdOjvI/TDFkLNUHhdoWxmierdQbDeEg17Rov0hbBYtIzNaQA67ORpteOhvR9YEMTb6xeDCang==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -1748,9 +1846,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "24.0.15", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.15.tgz", - "integrity": "sha512-oaeTSbCef7U/z7rDeJA138xpG3NuKc64/rZ2qmUFkFJmnMsAPaluIifqyWd8hSSMxyP9oie3dLAqYPblag9KgA==", + "version": "24.1.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.1.0.tgz", + "integrity": "sha512-ut5FthK5moxFKH2T1CUOC6ctR67rQRvvHdFLCD2Ql6KXmMuCrjsSsRI9UsLCm9M18BMwClv4pn327UvB7eeO1w==", "devOptional": true, "license": "MIT", "dependencies": { @@ -1771,24 +1869,24 @@ "version": "19.1.6", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.6.tgz", "integrity": "sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw==", - "dev": true, + "devOptional": true, "license": "MIT", "peerDependencies": { "@types/react": "^19.0.0" } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.37.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.37.0.tgz", - "integrity": "sha512-jsuVWeIkb6ggzB+wPCsR4e6loj+rM72ohW6IBn2C+5NCvfUVY8s33iFPySSVXqtm5Hu29Ne/9bnA0JmyLmgenA==", + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.38.0.tgz", + "integrity": "sha512-CPoznzpuAnIOl4nhj4tRr4gIPj5AfKgkiJmGQDaq+fQnRJTYlcBjbX3wbciGmpoPf8DREufuPRe1tNMZnGdanA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.37.0", - "@typescript-eslint/type-utils": "8.37.0", - "@typescript-eslint/utils": "8.37.0", - "@typescript-eslint/visitor-keys": "8.37.0", + "@typescript-eslint/scope-manager": "8.38.0", + "@typescript-eslint/type-utils": "8.38.0", + "@typescript-eslint/utils": "8.38.0", + "@typescript-eslint/visitor-keys": "8.38.0", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", @@ -1802,7 +1900,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.37.0", + "@typescript-eslint/parser": "^8.38.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } @@ -1818,16 +1916,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.37.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.37.0.tgz", - "integrity": "sha512-kVIaQE9vrN9RLCQMQ3iyRlVJpTiDUY6woHGb30JDkfJErqrQEmtdWH3gV0PBAfGZgQXoqzXOO0T3K6ioApbbAA==", + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.38.0.tgz", + "integrity": "sha512-Zhy8HCvBUEfBECzIl1PKqF4p11+d0aUJS1GeUiuqK9WmOug8YCmC4h4bjyBvMyAMI9sbRczmrYL5lKg/YMbrcQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.37.0", - "@typescript-eslint/types": "8.37.0", - "@typescript-eslint/typescript-estree": "8.37.0", - "@typescript-eslint/visitor-keys": "8.37.0", + "@typescript-eslint/scope-manager": "8.38.0", + "@typescript-eslint/types": "8.38.0", + "@typescript-eslint/typescript-estree": "8.38.0", + "@typescript-eslint/visitor-keys": "8.38.0", "debug": "^4.3.4" }, "engines": { @@ -1843,14 +1941,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.37.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.37.0.tgz", - "integrity": "sha512-BIUXYsbkl5A1aJDdYJCBAo8rCEbAvdquQ8AnLb6z5Lp1u3x5PNgSSx9A/zqYc++Xnr/0DVpls8iQ2cJs/izTXA==", + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.38.0.tgz", + "integrity": "sha512-dbK7Jvqcb8c9QfH01YB6pORpqX1mn5gDZc9n63Ak/+jD67oWXn3Gs0M6vddAN+eDXBCS5EmNWzbSxsn9SzFWWg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.37.0", - "@typescript-eslint/types": "^8.37.0", + "@typescript-eslint/tsconfig-utils": "^8.38.0", + "@typescript-eslint/types": "^8.38.0", "debug": "^4.3.4" }, "engines": { @@ -1865,14 +1963,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.37.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.37.0.tgz", - "integrity": "sha512-0vGq0yiU1gbjKob2q691ybTg9JX6ShiVXAAfm2jGf3q0hdP6/BruaFjL/ManAR/lj05AvYCH+5bbVo0VtzmjOA==", + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.38.0.tgz", + "integrity": "sha512-WJw3AVlFFcdT9Ri1xs/lg8LwDqgekWXWhH3iAF+1ZM+QPd7oxQ6jvtW/JPwzAScxitILUIFs0/AnQ/UWHzbATQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.37.0", - "@typescript-eslint/visitor-keys": "8.37.0" + "@typescript-eslint/types": "8.38.0", + "@typescript-eslint/visitor-keys": "8.38.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1883,9 +1981,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.37.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.37.0.tgz", - "integrity": "sha512-1/YHvAVTimMM9mmlPvTec9NP4bobA1RkDbMydxG8omqwJJLEW/Iy2C4adsAESIXU3WGLXFHSZUU+C9EoFWl4Zg==", + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.38.0.tgz", + "integrity": "sha512-Lum9RtSE3EroKk/bYns+sPOodqb2Fv50XOl/gMviMKNvanETUuUcC9ObRbzrJ4VSd2JalPqgSAavwrPiPvnAiQ==", "dev": true, "license": "MIT", "engines": { @@ -1900,15 +1998,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.37.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.37.0.tgz", - "integrity": "sha512-SPkXWIkVZxhgwSwVq9rqj/4VFo7MnWwVaRNznfQDc/xPYHjXnPfLWn+4L6FF1cAz6e7dsqBeMawgl7QjUMj4Ow==", + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.38.0.tgz", + "integrity": "sha512-c7jAvGEZVf0ao2z+nnz8BUaHZD09Agbh+DY7qvBQqLiz8uJzRgVPj5YvOh8I8uEiH8oIUGIfHzMwUcGVco/SJg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.37.0", - "@typescript-eslint/typescript-estree": "8.37.0", - "@typescript-eslint/utils": "8.37.0", + "@typescript-eslint/types": "8.38.0", + "@typescript-eslint/typescript-estree": "8.38.0", + "@typescript-eslint/utils": "8.38.0", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, @@ -1925,9 +2023,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.37.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.37.0.tgz", - "integrity": "sha512-ax0nv7PUF9NOVPs+lmQ7yIE7IQmAf8LGcXbMvHX5Gm+YJUYNAl340XkGnrimxZ0elXyoQJuN5sbg6C4evKA4SQ==", + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.38.0.tgz", + "integrity": "sha512-wzkUfX3plUqij4YwWaJyqhiPE5UCRVlFpKn1oCRn2O1bJ592XxWJj8ROQ3JD5MYXLORW84063z3tZTb/cs4Tyw==", "dev": true, "license": "MIT", "engines": { @@ -1939,16 +2037,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.37.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.37.0.tgz", - "integrity": "sha512-zuWDMDuzMRbQOM+bHyU4/slw27bAUEcKSKKs3hcv2aNnc/tvE/h7w60dwVw8vnal2Pub6RT1T7BI8tFZ1fE+yg==", + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.38.0.tgz", + "integrity": "sha512-fooELKcAKzxux6fA6pxOflpNS0jc+nOQEEOipXFNjSlBS6fqrJOVY/whSn70SScHrcJ2LDsxWrneFoWYSVfqhQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.37.0", - "@typescript-eslint/tsconfig-utils": "8.37.0", - "@typescript-eslint/types": "8.37.0", - "@typescript-eslint/visitor-keys": "8.37.0", + "@typescript-eslint/project-service": "8.38.0", + "@typescript-eslint/tsconfig-utils": "8.38.0", + "@typescript-eslint/types": "8.38.0", + "@typescript-eslint/visitor-keys": "8.38.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -2007,16 +2105,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.37.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.37.0.tgz", - "integrity": "sha512-TSFvkIW6gGjN2p6zbXo20FzCABbyUAuq6tBvNRGsKdsSQ6a7rnV6ADfZ7f4iI3lIiXc4F4WWvtUfDw9CJ9pO5A==", + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.38.0.tgz", + "integrity": "sha512-hHcMA86Hgt+ijJlrD8fX0j1j8w4C92zue/8LOPAFioIno+W0+L7KqE8QZKCcPGc/92Vs9x36w/4MPTJhqXdyvg==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.37.0", - "@typescript-eslint/types": "8.37.0", - "@typescript-eslint/typescript-estree": "8.37.0" + "@typescript-eslint/scope-manager": "8.38.0", + "@typescript-eslint/types": "8.38.0", + "@typescript-eslint/typescript-estree": "8.38.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2031,13 +2129,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.37.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.37.0.tgz", - "integrity": "sha512-YzfhzcTnZVPiLfP/oeKtDp2evwvHLMe0LOy7oe+hb9KKIumLNohYS9Hgp1ifwpu42YWxhZE8yieggz6JpqO/1w==", + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.38.0.tgz", + "integrity": "sha512-pWrTcoFNWuwHlA9CvlfSsGWs14JxfN1TH25zM5L7o0pRLhsoZkDnTsXfQRJBEWJoV5DL0jf+Z+sxiud+K0mq1g==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.37.0", + "@typescript-eslint/types": "8.38.0", "eslint-visitor-keys": "^4.2.1" }, "engines": { @@ -2168,13 +2266,13 @@ "license": "MIT" }, "node_modules/axios": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.10.0.tgz", - "integrity": "sha512-/1xYAC4MP/HEG+3duIhFr4ZQXR4sQXOIe+o6sdqzeykGLx6Upp/1p8MHqhINOvGeP7xyNHe7tsiJByc4SSVUxw==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.11.0.tgz", + "integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==", "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", + "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, @@ -2460,6 +2558,16 @@ "devOptional": true, "license": "MIT" }, + "node_modules/date-fns": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", + "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, "node_modules/debug": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", @@ -2504,9 +2612,9 @@ } }, "node_modules/dotenv": { - "version": "17.2.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.0.tgz", - "integrity": "sha512-Q4sgBT60gzd0BB0lSyYD3xM4YxrXA9y4uBDof1JNYGzOXrQdQ6yX+7XIAqoFOGQFOTK1D3Hts5OllpxMDZFONQ==", + "version": "17.2.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.1.tgz", + "integrity": "sha512-kQhDYKZecqnM0fCnzI5eIv5L4cAe/iRI+HqMbO/hbRdTAeXDG+M9FjipUxNfbARuEg4iHIbhnhs78BCHNbSxEQ==", "license": "BSD-2-Clause", "engines": { "node": ">=12" @@ -2530,9 +2638,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.187", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.187.tgz", - "integrity": "sha512-cl5Jc9I0KGUoOoSbxvTywTa40uspGJt/BDBoDLoxJRSBpWh4FFXBsjNRHfQrONsV/OoEjDfHUmZQa2d6Ze4YgA==", + "version": "1.5.191", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.191.tgz", + "integrity": "sha512-xcwe9ELcuxYLUFqZZxL19Z6HVKcvNkIwhbHUz7L3us6u12yR+7uY89dSl570f/IqNthx8dAw3tojG7i4Ni4tDA==", "dev": true, "license": "ISC" }, @@ -2615,9 +2723,9 @@ } }, "node_modules/esbuild": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.6.tgz", - "integrity": "sha512-GVuzuUwtdsghE3ocJ9Bs8PNoF13HNQ5TXbEi2AhvVb8xU1Iwt9Fos9FEamfoee+u/TOsn7GUWc04lz46n2bbTg==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.8.tgz", + "integrity": "sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==", "hasInstallScript": true, "license": "MIT", "bin": { @@ -2627,32 +2735,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.6", - "@esbuild/android-arm": "0.25.6", - "@esbuild/android-arm64": "0.25.6", - "@esbuild/android-x64": "0.25.6", - "@esbuild/darwin-arm64": "0.25.6", - "@esbuild/darwin-x64": "0.25.6", - "@esbuild/freebsd-arm64": "0.25.6", - "@esbuild/freebsd-x64": "0.25.6", - "@esbuild/linux-arm": "0.25.6", - "@esbuild/linux-arm64": "0.25.6", - "@esbuild/linux-ia32": "0.25.6", - "@esbuild/linux-loong64": "0.25.6", - "@esbuild/linux-mips64el": "0.25.6", - "@esbuild/linux-ppc64": "0.25.6", - "@esbuild/linux-riscv64": "0.25.6", - "@esbuild/linux-s390x": "0.25.6", - "@esbuild/linux-x64": "0.25.6", - "@esbuild/netbsd-arm64": "0.25.6", - "@esbuild/netbsd-x64": "0.25.6", - "@esbuild/openbsd-arm64": "0.25.6", - "@esbuild/openbsd-x64": "0.25.6", - "@esbuild/openharmony-arm64": "0.25.6", - "@esbuild/sunos-x64": "0.25.6", - "@esbuild/win32-arm64": "0.25.6", - "@esbuild/win32-ia32": "0.25.6", - "@esbuild/win32-x64": "0.25.6" + "@esbuild/aix-ppc64": "0.25.8", + "@esbuild/android-arm": "0.25.8", + "@esbuild/android-arm64": "0.25.8", + "@esbuild/android-x64": "0.25.8", + "@esbuild/darwin-arm64": "0.25.8", + "@esbuild/darwin-x64": "0.25.8", + "@esbuild/freebsd-arm64": "0.25.8", + "@esbuild/freebsd-x64": "0.25.8", + "@esbuild/linux-arm": "0.25.8", + "@esbuild/linux-arm64": "0.25.8", + "@esbuild/linux-ia32": "0.25.8", + "@esbuild/linux-loong64": "0.25.8", + "@esbuild/linux-mips64el": "0.25.8", + "@esbuild/linux-ppc64": "0.25.8", + "@esbuild/linux-riscv64": "0.25.8", + "@esbuild/linux-s390x": "0.25.8", + "@esbuild/linux-x64": "0.25.8", + "@esbuild/netbsd-arm64": "0.25.8", + "@esbuild/netbsd-x64": "0.25.8", + "@esbuild/openbsd-arm64": "0.25.8", + "@esbuild/openbsd-x64": "0.25.8", + "@esbuild/openharmony-arm64": "0.25.8", + "@esbuild/sunos-x64": "0.25.8", + "@esbuild/win32-arm64": "0.25.8", + "@esbuild/win32-ia32": "0.25.8", + "@esbuild/win32-x64": "0.25.8" } }, "node_modules/escalade": { @@ -2679,9 +2787,9 @@ } }, "node_modules/eslint": { - "version": "9.31.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.31.0.tgz", - "integrity": "sha512-QldCVh/ztyKJJZLr4jXNUByx3gR+TDYZCRXEktiZoUR3PGy4qCmSbkxcIle8GEwGpb5JBZazlaJ/CxLidXdEbQ==", + "version": "9.32.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.32.0.tgz", + "integrity": "sha512-LSehfdpgMeWcTZkWZVIJl+tkZ2nuSkyyB9C27MZqFWXuph7DvaowgcTvKqxvpLW1JZIk8PN7hFY3Rj9LQ7m7lg==", "dev": true, "license": "MIT", "dependencies": { @@ -2691,8 +2799,8 @@ "@eslint/config-helpers": "^0.3.0", "@eslint/core": "^0.15.0", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.31.0", - "@eslint/plugin-kit": "^0.3.1", + "@eslint/js": "9.32.0", + "@eslint/plugin-kit": "^0.3.4", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", @@ -2740,9 +2848,9 @@ } }, "node_modules/eslint-config-prettier": { - "version": "10.1.5", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.5.tgz", - "integrity": "sha512-zc1UmCpNltmVY34vuLRV61r1K27sWuX39E+uyUnY8xS2Bex88VV9cugG+UZbRSRGtGyFboj+D8JODyme1plMpw==", + "version": "10.1.8", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", + "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", "dev": true, "license": "MIT", "bin": { @@ -2756,9 +2864,9 @@ } }, "node_modules/eslint-plugin-prettier": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.1.tgz", - "integrity": "sha512-dobTkHT6XaEVOo8IO90Q4DOSxnm3Y151QxPJlM/vKC0bVy+d6cVWQZLlFiuZPP0wS6vZwSKeJgKkcS+KfMBlRw==", + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.3.tgz", + "integrity": "sha512-NAdMYww51ehKfDyDhv59/eIItUVzU0Io9H2E8nHNGKEeeqlnci+1gCvrHib6EmZdf6GxF+LCV5K7UC65Ezvw7w==", "dev": true, "license": "MIT", "dependencies": { @@ -3368,9 +3476,9 @@ "license": "ISC" }, "node_modules/jiti": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz", - "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.5.1.tgz", + "integrity": "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==", "license": "MIT", "bin": { "jiti": "lib/jiti-cli.mjs" @@ -4045,6 +4153,16 @@ "dev": true, "license": "MIT" }, + "node_modules/next-themes": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.4.6.tgz", + "integrity": "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc" + } + }, "node_modules/node-releases": { "version": "2.0.19", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", @@ -4250,6 +4368,93 @@ "node": ">=6.0.0" } }, + "node_modules/prettier-plugin-tailwindcss": { + "version": "0.6.14", + "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.6.14.tgz", + "integrity": "sha512-pi2e/+ZygeIqntN+vC573BcW5Cve8zUB0SSAGxqpB4f96boZF4M3phPVoOFCeypwkpRYdi7+jQ5YJJUwrkGUAg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.21.3" + }, + "peerDependencies": { + "@ianvs/prettier-plugin-sort-imports": "*", + "@prettier/plugin-hermes": "*", + "@prettier/plugin-oxc": "*", + "@prettier/plugin-pug": "*", + "@shopify/prettier-plugin-liquid": "*", + "@trivago/prettier-plugin-sort-imports": "*", + "@zackad/prettier-plugin-twig": "*", + "prettier": "^3.0", + "prettier-plugin-astro": "*", + "prettier-plugin-css-order": "*", + "prettier-plugin-import-sort": "*", + "prettier-plugin-jsdoc": "*", + "prettier-plugin-marko": "*", + "prettier-plugin-multiline-arrays": "*", + "prettier-plugin-organize-attributes": "*", + "prettier-plugin-organize-imports": "*", + "prettier-plugin-sort-imports": "*", + "prettier-plugin-style-order": "*", + "prettier-plugin-svelte": "*" + }, + "peerDependenciesMeta": { + "@ianvs/prettier-plugin-sort-imports": { + "optional": true + }, + "@prettier/plugin-hermes": { + "optional": true + }, + "@prettier/plugin-oxc": { + "optional": true + }, + "@prettier/plugin-pug": { + "optional": true + }, + "@shopify/prettier-plugin-liquid": { + "optional": true + }, + "@trivago/prettier-plugin-sort-imports": { + "optional": true + }, + "@zackad/prettier-plugin-twig": { + "optional": true + }, + "prettier-plugin-astro": { + "optional": true + }, + "prettier-plugin-css-order": { + "optional": true + }, + "prettier-plugin-import-sort": { + "optional": true + }, + "prettier-plugin-jsdoc": { + "optional": true + }, + "prettier-plugin-marko": { + "optional": true + }, + "prettier-plugin-multiline-arrays": { + "optional": true + }, + "prettier-plugin-organize-attributes": { + "optional": true + }, + "prettier-plugin-organize-imports": { + "optional": true + }, + "prettier-plugin-sort-imports": { + "optional": true + }, + "prettier-plugin-style-order": { + "optional": true + }, + "prettier-plugin-svelte": { + "optional": true + } + } + }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -4319,9 +4524,9 @@ } }, "node_modules/react-router": { - "version": "7.7.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.7.0.tgz", - "integrity": "sha512-3FUYSwlvB/5wRJVTL/aavqHmfUKe0+Xm9MllkYgGo9eDwNdkvwlJGjpPxono1kCycLt6AnDTgjmXvK3/B4QGuw==", + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.7.1.tgz", + "integrity": "sha512-jVKHXoWRIsD/qS6lvGveckwb862EekvapdHJN/cGmzw40KnJH5gg53ujOJ4qX6EKIK9LSBfFed/xiQ5yeXNrUA==", "license": "MIT", "dependencies": { "cookie": "^1.0.1", @@ -4341,12 +4546,12 @@ } }, "node_modules/react-router-dom": { - "version": "7.7.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.7.0.tgz", - "integrity": "sha512-wwGS19VkNBkneVh9/YD0pK3IsjWxQUVMDD6drlG7eJpo1rXBtctBqDyBm/k+oKHRAm1x9XWT3JFC82QI9YOXXA==", + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.7.1.tgz", + "integrity": "sha512-bavdk2BA5r3MYalGKZ01u8PGuDBloQmzpBZVhDLrOOv1N943Wq6dcM9GhB3x8b7AbqPMEezauv4PeGkAJfy7FQ==", "license": "MIT", "dependencies": { - "react-router": "7.7.0" + "react-router": "7.7.1" }, "engines": { "node": ">=20.0.0" @@ -4356,19 +4561,6 @@ "react-dom": ">=18" } }, - "node_modules/react-toastify": { - "version": "11.0.5", - "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-11.0.5.tgz", - "integrity": "sha512-EpqHBGvnSTtHYhCPLxML05NLY2ZX0JURbAdNYa6BUkk+amz4wbKBQvoKQAB0ardvSarUBuY4Q4s1sluAzZwkmA==", - "license": "MIT", - "dependencies": { - "clsx": "^2.1.1" - }, - "peerDependencies": { - "react": "^18 || ^19", - "react-dom": "^18 || ^19" - } - }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -4565,6 +4757,16 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/sonner": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.6.tgz", + "integrity": "sha512-yHFhk8T/DK3YxjFQXIrcHT1rGEeTLliVzWbO0xN8GberVun2RiBnxAjXAYpZrqwEVHBG9asI/Li8TAAhN9m59Q==", + "license": "MIT", + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", + "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -4645,13 +4847,13 @@ } }, "node_modules/synckit": { - "version": "0.11.8", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.8.tgz", - "integrity": "sha512-+XZ+r1XGIJGeQk3VvXhT6xx/VpbHsRzsTkGgF6E5RX9TTXD0118l87puaEBZ566FhqblC6U0d4XnubznJDm30A==", + "version": "0.11.11", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.11.tgz", + "integrity": "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==", "dev": true, "license": "MIT", "dependencies": { - "@pkgr/core": "^0.2.4" + "@pkgr/core": "^0.2.9" }, "engines": { "node": "^14.18.0 || >=16.0.0" @@ -4780,9 +4982,9 @@ } }, "node_modules/tw-animate-css": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/tw-animate-css/-/tw-animate-css-1.3.5.tgz", - "integrity": "sha512-t3u+0YNoloIhj1mMXs779P6MO9q3p3mvGn4k1n3nJPqJw/glZcuijG2qTSN4z4mgNRfW5ZC3aXJFLwDtiipZXA==", + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/tw-animate-css/-/tw-animate-css-1.3.6.tgz", + "integrity": "sha512-9dy0R9UsYEGmgf26L8UcHiLmSFTHa9+D7+dAt/G/sF5dCnPePZbfgDYinc7/UzAM7g/baVrmS6m9yEpU46d+LA==", "dev": true, "license": "MIT", "funding": { @@ -4817,16 +5019,16 @@ } }, "node_modules/typescript-eslint": { - "version": "8.37.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.37.0.tgz", - "integrity": "sha512-TnbEjzkE9EmcO0Q2zM+GE8NQLItNAJpMmED1BdgoBMYNdqMhzlbqfdSwiRlAzEK2pA9UzVW0gzaaIzXWg2BjfA==", + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.38.0.tgz", + "integrity": "sha512-FsZlrYK6bPDGoLeZRuvx2v6qrM03I0U0SnfCLPs/XCCPCFD80xU9Pg09H/K+XFa68uJuZo7l/Xhs+eDRg2l3hg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.37.0", - "@typescript-eslint/parser": "8.37.0", - "@typescript-eslint/typescript-estree": "8.37.0", - "@typescript-eslint/utils": "8.37.0" + "@typescript-eslint/eslint-plugin": "8.38.0", + "@typescript-eslint/parser": "8.38.0", + "@typescript-eslint/typescript-estree": "8.38.0", + "@typescript-eslint/utils": "8.38.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4889,14 +5091,14 @@ } }, "node_modules/vite": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.0.5.tgz", - "integrity": "sha512-1mncVwJxy2C9ThLwz0+2GKZyEXuC3MyWtAAlNftlZZXZDP3AJt5FmwcMit/IGGaNZ8ZOB2BNO/HFUB+CpN0NQw==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.0.6.tgz", + "integrity": "sha512-MHFiOENNBd+Bd9uvc8GEsIzdkn1JxMmEeYX35tI3fv0sJBUTfW5tQsoaOwuY4KhBI09A3dUJ/DXf2yxPVPUceg==", "license": "MIT", "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.6", - "picomatch": "^4.0.2", + "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.40.0", "tinyglobby": "^0.2.14" diff --git a/frontend/package.json b/frontend/package.json index 86d75af..f6ce37b 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -14,6 +14,9 @@ "format:fix": "npm run prettier -- --write" }, "dependencies": { + "@radix-ui/react-dropdown-menu": "^2.1.15", + "@radix-ui/react-progress": "^1.1.7", + "@radix-ui/react-separator": "^1.1.7", "@radix-ui/react-slot": "^1.2.3", "@tailwindcss/vite": "^4.1.11", "@tanstack/react-query": "^5.83.0", @@ -21,17 +24,20 @@ "axios": "^1.10.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "date-fns": "^4.1.0", "dotenv": "^17.2.0", "lucide-react": "^0.525.0", + "next-themes": "^0.4.6", "react": "^19.1.0", "react-dom": "^19.1.0", "react-router-dom": "^7.7.0", - "react-toastify": "^11.0.5", + "sonner": "^2.0.6", "tailwind-merge": "^3.3.1", "tailwindcss": "^4.1.11" }, "devDependencies": { "@eslint/js": "^9.29.0", + "@types/date-fns": "^2.5.3", "@types/node": "^24.0.15", "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", @@ -45,6 +51,7 @@ "husky": "^8.0.0", "lint-staged": "^16.1.2", "prettier": "^3.6.2", + "prettier-plugin-tailwindcss": "^0.6.14", "tw-animate-css": "^1.3.5", "typescript": "~5.8.3", "typescript-eslint": "^8.34.1", diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx deleted file mode 100644 index e1cff62..0000000 --- a/frontend/src/App.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { UserProvider } from './context/AuthProvider'; -import Router from './root/Router'; - -function App() { - return ( - - - - ); -} - -export default App; diff --git a/frontend/src/utils/axios.ts b/frontend/src/api/axios.ts similarity index 51% rename from frontend/src/utils/axios.ts rename to frontend/src/api/axios.ts index b612ca6..9ef6ebc 100644 --- a/frontend/src/utils/axios.ts +++ b/frontend/src/api/axios.ts @@ -1,12 +1,15 @@ -import axios from 'axios'; +import axios from "axios"; + +import { BACKEND_URL } from "@/shared/constants/endpoints"; +import { getAccessToken } from "@/shared/utils/local-storage"; export const api = axios.create({ - baseURL: 'http://localhost:8080/api/v1' + baseURL: BACKEND_URL }); api.interceptors.request.use( config => { - const token = localStorage.getItem('token'); + const token = getAccessToken(); if (token) { config.headers.Authorization = `Bearer ${token}`; } @@ -14,3 +17,5 @@ api.interceptors.request.use( }, error => Promise.reject(error) ); + + diff --git a/frontend/src/api/queries/Leaderboard.ts b/frontend/src/api/queries/Leaderboard.ts new file mode 100644 index 0000000..e1a47cf --- /dev/null +++ b/frontend/src/api/queries/Leaderboard.ts @@ -0,0 +1,38 @@ +import type { LeaderboardUser } from "@/shared/types/types"; +import { api } from "../axios"; +import { BACKEND_URL } from "@/shared/constants/endpoints"; +import type { ApiResponse } from "@/shared/types/api"; +import { CURRENT_USER_RANK_QUERY_KEY, LEADERBOARD_QUERY_KEY } from "@/shared/constants/query-keys"; +import { useQuery } from "@tanstack/react-query"; + +const fetchLeaderboard = async (): Promise> => { + const response = await api.get<{ + message: string; + data: LeaderboardUser[]; + }>(`${BACKEND_URL}/api/v1/leaderboard`); + + return response.data; +} + +export const useLeaderboard = () => { + return useQuery({ + queryKey: [LEADERBOARD_QUERY_KEY], + queryFn: fetchLeaderboard, + }); +} + +const fetchCurrentUserRank = async (): Promise> => { + const response = await api.get<{ + message: string; + data: LeaderboardUser; + }>(`${BACKEND_URL}/api/v1/user/leaderboard`); + + return response.data; +} + +export const useCurrentUserRank = () => { + return useQuery({ + queryKey: [CURRENT_USER_RANK_QUERY_KEY], + queryFn: fetchCurrentUserRank, + }); +} \ No newline at end of file diff --git a/frontend/src/api/queries/Overview.ts b/frontend/src/api/queries/Overview.ts new file mode 100644 index 0000000..78e3bba --- /dev/null +++ b/frontend/src/api/queries/Overview.ts @@ -0,0 +1,32 @@ +import type { ApiResponse } from "@/shared/types/api"; +import type { Overview } from "@/shared/types/types"; +import { api } from "../axios"; +import { BACKEND_URL } from "@/shared/constants/endpoints"; +import { useQuery } from "@tanstack/react-query"; +import { OVERVIEW_QUERY_KEY } from "@/shared/constants/query-keys"; + +interface OverviewParams { + year: number; + month: number; +} + +const fetchOverview = async ({ year, month }: OverviewParams): Promise> => { + const response = await api.get<{ + message: string; + data: Overview[]; + }>(`${BACKEND_URL}/api/v1/user/overview`, { + params: { + year, + month + } + }); + + return response.data; +} + +export const useOverview = ({ year, month }: OverviewParams) => { + return useQuery({ + queryKey: [OVERVIEW_QUERY_KEY, year, month], + queryFn: () => fetchOverview({ year, month }), + }); +} \ No newline at end of file diff --git a/frontend/src/api/queries/RecentActivities.ts b/frontend/src/api/queries/RecentActivities.ts new file mode 100644 index 0000000..b39b50d --- /dev/null +++ b/frontend/src/api/queries/RecentActivities.ts @@ -0,0 +1,22 @@ +import type { ApiResponse } from "@/shared/types/api"; +import type { RecentActivity } from "@/shared/types/types"; +import { api } from "../axios"; +import { BACKEND_URL } from "@/shared/constants/endpoints"; +import { useQuery } from "@tanstack/react-query"; +import { RECENT_ACTIVITIES_QUERY_KEY } from "@/shared/constants/query-keys"; + +const fetchRecentActivities = async (): Promise> => { + const response = await api.get<{ + message: string; + data: RecentActivity[]; + }>(`${BACKEND_URL}/api/v1/user/contributions/all`); + + return response.data; +}; + +export const useRecentActivities = () => { + return useQuery({ + queryKey: [RECENT_ACTIVITIES_QUERY_KEY], + queryFn: fetchRecentActivities, + }); +} \ No newline at end of file diff --git a/frontend/src/api/queries/UserBadges.ts b/frontend/src/api/queries/UserBadges.ts new file mode 100644 index 0000000..9f50e85 --- /dev/null +++ b/frontend/src/api/queries/UserBadges.ts @@ -0,0 +1,22 @@ +import { BACKEND_URL } from "@/shared/constants/endpoints"; +import { api } from "../axios"; +import type { ApiResponse } from "@/shared/types/api"; +import type { Badge } from "@/shared/types/types"; +import { USER_BADGES_QUERY_KEY } from "@/shared/constants/query-keys"; +import { useQuery } from "@tanstack/react-query"; + +const fetchUserBadges = async (): Promise> => { + const response = await api.get<{ + message: string; + data: Badge[]; + }>(`${BACKEND_URL}/api/v1/user/badges`); + + return response.data; +} + +export const useUserBadges = () => { + return useQuery({ + queryKey: [USER_BADGES_QUERY_KEY], + queryFn: fetchUserBadges, + }); +} \ No newline at end of file diff --git a/frontend/src/api/queries/UserProfileDetails.ts b/frontend/src/api/queries/UserProfileDetails.ts new file mode 100644 index 0000000..3d4639e --- /dev/null +++ b/frontend/src/api/queries/UserProfileDetails.ts @@ -0,0 +1,22 @@ +import type { User } from "@/shared/types/types"; +import { api } from "../axios"; +import { useQuery } from "@tanstack/react-query"; +import type { ApiResponse } from "@/shared/types/api"; +import { LOGGED_IN_USER_QUERY_KEY } from "@/shared/constants/query-keys"; +import { BACKEND_URL } from "@/shared/constants/endpoints"; + +const fetchLoggedInUser = async (): Promise> => { + const response = await api.get<{ + message: string; + data: User; + }>(`${BACKEND_URL}/api/v1/auth/user`); + + return response.data; +}; + +export const useLoggedInUser = () => { + return useQuery({ + queryKey: [LOGGED_IN_USER_QUERY_KEY], + queryFn: fetchLoggedInUser, + }); +}; diff --git a/frontend/src/utils/react-query.ts b/frontend/src/api/react-query.ts similarity index 76% rename from frontend/src/utils/react-query.ts rename to frontend/src/api/react-query.ts index a97896c..29c17f0 100644 --- a/frontend/src/utils/react-query.ts +++ b/frontend/src/api/react-query.ts @@ -1,4 +1,4 @@ -import { QueryClient } from '@tanstack/react-query'; +import { QueryClient } from "@tanstack/react-query"; export const queryClient = new QueryClient({ defaultOptions: { diff --git a/frontend/src/assets/default-profile-pic.svg b/frontend/src/assets/default-profile-pic.svg new file mode 100644 index 0000000..a8d1174 --- /dev/null +++ b/frontend/src/assets/default-profile-pic.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/src/assets/react.svg b/frontend/src/assets/react.svg deleted file mode 100644 index 6c87de9..0000000 --- a/frontend/src/assets/react.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/frontend/src/context/AuthProvider.tsx b/frontend/src/context/AuthProvider.tsx deleted file mode 100644 index da7b91a..0000000 --- a/frontend/src/context/AuthProvider.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { createContext, useMemo, useState, type ReactNode } from 'react'; - -export type User = { - githubId: string; - githubUsername: string; - avatarUrl: string; -}; - -export interface UserContextInterface { - user: User; - login: (user: User, token: string) => void; - logout: () => void; -} - -const defaultUser: User = { - githubId: '', - githubUsername: '', - avatarUrl: '' -}; - -const defaultState: UserContextInterface = { - user: defaultUser, - login: () => { - throw new Error('login must be used within UserProvider'); - }, - logout: () => { - throw new Error('logout must be used within UserProvider'); - } -}; - -export const UserContext = createContext(defaultState); - -type UserProviderProps = { - children: ReactNode; -}; - -export const UserProvider = ({ children }: UserProviderProps) => { - const [user, setUser] = useState(defaultUser); - - const login = (newUser: User, token: string) => { - setUser(newUser); - localStorage.setItem('token', token); - }; - - const logout = () => { - setUser(defaultUser); - localStorage.removeItem('token'); - }; - - const value = useMemo(() => ({ user, login, logout }), [user]); - - return {children}; -}; diff --git a/frontend/src/features/Login/components/LoginComponent.tsx b/frontend/src/features/Login/components/LoginComponent.tsx new file mode 100644 index 0000000..9084984 --- /dev/null +++ b/frontend/src/features/Login/components/LoginComponent.tsx @@ -0,0 +1,51 @@ +import { Button } from "@/shared/components/ui/button"; +import { + Card, + CardContent, + CardFooter, + CardHeader +} from "@/shared/components/ui/card"; +import { GITHUB_AUTH_URL } from "@/shared/constants/endpoints"; +import Coder from "@/assets/coder.svg"; +import { ACCESS_TOKEN_KEY } from "@/shared/constants/local-storage"; + +const LoginComponent = () => { + const handleGithubLogin = () => { + window.location.href = GITHUB_AUTH_URL || ""; + localStorage.setItem(ACCESS_TOKEN_KEY, 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjIsIklzQWRtaW4iOmZhbHNlLCJleHAiOjE3NTQxMzI3NTh9.VKEboNEvSeVKYnqLuBrvTyvx9IglhYzEyeE57x7Qzto') + }; + + return ( + + + Developer Illustration + + + + +
+
+ + +

+ No idea where to start? Try this{" "} + + Code Triage + +

+
+
+ ); +}; + +export default LoginComponent; diff --git a/frontend/src/features/Login/index.tsx b/frontend/src/features/Login/index.tsx new file mode 100644 index 0000000..4114e41 --- /dev/null +++ b/frontend/src/features/Login/index.tsx @@ -0,0 +1,12 @@ +import AuthLayout from "@/shared/layout/AuthLayout"; +import LoginComponent from "@/features/Login/components/LoginComponent"; + +const Login = () => { + return ( + + + + ); +}; + +export default Login; diff --git a/frontend/src/features/MyContributions/index.tsx b/frontend/src/features/MyContributions/index.tsx new file mode 100644 index 0000000..3a454a7 --- /dev/null +++ b/frontend/src/features/MyContributions/index.tsx @@ -0,0 +1,7 @@ +import UserDashboardLayout from "@/shared/layout/UserDashboardLayout"; + +const MyContributions = () => { + return ; +}; + +export default MyContributions; diff --git a/frontend/src/features/UserDashboard/components/Leaderboard.tsx b/frontend/src/features/UserDashboard/components/Leaderboard.tsx new file mode 100644 index 0000000..b1482e4 --- /dev/null +++ b/frontend/src/features/UserDashboard/components/Leaderboard.tsx @@ -0,0 +1,87 @@ +import { useState, type FC } from "react"; +import clsx from "clsx"; + +import { Button } from "@/shared/components/ui/button"; +import { Card } from "@/shared/components/ui/card"; +import LeaderboardCard from "@/features/UserDashboard/components/LeaderboardCard"; +import { useCurrentUserRank, useLeaderboard } from "@/api/queries/Leaderboard"; +import { TrendingUp } from "lucide-react"; + +interface LeaderboardProps { + className?: string; +} + +const Leaderboard: FC = ({ className }) => { + const [viewAll, setViewAll] = useState(false); + + const handleViewAll = () => { + setViewAll(!viewAll); + }; + + const { data, isLoading } = useLeaderboard(); + const leaderboard = data?.data ?? []; + + const leaderboardData = viewAll ? leaderboard : leaderboard?.slice(0, 10); + + const { data: userData } = useCurrentUserRank(); + const currentUser = userData?.data; + + return ( + +
+

Leader Board

+ +
+ + {isLoading ? ( +
+
+
+ ) : leaderboardData?.length === 0 ? ( +
+ +

+ No leaderboard data +

+
+ ) : ( + <> +
+ {leaderboardData?.map(user => ( + + ))} +
+ {!viewAll && ( +
+ +
+ )} + + )} +
+ ); +}; + +export default Leaderboard; diff --git a/frontend/src/features/UserDashboard/components/LeaderboardCard.tsx b/frontend/src/features/UserDashboard/components/LeaderboardCard.tsx new file mode 100644 index 0000000..f217161 --- /dev/null +++ b/frontend/src/features/UserDashboard/components/LeaderboardCard.tsx @@ -0,0 +1,46 @@ +import type { FC } from "react"; + +import Coin from "@/shared/components/common/Coin"; +import { Card, CardContent } from "@/shared/components/ui/card"; + +interface LeaderboardCardProps { + rank: number; + username: string; + repositories: number; + balance: number; +} + +const LeaderboardCard: FC = ({ + rank, + username, + repositories, + balance +}) => { + return ( +
+ + +
+ {rank} +
+
+
{username}
+
+ Contributed to{" "} + + {repositories}{" "} + {repositories > 1 ? "Repositories" : "Repository"} + +
+
+
+ + {balance} +
+
+
+
+ ); +}; + +export default LeaderboardCard; \ No newline at end of file diff --git a/frontend/src/features/UserDashboard/components/Overview.tsx b/frontend/src/features/UserDashboard/components/Overview.tsx new file mode 100644 index 0000000..daeb90b --- /dev/null +++ b/frontend/src/features/UserDashboard/components/Overview.tsx @@ -0,0 +1,118 @@ +import { useState, type FC } from "react"; +import clsx from "clsx"; +import { Card } from "@/shared/components/ui/card"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger +} from "@/shared/components/ui/dropdown-menu"; +import OverviewCard from "@/features/UserDashboard/components/OverviewCard"; +import { ChevronDown, TrendingUp } from "lucide-react"; +import { format, subMonths } from "date-fns"; +import { useOverview } from "@/api/queries/Overview"; + +interface OverviewProps { + className?: string; +} + +const getLastNMonths = (n: number) => { + return Array.from({ length: n }).map((_, i) => { + const date = subMonths(new Date(), i); + return { + label: format(date, "MMMM yyyy"), + month: date.getMonth() + 1, + year: date.getFullYear() + }; + }); +}; + +const Overview: FC = ({ className }) => { + const monthOptions = getLastNMonths(3); + const [selectedPeriod, setSelectedPeriod] = useState<{ + month: number; + year: number; + }>(monthOptions[0]); + + const { data, isLoading } = useOverview(selectedPeriod); + const overview = data?.data ?? []; + + const overviewData = overview?.filter(data => { + const date = new Date(data.month); + return ( + date.getFullYear() === selectedPeriod.year && + date.getMonth() + 1 === selectedPeriod.month + ); + }); + + return ( + +
+

Overview

+ + + {monthOptions.find( + opt => + opt.month === selectedPeriod.month && + opt.year === selectedPeriod.year + )?.label ?? "Select Month"} + + + + {monthOptions.map(option => ( + + setSelectedPeriod({ month: option.month, year: option.year }) + } + className={clsx( + "cursor-pointer rounded-sm px-3 py-2 text-sm hover:bg-gray-100", + selectedPeriod.month === option.month && + selectedPeriod.year === option.year && + "bg-cc-app-gray-background" + )} + > + {option.label} + + ))} + + +
+ {isLoading ? ( +
+
+
+ ) : overviewData?.length === 0 ? ( +
+ +

+ No overview data +

+

+ No activity found for the selected period. +
+ Try selecting a different month or start contributing! +

+
+ ) : ( +
+ {overviewData?.map(user => ( + + ))} +
+ )} +
+ ); +}; + +export default Overview; diff --git a/frontend/src/features/UserDashboard/components/OverviewCard.tsx b/frontend/src/features/UserDashboard/components/OverviewCard.tsx new file mode 100644 index 0000000..81e1976 --- /dev/null +++ b/frontend/src/features/UserDashboard/components/OverviewCard.tsx @@ -0,0 +1,32 @@ +import { type FC } from "react"; +import Coin from "@/shared/components/common/Coin"; +import { Card } from "@/shared/components/ui/card"; + +interface OverviewCardProps { + type: string; + count: number; + totalCoins: number; +} + +const OverviewCard: FC = ({ type, count, totalCoins }) => { + return ( + +
+

{type}

+
+ + {count} + +
+ + + {totalCoins} + +
+
+
+
+ ); +}; + +export default OverviewCard; diff --git a/frontend/src/features/UserDashboard/components/RecentActivities.tsx b/frontend/src/features/UserDashboard/components/RecentActivities.tsx new file mode 100644 index 0000000..614807a --- /dev/null +++ b/frontend/src/features/UserDashboard/components/RecentActivities.tsx @@ -0,0 +1,89 @@ +import { useState, type FC } from "react"; +import clsx from "clsx"; +import { Button } from "@/shared/components/ui/button"; +import { Card } from "@/shared/components/ui/card"; +import ActivityCard from "@/shared/components/common/ActivityCard"; +import { useRecentActivities } from "@/api/queries/RecentActivities"; +import { Link } from "react-router-dom"; +import { TrendingUp } from "lucide-react"; + +interface RecentActivitiesProps { + className?: string; +} + +const RecentActivities: FC = ({ className }) => { + const [viewAll, setViewAll] = useState(false); + + const handleViewAll = () => { + setViewAll(!viewAll); + }; + + const { data, isLoading } = useRecentActivities(); + const recentActivities = data?.data ?? []; + const recentActivitiesData = viewAll + ? recentActivities + : recentActivities?.slice(0, 4); + + return ( + +
+

Recent Activities

+ +
+ + {isLoading ? ( +
+
+
+ ) : recentActivitiesData?.length === 0 ? ( +
+ +

+ No recent activities found +

+
+ ) : ( +
+ {recentActivitiesData?.map((activity, index) => ( + + ))} + {!viewAll && ( +
+ + How does points work? + +
+ )} +
+ )} +
+ ); +}; + +export default RecentActivities; diff --git a/frontend/src/features/UserDashboard/components/UserDashboardComponent.tsx b/frontend/src/features/UserDashboard/components/UserDashboardComponent.tsx new file mode 100644 index 0000000..176d269 --- /dev/null +++ b/frontend/src/features/UserDashboard/components/UserDashboardComponent.tsx @@ -0,0 +1,17 @@ +import Leaderboard from "@/features/UserDashboard/components/Leaderboard"; +import RecentActivities from "@/features/UserDashboard/components/RecentActivities"; +import Overview from "@/features/UserDashboard/components/Overview"; + +const UserDashboardComponent = () => { + return ( +
+ +
+ + +
+
+ ); +}; + +export default UserDashboardComponent; diff --git a/frontend/src/features/UserDashboard/index.tsx b/frontend/src/features/UserDashboard/index.tsx new file mode 100644 index 0000000..dcc6332 --- /dev/null +++ b/frontend/src/features/UserDashboard/index.tsx @@ -0,0 +1,12 @@ +import UserDashboardLayout from "@/shared/layout/UserDashboardLayout"; +import UserDashboardComponent from "@/features/UserDashboard/components/UserDashboardComponent"; + +const UserDashboard = () => { + return ( + + + + ); +}; + +export default UserDashboard; diff --git a/frontend/src/index.css b/frontend/src/index.css index d003164..d277bc1 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -56,12 +56,12 @@ --sidebar-border: oklch(0.922 0 0); --sidebar-ring: oklch(0.708 0 0); - --cc-light-blue: oklch(0.6686 0.1358 231.66); - --cc-mid-blue:oklch(0.6163 0.140573 239.7492); - --cc-dark-blue:oklch(0.4668 0.1625 256.62); - --cc-app-blue:oklch(0.3876 0.1761 261.76); - --cc-app-gray-background:oklch(0.9585 0.0195 270.21); - --cc-app-orange:oklch(0.7362 0.1641 62.07); + --cc-app-light-blue: oklch(0.6686 0.1358 231.66); + --cc-app-sky-blue: oklch(0.6163 0.140573 239.7492); + --cc-app-mid-blue: oklch(0.4668 0.1625 256.62); + --cc-app-blue: oklch(0.3876 0.1761 261.76); + --cc-app-gray-background: oklch(0.9585 0.0195 270.21); + --cc-app-orange: oklch(0.7362 0.1641 62.07); } @media (prefers-color-scheme: light) { @@ -101,34 +101,6 @@ --color-chart-5: var(--chart-5); --color-sidebar: var(--sidebar); --color-sidebar-foreground: var(--sidebar-foreground); - --color-sidebar-prima --radius-md: calc(var(--radius) - 2px); - --radius-lg: var(--radius); - --radius-xl: calc(var(--radius) + 4px); - --color-background: var(--background); - --color-foreground: var(--foreground); - --color-card: var(--card); - --color-card-foreground: var(--card-foreground); - --color-popover: var(--popover); - --color-popover-foreground: var(--popover-foreground); - --color-primary: var(--primary); - --color-primary-foreground: var(--primary-foreground); - --color-secondary: var(--secondary); - --color-secondary-foreground: var(--secondary-foreground); - --color-muted: var(--muted); - --color-muted-foreground: var(--muted-foreground); - --color-accent: var(--accent); - --color-accent-foreground: var(--accent-foreground); - --color-destructive: var(--destructive); - --color-border: var(--border); - --color-input: var(--input); - --color-ring: var(--ring); - --color-chart-1: var(--chart-1); - --color-chart-2: var(--chart-2); - --color-chart-3: var(--chart-3); - --color-chart-4: var(--chart-4); - --color-chart-5: var(--chart-5); - --color-sidebar: var(--sidebar); - --color-sidebar-foreground: var(--sidebar-foreground); --color-sidebar-primary: var(--sidebar-primary); --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); --color-sidebar-acry: var(--sidebar-primary); @@ -138,9 +110,9 @@ --color-sidebar-border: var(--sidebar-border); --color-sidebar-ring: var(--sidebar-ring); - --color-cc-light-blue: var(--cc-light-blue); - --color-cc-mid-blue: var(--cc-mid-blue); - --color-cc-dark-blue: var(--cc-dark-blue); + --color-cc-app-light-blue: var(--cc-app-light-blue); + --color-cc-app-sky-blue: var(--cc-app-sky-blue); + --color-cc-app-mid-blue: var(--cc-app-mid-blue); --color-cc-app-blue: var(--cc-app-blue); --color-cc-app-gray-background: var(--cc-app-gray-background); --color-cc-app-orange: var(--cc-app-orange); @@ -184,7 +156,17 @@ * { @apply border-border outline-ring/50; } + body { @apply bg-background text-foreground; } -} \ No newline at end of file + + .no-scrollbar::-webkit-scrollbar { + display: none; + } + + .no-scrollbar { + -ms-overflow-style: none; + scrollbar-width: none; + } +} diff --git a/frontend/src/lib/utils.ts b/frontend/src/lib/utils.ts deleted file mode 100644 index bd0c391..0000000 --- a/frontend/src/lib/utils.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { clsx, type ClassValue } from "clsx" -import { twMerge } from "tailwind-merge" - -export function cn(...inputs: ClassValue[]) { - return twMerge(clsx(inputs)) -} diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index cb2764c..817fad2 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -1,14 +1,20 @@ -import { StrictMode } from 'react'; -import { createRoot } from 'react-dom/client'; -import './index.css'; -import App from './App.tsx'; -import { QueryClientProvider } from '@tanstack/react-query'; -import { queryClient } from './utils/react-query.ts'; +import { StrictMode } from "react"; +import { createRoot } from "react-dom/client"; +import { QueryClientProvider } from "@tanstack/react-query"; +import { Toaster } from "sonner"; -createRoot(document.getElementById('root')!).render( +import Router from "@/root/Router"; +import { AuthProvider } from "@/shared/context/AuthProvider"; +import { queryClient } from "@/api/react-query.ts"; +import "./index.css"; + +createRoot(document.getElementById("root")!).render( - + + + + ); diff --git a/frontend/src/pages/Login/index.tsx b/frontend/src/pages/Login/index.tsx deleted file mode 100644 index 7ee300a..0000000 --- a/frontend/src/pages/Login/index.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import type { FC } from 'react'; -import LoginComponent from './loginComponent'; -import AuthLayout from '@/shared/components/AuthLayout'; - -const Login: FC = () => { - return ( - - - - ); -}; - -export default Login; diff --git a/frontend/src/pages/Login/loginComponent.tsx b/frontend/src/pages/Login/loginComponent.tsx deleted file mode 100644 index d60a061..0000000 --- a/frontend/src/pages/Login/loginComponent.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import { Button } from '@/components/ui/button'; -import { Card, CardContent, CardFooter, CardHeader } from '@/components/ui/card'; -import { type FC } from 'react'; -import Coder from '@/assets/coder.svg'; -import { env } from '@/utils/endpoints'; - -const LoginComponent: FC = () => { - const handleGithubLogin = () => { - window.location.href = env.GithubAuthUrl || ''; - }; - - return ( - - - Developer Illustration - - - - -
-
- - -

- No idea where to start? Try this{' '} - - Code Triage - -

-
-
- ); -}; - -export default LoginComponent; diff --git a/frontend/src/root/PrivateRoutes.tsx b/frontend/src/root/PrivateRoutes.tsx deleted file mode 100644 index feebec9..0000000 --- a/frontend/src/root/PrivateRoutes.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { useEffect, type ReactNode } from 'react'; -import { Navigate } from 'react-router-dom'; -import { toast } from 'react-toastify'; -import 'react-toastify/dist/ReactToastify.css'; - -interface PrivateRoutesProps { - children: ReactNode; -} - -const PrivateRoutes: React.FC = ({ children }) => { - const localStorageToken = localStorage.getItem('token'); - - useEffect(() => { - if (!localStorageToken) { - toast.warning('You need to login to proceed!'); - } - }, [localStorageToken]); - - return localStorageToken ? children : ; -}; - -export default PrivateRoutes; diff --git a/frontend/src/root/Router.tsx b/frontend/src/root/Router.tsx index d74e198..dc00ece 100644 --- a/frontend/src/root/Router.tsx +++ b/frontend/src/root/Router.tsx @@ -1,13 +1,14 @@ -import { createBrowserRouter, RouterProvider } from 'react-router-dom'; -import PrivateRoutes from './PrivateRoutes'; -import { routesConfig, type RoutesType } from './routesConfig'; +import { RouterProvider, createBrowserRouter } from "react-router-dom"; + +import WithAuth from "@/shared/HOC/WithAuth"; +import { type RoutesType, routesConfig } from "@/root/routes-config"; const generateRoutes = (routes: RoutesType[]) => { return routes.map(({ path, element, isProtected }) => { let wrappedElement = element; if (isProtected) { - wrappedElement = {wrappedElement}; + wrappedElement = {wrappedElement}; } return { path, element: wrappedElement }; diff --git a/frontend/src/root/routeConstants.ts b/frontend/src/root/routeConstants.ts deleted file mode 100644 index 48ed62e..0000000 --- a/frontend/src/root/routeConstants.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const ROUTES = { - LOGIN: '/login', - LANDING: '/' -}; diff --git a/frontend/src/root/routes-config.tsx b/frontend/src/root/routes-config.tsx new file mode 100644 index 0000000..62633f1 --- /dev/null +++ b/frontend/src/root/routes-config.tsx @@ -0,0 +1,34 @@ +import type { ReactNode } from "react"; + +import Login from "@/features/Login"; +import MyContributions from "@/features/MyContributions"; +import UserDashboard from "@/features/UserDashboard"; +import { + LOGIN_PATH, + MY_CONTRIBUTIONS_PATH, + USER_DASHBOARD_PATH +} from "@/shared/constants/routes"; + +export interface RoutesType { + path: string; + element: ReactNode; + isProtected?: boolean; +} + +export const routesConfig: RoutesType[] = [ + { + path: LOGIN_PATH, + element: , + isProtected: false + }, + { + path: USER_DASHBOARD_PATH, + element: , + isProtected: false + }, + { + path: MY_CONTRIBUTIONS_PATH, + element: , + isProtected: false + } +]; diff --git a/frontend/src/root/routesConfig.tsx b/frontend/src/root/routesConfig.tsx deleted file mode 100644 index 28f92c5..0000000 --- a/frontend/src/root/routesConfig.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import type { ReactNode } from 'react'; -import Login from '../pages/Login'; -import { ROUTES } from './routeConstants'; - -export interface RoutesType { - path: string; - element: ReactNode; - isProtected: boolean; -} - -export const routesConfig: RoutesType[] = [ - { - path: ROUTES.LOGIN, - element: , - isProtected: false - } - // { - // path: //path, - // element: ( - // - // {/* protected component */} - // - // ), - // }, -]; diff --git a/frontend/src/shared/HOC/WithAuth.tsx b/frontend/src/shared/HOC/WithAuth.tsx new file mode 100644 index 0000000..fb44e44 --- /dev/null +++ b/frontend/src/shared/HOC/WithAuth.tsx @@ -0,0 +1,29 @@ +import { type FC, type ReactNode, useEffect } from "react"; +import { Navigate, useLocation, useNavigate } from "react-router-dom"; + +import { LOGIN_PATH } from "@/shared/constants/routes"; +import { getAccessToken } from "@/shared/utils/local-storage"; + +interface WithAuthProps { + children: ReactNode; +} + +const WithAuth: FC = ({ children }) => { + const navigate = useNavigate(); + const location = useLocation(); + const userAccessToken = getAccessToken(); + + useEffect(() => { + if (!userAccessToken) { + navigate(LOGIN_PATH, { replace: true }); + } + }, [userAccessToken, location.pathname, navigate]); + + if (!userAccessToken) { + return ; + } + + return <>{children}; +}; + +export default WithAuth; diff --git a/frontend/src/shared/components/AuthLayout.tsx b/frontend/src/shared/components/AuthLayout.tsx deleted file mode 100644 index 5dd6143..0000000 --- a/frontend/src/shared/components/AuthLayout.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import { useEffect, type FC, type ReactNode } from 'react'; -import { useLocation, useNavigate } from 'react-router-dom'; -import { getAccessToken, getUserData } from '../utils/local-storage'; -import { ROUTES } from '@/root/routeConstants'; -import { Card } from '@/components/ui/card'; -import { CheckCircle } from 'lucide-react'; - -interface AuthLayoutProps { - children: ReactNode; -} - -const AuthLayout: FC = ({ children }) => { - const location = useLocation(); - const navigate = useNavigate(); - - useEffect(() => { - const userAccessToken = getAccessToken(); - const userData = getUserData(); - const shouldRedirect = [ROUTES.LOGIN].includes(location.pathname); - - if (userAccessToken && userData && shouldRedirect) { - navigate(ROUTES.LANDING); - } - }, [navigate, location.pathname]); - - return ( -
-
- -
-
- {Array.from({ length: 64 }).map((_, i) => ( -
- ))} -
-
- -
-
-
-
-

- Code Curiosity -

-
- -
- {[ - 'Earn and Upskill', - 'Set Your Goals', - 'Leader Board', - 'Open Source Contribution' - ].map((text, i) => ( -
- - - {text} - -
- ))} -
-
-
-
- - {/* Bottom Grid Decoration */} -
-
- {Array.from({ length: 64 }).map((_, i) => ( -
- ))} -
-
- - - - {children} - -
-
- ); -}; - -export default AuthLayout; diff --git a/frontend/src/shared/components/UserDashboard/Navbar.tsx b/frontend/src/shared/components/UserDashboard/Navbar.tsx new file mode 100644 index 0000000..b509d22 --- /dev/null +++ b/frontend/src/shared/components/UserDashboard/Navbar.tsx @@ -0,0 +1,30 @@ +import { Link, useLocation } from "react-router-dom"; + +import { Card } from "@/shared/components/ui/card"; +import { USER_DASHBOARD_NAVBAR_OPTIONS } from "@/shared/types/navbar"; + +const Navbar = () => { + const location = useLocation(); + + const isActive = (path: string) => location.pathname === path; + + return ( + + {USER_DASHBOARD_NAVBAR_OPTIONS.map(option => ( + + {option.name} + + ))} + + ); +}; + +export default Navbar; diff --git a/frontend/src/shared/components/UserDashboard/UserBadges.tsx b/frontend/src/shared/components/UserDashboard/UserBadges.tsx new file mode 100644 index 0000000..0cc35a7 --- /dev/null +++ b/frontend/src/shared/components/UserDashboard/UserBadges.tsx @@ -0,0 +1,50 @@ +import { useUserBadges } from "@/api/queries/UserBadges"; +import { Star } from "lucide-react"; +import type { Badge } from "@/shared/types/types"; + +const badgeColorMap: Record = { + BEGINNER: "text-[#cd7f32]", + INTERMEDIATE: "text-[#c0c0c0]", + ADVANCED: "text-[#ffd700]", + CUSTOM: "text-orange-400", +}; + +const UserBadges = () => { + const { data } = useUserBadges(); + const badges = data?.data ?? []; + + const grouped = badges.reduce>((acc, badge) => { + const type = badge.badgeType.toUpperCase(); + if (!acc[type]) acc[type] = []; + acc[type].push(badge); + return acc; + }, {}); + + return ( +
+

+ BADGES +

+
+ {Object.entries(grouped).map(([type, badgeList]) => { + const color = badgeColorMap[type] ?? "text-gray-400"; + return ( +
+ + {badgeList.length > 1 && ( + ×{badgeList.length} + )} +
+ {type}
+
+
+ ); + })} +
+
+ ); +}; + +export default UserBadges; diff --git a/frontend/src/shared/components/UserDashboard/UserGoals.tsx b/frontend/src/shared/components/UserDashboard/UserGoals.tsx new file mode 100644 index 0000000..f6cdd95 --- /dev/null +++ b/frontend/src/shared/components/UserDashboard/UserGoals.tsx @@ -0,0 +1,42 @@ +import { Progress } from "@/shared/components/ui/progress"; +const goals = [ + { name: "Issue Resolve", current: 2, total: 5, progress: 40 }, + { name: "PR Review", current: 6, total: 8, progress: 75 }, + { name: "PR Merge", current: 2, total: 2, progress: 100 }, + { name: "PR Close", current: 1, total: 5, progress: 20 }, + { name: "PR Close", current: 1, total: 5, progress: 20 }, + { name: "PR Close", current: 1, total: 5, progress: 20 }, + { name: "PR Close", current: 1, total: 5, progress: 20 }, + { name: "PR Close", current: 1, total: 5, progress: 20 }, + { name: "PR Close", current: 1, total: 5, progress: 20 }, + { name: "PR Close", current: 1, total: 5, progress: 20 } +]; + +const UserGoals = () => { + return ( +
+

+ MY GOALS (BEGINNER) +

+
+ {goals.map((goal, index) => ( +
+
+ {goal.name} + + {goal.current}/{goal.total} + +
+ +
+ ))} +
+
+ ); +}; + +export default UserGoals; diff --git a/frontend/src/shared/components/UserDashboard/UserProfileCard.tsx b/frontend/src/shared/components/UserDashboard/UserProfileCard.tsx new file mode 100644 index 0000000..36918e5 --- /dev/null +++ b/frontend/src/shared/components/UserDashboard/UserProfileCard.tsx @@ -0,0 +1,28 @@ +import { Card } from "@/shared/components/ui/card"; +import { Separator } from "@/shared/components/ui/separator"; +import UserProfileDetails from "@/shared/components/UserDashboard/UserProfileDetails"; +import UserBadges from "@/shared/components/UserDashboard/UserBadges"; +import UserGoals from "@/shared/components/UserDashboard/UserGoals"; + +const UserProfileCard = () => { + return ( + +
+
+
+ {Array.from({ length: 30 }).map((_, i) => ( +
+ ))} +
+
+ + + + + +
+ + ); +}; + +export default UserProfileCard; diff --git a/frontend/src/shared/components/UserDashboard/UserProfileDetails.tsx b/frontend/src/shared/components/UserDashboard/UserProfileDetails.tsx new file mode 100644 index 0000000..111faf4 --- /dev/null +++ b/frontend/src/shared/components/UserDashboard/UserProfileDetails.tsx @@ -0,0 +1,62 @@ +import { ExternalLink, MoreHorizontal } from "lucide-react"; + +import Coin from "@/shared/components/common/Coin"; +import { Button } from "@/shared/components/ui/button"; +import DefaultProfilePic from "@/assets/default-profile-pic.svg" +import { Separator } from "@/shared/components/ui/separator"; +import { useLoggedInUser } from "@/api/queries/UserProfileDetails"; +import { Link } from "react-router-dom"; + +const UserProfileDetails = () => { + const { data } = useLoggedInUser(); + const user = data?.data + + return ( +
+
+
+
+ Profile +
+
+ +
+

{user?.githubUsername || "username"}

+ + + + +
+
+ +
+
+ + {user?.currentBalance || "0"} +
+
+ + +
+
+
+ ); +}; + +export default UserProfileDetails; diff --git a/frontend/src/shared/components/common/ActivityCard.tsx b/frontend/src/shared/components/common/ActivityCard.tsx new file mode 100644 index 0000000..5d55a75 --- /dev/null +++ b/frontend/src/shared/components/common/ActivityCard.tsx @@ -0,0 +1,58 @@ +import type { FC } from "react"; +import Coin from "@/shared/components/common/Coin"; + +interface ActivityCardProps { + contributionType: string; + repositoryName: string; + contributedAt: string; + balanceChange: number; + showLine: boolean; +} + +const ActivityCard: FC = ({ + contributionType, + repositoryName, + contributedAt, + balanceChange, + showLine = true +}) => { + return ( +
+ {showLine && ( +
+ )} + +
+ +
+
+
+ {contributionType} +
+
+ Contributed to{" "} + + <{repositoryName}> + +
+
+ Contributed on {contributedAt} +
+
+ + {balanceChange && ( +
+ + {balanceChange < 0 ? `-${Math.abs(balanceChange)}` : balanceChange} +
+ )} +
+
+ ); +}; + +export default ActivityCard; diff --git a/frontend/src/shared/components/common/Coin.tsx b/frontend/src/shared/components/common/Coin.tsx new file mode 100644 index 0000000..e85fdbd --- /dev/null +++ b/frontend/src/shared/components/common/Coin.tsx @@ -0,0 +1,9 @@ +const Coin = () => { + return ( +
+
+
+ ); +}; + +export default Coin; diff --git a/frontend/src/components/ui/button.tsx b/frontend/src/shared/components/ui/button.tsx similarity index 70% rename from frontend/src/components/ui/button.tsx rename to frontend/src/shared/components/ui/button.tsx index a2df8dc..61ab42c 100644 --- a/frontend/src/components/ui/button.tsx +++ b/frontend/src/shared/components/ui/button.tsx @@ -1,15 +1,15 @@ -import * as React from "react" -import { Slot } from "@radix-ui/react-slot" -import { cva, type VariantProps } from "class-variance-authority" +import * as React from "react"; +import { type VariantProps, cva } from "class-variance-authority"; +import { Slot } from "@radix-ui/react-slot"; -import { cn } from "@/lib/utils" +import { cn } from "@/shared/utils/tailwindcss"; const buttonVariants = cva( "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", { variants: { variant: { - default: + primary: "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90", destructive: "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", @@ -20,20 +20,24 @@ const buttonVariants = cva( ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", link: "text-primary underline-offset-4 hover:underline", + ccAppOutlineMidBlue: + "bg-cc-app-mid-blue hover:bg-cc-app-blue rounded-sm border border-white text-white", + ccAppOutline: + "border-cc-app-mid-blue text-cc-app-blue hover:bg-cc-app-mid-blue/5 rounded-sm border focus:outline-none" }, size: { - default: "h-9 px-4 py-2 has-[>svg]:px-3", + md: "h-9 px-4 py-2 has-[>svg]:px-3", sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5", lg: "h-10 rounded-md px-6 has-[>svg]:px-4", - icon: "size-9", - }, + icon: "size-9" + } }, defaultVariants: { - variant: "default", - size: "default", - }, + variant: "primary", + size: "md" + } } -) +); function Button({ className, @@ -43,9 +47,9 @@ function Button({ ...props }: React.ComponentProps<"button"> & VariantProps & { - asChild?: boolean + asChild?: boolean; }) { - const Comp = asChild ? Slot : "button" + const Comp = asChild ? Slot : "button"; return ( - ) + ); } -export { Button, buttonVariants } +export { Button, buttonVariants }; diff --git a/frontend/src/components/ui/card.tsx b/frontend/src/shared/components/ui/card.tsx similarity index 93% rename from frontend/src/components/ui/card.tsx rename to frontend/src/shared/components/ui/card.tsx index d05bbc6..f27e167 100644 --- a/frontend/src/components/ui/card.tsx +++ b/frontend/src/shared/components/ui/card.tsx @@ -1,6 +1,6 @@ -import * as React from "react" +import * as React from "react"; -import { cn } from "@/lib/utils" +import { cn } from "@/shared/utils/tailwindcss"; function Card({ className, ...props }: React.ComponentProps<"div">) { return ( @@ -12,7 +12,7 @@ function Card({ className, ...props }: React.ComponentProps<"div">) { )} {...props} /> - ) + ); } function CardHeader({ className, ...props }: React.ComponentProps<"div">) { @@ -25,7 +25,7 @@ function CardHeader({ className, ...props }: React.ComponentProps<"div">) { )} {...props} /> - ) + ); } function CardTitle({ className, ...props }: React.ComponentProps<"div">) { @@ -35,7 +35,7 @@ function CardTitle({ className, ...props }: React.ComponentProps<"div">) { className={cn("leading-none font-semibold", className)} {...props} /> - ) + ); } function CardDescription({ className, ...props }: React.ComponentProps<"div">) { @@ -45,7 +45,7 @@ function CardDescription({ className, ...props }: React.ComponentProps<"div">) { className={cn("text-muted-foreground text-sm", className)} {...props} /> - ) + ); } function CardAction({ className, ...props }: React.ComponentProps<"div">) { @@ -58,7 +58,7 @@ function CardAction({ className, ...props }: React.ComponentProps<"div">) { )} {...props} /> - ) + ); } function CardContent({ className, ...props }: React.ComponentProps<"div">) { @@ -68,7 +68,7 @@ function CardContent({ className, ...props }: React.ComponentProps<"div">) { className={cn("px-6", className)} {...props} /> - ) + ); } function CardFooter({ className, ...props }: React.ComponentProps<"div">) { @@ -78,15 +78,15 @@ function CardFooter({ className, ...props }: React.ComponentProps<"div">) { className={cn("flex items-center px-6 [.border-t]:pt-6", className)} {...props} /> - ) + ); } export { Card, - CardHeader, - CardFooter, - CardTitle, CardAction, - CardDescription, CardContent, -} + CardDescription, + CardFooter, + CardHeader, + CardTitle +}; diff --git a/frontend/src/shared/components/ui/dropdown-menu.tsx b/frontend/src/shared/components/ui/dropdown-menu.tsx new file mode 100644 index 0000000..05a5157 --- /dev/null +++ b/frontend/src/shared/components/ui/dropdown-menu.tsx @@ -0,0 +1,255 @@ +import * as React from "react" +import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu" +import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react" + +import { cn } from "@/shared/utils/tailwindcss" + +function DropdownMenu({ + ...props +}: React.ComponentProps) { + return +} + +function DropdownMenuPortal({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function DropdownMenuTrigger({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function DropdownMenuContent({ + className, + sideOffset = 4, + ...props +}: React.ComponentProps) { + return ( + + + + ) +} + +function DropdownMenuGroup({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function DropdownMenuItem({ + className, + inset, + variant = "default", + ...props +}: React.ComponentProps & { + inset?: boolean + variant?: "default" | "destructive" +}) { + return ( + + ) +} + +function DropdownMenuCheckboxItem({ + className, + children, + checked, + ...props +}: React.ComponentProps) { + return ( + + + + + + + {children} + + ) +} + +function DropdownMenuRadioGroup({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function DropdownMenuRadioItem({ + className, + children, + ...props +}: React.ComponentProps) { + return ( + + + + + + + {children} + + ) +} + +function DropdownMenuLabel({ + className, + inset, + ...props +}: React.ComponentProps & { + inset?: boolean +}) { + return ( + + ) +} + +function DropdownMenuSeparator({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function DropdownMenuShortcut({ + className, + ...props +}: React.ComponentProps<"span">) { + return ( + + ) +} + +function DropdownMenuSub({ + ...props +}: React.ComponentProps) { + return +} + +function DropdownMenuSubTrigger({ + className, + inset, + children, + ...props +}: React.ComponentProps & { + inset?: boolean +}) { + return ( + + {children} + + + ) +} + +function DropdownMenuSubContent({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { + DropdownMenu, + DropdownMenuPortal, + DropdownMenuTrigger, + DropdownMenuContent, + DropdownMenuGroup, + DropdownMenuLabel, + DropdownMenuItem, + DropdownMenuCheckboxItem, + DropdownMenuRadioGroup, + DropdownMenuRadioItem, + DropdownMenuSeparator, + DropdownMenuShortcut, + DropdownMenuSub, + DropdownMenuSubTrigger, + DropdownMenuSubContent, +} diff --git a/frontend/src/shared/components/ui/progress.tsx b/frontend/src/shared/components/ui/progress.tsx new file mode 100644 index 0000000..d2110be --- /dev/null +++ b/frontend/src/shared/components/ui/progress.tsx @@ -0,0 +1,35 @@ +import * as React from "react"; +import * as ProgressPrimitive from "@radix-ui/react-progress"; + +import { cn } from "@/shared/utils/tailwindcss"; + +function Progress({ + className, + indicatorClassName, + value, + ...props +}: React.ComponentProps & { + indicatorClassName?: string; +}) { + return ( + + + + ); +} + +export { Progress }; diff --git a/frontend/src/shared/components/ui/separator.tsx b/frontend/src/shared/components/ui/separator.tsx new file mode 100644 index 0000000..70af3ab --- /dev/null +++ b/frontend/src/shared/components/ui/separator.tsx @@ -0,0 +1,26 @@ +import * as React from "react"; +import * as SeparatorPrimitive from "@radix-ui/react-separator"; + +import { cn } from "@/shared/utils/tailwindcss"; + +function Separator({ + className, + orientation = "horizontal", + decorative = true, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +export { Separator }; diff --git a/frontend/src/shared/components/ui/sonner.tsx b/frontend/src/shared/components/ui/sonner.tsx new file mode 100644 index 0000000..85514ec --- /dev/null +++ b/frontend/src/shared/components/ui/sonner.tsx @@ -0,0 +1,23 @@ +import { useTheme } from "next-themes"; +import { Toaster as Sonner, type ToasterProps } from "sonner"; + +const Toaster = ({ ...props }: ToasterProps) => { + const { theme = "system" } = useTheme(); + + return ( + + ); +}; + +export { Toaster }; diff --git a/frontend/src/shared/constants/endpoints.ts b/frontend/src/shared/constants/endpoints.ts new file mode 100644 index 0000000..ac10321 --- /dev/null +++ b/frontend/src/shared/constants/endpoints.ts @@ -0,0 +1,3 @@ +export const GITHUB_AUTH_URL = import.meta.env.VITE_GITHUB_AUTH_URL as string; +export const BACKEND_URL = import.meta.env.VITE_BACKEND_URL as string; +export const FRONTEND_URL = import.meta.env.VITE_FRONTEND_URL as string; diff --git a/frontend/src/shared/constants/local-storage.ts b/frontend/src/shared/constants/local-storage.ts new file mode 100644 index 0000000..964b3b2 --- /dev/null +++ b/frontend/src/shared/constants/local-storage.ts @@ -0,0 +1 @@ +export const ACCESS_TOKEN_KEY = "cc-7db23e66-accessToken"; diff --git a/frontend/src/shared/constants/query-keys.ts b/frontend/src/shared/constants/query-keys.ts new file mode 100644 index 0000000..5f5b57c --- /dev/null +++ b/frontend/src/shared/constants/query-keys.ts @@ -0,0 +1,6 @@ +export const LOGGED_IN_USER_QUERY_KEY = "logged-in-user" +export const USER_BADGES_QUERY_KEY = "user-badges" +export const LEADERBOARD_QUERY_KEY="leaderboard" +export const CURRENT_USER_RANK_QUERY_KEY="current-user-rank" +export const RECENT_ACTIVITIES_QUERY_KEY="recent-activities" +export const OVERVIEW_QUERY_KEY="overview" \ No newline at end of file diff --git a/frontend/src/shared/constants/routes.ts b/frontend/src/shared/constants/routes.ts new file mode 100644 index 0000000..8cbdabf --- /dev/null +++ b/frontend/src/shared/constants/routes.ts @@ -0,0 +1,4 @@ +export const LOGIN_PATH = "/login"; + +export const USER_DASHBOARD_PATH = "/"; +export const MY_CONTRIBUTIONS_PATH = "/my-contributions"; diff --git a/frontend/src/shared/context/AuthProvider.tsx b/frontend/src/shared/context/AuthProvider.tsx new file mode 100644 index 0000000..a5e1340 --- /dev/null +++ b/frontend/src/shared/context/AuthProvider.tsx @@ -0,0 +1,53 @@ +import { type ReactNode, createContext, useMemo, useState } from "react"; + +import { clearAccessToken, setAccessToken } from "@/shared/utils/local-storage"; + +export type UserCredentials = { + githubId: string; + githubUsername: string; + avatarUrl: string; +}; + +export interface AuthContextInterface { + userCredentials: UserCredentials | null; + login: (userCredentials: UserCredentials, token: string) => void; + logout: () => void; +} + +const AuthContext = createContext({ + userCredentials: null, + login: () => { + throw new Error("AuthContext: login called outside AuthProvider"); + }, + logout: () => { + throw new Error("AuthContext: logout called outside AuthProvider"); + } +}); + +type AuthProviderProps = { + children: ReactNode; +}; + +export const AuthProvider = ({ children }: AuthProviderProps) => { + const [userCredentials, setUserCredentials] = + useState(null); + + const login = (userCredentials: UserCredentials, token: string) => { + setUserCredentials(userCredentials); + setAccessToken(token); + }; + + const logout = () => { + setUserCredentials(null); + clearAccessToken(); + }; + + const value = useMemo( + () => ({ userCredentials, login, logout }), + [userCredentials] + ); + + return {children}; +}; + +export { AuthContext }; diff --git a/frontend/src/shared/layout/AuthLayout.tsx b/frontend/src/shared/layout/AuthLayout.tsx new file mode 100644 index 0000000..9cbad0b --- /dev/null +++ b/frontend/src/shared/layout/AuthLayout.tsx @@ -0,0 +1,86 @@ +import { type FC, type ReactNode, useEffect } from "react"; +import { useLocation, useNavigate } from "react-router-dom"; +import { CheckCircle } from "lucide-react"; + +import { Card } from "@/shared/components/ui/card"; +import { LOGIN_PATH, USER_DASHBOARD_PATH } from "@/shared/constants/routes"; +import { getAccessToken } from "@/shared/utils/local-storage"; + +interface AuthLayoutProps { + children: ReactNode; +} + +const AuthLayout: FC = ({ children }) => { + const location = useLocation(); + const navigate = useNavigate(); + + useEffect(() => { + const userAccessToken = getAccessToken(); + const shouldRedirect = [LOGIN_PATH].includes(location.pathname); + + if (userAccessToken && shouldRedirect) { + navigate(USER_DASHBOARD_PATH); + } + }, [navigate, location]); + + return ( +
+
+ +
+
+ {Array.from({ length: 64 }).map((_, i) => ( +
+ ))} +
+
+ +
+
+
+
+

+ Code Curiosity +

+
+ +
+ {[ + "Earn and Upskill", + "Set Your Goals", + "Leader Board", + "Open Source Contribution" + ].map((text, i) => ( +
+ + + {text} + +
+ ))} +
+
+
+
+ +
+
+ {Array.from({ length: 64 }).map((_, i) => ( +
+ ))} +
+
+ + + + {children} + +
+
+ ); +}; + +export default AuthLayout; diff --git a/frontend/src/shared/layout/UserDashboardLayout.tsx b/frontend/src/shared/layout/UserDashboardLayout.tsx new file mode 100644 index 0000000..fd087c2 --- /dev/null +++ b/frontend/src/shared/layout/UserDashboardLayout.tsx @@ -0,0 +1,24 @@ +import type { FC, ReactNode } from "react"; + +import Navbar from "@/shared/components/UserDashboard/Navbar"; +import UserProfileCard from "@/shared/components/UserDashboard/UserProfileCard"; + +interface UserDashboardLayoutProps { + children?: ReactNode; +} + +const UserDashboardLayout: FC = ({ children }) => { + return ( +
+ +
+ +
+ {children} +
+
+
+ ); +}; + +export default UserDashboardLayout; diff --git a/frontend/src/shared/lib/constants.ts b/frontend/src/shared/lib/constants.ts deleted file mode 100644 index 7ed62cd..0000000 --- a/frontend/src/shared/lib/constants.ts +++ /dev/null @@ -1,2 +0,0 @@ -export const ACCESS_TOKEN_KEY = 'accessToken'; -export const USER_DATA_KEY = 'userData'; diff --git a/frontend/src/shared/types/api.ts b/frontend/src/shared/types/api.ts new file mode 100644 index 0000000..c4e703d --- /dev/null +++ b/frontend/src/shared/types/api.ts @@ -0,0 +1,4 @@ +export interface ApiResponse { + message :string; + data: T; +} \ No newline at end of file diff --git a/frontend/src/shared/types/navbar.ts b/frontend/src/shared/types/navbar.ts new file mode 100644 index 0000000..157b678 --- /dev/null +++ b/frontend/src/shared/types/navbar.ts @@ -0,0 +1,6 @@ +import { MY_CONTRIBUTIONS_PATH, USER_DASHBOARD_PATH } from "@/shared/constants/routes"; + +export const USER_DASHBOARD_NAVBAR_OPTIONS = [ + { name: "Dashboard", path: USER_DASHBOARD_PATH }, + { name: "My Contributions", path: MY_CONTRIBUTIONS_PATH } +]; diff --git a/frontend/src/shared/types/types.ts b/frontend/src/shared/types/types.ts new file mode 100644 index 0000000..d53b24f --- /dev/null +++ b/frontend/src/shared/types/types.ts @@ -0,0 +1,58 @@ +export interface User { + userId: number; + githubId: number; + githubUsername: string; + email: string; + avatarUrl: string; + currentBalance: number; + currentActiveGoalId: number; + isBlocked: boolean; + isAdmin: boolean; + password: string; + isDeleted: boolean; + deletedAt: string; + createdAt: string; + updatedAt: string; +} + +export interface Badge { + id: number; + userId: number; + badgeType: string; + earnedAt: string; + createdAt: string; +} + +export interface LeaderboardUser { + id: number; + githubUsername: string; + avatarUrl: string; + contributedReposCount: number; + currentBalance: number; + rank: number; +} + +export interface RecentActivity { + userId: number; + repositoryId: number; + contributionScoreId: number; + contributionType: string; + balanceChange: number; + contributedAt: string; + githubEventId: number; + githubRepoId: number; + repoName: string; + description: string; + languagesUrl: string; + repoUrl: string; + ownerName: string; + updateDate: string; + contributorsUrl: string; +} + +export interface Overview { + type: string; + count: number; + totalCoins: number; + month: string; +} diff --git a/frontend/src/shared/utils/local-storage.ts b/frontend/src/shared/utils/local-storage.ts index 4c7be90..455701c 100644 --- a/frontend/src/shared/utils/local-storage.ts +++ b/frontend/src/shared/utils/local-storage.ts @@ -1,19 +1,4 @@ -import { USER_DATA_KEY, ACCESS_TOKEN_KEY } from '@/shared/lib/constants'; -import type { User } from '../types/auth'; - -export const getUserData = () => { - try { - const userData = localStorage.getItem(USER_DATA_KEY); - return userData ? (JSON.parse(userData) as User) : null; - // eslint-disable-next-line @typescript-eslint/no-unused-vars - } catch (error) { - return null; - } -}; - -export const setUserData = (data: User) => { - localStorage.setItem(USER_DATA_KEY, JSON.stringify(data)); -}; +import { ACCESS_TOKEN_KEY } from "@/shared/constants/local-storage"; export const getAccessToken = (): string | null => { return localStorage.getItem(ACCESS_TOKEN_KEY) || null; @@ -23,7 +8,6 @@ export const setAccessToken = (token: string) => { localStorage.setItem(ACCESS_TOKEN_KEY, token); }; -export const clearUserCredentials = () => { - localStorage.removeItem(USER_DATA_KEY); +export const clearAccessToken = () => { localStorage.removeItem(ACCESS_TOKEN_KEY); }; diff --git a/frontend/src/shared/utils/tailwindcss.ts b/frontend/src/shared/utils/tailwindcss.ts new file mode 100644 index 0000000..365058c --- /dev/null +++ b/frontend/src/shared/utils/tailwindcss.ts @@ -0,0 +1,6 @@ +import { type ClassValue, clsx } from "clsx"; +import { twMerge } from "tailwind-merge"; + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} diff --git a/frontend/src/utils/endpoints.ts b/frontend/src/utils/endpoints.ts deleted file mode 100644 index 6708f2c..0000000 --- a/frontend/src/utils/endpoints.ts +++ /dev/null @@ -1,5 +0,0 @@ -export const env = { - GithubAuthUrl: import.meta.env.VITE_GITHUB_AUTH_URL as string, - BackendUrl: import.meta.env.VITE_BACKEND_URL as string, - ServerBaseUrl: import.meta.env.VITE_SERVER_BASE_URL as string -}; diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js index e916a26..0e4ba5f 100644 --- a/frontend/tailwind.config.js +++ b/frontend/tailwind.config.js @@ -1,60 +1,60 @@ /** @type {import('tailwindcss').Config} */ module.exports = { - content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'], + content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"], theme: { extend: { borderRadius: { - lg: 'var(--radius)', - md: 'calc(var(--radius) - 2px)', - sm: 'calc(var(--radius) - 4px)' + lg: "var(--radius)", + md: "calc(var(--radius) - 2px)", + sm: "calc(var(--radius) - 4px)" }, colors: { - cclightblue: 'hsl(var(--cc-light-blue))', - ccmidblue: 'hsl(var(--cc-mid-blue))', - ccdarkblue: 'hsl(var(--cc-dark-blue))', - ccappblue: 'hsl(var(--cc-app-blue))', - ccappgraybackground: 'hsl(var(--cc-app-gray-background))', - ccapporange: 'hsl(var(--cc-app-orange))', + cclightblue: "hsl(var(--cc-app-light-blue))", + ccskyblue: "hsl(var(--cc-app-sky-blue))", + ccmidblue: "hsl(var(--cc-app-mid-blue))", + ccappblue: "hsl(var(--cc-app-blue))", + ccappgraybackground: "hsl(var(--cc-app-gray-background))", + ccapporange: "hsl(var(--cc-app-orange))", - background: 'hsl(var(--background))', - foreground: 'hsl(var(--foreground))', + background: "hsl(var(--background))", + foreground: "hsl(var(--foreground))", card: { - DEFAULT: 'hsl(var(--card))', - foreground: 'hsl(var(--card-foreground))' + DEFAULT: "hsl(var(--card))", + foreground: "hsl(var(--card-foreground))" }, popover: { - DEFAULT: 'hsl(var(--popover))', - foreground: 'hsl(var(--popover-foreground))' + DEFAULT: "hsl(var(--popover))", + foreground: "hsl(var(--popover-foreground))" }, primary: { - DEFAULT: 'hsl(var(--primary))', - foreground: 'hsl(var(--primary-foreground))' + DEFAULT: "hsl(var(--primary))", + foreground: "hsl(var(--primary-foreground))" }, secondary: { - DEFAULT: 'hsl(var(--secondary))', - foreground: 'hsl(var(--secondary-foreground))' + DEFAULT: "hsl(var(--secondary))", + foreground: "hsl(var(--secondary-foreground))" }, muted: { - DEFAULT: 'hsl(var(--muted))', - foreground: 'hsl(var(--muted-foreground))' + DEFAULT: "hsl(var(--muted))", + foreground: "hsl(var(--muted-foreground))" }, accent: { - DEFAULT: 'hsl(var(--accent))', - foreground: 'hsl(var(--accent-foreground))' + DEFAULT: "hsl(var(--accent))", + foreground: "hsl(var(--accent-foreground))" }, destructive: { - DEFAULT: 'hsl(var(--destructive))', - foreground: 'hsl(var(--destructive-foreground))' + DEFAULT: "hsl(var(--destructive))", + foreground: "hsl(var(--destructive-foreground))" }, - border: 'hsl(var(--border))', - input: 'hsl(var(--input))', - ring: 'hsl(var(--ring))', + border: "hsl(var(--border))", + input: "hsl(var(--input))", + ring: "hsl(var(--ring))", chart: { - 1: 'hsl(var(--chart-1))', - 2: 'hsl(var(--chart-2))', - 3: 'hsl(var(--chart-3))', - 4: 'hsl(var(--chart-4))', - 5: 'hsl(var(--chart-5))' + 1: "hsl(var(--chart-1))", + 2: "hsl(var(--chart-2))", + 3: "hsl(var(--chart-3))", + 4: "hsl(var(--chart-4))", + 5: "hsl(var(--chart-5))" } } } diff --git a/frontend/tsconfig.app.json b/frontend/tsconfig.app.json index 0b235e3..f39faca 100644 --- a/frontend/tsconfig.app.json +++ b/frontend/tsconfig.app.json @@ -8,9 +8,7 @@ "skipLibCheck": true, "baseUrl": ".", "paths": { - "@/*": [ - "./src/*" - ] + "@/*": ["./src/*"] }, /* Bundler mode */ diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index 0747f05..1e17393 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -8,7 +8,7 @@ "path": "./tsconfig.node.json" } ], - "compilerOptions": { + "compilerOptions": { "baseUrl": ".", "paths": { "@/*": ["./src/*"] diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index e0e1b90..f916da5 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -1,13 +1,13 @@ -import { defineConfig } from 'vite'; -import react from '@vitejs/plugin-react'; -import tailwindcss from '@tailwindcss/vite'; -import path from "path" +import tailwindcss from "@tailwindcss/vite"; +import react from "@vitejs/plugin-react"; +import path from "path"; +import { defineConfig } from "vite"; export default defineConfig({ plugins: [react(), tailwindcss()], resolve: { alias: { - "@": path.resolve(__dirname, "./src"), - }, - }, + "@": path.resolve(__dirname, "./src") + } + } });