diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 3cffcf691..10a8052a8 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -19,7 +19,7 @@ jobs: node-version-file: '.nvmrc' cache: 'pnpm' - run: pnpm install - - run: pnpm prettier:check + - run: pnpm lint:check - run: pnpm tsc --noEmit - run: pnpm test -- --coverage --runInBand --verbose - name: Coveralls diff --git a/.gitignore b/.gitignore index 78cd442e0..d3525a4ac 100644 --- a/.gitignore +++ b/.gitignore @@ -73,4 +73,3 @@ temp/ dist/ build/ -.vscode/settings.json diff --git a/.prettierrc b/.prettierrc deleted file mode 100644 index 1fb73bc2e..000000000 --- a/.prettierrc +++ /dev/null @@ -1,4 +0,0 @@ -{ - "tabWidth": 2, - "singleQuote": true -} diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 000000000..699ed7331 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["biomejs.biome"] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..66b252e97 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "editor.codeActionsOnSave": { + "source.fixAll.eslint": "explicit", + "source.organizeImports.biome": "explicit", + "quickfix.biome": "explicit" + }, + "editor.defaultFormatter": "biomejs.biome" +} diff --git a/README.md b/README.md index f740f0ba3..0e2b34629 100644 --- a/README.md +++ b/README.md @@ -46,18 +46,18 @@ The release process is automated. Follow the steps below. ### Tests -There are 2 checks - one for prettier and one for the unit tests with `jest`. +There are 2 checks - one for biome (linter & formatter) and one for the unit tests with `jest`. -``` - // Run prettier to check - pnpm prettier:check +```shell +# Run biome to check linting and formatting +pnpm lint:check - // Run linter & unit tests with coverage - pnpm test +# Run unit tests with coverage +pnpm test - // If you want to pass arguments to jest (or other `pnpm` commands) - // like `--watch`, you can prepend `--` to the command - pnpm test -- --watch +# If you want to pass arguments to jest (or other `pnpm` commands) +# like `--watch`, you can prepend `--` to the command +pnpm test -- --watch ``` ### FAQ diff --git a/biome.json b/biome.json new file mode 100644 index 000000000..bc5a279b5 --- /dev/null +++ b/biome.json @@ -0,0 +1,36 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.6.4/schema.json", + "organizeImports": { + "enabled": true + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "correctness": { + "useExhaustiveDependencies": "warn" + } + } + }, + "vcs": { + "enabled": true, + "clientKind": "git", + "useIgnoreFile": true + }, + "formatter": { + "enabled": true, + "indentStyle": "space", + "indentWidth": 2 + }, + "javascript": { + "formatter": { + "quoteStyle": "single", + "jsxQuoteStyle": "double" + } + }, + "json": { + "parser": { + "allowComments": true + } + } +} diff --git a/package.json b/package.json index ccc771de3..b45950704 100644 --- a/package.json +++ b/package.json @@ -10,8 +10,8 @@ "make:linux": "electron-builder --linux", "make:macos": "electron-builder --mac --universal", "make:win": "electron-builder --win", - "prettier:check": "prettier --check '*.{html,js,json,md,ts,tsx}'", - "prettier:apply": "prettier --write '*.{html,js,json,md,ts,tsx}'", + "lint:check": "biome check *", + "lint": "biome check --apply *", "test": "jest", "start": "electron . --enable-logging", "prepare": "husky" @@ -50,6 +50,10 @@ { "name": "Adam Setch", "url": "https://github.com/setchy" + }, + { + "name": "Afonso Ramos", + "url": "https://github.com/afonsojramos" } ], "license": "MIT", @@ -76,9 +80,7 @@ "gatekeeperAssess": false, "entitlements": "./entitlements/entitlements.mac.plist", "entitlementsInherit": "./entitlements/entitlements.mac.plist", - "publish": [ - "github" - ], + "publish": ["github"], "extendInfo": { "NSBluetoothAlwaysUsageDescription": null, "NSBluetoothPeripheralUsageDescription": null, @@ -98,12 +100,7 @@ "oneClick": false }, "linux": { - "target": [ - "AppImage", - "deb", - "rpm", - "snap" - ], + "target": ["AppImage", "deb", "rpm", "snap"], "category": "Development", "maintainer": "Emmanouil Konstantinidis" }, @@ -127,6 +124,7 @@ "typescript": "5.4.4" }, "devDependencies": { + "@biomejs/biome": "1.6.4", "@electron/notarize": "2.3.0", "@testing-library/react": "14.2.2", "@types/jest": "29.5.12", @@ -146,7 +144,6 @@ "nock": "13.5.4", "postcss": "8.4.38", "postcss-loader": "8.1.1", - "prettier": "3.2.5", "react-test-renderer": "18.2.0", "rimraf": "5.0.5", "style-loader": "3.3.4", @@ -158,6 +155,6 @@ }, "packageManager": "pnpm@8.15.6", "lint-staged": { - "*.{html,js,json,md,ts,tsx}": "prettier --write" + "*.{html,js,json,md,ts,tsx}": "biome format --write" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e03caf77a..afef54b02 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -52,6 +52,9 @@ dependencies: version: 5.4.4 devDependencies: + '@biomejs/biome': + specifier: 1.6.4 + version: 1.6.4 '@electron/notarize': specifier: 2.3.0 version: 2.3.0 @@ -109,9 +112,6 @@ devDependencies: postcss-loader: specifier: 8.1.1 version: 8.1.1(postcss@8.4.38)(typescript@5.4.4)(webpack@5.91.0) - prettier: - specifier: 3.2.5 - version: 3.2.5 react-test-renderer: specifier: 18.2.0 version: 18.2.0(react@18.2.0) @@ -492,6 +492,94 @@ packages: resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} dev: true + /@biomejs/biome@1.6.4: + resolution: {integrity: sha512-3groVd2oWsLC0ZU+XXgHSNbq31lUcOCBkCcA7sAQGBopHcmL+jmmdoWlY3S61zIh+f2mqQTQte1g6PZKb3JJjA==} + engines: {node: '>=14.21.3'} + hasBin: true + requiresBuild: true + optionalDependencies: + '@biomejs/cli-darwin-arm64': 1.6.4 + '@biomejs/cli-darwin-x64': 1.6.4 + '@biomejs/cli-linux-arm64': 1.6.4 + '@biomejs/cli-linux-arm64-musl': 1.6.4 + '@biomejs/cli-linux-x64': 1.6.4 + '@biomejs/cli-linux-x64-musl': 1.6.4 + '@biomejs/cli-win32-arm64': 1.6.4 + '@biomejs/cli-win32-x64': 1.6.4 + dev: true + + /@biomejs/cli-darwin-arm64@1.6.4: + resolution: {integrity: sha512-2WZef8byI9NRzGajGj5RTrroW9BxtfbP9etigW1QGAtwu/6+cLkdPOWRAs7uFtaxBNiKFYA8j/BxV5zeAo5QOQ==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@biomejs/cli-darwin-x64@1.6.4: + resolution: {integrity: sha512-uo1zgM7jvzcoDpF6dbGizejDLCqNpUIRkCj/oEK0PB0NUw8re/cn1EnxuOLZqDpn+8G75COLQTOx8UQIBBN/Kg==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@biomejs/cli-linux-arm64-musl@1.6.4: + resolution: {integrity: sha512-Hp8Jwt6rjj0wCcYAEN6/cfwrrPLLlGOXZ56Lei4Pt4jy39+UuPeAVFPeclrrCfxyL1wQ2xPrhd/saTHSL6DoJg==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@biomejs/cli-linux-arm64@1.6.4: + resolution: {integrity: sha512-wAOieaMNIpLrxGc2/xNvM//CIZg7ueWy3V5A4T7gDZ3OL/Go27EKE59a+vMKsBCYmTt7jFl4yHz0TUkUbodA/w==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@biomejs/cli-linux-x64-musl@1.6.4: + resolution: {integrity: sha512-wqi0hr8KAx5kBO0B+m5u8QqiYFFBJOSJVSuRqTeGWW+GYLVUtXNidykNqf1JsW6jJDpbkSp2xHKE/bTlVaG2Kg==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@biomejs/cli-linux-x64@1.6.4: + resolution: {integrity: sha512-qTWhuIw+/ePvOkjE9Zxf5OqSCYxtAvcTJtVmZT8YQnmY2I62JKNV2m7tf6O5ViKZUOP0mOQ6NgqHKcHH1eT8jw==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@biomejs/cli-win32-arm64@1.6.4: + resolution: {integrity: sha512-Wp3FiEeF6v6C5qMfLkHwf4YsoNHr/n0efvoC8jCKO/kX05OXaVExj+1uVQ1eGT7Pvx0XVm/TLprRO0vq/V6UzA==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@biomejs/cli-win32-x64@1.6.4: + resolution: {integrity: sha512-mz183Di5hTSGP7KjNWEhivcP1wnHLGmOxEROvoFsIxMYtDhzJDad4k5gI/1JbmA0xe4n52vsgqo09tBhrMT/Zg==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + /@develar/schema-utils@2.6.5: resolution: {integrity: sha512-0cp4PsWQ/9avqTVMCtZ+GirikIA36ikvjtHweU4/j8yLtgObI0+JUPhYFScgwlteveGB1rt3Cm8UhN04XayDig==} engines: {node: '>= 8.9.0'} @@ -4562,12 +4650,6 @@ packages: source-map-js: 1.2.0 dev: true - /prettier@3.2.5: - resolution: {integrity: sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==} - engines: {node: '>=14'} - hasBin: true - dev: true - /pretty-format@27.5.1: resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} diff --git a/src/__mocks__/mock-state.ts b/src/__mocks__/mock-state.ts index bb71f3574..9624e91a1 100644 --- a/src/__mocks__/mock-state.ts +++ b/src/__mocks__/mock-state.ts @@ -1,5 +1,5 @@ -import { Theme, AuthState, SettingsState } from '../types'; -import { mockedUser, mockedEnterpriseAccounts } from './mockedData'; +import { type AuthState, type SettingsState, Theme } from '../types'; +import { mockedEnterpriseAccounts, mockedUser } from './mockedData'; export const mockAccounts: AuthState = { token: 'token-123-456', diff --git a/src/__mocks__/mockedData.ts b/src/__mocks__/mockedData.ts index 1a2c45ba6..3db3691a4 100644 --- a/src/__mocks__/mockedData.ts +++ b/src/__mocks__/mockedData.ts @@ -1,10 +1,14 @@ -import { AccountNotifications, EnterpriseAccount, GitifyUser } from '../types'; -import { - Notification, - Repository, - GraphQLSearch, +import type { + AccountNotifications, + EnterpriseAccount, + GitifyUser, +} from '../types'; +import type { Discussion, DiscussionComments, + GraphQLSearch, + Notification, + Repository, User, } from '../typesGithub'; import Constants from '../utils/constants'; @@ -65,7 +69,6 @@ export const mockedCommenterUser: User = { site_admin: false, }; -// prettier-ignore export const mockedSingleNotification: Notification = { hostname: Constants.DEFAULT_AUTH_OPTIONS.hostname, id: '138661096', @@ -242,16 +245,16 @@ export const mockedGithubNotifications = [ description: null, fork: false, // Removed the rest of the properties - }, + } as unknown as Repository, url: 'https://api.github.com/notifications/threads/148827438', subscription_url: 'https://api.github.com/notifications/threads/148827438/subscription', + hostname: Constants.DEFAULT_AUTH_OPTIONS.hostname, } as Notification, ]; // 2 Notifications // Repository : 'myorg/notifications-test' -// prettier-ignore export const mockedEnterpriseNotifications = [ { id: '4', @@ -262,7 +265,8 @@ export const mockedEnterpriseNotifications = [ subject: { title: 'Release 0.0.1', url: 'https://github.gitify.io/api/v3/repos/myorg/notifications-test/releases/1', - latest_comment_url: 'https://github.gitify.io/api/v3/repos/myorg/notifications-test/releases/1', + latest_comment_url: + 'https://github.gitify.io/api/v3/repos/myorg/notifications-test/releases/1', type: 'Release', }, repository: { @@ -277,14 +281,20 @@ export const mockedEnterpriseNotifications = [ url: 'https://github.gitify.io/api/v3/users/myorg', html_url: 'https://github.gitify.io/myorg', followers_url: 'https://github.gitify.io/api/v3/users/myorg/followers', - following_url: 'https://github.gitify.io/api/v3/users/myorg/following{/other_user}', - gists_url: 'https://github.gitify.io/api/v3/users/myorg/gists{/gist_id}', - starred_url: 'https://github.gitify.io/api/v3/users/myorg/starred{/owner}{/repo}', - subscriptions_url: 'https://github.gitify.io/api/v3/users/myorg/subscriptions', + following_url: + 'https://github.gitify.io/api/v3/users/myorg/following{/other_user}', + gists_url: + 'https://github.gitify.io/api/v3/users/myorg/gists{/gist_id}', + starred_url: + 'https://github.gitify.io/api/v3/users/myorg/starred{/owner}{/repo}', + subscriptions_url: + 'https://github.gitify.io/api/v3/users/myorg/subscriptions', organizations_url: 'https://github.gitify.io/api/v3/users/myorg/orgs', repos_url: 'https://github.gitify.io/api/v3/users/myorg/repos', - events_url: 'https://github.gitify.io/api/v3/users/myorg/events{/privacy}', - received_events_url: 'https://github.gitify.io/api/v3/users/myorg/received_events', + events_url: + 'https://github.gitify.io/api/v3/users/myorg/events{/privacy}', + received_events_url: + 'https://github.gitify.io/api/v3/users/myorg/received_events', type: 'Organization', site_admin: false, }, @@ -293,7 +303,7 @@ export const mockedEnterpriseNotifications = [ description: null, fork: false, // Removed the rest of the properties - } as Repository, + } as unknown as Repository, url: 'https://github.gitify.io/api/v3/notifications/threads/4', subscription_url: 'https://github.gitify.io/api/v3/notifications/threads/4/subscription', @@ -306,8 +316,7 @@ export const mockedEnterpriseNotifications = [ last_read_at: '2017-05-20T14:20:55Z', subject: { title: 'Bump Version', - url: - 'https://github.gitify.io/api/v3/repos/myorg/notifications-test/pulls/3', + url: 'https://github.gitify.io/api/v3/repos/myorg/notifications-test/pulls/3', latest_comment_url: 'https://github.gitify.io/api/v3/repos/myorg/notifications-test/issues/comments/21', type: 'PullRequest', @@ -346,10 +355,11 @@ export const mockedEnterpriseNotifications = [ description: null, fork: false, // Removed the rest of the properties - }, + } as unknown as Repository, url: 'https://github.gitify.io/api/v3/notifications/threads/3', subscription_url: 'https://github.gitify.io/api/v3/notifications/threads/3/subscription', + hostname: Constants.DEFAULT_AUTH_OPTIONS.hostname, } as Notification, ]; @@ -525,7 +535,7 @@ export const mockedGraphQLResponse: GraphQLSearch = { viewerSubscription: 'SUBSCRIBED', title: '1.16.0', isAnswered: false, - stateReason: null, + stateReason: 'OPEN', url: 'https://github.com/manosim/notifications-test/discussions/612', author: { login: 'discussion-creator', @@ -540,7 +550,7 @@ export const mockedGraphQLResponse: GraphQLSearch = { viewerSubscription: 'IGNORED', title: '1.16.0', isAnswered: false, - stateReason: null, + stateReason: 'ANSWERED', url: 'https://github.com/manosim/notifications-test/discussions/123', author: { login: 'discussion-creator', diff --git a/src/app.tsx b/src/app.tsx index 73155c100..9ae971b14 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -1,20 +1,20 @@ -import React, { useContext } from 'react'; +import { useContext } from 'react'; import { Navigate, - HashRouter as Router, Route, + HashRouter as Router, Routes, useLocation, } from 'react-router-dom'; -import { AppContext, AppProvider } from './context/App'; import { Loading } from './components/Loading'; -import { LoginEnterpriseRoute } from './routes/LoginEnterprise'; +import { Sidebar } from './components/Sidebar'; +import { AppContext, AppProvider } from './context/App'; import { LoginRoute } from './routes/Login'; +import { LoginEnterpriseRoute } from './routes/LoginEnterprise'; import { LoginWithToken } from './routes/LoginWithToken'; import { NotificationsRoute } from './routes/Notifications'; import { SettingsRoute } from './routes/Settings'; -import { Sidebar } from './components/Sidebar'; function RequireAuth({ children }) { const { isLoggedIn } = useContext(AppContext); diff --git a/src/components/AccountNotifications.test.tsx b/src/components/AccountNotifications.test.tsx index 088fbfc4b..5d6e47373 100644 --- a/src/components/AccountNotifications.test.tsx +++ b/src/components/AccountNotifications.test.tsx @@ -1,8 +1,7 @@ -import React from 'react'; import TestRendener from 'react-test-renderer'; -import { AccountNotifications } from './AccountNotifications'; import { mockedGithubNotifications } from '../__mocks__/mockedData'; +import { AccountNotifications } from './AccountNotifications'; jest.mock('./Repository', () => ({ RepositoryNotifications: () =>
Repository
, diff --git a/src/components/AccountNotifications.tsx b/src/components/AccountNotifications.tsx index e28cfaffc..4c94bac08 100644 --- a/src/components/AccountNotifications.tsx +++ b/src/components/AccountNotifications.tsx @@ -1,7 +1,6 @@ -import React from 'react'; import { ChevronDownIcon, ChevronLeftIcon } from '@primer/octicons-react'; -import { Notification } from '../typesGithub'; +import type { Notification } from '../typesGithub'; import { RepositoryNotifications } from './Repository'; interface IProps { diff --git a/src/components/AllRead.test.tsx b/src/components/AllRead.test.tsx index fc3e06550..8f2fe6618 100644 --- a/src/components/AllRead.test.tsx +++ b/src/components/AllRead.test.tsx @@ -1,16 +1,15 @@ -import * as React from 'react'; import * as TestRenderer from 'react-test-renderer'; import { mockMathRandom } from './test-utils'; import { AllRead } from './AllRead'; -describe('components/AllRead.tsx', function () { +describe('components/AllRead.tsx', () => { // The read emoji randomly rotates, but then the snapshots would never match // Have to make it consistent so the emojis are always the same mockMathRandom(0.1); - it('should render itself & its children', function () { + it('should render itself & its children', () => { const tree = TestRenderer.create(); expect(tree).toMatchSnapshot(); diff --git a/src/components/AllRead.tsx b/src/components/AllRead.tsx index a15488c6f..48cb20711 100644 --- a/src/components/AllRead.tsx +++ b/src/components/AllRead.tsx @@ -1,9 +1,8 @@ -import * as React from 'react'; - +import { useMemo } from 'react'; import { Constants } from '../utils/constants'; export const AllRead = () => { - const emoji = React.useMemo( + const emoji = useMemo( () => Constants.ALLREAD_EMOJIS[ Math.floor(Math.random() * Constants.ALLREAD_EMOJIS.length) diff --git a/src/components/Loading.test.tsx b/src/components/Loading.test.tsx index 5e96865f3..80a1c02f9 100644 --- a/src/components/Loading.test.tsx +++ b/src/components/Loading.test.tsx @@ -1,6 +1,5 @@ import { render } from '@testing-library/react'; import NProgress from 'nprogress'; -import React from 'react'; import { AppContext } from '../context/App'; import { Loading } from './Loading'; diff --git a/src/components/Loading.tsx b/src/components/Loading.tsx index 3ecd92caa..18c1dc152 100644 --- a/src/components/Loading.tsx +++ b/src/components/Loading.tsx @@ -1,5 +1,5 @@ -import { useContext, useEffect } from 'react'; import NProgress from 'nprogress'; +import { useContext, useEffect } from 'react'; import { AppContext } from '../context/App'; diff --git a/src/components/Logo.test.tsx b/src/components/Logo.test.tsx index 10ab08db3..97773b1b5 100644 --- a/src/components/Logo.test.tsx +++ b/src/components/Logo.test.tsx @@ -1,7 +1,7 @@ -import * as React from 'react'; -import * as TestRenderer from 'react-test-renderer'; import { fireEvent, render } from '@testing-library/react'; +import * as TestRenderer from 'react-test-renderer'; + import { Logo } from './Logo'; describe('components/Logo.tsx', () => { diff --git a/src/components/Logo.tsx b/src/components/Logo.tsx index 1193ba820..6ed6de59f 100644 --- a/src/components/Logo.tsx +++ b/src/components/Logo.tsx @@ -1,5 +1,3 @@ -import * as React from 'react'; - interface IProps { isDark?: boolean; onClick?: () => void; diff --git a/src/components/NotificationRow.test.tsx b/src/components/NotificationRow.test.tsx index 4b8014ac4..081b7b122 100644 --- a/src/components/NotificationRow.test.tsx +++ b/src/components/NotificationRow.test.tsx @@ -1,14 +1,14 @@ -import * as React from 'react'; -import * as TestRenderer from 'react-test-renderer'; import { fireEvent, render } from '@testing-library/react'; +import * as TestRenderer from 'react-test-renderer'; + import * as helpers from '../utils/helpers'; -import { AppContext } from '../context/App'; +import { shell } from 'electron'; +import { mockAccounts, mockSettings } from '../__mocks__/mock-state'; import { mockedSingleNotification } from '../__mocks__/mockedData'; +import { AppContext } from '../context/App'; import { NotificationRow } from './NotificationRow'; -import { mockAccounts, mockSettings } from '../__mocks__/mock-state'; -import { shell } from 'electron'; describe('components/NotificationRow.tsx', () => { beforeEach(() => { diff --git a/src/components/NotificationRow.tsx b/src/components/NotificationRow.tsx index eab58583c..8968245e4 100644 --- a/src/components/NotificationRow.tsx +++ b/src/components/NotificationRow.tsx @@ -1,31 +1,28 @@ -import React, { useCallback, useContext } from 'react'; -import { formatDistanceToNow, parseISO } from 'date-fns'; import { - CheckIcon, BellSlashIcon, - ReadIcon, + CheckIcon, FeedPersonIcon, + ReadIcon, } from '@primer/octicons-react'; +import { formatDistanceToNow, parseISO } from 'date-fns'; +import { type FC, type MouseEvent, useCallback, useContext } from 'react'; +import { AppContext } from '../context/App'; +import type { Notification } from '../typesGithub'; +import { openExternalLink } from '../utils/comms'; import { formatReason, getNotificationTypeIcon, getNotificationTypeIconColor, } from '../utils/github-api'; import { formatForDisplay, openInBrowser } from '../utils/helpers'; -import { Notification } from '../typesGithub'; -import { AppContext } from '../context/App'; -import { openExternalLink } from '../utils/comms'; interface IProps { hostname: string; notification: Notification; } -export const NotificationRow: React.FC = ({ - notification, - hostname, -}) => { +export const NotificationRow: FC = ({ notification, hostname }) => { const { settings, accounts, @@ -51,14 +48,14 @@ export const NotificationRow: React.FC = ({ [notification], ); - const unsubscribe = (event: React.MouseEvent) => { + const unsubscribe = (event: MouseEvent) => { // Don't trigger onClick of parent element. event.stopPropagation(); unsubscribeNotification(notification.id, hostname); }; - const openUserProfile = (event: React.MouseEvent) => { + const openUserProfile = (event: MouseEvent) => { // Don't trigger onClick of parent element. event.stopPropagation(); @@ -105,10 +102,7 @@ export const NotificationRow: React.FC = ({ {notification.subject.user ? ( - + { // The error emoji randomly rotates, but then the snapshots would never match // Have to make it consistent so the emojis are always the same mockMathRandom(0.1); - it('should render itself & its children', function () { + it('should render itself & its children', () => { const tree = TestRenderer.create(); expect(tree).toMatchSnapshot(); diff --git a/src/components/Oops.tsx b/src/components/Oops.tsx index f37e310e8..8025d5a7c 100644 --- a/src/components/Oops.tsx +++ b/src/components/Oops.tsx @@ -1,9 +1,8 @@ -import * as React from 'react'; - +import { useMemo } from 'react'; import { Constants } from '../utils/constants'; export const Oops = () => { - const emoji = React.useMemo( + const emoji = useMemo( () => Constants.ERROR_EMOJIS[ Math.floor(Math.random() * Constants.ERROR_EMOJIS.length) diff --git a/src/components/Repository.test.tsx b/src/components/Repository.test.tsx index 9e4726708..e4ec02067 100644 --- a/src/components/Repository.test.tsx +++ b/src/components/Repository.test.tsx @@ -1,9 +1,9 @@ -import React from 'react'; +import { fireEvent, render } from '@testing-library/react'; + import TestRenderer from 'react-test-renderer'; -import { render, fireEvent } from '@testing-library/react'; -import { AppContext } from '../context/App'; import { mockedGithubNotifications } from '../__mocks__/mockedData'; +import { AppContext } from '../context/App'; import { RepositoryNotifications } from './Repository'; const { shell } = require('electron'); @@ -52,7 +52,7 @@ describe('components/Repository.tsx', () => { ); }); - it('should mark a repo as read', function () { + it('should mark a repo as read', () => { const { getByTitle } = render( @@ -67,7 +67,7 @@ describe('components/Repository.tsx', () => { ); }); - it('should mark a repo as done', function () { + it('should mark a repo as done', () => { const { getByTitle } = render( diff --git a/src/components/Repository.tsx b/src/components/Repository.tsx index 933ce6a42..80847e70f 100644 --- a/src/components/Repository.tsx +++ b/src/components/Repository.tsx @@ -1,11 +1,12 @@ -import React, { useCallback, useContext } from 'react'; -import { ReadIcon, CheckIcon, MarkGithubIcon } from '@primer/octicons-react'; +import { CheckIcon, MarkGithubIcon, ReadIcon } from '@primer/octicons-react'; + +import { type FC, useCallback, useContext } from 'react'; import { CSSTransition, TransitionGroup } from 'react-transition-group'; import { AppContext } from '../context/App'; -import { Notification } from '../typesGithub'; -import { NotificationRow } from './NotificationRow'; +import type { Notification } from '../typesGithub'; import { openExternalLink } from '../utils/comms'; +import { NotificationRow } from './NotificationRow'; interface IProps { hostname: string; @@ -13,7 +14,7 @@ interface IProps { repoName: string; } -export const RepositoryNotifications: React.FC = ({ +export const RepositoryNotifications: FC = ({ repoName, repoNotifications, hostname, diff --git a/src/components/Sidebar.test.tsx b/src/components/Sidebar.test.tsx index 685b496b3..2c4d124b6 100644 --- a/src/components/Sidebar.test.tsx +++ b/src/components/Sidebar.test.tsx @@ -1,5 +1,5 @@ import { fireEvent, render, screen } from '@testing-library/react'; -import * as React from 'react'; + import { MemoryRouter } from 'react-router-dom'; import * as TestRenderer from 'react-test-renderer'; diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index a13f715ec..0b34e78bb 100644 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -5,16 +5,17 @@ import { XCircleIcon, } from '@primer/octicons-react'; import { ipcRenderer } from 'electron'; -import React, { useCallback, useContext, useMemo } from 'react'; -import { useNavigate, useLocation } from 'react-router-dom'; -import { getNotificationCount } from '../utils/notifications'; +import { type FC, useCallback, useContext, useMemo } from 'react'; +import { useLocation, useNavigate } from 'react-router-dom'; + import { Logo } from '../components/Logo'; import { AppContext } from '../context/App'; -import { Constants } from '../utils/constants'; import { openExternalLink } from '../utils/comms'; +import { Constants } from '../utils/constants'; +import { getNotificationCount } from '../utils/notifications'; -export const Sidebar: React.FC = () => { +export const Sidebar: FC = () => { const navigate = useNavigate(); const location = useLocation(); diff --git a/src/components/fields/Checkbox.tsx b/src/components/fields/Checkbox.tsx index 1197278a9..ad44d442e 100644 --- a/src/components/fields/Checkbox.tsx +++ b/src/components/fields/Checkbox.tsx @@ -1,5 +1,3 @@ -import * as React from 'react'; - interface IFieldCheckbox { name: string; label: string; diff --git a/src/components/fields/FieldInput.tsx b/src/components/fields/FieldInput.tsx index c9dcb0946..930932784 100644 --- a/src/components/fields/FieldInput.tsx +++ b/src/components/fields/FieldInput.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import type { FC, ReactNode } from 'react'; import { Field } from 'react-final-form'; export interface IProps { @@ -6,11 +6,11 @@ export interface IProps { type?: string; label: string; placeholder?: string; - helpText?: React.ReactNode | string; + helpText?: ReactNode | string; required?: boolean; } -export const FieldInput: React.FC = ({ +export const FieldInput: FC = ({ label, name, placeholder = '', diff --git a/src/components/fields/RadioGroup.test.tsx b/src/components/fields/RadioGroup.test.tsx index a3bfc825a..a94374094 100644 --- a/src/components/fields/RadioGroup.test.tsx +++ b/src/components/fields/RadioGroup.test.tsx @@ -1,7 +1,7 @@ -import * as React from 'react'; -import * as TestRendener from 'react-test-renderer'; import { fireEvent, render } from '@testing-library/react'; +import * as TestRendener from 'react-test-renderer'; + import { FieldRadioGroup } from './RadioGroup'; describe('components/fields/radiogroup.tsx', () => { @@ -22,7 +22,7 @@ describe('components/fields/radiogroup.tsx', () => { expect(tree).toMatchSnapshot(); }); - it('should check that NProgress is getting called in getDerivedStateFromProps (loading)', function () { + it('should check that NProgress is getting called in getDerivedStateFromProps (loading)', () => { const { getByLabelText } = render(); fireEvent.click(getByLabelText('Value 1')); expect(props.onChange).toHaveBeenCalledTimes(1); diff --git a/src/components/fields/RadioGroup.tsx b/src/components/fields/RadioGroup.tsx index cc21327d4..ebf5c18ec 100644 --- a/src/components/fields/RadioGroup.tsx +++ b/src/components/fields/RadioGroup.tsx @@ -1,5 +1,5 @@ -import * as React from 'react'; -import { RadioGroupItem } from '../../types'; +import type { ChangeEvent } from 'react'; +import type { RadioGroupItem } from '../../types'; export const FieldRadioGroup = ({ label, @@ -13,7 +13,7 @@ export const FieldRadioGroup = ({ label: string; placeholder?: string; options: RadioGroupItem[]; - onChange: (event: React.ChangeEvent) => void; + onChange: (event: ChangeEvent) => void; value: string; }) => { return ( diff --git a/src/context/App.test.tsx b/src/context/App.test.tsx index 76ff5c25c..cbb3e745c 100644 --- a/src/context/App.test.tsx +++ b/src/context/App.test.tsx @@ -1,14 +1,14 @@ -import React, { useContext } from 'react'; import { act, fireEvent, render, waitFor } from '@testing-library/react'; +import { useContext } from 'react'; -import { AppContext, AppProvider } from './App'; -import { AuthState, SettingsState } from '../types'; import { mockAccounts, mockSettings } from '../__mocks__/mock-state'; import { useNotifications } from '../hooks/useNotifications'; +import type { AuthState, SettingsState } from '../types'; import * as apiRequests from '../utils/api-requests'; import * as comms from '../utils/comms'; -import * as storage from '../utils/storage'; import * as notifications from '../utils/notifications'; +import * as storage from '../utils/storage'; +import { AppContext, AppProvider } from './App'; jest.mock('../hooks/useNotifications'); @@ -92,7 +92,11 @@ describe('context/App.tsx', () => { const TestComponent = () => { const { fetchNotifications } = useContext(AppContext); - return ; + return ( + + ); }; const { getByText } = customRender(); @@ -109,7 +113,10 @@ describe('context/App.tsx', () => { const { markNotificationRead } = useContext(AppContext); return ( - ); @@ -134,7 +141,10 @@ describe('context/App.tsx', () => { const { markNotificationDone } = useContext(AppContext); return ( - ); @@ -160,6 +170,7 @@ describe('context/App.tsx', () => { return ( ; + return ( + + ); }; const { getByText } = customRender(); @@ -276,7 +293,10 @@ describe('context/App.tsx', () => { const { updateSetting } = useContext(AppContext); return ( - ); @@ -315,7 +335,10 @@ describe('context/App.tsx', () => { const { updateSetting } = useContext(AppContext); return ( - ); diff --git a/src/context/App.tsx b/src/context/App.tsx index 9b785fb08..6d9d5c813 100644 --- a/src/context/App.tsx +++ b/src/context/App.tsx @@ -1,4 +1,5 @@ -import React, { +import { + type ReactNode, createContext, useCallback, useEffect, @@ -9,21 +10,21 @@ import React, { import { useInterval } from '../hooks/useInterval'; import { useNotifications } from '../hooks/useNotifications'; import { - AccountNotifications, + type AccountNotifications, + type AuthOptions, + type AuthState, + type AuthTokenOptions, + type SettingsState, Theme, - AuthOptions, - AuthState, - AuthTokenOptions, - SettingsState, } from '../types'; import { apiRequestAuth } from '../utils/api-requests'; -import { setTheme } from '../utils/theme'; import { addAccount, authGitHub, getToken, getUserData } from '../utils/auth'; import { setAutoLaunch, updateTrayTitle } from '../utils/comms'; import Constants from '../utils/constants'; import { generateGitHubAPIUrl } from '../utils/helpers'; -import { clearState, loadState, saveState } from '../utils/storage'; import { getNotificationCount } from '../utils/notifications'; +import { clearState, loadState, saveState } from '../utils/storage'; +import { setTheme } from '../utils/theme'; const defaultAccounts: AuthState = { token: null, @@ -69,7 +70,7 @@ interface AppContextState { export const AppContext = createContext>({}); -export const AppProvider = ({ children }: { children: React.ReactNode }) => { +export const AppProvider = ({ children }: { children: ReactNode }) => { const [accounts, setAccounts] = useState(defaultAccounts); const [settings, setSettings] = useState(defaultSettings); const { diff --git a/src/hooks/useInterval.ts b/src/hooks/useInterval.ts index c4d7db5ea..cfda92fc7 100644 --- a/src/hooks/useInterval.ts +++ b/src/hooks/useInterval.ts @@ -18,7 +18,7 @@ export const useInterval = (callback, delay) => { } if (delay !== null) { - let id = setInterval(tick, delay); + const id = setInterval(tick, delay); return () => clearInterval(id); } }, [delay]); diff --git a/src/hooks/useNotifications.test.ts b/src/hooks/useNotifications.test.ts index 5fe63c040..43196bd03 100644 --- a/src/hooks/useNotifications.test.ts +++ b/src/hooks/useNotifications.test.ts @@ -4,7 +4,7 @@ import nock from 'nock'; import { mockAccounts, mockSettings } from '../__mocks__/mock-state'; import { mockedNotificationUser, mockedUser } from '../__mocks__/mockedData'; -import { AuthState } from '../types'; +import type { AuthState } from '../types'; import { useNotifications } from './useNotifications'; describe('hooks/useNotifications.ts', () => { diff --git a/src/hooks/useNotifications.ts b/src/hooks/useNotifications.ts index d90bb7699..a6a7cbd57 100644 --- a/src/hooks/useNotifications.ts +++ b/src/hooks/useNotifications.ts @@ -1,21 +1,21 @@ import axios from 'axios'; import { useCallback, useState } from 'react'; -import { AccountNotifications, AuthState, SettingsState } from '../types'; -import { Notification } from '../typesGithub'; +import type { AccountNotifications, AuthState, SettingsState } from '../types'; +import type { Notification } from '../typesGithub'; import { apiRequestAuth } from '../utils/api-requests'; +import Constants from '../utils/constants'; import { - getEnterpriseAccountToken, generateGitHubAPIUrl, - isEnterpriseHost, + getEnterpriseAccountToken, getTokenForHost, + isEnterpriseHost, } from '../utils/helpers'; -import { removeNotification } from '../utils/remove-notification'; import { - triggerNativeNotifications, setTrayIconColor, + triggerNativeNotifications, } from '../utils/notifications'; -import Constants from '../utils/constants'; +import { removeNotification } from '../utils/remove-notification'; import { removeNotifications } from '../utils/remove-notifications'; import { getGitifySubjectDetails } from '../utils/subject'; diff --git a/src/index.tsx b/src/index.tsx index 1af47c973..7e00ed36a 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,8 +1,7 @@ import { createRoot } from 'react-dom/client'; -import * as React from 'react'; -import 'tailwindcss/tailwind.css'; import 'nprogress/nprogress.css'; +import 'tailwindcss/tailwind.css'; import { App } from './app'; diff --git a/src/routes/Login.test.tsx b/src/routes/Login.test.tsx index 77419ade4..4fc016142 100644 --- a/src/routes/Login.test.tsx +++ b/src/routes/Login.test.tsx @@ -1,7 +1,7 @@ -import React from 'react'; -import TestRenderer from 'react-test-renderer'; +import { fireEvent, render } from '@testing-library/react'; + import { MemoryRouter } from 'react-router-dom'; -import { render, fireEvent } from '@testing-library/react'; +import TestRenderer from 'react-test-renderer'; const { ipcRenderer } = require('electron'); diff --git a/src/routes/Login.tsx b/src/routes/Login.tsx index 0c8d27425..acd373d2f 100644 --- a/src/routes/Login.tsx +++ b/src/routes/Login.tsx @@ -1,12 +1,12 @@ const { ipcRenderer } = require('electron'); -import React, { useContext, useEffect } from 'react'; +import { type FC, useContext, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; -import { AppContext } from '../context/App'; import { Logo } from '../components/Logo'; +import { AppContext } from '../context/App'; -export const LoginRoute: React.FC = () => { +export const LoginRoute: FC = () => { const navigate = useNavigate(); const { isLoggedIn } = useContext(AppContext); diff --git a/src/routes/LoginEnterprise.test.tsx b/src/routes/LoginEnterprise.test.tsx index 64c69f7d5..e608848d3 100644 --- a/src/routes/LoginEnterprise.test.tsx +++ b/src/routes/LoginEnterprise.test.tsx @@ -1,14 +1,14 @@ -import * as React from 'react'; -import * as TestRenderer from 'react-test-renderer'; import { fireEvent, render } from '@testing-library/react'; + import { MemoryRouter } from 'react-router-dom'; +import * as TestRenderer from 'react-test-renderer'; const { ipcRenderer } = require('electron'); +import { mockedEnterpriseAccounts } from '../__mocks__/mockedData'; import { AppContext } from '../context/App'; -import { AuthState } from '../types'; +import type { AuthState } from '../types'; import { LoginEnterpriseRoute, validate } from './LoginEnterprise'; -import { mockedEnterpriseAccounts } from '../__mocks__/mockedData'; const mockNavigate = jest.fn(); jest.mock('react-router-dom', () => ({ @@ -22,7 +22,7 @@ describe('routes/LoginEnterprise.tsx', () => { user: null, }; - beforeEach(function () { + beforeEach(() => { mockNavigate.mockReset(); jest.spyOn(ipcRenderer, 'send'); diff --git a/src/routes/LoginEnterprise.tsx b/src/routes/LoginEnterprise.tsx index af7bd5615..30971256b 100644 --- a/src/routes/LoginEnterprise.tsx +++ b/src/routes/LoginEnterprise.tsx @@ -1,13 +1,14 @@ const ipcRenderer = require('electron').ipcRenderer; -import React, { useCallback, useContext, useEffect } from 'react'; -import { Form, FormRenderProps } from 'react-final-form'; import { ArrowLeftIcon } from '@primer/octicons-react'; + +import { type FC, useCallback, useContext, useEffect } from 'react'; +import { Form, type FormRenderProps } from 'react-final-form'; import { useNavigate } from 'react-router-dom'; -import { AppContext } from '../context/App'; import { FieldInput } from '../components/fields/FieldInput'; -import { AuthOptions } from '../types'; +import { AppContext } from '../context/App'; +import type { AuthOptions } from '../types'; interface IValues { hostname?: string; @@ -50,7 +51,7 @@ export const validate = (values: IValues): IFormErrors => { return errors; }; -export const LoginEnterpriseRoute: React.FC = () => { +export const LoginEnterpriseRoute: FC = () => { const { accounts: { enterpriseAccounts }, loginEnterprise, diff --git a/src/routes/LoginWithToken.test.tsx b/src/routes/LoginWithToken.test.tsx index 1e34d288c..f3a3d4b14 100644 --- a/src/routes/LoginWithToken.test.tsx +++ b/src/routes/LoginWithToken.test.tsx @@ -1,9 +1,9 @@ -import React from 'react'; -import TestRenderer from 'react-test-renderer'; import { act, fireEvent, render, waitFor } from '@testing-library/react'; -import { MemoryRouter } from 'react-router-dom'; import { shell } from 'electron'; +import { MemoryRouter } from 'react-router-dom'; +import TestRenderer from 'react-test-renderer'; + import { AppContext } from '../context/App'; import { LoginWithToken, validate } from './LoginWithToken'; @@ -18,7 +18,7 @@ describe('routes/LoginWithToken.tsx', () => { const mockValidateToken = jest.fn(); - beforeEach(function () { + beforeEach(() => { mockValidateToken.mockReset(); openExternalMock.mockReset(); mockNavigate.mockReset(); diff --git a/src/routes/LoginWithToken.tsx b/src/routes/LoginWithToken.tsx index 6103b9632..91f45db2f 100644 --- a/src/routes/LoginWithToken.tsx +++ b/src/routes/LoginWithToken.tsx @@ -1,13 +1,14 @@ -import React, { useCallback, useContext, useState } from 'react'; -import { Form, FormRenderProps } from 'react-final-form'; import { ArrowLeftIcon } from '@primer/octicons-react'; + +import { type FC, useCallback, useContext, useState } from 'react'; +import { Form, type FormRenderProps } from 'react-final-form'; import { useNavigate } from 'react-router-dom'; -import { AppContext } from '../context/App'; -import { AuthTokenOptions } from '../types'; -import { Constants } from '../utils/constants'; import { FieldInput } from '../components/fields/FieldInput'; +import { AppContext } from '../context/App'; +import type { AuthTokenOptions } from '../types'; import { openExternalLink } from '../utils/comms'; +import { Constants } from '../utils/constants'; interface IValues { token?: string; @@ -40,7 +41,7 @@ export const validate = (values: IValues): IFormErrors => { return errors; }; -export const LoginWithToken: React.FC = () => { +export const LoginWithToken: FC = () => { const { validateToken } = useContext(AppContext); const navigate = useNavigate(); const [isValidToken, setIsValidToken] = useState(true); diff --git a/src/routes/Notifications.test.tsx b/src/routes/Notifications.test.tsx index 2ad5db7e1..550570203 100644 --- a/src/routes/Notifications.test.tsx +++ b/src/routes/Notifications.test.tsx @@ -1,10 +1,9 @@ -import React from 'react'; import TestRenderer from 'react-test-renderer'; -import { AppContext } from '../context/App'; +import { mockSettings } from '../__mocks__/mock-state'; import { mockedAccountNotifications } from '../__mocks__/mockedData'; +import { AppContext } from '../context/App'; import { NotificationsRoute } from './Notifications'; -import { mockSettings } from '../__mocks__/mock-state'; jest.mock('../components/AccountNotifications', () => ({ AccountNotifications: 'AccountNotifications', diff --git a/src/routes/Notifications.tsx b/src/routes/Notifications.tsx index 75b794ca4..cafde45e5 100644 --- a/src/routes/Notifications.tsx +++ b/src/routes/Notifications.tsx @@ -1,12 +1,12 @@ -import React, { useContext, useMemo } from 'react'; +import { type FC, useContext, useMemo } from 'react'; import { AccountNotifications } from '../components/AccountNotifications'; import { AllRead } from '../components/AllRead'; -import { AppContext } from '../context/App'; import { Oops } from '../components/Oops'; +import { AppContext } from '../context/App'; import { getNotificationCount } from '../utils/notifications'; -export const NotificationsRoute: React.FC = (props) => { +export const NotificationsRoute: FC = () => { const { notifications, requestFailed, settings } = useContext(AppContext); const hasMultipleAccounts = useMemo( diff --git a/src/routes/Settings.test.tsx b/src/routes/Settings.test.tsx index 8011b6081..9ff7d8465 100644 --- a/src/routes/Settings.test.tsx +++ b/src/routes/Settings.test.tsx @@ -1,16 +1,16 @@ import { act, fireEvent, render, screen } from '@testing-library/react'; -import React from 'react'; + import { MemoryRouter } from 'react-router-dom'; const { ipcRenderer } = require('electron'); -import { AxiosResponse } from 'axios'; +import type { AxiosResponse } from 'axios'; +import { shell } from 'electron'; import { mockAccounts, mockSettings } from '../__mocks__/mock-state'; import { AppContext } from '../context/App'; import * as apiRequests from '../utils/api-requests'; import Constants from '../utils/constants'; import { SettingsRoute } from './Settings'; -import { shell } from 'electron'; const mockNavigate = jest.fn(); jest.mock('react-router-dom', () => ({ @@ -47,10 +47,10 @@ describe('routes/Settings.tsx', () => { it('should press the logout', async () => { const logoutMock = jest.fn(); - let getByRole; + let getByTitle; await act(async () => { - const { getByRole: getByRoleLocal } = render( + const { getByTitle: getByTitleLocal } = render( { , ); - getByRole = getByRoleLocal; + getByTitle = getByTitleLocal; }); - fireEvent.click(getByRole('Logout')); + fireEvent.click(getByTitle('Logout octocat')); expect(logoutMock).toHaveBeenCalledTimes(1); diff --git a/src/routes/Settings.tsx b/src/routes/Settings.tsx index 65845df52..684785795 100644 --- a/src/routes/Settings.tsx +++ b/src/routes/Settings.tsx @@ -5,7 +5,9 @@ import { XCircleIcon, } from '@primer/octicons-react'; import { ipcRenderer } from 'electron'; -import React, { + +import { + type FC, useCallback, useContext, useEffect, @@ -19,7 +21,6 @@ import { FieldRadioGroup } from '../components/fields/RadioGroup'; import { AppContext } from '../context/App'; import { Theme } from '../types'; import { apiRequestAuth } from '../utils/api-requests'; -import { setTheme } from '../utils/theme'; import { openExternalLink, updateTrayIcon, @@ -27,8 +28,9 @@ import { } from '../utils/comms'; import Constants from '../utils/constants'; import { generateGitHubAPIUrl } from '../utils/helpers'; +import { setTheme } from '../utils/theme'; -export const SettingsRoute: React.FC = () => { +export const SettingsRoute: FC = () => { const { accounts, settings, updateSetting, logout } = useContext(AppContext); const navigate = useNavigate(); @@ -239,7 +241,7 @@ export const SettingsRoute: React.FC = () => {