Repository: gitify-app/gitify
Pull request #1320 (2024-06-28 14:56:59)

refactor: split settings into smaller components
Split our growing `Settings` component into smaller, more focused components.

- Appearance Settings
- Notification Settings
- System Settings
- Settings Footer


Patch:
diff --git a/README.md b/README.md
index 4b852be4..d0843691 100644
--- a/README.md
+++ b/README.md
@@ -40,17 +40,17 @@ For more information, see [LICENSE](LICENSE).
 [brew]: http://brew.sh/
 [homebrew-cask]: https://formulae.brew.sh/cask/gitify
 [coveralls]: https://coveralls.io/github/gitify-app/gitify
-[coveralls-badge]: https://img.shields.io/coverallsCoverage/github/gitify-app/gitify
+[coveralls-badge]: https://img.shields.io/coverallsCoverage/github/gitify-app/gitify?logo=coveralls
 [build-workflow-badge]: https://github.com/gitify-app/gitify/actions/workflows/build.yml/badge.svg
 [release-workflow-badge]: https://github.com/gitify-app/gitify/actions/workflows/release.yml/badge.svg
-[downloads-total-badge]: https://img.shields.io/github/downloads/gitify-app/gitify/total?label=downloads@all
-[downloads-latest-badge]: https://img.shields.io/github/downloads/gitify-app/gitify/latest/total
-[contributors-badge]: https://img.shields.io/github/contributors/gitify-app/gitify
+[downloads-total-badge]: https://img.shields.io/github/downloads/gitify-app/gitify/total?label=downloads@all&logo=github
+[downloads-latest-badge]: https://img.shields.io/github/downloads/gitify-app/gitify/latest/total?logo=github
+[contributors-badge]: https://img.shields.io/github/contributors/gitify-app/gitify?logo=github
 [librariesio]: https://libraries.io/
-[librariesio-badge]: https://img.shields.io/librariesio/github/gitify-app/gitify
+[librariesio-badge]: https://img.shields.io/librariesio/github/gitify-app/gitify?logo=librariesdotio
 [license]: LICENSE
-[license-badge]: https://img.shields.io/github/license/gitify-app/gitify
-[github-release-badge]: https://img.shields.io/github/v/release/gitify-app/gitify
-[homebrew-cask-badge]: https://img.shields.io/homebrew/cask/v/gitify
+[license-badge]: https://img.shields.io/github/license/gitify-app/gitify?logo=github
+[github-release-badge]: https://img.shields.io/github/v/release/gitify-app/gitify?logo=github
+[homebrew-cask-badge]: https://img.shields.io/homebrew/cask/v/gitify?logo=homebrew
 [renovate]: https://renovatebot.com/
-[renovate-badge]: https://img.shields.io/badge/renovate-enabled-brightgreen.svg
+[renovate-badge]: https://img.shields.io/badge/renovate-enabled-brightgreen.svg?logo=renovatebot
diff --git a/package.json b/package.json
index ff95e0d2..ca31bf4c 100644
--- a/package.json
+++ b/package.json
@@ -134,7 +134,7 @@
     "jest": "29.7.0",
     "jest-environment-jsdom": "29.7.0",
     "nock": "13.5.4",
-    "postcss": "8.4.38",
+    "postcss": "8.4.39",
     "postcss-loader": "8.1.1",
     "rimraf": "5.0.7",
     "style-loader": "4.0.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 2db4c9db..0581cb2a 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -80,7 +80,7 @@ importers:
         version: 5.3.3
       autoprefixer:
         specifier: 10.4.19
-        version: 10.4.19(postcss@8.4.38)
+        version: 10.4.19(postcss@8.4.39)
       css-loader:
         specifier: 7.1.2
         version: 7.1.2(webpack@5.92.1(webpack-cli@5.1.4))
@@ -106,11 +106,11 @@ importers:
         specifier: 13.5.4
         version: 13.5.4
       postcss:
-        specifier: 8.4.38
-        version: 8.4.38
+        specifier: 8.4.39
+        version: 8.4.39
       postcss-loader:
         specifier: 8.1.1
-        version: 8.1.1(postcss@8.4.38)(typescript@5.5.2)(webpack@5.92.1(webpack-cli@5.1.4))
+        version: 8.1.1(postcss@8.4.39)(typescript@5.5.2)(webpack@5.92.1(webpack-cli@5.1.4))
       rimraf:
         specifier: 5.0.7
         version: 5.0.7
@@ -2356,6 +2356,9 @@ packages:
   picocolors@1.0.0:
     resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}
 
+  picocolors@1.0.1:
+    resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==}
+
   picomatch@2.3.1:
     resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
     engines: {node: '>=8.6'}
@@ -2450,8 +2453,8 @@ packages:
   postcss-value-parser@4.2.0:
     resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==}
 
-  postcss@8.4.38:
-    resolution: {integrity: sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==}
+  postcss@8.4.39:
+    resolution: {integrity: sha512-0vzE+lAiG7hZl1/9I8yzKLx3aR9Xbof3fBHKunvMfOCYAtMhrsnccJY2iTURb9EZd5+pLuiNV9/c/GZJOHsgIw==}
     engines: {node: ^10 || ^12 || >=14}
 
   pretty-format@27.5.1:
@@ -4167,14 +4170,14 @@ snapshots:
 
   at-least-node@1.0.0: {}
 
-  autoprefixer@10.4.19(postcss@8.4.38):
+  autoprefixer@10.4.19(postcss@8.4.39):
     dependencies:
       browserslist: 4.23.0
       caniuse-lite: 1.0.30001600
       fraction.js: 4.3.7
       normalize-range: 0.1.2
       picocolors: 1.0.0
-      postcss: 8.4.38
+      postcss: 8.4.39
       postcss-value-parser: 4.2.0
 
   axios@1.7.2:
@@ -4505,12 +4508,12 @@ snapshots:
 
   css-loader@7.1.2(webpack@5.92.1(webpack-cli@5.1.4)):
     dependencies:
-      icss-utils: 5.1.0(postcss@8.4.38)
-      postcss: 8.4.38
-      postcss-modules-extract-imports: 3.1.0(postcss@8.4.38)
-      postcss-modules-local-by-default: 4.0.5(postcss@8.4.38)
-      postcss-modules-scope: 3.2.0(postcss@8.4.38)
-      postcss-modules-values: 4.0.0(postcss@8.4.38)
+      icss-utils: 5.1.0(postcss@8.4.39)
+      postcss: 8.4.39
+      postcss-modules-extract-imports: 3.1.0(postcss@8.4.39)
+      postcss-modules-local-by-default: 4.0.5(postcss@8.4.39)
+      postcss-modules-scope: 3.2.0(postcss@8.4.39)
+      postcss-modules-values: 4.0.0(postcss@8.4.39)
       postcss-value-parser: 4.2.0
       semver: 7.6.0
     optionalDependencies:
@@ -5060,9 +5063,9 @@ snapshots:
     dependencies:
       safer-buffer: 2.1.2
 
-  icss-utils@5.1.0(postcss@8.4.38):
+  icss-utils@5.1.0(postcss@8.4.39):
     dependencies:
-      postcss: 8.4.38
+      postcss: 8.4.39
 
   ieee754@1.2.1: {}
 
@@ -5825,6 +5828,8 @@ snapshots:
 
   picocolors@1.0.0: {}
 
+  picocolors@1.0.1: {}
+
   picomatch@2.3.1: {}
 
   pify@2.3.0: {}
@@ -5841,61 +5846,61 @@ snapshots:
       base64-js: 1.5.1
       xmlbuilder: 15.1.1
 
-  postcss-import@15.1.0(postcss@8.4.38):
+  postcss-import@15.1.0(postcss@8.4.39):
     dependencies:
-      postcss: 8.4.38
+      postcss: 8.4.39
       postcss-value-parser: 4.2.0
       read-cache: 1.0.0
       resolve: 1.22.8
 
-  postcss-js@4.0.1(postcss@8.4.38):
+  postcss-js@4.0.1(postcss@8.4.39):
     dependencies:
       camelcase-css: 2.0.1
-      postcss: 8.4.38
+      postcss: 8.4.39
 
-  postcss-load-config@4.0.2(postcss@8.4.38)(ts-node@10.9.2(@types/node@20.14.9)(typescript@5.5.2)):
+  postcss-load-config@4.0.2(postcss@8.4.39)(ts-node@10.9.2(@types/node@20.14.9)(typescript@5.5.2)):
     dependencies:
       lilconfig: 3.1.1
       yaml: 2.4.1
     optionalDependencies:
-      postcss: 8.4.38
+      postcss: 8.4.39
       ts-node: 10.9.2(@types/node@20.14.9)(typescript@5.5.2)
 
-  postcss-loader@8.1.1(postcss@8.4.38)(typescript@5.5.2)(webpack@5.92.1(webpack-cli@5.1.4)):
+  postcss-loader@8.1.1(postcss@8.4.39)(typescript@5.5.2)(webpack@5.92.1(webpack-cli@5.1.4)):
     dependencies:
       cosmiconfig: 9.0.0(typescript@5.5.2)
       jiti: 1.21.0
-      postcss: 8.4.38
+      postcss: 8.4.39
       semver: 7.6.0
     optionalDependencies:
       webpack: 5.92.1(webpack-cli@5.1.4)
     transitivePeerDependencies:
       - typescript
 
-  postcss-modules-extract-imports@3.1.0(postcss@8.4.38):
+  postcss-modules-extract-imports@3.1.0(postcss@8.4.39):
     dependencies:
-      postcss: 8.4.38
+      postcss: 8.4.39
 
-  postcss-modules-local-by-default@4.0.5(postcss@8.4.38):
+  postcss-modules-local-by-default@4.0.5(postcss@8.4.39):
     dependencies:
-      icss-utils: 5.1.0(postcss@8.4.38)
-      postcss: 8.4.38
+      icss-utils: 5.1.0(postcss@8.4.39)
+      postcss: 8.4.39
       postcss-selector-parser: 6.0.16
       postcss-value-parser: 4.2.0
 
-  postcss-modules-scope@3.2.0(postcss@8.4.38):
+  postcss-modules-scope@3.2.0(postcss@8.4.39):
     dependencies:
-      postcss: 8.4.38
+      postcss: 8.4.39
       postcss-selector-parser: 6.0.16
 
