From c3b4fee90b80c9569c72aee4f0b82febe88746c0 Mon Sep 17 00:00:00 2001 From: Matthew Warman Date: Sun, 27 Oct 2024 08:11:44 -0400 Subject: [PATCH 1/7] upgrade deps; new lint rules --- package-lock.json | 141 ++++++++---------- package.json | 12 +- src/__fixtures__/toasts.ts | 2 +- src/components/Toast/Toast.tsx | 2 +- src/components/Toast/__tests__/Toast.test.tsx | 2 +- src/hooks/useAuth.ts | 2 +- src/hooks/useAxios.ts | 2 +- src/hooks/useConfig.ts | 3 +- src/hooks/useSettings.ts | 2 +- src/hooks/useToasts.ts | 2 +- src/providers/AuthContext.ts | 25 ++++ src/providers/AuthProvider.tsx | 27 +--- src/providers/AxiosContext.ts | 17 +++ src/providers/AxiosProvider.tsx | 28 +--- src/providers/ConfigContext.ts | 22 +++ src/providers/ConfigProvider.tsx | 52 ++----- src/providers/SettingsContext.ts | 8 + src/providers/SettingsProvider.tsx | 10 +- src/providers/ToastsContext.ts | 30 ++++ src/providers/ToastsProvider.tsx | 31 +--- 20 files changed, 212 insertions(+), 208 deletions(-) create mode 100644 src/providers/AuthContext.ts create mode 100644 src/providers/AxiosContext.ts create mode 100644 src/providers/ConfigContext.ts create mode 100644 src/providers/SettingsContext.ts create mode 100644 src/providers/ToastsContext.ts diff --git a/package-lock.json b/package-lock.json index 7fee70b..8dbc0a1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,16 +10,16 @@ "license": "MIT", "dependencies": { "@codesandbox/sandpack-react": "2.19.9", - "@leanstacks/react-common": "1.0.0", + "@leanstacks/react-common": "1.1.0", "@react-spring/web": "9.7.5", - "@tanstack/react-query": "5.59.15", - "@tanstack/react-query-devtools": "5.59.15", + "@tanstack/react-query": "5.59.16", + "@tanstack/react-query-devtools": "5.59.16", "@tanstack/react-table": "8.20.5", "axios": "1.7.7", "classnames": "2.5.1", "dayjs": "1.11.13", "formik": "2.4.6", - "i18next": "23.16.2", + "i18next": "23.16.4", "i18next-browser-languagedetector": "8.0.0", "lodash": "4.17.21", "qs": "6.13.0", @@ -46,9 +46,9 @@ "autoprefixer": "10.4.20", "eslint": "8.57.1", "eslint-plugin-react-hooks": "5.0.0", - "eslint-plugin-react-refresh": "0.4.13", + "eslint-plugin-react-refresh": "0.4.14", "jsdom": "25.0.1", - "msw": "2.5.0", + "msw": "2.5.2", "postcss": "8.4.47", "prettier": "3.3.3", "prettier-plugin-tailwindcss": "0.6.8", @@ -1176,34 +1176,34 @@ "license": "BSD-3-Clause" }, "node_modules/@inquirer/confirm": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-4.0.1.tgz", - "integrity": "sha512-46yL28o2NJ9doViqOy0VDcoTzng7rAb6yPQKU7VDLqkmbCaH4JqK4yk4XqlzNWy9PVC5pG1ZUXPBQv+VqnYs2w==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.0.1.tgz", + "integrity": "sha512-6ycMm7k7NUApiMGfVc32yIPp28iPKxhGRMqoNDiUjq2RyTAkbs5Fx0TdzBqhabcKvniDdAAvHCmsRjnNfTsogw==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^9.2.1", - "@inquirer/type": "^2.0.0" + "@inquirer/core": "^10.0.1", + "@inquirer/type": "^3.0.0" }, "engines": { "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" } }, "node_modules/@inquirer/core": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-9.2.1.tgz", - "integrity": "sha512-F2VBt7W/mwqEU4bL0RnHNZmC/OxzNx9cOYxHqnXX3MP6ruYvZUZAW9imgN9+h/uBT/oP8Gh888J2OZSbjSeWcg==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.0.1.tgz", + "integrity": "sha512-KKTgjViBQUi3AAssqjUFMnMO3CM3qwCHvePV9EW+zTKGKafFGFF01sc1yOIYjLJ7QU52G/FbzKc+c01WLzXmVQ==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/figures": "^1.0.6", - "@inquirer/type": "^2.0.0", - "@types/mute-stream": "^0.0.4", - "@types/node": "^22.5.5", - "@types/wrap-ansi": "^3.0.0", + "@inquirer/figures": "^1.0.7", + "@inquirer/type": "^3.0.0", "ansi-escapes": "^4.3.2", "cli-width": "^4.1.0", - "mute-stream": "^1.0.0", + "mute-stream": "^2.0.0", "signal-exit": "^4.1.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^6.2.0", @@ -1297,16 +1297,16 @@ } }, "node_modules/@inquirer/type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-2.0.0.tgz", - "integrity": "sha512-XvJRx+2KR3YXyYtPUUy+qd9i7p+GO9Ko6VIIpWlBrpWwXDv8WLFeHTxz35CfQFUiBMLXlGHhGzys7lqit9gWag==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.0.tgz", + "integrity": "sha512-YYykfbw/lefC7yKj7nanzQXILM7r3suIvyFlCcMskc99axmsSewXWkAfXKwMbgxL76iAFVmRwmYdwNZNc8gjog==", "dev": true, "license": "MIT", - "dependencies": { - "mute-stream": "^1.0.0" - }, "engines": { "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" } }, "node_modules/@isaacs/cliui": { @@ -1405,16 +1405,17 @@ } }, "node_modules/@leanstacks/react-common": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@leanstacks/react-common/-/react-common-1.0.0.tgz", - "integrity": "sha512-moK43Deosg03qO92OP+qhfuT5v7ymWeORoJFRG0S5YhvfMWsZlB3G83ruxmCxpokKox8Qk6cimf1AievlxYLdA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@leanstacks/react-common/-/react-common-1.1.0.tgz", + "integrity": "sha512-2ThKzKL3/uM448gXIPTVyzP/hTZoFW2h90miXTYdMW6F63CUcoyWEpsYdD1RcjN5diRuKnOWmNuTlXSpGtxmRg==", + "license": "MIT", "dependencies": { - "classnames": "^2.3.2", - "dayjs": "^1.11.9" + "classnames": "2.5.1", + "dayjs": "1.11.13" }, "peerDependencies": { - "react": "^18.2.0", - "tailwindcss": "^3.3.0" + "react": "18.x", + "tailwindcss": "3.x" } }, "node_modules/@lezer/common": { @@ -1887,9 +1888,10 @@ "integrity": "sha512-Gfkvwk9o9kE9r9XNBmJRfV8zONvXThnm1tcuojL04Uy5uRyqg93DC83lDebl0rocZCfKSjUv+fWYtMQmEDJldg==" }, "node_modules/@tanstack/query-core": { - "version": "5.59.13", - "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.59.13.tgz", - "integrity": "sha512-Oou0bBu/P8+oYjXsJQ11j+gcpLAMpqW42UlokQYEz4dE7+hOtVO9rVuolJKgEccqzvyFzqX4/zZWY+R/v1wVsQ==", + "version": "5.59.16", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.59.16.tgz", + "integrity": "sha512-crHn+G3ltqb5JG0oUv6q+PMz1m1YkjpASrXTU+sYWW9pLk0t2GybUHNRqYPZWhxgjPaVGC4yp92gSFEJgYEsPw==", + "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/tannerlinsley" @@ -1905,11 +1907,12 @@ } }, "node_modules/@tanstack/react-query": { - "version": "5.59.15", - "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.59.15.tgz", - "integrity": "sha512-QbVlAkTI78wB4Mqgf2RDmgC0AOiJqer2c5k9STOOSXGv1S6ZkY37r/6UpE8DbQ2Du0ohsdoXgFNEyv+4eDoPEw==", + "version": "5.59.16", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.59.16.tgz", + "integrity": "sha512-MuyWheG47h6ERd4PKQ6V8gDyBu3ThNG22e1fRVwvq6ap3EqsFhyuxCAwhNP/03m/mLg+DAb0upgbPaX6VB+CkQ==", + "license": "MIT", "dependencies": { - "@tanstack/query-core": "5.59.13" + "@tanstack/query-core": "5.59.16" }, "funding": { "type": "github", @@ -1920,9 +1923,10 @@ } }, "node_modules/@tanstack/react-query-devtools": { - "version": "5.59.15", - "resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-5.59.15.tgz", - "integrity": "sha512-rX28KTivkA2XEn3Fj9ckDtnTPY8giWYgssySSAperpVol4+th+NCij/MhLylfB+Mfg2JfCxOcwnM/fwzS8iSog==", + "version": "5.59.16", + "resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-5.59.16.tgz", + "integrity": "sha512-Dejo39QBXmDqXZ3vdrk7vHDvs7TvL573/AX2NveMBmRAufAPYuE3oWSKP/gGqkDfEqyr4CmldOj+v9cKskUchQ==", + "license": "MIT", "dependencies": { "@tanstack/query-devtools": "5.58.0" }, @@ -1931,7 +1935,7 @@ "url": "https://github.com/sponsors/tannerlinsley" }, "peerDependencies": { - "@tanstack/react-query": "^5.59.15", + "@tanstack/react-query": "^5.59.16", "react": "^18 || ^19" } }, @@ -2210,22 +2214,13 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/mute-stream": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/@types/mute-stream/-/mute-stream-0.0.4.tgz", - "integrity": "sha512-CPM9nzrCPPJHQNA9keH9CVkVI+WR5kMa+7XEs5jcGQ0VoAGnLv242w8lIVgwAEfmE4oufJRaTc9PNLQl0ioAow==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/node": { "version": "22.7.9", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.9.tgz", "integrity": "sha512-jrTfRC7FM6nChvU7X2KqcrgquofrWLFDeYC1hKfwNWomVvrn7JIksqf344WN2X/y8xrgqBd2dJATZV4GbatBfg==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~6.19.2" } @@ -2282,13 +2277,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/wrap-ansi": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/wrap-ansi/-/wrap-ansi-3.0.0.tgz", - "integrity": "sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g==", - "dev": true, - "license": "MIT" - }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.11.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.11.0.tgz", @@ -3696,9 +3684,9 @@ } }, "node_modules/eslint-plugin-react-refresh": { - "version": "0.4.13", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.13.tgz", - "integrity": "sha512-f1EppwrpJRWmqDTyvAyomFVDYRtrS7iTEqv3nokETnMiMzs2SSTmKRTACce4O2p4jYyowiSMvpdwC/RLcMFhuQ==", + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.14.tgz", + "integrity": "sha512-aXvzCTK7ZBv1e7fahFuR3Z/fyQQSIQ711yPgYRj+Oj64tyTgO4iQIDmYXDBqvSWQ/FA4OSCsXOStlF+noU0/NA==", "dev": true, "license": "MIT", "peerDependencies": { @@ -4437,9 +4425,9 @@ } }, "node_modules/i18next": { - "version": "23.16.2", - "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.16.2.tgz", - "integrity": "sha512-dFyxwLXxEQK32f6tITBMaRht25mZPJhQ0WbC0p3bO2mWBal9lABTMqSka5k+GLSRWLzeJBKDpH7BeIA9TZI7Jg==", + "version": "23.16.4", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.16.4.tgz", + "integrity": "sha512-9NIYBVy9cs4wIqzurf7nLXPyf3R78xYbxExVqHLK9od3038rjpyOEzW+XB130kZ1N4PZ9inTtJ471CRJ4Ituyg==", "funding": [ { "type": "individual", @@ -5073,9 +5061,9 @@ "license": "MIT" }, "node_modules/msw": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/msw/-/msw-2.5.0.tgz", - "integrity": "sha512-DwIGQV/XFzkseQD7Ux3rPWMRa7DpozicRQ87QLX9Gzik2xyqcXvtvlBEUs57RziTBZPe/QzaKSl92fJVdxxt2g==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/msw/-/msw-2.5.2.tgz", + "integrity": "sha512-eBsFgU30NYtrfC62XzS1rdAzFK+Br0zKU4ORqD9Qliq86362DWZyPiD6FLfMgy0Ktik83DPTXmqPMz2bqwmJdA==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -5083,7 +5071,7 @@ "@bundled-es-modules/cookie": "^2.0.0", "@bundled-es-modules/statuses": "^1.0.1", "@bundled-es-modules/tough-cookie": "^0.1.6", - "@inquirer/confirm": "^4.0.0", + "@inquirer/confirm": "^5.0.0", "@mswjs/interceptors": "^0.36.5", "@open-draft/until": "^2.1.0", "@types/cookie": "^0.6.0", @@ -5206,13 +5194,13 @@ } }, "node_modules/mute-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz", - "integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz", + "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==", "dev": true, "license": "ISC", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/mz": { @@ -6806,7 +6794,8 @@ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", "devOptional": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/universalify": { "version": "0.2.0", diff --git a/package.json b/package.json index e805882..4e1bb00 100644 --- a/package.json +++ b/package.json @@ -23,16 +23,16 @@ }, "dependencies": { "@codesandbox/sandpack-react": "2.19.9", - "@leanstacks/react-common": "1.0.0", + "@leanstacks/react-common": "1.1.0", "@react-spring/web": "9.7.5", - "@tanstack/react-query": "5.59.15", - "@tanstack/react-query-devtools": "5.59.15", + "@tanstack/react-query": "5.59.16", + "@tanstack/react-query-devtools": "5.59.16", "@tanstack/react-table": "8.20.5", "axios": "1.7.7", "classnames": "2.5.1", "dayjs": "1.11.13", "formik": "2.4.6", - "i18next": "23.16.2", + "i18next": "23.16.4", "i18next-browser-languagedetector": "8.0.0", "lodash": "4.17.21", "qs": "6.13.0", @@ -59,9 +59,9 @@ "autoprefixer": "10.4.20", "eslint": "8.57.1", "eslint-plugin-react-hooks": "5.0.0", - "eslint-plugin-react-refresh": "0.4.13", + "eslint-plugin-react-refresh": "0.4.14", "jsdom": "25.0.1", - "msw": "2.5.0", + "msw": "2.5.2", "postcss": "8.4.47", "prettier": "3.3.3", "prettier-plugin-tailwindcss": "0.6.8", diff --git a/src/__fixtures__/toasts.ts b/src/__fixtures__/toasts.ts index 854b4fe..9f18227 100644 --- a/src/__fixtures__/toasts.ts +++ b/src/__fixtures__/toasts.ts @@ -1,4 +1,4 @@ -import { ToastDetail } from 'providers/ToastsProvider'; +import { ToastDetail } from 'providers/ToastsContext'; export const toastFixture: ToastDetail = { id: 'toast1', diff --git a/src/components/Toast/Toast.tsx b/src/components/Toast/Toast.tsx index 26f5f13..06d3493 100644 --- a/src/components/Toast/Toast.tsx +++ b/src/components/Toast/Toast.tsx @@ -4,7 +4,7 @@ import classNames from 'classnames'; import dayjs from 'dayjs'; import { animated, useSpring } from '@react-spring/web'; -import { ToastDetail } from 'providers/ToastsProvider'; +import { ToastDetail } from 'providers/ToastsContext'; import { useConfig } from 'hooks/useConfig'; import Icon from 'components/Icon/Icon'; diff --git a/src/components/Toast/__tests__/Toast.test.tsx b/src/components/Toast/__tests__/Toast.test.tsx index e65c98e..1a94ac5 100644 --- a/src/components/Toast/__tests__/Toast.test.tsx +++ b/src/components/Toast/__tests__/Toast.test.tsx @@ -4,7 +4,7 @@ import userEvent from '@testing-library/user-event'; import { render, screen, waitFor } from 'test/test-utils'; -import { ToastDetail } from 'providers/ToastsProvider'; +import { ToastDetail } from 'providers/ToastsContext'; import { toastFixture } from '__fixtures__/toasts'; import Toast from '../Toast'; diff --git a/src/hooks/useAuth.ts b/src/hooks/useAuth.ts index 6b4fba0..a24b0e0 100644 --- a/src/hooks/useAuth.ts +++ b/src/hooks/useAuth.ts @@ -1,6 +1,6 @@ import { useContext } from 'react'; -import { AuthContext, AuthContextValue } from 'providers/AuthProvider'; +import { AuthContext, AuthContextValue } from 'providers/AuthContext'; /** * The `useAuth` hook returns the current `AuthContext` value. diff --git a/src/hooks/useAxios.ts b/src/hooks/useAxios.ts index 76d560f..181d23c 100644 --- a/src/hooks/useAxios.ts +++ b/src/hooks/useAxios.ts @@ -1,7 +1,7 @@ import { useContext } from 'react'; import { AxiosInstance } from 'axios'; -import { AxiosContext } from 'providers/AxiosProvider'; +import { AxiosContext } from 'providers/AxiosContext'; /** * The `useAxios` hook returns the current `AxiosContext` value. diff --git a/src/hooks/useConfig.ts b/src/hooks/useConfig.ts index 2b8628f..2a194ac 100644 --- a/src/hooks/useConfig.ts +++ b/src/hooks/useConfig.ts @@ -1,7 +1,6 @@ +import { Config, ConfigContext } from 'providers/ConfigContext'; import { useContext } from 'react'; -import { Config, ConfigContext } from 'providers/ConfigProvider'; - /** * The `useConfig` hook returns the current `ConfigContext` value. * @returns {Config} The current `ConfigContext` value, `Config`. diff --git a/src/hooks/useSettings.ts b/src/hooks/useSettings.ts index 4eccccc..33f0eb3 100644 --- a/src/hooks/useSettings.ts +++ b/src/hooks/useSettings.ts @@ -1,7 +1,7 @@ import { useContext } from 'react'; import { Settings } from 'api/useGetSettings'; -import { SettingsContext } from 'providers/SettingsProvider'; +import { SettingsContext } from 'providers/SettingsContext'; /** * The `useSettings` hook returns the current `SettingsContext` value. diff --git a/src/hooks/useToasts.ts b/src/hooks/useToasts.ts index b9f7b8b..7ae3e22 100644 --- a/src/hooks/useToasts.ts +++ b/src/hooks/useToasts.ts @@ -1,6 +1,6 @@ import { useContext } from 'react'; -import { ToastsContext, ToastsContextValue } from 'providers/ToastsProvider'; +import { ToastsContext, ToastsContextValue } from 'providers/ToastsContext'; /** * The `useToasts` hook returns the current `ToastsContext` value. diff --git a/src/providers/AuthContext.ts b/src/providers/AuthContext.ts new file mode 100644 index 0000000..8263e65 --- /dev/null +++ b/src/providers/AuthContext.ts @@ -0,0 +1,25 @@ +import { createContext } from 'react'; +import { QueryObserverBaseResult } from '@tanstack/react-query'; + +import { UserTokens } from 'api/useGetUserTokens'; + +/** + * The `value` provided by the `AuthContext`. + */ +export interface AuthContextValue { + isAuthenticated: boolean; + userToken?: UserTokens; + refetchUserTokens?: () => Promise>; +} + +/** + * The default/initial `AuthContext` value. + */ +const DEFAULT_CONTEXT_VALUE: AuthContextValue = { + isAuthenticated: false, +}; + +/** + * The `AuthContext` instance. + */ +export const AuthContext = createContext(DEFAULT_CONTEXT_VALUE); diff --git a/src/providers/AuthProvider.tsx b/src/providers/AuthProvider.tsx index 1e132d7..3f85976 100644 --- a/src/providers/AuthProvider.tsx +++ b/src/providers/AuthProvider.tsx @@ -1,29 +1,8 @@ -import React, { PropsWithChildren } from 'react'; +import { PropsWithChildren } from 'react'; -import { UserTokens, useGetUserTokens } from 'api/useGetUserTokens'; +import { AuthContext, AuthContextValue } from './AuthContext'; +import { useGetUserTokens } from 'api/useGetUserTokens'; import LoaderSpinner from 'components/Loader/LoaderSpinner'; -import { QueryObserverBaseResult } from '@tanstack/react-query'; - -/** - * The `value` provided by the `AuthContext`. - */ -export interface AuthContextValue { - isAuthenticated: boolean; - userToken?: UserTokens; - refetchUserTokens?: () => Promise>; -} - -/** - * The default/initial `AuthContext` value. - */ -const DEFAULT_CONTEXT_VALUE: AuthContextValue = { - isAuthenticated: false, -}; - -/** - * The `AuthContext` instance. - */ -export const AuthContext = React.createContext(DEFAULT_CONTEXT_VALUE); /** * The `AuthContextProvider` React component creates, maintains, and provides diff --git a/src/providers/AxiosContext.ts b/src/providers/AxiosContext.ts new file mode 100644 index 0000000..e119c94 --- /dev/null +++ b/src/providers/AxiosContext.ts @@ -0,0 +1,17 @@ +import { createContext } from 'react'; +import axios, { AxiosInstance } from 'axios'; + +/** + * Custom `Axios` instance. + */ +export const customAxios = axios.create({ + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + }, +}); + +/** + * The `AxiosContext` instance. + */ +export const AxiosContext = createContext(customAxios); diff --git a/src/providers/AxiosProvider.tsx b/src/providers/AxiosProvider.tsx index 5562617..6be6abd 100644 --- a/src/providers/AxiosProvider.tsx +++ b/src/providers/AxiosProvider.tsx @@ -1,7 +1,8 @@ -import React, { PropsWithChildren, useEffect, useState } from 'react'; -import axios, { AxiosError, AxiosInstance, InternalAxiosRequestConfig } from 'axios'; +import { PropsWithChildren, useEffect, useState } from 'react'; +import axios, { AxiosError, InternalAxiosRequestConfig } from 'axios'; -import { AuthContextValue } from './AuthProvider'; +import { AxiosContext, customAxios } from './AxiosContext'; +import { AuthContextValue } from './AuthContext'; import { useAuth } from 'hooks/useAuth'; /** @@ -27,8 +28,8 @@ const authRequestInterceptor = async ( /** * An Axios response interceptor called for responses in error. If the http status * code is `401`, attempts to refresh the authentication tokens and retry the request. - * @param error {AxiosError} error - The AxiosError instance. - * @param authContext {AuthContextValue} authContext - The `AuthContextValue` containing + * @param {AxiosError} error - The AxiosError instance. + * @param {AuthContextValue} authContext - The `AuthContextValue` containing * the current user authentication state. * @returns {AxiosResponse} An AxiosResponse representing the retried request. */ @@ -55,28 +56,13 @@ const notAuthenticatedErrorInterceptor = async ( return Promise.reject(error); }; -/** - * Custom `Axios` instance. - */ -const customAxios = axios.create({ - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - }, -}); - -/** - * The `AxiosContext` instance. - */ -export const AxiosContext = React.createContext(customAxios); - /** * The `AxiosContextProvider` React component creates, maintains, and provides * access to the `AxiosContext` value. * @param {PropsWithChildren} props - Component properties, `PropsWithChildren`. * @returns {JSX.Element} JSX */ -const AxiosContextProvider = ({ children }: PropsWithChildren) => { +const AxiosContextProvider = ({ children }: PropsWithChildren): JSX.Element => { const [isReady, setIsReady] = useState(false); const authContext = useAuth(); diff --git a/src/providers/ConfigContext.ts b/src/providers/ConfigContext.ts new file mode 100644 index 0000000..8269957 --- /dev/null +++ b/src/providers/ConfigContext.ts @@ -0,0 +1,22 @@ +import { createContext } from 'react'; + +/** + * The application configuration. The `value` provided by the `ConfigContext`. + */ +export interface Config { + VITE_BASE_URL_API: string; + VITE_BUILD_DATE: string; + VITE_BUILD_TIME: string; + VITE_BUILD_TS: string; + VITE_BUILD_COMMIT_SHA: string; + VITE_BUILD_ENV_CODE: string; + VITE_BUILD_WORKFLOW_NAME: string; + VITE_BUILD_WORKFLOW_RUN_NUMBER: number; + VITE_BUILD_WORKFLOW_RUN_ATTEMPT: number; + VITE_TOAST_AUTO_DISMISS_MILLIS: number; +} + +/** + * The `ConfigContext` instance. + */ +export const ConfigContext = createContext(undefined); diff --git a/src/providers/ConfigProvider.tsx b/src/providers/ConfigProvider.tsx index de6ac7d..68dcd99 100644 --- a/src/providers/ConfigProvider.tsx +++ b/src/providers/ConfigProvider.tsx @@ -1,45 +1,25 @@ -import React, { PropsWithChildren, useEffect, useState } from 'react'; -import * as Yup from 'yup'; -import { ObjectSchema } from 'yup'; +import { PropsWithChildren, useEffect, useState } from 'react'; +import { number, object, ObjectSchema, string, ValidationError } from 'yup'; -/** - * The application configuration. The `value` provided by the `ConfigContext`. - */ -export interface Config { - VITE_BASE_URL_API: string; - VITE_BUILD_DATE: string; - VITE_BUILD_TIME: string; - VITE_BUILD_TS: string; - VITE_BUILD_COMMIT_SHA: string; - VITE_BUILD_ENV_CODE: string; - VITE_BUILD_WORKFLOW_NAME: string; - VITE_BUILD_WORKFLOW_RUN_NUMBER: number; - VITE_BUILD_WORKFLOW_RUN_ATTEMPT: number; - VITE_TOAST_AUTO_DISMISS_MILLIS: number; -} +import { Config, ConfigContext } from './ConfigContext'; /** * The configuration validation schema. * @see {@link https://github.com/jquense/yup | Yup} */ -const configSchema: ObjectSchema = Yup.object({ - VITE_BASE_URL_API: Yup.string().url().required(), - VITE_BUILD_DATE: Yup.string().default('1970-01-01'), - VITE_BUILD_TIME: Yup.string().default('00:00:00'), - VITE_BUILD_TS: Yup.string().default('1970-01-01T00:00:00+0000'), - VITE_BUILD_COMMIT_SHA: Yup.string().default('local'), - VITE_BUILD_ENV_CODE: Yup.string().default('local'), - VITE_BUILD_WORKFLOW_NAME: Yup.string().default('local'), - VITE_BUILD_WORKFLOW_RUN_NUMBER: Yup.number().default(1), - VITE_BUILD_WORKFLOW_RUN_ATTEMPT: Yup.number().default(1), - VITE_TOAST_AUTO_DISMISS_MILLIS: Yup.number().default(5000), +const configSchema: ObjectSchema = object({ + VITE_BASE_URL_API: string().url().required(), + VITE_BUILD_DATE: string().default('1970-01-01'), + VITE_BUILD_TIME: string().default('00:00:00'), + VITE_BUILD_TS: string().default('1970-01-01T00:00:00+0000'), + VITE_BUILD_COMMIT_SHA: string().default('local'), + VITE_BUILD_ENV_CODE: string().default('local'), + VITE_BUILD_WORKFLOW_NAME: string().default('local'), + VITE_BUILD_WORKFLOW_RUN_NUMBER: number().default(1), + VITE_BUILD_WORKFLOW_RUN_ATTEMPT: number().default(1), + VITE_TOAST_AUTO_DISMISS_MILLIS: number().default(5000), }); -/** - * The `ConfigContext` instance. - */ -export const ConfigContext = React.createContext(undefined); - /** * The `ConfigContextProvider` React component creates, maintains, and provides * access to the `ConfigContext` value. @@ -49,7 +29,7 @@ export const ConfigContext = React.createContext(undefined); * @param {PropsWithChildren} props - Component properties, `PropsWithChildren`. * @returns {JSX.Element} JSX */ -const ConfigContextProvider = ({ children }: PropsWithChildren) => { +const ConfigContextProvider = ({ children }: PropsWithChildren): JSX.Element => { const [isReady, setIsReady] = useState(false); const [config, setConfig] = useState(); @@ -62,7 +42,7 @@ const ConfigContextProvider = ({ children }: PropsWithChildren) => { setConfig(validatedConfig); setIsReady(true); } catch (err) { - if (err instanceof Yup.ValidationError) throw new Error(`${err}::${err.errors}`); + if (err instanceof ValidationError) throw new Error(`${err}::${err.errors}`); if (err instanceof Error) throw new Error(`Configuration error: ${err.message}`); throw err; } diff --git a/src/providers/SettingsContext.ts b/src/providers/SettingsContext.ts new file mode 100644 index 0000000..746d34a --- /dev/null +++ b/src/providers/SettingsContext.ts @@ -0,0 +1,8 @@ +import { createContext } from 'react'; + +import { Settings } from 'api/useGetSettings'; + +/** + * The `SettingsContext` instance. + */ +export const SettingsContext = createContext(undefined); diff --git a/src/providers/SettingsProvider.tsx b/src/providers/SettingsProvider.tsx index 8799bf2..3408907 100644 --- a/src/providers/SettingsProvider.tsx +++ b/src/providers/SettingsProvider.tsx @@ -1,11 +1,7 @@ -import React, { PropsWithChildren, useMemo } from 'react'; +import { PropsWithChildren, useMemo } from 'react'; -import { Settings, useGetSettings } from 'api/useGetSettings'; - -/** - * The `SettingsContext` instance. - */ -export const SettingsContext = React.createContext(undefined); +import { useGetSettings } from 'api/useGetSettings'; +import { SettingsContext } from './SettingsContext'; /** * The `SettingsContextProvider` React component creates, maintains, and provides diff --git a/src/providers/ToastsContext.ts b/src/providers/ToastsContext.ts new file mode 100644 index 0000000..97ff185 --- /dev/null +++ b/src/providers/ToastsContext.ts @@ -0,0 +1,30 @@ +import { createContext } from 'react'; + +/** + * Describes the attributes of a single Toast. + */ +export interface ToastDetail { + id: string; + text: string; + createdAt: string; + isAutoDismiss: boolean; +} + +/** + * A DTO type which describes the attributes to create a new Toast. + */ +export type CreateToastDTO = Pick; + +/** + * The `value` provided by the `ToastsContext`. + */ +export interface ToastsContextValue { + toasts: ToastDetail[]; + createToast: (toast: CreateToastDTO) => void; + removeToast: (id: string) => void; +} + +/** + * The `ToastsContext` instance. + */ +export const ToastsContext = createContext(undefined); diff --git a/src/providers/ToastsProvider.tsx b/src/providers/ToastsProvider.tsx index c6be52a..1e07240 100644 --- a/src/providers/ToastsProvider.tsx +++ b/src/providers/ToastsProvider.tsx @@ -1,30 +1,8 @@ -import React, { Dispatch, PropsWithChildren, useMemo, useReducer } from 'react'; +import { Dispatch, PropsWithChildren, useMemo, useReducer } from 'react'; import { v4 as uuid } from 'uuid'; import dayjs from 'dayjs'; -/** - * Describes the attributes of a single Toast. - */ -export interface ToastDetail { - id: string; - text: string; - createdAt: string; - isAutoDismiss: boolean; -} - -/** - * A DTO type which describes the attributes to create a new Toast. - */ -export type CreateToastDTO = Pick; - -/** - * The `value` provided by the `ToastsContext`. - */ -export interface ToastsContextValue { - toasts: ToastDetail[]; - createToast: (toast: CreateToastDTO) => void; - removeToast: (id: string) => void; -} +import { CreateToastDTO, ToastDetail, ToastsContext, ToastsContextValue } from './ToastsContext'; /** * The `ToastsContext` reducer `state`. @@ -116,11 +94,6 @@ const actions = (dispatch: Dispatch) => { }; }; -/** - * The `ToastsContext` instance. - */ -export const ToastsContext = React.createContext(undefined); - /** * The `ToastsProvider` React component creates, maintains, and provides * access to the `ToastsContext` value. From d2671ffdd0ac99ccfe8a14e82804356717e12d38 Mon Sep 17 00:00:00 2001 From: Matthew Warman Date: Sun, 27 Oct 2024 08:52:16 -0400 Subject: [PATCH 2/7] initial FAIcon component --- package-lock.json | 60 ++++++++++++++++++ package.json | 3 + src/components/Icon/FAIcon.tsx | 61 +++++++++++++++++++ src/components/Icon/__tests__/FAIcon.test.tsx | 16 +++++ .../UsersPage/components/UserDetailLayout.tsx | 10 +-- 5 files changed, 145 insertions(+), 5 deletions(-) create mode 100644 src/components/Icon/FAIcon.tsx create mode 100644 src/components/Icon/__tests__/FAIcon.test.tsx diff --git a/package-lock.json b/package-lock.json index 8dbc0a1..70b1473 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,9 @@ "license": "MIT", "dependencies": { "@codesandbox/sandpack-react": "2.19.9", + "@fortawesome/fontawesome-svg-core": "6.6.0", + "@fortawesome/free-solid-svg-icons": "6.6.0", + "@fortawesome/react-fontawesome": "0.2.2", "@leanstacks/react-common": "1.1.0", "@react-spring/web": "9.7.5", "@tanstack/react-query": "5.59.16", @@ -1114,6 +1117,52 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@fortawesome/fontawesome-common-types": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.6.0.tgz", + "integrity": "sha512-xyX0X9mc0kyz9plIyryrRbl7ngsA9jz77mCZJsUkLl+ZKs0KWObgaEBoSgQiYWAsSmjz/yjl0F++Got0Mdp4Rw==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/fontawesome-svg-core": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.6.0.tgz", + "integrity": "sha512-KHwPkCk6oRT4HADE7smhfsKudt9N/9lm6EJ5BVg0tD1yPA5hht837fB87F8pn15D8JfTqQOjhKTktwmLMiD7Kg==", + "license": "MIT", + "dependencies": { + "@fortawesome/fontawesome-common-types": "6.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/free-solid-svg-icons": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.6.0.tgz", + "integrity": "sha512-IYv/2skhEDFc2WGUcqvFJkeK39Q+HyPf5GHUrT/l2pKbtgEIv1al1TKd6qStR5OIwQdN1GZP54ci3y4mroJWjA==", + "license": "(CC-BY-4.0 AND MIT)", + "dependencies": { + "@fortawesome/fontawesome-common-types": "6.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/react-fontawesome": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.2.tgz", + "integrity": "sha512-EnkrprPNqI6SXJl//m29hpaNzOp1bruISWaOiRtkMi/xSvHJlzc2j2JAYS7egxt/EbjSNV/k6Xy0AQI6vB2+1g==", + "license": "MIT", + "dependencies": { + "prop-types": "^15.8.1" + }, + "peerDependencies": { + "@fortawesome/fontawesome-svg-core": "~1 || ~6", + "react": ">=16.3" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.13.0", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", @@ -5784,6 +5833,17 @@ "dev": true, "peer": true }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, "node_modules/property-expr": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.6.tgz", diff --git a/package.json b/package.json index 4e1bb00..d75c730 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,9 @@ }, "dependencies": { "@codesandbox/sandpack-react": "2.19.9", + "@fortawesome/fontawesome-svg-core": "6.6.0", + "@fortawesome/free-solid-svg-icons": "6.6.0", + "@fortawesome/react-fontawesome": "0.2.2", "@leanstacks/react-common": "1.1.0", "@react-spring/web": "9.7.5", "@tanstack/react-query": "5.59.16", diff --git a/src/components/Icon/FAIcon.tsx b/src/components/Icon/FAIcon.tsx new file mode 100644 index 0000000..7784205 --- /dev/null +++ b/src/components/Icon/FAIcon.tsx @@ -0,0 +1,61 @@ +import { ComponentPropsWithoutRef } from 'react'; +import { BaseComponentProps } from '@leanstacks/react-common'; +import classNames from 'classnames'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { IconProp } from '@fortawesome/fontawesome-svg-core'; +import { faEnvelope, faLink, faPhone, faX } from '@fortawesome/free-solid-svg-icons'; + +/** + * A union type of all Font Awesome icon names (without the `fa-` prefix) + * used in the application. + */ +export type FAIconName = 'envelope' | 'link' | 'phone' | 'x'; + +/** + * Properties for the `FAIcon` component. + * @param {FAIconName} icon - The Font Awesome icon name without the `fa-` prefix. + * @see {@link BaseComponentProps} + * @see {@link FontAwesomeIcon} + */ +export interface FAIconProps + extends BaseComponentProps, + Omit, 'icon'> { + icon: FAIconName; +} + +/** + * A key/value mapping of every icon used in the application. + */ +const icons: Record = { + envelope: faEnvelope, + link: faLink, + phone: faPhone, + x: faX, +}; + +/** + * The `FAIcon` component renders a Font Awesome icon. + * + * Note: Wraps the `FontAwesomeIcon` component. + * @param param0 + * @returns + */ +const FAIcon = ({ + className, + icon, + testId = 'fa-icon', + ...iconProps +}: FAIconProps): JSX.Element => { + const faIcon = icons[icon]; + + return ( + + ); +}; + +export default FAIcon; diff --git a/src/components/Icon/__tests__/FAIcon.test.tsx b/src/components/Icon/__tests__/FAIcon.test.tsx new file mode 100644 index 0000000..b693369 --- /dev/null +++ b/src/components/Icon/__tests__/FAIcon.test.tsx @@ -0,0 +1,16 @@ +import { describe, expect, it } from 'vitest'; + +import { render, screen } from 'test/test-utils'; + +import FAIcon from '../FAIcon'; + +describe('FAIcon', () => { + it('should render successfully', async () => { + // ARRANGE + render(); + await screen.findByTestId('fa-icon'); + + // ASSERT + expect(screen.getByTestId('fa-icon')).toBeDefined(); + }); +}); diff --git a/src/pages/UsersPage/components/UserDetailLayout.tsx b/src/pages/UsersPage/components/UserDetailLayout.tsx index 4bce9f7..316bce7 100644 --- a/src/pages/UsersPage/components/UserDetailLayout.tsx +++ b/src/pages/UsersPage/components/UserDetailLayout.tsx @@ -4,8 +4,8 @@ import { Outlet, useParams } from 'react-router-dom'; import { useGetUser } from 'api/useGetUser'; import Text from 'components/Text/Text'; import LoaderSkeleton from 'components/Loader/LoaderSkeleton'; -import Icon from 'components/Icon/Icon'; import Avatar from 'components/Icon/Avatar'; +import FAIcon from 'components/Icon/FAIcon'; /** * Properties for the `UserDetailLayout` React component. @@ -52,17 +52,17 @@ const UserDetailLayout = ({ -
+
- + {user.email}
- + {user.phone}
- + {user.website}
From ca894b253a68a7f05ef87f5396b9c68a255e7faa Mon Sep 17 00:00:00 2001 From: Matthew Warman Date: Tue, 29 Oct 2024 06:57:30 -0400 Subject: [PATCH 3/7] use font awesome icons --- README.md | 3 + package-lock.json | 177 +++++++++++++++++- package.json | 2 + src/components/Button/LanguageToggle.tsx | 4 +- src/components/Button/ThemeToggle.tsx | 6 +- .../Button/__tests__/ThemeToggle.test.tsx | 4 +- src/components/Card/MessageCard.tsx | 8 +- .../Card/__tests__/MessageCard.test.tsx | 5 +- src/components/Dropdown/Dropdown.tsx | 2 +- src/components/Form/SearchField.tsx | 11 +- src/components/Form/SelectField.tsx | 8 +- src/components/Header/AppMenu.tsx | 18 +- .../Header/__tests__/AppMenu.test.tsx | 4 +- src/components/Icon/FAIcon.tsx | 90 ++++++++- src/components/Icon/__tests__/FAIcon.test.tsx | 3 +- src/components/Loader/LoaderSpinner.tsx | 18 +- .../Loader/__tests__/LoaderSpinner.test.tsx | 12 +- src/components/Menu/MenuButton.tsx | 4 +- src/components/Menu/MenuCloseButton.tsx | 4 +- src/components/Menu/MenuNavLink.tsx | 22 ++- .../Menu/__tests__/MenuNavLink.test.tsx | 6 +- src/components/Router/Router.tsx | 6 +- .../Router/__tests__/PrivateOutlet.test.tsx | 14 +- src/components/Toast/Toast.tsx | 4 +- src/pages/ComponentsPage/ComponentsPage.tsx | 16 +- .../components/CardComponents.tsx | 8 +- src/pages/SettingsPage/SettingsPage.tsx | 2 +- .../SigninPage/components/SigninForm.tsx | 4 +- .../Tasks/components/TaskCompleteToggle.tsx | 9 +- .../UsersPage/Tasks/components/TaskDetail.tsx | 20 +- .../__tests__/TaskCompleteToggle.test.tsx | 14 +- src/pages/UsersPage/components/UserDetail.tsx | 10 +- .../UsersPage/components/UserDetailEmpty.tsx | 2 +- .../UsersPage/components/UserTaskList.tsx | 6 +- src/pages/UsersPage/components/UserTasks.tsx | 13 +- .../__tests__/UserTaskListItem.test.tsx | 5 +- tsconfig.json | 2 +- vitest.setup.ts | 1 + 38 files changed, 407 insertions(+), 140 deletions(-) diff --git a/README.md b/README.md index d1842a5..83f22ff 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,7 @@ The technology stack includes: - TanStack React Table - tables and datagrids - Tailwind - styling - Material Symbols - icons +- Font Awesome - icons - Testing Library React - tests - Vitest - tests - MSW - API mocking @@ -244,6 +245,7 @@ This project uses GitHub Actions to perform DevOps automation activities such as - [Yup][yup] - [Tailwind CSS][tailwind] - [Material Symbols][icons] +- [Font Awesome][fontawesome] - [Testing Library][testing-library] - [GitHub Actions][ghactions] @@ -255,6 +257,7 @@ This project uses GitHub Actions to perform DevOps automation activities such as [yup]: https://github.com/jquense/yup 'Yup' [tailwind]: https://tailwindcss.com/ 'Tailwind CSS' [icons]: https://fonts.google.com/icons 'Material Symbols' +[fontawesome]: https://fontawesome.com/ 'Font Awesome' [tanstack]: https://tanstack.com/ 'TanStack' [testing-library]: https://testing-library.com/ 'Testing Library' [ghactions]: https://docs.github.com/en/actions 'GitHub Actions' diff --git a/package-lock.json b/package-lock.json index 70b1473..af16a5b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "dependencies": { "@codesandbox/sandpack-react": "2.19.9", "@fortawesome/fontawesome-svg-core": "6.6.0", + "@fortawesome/free-regular-svg-icons": "6.6.0", "@fortawesome/free-solid-svg-icons": "6.6.0", "@fortawesome/react-fontawesome": "0.2.2", "@leanstacks/react-common": "1.1.0", @@ -35,6 +36,7 @@ "yup": "1.4.0" }, "devDependencies": { + "@testing-library/jest-dom": "6.6.2", "@testing-library/react": "16.0.1", "@testing-library/user-event": "14.5.2", "@types/lodash": "4.17.12", @@ -60,6 +62,13 @@ "vitest": "2.1.3" } }, + "node_modules/@adobe/css-tools": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.0.tgz", + "integrity": "sha512-Ff9+ksdQQB3rMncgqDK78uLznstjyfIf2Arnh22pW8kBpLs6rpKDwgnZT46hin5Hl1WzazzK64DOrhSwYpS7bQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@alloc/quick-lru": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", @@ -1138,6 +1147,18 @@ "node": ">=6" } }, + "node_modules/@fortawesome/free-regular-svg-icons": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.6.0.tgz", + "integrity": "sha512-Yv9hDzL4aI73BEwSEh20clrY8q/uLxawaQ98lekBx6t9dQKDHcDzzV1p2YtBGTtolYtNqcWdniOnhzB+JPnQEQ==", + "license": "(CC-BY-4.0 AND MIT)", + "dependencies": { + "@fortawesome/fontawesome-common-types": "6.6.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/@fortawesome/free-solid-svg-icons": { "version": "6.6.0", "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.6.0.tgz", @@ -2117,6 +2138,107 @@ "node": ">=8" } }, + "node_modules/@testing-library/jest-dom": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.6.2.tgz", + "integrity": "sha512-P6GJD4yqc9jZLbe98j/EkyQDTPgqftohZF5FBkHY5BUERZmcf4HeO2k0XaefEg329ux2p21i1A1DmyQ1kKw2Jw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@adobe/css-tools": "^4.4.0", + "aria-query": "^5.0.0", + "chalk": "^3.0.0", + "css.escape": "^1.5.1", + "dom-accessibility-api": "^0.6.3", + "lodash": "^4.17.21", + "redent": "^3.0.0" + }, + "engines": { + "node": ">=14", + "npm": ">=6", + "yarn": ">=1" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", + "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@testing-library/jest-dom/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/@testing-library/react": { "version": "16.0.1", "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.0.1.tgz", @@ -2813,7 +2935,6 @@ "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", "dev": true, - "peer": true, "dependencies": { "dequal": "^2.0.3" } @@ -3322,6 +3443,13 @@ "node": ">= 8" } }, + "node_modules/css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "dev": true, + "license": "MIT" + }, "node_modules/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -4571,6 +4699,16 @@ "node": ">=0.8.19" } }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -5079,6 +5217,16 @@ "node": ">= 0.6" } }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/minimatch": { "version": "9.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", @@ -6032,6 +6180,20 @@ "node": ">=8.10.0" } }, + "node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/regenerator-runtime": { "version": "0.14.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", @@ -6410,6 +6572,19 @@ "node": ">=8" } }, + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", diff --git a/package.json b/package.json index d75c730..91faeb1 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "dependencies": { "@codesandbox/sandpack-react": "2.19.9", "@fortawesome/fontawesome-svg-core": "6.6.0", + "@fortawesome/free-regular-svg-icons": "6.6.0", "@fortawesome/free-solid-svg-icons": "6.6.0", "@fortawesome/react-fontawesome": "0.2.2", "@leanstacks/react-common": "1.1.0", @@ -48,6 +49,7 @@ "yup": "1.4.0" }, "devDependencies": { + "@testing-library/jest-dom": "6.6.2", "@testing-library/react": "16.0.1", "@testing-library/user-event": "14.5.2", "@types/lodash": "4.17.12", diff --git a/src/components/Button/LanguageToggle.tsx b/src/components/Button/LanguageToggle.tsx index 73b59a4..97b0daf 100644 --- a/src/components/Button/LanguageToggle.tsx +++ b/src/components/Button/LanguageToggle.tsx @@ -4,7 +4,7 @@ import { useTranslation } from 'react-i18next'; import { StorageKeys } from 'utils/constants'; import storage from 'utils/storage'; import Dropdown from 'components/Dropdown/Dropdown'; -import Icon from 'components/Icon/Icon'; +import FAIcon from 'components/Icon/FAIcon'; import DropdownContent from 'components/Dropdown/DropdownContent'; import DropdownItem from 'components/Dropdown/DropdownItem'; import Button from './Button'; @@ -37,7 +37,7 @@ const LanguageToggle = ({ className }: LanguageToggleProps): JSX.Element => { - + } content={ diff --git a/src/components/Button/ThemeToggle.tsx b/src/components/Button/ThemeToggle.tsx index 7b8d33d..9ece960 100644 --- a/src/components/Button/ThemeToggle.tsx +++ b/src/components/Button/ThemeToggle.tsx @@ -4,7 +4,7 @@ import classNames from 'classnames'; import { useSetSettings } from 'api/useSetSettings'; import { useSettings } from 'hooks/useSettings'; import Button from './Button'; -import Icon from 'components/Icon/Icon'; +import FAIcon from 'components/Icon/FAIcon'; /** * Properties for the `ThemeToggle` component. @@ -32,7 +32,7 @@ const ThemeToggle = ({ className }: ThemeToggleProps): JSX.Element => { onClick={() => setSettings({ theme: 'dark' })} testId="button-theme-dark" > - + ) : ( )} diff --git a/src/components/Button/__tests__/ThemeToggle.test.tsx b/src/components/Button/__tests__/ThemeToggle.test.tsx index e6e5d89..5b99fb2 100644 --- a/src/components/Button/__tests__/ThemeToggle.test.tsx +++ b/src/components/Button/__tests__/ThemeToggle.test.tsx @@ -42,7 +42,7 @@ describe('ThemeToggle', () => { // ASSERT expect(screen.getByTestId('button-theme-dark')).toBeDefined(); - expect(screen.getByTestId('icon-dark-mode').textContent).toBe('lightbulb'); + expect(screen.getByTestId('icon-dark-mode')).toHaveAttribute('data-icon', 'moon'); }); it('should render light mode icon', async () => { @@ -56,7 +56,7 @@ describe('ThemeToggle', () => { // ASSERT expect(screen.getByTestId('button-theme-light')).toBeDefined(); - expect(screen.getByTestId('icon-light-mode').textContent).toBe('lightbulb'); + expect(screen.getByTestId('icon-light-mode')).toHaveAttribute('data-icon', 'sun'); }); it('should set dark theme when clicked', async () => { diff --git a/src/components/Card/MessageCard.tsx b/src/components/Card/MessageCard.tsx index 8069dab..4aca8e5 100644 --- a/src/components/Card/MessageCard.tsx +++ b/src/components/Card/MessageCard.tsx @@ -1,17 +1,17 @@ import classNames from 'classnames'; import Card, { CardProps } from './Card'; -import Icon, { IconProps } from 'components/Icon/Icon'; +import FAIcon, { FAIconProps } from 'components/Icon/FAIcon'; /** * Properties for the `MessageCard` React component. - * @param {IconProps} [iconProps] - Optional. Icon properties. + * @param {FAIconProps} [iconProps] - Optional. Icon properties. * @param {string} [title] - Optional. A card title. * @param {string} message - A card message. * @see {@link CardProps} */ interface MessageCardProps extends CardProps { - iconProps?: IconProps; + iconProps?: FAIconProps; title?: string; message: string; } @@ -32,7 +32,7 @@ const MessageCard = ({ return (
- {iconProps && } + {iconProps && } {title && (
{title} diff --git a/src/components/Card/__tests__/MessageCard.test.tsx b/src/components/Card/__tests__/MessageCard.test.tsx index 55280dc..0a66df7 100644 --- a/src/components/Card/__tests__/MessageCard.test.tsx +++ b/src/components/Card/__tests__/MessageCard.test.tsx @@ -44,11 +44,12 @@ describe('MessageCard', () => { it('should display icon', async () => { // ARRANGE - render(); + render(); await screen.findByTestId('card-message'); // ASSERT expect(screen.getByTestId('card-message')).toBeDefined(); - expect(screen.getByTestId('card-message-icon').textContent).toBe('info'); + expect(screen.getByTestId('card-message-icon')).toHaveAttribute('data-icon', 'circle-info'); + // expect(screen.getByTestId('card-message-icon').getAttribute('data-icon')).toBe('circle-info'); }); }); diff --git a/src/components/Dropdown/Dropdown.tsx b/src/components/Dropdown/Dropdown.tsx index a12a3ad..7a42740 100644 --- a/src/components/Dropdown/Dropdown.tsx +++ b/src/components/Dropdown/Dropdown.tsx @@ -25,7 +25,7 @@ interface DropdownProps extends PropsWithClassName, PropsWithTestId { * *Example:* * ```jsx } + toggle={} content={ setLanguage('en')} testId="dropdown-item-en"> diff --git a/src/components/Form/SearchField.tsx b/src/components/Form/SearchField.tsx index ebd23ba..9e0cb05 100644 --- a/src/components/Form/SearchField.tsx +++ b/src/components/Form/SearchField.tsx @@ -2,7 +2,7 @@ import { ChangeEvent, ReactElement, useEffect, useRef } from 'react'; import { PropsWithClassName, PropsWithTestId } from '@leanstacks/react-common'; import classNames from 'classnames'; -import Icon from 'components/Icon/Icon'; +import FAIcon from 'components/Icon/FAIcon'; import { SearchResultProps } from './SearchResult'; /** @@ -80,7 +80,7 @@ const SearchField = ({ 'flex h-16 items-center border-b border-neutral-500/50 bg-neutral-500/10 px-4 py-2 has-[:focus]:border-blue-600', )} > - + -
diff --git a/src/components/Form/SelectField.tsx b/src/components/Form/SelectField.tsx index 3714651..b1de7ba 100644 --- a/src/components/Form/SelectField.tsx +++ b/src/components/Form/SelectField.tsx @@ -3,7 +3,7 @@ import { PropsWithTestId } from '@leanstacks/react-common'; import { useField } from 'formik'; import classNames from 'classnames'; -import Icon from 'components/Icon/Icon'; +import FAIcon from 'components/Icon/FAIcon'; /** * Describes a single option for a `SelectField`. If `label` is omitted, @@ -136,8 +136,8 @@ const SelectField = ({ {selectedValue}
- @@ -174,7 +174,7 @@ const SelectField = ({ key={value} >
{label ?? value}
- {field.value === value && } + {field.value === value && } ))} diff --git a/src/components/Header/AppMenu.tsx b/src/components/Header/AppMenu.tsx index 3d435af..5f3ec03 100644 --- a/src/components/Header/AppMenu.tsx +++ b/src/components/Header/AppMenu.tsx @@ -40,32 +40,30 @@ const AppMenu = ({ side = 'right', testId = 'menu-app', ...props }: AppMenuProps {isAuthenticated ? ( <> - + Sign Out - + Settings - - + Components - - + Users ) : ( <> - + Sign In - - Sign Up + + Need an account? Sign Up - + Components diff --git a/src/components/Header/__tests__/AppMenu.test.tsx b/src/components/Header/__tests__/AppMenu.test.tsx index f7cf08d..5ee63ba 100644 --- a/src/components/Header/__tests__/AppMenu.test.tsx +++ b/src/components/Header/__tests__/AppMenu.test.tsx @@ -76,7 +76,7 @@ describe('AppMenu', () => { // ASSERT expect(screen.getByTestId('menu-app')).toBeDefined(); expect(screen.getByAltText('Logo')).toBeDefined(); - expect(screen.getByText('Sign In')).toBeDefined(); - expect(screen.getByText('Sign Up')).toBeDefined(); + expect(screen.getByText(/^Sign In$/i)).toBeDefined(); + expect(screen.getByText(/Sign Up/i)).toBeDefined(); }); }); diff --git a/src/components/Icon/FAIcon.tsx b/src/components/Icon/FAIcon.tsx index 7784205..c017cf0 100644 --- a/src/components/Icon/FAIcon.tsx +++ b/src/components/Icon/FAIcon.tsx @@ -3,13 +3,72 @@ import { BaseComponentProps } from '@leanstacks/react-common'; import classNames from 'classnames'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { IconProp } from '@fortawesome/fontawesome-svg-core'; -import { faEnvelope, faLink, faPhone, faX } from '@fortawesome/free-solid-svg-icons'; +import { faCircle as faCircleRegular } from '@fortawesome/free-regular-svg-icons'; +import { + faBars, + faBuilding, + faCheck, + faChevronDown, + faChevronUp, + faCircleCheck, + faCircleExclamation, + faCircleInfo, + faCircleNotch, + faCircleXmark, + faEnvelope, + faLanguage, + faLink, + faListCheck, + faMagnifyingGlass, + faMapLocationDot, + faMoon, + faPaintBrush, + faPencil, + faPhone, + faPuzzlePiece, + faRightFromBracket, + faRightToBracket, + faSliders, + faSun, + faTrash, + faUsers, + faXmark, +} from '@fortawesome/free-solid-svg-icons'; /** * A union type of all Font Awesome icon names (without the `fa-` prefix) * used in the application. */ -export type FAIconName = 'envelope' | 'link' | 'phone' | 'x'; +export type FAIconName = + | 'bars' + | 'building' + | 'check' + | 'chevronDown' + | 'chevronUp' + | 'circleCheck' + | 'circleExclamation' + | 'circleInfo' + | 'circleNotch' + | 'circleRegular' + | 'circleXmark' + | 'envelope' + | 'language' + | 'link' + | 'listCheck' + | 'magnifyingGlass' + | 'mapLocationDot' + | 'moon' + | 'paintbrush' + | 'pencil' + | 'phone' + | 'puzzlePiece' + | 'rightFromBracket' + | 'rightToBracket' + | 'sliders' + | 'sun' + | 'trash' + | 'users' + | 'xmark'; /** * Properties for the `FAIcon` component. @@ -27,10 +86,35 @@ export interface FAIconProps * A key/value mapping of every icon used in the application. */ const icons: Record = { + bars: faBars, + building: faBuilding, + check: faCheck, + chevronDown: faChevronDown, + chevronUp: faChevronUp, + circleCheck: faCircleCheck, + circleExclamation: faCircleExclamation, + circleInfo: faCircleInfo, + circleNotch: faCircleNotch, + circleRegular: faCircleRegular, + circleXmark: faCircleXmark, envelope: faEnvelope, + language: faLanguage, link: faLink, + listCheck: faListCheck, + magnifyingGlass: faMagnifyingGlass, + mapLocationDot: faMapLocationDot, + moon: faMoon, + paintbrush: faPaintBrush, + pencil: faPencil, phone: faPhone, - x: faX, + puzzlePiece: faPuzzlePiece, + rightFromBracket: faRightFromBracket, + rightToBracket: faRightToBracket, + sliders: faSliders, + sun: faSun, + trash: faTrash, + users: faUsers, + xmark: faXmark, }; /** diff --git a/src/components/Icon/__tests__/FAIcon.test.tsx b/src/components/Icon/__tests__/FAIcon.test.tsx index b693369..fddd68a 100644 --- a/src/components/Icon/__tests__/FAIcon.test.tsx +++ b/src/components/Icon/__tests__/FAIcon.test.tsx @@ -7,10 +7,11 @@ import FAIcon from '../FAIcon'; describe('FAIcon', () => { it('should render successfully', async () => { // ARRANGE - render(); + render(); await screen.findByTestId('fa-icon'); // ASSERT expect(screen.getByTestId('fa-icon')).toBeDefined(); + expect(screen.getByTestId('fa-icon')).toHaveAttribute('data-icon', 'xmark'); }); }); diff --git a/src/components/Loader/LoaderSpinner.tsx b/src/components/Loader/LoaderSpinner.tsx index 519366f..fc633d0 100644 --- a/src/components/Loader/LoaderSpinner.tsx +++ b/src/components/Loader/LoaderSpinner.tsx @@ -1,7 +1,7 @@ import classNames from 'classnames'; -import { PropsWithClassName, PropsWithTestId } from '@leanstacks/react-common'; +import { BaseComponentProps } from '@leanstacks/react-common'; -import Icon from 'components/Icon/Icon'; +import FAIcon, { FAIconProps } from 'components/Icon/FAIcon'; /** * Properties for the `LoaderSpinner` component. @@ -9,12 +9,10 @@ import Icon from 'components/Icon/Icon'; * @param {string} [iconName] - Optional. The icon name. Default: "Progress Activity". * @param {string} [text] - Optional. The loader text. * @param {string} [textClassName] - Optional. CSS class names for the text. - * @see {@link PropsWithClassName} - * @see {@link PropsWithTestId} + * @see {@link BaseComponentProps} */ -interface LoaderSpinnerProps extends PropsWithClassName, PropsWithTestId { +interface LoaderSpinnerProps extends BaseComponentProps, Partial> { iconClassName?: string; - iconName?: string; text?: string; textClassName?: string; } @@ -29,18 +27,14 @@ interface LoaderSpinnerProps extends PropsWithClassName, PropsWithTestId { const LoaderSpinner = ({ className, iconClassName, - iconName = 'progress_activity', + icon = 'circleNotch', testId = 'loader-spinner', text, textClassName, }: LoaderSpinnerProps): JSX.Element => { return (
- + {!!text &&
{text}
}
); diff --git a/src/components/Loader/__tests__/LoaderSpinner.test.tsx b/src/components/Loader/__tests__/LoaderSpinner.test.tsx index 905b61f..e3e604c 100644 --- a/src/components/Loader/__tests__/LoaderSpinner.test.tsx +++ b/src/components/Loader/__tests__/LoaderSpinner.test.tsx @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest'; -import { render, screen } from 'test/test-utils'; +import { render, screen, waitFor } from 'test/test-utils'; import LoaderSpinner from '../LoaderSpinner'; @@ -28,16 +28,18 @@ describe('LoaderSpinner', () => { await screen.findByTestId('loader-spinner'); // ASSERT - expect(screen.getByTestId('loader-spinner-icon').textContent).toBe('progress_activity'); + expect(screen.getByTestId('loader-spinner-icon')).toHaveAttribute('data-icon', 'circle-notch'); }); it('should render custom icon', async () => { // ARRANGE - render(); - await screen.findByTestId('loader-spinner-circle'); + render(); + await waitFor(() => + expect(screen.getByTestId('loader-spinner-icon')).toHaveAttribute('data-icon', 'bars'), + ); // ASSERT - expect(screen.getByTestId('loader-spinner-circle-icon').textContent).toBe('circle'); + expect(screen.getByTestId('loader-spinner-icon')).toHaveAttribute('data-icon', 'bars'); }); it('should render custom icon class name', async () => { diff --git a/src/components/Menu/MenuButton.tsx b/src/components/Menu/MenuButton.tsx index 6ad374c..7c9ab71 100644 --- a/src/components/Menu/MenuButton.tsx +++ b/src/components/Menu/MenuButton.tsx @@ -4,7 +4,7 @@ import classNames from 'classnames'; import { MenuProps } from './Menu'; import Button from 'components/Button/Button'; -import Icon from 'components/Icon/Icon'; +import FAIcon from 'components/Icon/FAIcon'; /** * Properties for the `MenuButton` component. @@ -42,7 +42,7 @@ const MenuButton = ({ title={title} testId={testId} > - + {isMenuOpen && setIsMenuOpen(false)} />} diff --git a/src/components/Menu/MenuCloseButton.tsx b/src/components/Menu/MenuCloseButton.tsx index abe01cb..7a385d9 100644 --- a/src/components/Menu/MenuCloseButton.tsx +++ b/src/components/Menu/MenuCloseButton.tsx @@ -2,7 +2,7 @@ import { ButtonVariant, PropsWithClassName, PropsWithTestId } from '@leanstacks/ import classNames from 'classnames'; import Button from 'components/Button/Button'; -import Icon from 'components/Icon/Icon'; +import FAIcon from 'components/Icon/FAIcon'; /** * Properties for the `MenuCloseButton` component. @@ -34,7 +34,7 @@ const MenuCloseButton = ({ title="Close" testId={testId} > - + ); }; diff --git a/src/components/Menu/MenuNavLink.tsx b/src/components/Menu/MenuNavLink.tsx index e224e22..d427e21 100644 --- a/src/components/Menu/MenuNavLink.tsx +++ b/src/components/Menu/MenuNavLink.tsx @@ -2,21 +2,22 @@ import { NavLink, NavLinkProps } from 'react-router-dom'; import { PropsWithTestId } from '@leanstacks/react-common'; import classNames from 'classnames'; -import Icon from 'components/Icon/Icon'; +import FAIcon, { FAIconProps } from 'components/Icon/FAIcon'; /** * Properties for the `MenuNavLink` component. - * @param {string} [iconName] - Optional. Name of `Icon` to display. When not - * provided, no `Icon` will be displayed. * @param {string} [iconClassName] - Optional. Additional CSS classes to apply * to the `Icon`. * @param {boolean} [styleActive] - Optional. Indicates if active `NavLink` styles * should be applied. Default: `false`. * @see {@link NavLinkProps} + * @see {@link FAIconProps} * @see {@link PropsWithTestId} */ -interface MenuNavLinkProps extends NavLinkProps, PropsWithTestId { - iconName?: string; +interface MenuNavLinkProps + extends NavLinkProps, + Partial>, + PropsWithTestId { iconClassName?: string; styleActive?: boolean; } @@ -39,7 +40,7 @@ type NavLinkRenderProps = { const MenuNavLink = ({ children, className, - iconName, + icon, iconClassName, styleActive = false, testId = 'menu-navlink', @@ -63,10 +64,11 @@ const MenuNavLink = ({ return ( <> - {iconName && ( - )} diff --git a/src/components/Menu/__tests__/MenuNavLink.test.tsx b/src/components/Menu/__tests__/MenuNavLink.test.tsx index d7ef54c..4c0fa49 100644 --- a/src/components/Menu/__tests__/MenuNavLink.test.tsx +++ b/src/components/Menu/__tests__/MenuNavLink.test.tsx @@ -35,12 +35,12 @@ describe('MenuNavLink', () => { it('should render Icon when iconName provided', async () => { // ARRANGE - render(); + render(); await screen.findByTestId('menu-navlink-icon'); // ASSERT expect(screen.getByTestId('menu-navlink-icon')).toBeDefined(); - expect(screen.getByTestId('menu-navlink-icon').textContent).toBe('circle'); + expect(screen.getByTestId('menu-navlink-icon')).toHaveAttribute('data-icon', 'bars'); }); it('should not render Icon when iconName omitted', async () => { @@ -54,7 +54,7 @@ describe('MenuNavLink', () => { it('should use custom icon classes when provided', async () => { // ARRANGE - render(); + render(); await screen.findByTestId('menu-navlink-icon'); // ASSERT diff --git a/src/components/Router/Router.tsx b/src/components/Router/Router.tsx index c7d67a8..5230034 100644 --- a/src/components/Router/Router.tsx +++ b/src/components/Router/Router.tsx @@ -76,15 +76,15 @@ export const routes: RouteObject[] = [ element: , }, { - path: 'badges', + path: 'badge', element: , }, { - path: 'buttons', + path: 'button', element: , }, { - path: 'cards', + path: 'card', element: , }, { diff --git a/src/components/Router/__tests__/PrivateOutlet.test.tsx b/src/components/Router/__tests__/PrivateOutlet.test.tsx index 03b1e59..a7d19f7 100644 --- a/src/components/Router/__tests__/PrivateOutlet.test.tsx +++ b/src/components/Router/__tests__/PrivateOutlet.test.tsx @@ -4,8 +4,6 @@ import { Navigate, Route, Routes } from 'react-router-dom'; import { render, screen } from 'test/test-utils'; import * as UseAuth from 'hooks/useAuth'; -import Icon from 'components/Icon/Icon'; - import PrivateOutlet from '../PrivateOutlet'; describe('PrivateOutlet', () => { @@ -20,9 +18,9 @@ describe('PrivateOutlet', () => { render( } /> - } /> + } /> }> - } /> + } /> , ); @@ -37,9 +35,9 @@ describe('PrivateOutlet', () => { render( } /> - } /> + } /> }> - } /> + } /> , ); @@ -55,9 +53,9 @@ describe('PrivateOutlet', () => { render( } /> - } /> + } /> }> - } /> + } /> , ); diff --git a/src/components/Toast/Toast.tsx b/src/components/Toast/Toast.tsx index 06d3493..8829841 100644 --- a/src/components/Toast/Toast.tsx +++ b/src/components/Toast/Toast.tsx @@ -7,8 +7,8 @@ import { animated, useSpring } from '@react-spring/web'; import { ToastDetail } from 'providers/ToastsContext'; import { useConfig } from 'hooks/useConfig'; -import Icon from 'components/Icon/Icon'; import Button from 'components/Button/Button'; +import FAIcon from 'components/Icon/FAIcon'; /** * Properties for the `Toast` component. @@ -80,7 +80,7 @@ const Toast = ({ className, dismiss, testId = 'toast', toast }: ToastProps): JSX onClick={() => doDismiss()} data-testid={`${testId}-button-dismiss`} > - + diff --git a/src/pages/ComponentsPage/ComponentsPage.tsx b/src/pages/ComponentsPage/ComponentsPage.tsx index 3e012be..f7ba234 100644 --- a/src/pages/ComponentsPage/ComponentsPage.tsx +++ b/src/pages/ComponentsPage/ComponentsPage.tsx @@ -20,19 +20,19 @@ const ComponentsPage = (): JSX.Element => {
- + Avatar - - Badges + + Badge - - Buttons + + Button - - Cards + + Card - + Text
diff --git a/src/pages/ComponentsPage/components/CardComponents.tsx b/src/pages/ComponentsPage/components/CardComponents.tsx index dd5156d..bcecf26 100644 --- a/src/pages/ComponentsPage/components/CardComponents.tsx +++ b/src/pages/ComponentsPage/components/CardComponents.tsx @@ -146,7 +146,7 @@ const CardComponents = ({
@@ -154,7 +154,7 @@ const CardComponents = ({ `} @@ -165,7 +165,7 @@ const CardComponents = ({
@@ -174,7 +174,7 @@ const CardComponents = ({ className="my-2" code={``} diff --git a/src/pages/SettingsPage/SettingsPage.tsx b/src/pages/SettingsPage/SettingsPage.tsx index c5c1b05..409ab27 100644 --- a/src/pages/SettingsPage/SettingsPage.tsx +++ b/src/pages/SettingsPage/SettingsPage.tsx @@ -31,7 +31,7 @@ const SettingsPage = (): JSX.Element => { )}
- + Appearance
diff --git a/src/pages/SigninPage/components/SigninForm.tsx b/src/pages/SigninPage/components/SigninForm.tsx index 0e91429..82cc442 100644 --- a/src/pages/SigninPage/components/SigninForm.tsx +++ b/src/pages/SigninPage/components/SigninForm.tsx @@ -8,7 +8,7 @@ import { Button } from '@leanstacks/react-common'; import { useSignin } from '../api/useSignin'; import TextField from 'components/Form/TextField'; -import Icon from 'components/Icon/Icon'; +import FAIcon from 'components/Icon/FAIcon'; /** * Properties for the `SigninForm` component. @@ -63,7 +63,7 @@ const SigninForm = ({ className, testId = 'form-signin' }: SigninFormProps): JSX className="mb-4 flex items-center gap-2 rounded-none" testId={`${testId}-alert`} > - + {error} )} diff --git a/src/pages/UsersPage/Tasks/components/TaskCompleteToggle.tsx b/src/pages/UsersPage/Tasks/components/TaskCompleteToggle.tsx index 653e878..1be769d 100644 --- a/src/pages/UsersPage/Tasks/components/TaskCompleteToggle.tsx +++ b/src/pages/UsersPage/Tasks/components/TaskCompleteToggle.tsx @@ -10,7 +10,7 @@ import { useTranslation } from 'react-i18next'; import { Task } from 'pages/UsersPage/api/useGetUserTasks'; import { useUpdateTask } from '../api/useUpdateTask'; import { useToasts } from 'hooks/useToasts'; -import Icon from 'components/Icon/Icon'; +import FAIcon from 'components/Icon/FAIcon'; /** * Propeties for the `TaskCompleteToggle` component. @@ -73,10 +73,9 @@ const TaskCompleteToggle = ({ onClick={handleButtonClick} data-testid={testId} > - diff --git a/src/pages/UsersPage/Tasks/components/TaskDetail.tsx b/src/pages/UsersPage/Tasks/components/TaskDetail.tsx index a8e1199..b8e277f 100644 --- a/src/pages/UsersPage/Tasks/components/TaskDetail.tsx +++ b/src/pages/UsersPage/Tasks/components/TaskDetail.tsx @@ -9,13 +9,13 @@ import { import { useNavigate, useParams } from 'react-router-dom'; import classNames from 'classnames'; -import Icon from 'components/Icon/Icon'; import Text from 'components/Text/Text'; import { useGetTask } from '../api/useGetTask'; import LoaderSkeleton from 'components/Loader/LoaderSkeleton'; import { useGetUser } from 'api/useGetUser'; import LoaderSpinner from 'components/Loader/LoaderSpinner'; import Badge from 'components/Badge/Badge'; +import FAIcon from 'components/Icon/FAIcon'; /** * Properties for the `TaskDetail` component. @@ -46,22 +46,22 @@ const TaskDetail = ({ className, testId = 'task-detail' }: TaskDetailProps): JSX return (
-
- +
+ Task {isLoadingTask && } {!!task &&
{`#${task.id}`}
} -
- - +
+ +
@@ -72,7 +72,7 @@ const TaskDetail = ({ className, testId = 'task-detail' }: TaskDetailProps): JSX className="mb-4 flex items-center gap-2 rounded-none" testId={`${testId}-alert-taskError`} > - + {taskError.message} )} @@ -83,7 +83,7 @@ const TaskDetail = ({ className, testId = 'task-detail' }: TaskDetailProps): JSX className="mb-4 flex items-center gap-2 rounded-none" testId={`${testId}-alert-userError`} > - + {userError.message} )} diff --git a/src/pages/UsersPage/Tasks/components/__tests__/TaskCompleteToggle.test.tsx b/src/pages/UsersPage/Tasks/components/__tests__/TaskCompleteToggle.test.tsx index 27e96ab..d5b2d9f 100644 --- a/src/pages/UsersPage/Tasks/components/__tests__/TaskCompleteToggle.test.tsx +++ b/src/pages/UsersPage/Tasks/components/__tests__/TaskCompleteToggle.test.tsx @@ -57,7 +57,7 @@ describe('TaskCompleteToggle', () => { // ASSERT expect(screen.getByTestId('toggle-task-complete')).toBeDefined(); expect(screen.getByTestId('toggle-task-complete').title).toBe('Mark complete'); - expect(screen.getByTestId('toggle-task-complete-icon').textContent).toBe('circle'); + expect(screen.getByTestId('toggle-task-complete-icon')).toHaveAttribute('data-icon', 'circle'); }); it('should render complete task', async () => { @@ -68,7 +68,10 @@ describe('TaskCompleteToggle', () => { // ASSERT expect(screen.getByTestId('toggle-task-complete')).toBeDefined(); expect(screen.getByTestId('toggle-task-complete').title).toBe('Mark incomplete'); - expect(screen.getByTestId('toggle-task-complete-icon').textContent).toBe('task_alt'); + expect(screen.getByTestId('toggle-task-complete-icon')).toHaveAttribute( + 'data-icon', + 'circle-check', + ); }); it('should toggle task complete when clicked', async () => { @@ -76,7 +79,7 @@ describe('TaskCompleteToggle', () => { render(); await screen.findByTestId('toggle-task-complete'); expect(screen.getByTestId('toggle-task-complete').title).toBe('Mark complete'); - expect(screen.getByTestId('toggle-task-complete-icon').textContent).toBe('circle'); + expect(screen.getByTestId('toggle-task-complete-icon')).toHaveAttribute('data-icon', 'circle'); // ACT await userEvent.click(screen.getByTestId('toggle-task-complete')); @@ -94,7 +97,10 @@ describe('TaskCompleteToggle', () => { render(); await screen.findByTestId('toggle-task-complete'); expect(screen.getByTestId('toggle-task-complete').title).toBe('Mark incomplete'); - expect(screen.getByTestId('toggle-task-complete-icon').textContent).toBe('task_alt'); + expect(screen.getByTestId('toggle-task-complete-icon')).toHaveAttribute( + 'data-icon', + 'circle-check', + ); // ACT await userEvent.click(screen.getByTestId('toggle-task-complete')); diff --git a/src/pages/UsersPage/components/UserDetail.tsx b/src/pages/UsersPage/components/UserDetail.tsx index e946c56..f8f0170 100644 --- a/src/pages/UsersPage/components/UserDetail.tsx +++ b/src/pages/UsersPage/components/UserDetail.tsx @@ -4,7 +4,7 @@ import { useParams } from 'react-router-dom'; import { useGetUser } from 'api/useGetUser'; import Text from 'components/Text/Text'; import LoaderSkeleton from 'components/Loader/LoaderSkeleton'; -import Icon from 'components/Icon/Icon'; +import FAIcon from 'components/Icon/FAIcon'; import UserTasks from './UserTasks'; /** @@ -38,8 +38,8 @@ const UserDetail = ({ className, testId = 'user-detail' }: UserDetailProps): JSX
-
- +
+ Company
{user.company.name}
@@ -47,8 +47,8 @@ const UserDetail = ({ className, testId = 'user-detail' }: UserDetailProps): JSX
{user.company.bs}
-
- +
+ Address
{user.address.street}
diff --git a/src/pages/UsersPage/components/UserDetailEmpty.tsx b/src/pages/UsersPage/components/UserDetailEmpty.tsx index e1aac04..8ebc0a8 100644 --- a/src/pages/UsersPage/components/UserDetailEmpty.tsx +++ b/src/pages/UsersPage/components/UserDetailEmpty.tsx @@ -23,7 +23,7 @@ const UserDetailEmpty = ({
diff --git a/src/pages/UsersPage/components/UserTaskList.tsx b/src/pages/UsersPage/components/UserTaskList.tsx index 7e1eed4..531f0f6 100644 --- a/src/pages/UsersPage/components/UserTaskList.tsx +++ b/src/pages/UsersPage/components/UserTaskList.tsx @@ -3,7 +3,7 @@ import { useParams } from 'react-router-dom'; import filter from 'lodash/filter'; import { useGetUserTasks } from '../api/useGetUserTasks'; -import Icon from 'components/Icon/Icon'; +import FAIcon from 'components/Icon/FAIcon'; import Text from 'components/Text/Text'; import LoaderSkeleton from 'components/Loader/LoaderSkeleton'; import UserTaskListItem from './UserTaskListItem'; @@ -31,8 +31,8 @@ const UserTaskList = ({ className, testId = 'user-task-list' }: UserTaskListProp return (
-
- +
+ Tasks
diff --git a/src/pages/UsersPage/components/UserTasks.tsx b/src/pages/UsersPage/components/UserTasks.tsx index 623ba60..9e5a607 100644 --- a/src/pages/UsersPage/components/UserTasks.tsx +++ b/src/pages/UsersPage/components/UserTasks.tsx @@ -2,7 +2,7 @@ import { PropsWithClassName, PropsWithTestId } from '@leanstacks/react-common'; import classNames from 'classnames'; import { useGetUserTasks } from '../api/useGetUserTasks'; -import Icon from 'components/Icon/Icon'; +import FAIcon from 'components/Icon/FAIcon'; import Text from 'components/Text/Text'; import Link from 'components/Link/Link'; import LoaderSkeleton from 'components/Loader/LoaderSkeleton'; @@ -30,8 +30,8 @@ const UserTasks = ({ className, testId = 'user-tasks', userId }: UserTasksProps) return (
-
- +
+ Tasks {!!tasks && ( @@ -51,10 +51,9 @@ const UserTasks = ({ className, testId = 'user-tasks', userId }: UserTasksProps) {!!tasks && tasks.slice(0, 3).map((task) => (
-
{task.title}
diff --git a/src/pages/UsersPage/components/__tests__/UserTaskListItem.test.tsx b/src/pages/UsersPage/components/__tests__/UserTaskListItem.test.tsx index 09042ff..efbce1c 100644 --- a/src/pages/UsersPage/components/__tests__/UserTaskListItem.test.tsx +++ b/src/pages/UsersPage/components/__tests__/UserTaskListItem.test.tsx @@ -43,8 +43,9 @@ describe('UserTaskListItem', () => { await screen.findByTestId('user-task-list-item'); // ASSERT - expect(screen.getByTestId('user-task-list-item-toggle-complete-icon').textContent).toBe( - 'task_alt', + expect(screen.getByTestId('user-task-list-item-toggle-complete-icon')).toHaveAttribute( + 'data-icon', + 'circle-check', ); }); }); diff --git a/tsconfig.json b/tsconfig.json index 26fd866..37849a7 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -34,6 +34,6 @@ "utils/*": ["./src/utils/*"] } }, - "include": ["src"], + "include": ["src", "./vitest.setup.ts"], "references": [{ "path": "./tsconfig.node.json" }] } diff --git a/vitest.setup.ts b/vitest.setup.ts index 1d42b77..ce4edf5 100644 --- a/vitest.setup.ts +++ b/vitest.setup.ts @@ -1,4 +1,5 @@ import { afterAll, afterEach, beforeAll } from 'vitest'; +import '@testing-library/jest-dom/vitest'; import { server } from './src/test/mocks/server'; import { queryClient } from './src/test/query-client'; From 00c279eaa36aec9f2846c74dd7ce7167ff9b9dc3 Mon Sep 17 00:00:00 2001 From: Matthew Warman Date: Tue, 29 Oct 2024 07:13:19 -0400 Subject: [PATCH 4/7] upgrade deps --- package-lock.json | 300 ++++++++++++++++++++++++---------------------- package.json | 10 +- 2 files changed, 161 insertions(+), 149 deletions(-) diff --git a/package-lock.json b/package-lock.json index af16a5b..b495494 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,7 +32,7 @@ "react-i18next": "15.1.0", "react-router-dom": "6.27.0", "tailwindcss": "3.4.14", - "uuid": "10.0.0", + "uuid": "11.0.2", "yup": "1.4.0" }, "devDependencies": { @@ -44,10 +44,10 @@ "@types/react": "18.3.12", "@types/react-dom": "18.3.1", "@types/uuid": "10.0.0", - "@typescript-eslint/eslint-plugin": "8.11.0", - "@typescript-eslint/parser": "8.11.0", + "@typescript-eslint/eslint-plugin": "8.12.1", + "@typescript-eslint/parser": "8.12.1", "@vitejs/plugin-react": "4.3.3", - "@vitest/coverage-v8": "2.1.3", + "@vitest/coverage-v8": "2.1.4", "autoprefixer": "10.4.20", "eslint": "8.57.1", "eslint-plugin-react-hooks": "5.0.0", @@ -59,7 +59,7 @@ "prettier-plugin-tailwindcss": "0.6.8", "typescript": "5.6.3", "vite": "5.4.10", - "vitest": "2.1.3" + "vitest": "2.1.4" } }, "node_modules/@adobe/css-tools": { @@ -2449,17 +2449,17 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.11.0.tgz", - "integrity": "sha512-KhGn2LjW1PJT2A/GfDpiyOfS4a8xHQv2myUagTM5+zsormOmBlYsnQ6pobJ8XxJmh6hnHwa2Mbe3fPrDJoDhbA==", + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.12.1.tgz", + "integrity": "sha512-gNg/inLRcPoBsKKIe4Vv38SVSOhk4BKWNO0T56sVff33gRqtTpOsrhHtiOKD1lmIOmCtZMPaW2x/h2FlM+sCEg==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.11.0", - "@typescript-eslint/type-utils": "8.11.0", - "@typescript-eslint/utils": "8.11.0", - "@typescript-eslint/visitor-keys": "8.11.0", + "@typescript-eslint/scope-manager": "8.12.1", + "@typescript-eslint/type-utils": "8.12.1", + "@typescript-eslint/utils": "8.12.1", + "@typescript-eslint/visitor-keys": "8.12.1", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -2483,16 +2483,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.11.0.tgz", - "integrity": "sha512-lmt73NeHdy1Q/2ul295Qy3uninSqi6wQI18XwSpm8w0ZbQXUpjCAWP1Vlv/obudoBiIjJVjlztjQ+d/Md98Yxg==", + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.12.1.tgz", + "integrity": "sha512-I/I9Bg7qFa8rOgBnUUHIWTgzbB5wVkSLX+04xGUzTcJUtdq/I2uHWR9mbW6qUYJG/UmkuDcTax5JHvoEWOAHOQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/scope-manager": "8.11.0", - "@typescript-eslint/types": "8.11.0", - "@typescript-eslint/typescript-estree": "8.11.0", - "@typescript-eslint/visitor-keys": "8.11.0", + "@typescript-eslint/scope-manager": "8.12.1", + "@typescript-eslint/types": "8.12.1", + "@typescript-eslint/typescript-estree": "8.12.1", + "@typescript-eslint/visitor-keys": "8.12.1", "debug": "^4.3.4" }, "engines": { @@ -2512,14 +2512,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.11.0.tgz", - "integrity": "sha512-Uholz7tWhXmA4r6epo+vaeV7yjdKy5QFCERMjs1kMVsLRKIrSdM6o21W2He9ftp5PP6aWOVpD5zvrvuHZC0bMQ==", + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.12.1.tgz", + "integrity": "sha512-bma6sD1iViTt+y9MAwDlBdPTMCqoH/BNdcQk4rKhIZWv3eM0xHmzeSrPJA663PAqFqfpOmtdugycpr0E1mZDVA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.11.0", - "@typescript-eslint/visitor-keys": "8.11.0" + "@typescript-eslint/types": "8.12.1", + "@typescript-eslint/visitor-keys": "8.12.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2530,14 +2530,14 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.11.0.tgz", - "integrity": "sha512-ItiMfJS6pQU0NIKAaybBKkuVzo6IdnAhPFZA/2Mba/uBjuPQPet/8+zh5GtLHwmuFRShZx+8lhIs7/QeDHflOg==", + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.12.1.tgz", + "integrity": "sha512-zJzrvbDVjIzVKV2TGHcjembEhws8RWXJhmqfO9hS2gRXBN0gDwGhRPEdJ6AZglzfJ+YA1q09EWpSLSXjBJpIMQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.11.0", - "@typescript-eslint/utils": "8.11.0", + "@typescript-eslint/typescript-estree": "8.12.1", + "@typescript-eslint/utils": "8.12.1", "debug": "^4.3.4", "ts-api-utils": "^1.3.0" }, @@ -2555,9 +2555,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.11.0.tgz", - "integrity": "sha512-tn6sNMHf6EBAYMvmPUaKaVeYvhUsrE6x+bXQTxjQRp360h1giATU0WvgeEys1spbvb5R+VpNOZ+XJmjD8wOUHw==", + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.12.1.tgz", + "integrity": "sha512-anMS4es5lxBe4UVcDXOkcDb3csnm5BvaNIbOFfvy/pJEohorsggdVB8MFbl5EZiEuBnZZ0ei1z7W5b6FdFiV1Q==", "dev": true, "license": "MIT", "engines": { @@ -2569,14 +2569,14 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.11.0.tgz", - "integrity": "sha512-yHC3s1z1RCHoCz5t06gf7jH24rr3vns08XXhfEqzYpd6Hll3z/3g23JRi0jM8A47UFKNc3u/y5KIMx8Ynbjohg==", + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.12.1.tgz", + "integrity": "sha512-k/o9khHOckPeDXilFTIPsP9iAYhhdMh3OsOL3i2072PNpFqhqzRHx472/0DeC8H/WZee3bZG0z2ddGRSPgeOKw==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/types": "8.11.0", - "@typescript-eslint/visitor-keys": "8.11.0", + "@typescript-eslint/types": "8.12.1", + "@typescript-eslint/visitor-keys": "8.12.1", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -2598,16 +2598,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.11.0.tgz", - "integrity": "sha512-CYiX6WZcbXNJV7UNB4PLDIBtSdRmRI/nb0FMyqHPTQD1rMjA0foPLaPUV39C/MxkTd/QKSeX+Gb34PPsDVC35g==", + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.12.1.tgz", + "integrity": "sha512-sDv9yFHrhKe1WN8EYuzfhKCh/sFRupe9P+m/lZ5YgVvPoCUGHNN50IO4llSu7JAbftUM/QcCh+GeCortXPrBYQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.11.0", - "@typescript-eslint/types": "8.11.0", - "@typescript-eslint/typescript-estree": "8.11.0" + "@typescript-eslint/scope-manager": "8.12.1", + "@typescript-eslint/types": "8.12.1", + "@typescript-eslint/typescript-estree": "8.12.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2621,13 +2621,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.11.0.tgz", - "integrity": "sha512-EaewX6lxSjRJnc+99+dqzTeoDZUfyrA52d2/HRrkI830kgovWsmIiTfmr0NZorzqic7ga+1bS60lRBUgR3n/Bw==", + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.12.1.tgz", + "integrity": "sha512-2RwdwnNGuOQKdGjuhujQHUqBZhEuodg2sLVPvOfWktvA9sOXOVqARjOyHSyhN2LiJGKxV6c8oOcmOtRcAnEeFw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.11.0", + "@typescript-eslint/types": "8.12.1", "eslint-visitor-keys": "^3.4.3" }, "engines": { @@ -2666,21 +2666,21 @@ } }, "node_modules/@vitest/coverage-v8": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-2.1.3.tgz", - "integrity": "sha512-2OJ3c7UPoFSmBZwqD2VEkUw6A/tzPF0LmW0ZZhhB8PFxuc+9IBG/FaSM+RLEenc7ljzFvGN+G0nGQoZnh7sy2A==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-2.1.4.tgz", + "integrity": "sha512-FPKQuJfR6VTfcNMcGpqInmtJuVXFSCd9HQltYncfR01AzXhLucMEtQ5SinPdZxsT5x/5BK7I5qFJ5/ApGCmyTQ==", "dev": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.3.0", "@bcoe/v8-coverage": "^0.2.3", - "debug": "^4.3.6", + "debug": "^4.3.7", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", "istanbul-lib-source-maps": "^5.0.6", "istanbul-reports": "^3.1.7", - "magic-string": "^0.30.11", - "magicast": "^0.3.4", + "magic-string": "^0.30.12", + "magicast": "^0.3.5", "std-env": "^3.7.0", "test-exclude": "^7.0.1", "tinyrainbow": "^1.2.0" @@ -2689,8 +2689,8 @@ "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "@vitest/browser": "2.1.3", - "vitest": "2.1.3" + "@vitest/browser": "2.1.4", + "vitest": "2.1.4" }, "peerDependenciesMeta": { "@vitest/browser": { @@ -2699,25 +2699,52 @@ } }, "node_modules/@vitest/expect": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.3.tgz", - "integrity": "sha512-SNBoPubeCJhZ48agjXruCI57DvxcsivVDdWz+SSsmjTT4QN/DfHk3zB/xKsJqMs26bLZ/pNRLnCf0j679i0uWQ==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.4.tgz", + "integrity": "sha512-DOETT0Oh1avie/D/o2sgMHGrzYUFFo3zqESB2Hn70z6QB1HrS2IQ9z5DfyTqU8sg4Bpu13zZe9V4+UTNQlUeQA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "2.1.3", - "@vitest/utils": "2.1.3", - "chai": "^5.1.1", + "@vitest/spy": "2.1.4", + "@vitest/utils": "2.1.4", + "chai": "^5.1.2", "tinyrainbow": "^1.2.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, + "node_modules/@vitest/mocker": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.4.tgz", + "integrity": "sha512-Ky/O1Lc0QBbutJdW0rqLeFNbuLEyS+mIPiNdlVlp2/yhJ0SbyYqObS5IHdhferJud8MbbwMnexg4jordE5cCoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "2.1.4", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.12" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, "node_modules/@vitest/pretty-format": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.3.tgz", - "integrity": "sha512-XH1XdtoLZCpqV59KRbPrIhFCOO0hErxrQCMcvnQete3Vibb9UeIOX02uFPfVn3Z9ZXsq78etlfyhnkmIZSzIwQ==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.4.tgz", + "integrity": "sha512-L95zIAkEuTDbUX1IsjRl+vyBSLh3PwLLgKpghl37aCK9Jvw0iP+wKwIFhfjdUtA2myLgjrG6VU6JCFLv8q/3Ww==", "dev": true, "license": "MIT", "dependencies": { @@ -2728,13 +2755,13 @@ } }, "node_modules/@vitest/runner": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.3.tgz", - "integrity": "sha512-JGzpWqmFJ4fq5ZKHtVO3Xuy1iF2rHGV4d/pdzgkYHm1+gOzNZtqjvyiaDGJytRyMU54qkxpNzCx+PErzJ1/JqQ==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.4.tgz", + "integrity": "sha512-sKRautINI9XICAMl2bjxQM8VfCMTB0EbsBc/EDFA57V6UQevEKY/TOPOF5nzcvCALltiLfXWbq4MaAwWx/YxIA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "2.1.3", + "@vitest/utils": "2.1.4", "pathe": "^1.1.2" }, "funding": { @@ -2742,14 +2769,14 @@ } }, "node_modules/@vitest/snapshot": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.3.tgz", - "integrity": "sha512-qWC2mWc7VAXmjAkEKxrScWHWFyCQx/cmiZtuGqMi+WwqQJ2iURsVY4ZfAK6dVo6K2smKRU6l3BPwqEBvhnpQGg==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.4.tgz", + "integrity": "sha512-3Kab14fn/5QZRog5BPj6Rs8dc4B+mim27XaKWFWHWA87R56AKjHTGcBFKpvZKDzC4u5Wd0w/qKsUIio3KzWW4Q==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "2.1.3", - "magic-string": "^0.30.11", + "@vitest/pretty-format": "2.1.4", + "magic-string": "^0.30.12", "pathe": "^1.1.2" }, "funding": { @@ -2757,27 +2784,27 @@ } }, "node_modules/@vitest/spy": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.3.tgz", - "integrity": "sha512-Nb2UzbcUswzeSP7JksMDaqsI43Sj5+Kry6ry6jQJT4b5gAK+NS9NED6mDb8FlMRCX8m5guaHCDZmqYMMWRy5nQ==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.4.tgz", + "integrity": "sha512-4JOxa+UAizJgpZfaCPKK2smq9d8mmjZVPMt2kOsg/R8QkoRzydHH1qHxIYNvr1zlEaFj4SXiaaJWxq/LPLKaLg==", "dev": true, "license": "MIT", "dependencies": { - "tinyspy": "^3.0.0" + "tinyspy": "^3.0.2" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/utils": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.3.tgz", - "integrity": "sha512-xpiVfDSg1RrYT0tX6czgerkpcKFmFOF/gCr30+Mve5V2kewCy4Prn1/NDMSRwaSmT7PRaOF83wu+bEtsY1wrvA==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.4.tgz", + "integrity": "sha512-MXDnZn0Awl2S86PSNIim5PWXgIAx8CIkzu35mBdSApUip6RFOGXBCf3YFyeEu8n1IHk4bWD46DeYFu9mQlFIRg==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "2.1.3", - "loupe": "^3.1.1", + "@vitest/pretty-format": "2.1.4", + "loupe": "^3.1.2", "tinyrainbow": "^1.2.0" }, "funding": { @@ -4114,6 +4141,16 @@ "es5-ext": "~0.10.14" } }, + "node_modules/expect-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.1.0.tgz", + "integrity": "sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/ext": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", @@ -5146,13 +5183,14 @@ } }, "node_modules/magicast": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.4.tgz", - "integrity": "sha512-TyDF/Pn36bBji9rWKHlZe+PZb6Mx5V8IHCSxk7X4aljM4e/vyDvZZYwHewdVaqiA0nb3ghfHU/6AUpDxWoER2Q==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", + "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/parser": "^7.24.4", - "@babel/types": "^7.24.0", + "@babel/parser": "^7.25.4", + "@babel/types": "^7.25.4", "source-map-js": "^1.2.0" } }, @@ -7098,15 +7136,16 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "node_modules/uuid": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", - "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.0.2.tgz", + "integrity": "sha512-14FfcOJmqdjbBPdDjFQyk/SdT4NySW4eM0zcG+HqbHP5jzuH56xO3J1DGhgs/cEMCfwYi3HQI1gnTO62iaG+tQ==", "funding": [ "https://github.com/sponsors/broofa", "https://github.com/sponsors/ctavan" ], + "license": "MIT", "bin": { - "uuid": "dist/bin/uuid" + "uuid": "dist/esm/bin/uuid" } }, "node_modules/v8-compile-cache-lib": { @@ -7177,14 +7216,14 @@ } }, "node_modules/vite-node": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.3.tgz", - "integrity": "sha512-I1JadzO+xYX887S39Do+paRePCKoiDrWRRjp9kkG5he0t7RXNvPAJPCQSJqbGN4uCrFFeS3Kj3sLqY8NMYBEdA==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.4.tgz", + "integrity": "sha512-kqa9v+oi4HwkG6g8ufRnb5AeplcRw8jUF6/7/Qz1qRQOXHImG8YnLbB+LLszENwFnoBl9xIf9nVdCFzNd7GQEg==", "dev": true, "license": "MIT", "dependencies": { "cac": "^6.7.14", - "debug": "^4.3.6", + "debug": "^4.3.7", "pathe": "^1.1.2", "vite": "^5.0.0" }, @@ -7199,30 +7238,31 @@ } }, "node_modules/vitest": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.3.tgz", - "integrity": "sha512-Zrxbg/WiIvUP2uEzelDNTXmEMJXuzJ1kCpbDvaKByFA9MNeO95V+7r/3ti0qzJzrxdyuUw5VduN7k+D3VmVOSA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/expect": "2.1.3", - "@vitest/mocker": "2.1.3", - "@vitest/pretty-format": "^2.1.3", - "@vitest/runner": "2.1.3", - "@vitest/snapshot": "2.1.3", - "@vitest/spy": "2.1.3", - "@vitest/utils": "2.1.3", - "chai": "^5.1.1", - "debug": "^4.3.6", - "magic-string": "^0.30.11", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.4.tgz", + "integrity": "sha512-eDjxbVAJw1UJJCHr5xr/xM86Zx+YxIEXGAR+bmnEID7z9qWfoxpHw0zdobz+TQAFOLT+nEXz3+gx6nUJ7RgmlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "2.1.4", + "@vitest/mocker": "2.1.4", + "@vitest/pretty-format": "^2.1.4", + "@vitest/runner": "2.1.4", + "@vitest/snapshot": "2.1.4", + "@vitest/spy": "2.1.4", + "@vitest/utils": "2.1.4", + "chai": "^5.1.2", + "debug": "^4.3.7", + "expect-type": "^1.1.0", + "magic-string": "^0.30.12", "pathe": "^1.1.2", "std-env": "^3.7.0", "tinybench": "^2.9.0", - "tinyexec": "^0.3.0", - "tinypool": "^1.0.0", + "tinyexec": "^0.3.1", + "tinypool": "^1.0.1", "tinyrainbow": "^1.2.0", "vite": "^5.0.0", - "vite-node": "2.1.3", + "vite-node": "2.1.4", "why-is-node-running": "^2.3.0" }, "bin": { @@ -7237,8 +7277,8 @@ "peerDependencies": { "@edge-runtime/vm": "*", "@types/node": "^18.0.0 || >=20.0.0", - "@vitest/browser": "2.1.3", - "@vitest/ui": "2.1.3", + "@vitest/browser": "2.1.4", + "@vitest/ui": "2.1.4", "happy-dom": "*", "jsdom": "*" }, @@ -7263,34 +7303,6 @@ } } }, - "node_modules/vitest/node_modules/@vitest/mocker": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.3.tgz", - "integrity": "sha512-eSpdY/eJDuOvuTA3ASzCjdithHa+GIF1L4PqtEELl6Qa3XafdMLBpBlZCIUCX2J+Q6sNmjmxtosAG62fK4BlqQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/spy": "2.1.3", - "estree-walker": "^3.0.3", - "magic-string": "^0.30.11" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "@vitest/spy": "2.1.3", - "msw": "^2.3.5", - "vite": "^5.0.0" - }, - "peerDependenciesMeta": { - "msw": { - "optional": true - }, - "vite": { - "optional": true - } - } - }, "node_modules/void-elements": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", diff --git a/package.json b/package.json index 91faeb1..6a5c558 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ "react-i18next": "15.1.0", "react-router-dom": "6.27.0", "tailwindcss": "3.4.14", - "uuid": "10.0.0", + "uuid": "11.0.2", "yup": "1.4.0" }, "devDependencies": { @@ -57,10 +57,10 @@ "@types/react": "18.3.12", "@types/react-dom": "18.3.1", "@types/uuid": "10.0.0", - "@typescript-eslint/eslint-plugin": "8.11.0", - "@typescript-eslint/parser": "8.11.0", + "@typescript-eslint/eslint-plugin": "8.12.1", + "@typescript-eslint/parser": "8.12.1", "@vitejs/plugin-react": "4.3.3", - "@vitest/coverage-v8": "2.1.3", + "@vitest/coverage-v8": "2.1.4", "autoprefixer": "10.4.20", "eslint": "8.57.1", "eslint-plugin-react-hooks": "5.0.0", @@ -72,6 +72,6 @@ "prettier-plugin-tailwindcss": "0.6.8", "typescript": "5.6.3", "vite": "5.4.10", - "vitest": "2.1.3" + "vitest": "2.1.4" } } From 53efccbcea4ec88d64ea498fbfc145749af12a7d Mon Sep 17 00:00:00 2001 From: Matthew Warman Date: Tue, 29 Oct 2024 07:34:04 -0400 Subject: [PATCH 5/7] docs --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6a5c558..d887d9b 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "type": "module", "repository": { "type": "git", - "url": "https://github.com/leanstacks/skeleton-ui-react" + "url": "git+https://github.com/leanstacks/skeleton-ui-react.git" }, "scripts": { "dev": "vite", From a913289686d145c6ea639f73fa30b48a6193fa1f Mon Sep 17 00:00:00 2001 From: Matthew Warman Date: Tue, 29 Oct 2024 11:54:30 -0400 Subject: [PATCH 6/7] remove material symbols font and Icon component --- README.md | 3 - index.html | 4 - src/components/Icon/FAIcon.tsx | 2 +- src/components/Icon/Icon.tsx | 72 ------------ src/components/Icon/__tests__/Icon.test.tsx | 118 -------------------- 5 files changed, 1 insertion(+), 198 deletions(-) delete mode 100644 src/components/Icon/Icon.tsx delete mode 100644 src/components/Icon/__tests__/Icon.test.tsx diff --git a/README.md b/README.md index 83f22ff..52771ec 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,6 @@ The technology stack includes: - DayJS - date utility functions - TanStack React Table - tables and datagrids - Tailwind - styling -- Material Symbols - icons - Font Awesome - icons - Testing Library React - tests - Vitest - tests @@ -244,7 +243,6 @@ This project uses GitHub Actions to perform DevOps automation activities such as - [Formik][formik] - [Yup][yup] - [Tailwind CSS][tailwind] -- [Material Symbols][icons] - [Font Awesome][fontawesome] - [Testing Library][testing-library] - [GitHub Actions][ghactions] @@ -256,7 +254,6 @@ This project uses GitHub Actions to perform DevOps automation activities such as [formik]: https://formik.org/ 'Formik' [yup]: https://github.com/jquense/yup 'Yup' [tailwind]: https://tailwindcss.com/ 'Tailwind CSS' -[icons]: https://fonts.google.com/icons 'Material Symbols' [fontawesome]: https://fontawesome.com/ 'Font Awesome' [tanstack]: https://tanstack.com/ 'TanStack' [testing-library]: https://testing-library.com/ 'Testing Library' diff --git a/index.html b/index.html index f3783c2..e090e36 100644 --- a/index.html +++ b/index.html @@ -14,10 +14,6 @@ href="https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,100;0,300;0,400;0,500;0,600;0,700;1,400&display=swap" rel="stylesheet" /> - React Starter | LeanStacks diff --git a/src/components/Icon/FAIcon.tsx b/src/components/Icon/FAIcon.tsx index c017cf0..c9cf0e2 100644 --- a/src/components/Icon/FAIcon.tsx +++ b/src/components/Icon/FAIcon.tsx @@ -72,7 +72,7 @@ export type FAIconName = /** * Properties for the `FAIcon` component. - * @param {FAIconName} icon - The Font Awesome icon name without the `fa-` prefix. + * @param {FAIconName} icon - The icon name. * @see {@link BaseComponentProps} * @see {@link FontAwesomeIcon} */ diff --git a/src/components/Icon/Icon.tsx b/src/components/Icon/Icon.tsx deleted file mode 100644 index 505707b..0000000 --- a/src/components/Icon/Icon.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import { PropsWithClassName, PropsWithTestId } from '@leanstacks/react-common'; -import classNames from 'classnames'; - -/** - * Properties for the `Icon` component. - * @param {string} name - The Google Material Symbols icon name. - * @param {number} [fill] - Indicates if icon is filled. Values: `1` Filled, `0` - Outline. Default: `1`. - * @param {number} [weight] - The icon weight. Values: `100`, `200`, `300`, `400`, `500`, `600`, `700`. Default: `400`. - * @param {number} [grade] - Granular adjustments to the thickness. Range: `-25`-`200`. Default: `0`. - * @param {number} [opticalSize] - Optical size. Range: `20`-`48`. Default: `24`. - * @see {@link PropsWithClassName} - * @see {@link PropsWithTestId} - */ -export interface IconProps extends PropsWithClassName, PropsWithTestId { - name: string; - fill?: 0 | 1; - weight?: 100 | 200 | 300 | 400 | 500 | 600 | 700; - grade?: number; - opticalSize?: number; -} - -const FILL_VALUES = [0, 1]; -const FILL_DEFAULT = 1; -const WEIGHT_VALUES = [100, 200, 300, 400, 500, 600, 700]; -const WEIGHT_DEFAULT = 400; -const GRADE_MIN = -25; -const GRADE_MAX = 200; -const GRADE_DEFAULT = 0; -const OPTICAL_SIZE_MIN = 20; -const OPTICAL_SIZE_MAX = 48; -const OPTICAL_SIZE_DEFAULT = 24; - -/** - * The `Icon` React component formats and renders a styled icon from Google Material Symbols. - * @param {IconProps} props - Component properties, `IconProps`. - * @returns {JSX.Element} JSX - * @see {@link https://fonts.google.com/icons | Google Material Symbols} - * @see {@link IconProps} - */ -const Icon = ({ - className, - fill = FILL_DEFAULT, - grade = 0, - name, - opticalSize = 24, - testId = 'icon', - weight = 400, -}: IconProps): JSX.Element => { - const fillValue = FILL_VALUES.includes(fill) ? fill : FILL_DEFAULT; - const weightValue = WEIGHT_VALUES.includes(weight) ? weight : WEIGHT_DEFAULT; - const gradeValue = grade < GRADE_MIN || grade > GRADE_MAX ? GRADE_DEFAULT : grade; - const opticalSizeValue = - opticalSize < OPTICAL_SIZE_MIN || opticalSize > OPTICAL_SIZE_MAX - ? OPTICAL_SIZE_DEFAULT - : opticalSize; - - const iconStyle = { - fontVariationSettings: `'FILL' ${fillValue}, 'wght' ${weightValue}, 'GRAD' ${gradeValue}, 'opsz' ${opticalSizeValue}`, - }; - - return ( - - {name} - - ); -}; - -export default Icon; diff --git a/src/components/Icon/__tests__/Icon.test.tsx b/src/components/Icon/__tests__/Icon.test.tsx deleted file mode 100644 index c9575a4..0000000 --- a/src/components/Icon/__tests__/Icon.test.tsx +++ /dev/null @@ -1,118 +0,0 @@ -import { describe, expect, it } from 'vitest'; -import { render, screen } from '@testing-library/react'; - -import Icon from '../Icon'; - -describe('Icon', () => { - it('should render successfully', async () => { - // ARRANGE - render(); - await screen.findByTestId('icon'); - - // ASSERT - expect(screen.getByTestId('icon')).toBeDefined(); - }); - - it('should render custom testId', async () => { - // ARRANGE - render(); - await screen.findByTestId('test'); - - // ASSERT - expect(screen.getByTestId('test')).toBeDefined(); - }); - - it('should render font variation settings', async () => { - // ARRANGE - const { rerender } = render(); - await screen.findByTestId('icon'); - - // ASSERT - expect(screen.getByTestId('icon')).toBeDefined(); - expect(screen.getByTestId('icon').getAttribute('style')).toBe( - "font-variation-settings: 'FILL' 1, 'wght' 400, 'GRAD' 0, 'opsz' 24;", - ); - - // ARRANGE - rerender(); - await screen.findByTestId('icon'); - - // ASSERT - expect(screen.getByTestId('icon').getAttribute('style')).toBe( - "font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;", - ); - - // ARRANGE - rerender(); - await screen.findByTestId('icon'); - - // ASSERT - expect(screen.getByTestId('icon').getAttribute('style')).toBe( - "font-variation-settings: 'FILL' 1, 'wght' 100, 'GRAD' 0, 'opsz' 24;", - ); - - // ARRANGE - rerender(); - await screen.findByTestId('icon'); - - // ASSERT - expect(screen.getByTestId('icon').getAttribute('style')).toBe( - "font-variation-settings: 'FILL' 1, 'wght' 400, 'GRAD' 100, 'opsz' 24;", - ); - - // ARRANGE - rerender(); - await screen.findByTestId('icon'); - - // ASSERT - expect(screen.getByTestId('icon').getAttribute('style')).toBe( - "font-variation-settings: 'FILL' 1, 'wght' 400, 'GRAD' 0, 'opsz' 48;", - ); - }); - - it('should use default fill value when invalid prop', async () => { - // ARRANGE - // @ts-expect-error bad fill value - render(); - await screen.findByTestId('icon'); - - // ASSERT - expect(screen.getByTestId('icon').getAttribute('style')).toBe( - "font-variation-settings: 'FILL' 1, 'wght' 400, 'GRAD' 0, 'opsz' 24;", - ); - }); - - it('should use default weight value when invalid prop', async () => { - // ARRANGE - // @ts-expect-error bad weight value - render(); - await screen.findByTestId('icon'); - - // ASSERT - expect(screen.getByTestId('icon').getAttribute('style')).toBe( - "font-variation-settings: 'FILL' 1, 'wght' 400, 'GRAD' 0, 'opsz' 24;", - ); - }); - - it('should use default grade value when invalid prop', async () => { - // ARRANGE - render(); - await screen.findByTestId('icon'); - - // ASSERT - expect(screen.getByTestId('icon').getAttribute('style')).toBe( - "font-variation-settings: 'FILL' 1, 'wght' 400, 'GRAD' 0, 'opsz' 24;", - ); - }); - - it('should use default optical size value when invalid prop', async () => { - // ARRANGE - render(); - await screen.findByTestId('icon'); - - // ASSERT - expect(screen.getByTestId('icon').getAttribute('style')).toBe( - "font-variation-settings: 'FILL' 1, 'wght' 400, 'GRAD' 0, 'opsz' 24;", - ); - }); -}); From 5d777d18db6b670eb258af73d5c4504f6341512b Mon Sep 17 00:00:00 2001 From: Matthew Warman Date: Thu, 31 Oct 2024 06:49:13 -0400 Subject: [PATCH 7/7] pr fixes --- src/components/Card/__tests__/MessageCard.test.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/Card/__tests__/MessageCard.test.tsx b/src/components/Card/__tests__/MessageCard.test.tsx index 0a66df7..498ee38 100644 --- a/src/components/Card/__tests__/MessageCard.test.tsx +++ b/src/components/Card/__tests__/MessageCard.test.tsx @@ -50,6 +50,5 @@ describe('MessageCard', () => { // ASSERT expect(screen.getByTestId('card-message')).toBeDefined(); expect(screen.getByTestId('card-message-icon')).toHaveAttribute('data-icon', 'circle-info'); - // expect(screen.getByTestId('card-message-icon').getAttribute('data-icon')).toBe('circle-info'); }); });