diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index c4b40dcd0..dec286e3b 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -38,6 +38,10 @@ body: label: Gitify Version description: What version of Gitify are you using? options: + - 5.12.1 + - 5.12.0 + - 5.11.0 + - 5.10.0 - 5.9.0 - 5.8.1 - 5.8.0 @@ -49,7 +53,6 @@ body: - 5.2.0 - 5.1.0 - 5.0.0 - - 4.6.1 - Other validations: required: true diff --git a/assets/images/tray-active-update.png b/assets/images/tray-active-update.png new file mode 100644 index 000000000..bfe8cf141 Binary files /dev/null and b/assets/images/tray-active-update.png differ diff --git a/assets/images/tray-active-update@2x.png b/assets/images/tray-active-update@2x.png new file mode 100644 index 000000000..97329b1c7 Binary files /dev/null and b/assets/images/tray-active-update@2x.png differ diff --git a/assets/images/tray-idle-update.png b/assets/images/tray-idle-update.png new file mode 100644 index 000000000..cc7a382f8 Binary files /dev/null and b/assets/images/tray-idle-update.png differ diff --git a/assets/images/tray-idle-update@2x.png b/assets/images/tray-idle-update@2x.png new file mode 100644 index 000000000..92d2bb29f Binary files /dev/null and b/assets/images/tray-idle-update@2x.png differ diff --git a/package.json b/package.json index 550e51025..612d23a9c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gitify", - "version": "5.12.0", + "version": "5.12.1", "description": "GitHub Notifications on your menu bar.", "main": "src/electron/main.js", "scripts": { @@ -117,7 +117,8 @@ "react-router-dom": "6.26.0", "tailwind-merge": "2.4.0", "ts-loader": "9.5.1", - "typescript": "5.5.4" + "typescript": "5.5.4", + "update-electron-app": "3.0.0" }, "devDependencies": { "@biomejs/biome": "1.8.3", @@ -137,7 +138,7 @@ "jest": "29.7.0", "jest-environment-jsdom": "29.7.0", "nock": "13.5.4", - "postcss": "8.4.40", + "postcss": "8.4.41", "postcss-loader": "8.1.1", "resize-observer-polyfill": "1.5.1", "rimraf": "6.0.1", @@ -149,7 +150,7 @@ "webpack-cli": "5.1.4", "webpack-merge": "6.0.1" }, - "packageManager": "pnpm@9.6.0", + "packageManager": "pnpm@9.7.0", "lint-staged": { "*": "biome check --fix --no-errors-on-unmatched", "*.{js,ts,tsx}": "pnpm test -- --findRelatedTests -u --passWithNoTests" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 613abc2a6..41a7ae218 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -62,6 +62,9 @@ importers: typescript: specifier: 5.5.4 version: 5.5.4 + update-electron-app: + specifier: 3.0.0 + version: 3.0.0 devDependencies: '@biomejs/biome': specifier: 1.8.3 @@ -89,7 +92,7 @@ importers: version: 5.3.3 autoprefixer: specifier: 10.4.20 - version: 10.4.20(postcss@8.4.40) + version: 10.4.20(postcss@8.4.41) css-loader: specifier: 7.1.2 version: 7.1.2(webpack@5.93.0(webpack-cli@5.1.4)) @@ -115,11 +118,11 @@ importers: specifier: 13.5.4 version: 13.5.4 postcss: - specifier: 8.4.40 - version: 8.4.40 + specifier: 8.4.41 + version: 8.4.41 postcss-loader: specifier: 8.1.1 - version: 8.1.1(postcss@8.4.40)(typescript@5.5.4)(webpack@5.93.0(webpack-cli@5.1.4)) + version: 8.1.1(postcss@8.4.41)(typescript@5.5.4)(webpack@5.93.0(webpack-cli@5.1.4)) resize-observer-polyfill: specifier: 1.5.1 version: 1.5.1 @@ -1649,6 +1652,9 @@ packages: resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} engines: {node: '>=10'} + github-url-to-object@4.0.6: + resolution: {integrity: sha512-NaqbYHMUAlPcmWFdrAB7bcxrNIiiJWJe8s/2+iOc9vlcHlwHqSGrPk+Yi3nu6ebTwgsZEa7igz+NH2vEq3gYwQ==} + glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -1851,6 +1857,9 @@ packages: resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} engines: {node: '>=8'} + is-url@1.2.4: + resolution: {integrity: sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==} + isarray@1.0.0: resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} @@ -2520,8 +2529,8 @@ packages: postcss-value-parser@4.2.0: resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} - postcss@8.4.40: - resolution: {integrity: sha512-YF2kKIUzAofPMpfH6hOi2cGnv/HrUlfucspc7pDyvv7kGdqXrfj8SCl/t8owkEgKEuu8ZcRjSOxFxVLqwChZ2Q==} + postcss@8.4.41: + resolution: {integrity: sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==} engines: {node: ^10 || ^12 || >=14} pretty-format@27.5.1: @@ -3067,6 +3076,9 @@ packages: peerDependencies: browserslist: '>= 4.21.0' + update-electron-app@3.0.0: + resolution: {integrity: sha512-Ccs46fgUEcMpSRPMNw82DFMux2MGi5tkKkEpV723JmtPNI3qAtxvTeiYkKczN2/LehA3U7JGrGr4MhraxGdRTw==} + uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} @@ -4253,14 +4265,14 @@ snapshots: at-least-node@1.0.0: {} - autoprefixer@10.4.20(postcss@8.4.40): + autoprefixer@10.4.20(postcss@8.4.41): dependencies: browserslist: 4.23.3 caniuse-lite: 1.0.30001647 fraction.js: 4.3.7 normalize-range: 0.1.2 picocolors: 1.0.1 - postcss: 8.4.40 + postcss: 8.4.41 postcss-value-parser: 4.2.0 axios@1.7.3: @@ -4613,12 +4625,12 @@ snapshots: css-loader@7.1.2(webpack@5.93.0(webpack-cli@5.1.4)): dependencies: - icss-utils: 5.1.0(postcss@8.4.40) - postcss: 8.4.40 - postcss-modules-extract-imports: 3.1.0(postcss@8.4.40) - postcss-modules-local-by-default: 4.0.5(postcss@8.4.40) - postcss-modules-scope: 3.2.0(postcss@8.4.40) - postcss-modules-values: 4.0.0(postcss@8.4.40) + icss-utils: 5.1.0(postcss@8.4.41) + postcss: 8.4.41 + postcss-modules-extract-imports: 3.1.0(postcss@8.4.41) + postcss-modules-local-by-default: 4.0.5(postcss@8.4.41) + postcss-modules-scope: 3.2.0(postcss@8.4.41) + postcss-modules-values: 4.0.0(postcss@8.4.41) postcss-value-parser: 4.2.0 semver: 7.6.0 optionalDependencies: @@ -5039,6 +5051,10 @@ snapshots: get-stream@6.0.1: {} + github-url-to-object@4.0.6: + dependencies: + is-url: 1.2.4 + glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -5185,9 +5201,9 @@ snapshots: dependencies: safer-buffer: 2.1.2 - icss-utils@5.1.0(postcss@8.4.40): + icss-utils@5.1.0(postcss@8.4.41): dependencies: - postcss: 8.4.40 + postcss: 8.4.41 ieee754@1.2.1: {} @@ -5246,6 +5262,8 @@ snapshots: is-stream@2.0.1: {} + is-url@1.2.4: {} + isarray@1.0.0: {} isbinaryfile@4.0.10: {} @@ -5987,61 +6005,61 @@ snapshots: base64-js: 1.5.1 xmlbuilder: 15.1.1 - postcss-import@15.1.0(postcss@8.4.40): + postcss-import@15.1.0(postcss@8.4.41): dependencies: - postcss: 8.4.40 + postcss: 8.4.41 postcss-value-parser: 4.2.0 read-cache: 1.0.0 resolve: 1.22.8 - postcss-js@4.0.1(postcss@8.4.40): + postcss-js@4.0.1(postcss@8.4.41): dependencies: camelcase-css: 2.0.1 - postcss: 8.4.40 + postcss: 8.4.41 - postcss-load-config@4.0.2(postcss@8.4.40)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4)): + postcss-load-config@4.0.2(postcss@8.4.41)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4)): dependencies: lilconfig: 3.1.1 yaml: 2.4.1 optionalDependencies: - postcss: 8.4.40 + postcss: 8.4.41 ts-node: 10.9.2(@types/node@20.14.14)(typescript@5.5.4) - postcss-loader@8.1.1(postcss@8.4.40)(typescript@5.5.4)(webpack@5.93.0(webpack-cli@5.1.4)): + postcss-loader@8.1.1(postcss@8.4.41)(typescript@5.5.4)(webpack@5.93.0(webpack-cli@5.1.4)): dependencies: cosmiconfig: 9.0.0(typescript@5.5.4) jiti: 1.21.0 - postcss: 8.4.40 + postcss: 8.4.41 semver: 7.6.0 optionalDependencies: webpack: 5.93.0(webpack-cli@5.1.4) transitivePeerDependencies: - typescript - postcss-modules-extract-imports@3.1.0(postcss@8.4.40): + postcss-modules-extract-imports@3.1.0(postcss@8.4.41): dependencies: - postcss: 8.4.40 + postcss: 8.4.41 - postcss-modules-local-by-default@4.0.5(postcss@8.4.40): + postcss-modules-local-by-default@4.0.5(postcss@8.4.41): dependencies: - icss-utils: 5.1.0(postcss@8.4.40) - postcss: 8.4.40 + icss-utils: 5.1.0(postcss@8.4.41) + postcss: 8.4.41 postcss-selector-parser: 6.0.16 postcss-value-parser: 4.2.0 - postcss-modules-scope@3.2.0(postcss@8.4.40): + postcss-modules-scope@3.2.0(postcss@8.4.41): dependencies: - postcss: 8.4.40 + postcss: 8.4.41 postcss-selector-parser: 6.0.16 - postcss-modules-values@4.0.0(postcss@8.4.40): + postcss-modules-values@4.0.0(postcss@8.4.41): dependencies: - icss-utils: 5.1.0(postcss@8.4.40) - postcss: 8.4.40 + icss-utils: 5.1.0(postcss@8.4.41) + postcss: 8.4.41 - postcss-nested@6.0.1(postcss@8.4.40): + postcss-nested@6.0.1(postcss@8.4.41): dependencies: - postcss: 8.4.40 + postcss: 8.4.41 postcss-selector-parser: 6.0.16 postcss-selector-parser@6.0.16: @@ -6051,7 +6069,7 @@ snapshots: postcss-value-parser@4.2.0: {} - postcss@8.4.40: + postcss@8.4.41: dependencies: nanoid: 3.3.7 picocolors: 1.0.1 @@ -6430,11 +6448,11 @@ snapshots: normalize-path: 3.0.0 object-hash: 3.0.0 picocolors: 1.0.1 - postcss: 8.4.40 - postcss-import: 15.1.0(postcss@8.4.40) - postcss-js: 4.0.1(postcss@8.4.40) - postcss-load-config: 4.0.2(postcss@8.4.40)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4)) - postcss-nested: 6.0.1(postcss@8.4.40) + postcss: 8.4.41 + postcss-import: 15.1.0(postcss@8.4.41) + postcss-js: 4.0.1(postcss@8.4.41) + postcss-load-config: 4.0.2(postcss@8.4.41)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4)) + postcss-nested: 6.0.1(postcss@8.4.41) postcss-selector-parser: 6.0.16 resolve: 1.22.8 sucrase: 3.35.0 @@ -6606,6 +6624,12 @@ snapshots: escalade: 3.1.2 picocolors: 1.0.1 + update-electron-app@3.0.0: + dependencies: + github-url-to-object: 4.0.6 + is-url: 1.2.4 + ms: 2.1.2 + uri-js@4.4.1: dependencies: punycode: 2.3.1 diff --git a/src/components/settings/SettingsFooter.test.tsx b/src/components/settings/SettingsFooter.test.tsx index 5e0b23c0b..365593422 100644 --- a/src/components/settings/SettingsFooter.test.tsx +++ b/src/components/settings/SettingsFooter.test.tsx @@ -76,48 +76,6 @@ describe('routes/components/settings/SettingsFooter.tsx', () => { }); }); - describe('update available visual indicator', () => { - it('using latest version', async () => { - await act(async () => { - render( - - - - - , - ); - }); - - expect( - screen.getByTitle('You are using the latest version'), - ).toMatchSnapshot(); - }); - - it('new version available', async () => { - await act(async () => { - render( - - - - - , - ); - }); - - expect(screen.getByTitle('New version available')).toMatchSnapshot(); - }); - }); - it('should open release notes', async () => { process.env = { ...originalEnv, diff --git a/src/components/settings/SettingsFooter.tsx b/src/components/settings/SettingsFooter.tsx index e53ca02ec..a5fd609a4 100644 --- a/src/components/settings/SettingsFooter.tsx +++ b/src/components/settings/SettingsFooter.tsx @@ -1,23 +1,12 @@ -import { - AlertFillIcon, - CheckCircleFillIcon, - PersonIcon, - XCircleIcon, -} from '@primer/octicons-react'; +import { PersonIcon, XCircleIcon } from '@primer/octicons-react'; import { type FC, useEffect, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { BUTTON_CLASS_NAME } from '../../styles/gitify'; -import { IconColor, Size } from '../../types'; +import { Size } from '../../types'; import { getAppVersion, quitApp } from '../../utils/comms'; import { openGitifyReleaseNotes } from '../../utils/links'; -interface ISettingsFooter { - isUpdateAvailable?: boolean; -} - -export const SettingsFooter: FC = ({ - isUpdateAvailable = false, -}: ISettingsFooter) => { +export const SettingsFooter: FC = () => { const [appVersion, setAppVersion] = useState(null); const navigate = useNavigate(); @@ -42,23 +31,6 @@ export const SettingsFooter: FC = ({ >
Gitify {appVersion} - - {isUpdateAvailable ? ( - - - - ) : ( - - - - )} -
diff --git a/src/components/settings/__snapshots__/SettingsFooter.test.tsx.snap b/src/components/settings/__snapshots__/SettingsFooter.test.tsx.snap index c06b29078..eaef3dec9 100644 --- a/src/components/settings/__snapshots__/SettingsFooter.test.tsx.snap +++ b/src/components/settings/__snapshots__/SettingsFooter.test.tsx.snap @@ -17,45 +17,3 @@ exports[`routes/components/settings/SettingsFooter.tsx app version should show p v0.0.1 `; - -exports[`routes/components/settings/SettingsFooter.tsx update available visual indicator new version available 1`] = ` - - - -`; - -exports[`routes/components/settings/SettingsFooter.tsx update available visual indicator using latest version 1`] = ` - - - -`; diff --git a/src/context/App.tsx b/src/context/App.tsx index 67857be5e..77f7167e9 100644 --- a/src/context/App.tsx +++ b/src/context/App.tsx @@ -46,7 +46,7 @@ import { clearState, loadState, saveState } from '../utils/storage'; import { setTheme } from '../utils/theme'; import { zoomPercentageToLevel } from '../utils/zoom'; -const defaultAuth: AuthState = { +export const defaultAuth: AuthState = { accounts: [], token: null, enterpriseAccounts: [], @@ -164,8 +164,9 @@ export const AppProvider = ({ children }: { children: ReactNode }) => { useEffect(() => { ipcRenderer.on('gitify:reset-app', () => { - setAuth(defaultAuth); clearState(); + setAuth(defaultAuth); + setSettings(defaultSettings); }); }, []); diff --git a/src/electron/main.js b/src/electron/main.js index b8d6f853f..34d0b975e 100644 --- a/src/electron/main.js +++ b/src/electron/main.js @@ -5,27 +5,35 @@ const { globalShortcut, Menu, dialog, + MenuItem, } = require('electron/main'); const { menubar } = require('menubar'); -const { autoUpdater } = require('electron-updater'); const { onFirstRunMaybe } = require('./first-run'); const path = require('node:path'); const log = require('electron-log'); const fs = require('node:fs'); const os = require('node:os'); +const { autoUpdater } = require('electron-updater'); +const { updateElectronApp } = require('update-electron-app'); log.initialize(); -autoUpdater.logger = log; // TODO: Remove @electron/remote use - see #650 require('@electron/remote/main').initialize(); +// Tray Icons const idleIcon = path.resolve( `${__dirname}/../../assets/images/tray-idleTemplate.png`, ); +const idleUpdateAvailableIcon = path.resolve( + `${__dirname}/../../assets/images/tray-idle-update.png`, +); const activeIcon = path.resolve( `${__dirname}/../../assets/images/tray-active.png`, ); +const activeUpdateAvailableIcon = path.resolve( + `${__dirname}/../../assets/images/tray-active-update.png`, +); const browserWindowOpts = { width: 500, @@ -40,29 +48,32 @@ const browserWindowOpts = { }, }; -let isUpdateAvailable = false; -let isUpdateDownloaded = false; - -const contextMenu = Menu.buildFromTemplate([ - { - label: 'Check for updates', - visible: !isUpdateAvailable, - click: () => { - checkForUpdates(); - }, - }, - { - label: 'An update is available', - enabled: false, - visible: isUpdateAvailable, +const checkForUpdatesMenuItem = new MenuItem({ + label: 'Check for updates', + enabled: true, + click: () => { + autoUpdater.checkForUpdatesAndNotify(); }, - { - label: 'Restart to update', - visible: isUpdateDownloaded, - click: () => { - autoUpdater.quitAndInstall(); - }, +}); + +const updateAvailableMenuItem = new MenuItem({ + label: 'An update is available', + enabled: false, + visible: false, +}); + +const updateReadyForInstallMenuItem = new MenuItem({ + label: 'Restart to update', + visible: false, + click: () => { + autoUpdater.quitAndInstall(); }, +}); + +const contextMenu = Menu.buildFromTemplate([ + checkForUpdatesMenuItem, + updateAvailableMenuItem, + updateReadyForInstallMenuItem, { type: 'separator' }, { label: 'Developer', @@ -142,27 +153,6 @@ app.whenReady().then(async () => { mb.positioner.move('trayCenter', trayBounds); mb.window.resizable = false; }); - - // Auto Updater - checkForUpdates(); - setInterval(checkForUpdates, 24 * 60 * 60 * 1000); // 24 hours - - autoUpdater.on('update-available', () => { - log.info('Auto Updater: New update available'); - isUpdateAvailable = true; - mb.window.webContents.send('gitify:auto-updater', isUpdateAvailable); - }); - - autoUpdater.on('update-not-available', () => { - log.info('Auto Updater: Already on the latest version'); - isUpdateAvailable = false; - mb.window.webContents.send('gitify:auto-updater', isUpdateAvailable); - }); - - autoUpdater.on('update-downloaded', () => { - log.info('Auto Updater: Update downloaded'); - isUpdateDownloaded = true; - }); }); nativeTheme.on('updated', () => { @@ -186,19 +176,25 @@ app.whenReady().then(async () => { ipc.on('gitify:icon-active', () => { if (!mb.tray.isDestroyed()) { - mb.tray.setImage(activeIcon); + mb.tray.setImage( + updateAvailableMenuItem.visible + ? activeUpdateAvailableIcon + : activeIcon, + ); } }); ipc.on('gitify:icon-idle', () => { if (!mb.tray.isDestroyed()) { - mb.tray.setImage(idleIcon); + mb.tray.setImage( + updateAvailableMenuItem.visible ? idleUpdateAvailableIcon : idleIcon, + ); } }); ipc.on('gitify:update-title', (_, title) => { if (!mb.tray.isDestroyed()) { - mb.tray.setTitle(`${isUpdateAvailable ? '⤓' : ''}${title}`); + mb.tray.setTitle(title); } }); @@ -223,12 +219,40 @@ app.whenReady().then(async () => { ipc.on('gitify:update-auto-launch', (_, settings) => { app.setLoginItemSettings(settings); }); -}); -function checkForUpdates() { - log.info('Auto Updater: Checking for updates...'); - autoUpdater.checkForUpdatesAndNotify(); -} + // Auto Updater + updateElectronApp({ + updateInterval: '24 hours', + logger: log, + }); + + autoUpdater.on('checking-for-update', () => { + log.info('Auto Updater: Checking for update'); + checkForUpdatesMenuItem.enabled = false; + }); + + autoUpdater.on('error', (error) => { + log.error('Auto Updater: error checking for update', error); + checkForUpdatesMenuItem.enabled = true; + }); + + autoUpdater.on('update-available', () => { + log.info('Auto Updater: New update available'); + updateAvailableMenuItem.visible = true; + mb.tray.setToolTip('Gitify\nA new update is available'); + }); + + autoUpdater.on('update-downloaded', () => { + log.info('Auto Updater: Update downloaded'); + updateReadyForInstallMenuItem.visible = true; + mb.tray.setToolTip('Gitify\nA new update is ready to install'); + }); + + autoUpdater.on('update-not-available', () => { + log.info('Auto Updater: update not available'); + checkForUpdatesMenuItem.enabled = true; + }); +}); function takeScreenshot() { const date = new Date(); @@ -244,6 +268,7 @@ function takeScreenshot() { function resetApp() { const cancelButtonId = 0; + const resetButtonId = 1; const response = dialog.showMessageBoxSync(mb.window, { type: 'warning', @@ -255,10 +280,8 @@ function resetApp() { cancelId: cancelButtonId, }); - if (response === cancelButtonId) { - return; + if (response === resetButtonId) { + mb.window.webContents.send('gitify:reset-app'); + mb.app.quit(); } - - mb.window.webContents.send('gitify:reset-app'); - mb.app.quit(); } diff --git a/src/hooks/useNotifications.test.ts b/src/hooks/useNotifications.test.ts index 730364ea7..30e6f327a 100644 --- a/src/hooks/useNotifications.test.ts +++ b/src/hooks/useNotifications.test.ts @@ -476,7 +476,7 @@ describe('hooks/useNotifications.ts', () => { describe('markRepoNotificationsRead', () => { const repoSlug = 'gitify-app/notifications-test'; - it("should mark a repository's notifications as read with success", async () => { + it('should mark repository notifications as read with success', async () => { nock('https://api.github.com/') .put(`/repos/${repoSlug}/notifications`) .reply(200); @@ -497,7 +497,7 @@ describe('hooks/useNotifications.ts', () => { expect(result.current.notifications.length).toBe(0); }); - it("should mark a repository's notifications as read with failure", async () => { + it('should mark repository notifications as read with failure', async () => { nock('https://api.github.com/') .put(`/repos/${repoSlug}/notifications`) .reply(400); @@ -520,7 +520,7 @@ describe('hooks/useNotifications.ts', () => { }); describe('markRepoNotificationsDone', () => { - it("should mark a repository's notifications as done with success", async () => { + it('should mark repository notifications as done with success', async () => { nock('https://api.github.com/') .delete(`/notifications/threads/${id}`) .reply(200); @@ -541,7 +541,7 @@ describe('hooks/useNotifications.ts', () => { expect(result.current.notifications.length).toBe(0); }); - it("should mark a repository's notifications as done with failure", async () => { + it('should mark repository notifications as done with failure', async () => { nock('https://api.github.com/') .delete(`/notifications/threads/${id}`) .reply(400); diff --git a/src/hooks/useNotifications.ts b/src/hooks/useNotifications.ts index a5f385ab2..a6d392938 100644 --- a/src/hooks/useNotifications.ts +++ b/src/hooks/useNotifications.ts @@ -60,6 +60,7 @@ export const useNotifications = (): NotificationsState => { const fetchNotifications = useCallback( async (state: GitifyState) => { setStatus('loading'); + setGlobalError(null); const fetchedNotifications = await getAllNotifications(state); diff --git a/src/routes/Notifications.tsx b/src/routes/Notifications.tsx index 17543e543..0e60f1a8f 100644 --- a/src/routes/Notifications.tsx +++ b/src/routes/Notifications.tsx @@ -4,10 +4,12 @@ import { AllRead } from '../components/AllRead'; import { Oops } from '../components/Oops'; import { AppContext } from '../context/App'; import { getAccountUUID } from '../utils/auth/utils'; +import { Errors } from '../utils/constants'; import { getNotificationCount } from '../utils/notifications'; export const NotificationsRoute: FC = () => { - const { notifications, globalError, settings } = useContext(AppContext); + const { notifications, status, globalError, settings } = + useContext(AppContext); const hasMultipleAccounts = useMemo( () => notifications.length > 1, @@ -24,8 +26,8 @@ export const NotificationsRoute: FC = () => { [notifications], ); - if (globalError) { - return ; + if (status === 'error') { + return ; } if (!hasNotifications && hasNoAccountErrors) { diff --git a/src/routes/Settings.tsx b/src/routes/Settings.tsx index 3d07f80ef..2ee168061 100644 --- a/src/routes/Settings.tsx +++ b/src/routes/Settings.tsx @@ -1,6 +1,5 @@ import { GearIcon } from '@primer/octicons-react'; -import { ipcRenderer } from 'electron'; -import { type FC, useContext, useEffect, useState } from 'react'; +import { type FC, useContext } from 'react'; import { Header } from '../components/Header'; import { AppearanceSettings } from '../components/settings/AppearanceSettings'; import { NotificationSettings } from '../components/settings/NotificationSettings'; @@ -10,13 +9,6 @@ import { AppContext } from '../context/App'; export const SettingsRoute: FC = () => { const { resetSettings } = useContext(AppContext); - const [isUpdateAvailable, setIsUpdateAvailable] = useState(false); - - useEffect(() => { - ipcRenderer.on('gitify:auto-updater', (_, isUpdateAvailable: boolean) => { - setIsUpdateAvailable(isUpdateAvailable); - }); - }, []); return (
@@ -40,7 +32,7 @@ export const SettingsRoute: FC = () => {
- +
); }; diff --git a/src/routes/__snapshots__/Notifications.test.tsx.snap b/src/routes/__snapshots__/Notifications.test.tsx.snap index 5dbd73147..8bb99d300 100644 --- a/src/routes/__snapshots__/Notifications.test.tsx.snap +++ b/src/routes/__snapshots__/Notifications.test.tsx.snap @@ -144,13 +144,13 @@ exports[`routes/Notifications.tsx should render itself & its children (error con "baseElement":

- AllRead + Oops

, "container":

- AllRead + Oops

, "debug": [Function], diff --git a/src/routes/__snapshots__/Settings.test.tsx.snap b/src/routes/__snapshots__/Settings.test.tsx.snap index 54e98764e..0f0b2a506 100644 --- a/src/routes/__snapshots__/Settings.test.tsx.snap +++ b/src/routes/__snapshots__/Settings.test.tsx.snap @@ -797,28 +797,6 @@ exports[`routes/Settings.tsx should render itself & its children 1`] = ` Gitify v0.0.1 - - - - -
diff --git a/src/utils/comms.test.ts b/src/utils/comms.test.ts index 3c7402ba4..e78779d14 100644 --- a/src/utils/comms.test.ts +++ b/src/utils/comms.test.ts @@ -72,6 +72,16 @@ describe('utils/comms.ts', () => { }); }); + it('should use default open preference if user settings not found', () => { + jest.spyOn(storage, 'loadState').mockReturnValue({ settings: null }); + + openExternalLink('https://www.gitify.io/' as Link); + expect(shell.openExternal).toHaveBeenCalledTimes(1); + expect(shell.openExternal).toHaveBeenCalledWith('https://www.gitify.io/', { + activate: true, + }); + }); + it('should ignore opening external local links file:///', () => { openExternalLink('file:///Applications/SomeApp.app' as Link); expect(shell.openExternal).toHaveBeenCalledTimes(0); diff --git a/src/utils/comms.ts b/src/utils/comms.ts index 13cef346e..0a11fa0c3 100644 --- a/src/utils/comms.ts +++ b/src/utils/comms.ts @@ -1,4 +1,5 @@ import { ipcRenderer, shell } from 'electron'; +import { defaultSettings } from '../context/App'; import { type Link, OpenPreference } from '../types'; import Constants from './constants'; import { loadState } from './storage'; @@ -8,8 +9,12 @@ export function openExternalLink(url: Link): void { // Load the state from local storage to avoid having to pass settings as a parameter const { settings } = loadState(); + const openPreference = settings + ? settings.openLinks + : defaultSettings.openLinks; + shell.openExternal(url, { - activate: settings.openLinks === OpenPreference.FOREGROUND, + activate: openPreference === OpenPreference.FOREGROUND, }); } }