-  postcss-modules-values@4.0.0(postcss@8.4.38):
+  postcss-modules-values@4.0.0(postcss@8.4.39):
     dependencies:
-      icss-utils: 5.1.0(postcss@8.4.38)
-      postcss: 8.4.38
+      icss-utils: 5.1.0(postcss@8.4.39)
+      postcss: 8.4.39
 
-  postcss-nested@6.0.1(postcss@8.4.38):
+  postcss-nested@6.0.1(postcss@8.4.39):
     dependencies:
-      postcss: 8.4.38
+      postcss: 8.4.39
       postcss-selector-parser: 6.0.16
 
   postcss-selector-parser@6.0.16:
@@ -5905,10 +5910,10 @@ snapshots:
 
   postcss-value-parser@4.2.0: {}
 
-  postcss@8.4.38:
+  postcss@8.4.39:
     dependencies:
       nanoid: 3.3.7
-      picocolors: 1.0.0
+      picocolors: 1.0.1
       source-map-js: 1.2.0
 
   pretty-format@27.5.1:
@@ -6279,11 +6284,11 @@ snapshots:
       normalize-path: 3.0.0
       object-hash: 3.0.0
       picocolors: 1.0.0
-      postcss: 8.4.38
-      postcss-import: 15.1.0(postcss@8.4.38)
-      postcss-js: 4.0.1(postcss@8.4.38)
-      postcss-load-config: 4.0.2(postcss@8.4.38)(ts-node@10.9.2(@types/node@20.14.9)(typescript@5.5.2))
-      postcss-nested: 6.0.1(postcss@8.4.38)
+      postcss: 8.4.39
+      postcss-import: 15.1.0(postcss@8.4.39)
+      postcss-js: 4.0.1(postcss@8.4.39)
+      postcss-load-config: 4.0.2(postcss@8.4.39)(ts-node@10.9.2(@types/node@20.14.9)(typescript@5.5.2))
+      postcss-nested: 6.0.1(postcss@8.4.39)
       postcss-selector-parser: 6.0.16
       resolve: 1.22.8
       sucrase: 3.35.0
diff --git a/src/components/settings/AppearanceSettings.tsx b/src/components/settings/AppearanceSettings.tsx
new file mode 100644
index 00000000..e50a818a
--- /dev/null
+++ b/src/components/settings/AppearanceSettings.tsx
@@ -0,0 +1,111 @@
+import {
+  CheckIcon,
+  CommentIcon,
+  IssueClosedIcon,
+  MilestoneIcon,
+  TagIcon,
+} from '@primer/octicons-react';
+import { ipcRenderer } from 'electron';
+import { type FC, useContext, useEffect } from 'react';
+import { AppContext } from '../../context/App';
+import { Size, Theme } from '../../types';
+import { setTheme } from '../../utils/theme';
+import { Checkbox } from '../fields/Checkbox';
+import { RadioGroup } from '../fields/RadioGroup';
+
+export const AppearanceSettings: FC = () => {
+  const { settings, updateSetting } = useContext(AppContext);
+
+  useEffect(() => {
+    ipcRenderer.on('gitify:update-theme', (_, updatedTheme: Theme) => {
+      if (settings.theme === Theme.SYSTEM) {
+        setTheme(updatedTheme);
+      }
+    });
+  }, [settings.theme]);
+
+  return (
+    <fieldset className="mb-3">
+      <legend id="appearance" className="mb-1 mt-2 font-semibold">
+        Appearance
+      </legend>
+      <RadioGroup
+        name="theme"
+        label="Theme:"
+        value={settings.theme}
+        options={[
+          { label: 'System', value: Theme.SYSTEM },
+          { label: 'Light', value: Theme.LIGHT },
+          { label: 'Dark', value: Theme.DARK },
+        ]}
+        onChange={(evt) => {
+          updateSetting('theme', evt.target.value);
+        }}
+      />
+      <Checkbox
+        name="detailedNotifications"
+        label="Detailed notifications"
+        checked={settings.detailedNotifications}
+        onChange={(evt) =>
+          updateSetting('detailedNotifications', evt.target.checked)
+        }
+        tooltip={
+          <div>
+            <div className="pb-3">
+              Enrich notifications with author or last commenter profile
+              information, state and GitHub-like colors.
+            </div>
+            <div className="text-orange-600">
+              ⚠️ Users with a large number of unread notifications <i>may</i>{' '}
+              experience rate limiting under certain circumstances. Disable this
+              setting if you experience this.
+            </div>
+          </div>
+        }
+      />
+      <Checkbox
+        name="showPills"
+        label="Show notification metric pills"
+        checked={settings.showPills}
+        onChange={(evt) => updateSetting('showPills', evt.target.checked)}
+        tooltip={
+          <div>
+            <div>Show notification metric pills for:</div>
+            <div className="pl-6">
+              <ul className="list-disc">
+                <li>
+                  <IssueClosedIcon size={Size.MEDIUM} className="pr-1" />
+                  linked issues
+                </li>
+                <li>
+                  <CheckIcon size={Size.MEDIUM} className="pr-1" /> pr reviews
+                </li>
+                <li>
+                  <CommentIcon size={Size.MEDIUM} className="pr-1" />
+                  comments
+                </li>
+
+                <li>
+                  <TagIcon size={Size.MEDIUM} className="pr-1" />
+                  labels
+                </li>
+                <li>
+                  <MilestoneIcon size={Size.MEDIUM} className="pr-1" />
+                  milestones
+                </li>
+              </ul>
+            </div>
+          </div>
+        }
+      />
+      <Checkbox
+        name="showAccountHostname"
+        label="Show account hostname"
+        checked={settings.showAccountHostname}
+        onChange={(evt) =>
+          updateSetting('showAccountHostname', evt.target.checked)
+        }
+      />
+    </fieldset>
+  );
+};
diff --git a/src/components/settings/NotificationSettings.tsx b/src/components/settings/NotificationSettings.tsx
new file mode 100644
index 00000000..de33b5c5
--- /dev/null
+++ b/src/components/settings/NotificationSettings.tsx
@@ -0,0 +1,99 @@
+import { type FC, type MouseEvent, useContext } from 'react';
+import { AppContext } from '../../context/App';
+import { GroupBy } from '../../types';
+import { openGitHubParticipatingDocs } from '../../utils/links';
+import { Checkbox } from '../fields/Checkbox';
+import { RadioGroup } from '../fields/RadioGroup';
+
+export const NotificationSettings: FC = () => {
+  const { settings, updateSetting } = useContext(AppContext);
+
+  return (
+    <fieldset className="mb-3">
+      <legend id="notifications" className="mb-1 mt-2 font-semibold">
+        Notifications
+      </legend>
+      <RadioGroup
+        name="groupBy"
+        label="Group by:"
+        value={settings.groupBy}
+        options={[
+          { label: 'Repository', value: GroupBy.REPOSITORY },
+          { label: 'Date', value: GroupBy.DATE },
+        ]}
+        onChange={(evt) => {
+          updateSetting('groupBy', evt.target.value);
+        }}
+      />
+      <Checkbox
+        name="showOnlyParticipating"
+        label="Show only participating"
+        checked={settings.participating}
+        onChange={(evt) => updateSetting('participating', evt.target.checked)}
+        tooltip={
+          <div>
+            See
+            <button
+              type="button"
+              className="mx-1 text-blue-500"
+              title="Open GitHub documentation for participating and watching notifications"
+              onClick={(event: MouseEvent<HTMLElement>) => {
+                // Don't trigger onClick of parent element.
+                event.stopPropagation();
+                openGitHubParticipatingDocs();
+              }}
+            >
+              official docs
+            </button>
+            for more details.
+          </div>
+        }
+      />
+      <Checkbox
+        name="showBots"
+        label="Show notifications from Bot accounts"
+        checked={!settings.detailedNotifications || settings.showBots}
+        onChange={(evt) =>
+          settings.detailedNotifications &&
+          updateSetting('showBots', evt.target.checked)
+        }
+        disabled={!settings.detailedNotifications}
+        tooltip={
+          <div>
+            <div className="pb-3">
+              Show or hide notifications from Bot accounts, such as @dependabot,
+              @renovatebot, etc
+            </div>
+            <div className="text-orange-600">
+              ⚠️ This setting requires <strong>Detailed Notifications</strong> to
+              be enabled.
+            </div>
+          </div>
+        }
+      />
+      <Checkbox
+        name="markAsDoneOnOpen"
+        label="Mark as done on open"
+        checked={settings.markAsDoneOnOpen}
+        onChange={(evt) =>
+          updateSetting('markAsDoneOnOpen', evt.target.checked)
+        }
+      />
+      <Checkbox
+        name="delayNotificationState"
+        label="Delay notification state"
+        checked={settings.delayNotificationState}
+        onChange={(evt) =>
+          updateSetting('delayNotificationState', evt.target.checked)
+        }
+        tooltip={
+          <div>
+            Keep the notification within Gitify window upon interaction (click,
+            mark as read, mark as done, etc) until the next refresh window
+            (scheduled or user initiated).
+          </div>
+        }
+      />
+    </fieldset>
+  );
+};
diff --git a/src/components/settings/SettingsFooter.tsx b/src/components/settings/SettingsFooter.tsx
new file mode 100644
index 00000000..54d4c470
--- /dev/null
+++ b/src/components/settings/SettingsFooter.tsx
@@ -0,0 +1,57 @@
+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 { Size } from '../../types';
+import { getAppVersion, quitApp } from '../../utils/comms';
+import { openGitifyReleaseNotes } from '../../utils/links';
+
+export const SettingsFooter: FC = () => {
+  const [appVersion, setAppVersion] = useState<string | null>(null);
+  const navigate = useNavigate();
+
+  useEffect(() => {
+    (async () => {
+      if (process.env.NODE_ENV === 'development') {
+        setAppVersion('dev');
+      } else {
+        const result = await getAppVersion();
+        setAppVersion(`v${result}`);
+      }
+    })();
+  }, []);
+
+  return (
+    <div className="flex items-center justify-between bg-gray-200 px-8 py-1 text-sm dark:bg-gray-darker">
+      <button
+        type="button"
+        className="cursor-pointer font-semibold"
+        title="View release notes"
+        onClick={() => openGitifyReleaseNotes(appVersion)}
+      >
+        <span title="app-version">Gitify {appVersion}</span>
+      </button>
+      <div>
+        <button
+          type="button"
+          className={BUTTON_CLASS_NAME}
+          title="Accounts"
+          onClick={() => {
+            navigate('/accounts');
+          }}
+        >
+          <PersonIcon size={Size.LARGE} aria-label="Accounts" />
+        </button>
+
+        <button
+          type="button"
+          className={BUTTON_CLASS_NAME}
+          title="Quit Gitify"
+          onClick={quitApp}
+        >
+          <XCircleIcon size={Size.LARGE} aria-label="Quit Gitify" />
+        </button>
+      </div>
+    </div>
+  );
+};
diff --git a/src/components/settings/SystemSettings.tsx b/src/components/settings/SystemSettings.tsx
new file mode 100644
index 00000000..44e7a936
--- /dev/null
+++ b/src/components/settings/SystemSettings.tsx
@@ -0,0 +1,66 @@
+import { type FC, useContext } from 'react';
+import { AppContext } from '../../context/App';
+import Constants from '../../utils/constants';
+import { isLinux, isMacOS } from '../../utils/platform';
+import { Checkbox } from '../fields/Checkbox';
+
+export const SystemSettings: FC = () => {
+  const { settings, updateSetting } = useContext(AppContext);
+
+  return (
+    <fieldset className="mb-3">
+      <legend id="system" className="mb-1 mt-2 font-semibold">
+        System
+      </legend>
+      <Checkbox
+        name="kbdShortcutEnabled"
+        label="Enable keyboard shortcut"
+        checked={settings.keyboardShortcut}
+        onChange={(evt) =>
+          updateSetting('keyboardShortcut', evt.target.checked)
+        }
+        tooltip={
+          <div>
+            When enabled you can use the hotkeys{' '}
+            <span className="text-orange-600">
+              {Constants.DEFAULT_KEYBOARD_SHORTCUT}
+            </span>{' '}
+            to show or hide Gitify.
+          </div>
+        }
+      />
+      {isMacOS() && (
+        <Checkbox
+          name="showNotificationsCountInTray"
+          label="Show notifications count in tray"
+          checked={settings.showNotificationsCountInTray}
+          onChange={(evt) =>
+            updateSetting('showNotificationsCountInTray', evt.target.checked)
+          }
+        />
+      )}
+      <Checkbox
+        name="showNotifications"
+        label="Show system notifications"
+        checked={settings.showNotifications}
+        onChange={(evt) =>
+          updateSetting('showNotifications', evt.target.checked)
+        }
+      />
+      <Checkbox
+        name="playSound"
+        label="Play sound"
+        checked={settings.playSound}
+        onChange={(evt) => updateSetting('playSound', evt.target.checked)}
+      />
+      {!isLinux() && (
+        <Checkbox
+          name="openAtStartUp"
+          label="Open at startup"
+          checked={settings.openAtStartup}
+          onChange={(evt) => updateSetting('openAtStartup', evt.target.checked)}
+        />
+      )}
+    </fieldset>
+  );
+};
diff --git a/src/routes/Settings.tsx b/src/routes/Settings.tsx
index fada5716..878a6e1d 100644
--- a/src/routes/Settings.tsx
+++ b/src/routes/Settings.tsx
@@ -1,328 +1,22 @@
-import {
-  CheckIcon,
-  CommentIcon,
-  IssueClosedIcon,
-  MilestoneIcon,
-  PersonIcon,
-  TagIcon,
-  XCircleIcon,
-} from '@primer/octicons-react';
-import { ipcRenderer } from 'electron';
-import {
-  type FC,
-  type MouseEvent,
-  useContext,
-  useEffect,
-  useState,
-} from 'react';
-import { useNavigate } from 'react-router-dom';
+import type { FC } from 'react';
 import { Header } from '../components/Header';
