Repository: gitify-app/gitify
Pull request #1464 (2024-08-12 21:55:47)

refactor: rename `showAccountHostname` to `showAccountHeader`
From our simple beginnings as a `hostname` only account header, we have grown to now show avatar, login name, account-specific quick links and more. 

As such, it's time to rename these vars.

The setting rename is somewhat breaking, but given it's a setting that isn't that commonly used (assumption), i think it's OK


Patch:
diff --git a/package.json b/package.json
index 1b35ae54..210f4725 100644
--- a/package.json
+++ b/package.json
@@ -102,7 +102,7 @@
   "dependencies": {
     "@electron/remote": "2.1.2",
     "@primer/octicons-react": "19.11.0",
-    "axios": "1.7.3",
+    "axios": "1.7.4",
     "class-variance-authority": "0.7.0",
     "clsx": "2.1.1",
     "date-fns": "3.6.0",
@@ -115,7 +115,7 @@
     "react-dom": "18.3.1",
     "react-final-form": "6.5.9",
     "react-router-dom": "6.26.0",
-    "tailwind-merge": "2.5.0",
+    "tailwind-merge": "2.5.2",
     "ts-loader": "9.5.1",
     "typescript": "5.5.4",
     "update-electron-app": "3.0.0"
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 0d23deb1..4311b39e 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -15,8 +15,8 @@ importers:
         specifier: 19.11.0
         version: 19.11.0(react@18.3.1)
       axios:
-        specifier: 1.7.3
-        version: 1.7.3
+        specifier: 1.7.4
+        version: 1.7.4
       class-variance-authority:
         specifier: 0.7.0
         version: 0.7.0
@@ -54,8 +54,8 @@ importers:
         specifier: 6.26.0
         version: 6.26.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
       tailwind-merge:
-        specifier: 2.5.0
-        version: 2.5.0
+        specifier: 2.5.2
+        version: 2.5.2
       ts-loader:
         specifier: 9.5.1
         version: 9.5.1(typescript@5.5.4)(webpack@5.93.0(webpack-cli@5.1.4))
@@ -944,8 +944,8 @@ packages:
     peerDependencies:
       postcss: ^8.1.0
 
-  axios@1.7.3:
-    resolution: {integrity: sha512-Ar7ND9pU99eJ9GpoGQKhKf58GpUOgnzuaB7ueNQ5BMi0p+LZ5oaEnfF999fAArcTIBwXTCHAmGcHOZJaWPq9Nw==}
+  axios@1.7.4:
+    resolution: {integrity: sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==}
 
   babel-jest@29.7.0:
     resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==}
@@ -2894,8 +2894,8 @@ packages:
   symbol-tree@3.2.4:
     resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
 
-  tailwind-merge@2.5.0:
-    resolution: {integrity: sha512-a6Q/isR5XAo9IR7Hjh80BQDkn8PG9ONJpSO/U3vGzdKyKG125lPHNXdiPfeQ5X0EOG0qKlS/0qnxdBYkLlD6tA==}
+  tailwind-merge@2.5.2:
+    resolution: {integrity: sha512-kjEBm+pvD+6eAwzJL2Bi+02/9LFLal1Gs61+QB7HvTfQQ0aXwC5LGT8PEt1gS0CWKktKe6ysPTAy3cBC5MeiIg==}
 
   tailwindcss@3.4.9:
     resolution: {integrity: sha512-1SEOvRr6sSdV5IDf9iC+NU4dhwdqzF4zKKq3sAbasUWHEM6lsMhX+eNN5gkPx1BvLFEnZQEUFbXnGj8Qlp83Pg==}
@@ -4275,7 +4275,7 @@ snapshots:
       postcss: 8.4.41
       postcss-value-parser: 4.2.0
 
-  axios@1.7.3:
+  axios@1.7.4:
     dependencies:
       follow-redirects: 1.15.6
       form-data: 4.0.0
@@ -6430,7 +6430,7 @@ snapshots:
 
   symbol-tree@3.2.4: {}
 
-  tailwind-merge@2.5.0: {}
+  tailwind-merge@2.5.2: {}
 
   tailwindcss@3.4.9(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4)):
     dependencies:
