From d0034692329726fa8c3368b0e1dcc9959b1975ef Mon Sep 17 00:00:00 2001 From: Sri Krishna Chekuri Date: Wed, 29 May 2024 16:23:07 -0400 Subject: [PATCH] Implement Base Structure and GitHub OAuth Login (#97) * Initializing theme files (#11) * Adding a basic mantine theme file. It will be updated as the code base develops. Any colors or gaps will not be hardcoded. They should first be defined in the theme and then should be used in the code. * updating .gitignore * Includes a comprehensive test setup with mock data and support structure. (#12) setting up tests in the future will be easier. * 9 tech debt setup public private routing (#13) * Working version of routing * Implemented routing, however tests for the login page needs to figured out. * implemented notification from mantine (#14) * 8 feature implement login functionality using GitHub oauth (#15) * clean up * A simple but beautiful login page * Github Oauth setup * 16 tech debt complete todos (#17) * Fixed testing setup and have all tests for OAuth. * Fixed remaining TODOs, added tests. * Adding a DEVELOPER.md and .env.example --- ui/.env.example | 8 + ui/.eslintignore | 2 +- ui/.gitignore | 99 ++++ ui/.stylelintrc.json | 8 +- ui/DEVELOPER.md | 47 ++ ui/package.json | 9 +- ui/src/App.tsx | 8 +- .../WithLoadingAndError.tests.tsx | 0 .../WithLoadingAndError.tsx | 38 -- ui/src/HOC/WithLoadingAndError/index.ts | 0 ui/src/HOC/index.ts | 0 ui/src/__tests__/theme.test.ts | 35 ++ ui/src/api/github-client.ts | 20 +- ui/src/api/index.ts | 2 + ui/src/api/oauth/github/index.ts | 1 + ui/src/api/oauth/github/use-oauth.ts | 24 + ui/src/api/oauth/index.ts | 1 + ui/src/api/oauth/oauth.d.ts | 5 + ui/src/api/repos/github/use-repos.ts | 7 +- ui/src/api/repos/index.ts | 2 +- .../api/user/github/{user.d.ts => types.ts} | 3 +- ui/src/api/user/github/use-user.ts | 9 +- ui/src/api/user/user.d.ts | 7 + .../Header/ReposList/ReposList.test.tsx | 35 -- .../components/Header/ReposList/ReposList.tsx | 94 ---- ui/src/components/Header/ReposList/index.ts | 1 - .../{Loading.tests.tsx => Loading.test.tsx} | 0 ui/src/components/Loading/Loading.tsx | 1 - ui/src/components/NavBar/NavBar.tests.ts | 0 ui/src/components/NavBar/NavBar.tsx | 10 - .../OAuthCallback/OAuthCallback.test.tsx | 57 +++ .../OAuthCallback/OAuthCallback.tsx | 34 ++ ui/src/components/OAuthCallback/index.ts | 1 + .../{Header => }/UserMenu/UserMenu.css | 0 .../{Header => }/UserMenu/UserMenu.test.tsx | 7 +- .../{Header => }/UserMenu/UserMenu.tsx | 5 - .../components/{Header => }/UserMenu/index.ts | 0 ui/src/components/index.ts | 4 +- ui/src/layouts/AppLayout.tsx | 14 +- .../{components => layouts}/Header/Header.css | 0 .../Header/Header.test.tsx | 0 .../{components => layouts}/Header/Header.tsx | 2 +- .../{components => layouts}/Header/index.ts | 0 .../{components => layouts}/NavBar/NavBar.css | 0 ui/src/layouts/NavBar/NavBar.tsx | 3 + .../{components => layouts}/NavBar/index.ts | 0 ui/src/pages/Login/Login.css | 11 + ui/src/pages/Login/Login.test.tsx | 42 ++ ui/src/pages/Login/Login.tsx | 26 + ui/src/pages/Login/index.ts | 1 + ui/src/pages/index.ts | 1 + ui/src/providers/app-provider.tsx | 8 +- .../auth-provider/auth-provider.test.tsx | 26 - .../providers/auth-provider/auth-provider.tsx | 70 --- ui/src/providers/auth-provider/index.ts | 1 - ui/src/providers/index.ts | 3 + ui/src/providers/mantine-provider.tsx | 4 +- ui/src/providers/react-query-provider.tsx | 19 +- ui/src/providers/router-provider.tsx | 4 + ui/src/routes/index.ts | 1 + ui/src/routes/protected.tsx | 12 + ui/src/routes/router.tsx | 19 + ui/src/store/auth-store/auth-store.test.ts | 17 +- ui/src/store/auth-store/auth-store.ts | 23 +- ui/src/store/custom-store.ts | 29 ++ ui/src/theme.ts | 43 +- ui/test-utils/__mocks__/index.ts | 3 + ui/test-utils/__mocks__/repo.mock.ts | 56 +++ ui/test-utils/__mocks__/repos.mock.ts | 7 + ui/test-utils/__mocks__/user.mock.ts | 66 +++ ui/test-utils/index.ts | 6 +- ui/test-utils/render.tsx | 97 +++- ui/vite.config.mjs | 35 +- ui/yarn.lock | 446 +++++++++++++++++- 74 files changed, 1279 insertions(+), 400 deletions(-) create mode 100644 ui/.env.example create mode 100644 ui/DEVELOPER.md delete mode 100644 ui/src/HOC/WithLoadingAndError/WithLoadingAndError.tests.tsx delete mode 100644 ui/src/HOC/WithLoadingAndError/WithLoadingAndError.tsx delete mode 100644 ui/src/HOC/WithLoadingAndError/index.ts delete mode 100644 ui/src/HOC/index.ts create mode 100644 ui/src/__tests__/theme.test.ts create mode 100644 ui/src/api/oauth/github/index.ts create mode 100644 ui/src/api/oauth/github/use-oauth.ts create mode 100644 ui/src/api/oauth/index.ts create mode 100644 ui/src/api/oauth/oauth.d.ts rename ui/src/api/user/github/{user.d.ts => types.ts} (91%) create mode 100644 ui/src/api/user/user.d.ts delete mode 100644 ui/src/components/Header/ReposList/ReposList.test.tsx delete mode 100644 ui/src/components/Header/ReposList/ReposList.tsx delete mode 100644 ui/src/components/Header/ReposList/index.ts rename ui/src/components/Loading/{Loading.tests.tsx => Loading.test.tsx} (100%) delete mode 100644 ui/src/components/NavBar/NavBar.tests.ts delete mode 100644 ui/src/components/NavBar/NavBar.tsx create mode 100644 ui/src/components/OAuthCallback/OAuthCallback.test.tsx create mode 100644 ui/src/components/OAuthCallback/OAuthCallback.tsx create mode 100644 ui/src/components/OAuthCallback/index.ts rename ui/src/components/{Header => }/UserMenu/UserMenu.css (100%) rename ui/src/components/{Header => }/UserMenu/UserMenu.test.tsx (89%) rename ui/src/components/{Header => }/UserMenu/UserMenu.tsx (93%) rename ui/src/components/{Header => }/UserMenu/index.ts (100%) rename ui/src/{components => layouts}/Header/Header.css (100%) rename ui/src/{components => layouts}/Header/Header.test.tsx (100%) rename ui/src/{components => layouts}/Header/Header.tsx (89%) rename ui/src/{components => layouts}/Header/index.ts (100%) rename ui/src/{components => layouts}/NavBar/NavBar.css (100%) create mode 100644 ui/src/layouts/NavBar/NavBar.tsx rename ui/src/{components => layouts}/NavBar/index.ts (100%) create mode 100644 ui/src/pages/Login/Login.css create mode 100644 ui/src/pages/Login/Login.test.tsx create mode 100644 ui/src/pages/Login/Login.tsx create mode 100644 ui/src/pages/Login/index.ts create mode 100644 ui/src/pages/index.ts delete mode 100644 ui/src/providers/auth-provider/auth-provider.test.tsx delete mode 100644 ui/src/providers/auth-provider/auth-provider.tsx delete mode 100644 ui/src/providers/auth-provider/index.ts create mode 100644 ui/src/providers/router-provider.tsx create mode 100644 ui/src/routes/index.ts create mode 100644 ui/src/routes/protected.tsx create mode 100644 ui/src/routes/router.tsx create mode 100644 ui/src/store/custom-store.ts create mode 100644 ui/test-utils/__mocks__/index.ts create mode 100644 ui/test-utils/__mocks__/repo.mock.ts create mode 100644 ui/test-utils/__mocks__/repos.mock.ts create mode 100644 ui/test-utils/__mocks__/user.mock.ts diff --git a/ui/.env.example b/ui/.env.example new file mode 100644 index 00000000..fee0fb08 --- /dev/null +++ b/ui/.env.example @@ -0,0 +1,8 @@ +VITE_GITHUB_CLIENT_ID=your_client_id +VITE_GITHUB_REDIRECT_URI=http://localhost:3000/oauth/callback +VITE_GITHUB_CLIENT_SECRET=your_client_secret +VITE_GITHUB_CLIENT_USER_IDENTITY_URL=https://github.com/login/oauth/authorize +VITE_GITHUB_LOGIN_URL=https://github.com/login/oauth/access_token +VITE_GITHUB_CORS_LOGIN_URL=https://cors-anywhere.herokuapp.com/https://github.com/login/oauth/authorize +VITE_GITHUB_SCOPES=repo +VITE_STORAGE_EXPIRY_DURATION=21600000 diff --git a/ui/.eslintignore b/ui/.eslintignore index 6805ef16..eaa4ba38 100644 --- a/ui/.eslintignore +++ b/ui/.eslintignore @@ -3,4 +3,4 @@ *.mjs *.d.ts *.d.mts -vite.config.ts \ No newline at end of file +vite.config.ts!/coverage/ diff --git a/ui/.gitignore b/ui/.gitignore index 87c91007..f7680837 100644 --- a/ui/.gitignore +++ b/ui/.gitignore @@ -23,3 +23,102 @@ yarn-debug.log* yarn-error.log* /.stylelintcache /.eslintcache +!/coverage/ +!/.eslintcache +/.eslintcache +/coverage/ui/src/api/api-client.ts.html +/coverage/ui/src/App.tsx.html +/coverage/ui/src/providers/app-provider.tsx.html +/coverage/ui/src/layouts/AppLayout.tsx.html +/coverage/ui/src/providers/auth-provider/auth-provider.tsx.html +/coverage/ui/src/store/auth-store/auth-store.ts.html +/coverage/base.css +/coverage/block-navigation.js +/coverage/clover.xml +/coverage/coverage-final.json +/coverage/favicon.png +/coverage/ui/src/api/github-client.ts.html +/coverage/ui/src/components/Header/Header.tsx.html +/coverage/ui/src/api/repos/github/index.html +/coverage/ui/src/api/repos/index.html +/coverage/ui/src/api/user/github/index.html +/coverage/ui/src/api/user/index.html +/coverage/ui/src/api/index.html +/coverage/ui/src/components/Header/ReposList/index.html +/coverage/ui/src/components/Header/UserMenu/index.html +/coverage/ui/src/components/Header/index.html +/coverage/ui/src/components/Loading/index.html +/coverage/ui/src/components/NavBar/index.html +/coverage/ui/src/components/index.html +/coverage/ui/src/layouts/index.html +/coverage/ui/src/providers/auth-provider/index.html +/coverage/ui/src/providers/index.html +/coverage/ui/src/store/auth-store/index.html +/coverage/ui/src/store/index.html +/coverage/ui/src/utils/index.html +/coverage/ui/src/index.html +/coverage/ui/test-utils/index.html +/coverage/ui/index.html +/coverage/index.html +/coverage/ui/src/api/repos/github/index.ts.html +/coverage/ui/src/api/repos/index.ts.html +/coverage/ui/src/api/user/github/index.ts.html +/coverage/ui/src/api/user/index.ts.html +/coverage/ui/src/api/index.ts.html +/coverage/ui/src/components/Header/ReposList/index.ts.html +/coverage/ui/src/components/Header/UserMenu/index.ts.html +/coverage/ui/src/components/Header/index.ts.html +/coverage/ui/src/components/Loading/index.ts.html +/coverage/ui/src/components/NavBar/index.ts.html +/coverage/ui/src/components/index.ts.html +/coverage/ui/src/layouts/index.ts.html +/coverage/ui/src/providers/auth-provider/index.ts.html +/coverage/ui/src/providers/index.ts.html +/coverage/ui/src/store/auth-store/index.ts.html +/coverage/ui/src/store/index.ts.html +/coverage/ui/src/utils/index.ts.html +/coverage/ui/test-utils/index.ts.html +/coverage/ui/src/components/Loading/Loading.tsx.html +/coverage/ui/src/main.tsx.html +/coverage/ui/src/providers/mantine-provider.tsx.html +/coverage/ui/src/components/NavBar/NavBar.tsx.html +/coverage/ui/postcss.config.cjs.html +/coverage/prettify.css +/coverage/prettify.js +/coverage/ui/src/providers/react-query-provider.tsx.html +/coverage/ui/test-utils/render.tsx.html +/coverage/ui/src/api/repos/repo-query-keys.ts.html +/coverage/ui/src/components/Header/ReposList/ReposList.tsx.html +/coverage/sort-arrow-sprite.png +/coverage/sorter.js +/coverage/ui/src/theme.ts.html +/coverage/ui/src/api/repos/github/use-repos.ts.html +/coverage/ui/src/api/user/github/use-user.ts.html +/coverage/ui/src/api/user/user-query-keys.ts.html +/coverage/ui/src/components/Header/UserMenu/UserMenu.tsx.html +/coverage/ui/src/utils/yaml_to_json.ts.html +/coverage/ui/test-utils/__mocks__/dynamic-store.mock.ts.html +/coverage/ui/test-utils/__mocks__/index.html +/coverage/ui/test-utils/__mocks__/index.ts.html +/coverage/ui/test-utils/__mocks__/repo.mock.ts.html +/coverage/ui/test-utils/__mocks__/repos.mock.ts.html +/coverage/ui/test-utils/__mocks__/user.mock.ts.html +/coverage/.tmp/coverage-0.json +/coverage/.tmp/coverage-1.json +/coverage/.tmp/coverage-2.json +/coverage/.tmp/coverage-3.json +/coverage/.tmp/coverage-4.json +/coverage/.tmp/coverage-5.json +/coverage/.tmp/coverage-6.json +/coverage/.tmp/coverage-7.json +/coverage/ui/src/pages/Login/index.html +/coverage/ui/src/pages/index.html +/coverage/ui/src/routes/index.html +/coverage/ui/src/pages/Login/index.ts.html +/coverage/ui/src/pages/index.ts.html +/coverage/ui/src/routes/index.ts.html +/coverage/ui/src/utils/isAuthenticated.ts.html +/coverage/ui/src/pages/Login/Login.tsx.html +/coverage/ui/src/routes/protected.tsx.html +/coverage/ui/src/routes/router.tsx.html +/coverage/ui/src/providers/router-provider.tsx.html diff --git a/ui/.stylelintrc.json b/ui/.stylelintrc.json index 971ae9cf..5b36a97d 100644 --- a/ui/.stylelintrc.json +++ b/ui/.stylelintrc.json @@ -24,5 +24,9 @@ "ignorePseudoClasses": ["global"] } ] - } -} \ No newline at end of file + }, + "ignoreFiles": [ + "coverage/**", + "node_modules/**" + ] +} diff --git a/ui/DEVELOPER.md b/ui/DEVELOPER.md new file mode 100644 index 00000000..1c75fab0 --- /dev/null +++ b/ui/DEVELOPER.md @@ -0,0 +1,47 @@ +# Developer Setup Guide + +## Setting Up GitHub OAuth + +To set up GitHub OAuth for this application, you will need to configure several environment variables in a `.env.local` file. Below are the steps to get you started. + +1. **Create a GitHub OAuth App**: + - Go to [GitHub Developer Settings](https://github.com/settings/developers). + - Click on "New OAuth App". + - Fill in the details with your application's information: + - **Application name**: Your app's name + - **Homepage URL**: `http://localhost:3000` + - **Authorization callback URL**: `http://localhost:3000/oauth/callback` + - After creating the app, you will get a **Client ID** and **Client Secret**. + +2. **Set Environment Variables**: + - Create a file named `.env.local` in the root directory of your project. + - Copy and paste the following template into `.env.local` and fill in the values with your GitHub OAuth credentials and URLs. + + ```plaintext + VITE_GITHUB_CLIENT_ID=your_client_id + VITE_GITHUB_REDIRECT_URI=http://localhost:3000/oauth/callback + VITE_GITHUB_CLIENT_SECRET=your_client_secret + VITE_GITHUB_CLIENT_USER_IDENTITY_URL=https://github.com/login/oauth/authorize + VITE_GITHUB_LOGIN_URL=https://github.com/login/oauth/access_token + VITE_GITHUB_CORS_LOGIN_URL=https://cors-anywhere.herokuapp.com/https://github.com/login/oauth/authorize + VITE_GITHUB_SCOPES=repo + VITE_STORAGE_EXPIRY_DURATION=21600000 + ``` + +3. **Run the Application**: + - Make sure you have all dependencies installed. + - Start the development server: + + ```sh + npm install + npm run dev + ``` + +## Additional Notes + +- The `VITE_GITHUB_CORS_LOGIN_URL` is used to handle CORS issues during development. You might need to adjust this depending on your deployment environment. +- The `VITE_STORAGE_EXPIRY_DURATION` is set to 6 hours (21600000 milliseconds). + +By following these steps, you should be able to set up and run the application with GitHub OAuth configured. + +Happy coding! diff --git a/ui/package.json b/ui/package.json index 69b4a61a..f5184d6d 100644 --- a/ui/package.json +++ b/ui/package.json @@ -7,6 +7,8 @@ "build": "tsc && vite build", "preview": "vite preview", "typecheck": "tsc --noEmit", + "clean": "rm -rf node_modules && rm -rf coverage && yarn cache clean", + "ci": "npm run clean && yarn install", "code:fix": "npm run lint:fix && npm run prettier:write && npm run typecheck", "lint": "npm run lint:eslint && npm run lint:stylelint", "lint:fix": "npm run lint:eslint:fix && npm run lint:stylelint:fix", @@ -17,13 +19,15 @@ "prettier": "prettier --check \"**/*.{ts,tsx}\"", "prettier:write": "prettier --write \"**/*.{ts,tsx}\"", "vitest": "vitest run", + "vitest:cover": "vitest run --coverage", "vitest:watch": "vitest", - "test": "npm run typecheck && npm run prettier && npm run lint && npm run vitest" + "test": "npm run typecheck && npm run prettier && npm run lint && npm run vitest:cover" }, "dependencies": { "@mantine/core": "^7.9.0", "@mantine/form": "^7.9.0", "@mantine/hooks": "^7.9.0", + "@mantine/notifications": "^7.9.2", "@tabler/icons-react": "^3.3.0", "@tanstack/react-query": "^5.34.1", "@testing-library/dom": "^10.1.0", @@ -37,6 +41,7 @@ "zustand": "^4.5.2" }, "devDependencies": { + "@faker-js/faker": "^8.4.1", "@tanstack/react-query-devtools": "^5.34.1", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", @@ -50,6 +55,7 @@ "@typescript-eslint/eslint-plugin": "^7.2.0", "@typescript-eslint/parser": "^7.2.0", "@vitejs/plugin-react": "^4.2.1", + "@vitest/coverage-v8": "^1.6.0", "eslint": "^8.57.0", "eslint-config-airbnb": "19.0.4", "eslint-config-airbnb-typescript": "^18.0.0", @@ -69,6 +75,7 @@ "stylelint-config-standard-scss": "^13.0.0", "typescript": "^5.4.5", "vite": "^5.2.10", + "vite-plugin-istanbul": "^6.0.2", "vite-tsconfig-paths": "^4.3.1", "vitest": "^1.5.2" } diff --git a/ui/src/App.tsx b/ui/src/App.tsx index a5b4d6b2..f9a93389 100644 --- a/ui/src/App.tsx +++ b/ui/src/App.tsx @@ -1,11 +1,7 @@ import '@mantine/core/styles.css'; +import '@mantine/notifications/styles.css'; import React, { FC } from 'react'; import './App.css'; -import { AppLayout } from '@/layouts'; import { AppProvider } from '@/providers'; -export const App: FC = () => ( - - - -); +export const App: FC = () => ; diff --git a/ui/src/HOC/WithLoadingAndError/WithLoadingAndError.tests.tsx b/ui/src/HOC/WithLoadingAndError/WithLoadingAndError.tests.tsx deleted file mode 100644 index e69de29b..00000000 diff --git a/ui/src/HOC/WithLoadingAndError/WithLoadingAndError.tsx b/ui/src/HOC/WithLoadingAndError/WithLoadingAndError.tsx deleted file mode 100644 index db55a22c..00000000 --- a/ui/src/HOC/WithLoadingAndError/WithLoadingAndError.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import React, { ComponentType, FC } from 'react'; -import { Alert } from '@mantine/core'; -import { Loading } from '@/components'; - -interface WithLoadingAndErrorProps { - isLoading: boolean; - error: string | null; -} - -// TODO: Not using this, as I dont want to get into prop drilling. Keeping it for reference, -// need to investigate other venues -/* eslint-disable @typescript-eslint/no-unused-vars */ -export const withLoadingAndError = ( - WrappedComponent: ComponentType -) => { - /* eslint-disable @typescript-eslint/no-unused-vars */ - const WithLoadingAndError: FC = ({ - isLoading, - error, - ...props - }) => { - if (isLoading) { - return ; - } - - if (error) { - return ; // You can style this or handle different types of errors differently - } - - return ; - }; -}; -/* eslint-enable @typescript-eslint/no-unused-vars */ -const ErrorComponent = () => ( - - Error! - -); diff --git a/ui/src/HOC/WithLoadingAndError/index.ts b/ui/src/HOC/WithLoadingAndError/index.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/ui/src/HOC/index.ts b/ui/src/HOC/index.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/ui/src/__tests__/theme.test.ts b/ui/src/__tests__/theme.test.ts new file mode 100644 index 00000000..60e09602 --- /dev/null +++ b/ui/src/__tests__/theme.test.ts @@ -0,0 +1,35 @@ +import { theme } from '@/theme'; + +describe('Theme Configuration', () => { + describe('Colors', () => { + it('should have deepBlue and blue color palettes defined', () => { + expect(theme.colors?.deepBlue).toBeDefined(); + expect(theme.colors?.blue).toBeDefined(); + }); + + it('should have 10 shades for deepBlue', () => { + expect(theme.colors?.deepBlue?.length).toBe(10); + }); + }); + + describe('Shadows', () => { + it('should define medium and extra-large shadows', () => { + expect(theme.shadows?.md).toBeDefined(); + expect(theme.shadows?.xl).toBeDefined(); + }); + + it('should have correct shadow value for md', () => { + expect(theme.shadows?.md).toBe('1px 1px 3px rgba(0, 0, 0, .25)'); + }); + }); + + describe('Typography', () => { + it('should have a Roboto font family for headings', () => { + expect(theme.headings?.fontFamily).toBe('Roboto, sans-serif'); + }); + + it('should define font size for h1', () => { + expect(theme.headings?.sizes?.h1?.fontSize).toBeDefined(); + }); + }); +}); diff --git a/ui/src/api/github-client.ts b/ui/src/api/github-client.ts index b3e05713..7a6eda3e 100644 --- a/ui/src/api/github-client.ts +++ b/ui/src/api/github-client.ts @@ -1,15 +1,21 @@ import axios from 'axios'; -import { useAuthStore } from '@/store'; -export const gitHubClient = () => { - //TODO: Is this a good practice? - const { token } = useAuthStore.getState(); - - return axios.create({ +export const gitHubClient = () => + axios.create({ baseURL: 'https://api.github.com/', headers: { - Authorization: `Bearer ${token}`, 'Content-Type': 'application/json', }, }); + +const GithubLoginCredentials = { + clientId: import.meta.env.VITE_GITHUB_CLIENT_ID, + loginUrl: import.meta.env.VITE_GITHUB_CLIENT_USER_IDENTITY_URL, + redirectUri: import.meta.env.VITE_GITHUB_REDIRECT_URI, + scopes: import.meta.env.VITE_GITHUB_SCOPES, +}; + +export const githubLogin = () => { + const { loginUrl, clientId, redirectUri, scopes } = GithubLoginCredentials; + window.location.href = `${loginUrl}?client_id=${clientId}&redirect_uri=${redirectUri}&scope=${scopes}`; }; diff --git a/ui/src/api/index.ts b/ui/src/api/index.ts index 4f9515fb..b73abdca 100644 --- a/ui/src/api/index.ts +++ b/ui/src/api/index.ts @@ -1,4 +1,6 @@ export { apiClient } from './api-client'; +export { githubLogin } from './github-client'; export * from './user'; export * from './repos'; +export * from './oauth'; diff --git a/ui/src/api/oauth/github/index.ts b/ui/src/api/oauth/github/index.ts new file mode 100644 index 00000000..fd55eacd --- /dev/null +++ b/ui/src/api/oauth/github/index.ts @@ -0,0 +1 @@ +export * from './use-oauth'; diff --git a/ui/src/api/oauth/github/use-oauth.ts b/ui/src/api/oauth/github/use-oauth.ts new file mode 100644 index 00000000..d5e58a9c --- /dev/null +++ b/ui/src/api/oauth/github/use-oauth.ts @@ -0,0 +1,24 @@ +import axios from 'axios'; +import { useQuery } from '@tanstack/react-query'; + +export const exchangeGithubCodeForToken = async (code: string | null) => { + const data = { + client_id: import.meta.env.VITE_GITHUB_CLIENT_ID, + client_secret: import.meta.env.VITE_GITHUB_CLIENT_SECRET, + code, + redirectUri: import.meta.env.VITE_GITHUB_REDIRECT_URI, + }; + const response = await axios.post('/login/oauth/access_token', data, { + headers: { + Accept: 'application/json', + }, + }); + return response.data as OAuth; +}; + +export const useOAuth = ({ code }: { code: string | null }) => + useQuery({ + queryKey: ['Github', 'oauth', code], + queryFn: () => exchangeGithubCodeForToken(code), + retry: 1, + }); diff --git a/ui/src/api/oauth/index.ts b/ui/src/api/oauth/index.ts new file mode 100644 index 00000000..7302c7ad --- /dev/null +++ b/ui/src/api/oauth/index.ts @@ -0,0 +1 @@ +export * from './github'; diff --git a/ui/src/api/oauth/oauth.d.ts b/ui/src/api/oauth/oauth.d.ts new file mode 100644 index 00000000..a865ade4 --- /dev/null +++ b/ui/src/api/oauth/oauth.d.ts @@ -0,0 +1,5 @@ +type OAuth = { + access_token: string; + scope: string; + token_type: string; +}; diff --git a/ui/src/api/repos/github/use-repos.ts b/ui/src/api/repos/github/use-repos.ts index ef00cb49..0ecd83cb 100644 --- a/ui/src/api/repos/github/use-repos.ts +++ b/ui/src/api/repos/github/use-repos.ts @@ -6,12 +6,15 @@ import { repoQueryKeys } from '../repo-query-keys'; export const getReposFn = async () => { const { username } = useAuthStore.getState(); - const response = await apiClient.get(`/users/${username || ''}/repos`); + const response = await apiClient.get(`/users/${username || ''}/repos`, { + headers: { + Authorization: `Bearer ${useAuthStore.getState().token}`, + }, + }); return response.data as Repos[]; }; -// TODO: How to convert Github User to generic Repo type? export const useRepos = () => useQuery({ queryKey: repoQueryKeys.details(), diff --git a/ui/src/api/repos/index.ts b/ui/src/api/repos/index.ts index 7302c7ad..da45b74c 100644 --- a/ui/src/api/repos/index.ts +++ b/ui/src/api/repos/index.ts @@ -1 +1 @@ -export * from './github'; +export { useRepos } from './github'; diff --git a/ui/src/api/user/github/user.d.ts b/ui/src/api/user/github/types.ts similarity index 91% rename from ui/src/api/user/github/user.d.ts rename to ui/src/api/user/github/types.ts index 6f4e5fbb..e8a817f8 100644 --- a/ui/src/api/user/github/user.d.ts +++ b/ui/src/api/user/github/types.ts @@ -1,4 +1,5 @@ -interface User { +// eslint-disable-next-line @typescript-eslint/no-unused-vars +interface GitHubUser { login: string; id: number; node_id: string; diff --git a/ui/src/api/user/github/use-user.ts b/ui/src/api/user/github/use-user.ts index b8c9fd9e..226f44ee 100644 --- a/ui/src/api/user/github/use-user.ts +++ b/ui/src/api/user/github/use-user.ts @@ -1,17 +1,20 @@ import { useQuery } from '@tanstack/react-query'; import { apiClient } from '@/api'; import { userQueryKeys } from '../user-query-keys'; +import { useAuthStore } from '@/store'; export const getUserFn = async () => { - const response = await apiClient.get('/user'); + const response = await apiClient.get('/user', { + headers: { + Authorization: `Bearer ${useAuthStore.getState().token}`, + }, + }); return response.data as User; }; -// TODO: How to convert Github User to generic User type? export const useUser = () => useQuery({ queryKey: userQueryKeys.detail('me'), queryFn: getUserFn, - retry: 4, }); diff --git a/ui/src/api/user/user.d.ts b/ui/src/api/user/user.d.ts new file mode 100644 index 00000000..506ece8d --- /dev/null +++ b/ui/src/api/user/user.d.ts @@ -0,0 +1,7 @@ +// Here is the type that will be User across the codebase, +// If integrating with a different git provider, +// you will need to update the type of the user object. +type User = Pick< + GitHubUser, + 'avatar_url' | 'name' | 'login' | 'id' | 'organizations_url' | 'subscriptions_url' +>; diff --git a/ui/src/components/Header/ReposList/ReposList.test.tsx b/ui/src/components/Header/ReposList/ReposList.test.tsx deleted file mode 100644 index 6b689049..00000000 --- a/ui/src/components/Header/ReposList/ReposList.test.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { describe, it, expect, vi } from 'vitest'; -import { render, screen } from '@test-utils'; -import { ReposList } from './ReposList'; -import { useRepos } from '@/api'; - -// Mock the API hook -vi.mock('@/api', () => ({ - useRepos: vi.fn(), -})); - -describe('ReposList', () => { - it('renders loading state correctly', () => { - // @ts-ignore - useRepos.mockReturnValue({ data: null, error: null, isLoading: true }); - render(); - expect(screen.getByTestId('loading-user-menu')).toBeInTheDocument(); - }); - - it('renders error state correctly', () => { - // @ts-ignore - useRepos.mockReturnValue({ data: null, error: 'Failed to fetch', isLoading: false }); - render(); - expect(screen.getByText('Error!')).toBeInTheDocument(); - }); - - it('renders repository list correctly', () => { - const repos = [{ name: 'Repo1' }, { name: 'Repo2' }, { name: 'Repo3' }]; - // @ts-ignore - useRepos.mockReturnValue({ data: repos, error: null, isLoading: false }); - render(); - expect(screen.getByText('Repo1')).toBeInTheDocument(); - expect(screen.getByText('Repo2')).toBeInTheDocument(); - expect(screen.getByText('Repo3')).toBeInTheDocument(); - }); -}); diff --git a/ui/src/components/Header/ReposList/ReposList.tsx b/ui/src/components/Header/ReposList/ReposList.tsx deleted file mode 100644 index 5d5402f6..00000000 --- a/ui/src/components/Header/ReposList/ReposList.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import { - Alert, - Combobox, - ComboboxDropdown, - ComboboxEmpty, - ComboboxOption, - ComboboxOptions, - ComboboxTarget, - ScrollAreaAutosize, - Skeleton, - TextInput, - useCombobox, -} from '@mantine/core'; -import { useState } from 'react'; -import { useRepos } from '@/api'; - -export const ReposList = () => { - const combobox = useCombobox({ - scrollBehavior: 'smooth', - onDropdownClose: () => combobox.resetSelectedOption(), - }); - - const { data, error, isLoading } = useRepos(); - - const [value, setValue] = useState(''); - - if (error) { - return ; - } - - if (isLoading) { - return ; - } - - const shouldFilterOptions = !data?.some((item) => item.name === value); - const filteredOptions = shouldFilterOptions - ? data?.filter((item) => item.name.toLowerCase().includes(value.toLowerCase().trim())) - : data; - - const options = filteredOptions?.map((item: any) => ( - - {item.name} - - )); - - return ( - { - setValue(val); - combobox.closeDropdown(); - }} - > - - { - setValue(e.currentTarget.value); - combobox.openDropdown(); - combobox.updateSelectedOptionIndex(); - }} - onClick={() => combobox.openDropdown()} - onFocus={() => combobox.openDropdown()} - onBlur={() => combobox.closeDropdown()} - /> - - - - - - {options?.length === 0 ? Nothing Found : options} - - - - - ); -}; - -const LoadingSkeleton = () => ( -
- - - -
-); - -const ErrorComponent = () => ( - - Error! - -); diff --git a/ui/src/components/Header/ReposList/index.ts b/ui/src/components/Header/ReposList/index.ts deleted file mode 100644 index 827033e5..00000000 --- a/ui/src/components/Header/ReposList/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { ReposList } from './ReposList'; diff --git a/ui/src/components/Loading/Loading.tests.tsx b/ui/src/components/Loading/Loading.test.tsx similarity index 100% rename from ui/src/components/Loading/Loading.tests.tsx rename to ui/src/components/Loading/Loading.test.tsx diff --git a/ui/src/components/Loading/Loading.tsx b/ui/src/components/Loading/Loading.tsx index 4445f0f4..23a1ec77 100644 --- a/ui/src/components/Loading/Loading.tsx +++ b/ui/src/components/Loading/Loading.tsx @@ -5,7 +5,6 @@ interface LoadingProps { message?: string; // Optional prop to display a message below the spinner } -// TODO: Look into handling loaders via state. export const Loading: FC = ({ message = 'Loading...' }) => (
diff --git a/ui/src/components/NavBar/NavBar.tests.ts b/ui/src/components/NavBar/NavBar.tests.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/ui/src/components/NavBar/NavBar.tsx b/ui/src/components/NavBar/NavBar.tsx deleted file mode 100644 index be8f8b9a..00000000 --- a/ui/src/components/NavBar/NavBar.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import './NavBar.css'; -import { ReposList } from '@/components/Header/ReposList'; - -export const NavBar = () => ( - -); diff --git a/ui/src/components/OAuthCallback/OAuthCallback.test.tsx b/ui/src/components/OAuthCallback/OAuthCallback.test.tsx new file mode 100644 index 00000000..7d63d76c --- /dev/null +++ b/ui/src/components/OAuthCallback/OAuthCallback.test.tsx @@ -0,0 +1,57 @@ +import { describe } from 'vitest'; +import { render, screen } from '@test-utils'; +import { waitFor } from '@testing-library/react'; +import { useNavigate } from 'react-router-dom'; +import { useOAuth } from '@/api'; +import { OAuthCallback } from '@/components'; +import { useAuthStore } from '@/store'; + +describe('OAuthCallback', () => { + test('displays loading message while fetching data', () => { + //@ts-ignore + useOAuth.mockReturnValue({ data: null, isLoading: true, isError: false }); + render(); + expect(screen.getByText('Loading...')).toBeInTheDocument(); + }); + + test('sets token and navigates upon receving data', async () => { + const mockSetToken = vi.fn(); + const mockNavigate = vi.fn(); + //@ts-ignore + useOAuth.mockReturnValue({ + data: { access_token: 'token' }, + isLoading: false, + isError: false, + }); + // @ts-ignore + useAuthStore.mockReturnValue({ setToken: mockSetToken }); + // @ts-ignore + useNavigate.mockReturnValue(mockNavigate); + + render(); + await waitFor( + () => { + expect(mockSetToken).toHaveBeenCalledWith('token'); + expect(mockNavigate).toHaveBeenCalledWith('/'); + }, + { + timeout: 1000, + } + ); + }); + + test('displays error message if error occurs', () => { + const mockNavigate = vi.fn(); + //@ts-ignore + useOAuth.mockReturnValue({ + data: null, + isLoading: false, + isError: true, + error: { message: 'error' }, + }); + //@ts-ignore + useNavigate.mockReturnValue(mockNavigate); + render(); + expect(mockNavigate).toHaveBeenCalledWith('/login'); + }); +}); diff --git a/ui/src/components/OAuthCallback/OAuthCallback.tsx b/ui/src/components/OAuthCallback/OAuthCallback.tsx new file mode 100644 index 00000000..2baff539 --- /dev/null +++ b/ui/src/components/OAuthCallback/OAuthCallback.tsx @@ -0,0 +1,34 @@ +import { FC, useEffect } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { notifications } from '@mantine/notifications'; +import { useOAuth } from '@/api'; +import { useAuthStore } from '@/store'; +import { Loading } from '@/components'; + +export const OAuthCallback: FC = () => { + const code = new URLSearchParams(window.location.search).get('code'); + const { data, isLoading, isError, error } = useOAuth({ code }); + const navigate = useNavigate(); + const { setToken } = useAuthStore(); + + useEffect(() => { + if (data) { + setToken(data.access_token); + navigate('/'); + } + }, [data, setToken, navigate]); + + if (isLoading) { + return ; + } + + if (isError) { + notifications.show({ + title: 'Error while authenticating, redirecting to login page', + message: error.message, + }); + navigate('/login'); + } + + return null; +}; diff --git a/ui/src/components/OAuthCallback/index.ts b/ui/src/components/OAuthCallback/index.ts new file mode 100644 index 00000000..2b614843 --- /dev/null +++ b/ui/src/components/OAuthCallback/index.ts @@ -0,0 +1 @@ +export { OAuthCallback } from './OAuthCallback'; diff --git a/ui/src/components/Header/UserMenu/UserMenu.css b/ui/src/components/UserMenu/UserMenu.css similarity index 100% rename from ui/src/components/Header/UserMenu/UserMenu.css rename to ui/src/components/UserMenu/UserMenu.css diff --git a/ui/src/components/Header/UserMenu/UserMenu.test.tsx b/ui/src/components/UserMenu/UserMenu.test.tsx similarity index 89% rename from ui/src/components/Header/UserMenu/UserMenu.test.tsx rename to ui/src/components/UserMenu/UserMenu.test.tsx index 0bdb1480..8f66d86e 100644 --- a/ui/src/components/Header/UserMenu/UserMenu.test.tsx +++ b/ui/src/components/UserMenu/UserMenu.test.tsx @@ -1,14 +1,9 @@ // -import { describe, it, expect, vi } from 'vitest'; +import { describe, it, expect } from 'vitest'; import { render, screen } from '@test-utils'; import { UserMenu } from './UserMenu'; import { useUser } from '@/api'; -// Mock the API hook and store -vi.mock('@/api', () => ({ - useUser: vi.fn(), -})); - describe('UserMenu', () => { it('renders loading state correctly', () => { // @ts-ignore diff --git a/ui/src/components/Header/UserMenu/UserMenu.tsx b/ui/src/components/UserMenu/UserMenu.tsx similarity index 93% rename from ui/src/components/Header/UserMenu/UserMenu.tsx rename to ui/src/components/UserMenu/UserMenu.tsx index e4e0056b..c1471cb3 100644 --- a/ui/src/components/Header/UserMenu/UserMenu.tsx +++ b/ui/src/components/UserMenu/UserMenu.tsx @@ -3,14 +3,10 @@ import { IconAlertCircle, IconChevronDown, IconSettings } from '@tabler/icons-re import './UserMenu.css'; import { useState } from 'react'; import { useUser } from '@/api'; -import { useAuthStore } from '@/store'; export const UserMenu = () => { const { data, error, isLoading } = useUser(); const [userMenuOpened, setUserMenuOpened] = useState(false); - const { openModal } = useAuthStore((state) => ({ - openModal: state.openModal, - })); if (isLoading) { return ; @@ -44,7 +40,6 @@ export const UserMenu = () => { Settings } - onClick={openModal} > Change Token diff --git a/ui/src/components/Header/UserMenu/index.ts b/ui/src/components/UserMenu/index.ts similarity index 100% rename from ui/src/components/Header/UserMenu/index.ts rename to ui/src/components/UserMenu/index.ts diff --git a/ui/src/components/index.ts b/ui/src/components/index.ts index 89c715e9..f4a1ee49 100644 --- a/ui/src/components/index.ts +++ b/ui/src/components/index.ts @@ -1,2 +1,4 @@ -export { Header } from './Header'; +export { Header } from '../layouts/Header'; export { Loading } from './Loading'; +export { OAuthCallback } from './OAuthCallback'; +export { UserMenu } from './UserMenu'; diff --git a/ui/src/layouts/AppLayout.tsx b/ui/src/layouts/AppLayout.tsx index a89b0dc2..9d0aef93 100644 --- a/ui/src/layouts/AppLayout.tsx +++ b/ui/src/layouts/AppLayout.tsx @@ -1,25 +1,15 @@ import { AppShell } from '@mantine/core'; import React from 'react'; -import { Header } from '@/components'; -import { NavBar } from '@/components/NavBar'; +import { NavBar } from './NavBar'; +import { Header } from './Header'; export const AppLayout = () => ( - {/*TODO: data-testid is not retained after the component is mounted. Need to investigate this*/}
- - {/**/} - {/* Navbar*/} - {/* {Array(15)*/} - {/* .fill(0)*/} - {/* .map((_, index) => (*/} - {/* */} - {/* ))}*/} - {/**/} Main ); diff --git a/ui/src/components/Header/Header.css b/ui/src/layouts/Header/Header.css similarity index 100% rename from ui/src/components/Header/Header.css rename to ui/src/layouts/Header/Header.css diff --git a/ui/src/components/Header/Header.test.tsx b/ui/src/layouts/Header/Header.test.tsx similarity index 100% rename from ui/src/components/Header/Header.test.tsx rename to ui/src/layouts/Header/Header.test.tsx diff --git a/ui/src/components/Header/Header.tsx b/ui/src/layouts/Header/Header.tsx similarity index 89% rename from ui/src/components/Header/Header.tsx rename to ui/src/layouts/Header/Header.tsx index 8e259dbb..e5b24201 100644 --- a/ui/src/components/Header/Header.tsx +++ b/ui/src/layouts/Header/Header.tsx @@ -1,7 +1,7 @@ import React, { FC } from 'react'; import { Container, Group } from '@mantine/core'; -import { UserMenu } from './UserMenu'; +import { UserMenu } from '@/components'; import './Header.css'; export const Header: FC = () => ( diff --git a/ui/src/components/Header/index.ts b/ui/src/layouts/Header/index.ts similarity index 100% rename from ui/src/components/Header/index.ts rename to ui/src/layouts/Header/index.ts diff --git a/ui/src/components/NavBar/NavBar.css b/ui/src/layouts/NavBar/NavBar.css similarity index 100% rename from ui/src/components/NavBar/NavBar.css rename to ui/src/layouts/NavBar/NavBar.css diff --git a/ui/src/layouts/NavBar/NavBar.tsx b/ui/src/layouts/NavBar/NavBar.tsx new file mode 100644 index 00000000..a7a977a8 --- /dev/null +++ b/ui/src/layouts/NavBar/NavBar.tsx @@ -0,0 +1,3 @@ +import './NavBar.css'; + +export const NavBar = () => ; diff --git a/ui/src/components/NavBar/index.ts b/ui/src/layouts/NavBar/index.ts similarity index 100% rename from ui/src/components/NavBar/index.ts rename to ui/src/layouts/NavBar/index.ts diff --git a/ui/src/pages/Login/Login.css b/ui/src/pages/Login/Login.css new file mode 100644 index 00000000..4b162552 --- /dev/null +++ b/ui/src/pages/Login/Login.css @@ -0,0 +1,11 @@ +.login-page { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 100vh; +} + +.login-title { + margin-bottom: 20px; +} diff --git a/ui/src/pages/Login/Login.test.tsx b/ui/src/pages/Login/Login.test.tsx new file mode 100644 index 00000000..e09dcb31 --- /dev/null +++ b/ui/src/pages/Login/Login.test.tsx @@ -0,0 +1,42 @@ +import React from 'react'; +import { render, screen, waitFor, userEvent } from '@test-utils'; +import { vi } from 'vitest'; +import { Login } from '@/pages'; +import { githubLogin } from '@/api'; + +describe('Login Component', () => { + beforeEach(() => { + // Clear all mocks before each test + vi.clearAllMocks(); + }); + + it('renders correctly', () => { + render(); + expect(screen.getByText('Spark Expectations')).toBeInTheDocument(); + expect( + screen.getByText('Please login using one of the following providers:') + ).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'Login with GitHub' })).toBeInTheDocument(); + }); + + it('calls githubLogin on button click', async () => { + render(); + const loginButton = screen.getByRole('button', { name: 'Login with GitHub' }); + userEvent.click(loginButton); + await waitFor(() => expect(githubLogin).toHaveBeenCalled()); + }); + + it('handles the login process correctly', async () => { + // Assume githubLogin is a promise that resolves to an access token + (githubLogin as jest.Mock).mockResolvedValue({ access_token: 'fake-token' }); + + render(); + const loginButton = screen.getByRole('button', { name: 'Login with GitHub' }); + userEvent.click(loginButton); + + await waitFor(() => { + // You can add additional assertions here to check for changes in the UI or redirects + expect(githubLogin).toHaveBeenCalled(); + }); + }); +}); diff --git a/ui/src/pages/Login/Login.tsx b/ui/src/pages/Login/Login.tsx new file mode 100644 index 00000000..92cfaee6 --- /dev/null +++ b/ui/src/pages/Login/Login.tsx @@ -0,0 +1,26 @@ +import { FC } from 'react'; +import { Button, Container, Paper, Text, Title } from '@mantine/core'; + +import { githubLogin } from '@/api'; + +export const Login: FC = () => { + const loginWithGithub = () => { + githubLogin(); + }; + return ( + <> + + Spark Expectations + + Please login using one of the following providers: + + + + + + + + ); +}; diff --git a/ui/src/pages/Login/index.ts b/ui/src/pages/Login/index.ts new file mode 100644 index 00000000..a10c3a83 --- /dev/null +++ b/ui/src/pages/Login/index.ts @@ -0,0 +1 @@ +export * from './Login'; diff --git a/ui/src/pages/index.ts b/ui/src/pages/index.ts new file mode 100644 index 00000000..a10c3a83 --- /dev/null +++ b/ui/src/pages/index.ts @@ -0,0 +1 @@ +export * from './Login'; diff --git a/ui/src/providers/app-provider.tsx b/ui/src/providers/app-provider.tsx index 1610ae6b..82e5975f 100644 --- a/ui/src/providers/app-provider.tsx +++ b/ui/src/providers/app-provider.tsx @@ -1,12 +1,14 @@ import React from 'react'; +import { Notifications } from '@mantine/notifications'; import { ReactQueryProvider } from './react-query-provider'; import { CustomMantineProvider } from './mantine-provider'; -import { AuthProvider } from './auth-provider'; +import { RouterProvider } from './router-provider'; -export const AppProvider = ({ children }: { children: React.ReactNode }) => ( +export const AppProvider = () => ( - {children} + + ); diff --git a/ui/src/providers/auth-provider/auth-provider.test.tsx b/ui/src/providers/auth-provider/auth-provider.test.tsx deleted file mode 100644 index 2475d675..00000000 --- a/ui/src/providers/auth-provider/auth-provider.test.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { render, screen } from '@testing-library/react'; -import { AppProvider } from '@/providers'; -import { useAuthStore } from '@/store'; - -const TestConsumer = () => { - const { token, setToken } = useAuthStore(); - return ( -
- Token: {token} - -
- ); -}; - -describe('AuthProvider', () => { - it('provides the auth context correctly', () => { - render( - - - - ); - expect(screen.getByText('Token')).toBeInTheDocument(); - }); -}); diff --git a/ui/src/providers/auth-provider/auth-provider.tsx b/ui/src/providers/auth-provider/auth-provider.tsx deleted file mode 100644 index 94c8abe5..00000000 --- a/ui/src/providers/auth-provider/auth-provider.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import React, { ReactNode, useEffect } from 'react'; -import { Modal, TextInput, Button, Group } from '@mantine/core'; -import { useForm } from '@mantine/form'; -import { useAuthStore } from '@/store'; -import { getUserFn } from '@/api'; -import { Loading } from '@/components/Loading/Loading'; - -interface AuthProviderProps { - children: ReactNode; -} - -export const AuthProvider: React.FC = ({ children }) => { - const { token, username, isModalOpen, setUserName, setToken, openModal, closeModal } = - useAuthStore(); - - const form = useForm({ - initialValues: { - token, - }, - validate: { - token: (value: string | null) => value && value.length === 0 && 'Token is required', - }, - }); - - useEffect(() => { - if (!token) { - openModal(); - } else { - closeModal(); - } - }, [token, openModal, closeModal]); - - const handleLogin = (values: { token: null | string }) => { - setToken(values?.token); - - getUserFn().then((res) => { - setUserName(res.login); - }); - - closeModal(); - }; - - const handleCancel = () => { - if (token) closeModal(); - }; - - return ( - <> - -
handleLogin(values))}> - - - - - - -
- {/*TODO: Handle Error State*/} - {token && username ? children : } - - ); -}; diff --git a/ui/src/providers/auth-provider/index.ts b/ui/src/providers/auth-provider/index.ts deleted file mode 100644 index 83ad66de..00000000 --- a/ui/src/providers/auth-provider/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { AuthProvider } from './auth-provider'; diff --git a/ui/src/providers/index.ts b/ui/src/providers/index.ts index f01e5ba2..37ac7157 100644 --- a/ui/src/providers/index.ts +++ b/ui/src/providers/index.ts @@ -1 +1,4 @@ export { AppProvider } from './app-provider'; +export { CustomMantineProvider } from './mantine-provider'; +export { RouterProvider } from './router-provider'; +export { ReactQueryProvider } from './react-query-provider'; diff --git a/ui/src/providers/mantine-provider.tsx b/ui/src/providers/mantine-provider.tsx index 98aceae6..1ea3fcbb 100644 --- a/ui/src/providers/mantine-provider.tsx +++ b/ui/src/providers/mantine-provider.tsx @@ -1,7 +1,7 @@ -import { createTheme, MantineProvider } from '@mantine/core'; +import { MantineProvider } from '@mantine/core'; import React from 'react'; import { theme } from '@/theme'; export const CustomMantineProvider = ({ children }: { children: React.ReactNode }) => ( - {children} + {children} ); diff --git a/ui/src/providers/react-query-provider.tsx b/ui/src/providers/react-query-provider.tsx index 35272306..8bcd8aca 100644 --- a/ui/src/providers/react-query-provider.tsx +++ b/ui/src/providers/react-query-provider.tsx @@ -1,8 +1,23 @@ import { PropsWithChildren } from 'react'; -import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { QueryCache, QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; +import { notifications } from '@mantine/notifications'; -const queryClient = new QueryClient(); +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + retry: 1, + }, + }, + queryCache: new QueryCache({ + onError: (error, query) => + notifications.show({ + title: `${error.name} for ${query.queryKey}`, + message: error.message, + color: 'red', + }), + }), +}); export const ReactQueryProvider = ({ children }: PropsWithChildren) => ( diff --git a/ui/src/providers/router-provider.tsx b/ui/src/providers/router-provider.tsx new file mode 100644 index 00000000..b0bfaf51 --- /dev/null +++ b/ui/src/providers/router-provider.tsx @@ -0,0 +1,4 @@ +import { RouterProvider as ReactRouterProvider } from 'react-router-dom'; +import { router } from '@/routes'; + +export const RouterProvider = () => ; diff --git a/ui/src/routes/index.ts b/ui/src/routes/index.ts new file mode 100644 index 00000000..164ab508 --- /dev/null +++ b/ui/src/routes/index.ts @@ -0,0 +1 @@ +export * from './router'; diff --git a/ui/src/routes/protected.tsx b/ui/src/routes/protected.tsx new file mode 100644 index 00000000..4778e5cd --- /dev/null +++ b/ui/src/routes/protected.tsx @@ -0,0 +1,12 @@ +import { Navigate, Outlet } from 'react-router-dom'; +import { useAuthStore } from '@/store'; + +export const Protected = () => { + const { token } = useAuthStore.getState(); + + if (!token) { + return ; + } + + return ; +}; diff --git a/ui/src/routes/router.tsx b/ui/src/routes/router.tsx new file mode 100644 index 00000000..75069b85 --- /dev/null +++ b/ui/src/routes/router.tsx @@ -0,0 +1,19 @@ +import { createBrowserRouter, createRoutesFromElements, Route } from 'react-router-dom'; +import { Protected } from '@/routes/protected'; +import { AppLayout } from '@/layouts'; +import { Login } from '@/pages'; +import { OAuthCallback } from '@/components'; + +export const router = createBrowserRouter( + createRoutesFromElements( + + }> + } /> + + {/*} />*/} + } /> + } /> + Not Found} /> + + ) +); diff --git a/ui/src/store/auth-store/auth-store.test.ts b/ui/src/store/auth-store/auth-store.test.ts index 196aeda3..24c53c4b 100644 --- a/ui/src/store/auth-store/auth-store.test.ts +++ b/ui/src/store/auth-store/auth-store.test.ts @@ -1,24 +1,9 @@ // tests for src/store/auth-store.ts -import { act } from 'react-dom/test-utils'; +import { act } from 'react'; import { renderHook } from '@testing-library/react'; import { useAuthStore } from '@/store/auth-store'; describe('useAuthStore', () => { - it('should toggle modal open and close', () => { - const { result } = renderHook(() => useAuthStore()); - expect(result.current.isModalOpen).toBe(false); - - act(() => { - result.current.openModal(); - }); - expect(result.current.isModalOpen).toBe(true); - - act(() => { - result.current.closeModal(); - }); - expect(result.current.isModalOpen).toBe(false); - }); - it('should set and update token', () => { const { result } = renderHook(() => useAuthStore()); expect(result.current.token).toBeNull(); diff --git a/ui/src/store/auth-store/auth-store.ts b/ui/src/store/auth-store/auth-store.ts index 0229fd9e..fbee72a8 100644 --- a/ui/src/store/auth-store/auth-store.ts +++ b/ui/src/store/auth-store/auth-store.ts @@ -1,47 +1,32 @@ import { create } from 'zustand'; import { persist } from 'zustand/middleware'; +import { customStorage } from '../custom-store'; interface AuthState { token: string | null; - isModalOpen: boolean; username: string | null; setToken: (token: string | null) => void; setUserName: (username: string) => void; - openModal: () => void; - closeModal: () => void; } -/* - * Coupling modals with state management. - * Not sure if it's a good idea to do this. - * - * Keep an eye on this approach - * Potential TODO - * */ - export const useAuthStore = create( persist( (set: any) => ({ token: null, - isModalOpen: false, username: null, setToken: (token: string | null) => set(() => ({ token })), setUserName: (username: string) => set(() => ({ username })), - openModal: () => set(() => ({ isModalOpen: true })), - closeModal: () => set(() => ({ isModalOpen: false })), }), { name: 'auth', + storage: customStorage, } ) ); - +// // // export const useAuthStore = create((set) => ({ // token: null, -// isModalOpen: false, // username: null, -// setToken: (token: string) => set(() => ({ token })), +// setToken: (token: string | null) => set(() => ({ token })), // setUserName: (username: string) => set(() => ({ username })), -// openModal: () => set(() => ({ isModalOpen: true })), -// closeModal: () => set(() => ({ isModalOpen: false })), // })); diff --git a/ui/src/store/custom-store.ts b/ui/src/store/custom-store.ts new file mode 100644 index 00000000..deee999c --- /dev/null +++ b/ui/src/store/custom-store.ts @@ -0,0 +1,29 @@ +import { PersistStorage } from 'zustand/middleware'; + +export const customStorage: PersistStorage = { + getItem: (name) => { + const item = localStorage.getItem(name); + if (!item) return null; + + const parsedItem = JSON.parse(item); + const now = new Date().getTime(); + + if (parsedItem.expiry && now > parsedItem.expiry) { + localStorage.removeItem(name); + return null; + } + + return parsedItem.value; + }, + setItem: (name, value) => { + const now = new Date().getTime(); + const item = { + value, + expiry: now + 6 * 60 * 60 * 1000, // 6 hours from now + }; + localStorage.setItem(name, JSON.stringify(item)); + }, + removeItem: (name) => { + localStorage.removeItem(name); + }, +}; diff --git a/ui/src/theme.ts b/ui/src/theme.ts index 9868cfb8..fbe6de71 100644 --- a/ui/src/theme.ts +++ b/ui/src/theme.ts @@ -1,6 +1,43 @@ -import { createTheme } from '@mantine/core'; +import { createTheme, rem } from '@mantine/core'; -// TODO: LOOK INTO LIGHT DARK THEME export const theme = createTheme({ - /** Put your mantine theme override here */ + colors: { + deepBlue: [ + '#eef3ff', + '#dce4f5', + '#b9c7e2', + '#94a8d0', + '#748dc1', + '#5f7cb8', + '#5474b4', + '#44639f', + '#39588f', + '#2d4b81', + ], + + blue: [ + '#eef3ff', + '#dee2f2', + '#bdc2de', + '#98a0ca', + '#7a84ba', + '#6672b0', + '#5c68ac', + '#4c5897', + '#424e88', + '#364379', + ], + }, + + shadows: { + md: '1px 1px 3px rgba(0, 0, 0, .25)', + xl: '5px 5px 3px rgba(0, 0, 0, .25)', + }, + + headings: { + fontFamily: 'Roboto, sans-serif', + sizes: { + h1: { fontSize: rem(36) }, + }, + }, }); diff --git a/ui/test-utils/__mocks__/index.ts b/ui/test-utils/__mocks__/index.ts new file mode 100644 index 00000000..41db9682 --- /dev/null +++ b/ui/test-utils/__mocks__/index.ts @@ -0,0 +1,3 @@ +export * from './user.mock'; +export * from './repo.mock'; +export * from './repos.mock'; diff --git a/ui/test-utils/__mocks__/repo.mock.ts b/ui/test-utils/__mocks__/repo.mock.ts new file mode 100644 index 00000000..dc4ff8e1 --- /dev/null +++ b/ui/test-utils/__mocks__/repo.mock.ts @@ -0,0 +1,56 @@ +import { faker } from '@faker-js/faker'; + +const createSecurityStatusMock = (): SecurityStatus => ({ + status: faker.helpers.arrayElement(['enabled', 'disabled']), +}); + +const createSecurityAndAnalysisMock = (): SecurityAndAnalysis => ({ + advanced_security: createSecurityStatusMock(), + secret_scanning: createSecurityStatusMock(), + secret_scanning_push_protection: createSecurityStatusMock(), +}); + +const createPermissionsMock = (): Permissions => + { + admin: faker.datatype.boolean(), + push: faker.datatype.boolean(), + pull: faker.datatype.boolean(), + }; + +const createOwnerMock = (): Owner => ({ + login: faker.internet.userName(), + id: faker.number.int(), + node_id: faker.string.uuid(), + avatar_url: faker.image.avatar(), + gravatar_id: '', + url: faker.internet.url(), + html_url: faker.internet.url(), + followers_url: faker.internet.url(), + following_url: faker.internet.url(), + gists_url: faker.internet.url(), + starred_url: faker.internet.url(), + subscriptions_url: faker.internet.url(), + organizations_url: faker.internet.url(), + repos_url: faker.internet.url(), + events_url: faker.internet.url(), + received_events_url: faker.internet.url(), + type: 'User', + site_admin: faker.datatype.boolean(), +}); + +export const createRepoMock = (): Repos => + { + id: faker.number.int(), + node_id: faker.string.uuid(), + name: faker.company.name(), + full_name: `${faker.company.name()}/${faker.internet.userName()}`, + owner: createOwnerMock(), + private: faker.datatype.boolean(), + html_url: faker.internet.url(), + description: faker.lorem.sentence(), + fork: faker.datatype.boolean(), + url: faker.internet.url(), + permissions: createPermissionsMock(), + security_and_analysis: createSecurityAndAnalysisMock(), + // Populate other URLs and properties as needed... + }; diff --git a/ui/test-utils/__mocks__/repos.mock.ts b/ui/test-utils/__mocks__/repos.mock.ts new file mode 100644 index 00000000..036f229f --- /dev/null +++ b/ui/test-utils/__mocks__/repos.mock.ts @@ -0,0 +1,7 @@ +import { createRepoMock } from './repo.mock'; + +export const useReposMock = () => ({ + data: Array.from({ length: 10 }, createRepoMock), + isLoading: false, + isError: false, +}); diff --git a/ui/test-utils/__mocks__/user.mock.ts b/ui/test-utils/__mocks__/user.mock.ts new file mode 100644 index 00000000..8b905c55 --- /dev/null +++ b/ui/test-utils/__mocks__/user.mock.ts @@ -0,0 +1,66 @@ +import { faker } from '@faker-js/faker'; + +export const createPlanMock = (): Plan => ({ + name: faker.company.name(), + space: faker.number.int({ min: 1000, max: 10000 }), + private_repos: faker.number.int({ max: 100 }), + collaborators: faker.number.int({ max: 10 }), +}); + +export const createUserMock = (): User => ({ + login: faker.internet.userName(), + id: faker.number.int(), + avatar_url: faker.image.avatar(), + subscriptions_url: faker.internet.url(), + organizations_url: faker.internet.url(), + name: faker.person.fullName(), +}); + +// +// export const createUserMock = (): User => ({ +// login: faker.internet.userName(), +// id: faker.number.int(), +// node_id: faker.string.uuid(), +// avatar_url: faker.image.avatar(), +// gravatar_id: '', +// url: faker.internet.url(), +// html_url: faker.internet.url(), +// followers_url: faker.internet.url(), +// following_url: faker.internet.url(), +// gists_url: faker.internet.url(), +// starred_url: faker.internet.url(), +// subscriptions_url: faker.internet.url(), +// organizations_url: faker.internet.url(), +// repos_url: faker.internet.url(), +// events_url: faker.internet.url(), +// received_events_url: faker.internet.url(), +// type: 'User', +// site_admin: faker.datatype.boolean(), +// name: faker.person.fullName(), +// company: faker.company.name(), +// blog: faker.internet.url(), +// location: faker.location.secondaryAddress(), +// email: faker.internet.email(), +// hireable: faker.datatype.boolean(), +// bio: faker.lorem.sentence(), +// twitter_username: faker.internet.userName(), +// public_repos: faker.number.int({ max: 100 }), +// public_gists: faker.number.int({ max: 100 }), +// followers: faker.number.int({ max: 1000 }), +// following: faker.number.int({ max: 1000 }), +// created_at: faker.date.past().toISOString(), +// updated_at: faker.date.recent().toISOString(), +// private_gists: faker.number.int({ max: 100 }), +// total_private_repos: faker.number.int({ max: 100 }), +// owned_private_repos: faker.number.int({ max: 100 }), +// disk_usage: faker.number.int({ max: 10000 }), +// collaborators: faker.number.int({ max: 10 }), +// two_factor_authentication: faker.datatype.boolean(), +// plan: createPlanMock(), +// }); + +export const useUserMock = () => ({ + data: createUserMock(), + isLoading: false, + isError: false, +}); diff --git a/ui/test-utils/index.ts b/ui/test-utils/index.ts index 15d3a0e4..c1da34b0 100644 --- a/ui/test-utils/index.ts +++ b/ui/test-utils/index.ts @@ -1,5 +1,5 @@ import userEvent from '@testing-library/user-event'; +import { screen, waitFor } from '@testing-library/react'; -export * from '@testing-library/react'; -export { render } from './render'; -export { userEvent }; +export { render, renderWithOutMocks } from './render'; +export { userEvent, screen, waitFor }; diff --git a/ui/test-utils/render.tsx b/ui/test-utils/render.tsx index 87f49c3e..b5d7f46e 100644 --- a/ui/test-utils/render.tsx +++ b/ui/test-utils/render.tsx @@ -1,27 +1,88 @@ import { render as testingLibraryRender } from '@testing-library/react'; import React from 'react'; import { vi } from 'vitest'; -import { AppProvider } from '@/providers'; +import { MemoryRouter } from 'react-router-dom'; +import { createUserMock, useReposMock, useUserMock } from './__mocks__'; +import { CustomMantineProvider, ReactQueryProvider } from '@/providers'; export function render(ui: React.ReactNode) { - vi.mock('@/store', () => ({ - useAuthStore: vi.fn(() => ({ - token: 'mock-token', - username: 'mock-username', - openModal: vi.fn(), - closeModal: vi.fn(), - })), - })); - - vi.mock('@/api/github-client', () => ({ - gitHubClient: vi.fn(() => ({ - get: vi.fn(() => Promise.resolve({ data: 'mocked data' })), - post: vi.fn(() => Promise.resolve({ data: 'mocked response' })), - // Add other methods as needed - })), - })); + /* + * Any updates to the store should be replicated here. + * */ + vi.mock('@/store', () => { + const useAuthStore = vi.fn(() => ({ + token: null, + username: null, + setToken: vi.fn(), + setUserName: vi.fn(), + })); + return { useAuthStore }; + }); + + /* If additional methods are added to api client, this wrapper needs to be updated + * As api-client is an abstraction of the underlying GitHub client, extending the app to other git managers + * will be easy. And this wrapper doesn't have to be updated. + * */ + + vi.mock('@/api', () => { + const getUserFn = vi.fn(() => Promise.resolve(createUserMock())); + const useUser = vi.fn(() => useUserMock()); + // const getReposFn = vi.fn(() => Promise.resolve(Array.from({ length: 10 }, createRepoMock))); + const useRepos = vi.fn(() => useReposMock()); + const useOAuth = vi.fn(() => ({ + data: { access_token: 'test' }, + isLoading: false, + isError: false, + })); + + const apiClient = { + get: vi.fn(() => Promise.resolve({ data: 'mocked get' })), + post: vi.fn(() => Promise.resolve({ data: 'mocked post' })), + put: vi.fn(() => Promise.resolve({ data: 'mocked put' })), + delete: vi.fn(() => Promise.resolve({ data: 'mocked delete' })), + patch: vi.fn(() => Promise.resolve({ data: 'mocked patch' })), + head: vi.fn(() => Promise.resolve({ data: 'mocked head' })), + options: vi.fn(() => Promise.resolve({ data: 'mocked options' })), + request: vi.fn(() => Promise.resolve({ data: 'mocked request' })), + interceptors: { + request: { use: vi.fn(), eject: vi.fn() }, + response: { use: vi.fn(), eject: vi.fn() }, + }, + defaults: { headers: { common: {} } }, + }; + + return { getUserFn, useUser, useRepos, apiClient, useOAuth, githubLogin: vi.fn() }; + }); + + vi.mock('react-router-dom', async (importOriginal) => { + const actual = await importOriginal(); // Import the actual module + return { + // @ts-ignore + ...actual, // Spread all original exports + useNavigate: vi.fn(), // Override specific exports you want to mock + }; + }); + + return testingLibraryRender(<>{ui}, { + wrapper: ({ children }: { children: React.ReactNode }) => ( + + + {children} + + + ), + }); +} + +export function renderWithOutMocks(ui: React.ReactNode) { return testingLibraryRender(<>{ui}, { - wrapper: ({ children }: { children: React.ReactNode }) => {children}, + wrapper: ({ children }: { children: React.ReactNode }) => ( + + + {children} + + + ), }); } diff --git a/ui/vite.config.mjs b/ui/vite.config.mjs index d225fb8d..e800cd15 100644 --- a/ui/vite.config.mjs +++ b/ui/vite.config.mjs @@ -1,12 +1,43 @@ import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; import tsconfigPaths from 'vite-tsconfig-paths'; +import istanbul from 'vite-plugin-istanbul'; export default defineConfig({ - plugins: [react(), tsconfigPaths()], + plugins: [react(), tsconfigPaths(), istanbul({ + include: [ + 'src/**/*.ts', + 'src/**/*.tsx', + ], + exclude: [ + 'node_modules/**', + 'tests/**', + '**/__*', + '**/*.mock.ts', + 'test-utils/__mocks__/**', + 'postcss.config.cjs', + ], + extension: [ '.ts', '.tsx' ], + cypress: false, + requireEnv: false + })], test: { globals: true, environment: 'jsdom', setupFiles: './vitest.setup.mjs', }, -}); \ No newline at end of file + coverage: { + provider: 'v8', + reporter: ['text', 'html'], + }, + server: { + proxy: { + '/login/oauth/access_token': { + target: 'https://github.com', + changeOrigin: true, + rewrite: path => path.replace(/^\/login\/oauth\/access_token/, '/login/oauth/access_token') + + } + } + } +}); diff --git a/ui/yarn.lock b/ui/yarn.lock index ac11e8cc..d5520c45 100644 --- a/ui/yarn.lock +++ b/ui/yarn.lock @@ -7,7 +7,7 @@ resolved "https://registry.yarnpkg.com/@adobe/css-tools/-/css-tools-4.3.3.tgz#90749bde8b89cd41764224f5aac29cd4138f75ff" integrity sha512-rE0Pygv0sEZ4vBWHlAgJLGDU7Pm8xoO6p3wsEceb7GYAjScrOHpEo8KK/eVkAcnSM+slAEtXjA2JpdjLp4fJQQ== -"@ampproject/remapping@^2.2.0": +"@ampproject/remapping@^2.2.0", "@ampproject/remapping@^2.2.1": version "2.3.0" resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.3.0.tgz#ed441b6fa600072520ce18b43d2c8cc8caecc7f4" integrity sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw== @@ -23,11 +23,24 @@ "@babel/highlight" "^7.24.2" picocolors "^1.0.0" +"@babel/code-frame@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.24.6.tgz#ab88da19344445c3d8889af2216606d3329f3ef2" + integrity sha512-ZJhac6FkEd1yhG2AHOmfcXG4ceoLltoCVJjN5XsWN9BifBQr+cHJbWi0h68HZuSORq+3WtJ2z0hwF2NG1b5kcA== + dependencies: + "@babel/highlight" "^7.24.6" + picocolors "^1.0.0" + "@babel/compat-data@^7.23.5": version "7.24.4" resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.24.4.tgz#6f102372e9094f25d908ca0d34fc74c74606059a" integrity sha512-vg8Gih2MLK+kOkHJp4gBEIkyaIi00jgWot2D9QOmmfLC8jINSOzmCLta6Bvz/JSBCqnegV0L80jhxkol5GWNfQ== +"@babel/compat-data@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.24.6.tgz#b3600217688cabb26e25f8e467019e66d71b7ae2" + integrity sha512-aC2DGhBq5eEdyXWqrDInSqQjO0k8xtPRf5YylULqx8MCd6jBtzqfta/3ETMRpuKIc5hyswfO80ObyA1MvkCcUQ== + "@babel/core@^7.23.5": version "7.24.5" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.24.5.tgz#15ab5b98e101972d171aeef92ac70d8d6718f06a" @@ -49,6 +62,27 @@ json5 "^2.2.3" semver "^6.3.1" +"@babel/core@^7.23.9": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.24.6.tgz#8650e0e4b03589ebe886c4e4a60398db0a7ec787" + integrity sha512-qAHSfAdVyFmIvl0VHELib8xar7ONuSHrE2hLnsaWkYNTI68dmi1x8GYDhJjMI/e7XWal9QBlZkwbOnkcw7Z8gQ== + dependencies: + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.24.6" + "@babel/generator" "^7.24.6" + "@babel/helper-compilation-targets" "^7.24.6" + "@babel/helper-module-transforms" "^7.24.6" + "@babel/helpers" "^7.24.6" + "@babel/parser" "^7.24.6" + "@babel/template" "^7.24.6" + "@babel/traverse" "^7.24.6" + "@babel/types" "^7.24.6" + convert-source-map "^2.0.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" + "@babel/generator@^7.24.5": version "7.24.5" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.24.5.tgz#e5afc068f932f05616b66713e28d0f04e99daeb3" @@ -59,6 +93,16 @@ "@jridgewell/trace-mapping" "^0.3.25" jsesc "^2.5.1" +"@babel/generator@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.24.6.tgz#dfac82a228582a9d30c959fe50ad28951d4737a7" + integrity sha512-S7m4eNa6YAPJRHmKsLHIDJhNAGNKoWNiWefz1MBbpnt8g9lvMDl1hir4P9bo/57bQEmuwEhnRU/AMWsD0G/Fbg== + dependencies: + "@babel/types" "^7.24.6" + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.25" + jsesc "^2.5.1" + "@babel/helper-compilation-targets@^7.23.6": version "7.23.6" resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz#4d79069b16cbcf1461289eccfbbd81501ae39991" @@ -70,11 +114,27 @@ lru-cache "^5.1.1" semver "^6.3.1" +"@babel/helper-compilation-targets@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.24.6.tgz#4a51d681f7680043d38e212715e2a7b1ad29cb51" + integrity sha512-VZQ57UsDGlX/5fFA7GkVPplZhHsVc+vuErWgdOiysI9Ksnw0Pbbd6pnPiR/mmJyKHgyIW0c7KT32gmhiF+cirg== + dependencies: + "@babel/compat-data" "^7.24.6" + "@babel/helper-validator-option" "^7.24.6" + browserslist "^4.22.2" + lru-cache "^5.1.1" + semver "^6.3.1" + "@babel/helper-environment-visitor@^7.22.20": version "7.22.20" resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167" integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA== +"@babel/helper-environment-visitor@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.6.tgz#ac7ad5517821641550f6698dd5468f8cef78620d" + integrity sha512-Y50Cg3k0LKLMjxdPjIl40SdJgMB85iXn27Vk/qbHZCFx/o5XO3PSnpi675h1KEmmDb6OFArfd5SCQEQ5Q4H88g== + "@babel/helper-function-name@^7.23.0": version "7.23.0" resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz#1f9a3cdbd5b2698a670c30d2735f9af95ed52759" @@ -83,6 +143,14 @@ "@babel/template" "^7.22.15" "@babel/types" "^7.23.0" +"@babel/helper-function-name@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.24.6.tgz#cebdd063386fdb95d511d84b117e51fc68fec0c8" + integrity sha512-xpeLqeeRkbxhnYimfr2PC+iA0Q7ljX/d1eZ9/inYbmfG2jpl8Lu3DyXvpOAnrS5kxkfOWJjioIMQsaMBXFI05w== + dependencies: + "@babel/template" "^7.24.6" + "@babel/types" "^7.24.6" + "@babel/helper-hoist-variables@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz#c01a007dac05c085914e8fb652b339db50d823bb" @@ -90,6 +158,13 @@ dependencies: "@babel/types" "^7.22.5" +"@babel/helper-hoist-variables@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.6.tgz#8a7ece8c26756826b6ffcdd0e3cf65de275af7f9" + integrity sha512-SF/EMrC3OD7dSta1bLJIlrsVxwtd0UpjRJqLno6125epQMJ/kyFmpTT4pbvPbdQHzCHg+biQ7Syo8lnDtbR+uA== + dependencies: + "@babel/types" "^7.24.6" + "@babel/helper-module-imports@^7.24.3": version "7.24.3" resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.24.3.tgz#6ac476e6d168c7c23ff3ba3cf4f7841d46ac8128" @@ -97,6 +172,13 @@ dependencies: "@babel/types" "^7.24.0" +"@babel/helper-module-imports@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.24.6.tgz#65e54ffceed6a268dc4ce11f0433b82cfff57852" + integrity sha512-a26dmxFJBF62rRO9mmpgrfTLsAuyHk4e1hKTUkD/fcMfynt8gvEKwQPQDVxWhca8dHoDck+55DFt42zV0QMw5g== + dependencies: + "@babel/types" "^7.24.6" + "@babel/helper-module-transforms@^7.24.5": version "7.24.5" resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.24.5.tgz#ea6c5e33f7b262a0ae762fd5986355c45f54a545" @@ -108,6 +190,17 @@ "@babel/helper-split-export-declaration" "^7.24.5" "@babel/helper-validator-identifier" "^7.24.5" +"@babel/helper-module-transforms@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.24.6.tgz#22346ed9df44ce84dee850d7433c5b73fab1fe4e" + integrity sha512-Y/YMPm83mV2HJTbX1Qh2sjgjqcacvOlhbzdCCsSlblOKjSYmQqEbO6rUniWQyRo9ncyfjT8hnUjlG06RXDEmcA== + dependencies: + "@babel/helper-environment-visitor" "^7.24.6" + "@babel/helper-module-imports" "^7.24.6" + "@babel/helper-simple-access" "^7.24.6" + "@babel/helper-split-export-declaration" "^7.24.6" + "@babel/helper-validator-identifier" "^7.24.6" + "@babel/helper-plugin-utils@^7.24.0", "@babel/helper-plugin-utils@^7.24.5": version "7.24.5" resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.5.tgz#a924607dd254a65695e5bd209b98b902b3b2f11a" @@ -120,6 +213,13 @@ dependencies: "@babel/types" "^7.24.5" +"@babel/helper-simple-access@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.24.6.tgz#1d6e04d468bba4fc963b4906f6dac6286cfedff1" + integrity sha512-nZzcMMD4ZhmB35MOOzQuiGO5RzL6tJbsT37Zx8M5L/i9KSrukGXWTjLe1knIbb/RmxoJE9GON9soq0c0VEMM5g== + dependencies: + "@babel/types" "^7.24.6" + "@babel/helper-split-export-declaration@^7.24.5": version "7.24.5" resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.5.tgz#b9a67f06a46b0b339323617c8c6213b9055a78b6" @@ -127,21 +227,43 @@ dependencies: "@babel/types" "^7.24.5" +"@babel/helper-split-export-declaration@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.6.tgz#e830068f7ba8861c53b7421c284da30ae656d7a3" + integrity sha512-CvLSkwXGWnYlF9+J3iZUvwgAxKiYzK3BWuo+mLzD/MDGOZDj7Gq8+hqaOkMxmJwmlv0iu86uH5fdADd9Hxkymw== + dependencies: + "@babel/types" "^7.24.6" + "@babel/helper-string-parser@^7.24.1": version "7.24.1" resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz#f99c36d3593db9540705d0739a1f10b5e20c696e" integrity sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ== +"@babel/helper-string-parser@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.24.6.tgz#28583c28b15f2a3339cfafafeaad42f9a0e828df" + integrity sha512-WdJjwMEkmBicq5T9fm/cHND3+UlFa2Yj8ALLgmoSQAJZysYbBjw+azChSGPN4DSPLXOcooGRvDwZWMcF/mLO2Q== + "@babel/helper-validator-identifier@^7.24.5": version "7.24.5" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.5.tgz#918b1a7fa23056603506370089bd990d8720db62" integrity sha512-3q93SSKX2TWCG30M2G2kwaKeTYgEUp5Snjuj8qm729SObL6nbtUldAi37qbxkD5gg3xnBio+f9nqpSepGZMvxA== +"@babel/helper-validator-identifier@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.6.tgz#08bb6612b11bdec78f3feed3db196da682454a5e" + integrity sha512-4yA7s865JHaqUdRbnaxarZREuPTHrjpDT+pXoAZ1yhyo6uFnIEpS8VMu16siFOHDpZNKYv5BObhsB//ycbICyw== + "@babel/helper-validator-option@^7.23.5": version "7.23.5" resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz#907a3fbd4523426285365d1206c423c4c5520307" integrity sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw== +"@babel/helper-validator-option@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.24.6.tgz#59d8e81c40b7d9109ab7e74457393442177f460a" + integrity sha512-Jktc8KkF3zIkePb48QO+IapbXlSapOW9S+ogZZkcO6bABgYAxtZcjZ/O005111YLf+j4M84uEgwYoidDkXbCkQ== + "@babel/helpers@^7.24.5": version "7.24.5" resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.24.5.tgz#fedeb87eeafa62b621160402181ad8585a22a40a" @@ -151,6 +273,14 @@ "@babel/traverse" "^7.24.5" "@babel/types" "^7.24.5" +"@babel/helpers@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.24.6.tgz#cd124245299e494bd4e00edda0e4ea3545c2c176" + integrity sha512-V2PI+NqnyFu1i0GyTd/O/cTpxzQCYioSkUIRmgo7gFEHKKCg5w46+r/A6WeUR1+P3TeQ49dspGPNd/E3n9AnnA== + dependencies: + "@babel/template" "^7.24.6" + "@babel/types" "^7.24.6" + "@babel/highlight@^7.24.2": version "7.24.5" resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.24.5.tgz#bc0613f98e1dd0720e99b2a9ee3760194a704b6e" @@ -161,11 +291,26 @@ js-tokens "^4.0.0" picocolors "^1.0.0" -"@babel/parser@^7.1.0", "@babel/parser@^7.20.7", "@babel/parser@^7.24.0", "@babel/parser@^7.24.5": +"@babel/highlight@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.24.6.tgz#6d610c1ebd2c6e061cade0153bf69b0590b7b3df" + integrity sha512-2YnuOp4HAk2BsBrJJvYCbItHx0zWscI1C3zgWkz+wDyD9I7GIVrfnLyrR4Y1VR+7p+chAEcrgRQYZAGIKMV7vQ== + dependencies: + "@babel/helper-validator-identifier" "^7.24.6" + chalk "^2.4.2" + js-tokens "^4.0.0" + picocolors "^1.0.0" + +"@babel/parser@^7.1.0", "@babel/parser@^7.20.7", "@babel/parser@^7.24.0", "@babel/parser@^7.24.4", "@babel/parser@^7.24.5": version "7.24.5" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.24.5.tgz#4a4d5ab4315579e5398a82dcf636ca80c3392790" integrity sha512-EOv5IK8arwh3LI47dz1b0tKUb/1uhHAnHJOrjgtQMIpu1uXd9mlFrJg9IUgGUgZ41Ch0K8REPTYpO7B76b4vJg== +"@babel/parser@^7.23.9", "@babel/parser@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.24.6.tgz#5e030f440c3c6c78d195528c3b688b101a365328" + integrity sha512-eNZXdfU35nJC2h24RznROuOpO94h6x8sg9ju0tT9biNtLZ2vuP8SduLqqV+/8+cebSLV9SJEAN5Z3zQbJG/M+Q== + "@babel/plugin-transform-react-jsx-self@^7.23.3": version "7.24.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.24.5.tgz#22cc7572947895c8e4cd034462e65d8ecf857756" @@ -180,7 +325,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.24.0" -"@babel/runtime@^7.12.5", "@babel/runtime@^7.20.13", "@babel/runtime@^7.23.2", "@babel/runtime@^7.9.2": +"@babel/runtime@^7.12.5", "@babel/runtime@^7.20.13", "@babel/runtime@^7.23.2", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": version "7.24.5" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.5.tgz#230946857c053a36ccc66e1dd03b17dd0c4ed02c" integrity sha512-Nms86NXrsaeU9vbBJKni6gXiEXZ4CVpYVzEjDH9Sb8vmZ3UljyA1GSOJl/6LGPO8EHLuSF9H+IxNXHPX8QHJ4g== @@ -196,6 +341,15 @@ "@babel/parser" "^7.24.0" "@babel/types" "^7.24.0" +"@babel/template@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.24.6.tgz#048c347b2787a6072b24c723664c8d02b67a44f9" + integrity sha512-3vgazJlLwNXi9jhrR1ef8qiB65L1RK90+lEQwv4OxveHnqC3BfmnHdgySwRLzf6akhlOYenT+b7AfWq+a//AHw== + dependencies: + "@babel/code-frame" "^7.24.6" + "@babel/parser" "^7.24.6" + "@babel/types" "^7.24.6" + "@babel/traverse@^7.24.5": version "7.24.5" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.24.5.tgz#972aa0bc45f16983bf64aa1f877b2dd0eea7e6f8" @@ -212,6 +366,22 @@ debug "^4.3.1" globals "^11.1.0" +"@babel/traverse@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.24.6.tgz#0941ec50cdeaeacad0911eb67ae227a4f8424edc" + integrity sha512-OsNjaJwT9Zn8ozxcfoBc+RaHdj3gFmCmYoQLUII1o6ZrUwku0BMg80FoOTPx+Gi6XhcQxAYE4xyjPTo4SxEQqw== + dependencies: + "@babel/code-frame" "^7.24.6" + "@babel/generator" "^7.24.6" + "@babel/helper-environment-visitor" "^7.24.6" + "@babel/helper-function-name" "^7.24.6" + "@babel/helper-hoist-variables" "^7.24.6" + "@babel/helper-split-export-declaration" "^7.24.6" + "@babel/parser" "^7.24.6" + "@babel/types" "^7.24.6" + debug "^4.3.1" + globals "^11.1.0" + "@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.22.5", "@babel/types@^7.23.0", "@babel/types@^7.24.0", "@babel/types@^7.24.5": version "7.24.5" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.24.5.tgz#7661930afc638a5383eb0c4aee59b74f38db84d7" @@ -221,6 +391,20 @@ "@babel/helper-validator-identifier" "^7.24.5" to-fast-properties "^2.0.0" +"@babel/types@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.24.6.tgz#ba4e1f59870c10dc2fa95a274ac4feec23b21912" + integrity sha512-WaMsgi6Q8zMgMth93GvWPXkhAIEobfsIkLTacoVZoK1J0CevIPGYY2Vo5YvJGqyHqXM6P4ppOYGsIRU8MM9pFQ== + dependencies: + "@babel/helper-string-parser" "^7.24.6" + "@babel/helper-validator-identifier" "^7.24.6" + to-fast-properties "^2.0.0" + +"@bcoe/v8-coverage@^0.2.3": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" + integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== + "@csstools/css-parser-algorithms@^2.6.1": version "2.6.3" resolved "https://registry.yarnpkg.com/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.6.3.tgz#b5e7eb2bd2a42e968ef61484f1490a8a4148a8eb" @@ -410,6 +594,11 @@ resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.0.tgz#a5417ae8427873f1dd08b70b3574b453e67b5f7f" integrity sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g== +"@faker-js/faker@^8.4.1": + version "8.4.1" + resolved "https://registry.yarnpkg.com/@faker-js/faker/-/faker-8.4.1.tgz#5d5e8aee8fce48f5e189bf730ebd1f758f491451" + integrity sha512-XQ3cU+Q8Uqmrbf2e0cIC/QN43sTBSC8KF12u29Mb47tWrt2hAgBXSgpZMj4Ao8Uk0iJcU99QsOCaIL8934obCg== + "@floating-ui/core@^1.0.0": version "1.6.1" resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.6.1.tgz#a4e6fef1b069cda533cbc7a4998c083a37f37573" @@ -465,6 +654,22 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3" integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA== +"@istanbuljs/load-nyc-config@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" + integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== + dependencies: + camelcase "^5.3.1" + find-up "^4.1.0" + get-package-type "^0.1.0" + js-yaml "^3.13.1" + resolve-from "^5.0.0" + +"@istanbuljs/schema@^0.1.2", "@istanbuljs/schema@^0.1.3": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" + integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== + "@jest/expect-utils@^29.7.0": version "29.7.0" resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.7.0.tgz#023efe5d26a8a70f21677d0a1afc0f0a44e3a1c6" @@ -515,7 +720,7 @@ resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== -"@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": +"@jridgewell/trace-mapping@^0.3.23", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": version "0.3.25" resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0" integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== @@ -548,6 +753,19 @@ resolved "https://registry.yarnpkg.com/@mantine/hooks/-/hooks-7.9.0.tgz#41ecc95d55da398c8c0718743f8cac13bf66b74d" integrity sha512-LKgyrlaIK0S/gcn/VDbhqLBZOYjvhXfVcH7rMs4MIBVD+wuRo2LvvAYe3cUfQbBCfmlpRjqvewwvsIYYsjSofQ== +"@mantine/notifications@^7.9.2": + version "7.9.2" + resolved "https://registry.yarnpkg.com/@mantine/notifications/-/notifications-7.9.2.tgz#dd1aa2f5d635b7c305d9c9738eb161bfaae12895" + integrity sha512-ERgmPLkiVPOqPjCVfSSK2QcRb/2W9wJVPpIlkSyMNYUWosceAH9uPhZCtnWxyRqH/PLhYtOOflxq2i4hiArEJQ== + dependencies: + "@mantine/store" "7.9.2" + react-transition-group "4.4.5" + +"@mantine/store@7.9.2": + version "7.9.2" + resolved "https://registry.yarnpkg.com/@mantine/store/-/store-7.9.2.tgz#1b20ba4acff3c085660e28e2daa11705ae1ddcba" + integrity sha512-oqCjse3cAp0DQI1fT5AWLW+Me6Mu4b2DVPpoRRwm7Ptw8gzUEmxb/9Brx2rkhaAym+S9sGe8IdEpNVLXaZyGXw== + "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" @@ -1034,6 +1252,25 @@ "@types/babel__core" "^7.20.5" react-refresh "^0.14.0" +"@vitest/coverage-v8@^1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@vitest/coverage-v8/-/coverage-v8-1.6.0.tgz#2f54ccf4c2d9f23a71294aba7f95b3d2e27d14e7" + integrity sha512-KvapcbMY/8GYIG0rlwwOKCVNRc0OL20rrhFkg/CHNzncV03TE2XWvO5w9uZYoxNiMEBacAJt3unSOiZ7svePew== + dependencies: + "@ampproject/remapping" "^2.2.1" + "@bcoe/v8-coverage" "^0.2.3" + debug "^4.3.4" + istanbul-lib-coverage "^3.2.2" + istanbul-lib-report "^3.0.1" + istanbul-lib-source-maps "^5.0.4" + istanbul-reports "^3.1.6" + magic-string "^0.30.5" + magicast "^0.3.3" + picocolors "^1.0.0" + std-env "^3.5.0" + strip-literal "^2.0.0" + test-exclude "^6.0.0" + "@vitest/expect@1.6.0": version "1.6.0" resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-1.6.0.tgz#0b3ba0914f738508464983f4d811bc122b51fb30" @@ -1149,6 +1386,13 @@ ansi-styles@^5.0.0: resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + argparse@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" @@ -1388,6 +1632,11 @@ camelcase-css@^2.0.1: resolved "https://registry.yarnpkg.com/camelcase-css/-/camelcase-css-2.0.1.tgz#ee978f6947914cc30c6b44741b6ed1df7f043fd5" integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA== +camelcase@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + camelize@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/camelize/-/camelize-1.0.1.tgz#89b7e16884056331a35d6b5ad064332c91daa6c3" @@ -1746,6 +1995,14 @@ dom-accessibility-api@^0.5.6, dom-accessibility-api@^0.5.9: resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz#5a7429e6066eb3664d911e33fb0e45de8eb08453" integrity sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg== +dom-helpers@^5.0.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.1.tgz#d9400536b2bf8225ad98fe052e029451ac40e902" + integrity sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA== + dependencies: + "@babel/runtime" "^7.8.7" + csstype "^3.0.2" + electron-to-chromium@^1.4.668: version "1.4.757" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.757.tgz#45f7c9341b538f8c4b9ca8af9692e0ed1a776a44" @@ -2092,6 +2349,11 @@ eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4 resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== +eslint-visitor-keys@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz#e3adc021aa038a2a8e0b2f8b0ce8f66b9483b1fb" + integrity sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw== + eslint@^8.57.0: version "8.57.0" resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.57.0.tgz#c786a6fd0e0b68941aaf624596fb987089195668" @@ -2136,6 +2398,15 @@ eslint@^8.57.0: strip-ansi "^6.0.1" text-table "^0.2.0" +espree@^10.0.1: + version "10.0.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-10.0.1.tgz#600e60404157412751ba4a6f3a2ee1a42433139f" + integrity sha512-MWkrWZbJsL2UwnjxTX3gG8FneachS/Mwg7tdGXce011sJd5b0JG54vat5KHnfSBODZ3Wvzd2WnjxyzsRoVv+ww== + dependencies: + acorn "^8.11.3" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^4.0.0" + espree@^9.6.0, espree@^9.6.1: version "9.6.1" resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f" @@ -2145,6 +2416,11 @@ espree@^9.6.0, espree@^9.6.1: acorn-jsx "^5.3.2" eslint-visitor-keys "^3.4.1" +esprima@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + esquery@^1.4.2: version "1.5.0" resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.5.0.tgz#6ce17738de8577694edd7361c57182ac8cb0db0b" @@ -2261,6 +2537,14 @@ fill-range@^7.0.1: dependencies: to-regex-range "^5.0.1" +find-up@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + find-up@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" @@ -2368,6 +2652,11 @@ get-nonce@^1.0.0: resolved "https://registry.yarnpkg.com/get-nonce/-/get-nonce-1.0.1.tgz#fdf3f0278073820d2ce9426c18f07481b1e0cdf3" integrity sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q== +get-package-type@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" + integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== + get-stream@^8.0.1: version "8.0.1" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-8.0.1.tgz#def9dfd71742cd7754a7761ed43749a27d02eca2" @@ -2396,7 +2685,7 @@ glob-parent@^6.0.2: dependencies: is-glob "^4.0.3" -glob@^7.1.3: +glob@^7.1.3, glob@^7.1.4: version "7.2.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== @@ -2548,6 +2837,11 @@ html-encoding-sniffer@^4.0.0: dependencies: whatwg-encoding "^3.1.1" +html-escaper@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" + integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== + html-tags@^3.3.1: version "3.3.1" resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-3.3.1.tgz#a04026a18c882e4bba8a01a3d39cfe465d40b5ce" @@ -2858,6 +3152,48 @@ isexe@^2.0.0: resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== +istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0, istanbul-lib-coverage@^3.2.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz#2d166c4b0644d43a39f04bf6c2edd1e585f31756" + integrity sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg== + +istanbul-lib-instrument@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.2.tgz#91655936cf7380e4e473383081e38478b69993b1" + integrity sha512-1WUsZ9R1lA0HtBSohTkm39WTPlNKSJ5iFk7UwqXkBLoHQT+hfqPsfsTDVuZdKGaBwn7din9bS7SsnoAr943hvw== + dependencies: + "@babel/core" "^7.23.9" + "@babel/parser" "^7.23.9" + "@istanbuljs/schema" "^0.1.3" + istanbul-lib-coverage "^3.2.0" + semver "^7.5.4" + +istanbul-lib-report@^3.0.0, istanbul-lib-report@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz#908305bac9a5bd175ac6a74489eafd0fc2445a7d" + integrity sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw== + dependencies: + istanbul-lib-coverage "^3.0.0" + make-dir "^4.0.0" + supports-color "^7.1.0" + +istanbul-lib-source-maps@^5.0.4: + version "5.0.4" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.4.tgz#1947003c72a91b6310efeb92d2a91be8804d92c2" + integrity sha512-wHOoEsNJTVltaJp8eVkm8w+GVkVNHT2YDYo53YdzQEL2gWm1hBX5cGFR9hQJtuGLebidVX7et3+dmDZrmclduw== + dependencies: + "@jridgewell/trace-mapping" "^0.3.23" + debug "^4.1.1" + istanbul-lib-coverage "^3.0.0" + +istanbul-reports@^3.1.6: + version "3.1.7" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.7.tgz#daed12b9e1dca518e15c056e1e537e741280fa0b" + integrity sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g== + dependencies: + html-escaper "^2.0.0" + istanbul-lib-report "^3.0.0" + iterator.prototype@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/iterator.prototype/-/iterator.prototype-1.1.2.tgz#5e29c8924f01916cb9335f1ff80619dcff22b0c0" @@ -2956,6 +3292,14 @@ js-tokens@^9.0.0: resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-9.0.0.tgz#0f893996d6f3ed46df7f0a3b12a03f5fd84223c1" integrity sha512-WriZw1luRMlmV3LGJaR6QOJjWwgLUTf89OwT2lUOyjX2dJGBwgmIkbcz+7WFZjrZM635JOIR517++e/67CP9dQ== +js-yaml@^3.13.1: + version "3.14.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" + integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + js-yaml@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" @@ -3097,6 +3441,13 @@ local-pkg@^0.5.0: mlly "^1.4.2" pkg-types "^1.0.3" +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== + dependencies: + p-locate "^4.1.0" + locate-path@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" @@ -3159,6 +3510,22 @@ magic-string@^0.30.5: dependencies: "@jridgewell/sourcemap-codec" "^1.4.15" +magicast@^0.3.3: + version "0.3.4" + resolved "https://registry.yarnpkg.com/magicast/-/magicast-0.3.4.tgz#bbda1791d03190a24b00ff3dd18151e7fd381d19" + integrity sha512-TyDF/Pn36bBji9rWKHlZe+PZb6Mx5V8IHCSxk7X4aljM4e/vyDvZZYwHewdVaqiA0nb3ghfHU/6AUpDxWoER2Q== + dependencies: + "@babel/parser" "^7.24.4" + "@babel/types" "^7.24.0" + source-map-js "^1.2.0" + +make-dir@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-4.0.0.tgz#c3c2307a771277cd9638305f915c29ae741b614e" + integrity sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw== + dependencies: + semver "^7.5.3" + mathml-tag-names@^2.1.3: version "2.1.3" resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz#4ddadd67308e780cf16a47685878ee27b736a0a3" @@ -3214,7 +3581,7 @@ min-indent@^1.0.0: resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== -minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: +minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== @@ -3390,6 +3757,13 @@ optionator@^0.9.3: type-check "^0.4.0" word-wrap "^1.2.5" +p-limit@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + p-limit@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" @@ -3404,6 +3778,13 @@ p-limit@^5.0.0: dependencies: yocto-queue "^1.0.0" +p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== + dependencies: + p-limit "^2.2.0" + p-locate@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" @@ -3411,6 +3792,11 @@ p-locate@^5.0.0: dependencies: p-limit "^3.0.2" +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + parent-module@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" @@ -3615,7 +4001,7 @@ pretty-format@^29.0.0, pretty-format@^29.7.0: ansi-styles "^5.0.0" react-is "^18.0.0" -prop-types@^15.7.2, prop-types@^15.8.1: +prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1: version "15.8.1" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== @@ -3736,6 +4122,16 @@ react-textarea-autosize@8.5.3: use-composed-ref "^1.3.0" use-latest "^1.2.1" +react-transition-group@4.4.5: + version "4.4.5" + resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.5.tgz#e53d4e3f3344da8521489fbef8f2581d42becdd1" + integrity sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g== + dependencies: + "@babel/runtime" "^7.5.5" + dom-helpers "^5.0.1" + loose-envify "^1.4.0" + prop-types "^15.6.2" + react@^18.3.1: version "18.3.1" resolved "https://registry.yarnpkg.com/react/-/react-18.3.1.tgz#49ab892009c53933625bd16b2533fc754cab2891" @@ -3909,6 +4305,11 @@ semver@^6.3.0, semver@^6.3.1: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== +semver@^7.5.3, semver@^7.5.4: + version "7.6.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.2.tgz#1e3b34759f896e8f14d6134732ce798aeb0c6e13" + integrity sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w== + semver@^7.6.0: version "7.6.0" resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.0.tgz#1a46a4db4bffcccd97b743b5005c8325f23d4e2d" @@ -3994,6 +4395,16 @@ source-map-js@^1.0.1, source-map-js@^1.0.2, source-map-js@^1.2.0: resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.0.tgz#16b809c162517b5b8c3e7dcd315a2a5c2612b2af" integrity sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg== +source-map@^0.7.4: + version "0.7.4" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.4.tgz#a9bbe705c9d8846f4e08ff6765acf0f1b0898656" + integrity sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA== + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== + stack-utils@^2.0.3: version "2.0.6" resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f" @@ -4279,6 +4690,15 @@ table@^6.8.2: string-width "^4.2.3" strip-ansi "^6.0.1" +test-exclude@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" + integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== + dependencies: + "@istanbuljs/schema" "^0.1.2" + glob "^7.1.4" + minimatch "^3.0.4" + text-table@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" @@ -4530,6 +4950,18 @@ vite-node@1.6.0: picocolors "^1.0.0" vite "^5.0.0" +vite-plugin-istanbul@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/vite-plugin-istanbul/-/vite-plugin-istanbul-6.0.2.tgz#242a57b20d540adc49631f9e6c174a1c3559ece7" + integrity sha512-0/sKwjEEIwbEyl43xX7onX3dIbMJAsigNsKyyVPalG1oRFo5jn3qkJbS2PUfp9wrr3piy1eT6qRoeeum2p4B2A== + dependencies: + "@istanbuljs/load-nyc-config" "^1.1.0" + espree "^10.0.1" + istanbul-lib-instrument "^6.0.2" + picocolors "^1.0.0" + source-map "^0.7.4" + test-exclude "^6.0.0" + vite-tsconfig-paths@^4.3.1: version "4.3.2" resolved "https://registry.yarnpkg.com/vite-tsconfig-paths/-/vite-tsconfig-paths-4.3.2.tgz#321f02e4b736a90ff62f9086467faf4e2da857a9"