-import { Checkbox } from '../components/fields/Checkbox';
-import { RadioGroup } from '../components/fields/RadioGroup';
-import { AppContext } from '../context/App';
-import { BUTTON_CLASS_NAME } from '../styles/gitify';
-import { GroupBy, Size, Theme } from '../types';
-import { getAppVersion, quitApp } from '../utils/comms';
-import Constants from '../utils/constants';
-import {
-  openGitHubParticipatingDocs,
-  openGitifyReleaseNotes,
-} from '../utils/links';
-import { isLinux, isMacOS } from '../utils/platform';
-import { setTheme } from '../utils/theme';
+import { AppearanceSettings } from '../components/settings/AppearanceSettings';
+import { NotificationSettings } from '../components/settings/NotificationSettings';
+import { SettingsFooter } from '../components/settings/SettingsFooter';
+import { SystemSettings } from '../components/settings/SystemSettings';
 
 export const SettingsRoute: FC = () => {
-  const { settings, updateSetting } = useContext(AppContext);
-  const navigate = useNavigate();
-
-  const [appVersion, setAppVersion] = useState<string | null>(null);
-
-  useEffect(() => {
-    (async () => {
-      if (process.env.NODE_ENV === 'development') {
-        setAppVersion('dev');
-      } else {
-        const result = await getAppVersion();
-        setAppVersion(`v${result}`);
-      }
-    })();
-
-    ipcRenderer.on('gitify:update-theme', (_, updatedTheme: Theme) => {
-      if (settings.theme === Theme.SYSTEM) {
-        setTheme(updatedTheme);
-      }
-    });
-  }, [settings.theme]);
-
   return (
     <div className="flex h-screen flex-col" data-testid="settings">
       <Header fetchOnBack={true}>Settings</Header>
-      <div className="flex-grow overflow-x-auto px-8">
-        <fieldset className="mb-3">
-          <legend id="appearance" className="mb-1 mt-2 font-semibold">
-            Appearance
-          </legend>
-          <RadioGroup
-            name="theme"
-            label="Theme:"
-            value={settings.theme}
-            options={[
-              { label: 'System', value: Theme.SYSTEM },
-              { label: 'Light', value: Theme.LIGHT },
-              { label: 'Dark', value: Theme.DARK },
-            ]}
-            onChange={(evt) => {
-              updateSetting('theme', evt.target.value);
-            }}
-          />
-          <Checkbox
-            name="detailedNotifications"
-            label="Detailed notifications"
-            checked={settings.detailedNotifications}
-            onChange={(evt) =>
-              updateSetting('detailedNotifications', evt.target.checked)
-            }
-            tooltip={
-              <div>
-                <div className="pb-3">
-                  Enrich notifications with author or last commenter profile
-                  information, state and GitHub-like colors.
-                </div>
-                <div className="text-orange-600">
-                  ⚠️ Users with a large number of unread notifications <i>may</i>{' '}
-                  experience rate limiting under certain circumstances. Disable
-                  this setting if you experience this.
-                </div>
-              </div>
-            }
-          />
-          <Checkbox
-            name="showPills"
-            label="Show notification metric pills"
-            checked={settings.showPills}
-            onChange={(evt) => updateSetting('showPills', evt.target.checked)}
-            tooltip={
-              <div>
-                <div>Show notification metric pills for:</div>
-                <div className="pl-6">
-                  <ul className="list-disc">
-                    <li>
-                      <IssueClosedIcon size={Size.MEDIUM} className="pr-1" />
-                      linked issues
-                    </li>
-                    <li>
-                      <CheckIcon size={Size.MEDIUM} className="pr-1" /> pr
-                      reviews
-                    </li>
-                    <li>
-                      <CommentIcon size={Size.MEDIUM} className="pr-1" />
-                      comments
-                    </li>
 
-                    <li>
-                      <TagIcon size={Size.MEDIUM} className="pr-1" />
-                      labels
-                    </li>
-                    <li>
-                      <MilestoneIcon size={Size.MEDIUM} className="pr-1" />
-                      milestones
-                    </li>
-                  </ul>
-                </div>
-              </div>
-            }
-          />
-          <Checkbox
-            name="showAccountHostname"
-            label="Show account hostname"
-            checked={settings.showAccountHostname}
-            onChange={(evt) =>
-              updateSetting('showAccountHostname', evt.target.checked)
-            }
-          />
-        </fieldset>
-
-        <fieldset className="mb-3">
-          <legend id="notifications" className="mb-1 mt-2 font-semibold">
-            Notifications
-          </legend>
-          <RadioGroup
-            name="groupBy"
-            label="Group by:"
-            value={settings.groupBy}
-            options={[
-              { label: 'Repository', value: GroupBy.REPOSITORY },
-              { label: 'Date', value: GroupBy.DATE },
-            ]}
-            onChange={(evt) => {
-              updateSetting('groupBy', evt.target.value);
-            }}
-          />
-          <Checkbox
-            name="showOnlyParticipating"
-            label="Show only participating"
-            checked={settings.participating}
-            onChange={(evt) =>
-              updateSetting('participating', evt.target.checked)
-            }
-            tooltip={
-              <div>
-                See
-                <button
-                  type="button"
-                  className="mx-1 text-blue-500"
-                  title="Open GitHub documentation for participating and watching notifications"
-                  onClick={(event: MouseEvent<HTMLElement>) => {
-                    // Don't trigger onClick of parent element.
-                    event.stopPropagation();
-                    openGitHubParticipatingDocs();
-                  }}
-                >
-                  official docs
-                </button>
-                for more details.
-              </div>
-            }
-          />
-          <Checkbox
-            name="showBots"
-            label="Show notifications from Bot accounts"
-            checked={!settings.detailedNotifications || settings.showBots}
-            onChange={(evt) =>
-              settings.detailedNotifications &&
-              updateSetting('showBots', evt.target.checked)
-            }
-            disabled={!settings.detailedNotifications}
-            tooltip={
-              <div>
-                <div className="pb-3">
-                  Show or hide notifications from Bot accounts, such as
-                  @dependabot, @renovatebot, etc
-                </div>
-                <div className="text-orange-600">
-                  ⚠️ This setting requires{' '}
-                  <strong>Detailed Notifications</strong> to be enabled.
-                </div>
-              </div>
-            }
-          />
-          <Checkbox
-            name="markAsDoneOnOpen"
-            label="Mark as done on open"
-            checked={settings.markAsDoneOnOpen}
-            onChange={(evt) =>
-              updateSetting('markAsDoneOnOpen', evt.target.checked)
-            }
-          />
-          <Checkbox
-            name="delayNotificationState"
-            label="Delay notification state"
-            checked={settings.delayNotificationState}
-            onChange={(evt) =>
-              updateSetting('delayNotificationState', evt.target.checked)
-            }
-            tooltip={
-              <div>
-                Keep the notification within Gitify window upon interaction
-                (click, mark as read, mark as done, etc) until the next refresh
-                window (scheduled or user initiated).
-              </div>
-            }
-          />
-        </fieldset>
-
-        <fieldset className="mb-3">
-          <legend id="system" className="mb-1 mt-2 font-semibold">
-            System
-          </legend>
-          <Checkbox
-            name="kbdShortcutEnabled"
-            label="Enable keyboard shortcut"
-            checked={settings.keyboardShortcut}
-            onChange={(evt) =>
-              updateSetting('keyboardShortcut', evt.target.checked)
-            }
-            tooltip={
-              <div>
-                When enabled you can use the hotkeys{' '}
-                <span className="text-orange-600">
-                  {Constants.DEFAULT_KEYBOARD_SHORTCUT}
-                </span>{' '}
-                to show or hide Gitify.
-              </div>
-            }
-          />
-          {isMacOS() && (
-            <Checkbox
-              name="showNotificationsCountInTray"
-              label="Show notifications count in tray"
-              checked={settings.showNotificationsCountInTray}
-              onChange={(evt) =>
-                updateSetting(
-                  'showNotificationsCountInTray',
-                  evt.target.checked,
-                )
-              }
-            />
-          )}
-          <Checkbox
-            name="showNotifications"
-            label="Show system notifications"
-            checked={settings.showNotifications}
-            onChange={(evt) =>
-              updateSetting('showNotifications', evt.target.checked)
-            }
-          />
-          <Checkbox
-            name="playSound"
-            label="Play sound"
-            checked={settings.playSound}
-            onChange={(evt) => updateSetting('playSound', evt.target.checked)}
-          />
-          {!isLinux() && (
-            <Checkbox
-              name="openAtStartUp"
-              label="Open at startup"
-              checked={settings.openAtStartup}
-              onChange={(evt) =>
-                updateSetting('openAtStartup', evt.target.checked)
-              }
-            />
-          )}
-        </fieldset>
+      <div className="flex-grow overflow-x-auto px-8">
+        <AppearanceSettings />
+        <NotificationSettings />
+        <SystemSettings />
       </div>
 
-      <div className="flex items-center justify-between bg-gray-200 px-8 py-1 text-sm dark:bg-gray-darker">
-        <button
-          type="button"
-          className="cursor-pointer font-semibold"
-          title="View release notes"
-          onClick={() => openGitifyReleaseNotes(appVersion)}
-        >
-          <span title="app-version">Gitify {appVersion}</span>
-        </button>
-        <div>
-          <button
-            type="button"
-            className={BUTTON_CLASS_NAME}
-            title="Accounts"
-            onClick={() => {
-              navigate('/accounts');
-            }}
-          >
-            <PersonIcon size={Size.LARGE} aria-label="Accounts" />
-          </button>
-
-          <button
-            type="button"
-            className={BUTTON_CLASS_NAME}
-            title="Quit Gitify"
-            onClick={quitApp}
-          >
-            <XCircleIcon size={Size.LARGE} aria-label="Quit Gitify" />
-          </button>
-        </div>
-      </div>
+      <SettingsFooter />
     </div>
   );
 };