diff --git a/src/__mocks__/state-mocks.ts b/src/__mocks__/state-mocks.ts
index 68f46e8e..12b3f638 100644
--- a/src/__mocks__/state-mocks.ts
+++ b/src/__mocks__/state-mocks.ts
@@ -80,7 +80,7 @@ const mockAppearanceSettings = {
   detailedNotifications: true,
   showPills: true,
   showNumber: true,
-  showAccountHostname: false,
+  showAccountHeader: false,
 };
 
 const mockNotificationSettings = {
diff --git a/src/components/AccountNotifications.tsx b/src/components/AccountNotifications.tsx
index 4e3a2c97..6ed8afa8 100644
--- a/src/components/AccountNotifications.tsx
+++ b/src/components/AccountNotifications.tsx
@@ -29,13 +29,13 @@ interface IAccountNotifications {
   account: Account;
   notifications: Notification[];
   error: GitifyError | null;
-  showAccountHostname: boolean;
+  showAccountHeader: boolean;
 }
 
 export const AccountNotifications: FC<IAccountNotifications> = (
   props: IAccountNotifications,
 ) => {
-  const { account, showAccountHostname, notifications } = props;
+  const { account, showAccountHeader, notifications } = props;
 
   const { settings } = useContext(AppContext);
 
@@ -81,7 +81,7 @@ export const AccountNotifications: FC<IAccountNotifications> = (
 
   return (
     <>
-      {showAccountHostname && (
+      {showAccountHeader && (
         <div
           className={cn(
             'group flex items-center justify-between px-3 py-1.5 text-sm font-semibold dark:text-white',
diff --git a/src/components/settings/AppearanceSettings.tsx b/src/components/settings/AppearanceSettings.tsx
index 7d2f4cf6..6d5066be 100644
--- a/src/components/settings/AppearanceSettings.tsx
+++ b/src/components/settings/AppearanceSettings.tsx
@@ -196,11 +196,11 @@ export const AppearanceSettings: FC = () => {
         }
       />
       <Checkbox
-        name="showAccountHostname"
-        label="Show account hostname"
-        checked={settings.showAccountHostname}
+        name="showAccountHeader"
+        label="Show account header"
+        checked={settings.showAccountHeader}
         onChange={(evt) =>
-          updateSetting('showAccountHostname', evt.target.checked)
+          updateSetting('showAccountHeader', evt.target.checked)
         }
       />
     </fieldset>
diff --git a/src/context/App.tsx b/src/context/App.tsx
index a1db7ece..b0291548 100644
--- a/src/context/App.tsx
+++ b/src/context/App.tsx
@@ -59,7 +59,7 @@ const defaultAppearanceSettings = {
   detailedNotifications: true,
   showPills: true,
   showNumber: true,
-  showAccountHostname: false,
+  showAccountHeader: false,
 };
 
 const defaultNotificationSettings = {
diff --git a/src/electron/main.js b/src/electron/main.js
index 34d0b975..9bbb7bd9 100644
--- a/src/electron/main.js
+++ b/src/electron/main.js
@@ -17,10 +17,6 @@ const { autoUpdater } = require('electron-updater');
 const { updateElectronApp } = require('update-electron-app');
 
 log.initialize();
-
-// TODO: Remove @electron/remote use - see #650
-require('@electron/remote/main').initialize();
-
 // Tray Icons
 const idleIcon = path.resolve(
   `${__dirname}/../../assets/images/tray-idleTemplate.png`,
@@ -124,6 +120,13 @@ app.whenReady().then(async () => {
   mb.on('ready', () => {
     mb.app.setAppUserModelId('com.electron.gitify');
 
+    /**
+     * TODO: Remove @electron/remote use - see #650
+     * GitHub OAuth 2 Login Flows - Enable Remote Browser Window Launch
+     */
+    require('@electron/remote/main').initialize();
+    require('@electron/remote/main').enable(mb.window.webContents);
+
     // Tray configuration
     mb.tray.setToolTip('Gitify');
     mb.tray.setIgnoreDoubleClickEvents(true);
diff --git a/src/routes/Login.tsx b/src/routes/Login.tsx
index 325c019d..2a661eb6 100644
--- a/src/routes/Login.tsx
+++ b/src/routes/Login.tsx
@@ -1,5 +1,6 @@
-import { KeyIcon, PersonIcon } from '@primer/octicons-react';
-import { type FC, useContext, useEffect } from 'react';
+import { KeyIcon, MarkGithubIcon, PersonIcon } from '@primer/octicons-react';
+import log from 'electron-log';
+import { type FC, useCallback, useContext, useEffect } from 'react';
 import { useNavigate } from 'react-router-dom';
 import { Button } from '../components/buttons/Button';
 import { LogoIcon } from '../components/icons/LogoIcon';
@@ -9,7 +10,7 @@ import { showWindow } from '../utils/comms';
 
 export const LoginRoute: FC = () => {
   const navigate = useNavigate();
-  const { isLoggedIn } = useContext(AppContext);
+  const { loginWithGitHubApp, isLoggedIn } = useContext(AppContext);
 
   useEffect(() => {
     if (isLoggedIn) {
@@ -18,14 +19,13 @@ export const LoginRoute: FC = () => {
     }
   }, [isLoggedIn]);
 
-  // FIXME: Temporarily disable Login with GitHub (OAuth) as it's currently broken and requires a rewrite - see #485 #561 #747
-  /* const loginUser = useCallback(async () => {
+  const loginUser = useCallback(async () => {
     try {
-      await login();
+      await loginWithGitHubApp();
     } catch (err) {
       log.error('Auth: failed to login with GitHub', err);
     }
-  }, []); */
+  }, [loginWithGitHubApp]);
 
   return (
     <div className="flex flex-1 flex-col items-center justify-center p-4">
@@ -36,18 +36,16 @@ export const LoginRoute: FC = () => {
       </div>
 
       <div className="text-center text-sm font-semibold italic">Login with</div>
-      {
-        // FIXME: Temporarily disable Login with GitHub (OAuth) as it's currently broken and requires a rewrite - see #485 #561 #747
-        /*    
-       <Button
+
+      <Button
         name="GitHub"
-        icon={MarkGitHubIcon}
+        icon={{ icon: MarkGithubIcon }}
         label="Login with GitHub"
-        class="w-50 px-2 py-2 my-2 text-xs"
-        onClick={loginUser}
-      />  
-      */
-      }
+        className="mt-2 py-2"
+        onClick={() => loginUser()}
+      >
+        GitHub
+      </Button>
 
       <Button
         icon={{ icon: KeyIcon }}
diff --git a/src/routes/LoginWithOAuthApp.tsx b/src/routes/LoginWithOAuthApp.tsx
index 49567005..3d560b69 100644
--- a/src/routes/LoginWithOAuthApp.tsx
+++ b/src/routes/LoginWithOAuthApp.tsx
@@ -2,6 +2,7 @@ import { BookIcon, PersonIcon, SignInIcon } from '@primer/octicons-react';
 import log from 'electron-log';
 import { type FC, useCallback, useContext } from 'react';
 import { Form, type FormRenderProps } from 'react-final-form';
+import { useNavigate } from 'react-router-dom';
 import { Header } from '../components/Header';
 import { Button } from '../components/buttons/Button';
 import { FieldInput } from '../components/fields/FieldInput';
@@ -59,6 +60,8 @@ export const validate = (values: IValues): IFormErrors => {
 };
 
 export const LoginWithOAuthApp: FC = () => {
+  const navigate = useNavigate();
+
   const { loginWithOAuthApp } = useContext(AppContext);
 
   const renderForm = (formProps: FormRenderProps) => {
@@ -126,9 +129,9 @@ export const LoginWithOAuthApp: FC = () => {
     async (data: IValues) => {
       try {
         await loginWithOAuthApp(data as LoginOAuthAppOptions);
+        navigate(-1);
       } catch (err) {
         log.error('Auth: Failed to login with oauth app', err);
-        // Skip
       }
     },
     [loginWithOAuthApp],
diff --git a/src/routes/Notifications.tsx b/src/routes/Notifications.tsx
index 0e60f1a8..c92430ba 100644
--- a/src/routes/Notifications.tsx
+++ b/src/routes/Notifications.tsx
@@ -42,9 +42,7 @@ export const NotificationsRoute: FC = () => {
           account={accountNotifications.account}
           notifications={accountNotifications.notifications}
           error={accountNotifications.error}
-          showAccountHostname={
-            hasMultipleAccounts || settings.showAccountHostname
-          }
+          showAccountHeader={hasMultipleAccounts || settings.showAccountHeader}
         />
       ))}
     </div>
diff --git a/src/types.ts b/src/types.ts
index 826127dc..d21ffb95 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -69,7 +69,7 @@ interface AppearanceSettingsState {
   theme: Theme;
   zoomPercentage: number;
   detailedNotifications: boolean;
-  showAccountHostname: boolean;
+  showAccountHeader: boolean;
   showPills: boolean;
   showNumber: boolean;
 }
diff --git a/src/utils/auth/utils.ts b/src/utils/auth/utils.ts
index 4a502e28..94a1965b 100644
--- a/src/utils/auth/utils.ts
+++ b/src/utils/auth/utils.ts
@@ -18,6 +18,7 @@ import { Constants } from '../constants';
 import { getPlatformFromHostname } from '../helpers';
 import type { AuthMethod, AuthResponse, AuthTokenResponse } from './types';
 
+// TODO - Refactor our OAuth2 flow to use system browser and local app gitify://callback - see #485 #561 #654
 export function authGitHub(
   authOptions = Constants.DEFAULT_AUTH_OPTIONS,
 ): Promise<AuthResponse> {


Test patch:
diff --git a/src/components/AccountNotifications.test.tsx b/src/components/AccountNotifications.test.tsx
index cfb410fb..e5e844d4 100644
--- a/src/components/AccountNotifications.test.tsx
+++ b/src/components/AccountNotifications.test.tsx
@@ -20,7 +20,7 @@ describe('components/AccountNotifications.tsx', () => {
     const props = {
       account: mockGitHubCloudAccount,
       notifications: mockGitHubNotifications,
-      showAccountHostname: true,
+      showAccountHeader: true,
       error: null,
     };
 
@@ -38,7 +38,7 @@ describe('components/AccountNotifications.tsx', () => {
     const props = {
       account: mockGitHubCloudAccount,
       notifications: mockGitHubNotifications,
-      showAccountHostname: true,
+      showAccountHeader: true,
       error: null,
     };
 
@@ -56,7 +56,7 @@ describe('components/AccountNotifications.tsx', () => {
     const props = {
       account: mockGitHubCloudAccount,
       notifications: [],
-      showAccountHostname: true,
+      showAccountHeader: true,
       error: null,
     };
 
@@ -77,7 +77,7 @@ describe('components/AccountNotifications.tsx', () => {
         descriptions: ['Error description'],
         emojis: ['🔥'],
       },
-      showAccountHostname: true,
+      showAccountHeader: true,
     };
 
     const tree = render(
@@ -96,7 +96,7 @@ describe('components/AccountNotifications.tsx', () => {
     const props = {
       account: mockGitHubCloudAccount,
       notifications: [],
-      showAccountHostname: true,
+      showAccountHeader: true,
       error: null,
     };
 
@@ -122,7 +122,7 @@ describe('components/AccountNotifications.tsx', () => {
     const props = {
       account: mockGitHubCloudAccount,
       notifications: [],
-      showAccountHostname: true,
+      showAccountHeader: true,
       error: null,
     };
 
@@ -150,7 +150,7 @@ describe('components/AccountNotifications.tsx', () => {
     const props = {
       account: mockGitHubCloudAccount,
       notifications: [],
-      showAccountHostname: true,
+      showAccountHeader: true,
       error: null,
     };
 
@@ -174,7 +174,7 @@ describe('components/AccountNotifications.tsx', () => {
     const props = {
       account: mockGitHubCloudAccount,
       notifications: mockGitHubNotifications,
-      showAccountHostname: true,
+      showAccountHeader: true,
       error: null,
     };
 
diff --git a/src/components/settings/AppearanceSettings.test.tsx b/src/components/settings/AppearanceSettings.test.tsx
index 843b39d0..2cbba6fe 100644
--- a/src/components/settings/AppearanceSettings.test.tsx
+++ b/src/components/settings/AppearanceSettings.test.tsx
@@ -195,7 +195,7 @@ describe('routes/components/settings/AppearanceSettings.tsx', () => {
     expect(updateSetting).toHaveBeenCalledWith('showNumber', false);
   });
 
-  it('should toggle account hostname checkbox', async () => {
+  it('should toggle account header checkbox', async () => {
     await act(async () => {
       render(
         <AppContext.Provider
@@ -212,11 +212,11 @@ describe('routes/components/settings/AppearanceSettings.tsx', () => {
       );
     });
 
-    await screen.findByLabelText('Show account hostname');
+    await screen.findByLabelText('Show account header');
 
-    fireEvent.click(screen.getByLabelText('Show account hostname'));
+    fireEvent.click(screen.getByLabelText('Show account header'));
 
     expect(updateSetting).toHaveBeenCalledTimes(1);
-    expect(updateSetting).toHaveBeenCalledWith('showAccountHostname', true);
+    expect(updateSetting).toHaveBeenCalledWith('showAccountHeader', true);
   });
 });
diff --git a/src/context/App.test.tsx b/src/context/App.test.tsx
index f41f887b..3c7fe43b 100644
--- a/src/context/App.test.tsx
+++ b/src/context/App.test.tsx
@@ -354,7 +354,7 @@ describe('context/App.tsx', () => {
           theme: 'SYSTEM',
           detailedNotifications: true,
           markAsDoneOnOpen: false,
-          showAccountHostname: false,
+          showAccountHeader: false,
           delayNotificationState: false,
           showPills: true,
           showNumber: true,
@@ -411,7 +411,7 @@ describe('context/App.tsx', () => {
           theme: 'SYSTEM',
           detailedNotifications: true,
           markAsDoneOnOpen: false,
-          showAccountHostname: false,
+          showAccountHeader: false,
           delayNotificationState: false,
           showPills: true,
           showNumber: true,
diff --git a/src/routes/LoginWithOAuthApp.test.tsx b/src/routes/LoginWithOAuthApp.test.tsx
index 6647363c..3dc1a2ad 100644
--- a/src/routes/LoginWithOAuthApp.test.tsx
+++ b/src/routes/LoginWithOAuthApp.test.tsx
@@ -1,4 +1,4 @@
-import { fireEvent, render, screen } from '@testing-library/react';
+import { fireEvent, render, screen, waitFor } from '@testing-library/react';
 import { MemoryRouter } from 'react-router-dom';
 import { AppContext } from '../context/App';
 import type { AuthState, ClientID, ClientSecret, Hostname } from '../types';
@@ -12,6 +12,8 @@ jest.mock('react-router-dom', () => ({
 }));
 
 describe('routes/LoginWithOAuthApp.tsx', () => {
+  const mockLoginWithOAuthApp = jest.fn();
+
   const openExternalLinkMock = jest
     .spyOn(comms, 'openExternalLink')
     .mockImplementation();
@@ -108,6 +110,39 @@ describe('routes/LoginWithOAuthApp.tsx', () => {
     });
   });
 
+  it('should login using a token - success', async () => {
+    mockLoginWithOAuthApp.mockResolvedValueOnce(null);
+
+    render(
+      <AppContext.Provider
+        value={{
+          loginWithOAuthApp: mockLoginWithOAuthApp,
+        }}
+      >
+        <MemoryRouter>
+          <LoginWithOAuthApp />
+        </MemoryRouter>
+      </AppContext.Provider>,
+    );
+
+    fireEvent.change(screen.getByLabelText('Hostname'), {
+      target: { value: 'github.com' },
+    });
+    fireEvent.change(screen.getByLabelText('Client ID'), {
+      target: { value: '1234567890_ASDFGHJKL' },
+    });
+    fireEvent.change(screen.getByLabelText('Client Secret'), {
+      target: { value: '1234567890_asdfghjklPOIUYTREWQ0987654321' },
+    });
+
+    fireEvent.submit(screen.getByLabelText('Login'));
+
+    await waitFor(() => expect(mockLoginWithOAuthApp).toHaveBeenCalledTimes(1));
+
+    expect(mockLoginWithOAuthApp).toHaveBeenCalledTimes(1);
+    expect(mockNavigate).toHaveBeenNthCalledWith(1, -1);
+  });
+
   it('should render the form with errors', () => {
     render(
       <AppContext.Provider value={{ auth: mockAuth }}>
diff --git a/src/routes/LoginWithPersonalAccessToken.test.tsx b/src/routes/LoginWithPersonalAccessToken.test.tsx
index 9343f816..afcb05b7 100644
--- a/src/routes/LoginWithPersonalAccessToken.test.tsx
+++ b/src/routes/LoginWithPersonalAccessToken.test.tsx
@@ -20,7 +20,7 @@ jest.mock('react-router-dom', () => ({
 }));
 
 describe('routes/LoginWithPersonalAccessToken.tsx', () => {
-  const mockValidateToken = jest.fn();
+  const mockLoginWithPersonalAccessToken = jest.fn();
   const openExternalLinkMock = jest
     .spyOn(comms, 'openExternalLink')
     .mockImplementation();
@@ -75,7 +75,9 @@ describe('routes/LoginWithPersonalAccessToken.tsx', () => {
     it('should be disabled if no hostname configured', async () => {
       render(
         <AppContext.Provider
-          value={{ loginWithPersonalAccessToken: mockValidateToken }}
+          value={{
+            loginWithPersonalAccessToken: mockLoginWithPersonalAccessToken,
+          }}
         >
           <MemoryRouter>
             <LoginWithPersonalAccessToken />
@@ -95,7 +97,9 @@ describe('routes/LoginWithPersonalAccessToken.tsx', () => {
     it('should open in browser if hostname configured', async () => {
       render(
         <AppContext.Provider
-          value={{ loginWithPersonalAccessToken: mockValidateToken }}
+          value={{
+            loginWithPersonalAccessToken: mockLoginWithPersonalAccessToken,
+          }}
         >
           <MemoryRouter>
             <LoginWithPersonalAccessToken />
@@ -110,11 +114,13 @@ describe('routes/LoginWithPersonalAccessToken.tsx', () => {
   });
 
   it('should login using a token - success', async () => {
-    mockValidateToken.mockResolvedValueOnce(null);
+    mockLoginWithPersonalAccessToken.mockResolvedValueOnce(null);
 
     render(
       <AppContext.Provider
-        value={{ loginWithPersonalAccessToken: mockValidateToken }}
+        value={{
+          loginWithPersonalAccessToken: mockLoginWithPersonalAccessToken,
+        }}
       >
         <MemoryRouter>
           <LoginWithPersonalAccessToken />
@@ -131,18 +137,22 @@ describe('routes/LoginWithPersonalAccessToken.tsx', () => {
 
     fireEvent.submit(screen.getByLabelText('Login'));
 
-    await waitFor(() => expect(mockValidateToken).toHaveBeenCalledTimes(1));
+    await waitFor(() =>
+      expect(mockLoginWithPersonalAccessToken).toHaveBeenCalledTimes(1),
+    );
 
-    expect(mockValidateToken).toHaveBeenCalledTimes(1);
+    expect(mockLoginWithPersonalAccessToken).toHaveBeenCalledTimes(1);
     expect(mockNavigate).toHaveBeenNthCalledWith(1, -1);
   });
 
   it('should login using a token - failure', async () => {
-    mockValidateToken.mockRejectedValueOnce(null);
+    mockLoginWithPersonalAccessToken.mockRejectedValueOnce(null);
 
     render(
       <AppContext.Provider
-        value={{ loginWithPersonalAccessToken: mockValidateToken }}
+        value={{
+          loginWithPersonalAccessToken: mockLoginWithPersonalAccessToken,
+        }}
       >
         <MemoryRouter>
           <LoginWithPersonalAccessToken />
@@ -160,9 +170,11 @@ describe('routes/LoginWithPersonalAccessToken.tsx', () => {
       fireEvent.submit(screen.getByLabelText('Login'));
     });
 
-    await waitFor(() => expect(mockValidateToken).toHaveBeenCalledTimes(1));
+    await waitFor(() =>
+      expect(mockLoginWithPersonalAccessToken).toHaveBeenCalledTimes(1),
+    );
 
-    expect(mockValidateToken).toHaveBeenCalledTimes(1);
+    expect(mockLoginWithPersonalAccessToken).toHaveBeenCalledTimes(1);
     expect(mockNavigate).toHaveBeenCalledTimes(0);
   });
 
@@ -189,7 +201,9 @@ describe('routes/LoginWithPersonalAccessToken.tsx', () => {
   it('should open help docs in the browser', async () => {
     render(
       <AppContext.Provider
-        value={{ loginWithPersonalAccessToken: mockValidateToken }}
+        value={{
+          loginWithPersonalAccessToken: mockLoginWithPersonalAccessToken,
+        }}
       >
         <MemoryRouter>
           <LoginWithPersonalAccessToken />
diff --git a/src/routes/Notifications.test.tsx b/src/routes/Notifications.test.tsx
index 207083b4..f883bcc4 100644
--- a/src/routes/Notifications.test.tsx
+++ b/src/routes/Notifications.test.tsx
@@ -37,12 +37,12 @@ describe('routes/Notifications.tsx', () => {
     expect(tree).toMatchSnapshot();
   });
 
-  it('should render itself & its children (show account hostname)', () => {
+  it('should render itself & its children (show account header)', () => {
     const tree = render(
       <AppContext.Provider
         value={{
           notifications: [mockAccountNotifications[0]],
-          settings: { ...mockSettings, showAccountHostname: true },
+          settings: { ...mockSettings, showAccountHeader: true },
         }}
       >
         <NotificationsRoute />
diff --git a/src/routes/__snapshots__/Login.test.tsx.snap b/src/routes/__snapshots__/Login.test.tsx.snap
index b50466a3..e3c8e075 100644
--- a/src/routes/__snapshots__/Login.test.tsx.snap
+++ b/src/routes/__snapshots__/Login.test.tsx.snap
@@ -64,6 +64,28 @@ exports[`routes/Login.tsx should render itself & its children 1`] = `
         >
           Login with
         </div>
+        <button
+          aria-label="Login with GitHub"
+          class="ring-offset-background focus-visible:ring-ring inline-flex w-fit items-center justify-center whitespace-nowrap rounded text-xs font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 bg-gray-300 dark:text-black hover:opacity-90 min-w-20 h-10 px-4 mt-2 py-2"
+          icon="[object Object]"
+          name="GitHub"
+        >
+          <svg
+            aria-hidden="true"
+            class="mr-2"
+            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="M8 0c4.42 0 8 3.58 8 8a8.013 8.013 0 0 1-5.45 7.59c-.4.08-.55-.17-.55-.38 0-.27.01-1.13.01-2.2 0-.75-.25-1.23-.54-1.48 1.78-.2 3.65-.88 3.65-3.95 0-.88-.31-1.59-.82-2.15.08-.2.36-1.02-.08-2.12 0 0-.67-.22-2.2.82-.64-.18-1.32-.27-2-.27-.68 0-1.36.09-2 .27-1.53-1.03-2.2-.82-2.2-.82-.44 1.1-.16 1.92-.08 2.12-.51.56-.82 1.28-.82 2.15 0 3.06 1.86 3.75 3.64 3.95-.23.2-.44.55-.51 1.07-.46.21-1.61.55-2.33-.66-.15-.24-.6-.83-1.23-.82-.67.01-.27.38.01.53.34.19.73.9.82 1.13.16.45.68 1.31 2.69.94 0 .67.01 1.3.01 1.49 0 .21-.15.45-.55.38A7.995 7.995 0 0 1 0 8c0-4.42 3.58-8 8-8Z"
+            />
+          </svg>
+          GitHub
+        </button>
         <button
           aria-label="Login with Personal Access Token"
           class="ring-offset-background focus-visible:ring-ring inline-flex w-fit items-center justify-center whitespace-nowrap rounded text-xs font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 bg-gray-300 dark:text-black hover:opacity-90 min-w-20 h-10 px-4 mt-2 py-2"
@@ -169,6 +191,28 @@ exports[`routes/Login.tsx should render itself & its children 1`] = `
       >
         Login with
       </div>
+      <button
+        aria-label="Login with GitHub"
+        class="ring-offset-background focus-visible:ring-ring inline-flex w-fit items-center justify-center whitespace-nowrap rounded text-xs font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 bg-gray-300 dark:text-black hover:opacity-90 min-w-20 h-10 px-4 mt-2 py-2"
+        icon="[object Object]"
+        name="GitHub"
+      >
+        <svg
+          aria-hidden="true"
+          class="mr-2"
+          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="M8 0c4.42 0 8 3.58 8 8a8.013 8.013 0 0 1-5.45 7.59c-.4.08-.55-.17-.55-.38 0-.27.01-1.13.01-2.2 0-.75-.25-1.23-.54-1.48 1.78-.2 3.65-.88 3.65-3.95 0-.88-.31-1.59-.82-2.15.08-.2.36-1.02-.08-2.12 0 0-.67-.22-2.2.82-.64-.18-1.32-.27-2-.27-.68 0-1.36.09-2 .27-1.53-1.03-2.2-.82-2.2-.82-.44 1.1-.16 1.92-.08 2.12-.51.56-.82 1.28-.82 2.15 0 3.06 1.86 3.75 3.64 3.95-.23.2-.44.55-.51 1.07-.46.21-1.61.55-2.33-.66-.15-.24-.6-.83-1.23-.82-.67.01-.27.38.01.53.34.19.73.9.82 1.13.16.45.68 1.31 2.69.94 0 .67.01 1.3.01 1.49 0 .21-.15.45-.55.38A7.995 7.995 0 0 1 0 8c0-4.42 3.58-8 8-8Z"
+          />
+        </svg>
+        GitHub
+      </button>
       <button
         aria-label="Login with Personal Access Token"
         class="ring-offset-background focus-visible:ring-ring inline-flex w-fit items-center justify-center whitespace-nowrap rounded text-xs font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 bg-gray-300 dark:text-black hover:opacity-90 min-w-20 h-10 px-4 mt-2 py-2"
diff --git a/src/routes/__snapshots__/Notifications.test.tsx.snap b/src/routes/__snapshots__/Notifications.test.tsx.snap
index 8bb99d30..80f0f4d8 100644
--- a/src/routes/__snapshots__/Notifications.test.tsx.snap
+++ b/src/routes/__snapshots__/Notifications.test.tsx.snap
@@ -414,7 +414,7 @@ exports[`routes/Notifications.tsx should render itself & its children (error con
 }
 `;
 
-exports[`routes/Notifications.tsx should render itself & its children (show account hostname) 1`] = `
+exports[`routes/Notifications.tsx should render itself & its children (show account header) 1`] = `
 {
   "asFragment": [Function],
   "baseElement": <body>
diff --git a/src/routes/__snapshots__/Settings.test.tsx.snap b/src/routes/__snapshots__/Settings.test.tsx.snap
index 0f0b2a50..c3270ea9 100644
--- a/src/routes/__snapshots__/Settings.test.tsx.snap
+++ b/src/routes/__snapshots__/Settings.test.tsx.snap
@@ -328,7 +328,7 @@ exports[`routes/Settings.tsx should render itself & its children 1`] = `
         >
           <input
             class="size-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-500 cursor-pointer"
-            id="showAccountHostname"
+            id="showAccountHeader"
             type="checkbox"
           />
           <div
@@ -336,9 +336,9 @@ exports[`routes/Settings.tsx should render itself & its children 1`] = `
           >
             <label
               class="font-medium text-gray-700 dark:text-gray-200 cursor-pointer"
-              for="showAccountHostname"
+              for="showAccountHeader"
             >
-              Show account hostname
+              Show account header
             </label>
           </div>
         </div>