Test patch:
diff --git a/src/components/settings/AppearanceSettings.test.tsx b/src/components/settings/AppearanceSettings.test.tsx
new file mode 100644
index 00000000..96cae84b
--- /dev/null
+++ b/src/components/settings/AppearanceSettings.test.tsx
@@ -0,0 +1,111 @@
+import { act, fireEvent, render, screen } from '@testing-library/react';
+import { MemoryRouter } from 'react-router-dom';
+import { mockAuth, mockSettings } from '../../__mocks__/state-mocks';
+import { AppContext } from '../../context/App';
+import { AppearanceSettings } from './AppearanceSettings';
+
+describe('routes/components/AppearanceSettings.tsx', () => {
+  const updateSetting = jest.fn();
+
+  afterEach(() => {
+    jest.clearAllMocks();
+  });
+
+  it('should change the theme radio group', async () => {
+    await act(async () => {
+      render(
+        <AppContext.Provider
+          value={{
+            auth: mockAuth,
+            settings: mockSettings,
+            updateSetting,
+          }}
+        >
+          <MemoryRouter>
+            <AppearanceSettings />
+          </MemoryRouter>
+        </AppContext.Provider>,
+      );
+    });
+
+    fireEvent.click(screen.getByLabelText('Light'));
+
+    expect(updateSetting).toHaveBeenCalledTimes(1);
+    expect(updateSetting).toHaveBeenCalledWith('theme', 'LIGHT');
+  });
+
+  it('should toggle detailed notifications checkbox', async () => {
+    await act(async () => {
+      render(
+        <AppContext.Provider
+          value={{
+            auth: mockAuth,
+            settings: mockSettings,
+            updateSetting,
+          }}
+        >
+          <MemoryRouter>
+            <AppearanceSettings />
+          </MemoryRouter>
+        </AppContext.Provider>,
+      );
+    });
+
+    await screen.findByLabelText('Detailed notifications');
+
+    fireEvent.click(screen.getByLabelText('Detailed notifications'));
+
+    expect(updateSetting).toHaveBeenCalledTimes(1);
+    expect(updateSetting).toHaveBeenCalledWith('detailedNotifications', false);
+  });
+
+  it('should toggle metric pills checkbox', async () => {
+    await act(async () => {
+      render(
+        <AppContext.Provider
+          value={{
+            auth: mockAuth,
+            settings: mockSettings,
+            updateSetting,
+          }}
+        >
+          <MemoryRouter>
+            <AppearanceSettings />
+          </MemoryRouter>
+        </AppContext.Provider>,
+      );
+    });
+
+    await screen.findByLabelText('Show notification metric pills');
+
+    fireEvent.click(screen.getByLabelText('Show notification metric pills'));
+
+    expect(updateSetting).toHaveBeenCalledTimes(1);
+    expect(updateSetting).toHaveBeenCalledWith('showPills', false);
+  });
+
+  it('should toggle account hostname checkbox', async () => {
+    await act(async () => {
+      render(
+        <AppContext.Provider
+          value={{
+            auth: mockAuth,
+            settings: mockSettings,
+            updateSetting,
+          }}
+        >
+          <MemoryRouter>
+            <AppearanceSettings />
+          </MemoryRouter>
+        </AppContext.Provider>,
+      );
+    });
+
+    await screen.findByLabelText('Show account hostname');
+
+    fireEvent.click(screen.getByLabelText('Show account hostname'));
+
+    expect(updateSetting).toHaveBeenCalledTimes(1);
+    expect(updateSetting).toHaveBeenCalledWith('showAccountHostname', true);
+  });
+});
diff --git a/src/components/settings/NotificationSettings.test.tsx b/src/components/settings/NotificationSettings.test.tsx
new file mode 100644
index 00000000..b6ea137c
--- /dev/null
+++ b/src/components/settings/NotificationSettings.test.tsx
@@ -0,0 +1,230 @@
+import { act, fireEvent, render, screen } from '@testing-library/react';
+import { MemoryRouter } from 'react-router-dom';
+import { mockAuth, mockSettings } from '../../__mocks__/state-mocks';
+import { AppContext } from '../../context/App';
+import * as comms from '../../utils/comms';
+import { NotificationSettings } from './NotificationSettings';
+
+describe('routes/components/NotificationSettings.tsx', () => {
+  const updateSetting = jest.fn();
+
+  afterEach(() => {
+    jest.clearAllMocks();
+  });
+
+  it('should change the groupBy radio group', async () => {
+    await act(async () => {
+      render(
+        <AppContext.Provider
+          value={{
+            auth: mockAuth,
+            settings: mockSettings,
+            updateSetting,
+          }}
+        >
+          <MemoryRouter>
+            <NotificationSettings />
+          </MemoryRouter>
+        </AppContext.Provider>,
+      );
+    });
+
+    fireEvent.click(screen.getByLabelText('Date'));
+
+    expect(updateSetting).toHaveBeenCalledTimes(1);
+    expect(updateSetting).toHaveBeenCalledWith('groupBy', 'DATE');
+  });
+  it('should toggle the showOnlyParticipating checkbox', async () => {
+    await act(async () => {
+      render(
+        <AppContext.Provider
+          value={{
+            auth: mockAuth,
+            settings: mockSettings,
+            updateSetting,
+          }}
+        >
+          <MemoryRouter>
+            <NotificationSettings />
+          </MemoryRouter>
+        </AppContext.Provider>,
+      );
+    });
+
+    fireEvent.click(screen.getByLabelText('Show only participating'), {
+      target: { checked: true },
+    });
+
+    expect(updateSetting).toHaveBeenCalledTimes(1);
+    expect(updateSetting).toHaveBeenCalledWith('participating', false);
+  });
+
+  it('should open official docs for showOnlyParticipating tooltip', async () => {
+    const openExternalLinkMock = jest.spyOn(comms, 'openExternalLink');
+
+    await act(async () => {
+      render(
+        <AppContext.Provider
+          value={{
+            auth: mockAuth,
+            settings: mockSettings,
+            updateSetting,
+          }}
+        >
+          <MemoryRouter>
+            <NotificationSettings />
+          </MemoryRouter>
+        </AppContext.Provider>,
+      );
+    });
+
+    const tooltipElement = screen.getByLabelText(
+      'tooltip-showOnlyParticipating',
+    );
+
+    fireEvent.mouseEnter(tooltipElement);
+
+    fireEvent.click(
+      screen.getByTitle(
+        'Open GitHub documentation for participating and watching notifications',
+      ),
+    );
+
+    expect(openExternalLinkMock).toHaveBeenCalledTimes(1);
+    expect(openExternalLinkMock).toHaveBeenCalledWith(
+      'https://docs.github.com/en/account-and-profile/managing-subscriptions-and-notifications-on-github/setting-up-notifications/configuring-notifications#about-participating-and-watching-notifications',
+    );
+  });
+
+  it('should not be able to toggle the showBots checkbox when detailedNotifications is disabled', async () => {
+    await act(async () => {
+      render(
+        <AppContext.Provider
+          value={{
+            auth: mockAuth,
+            settings: {
+              ...mockSettings,
+              detailedNotifications: false,
+              showBots: true,
+            },
+            updateSetting,
+          }}
+        >
+          <MemoryRouter>
+            <NotificationSettings />
+          </MemoryRouter>
+        </AppContext.Provider>,
+      );
+    });
+
+    expect(
+      screen
+        .getByLabelText('Show notifications from Bot accounts')
+        .closest('input'),
+    ).toHaveProperty('disabled', true);
+
+    // click the checkbox
+    fireEvent.click(
+      screen.getByLabelText('Show notifications from Bot accounts'),
+    );
+
+    // check if the checkbox is still unchecked
+    expect(updateSetting).not.toHaveBeenCalled();
+
+    expect(
+      screen.getByLabelText('Show notifications from Bot accounts').parentNode
+        .parentNode,
+    ).toMatchSnapshot();
+  });
+
+  it('should be able to toggle the showBots checkbox when detailedNotifications is enabled', async () => {
+    await act(async () => {
+      render(
+        <AppContext.Provider
+          value={{
+            auth: mockAuth,
+            settings: {
+              ...mockSettings,
+              detailedNotifications: true,
+              showBots: true,
+            },
+            updateSetting,
+          }}
+        >
+          <MemoryRouter>
+            <NotificationSettings />
+          </MemoryRouter>
+        </AppContext.Provider>,
+      );
+    });
+
+    expect(
+      screen
+        .getByLabelText('Show notifications from Bot accounts')
+        .closest('input'),
+    ).toHaveProperty('disabled', false);
+
+    // click the checkbox
+    fireEvent.click(
+      screen.getByLabelText('Show notifications from Bot accounts'),
+    );
+
+    // check if the checkbox is still unchecked
+    expect(updateSetting).toHaveBeenCalledWith('showBots', false);
+
+    expect(
+      screen.getByLabelText('Show notifications from Bot accounts').parentNode
+        .parentNode,
+    ).toMatchSnapshot();
+  });
+
+  it('should toggle the markAsDoneOnOpen checkbox', async () => {
+    await act(async () => {
+      render(
+        <AppContext.Provider
+          value={{
+            auth: mockAuth,
+            settings: mockSettings,
+            updateSetting,
+          }}
+        >
+          <MemoryRouter>
+            <NotificationSettings />
+          </MemoryRouter>
+        </AppContext.Provider>,
+      );
+    });
+
+    fireEvent.click(screen.getByLabelText('Mark as done on open'), {
+      target: { checked: true },
+    });
+
+    expect(updateSetting).toHaveBeenCalledTimes(1);
+    expect(updateSetting).toHaveBeenCalledWith('markAsDoneOnOpen', false);
+  });
+
+  it('should toggle the delayNotificationState checkbox', async () => {
+    await act(async () => {
+      render(
+        <AppContext.Provider
+          value={{
+            auth: mockAuth,
+            settings: mockSettings,
+            updateSetting,
+          }}
+        >
+          <MemoryRouter>
+            <NotificationSettings />
+          </MemoryRouter>
+        </AppContext.Provider>,
+      );
+    });
+
+    fireEvent.click(screen.getByLabelText('Delay notification state'), {
+      target: { checked: true },
+    });
+
+    expect(updateSetting).toHaveBeenCalledTimes(1);
+    expect(updateSetting).toHaveBeenCalledWith('delayNotificationState', false);
+  });
+});
diff --git a/src/components/settings/SettingsFooter.test.tsx b/src/components/settings/SettingsFooter.test.tsx
new file mode 100644
index 00000000..546c7599
--- /dev/null
+++ b/src/components/settings/SettingsFooter.test.tsx
@@ -0,0 +1,148 @@
+import { act, fireEvent, render, screen } from '@testing-library/react';
+import { MemoryRouter } from 'react-router-dom';
+import { mockAuth, mockSettings } from '../../__mocks__/state-mocks';
+import { AppContext } from '../../context/App';
+import * as comms from '../../utils/comms';
+import { SettingsFooter } from './SettingsFooter';
+
+const mockNavigate = jest.fn();
+jest.mock('react-router-dom', () => ({
+  ...jest.requireActual('react-router-dom'),
+  useNavigate: () => mockNavigate,
+}));
+
+describe('routes/components/SettingsFooter.tsx', () => {
+  afterEach(() => {
+    jest.clearAllMocks();
+  });
+
+  describe('app version', () => {
+    let originalEnv: NodeJS.ProcessEnv;
+
+    beforeEach(() => {
+      // Save the original node env state
+      originalEnv = process.env;
+    });
+
+    afterEach(() => {
+      // Restore the original node env state
+      process.env = originalEnv;
+    });
+
+    it('should show production app version', async () => {
+      process.env = {
+        ...originalEnv,
+        NODE_ENV: 'production',
+      };
+
+      await act(async () => {
+        render(
+          <AppContext.Provider
+            value={{
+              auth: mockAuth,
+              settings: mockSettings,
+            }}
+          >
+            <MemoryRouter>
+              <SettingsFooter />
+            </MemoryRouter>
+          </AppContext.Provider>,
+        );
+      });
+
+      expect(screen.getByTitle('app-version')).toMatchSnapshot();
+    });
+
+    it('should show development app version', async () => {
+      process.env = {
+        ...originalEnv,
+        NODE_ENV: 'development',
+      };
+
+      await act(async () => {
+        render(
+          <AppContext.Provider
+            value={{
+              auth: mockAuth,
+              settings: mockSettings,
+            }}
+          >
+            <MemoryRouter>
+              <SettingsFooter />
+            </MemoryRouter>
+          </AppContext.Provider>,
+        );
+      });
+
+      expect(screen.getByTitle('app-version')).toMatchSnapshot();
+    });
+  });
+
+  it('should open release notes', async () => {
+    const openExternalLinkMock = jest.spyOn(comms, 'openExternalLink');
+
+    await act(async () => {
+      render(
+        <AppContext.Provider
+          value={{
+            auth: mockAuth,
+            settings: mockSettings,
+          }}
+        >
+          <MemoryRouter>
+            <SettingsFooter />
+          </MemoryRouter>
+        </AppContext.Provider>,
+      );
+    });
+
+    fireEvent.click(screen.getByTitle('View release notes'));
+
+    expect(openExternalLinkMock).toHaveBeenCalledTimes(1);
+    expect(openExternalLinkMock).toHaveBeenCalledWith(
+      'https://github.com/gitify-app/gitify/releases/tag/v0.0.1',
+    );
+  });
+
+  it('should open account management', async () => {
+    await act(async () => {
+      render(
+        <AppContext.Provider
+          value={{
+            auth: mockAuth,
+            settings: mockSettings,
+          }}
+        >
+          <MemoryRouter>
+            <SettingsFooter />
+          </MemoryRouter>
+        </AppContext.Provider>,
+      );
+    });
+
+    fireEvent.click(screen.getByTitle('Accounts'));
+    expect(mockNavigate).toHaveBeenCalledWith('/accounts');
+  });
+
+  it('should quit the app', async () => {
+    const quitAppMock = jest.spyOn(comms, 'quitApp');
+
+    await act(async () => {
+      render(
+        <AppContext.Provider
+          value={{
+            auth: mockAuth,
+            settings: mockSettings,
+          }}
+        >
+          <MemoryRouter>
+            <SettingsFooter />
+          </MemoryRouter>
+        </AppContext.Provider>,
+      );
+    });
+
+    fireEvent.click(screen.getByTitle('Quit Gitify'));
+    expect(quitAppMock).toHaveBeenCalledTimes(1);
+  });
+});
diff --git a/src/components/settings/SystemSettings.test.tsx b/src/components/settings/SystemSettings.test.tsx
new file mode 100644
index 00000000..3ca7c6df
--- /dev/null
+++ b/src/components/settings/SystemSettings.test.tsx
@@ -0,0 +1,141 @@
+import { act, fireEvent, render, screen } from '@testing-library/react';
+import { MemoryRouter } from 'react-router-dom';
+import { mockAuth, mockSettings } from '../../__mocks__/state-mocks';
+import { AppContext } from '../../context/App';
+import { SystemSettings } from './SystemSettings';
+
+describe('routes/components/SystemSettings.tsx', () => {
+  const updateSetting = jest.fn();
+
+  afterEach(() => {
+    jest.clearAllMocks();
+  });
+
+  it('should toggle the keyboardShortcut checkbox', async () => {
+    await act(async () => {
+      render(
+        <AppContext.Provider
+          value={{
+            auth: mockAuth,
+            settings: mockSettings,
+            updateSetting,
+          }}
+        >
+          <MemoryRouter>
+            <SystemSettings />
+          </MemoryRouter>
+        </AppContext.Provider>,
+      );
+    });
+
+    fireEvent.click(screen.getByLabelText('Enable keyboard shortcut'), {
+      target: { checked: true },
+    });
+
+    expect(updateSetting).toHaveBeenCalledTimes(1);
+    expect(updateSetting).toHaveBeenCalledWith('keyboardShortcut', false);
+  });
+
+  it('should toggle the showNotificationsCountInTray checkbox', async () => {
+    await act(async () => {
+      render(
+        <AppContext.Provider
+          value={{
+            auth: mockAuth,
+            settings: mockSettings,
+            updateSetting,
+          }}
+        >
+          <MemoryRouter>
+            <SystemSettings />
+          </MemoryRouter>
+        </AppContext.Provider>,
+      );
+    });
+
+    fireEvent.click(screen.getByLabelText('Show notifications count in tray'), {
+      target: { checked: true },
+    });
+
+    expect(updateSetting).toHaveBeenCalledTimes(1);
+    expect(updateSetting).toHaveBeenCalledWith(
+      'showNotificationsCountInTray',
+      false,
+    );
+  });
+
+  it('should toggle the showNotifications checkbox', async () => {
+    await act(async () => {
+      render(
+        <AppContext.Provider
+          value={{
+            auth: mockAuth,
+            settings: mockSettings,
+            updateSetting,
+          }}
+        >
+          <MemoryRouter>
+            <SystemSettings />
+          </MemoryRouter>
+        </AppContext.Provider>,
+      );
+    });
+
+    fireEvent.click(screen.getByLabelText('Show system notifications'), {
+      target: { checked: true },
+    });
+
+    expect(updateSetting).toHaveBeenCalledTimes(1);
+    expect(updateSetting).toHaveBeenCalledWith('showNotifications', false);
+  });
+
+  it('should toggle the playSound checkbox', async () => {
+    await act(async () => {
+      render(
+        <AppContext.Provider
+          value={{
+            auth: mockAuth,
+            settings: mockSettings,
+            updateSetting,
+          }}
+        >
+          <MemoryRouter>
+            <SystemSettings />
+          </MemoryRouter>
+        </AppContext.Provider>,
+      );
+    });
+
+    fireEvent.click(screen.getByLabelText('Play sound'), {
+      target: { checked: true },
+    });
+
+    expect(updateSetting).toHaveBeenCalledTimes(1);
+    expect(updateSetting).toHaveBeenCalledWith('playSound', false);
+  });
+
+  it('should toggle the openAtStartup checkbox', async () => {
+    await act(async () => {
+      render(
+        <AppContext.Provider
+          value={{
+            auth: mockAuth,
+            settings: mockSettings,
+            updateSetting,
+          }}
+        >
+          <MemoryRouter>
+            <SystemSettings />
+          </MemoryRouter>
+        </AppContext.Provider>,
+      );
+    });
+
+    fireEvent.click(screen.getByLabelText('Open at startup'), {
+      target: { checked: true },
+    });
+
+    expect(updateSetting).toHaveBeenCalledTimes(1);
+    expect(updateSetting).toHaveBeenCalledWith('openAtStartup', false);
+  });
+});
diff --git a/src/components/settings/__snapshots__/NotificationSettings.test.tsx.snap b/src/components/settings/__snapshots__/NotificationSettings.test.tsx.snap
new file mode 100644
index 00000000..ec5007e7
--- /dev/null
+++ b/src/components/settings/__snapshots__/NotificationSettings.test.tsx.snap
@@ -0,0 +1,97 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`routes/components/NotificationSettings.tsx should be able to toggle the showBots checkbox when detailedNotifications is enabled 1`] = `
+<div
+  class="flex items-start"
+>
+  <div
+    class="flex h-5 items-center"
+  >
+    <input
+      checked=""
+      class="size-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-500"
+      id="showBots"
+      type="checkbox"
+    />
+  </div>
+  <div
+    class="ml-3"
+  >
+    <label
+      class="font-medium text-gray-700 dark:text-gray-200"
+      for="showBots"
+    >
+      Show notifications from Bot accounts
+      <span
+        aria-label="tooltip-showBots"
+        class="relative"
+        id="tooltip-showBots"
+      >
+        <svg
+          aria-hidden="true"
+          class="ml-1 text-blue-500"
+          fill="currentColor"
+          focusable="false"
+          height="16"
+          style="display: inline-block; user-select: none; vertical-align: text-bottom; overflow: visible;"
+          viewBox="0 0 16 16"
+          width="16"
+        >
+          <path
+            d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Zm8-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM6.92 6.085h.001a.749.749 0 1 1-1.342-.67c.169-.339.436-.701.849-.977C6.845 4.16 7.369 4 8 4a2.756 2.756 0 0 1 1.637.525c.503.377.863.965.863 1.725 0 .448-.115.83-.329 1.15-.205.307-.47.513-.692.662-.109.072-.22.138-.313.195l-.006.004a6.24 6.24 0 0 0-.26.16.952.952 0 0 0-.276.245.75.75 0 0 1-1.248-.832c.184-.264.42-.489.692-.661.103-.067.207-.132.313-.195l.007-.004c.1-.061.182-.11.258-.161a.969.969 0 0 0 .277-.245C8.96 6.514 9 6.427 9 6.25a.612.612 0 0 0-.262-.525A1.27 1.27 0 0 0 8 5.5c-.369 0-.595.09-.74.187a1.01 1.01 0 0 0-.34.398ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"
+          />
+        </svg>
+      </span>
+    </label>
+  </div>
+</div>
+`;
+
+exports[`routes/components/NotificationSettings.tsx should not be able to toggle the showBots checkbox when detailedNotifications is disabled 1`] = `
+<div
+  class="flex items-start"
+>
+  <div
+    class="flex h-5 items-center"
+  >
+    <input
+      checked=""
+      class="size-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-500"
+      disabled=""
+      id="showBots"
+      type="checkbox"
+    />
+  </div>
+  <div
+    class="ml-3"
+  >
+    <label
+      class="font-medium text-gray-700 dark:text-gray-200"
+      for="showBots"
+      style="text-decoration: line-through;"
+    >
+      Show notifications from Bot accounts
+      <span
+        aria-label="tooltip-showBots"
+        class="relative"
+        id="tooltip-showBots"
+      >
+        <svg
+          aria-hidden="true"
+          class="ml-1 text-blue-500"
+          fill="currentColor"
+          focusable="false"
+          height="16"
+          style="display: inline-block; user-select: none; vertical-align: text-bottom; overflow: visible;"
+          viewBox="0 0 16 16"
+          width="16"
+        >
+          <path
+            d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Zm8-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM6.92 6.085h.001a.749.749 0 1 1-1.342-.67c.169-.339.436-.701.849-.977C6.845 4.16 7.369 4 8 4a2.756 2.756 0 0 1 1.637.525c.503.377.863.965.863 1.725 0 .448-.115.83-.329 1.15-.205.307-.47.513-.692.662-.109.072-.22.138-.313.195l-.006.004a6.24 6.24 0 0 0-.26.16.952.952 0 0 0-.276.245.75.75 0 0 1-1.248-.832c.184-.264.42-.489.692-.661.103-.067.207-.132.313-.195l.007-.004c.1-.061.182-.11.258-.161a.969.969 0 0 0 .277-.245C8.96 6.514 9 6.427 9 6.25a.612.612 0 0 0-.262-.525A1.27 1.27 0 0 0 8 5.5c-.369 0-.595.09-.74.187a1.01 1.01 0 0 0-.34.398ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"
+          />
+        </svg>
+      </span>
+    </label>
+  </div>
+</div>
+`;
diff --git a/src/components/settings/__snapshots__/SettingsFooter.test.tsx.snap b/src/components/settings/__snapshots__/SettingsFooter.test.tsx.snap
new file mode 100644
index 00000000..75e06fc2
--- /dev/null
+++ b/src/components/settings/__snapshots__/SettingsFooter.test.tsx.snap
@@ -0,0 +1,19 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`routes/components/SettingsFooter.tsx app version should show development app version 1`] = `
+<span
+  title="app-version"
+>
+  Gitify 
+  dev
+</span>
+`;
+
+exports[`routes/components/SettingsFooter.tsx app version should show production app version 1`] = `
+<span
+  title="app-version"
+>
+  Gitify 
+  v0.0.1
+</span>
+`;
diff --git a/src/routes/Settings.test.tsx b/src/routes/Settings.test.tsx
index 333cc184..962984fa 100644
--- a/src/routes/Settings.test.tsx
+++ b/src/routes/Settings.test.tsx
@@ -3,7 +3,6 @@ import { MemoryRouter } from 'react-router-dom';
 import { mockAuth, mockSettings } from '../__mocks__/state-mocks';
 import { mockPlatform } from '../__mocks__/utils';
 import { AppContext } from '../context/App';
-import * as comms from '../utils/comms';
 import { SettingsRoute } from './Settings';
 
 const mockNavigate = jest.fn();
@@ -14,7 +13,6 @@ jest.mock('react-router-dom', () => ({
 
 describe('routes/Settings.tsx', () => {
   let originalPlatform: NodeJS.Platform;
-  const updateSetting = jest.fn();
   const fetchNotifications = jest.fn();
 
   beforeAll(() => {
@@ -39,631 +37,39 @@ describe('routes/Settings.tsx', () => {
     });
   });
 
-  describe('General', () => {
-    it('should render itself & its children', async () => {
-      await act(async () => {
-        render(
-          <AppContext.Provider
-            value={{ auth: mockAuth, settings: mockSettings }}
-          >
-            <MemoryRouter>
-              <SettingsRoute />
-            </MemoryRouter>
-          </AppContext.Provider>,
-        );
-      });
-
-      expect(screen.getByTestId('settings')).toMatchSnapshot();
-    });
-
-    it('should go back by pressing the icon', async () => {
-      await act(async () => {
-        render(
-          <AppContext.Provider
-            value={{
-              auth: mockAuth,
-              settings: mockSettings,
-              fetchNotifications,
-            }}
-          >
-            <MemoryRouter>
-              <SettingsRoute />
-            </MemoryRouter>
-          </AppContext.Provider>,
-        );
-      });
-
-      fireEvent.click(screen.getByLabelText('Go Back'));
-      expect(fetchNotifications).toHaveBeenCalledTimes(1);
-      expect(mockNavigate).toHaveBeenNthCalledWith(1, -1);
-    });
-  });
-  describe('Appearance section', () => {
-    it('should change the theme radio group', async () => {
-      await act(async () => {
-        render(
-          <AppContext.Provider
-            value={{
-              auth: mockAuth,
-              settings: mockSettings,
-              updateSetting,
-            }}
-          >
-            <MemoryRouter>
-              <SettingsRoute />
-            </MemoryRouter>
-          </AppContext.Provider>,
-        );
-      });
-
-      fireEvent.click(screen.getByLabelText('Light'));
-
-      expect(updateSetting).toHaveBeenCalledTimes(1);
-      expect(updateSetting).toHaveBeenCalledWith('theme', 'LIGHT');
-    });
-
-    it('should toggle detailed notifications checkbox', async () => {
-      await act(async () => {
-        render(
-          <AppContext.Provider
-            value={{
-              auth: mockAuth,
-              settings: mockSettings,
-              updateSetting,
-            }}
-          >
-            <MemoryRouter>
-              <SettingsRoute />
-            </MemoryRouter>
-          </AppContext.Provider>,
-        );
-      });
-
-      await screen.findByLabelText('Detailed notifications');
-
-      fireEvent.click(screen.getByLabelText('Detailed notifications'));
-
-      expect(updateSetting).toHaveBeenCalledTimes(1);
-      expect(updateSetting).toHaveBeenCalledWith(
-        'detailedNotifications',
-        false,
+  it('should render itself & its children', async () => {
+    await act(async () => {
+      render(
+        <AppContext.Provider value={{ auth: mockAuth, settings: mockSettings }}>
+          <MemoryRouter>
+            <SettingsRoute />
+          </MemoryRouter>
+        </AppContext.Provider>,
       );
     });
 
-    it('should toggle metric pills checkbox', async () => {
-      await act(async () => {
-        render(
-          <AppContext.Provider
-            value={{
-              auth: mockAuth,
-              settings: mockSettings,
-              updateSetting,
-            }}
-          >
-            <MemoryRouter>
-              <SettingsRoute />
-            </MemoryRouter>
-          </AppContext.Provider>,
-        );
-      });
-
-      await screen.findByLabelText('Show notification metric pills');
-
-      fireEvent.click(screen.getByLabelText('Show notification metric pills'));
-
-      expect(updateSetting).toHaveBeenCalledTimes(1);
-      expect(updateSetting).toHaveBeenCalledWith('showPills', false);
-    });
-
-    it('should toggle account hostname checkbox', async () => {
-      await act(async () => {
-        render(
-          <AppContext.Provider
-            value={{
-              auth: mockAuth,
-              settings: mockSettings,
-              updateSetting,
-            }}
-          >
-            <MemoryRouter>
-              <SettingsRoute />
-            </MemoryRouter>
-          </AppContext.Provider>,
-        );
-      });
-
-      await screen.findByLabelText('Show account hostname');
-
-      fireEvent.click(screen.getByLabelText('Show account hostname'));
-
-      expect(updateSetting).toHaveBeenCalledTimes(1);
-      expect(updateSetting).toHaveBeenCalledWith('showAccountHostname', true);
-    });
+    expect(screen.getByTestId('settings')).toMatchSnapshot();
   });
 
-  describe('Notifications section', () => {
-    it('should change the groupBy radio group', async () => {
-      await act(async () => {
-        render(
-          <AppContext.Provider
-            value={{
-              auth: mockAuth,
-              settings: mockSettings,
-              updateSetting,
-            }}
-          >
-            <MemoryRouter>
-              <SettingsRoute />
-            </MemoryRouter>
-          </AppContext.Provider>,
-        );
-      });
-
-      fireEvent.click(screen.getByLabelText('Date'));
-
-      expect(updateSetting).toHaveBeenCalledTimes(1);
-      expect(updateSetting).toHaveBeenCalledWith('groupBy', 'DATE');
-    });
-    it('should toggle the showOnlyParticipating checkbox', async () => {
-      await act(async () => {
-        render(
-          <AppContext.Provider
-            value={{
-              auth: mockAuth,
-              settings: mockSettings,
-              updateSetting,
-            }}
-          >
-            <MemoryRouter>
-              <SettingsRoute />
-            </MemoryRouter>
-          </AppContext.Provider>,
-        );
-      });
-
-      fireEvent.click(screen.getByLabelText('Show only participating'), {
-        target: { checked: true },
-      });
-
-      expect(updateSetting).toHaveBeenCalledTimes(1);
-      expect(updateSetting).toHaveBeenCalledWith('participating', false);
-    });
-
-    it('should open official docs for showOnlyParticipating tooltip', async () => {
-      const openExternalLinkMock = jest.spyOn(comms, 'openExternalLink');
-
-      await act(async () => {
-        render(
-          <AppContext.Provider
-            value={{
-              auth: mockAuth,
-              settings: mockSettings,
-              updateSetting,
-            }}
-          >
-            <MemoryRouter>
-              <SettingsRoute />
-            </MemoryRouter>
-          </AppContext.Provider>,
-        );
-      });
-
-      const tooltipElement = screen.getByLabelText(
-        'tooltip-showOnlyParticipating',
-      );
-
-      fireEvent.mouseEnter(tooltipElement);
-
-      fireEvent.click(
-        screen.getByTitle(
-          'Open GitHub documentation for participating and watching notifications',
-        ),
-      );
-
-      expect(openExternalLinkMock).toHaveBeenCalledTimes(1);
-      expect(openExternalLinkMock).toHaveBeenCalledWith(
-        'https://docs.github.com/en/account-and-profile/managing-subscriptions-and-notifications-on-github/setting-up-notifications/configuring-notifications#about-participating-and-watching-notifications',
-      );
-    });
-
-    it('should not be able to toggle the showBots checkbox when detailedNotifications is disabled', async () => {
-      await act(async () => {
-        render(
-          <AppContext.Provider
-            value={{
-              auth: mockAuth,
-              settings: {
-                ...mockSettings,
-                detailedNotifications: false,
-                showBots: true,
-              },
-              updateSetting,
-            }}
-          >
-            <MemoryRouter>
-              <SettingsRoute />
-            </MemoryRouter>
-          </AppContext.Provider>,
-        );
-      });
-
-      expect(
-        screen
-          .getByLabelText('Show notifications from Bot accounts')
-          .closest('input'),
-      ).toHaveProperty('disabled', true);
-
-      // click the checkbox
-      fireEvent.click(
-        screen.getByLabelText('Show notifications from Bot accounts'),
+  it('should go back by pressing the icon', async () => {
+    await act(async () => {
+      render(
+        <AppContext.Provider
+          value={{
+            auth: mockAuth,
+            settings: mockSettings,
+            fetchNotifications,
+          }}
+        >
+          <MemoryRouter>
+            <SettingsRoute />
+          </MemoryRouter>
+        </AppContext.Provider>,
       );
-
-      // check if the checkbox is still unchecked
-      expect(updateSetting).not.toHaveBeenCalled();
-
-      expect(
-        screen.getByLabelText('Show notifications from Bot accounts').parentNode
-          .parentNode,
-      ).toMatchSnapshot();
     });
 
-    it('should be able to toggle the showBots checkbox when detailedNotifications is enabled', async () => {
-      await act(async () => {
-        render(
-          <AppContext.Provider
-            value={{
-              auth: mockAuth,
-              settings: {
-                ...mockSettings,
-                detailedNotifications: true,
-                showBots: true,
-              },
-              updateSetting,
-            }}
-          >
-            <MemoryRouter>
-              <SettingsRoute />
-            </MemoryRouter>
-          </AppContext.Provider>,
-        );
-      });
-
-      expect(
-        screen
-          .getByLabelText('Show notifications from Bot accounts')
-          .closest('input'),
-      ).toHaveProperty('disabled', false);
-
-      // click the checkbox
-      fireEvent.click(
-        screen.getByLabelText('Show notifications from Bot accounts'),
-      );
-
-      // check if the checkbox is still unchecked
-      expect(updateSetting).toHaveBeenCalledWith('showBots', false);
-
-      expect(
-        screen.getByLabelText('Show notifications from Bot accounts').parentNode
-          .parentNode,
-      ).toMatchSnapshot();
-    });
-
-    it('should toggle the markAsDoneOnOpen checkbox', async () => {
-      await act(async () => {
-        render(
-          <AppContext.Provider
-            value={{
-              auth: mockAuth,
-              settings: mockSettings,
-              updateSetting,
-            }}
-          >
-            <MemoryRouter>
-              <SettingsRoute />
-            </MemoryRouter>
-          </AppContext.Provider>,
-        );
-      });
-
-      fireEvent.click(screen.getByLabelText('Mark as done on open'), {
-        target: { checked: true },
-      });
-
-      expect(updateSetting).toHaveBeenCalledTimes(1);
-      expect(updateSetting).toHaveBeenCalledWith('markAsDoneOnOpen', false);
-    });
-
-    it('should toggle the delayNotificationState checkbox', async () => {
-      await act(async () => {
-        render(
-          <AppContext.Provider
-            value={{
-              auth: mockAuth,
-              settings: mockSettings,
-              updateSetting,
-            }}
-          >
-            <MemoryRouter>
-              <SettingsRoute />
-            </MemoryRouter>
-          </AppContext.Provider>,
-        );
-      });
-
-      fireEvent.click(screen.getByLabelText('Delay notification state'), {
-        target: { checked: true },
-      });
-
-      expect(updateSetting).toHaveBeenCalledTimes(1);
-      expect(updateSetting).toHaveBeenCalledWith(
-        'delayNotificationState',
-        false,
-      );
-    });
-  });
-
-  describe('System section', () => {
-    it('should toggle the keyboardShortcut checkbox', async () => {
-      await act(async () => {
-        render(
-          <AppContext.Provider
-            value={{
-              auth: mockAuth,
-              settings: mockSettings,
-              updateSetting,
-            }}
-          >
-            <MemoryRouter>
-              <SettingsRoute />
-            </MemoryRouter>
-          </AppContext.Provider>,
-        );
-      });
-
-      fireEvent.click(screen.getByLabelText('Enable keyboard shortcut'), {
-        target: { checked: true },
-      });
-
-      expect(updateSetting).toHaveBeenCalledTimes(1);
-      expect(updateSetting).toHaveBeenCalledWith('keyboardShortcut', false);
-    });
-
-    it('should toggle the showNotificationsCountInTray checkbox', async () => {
-      await act(async () => {
-        render(
-          <AppContext.Provider
-            value={{
-              auth: mockAuth,
-              settings: mockSettings,
-              updateSetting,
-            }}
-          >
-            <MemoryRouter>
-              <SettingsRoute />
-            </MemoryRouter>
-          </AppContext.Provider>,
-        );
-      });
-
-      fireEvent.click(
-        screen.getByLabelText('Show notifications count in tray'),
-        {
-          target: { checked: true },
-        },
-      );
-
-      expect(updateSetting).toHaveBeenCalledTimes(1);
-      expect(updateSetting).toHaveBeenCalledWith(
-        'showNotificationsCountInTray',
-        false,
-      );
-    });
-
-    it('should toggle the showNotifications checkbox', async () => {
-      await act(async () => {
-        render(
-          <AppContext.Provider
-            value={{
-              auth: mockAuth,
-              settings: mockSettings,
-              updateSetting,
-            }}
-          >
-            <MemoryRouter>
-              <SettingsRoute />
-            </MemoryRouter>
-          </AppContext.Provider>,
-        );
-      });
-
-      fireEvent.click(screen.getByLabelText('Show system notifications'), {
-        target: { checked: true },
-      });
-
-      expect(updateSetting).toHaveBeenCalledTimes(1);
-      expect(updateSetting).toHaveBeenCalledWith('showNotifications', false);
-    });
-
-    it('should toggle the playSound checkbox', async () => {
-      await act(async () => {
-        render(
-          <AppContext.Provider
-            value={{
-              auth: mockAuth,
-              settings: mockSettings,
-              updateSetting,
-            }}
-          >
-            <MemoryRouter>
-              <SettingsRoute />
-            </MemoryRouter>
-          </AppContext.Provider>,
-        );
-      });
-
-      fireEvent.click(screen.getByLabelText('Play sound'), {
-        target: { checked: true },
-      });
-
-      expect(updateSetting).toHaveBeenCalledTimes(1);
-      expect(updateSetting).toHaveBeenCalledWith('playSound', false);
-    });
-
-    it('should toggle the openAtStartup checkbox', async () => {
-      await act(async () => {
-        render(
-          <AppContext.Provider
-            value={{
-              auth: mockAuth,
-              settings: mockSettings,
-              updateSetting,
-            }}
-          >
-            <MemoryRouter>
-              <SettingsRoute />
-            </MemoryRouter>
-          </AppContext.Provider>,
-        );
-      });
-
-      fireEvent.click(screen.getByLabelText('Open at startup'), {
-        target: { checked: true },
-      });
-
-      expect(updateSetting).toHaveBeenCalledTimes(1);
-      expect(updateSetting).toHaveBeenCalledWith('openAtStartup', false);
-    });
-  });
-
-  describe('Footer section', () => {
-    describe('app version', () => {
-      let originalEnv: NodeJS.ProcessEnv;
-
-      beforeEach(() => {
-        // Save the original node env state
-        originalEnv = process.env;
-      });
-
-      afterEach(() => {
-        // Restore the original node env state
-        process.env = originalEnv;
-      });
-
-      it('should show production app version', async () => {
-        process.env = {
-          ...originalEnv,
-          NODE_ENV: 'production',
-        };
-
-        await act(async () => {
-          render(
-            <AppContext.Provider
-              value={{
-                auth: mockAuth,
-                settings: mockSettings,
-              }}
-            >
-              <MemoryRouter>
-                <SettingsRoute />
-              </MemoryRouter>
-            </AppContext.Provider>,
-          );
-        });
-
-        expect(screen.getByTitle('app-version')).toMatchSnapshot();
-      });
-
-      it('should show development app version', async () => {
-        process.env = {
-          ...originalEnv,
-          NODE_ENV: 'development',
-        };
-
-        await act(async () => {
-          render(
-            <AppContext.Provider
-              value={{
-                auth: mockAuth,
-                settings: mockSettings,
-              }}
-            >
-              <MemoryRouter>
-                <SettingsRoute />
-              </MemoryRouter>
-            </AppContext.Provider>,
-          );
-        });
-
-        expect(screen.getByTitle('app-version')).toMatchSnapshot();
-      });
-    });
-
-    it('should open release notes', async () => {
-      const openExternalLinkMock = jest.spyOn(comms, 'openExternalLink');
-
-      await act(async () => {
-        render(
-          <AppContext.Provider
-            value={{
-              auth: mockAuth,
-              settings: mockSettings,
-            }}
-          >
-            <MemoryRouter>
-              <SettingsRoute />
-            </MemoryRouter>
-          </AppContext.Provider>,
-        );
-      });
-
-      fireEvent.click(screen.getByTitle('View release notes'));
-
-      expect(openExternalLinkMock).toHaveBeenCalledTimes(1);
-      expect(openExternalLinkMock).toHaveBeenCalledWith(
-        'https://github.com/gitify-app/gitify/releases/tag/v0.0.1',
-      );
-    });
-
-    it('should open account management', async () => {
-      await act(async () => {
-        render(
-          <AppContext.Provider
-            value={{
-              auth: mockAuth,
-              settings: mockSettings,
-            }}
-          >
-            <MemoryRouter>
-              <SettingsRoute />
-            </MemoryRouter>
-          </AppContext.Provider>,
-        );
-      });
-
-      fireEvent.click(screen.getByTitle('Accounts'));
-      expect(mockNavigate).toHaveBeenCalledWith('/accounts');
-    });
-
-    it('should quit the app', async () => {
-      const quitAppMock = jest.spyOn(comms, 'quitApp');
-
-      await act(async () => {
-        render(
-          <AppContext.Provider
-            value={{
-              auth: mockAuth,
-              settings: mockSettings,
-            }}
-          >
-            <MemoryRouter>
-              <SettingsRoute />
-            </MemoryRouter>
-          </AppContext.Provider>,
-        );
-      });
-
-      fireEvent.click(screen.getByTitle('Quit Gitify'));
-      expect(quitAppMock).toHaveBeenCalledTimes(1);
-    });
+    fireEvent.click(screen.getByLabelText('Go Back'));
+    expect(fetchNotifications).toHaveBeenCalledTimes(1);
+    expect(mockNavigate).toHaveBeenNthCalledWith(1, -1);
   });
 });
diff --git a/src/routes/__snapshots__/Settings.test.tsx.snap b/src/routes/__snapshots__/Settings.test.tsx.snap
index 7dc30886..d5d0b6cf 100644
--- a/src/routes/__snapshots__/Settings.test.tsx.snap
+++ b/src/routes/__snapshots__/Settings.test.tsx.snap
@@ -1,24 +1,6 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
-exports[`routes/Settings.tsx Footer section app version should show development app version 1`] = `
-<span
-  title="app-version"
->
-  Gitify 
-  dev
-</span>
-`;
-
-exports[`routes/Settings.tsx Footer section app version should show production app version 1`] = `
-<span
-  title="app-version"
->
-  Gitify 
-  v0.0.1
-</span>
-`;
-
-exports[`routes/Settings.tsx General should render itself & its children 1`] = `
+exports[`routes/Settings.tsx should render itself & its children 1`] = `
 <div
   class="flex h-screen flex-col"
   data-testid="settings"
@@ -734,99 +716,3 @@ exports[`routes/Settings.tsx General should render itself & its children 1`] = `
   </div>
 </div>
 `;
-
-exports[`routes/Settings.tsx Notifications section should be able to toggle the showBots checkbox when detailedNotifications is enabled 1`] = `
-<div
-  class="flex items-start"
->
-  <div
-    class="flex h-5 items-center"
-  >
-    <input
-      checked=""
-      class="size-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-500"
-      id="showBots"
-      type="checkbox"
-    />
-  </div>
-  <div
-    class="ml-3"
-  >
-    <label
-      class="font-medium text-gray-700 dark:text-gray-200"
-      for="showBots"
-    >
-      Show notifications from Bot accounts
-      <span
-        aria-label="tooltip-showBots"
-        class="relative"
-        id="tooltip-showBots"
-      >
-        <svg
-          aria-hidden="true"
-          class="ml-1 text-blue-500"
-          fill="currentColor"
-          focusable="false"
-          height="16"
-          style="display: inline-block; user-select: none; vertical-align: text-bottom; overflow: visible;"
-          viewBox="0 0 16 16"
-          width="16"
-        >
-          <path
-            d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Zm8-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM6.92 6.085h.001a.749.749 0 1 1-1.342-.67c.169-.339.436-.701.849-.977C6.845 4.16 7.369 4 8 4a2.756 2.756 0 0 1 1.637.525c.503.377.863.965.863 1.725 0 .448-.115.83-.329 1.15-.205.307-.47.513-.692.662-.109.072-.22.138-.313.195l-.006.004a6.24 6.24 0 0 0-.26.16.952.952 0 0 0-.276.245.75.75 0 0 1-1.248-.832c.184-.264.42-.489.692-.661.103-.067.207-.132.313-.195l.007-.004c.1-.061.182-.11.258-.161a.969.969 0 0 0 .277-.245C8.96 6.514 9 6.427 9 6.25a.612.612 0 0 0-.262-.525A1.27 1.27 0 0 0 8 5.5c-.369 0-.595.09-.74.187a1.01 1.01 0 0 0-.34.398ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"
-          />
-        </svg>
-      </span>
-    </label>
-  </div>
-</div>
-`;
-
-exports[`routes/Settings.tsx Notifications section should not be able to toggle the showBots checkbox when detailedNotifications is disabled 1`] = `
-<div
-  class="flex items-start"
->
-  <div
-    class="flex h-5 items-center"
-  >
-    <input
-      checked=""
-      class="size-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-500"
-      disabled=""
-      id="showBots"
-      type="checkbox"
-    />
-  </div>
-  <div
-    class="ml-3"
-  >
-    <label
-      class="font-medium text-gray-700 dark:text-gray-200"
-      for="showBots"
-      style="text-decoration: line-through;"
-    >
-      Show notifications from Bot accounts
-      <span
-        aria-label="tooltip-showBots"
-        class="relative"
-        id="tooltip-showBots"
-      >
-        <svg
-          aria-hidden="true"
-          class="ml-1 text-blue-500"
-          fill="currentColor"
-          focusable="false"
-          height="16"
-          style="display: inline-block; user-select: none; vertical-align: text-bottom; overflow: visible;"
-          viewBox="0 0 16 16"
-          width="16"
-        >
-          <path
-            d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Zm8-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM6.92 6.085h.001a.749.749 0 1 1-1.342-.67c.169-.339.436-.701.849-.977C6.845 4.16 7.369 4 8 4a2.756 2.756 0 0 1 1.637.525c.503.377.863.965.863 1.725 0 .448-.115.83-.329 1.15-.205.307-.47.513-.692.662-.109.072-.22.138-.313.195l-.006.004a6.24 6.24 0 0 0-.26.16.952.952 0 0 0-.276.245.75.75 0 0 1-1.248-.832c.184-.264.42-.489.692-.661.103-.067.207-.132.313-.195l.007-.004c.1-.061.182-.11.258-.161a.969.969 0 0 0 .277-.245C8.96 6.514 9 6.427 9 6.25a.612.612 0 0 0-.262-.525A1.27 1.27 0 0 0 8 5.5c-.369 0-.595.09-.74.187a1.01 1.01 0 0 0-.34.398ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"
-          />
-        </svg>
-      </span>
-    </label>
-  </div>
-</div>
-`;