Repository: gitify-app/gitify
Pull request #1169 (2024-06-02 20:08:11)

feat: format notification metrics within "pills"
Use "pill" to group icon and metric.  Matches that of the GitHub Mobile UX

<img width="500" alt="Screenshot 2024-06-02 at 3 00 54 PM" src="https://github.com/gitify-app/gitify/assets/386277/be8e7580-01df-4380-a466-17bbef372c34">



Patch:
diff --git a/src/__mocks__/notifications-mocks.ts b/src/__mocks__/notifications-mocks.ts
index a4a359f1..438ab37b 100644
--- a/src/__mocks__/notifications-mocks.ts
+++ b/src/__mocks__/notifications-mocks.ts
@@ -2,23 +2,26 @@ import type { AccountNotifications } from '../types';
 import {
   mockEnterpriseNotifications,
   mockGitHubNotifications,
-  mockSingleNotification,
 } from '../utils/api/__mocks__/response-mocks';
+import {
+  mockGitHubCloudAccount,
+  mockGitHubEnterpriseServerAccount,
+} from './state-mocks';
 
 export const mockAccountNotifications: AccountNotifications[] = [
   {
-    hostname: 'github.com',
+    account: mockGitHubCloudAccount,
     notifications: mockGitHubNotifications,
   },
   {
-    hostname: 'github.gitify.io',
+    account: mockGitHubEnterpriseServerAccount,
     notifications: mockEnterpriseNotifications,
   },
 ];
 
 export const mockSingleAccountNotifications: AccountNotifications[] = [
   {
-    hostname: 'github.com',
-    notifications: [mockSingleNotification],
+    account: mockGitHubCloudAccount,
+    notifications: [mockGitHubNotifications[0]],
   },
 ];
diff --git a/src/__mocks__/partial-mocks.ts b/src/__mocks__/partial-mocks.ts
index f0d2c0ef..aefbbe9b 100644
--- a/src/__mocks__/partial-mocks.ts
+++ b/src/__mocks__/partial-mocks.ts
@@ -1,11 +1,18 @@
 import type { Notification, Subject, User } from '../typesGitHub';
 import Constants from '../utils/constants';
+import { mockGitifyUser, mockToken } from './state-mocks';
 
 export function partialMockNotification(
   subject: Partial<Subject>,
 ): Notification {
   const mockNotification: Partial<Notification> = {
-    hostname: Constants.GITHUB_API_BASE_URL,
+    account: {
+      method: 'Personal Access Token',
+      platform: 'GitHub Cloud',
+      hostname: Constants.GITHUB_API_BASE_URL,
+      token: mockToken,
+      user: mockGitifyUser,
+    },
     subject: subject as Subject,
   };
 
diff --git a/src/__mocks__/state-mocks.ts b/src/__mocks__/state-mocks.ts
index 90bf49f2..118e11bb 100644
--- a/src/__mocks__/state-mocks.ts
+++ b/src/__mocks__/state-mocks.ts
@@ -1,10 +1,12 @@
 import {
+  type Account,
   type AuthState,
   type GitifyUser,
   type SettingsState,
   Theme,
 } from '../types';
 import type { EnterpriseAccount } from '../utils/auth/types';
+import Constants from '../utils/constants';
 
 export const mockEnterpriseAccounts: EnterpriseAccount[] = [
   {
@@ -13,18 +15,50 @@ export const mockEnterpriseAccounts: EnterpriseAccount[] = [
   },
 ];
 
-export const mockUser: GitifyUser = {
+export const mockGitifyUser: GitifyUser = {
   login: 'octocat',
   name: 'Mona Lisa Octocat',
   id: 123456789,
 };
 
-export const mockAuth: AuthState = {
+export const mockPersonalAccessTokenAccount: Account = {
+  platform: 'GitHub Cloud',
+  method: 'Personal Access Token',
+  token: 'token-123-456',
+  hostname: Constants.DEFAULT_AUTH_OPTIONS.hostname,
+  user: mockGitifyUser,
+};
+
+export const mockOAuthAccount: Account = {
+  platform: 'GitHub Enterprise Server',
+  method: 'OAuth App',
+  token: '1234568790',
+  hostname: 'github.gitify.io',
+  user: mockGitifyUser,
+};
+
+export const mockGitHubCloudAccount: Account = {
+  platform: 'GitHub Cloud',
+  method: 'Personal Access Token',
   token: 'token-123-456',
-  enterpriseAccounts: mockEnterpriseAccounts,
-  user: mockUser,
+  hostname: Constants.DEFAULT_AUTH_OPTIONS.hostname,
+  user: mockGitifyUser,
 };
 
+export const mockGitHubEnterpriseServerAccount: Account = {
+  platform: 'GitHub Enterprise Server',
+  method: 'Personal Access Token',
+  token: '1234568790',
+  hostname: 'github.gitify.io',
+  user: mockGitifyUser,
+};
+
+export const mockAuth: AuthState = {
+  accounts: [mockGitHubCloudAccount, mockGitHubEnterpriseServerAccount],
+};
+
+export const mockToken = 'token-123-456';
+
 export const mockSettings: SettingsState = {
   participating: false,
   playSound: true,
diff --git a/src/components/NotificationRow.tsx b/src/components/NotificationRow.tsx
index b322f0fe..a98eb5c8 100644
--- a/src/components/NotificationRow.tsx
+++ b/src/components/NotificationRow.tsx
@@ -17,6 +17,7 @@ import { AppContext } from '../context/App';
 import { IconColor } from '../types';
 import type { Notification } from '../typesGitHub';
 import { openExternalLink } from '../utils/comms';
+import Constants from '../utils/constants';
 import {
   formatForDisplay,
   formatNotificationUpdatedAt,
@@ -36,8 +37,8 @@ interface IProps {
 
 export const NotificationRow: FC<IProps> = ({ notification, hostname }) => {
   const {
-    settings,
     auth,
+    settings,
     removeNotificationFromState,
     markNotificationRead,
     markNotificationDone,
@@ -46,7 +47,7 @@ export const NotificationRow: FC<IProps> = ({ notification, hostname }) => {
   } = useContext(AppContext);
 
   const openNotification = useCallback(() => {
-    openInBrowser(notification, auth);
+    openInBrowser(notification);
 
     if (settings.markAsDoneOnOpen) {
       markNotificationDone(notification.id, hostname);
@@ -86,7 +87,7 @@ export const NotificationRow: FC<IProps> = ({ notification, hostname }) => {
     notification.subject.type,
   ]);
 
-  const commentsLabel = `${notification.subject.comments} ${
+  const commentsPillDescription = `${notification.subject.comments} ${
     notification.subject.comments > 1 ? 'comments' : 'comment'
   }`;
 
@@ -156,22 +157,31 @@ export const NotificationRow: FC<IProps> = ({ notification, hostname }) => {
                         title={icon.description}
                         className="ml-1"
                       >
-                        <icon.type
-                          size={16}
-                          className={icon.color}
-                          aria-label={icon.description}
-                        />
+                        <button
+                          type="button"
+                          className={Constants.PILL_CLASS_NAME}
+                        >
+                          <icon.type
+                            size={12}
+                            className={`mr-1 ${icon.color}`}
+                            aria-label={icon.description}
+                          />
+                          {review.users.length}
+                        </button>
                       </span>
                     );
                   })
                 : null}
               {notification.subject?.comments > 0 && (
-                <span className="ml-1" title={commentsLabel}>
-                  <CommentIcon
-                    size={16}
-                    className={IconColor.GRAY}
-                    aria-label={commentsLabel}
-                  />
+                <span className="ml-1" title={commentsPillDescription}>
+                  <button type="button" className={Constants.PILL_CLASS_NAME}>
+                    <CommentIcon
+                      size={12}
+                      className={`mr-1 ${IconColor.GRAY}`}
+                      aria-label={commentsPillDescription}
+                    />
+                    {notification.subject.comments}
+                  </button>
                 </span>
               )}
             </span>
diff --git a/src/context/App.tsx b/src/context/App.tsx
index b29edebe..b84c8b3e 100644
--- a/src/context/App.tsx
+++ b/src/context/App.tsx
@@ -17,6 +17,7 @@ import {
   Theme,
 } from '../types';
 import { headNotifications } from '../utils/api/client';
+import { migrateAuthenticatedAccounts } from '../utils/auth/migration';
 import type {
   LoginOAuthAppOptions,
   LoginPersonalAccessTokenOptions,
@@ -34,6 +35,7 @@ import { clearState, loadState, saveState } from '../utils/storage';
 import { setTheme } from '../utils/theme';
 
 const defaultAuth: AuthState = {
+  accounts: [],
   token: null,
   enterpriseAccounts: [],
   user: null,
@@ -116,8 +118,7 @@ export const AppProvider = ({ children }: { children: ReactNode }) => {
     settings.participating,
     settings.showBots,
     settings.detailedNotifications,
-    auth.token,
-    auth.enterpriseAccounts.length,
+    auth.accounts.length,
   ]);
 
   useInterval(() => {
@@ -149,7 +150,7 @@ export const AppProvider = ({ children }: { children: ReactNode }) => {
   );
 
   const isLoggedIn = useMemo(() => {
-    return !!auth.token || auth.enterpriseAccounts.length > 0;
+    return auth.accounts.length > 0;
   }, [auth]);
 
   const loginWithGitHubApp = useCallback(async () => {
@@ -157,7 +158,7 @@ export const AppProvider = ({ children }: { children: ReactNode }) => {
     const { token } = await getToken(authCode);
     const hostname = Constants.DEFAULT_AUTH_OPTIONS.hostname;
     const user = await getUserData(token, hostname);
-    const updatedAuth = addAccount(auth, token, hostname, user);
+    const updatedAuth = addAccount(auth, 'GitHub App', token, hostname, user);
     setAuth(updatedAuth);
     saveState({ auth: updatedAuth, settings });
   }, [auth, settings]);
@@ -166,7 +167,7 @@ export const AppProvider = ({ children }: { children: ReactNode }) => {
     async (data: LoginOAuthAppOptions) => {
       const { authOptions, authCode } = await authGitHub(data);
       const { token, hostname } = await getToken(authCode, authOptions);
-      const updatedAuth = addAccount(auth, token, hostname);
+      const updatedAuth = addAccount(auth, 'OAuth App', token, hostname);
       setAuth(updatedAuth);
       saveState({ auth: updatedAuth, settings });
     },
@@ -178,7 +179,13 @@ export const AppProvider = ({ children }: { children: ReactNode }) => {
       await headNotifications(hostname, token);
 
       const user = await getUserData(token, hostname);
-      const updatedAuth = addAccount(auth, token, hostname, user);
+      const updatedAuth = addAccount(
+        auth,
+        'Personal Access Token',
+        token,
+        hostname,
+        user,
+      );
       setAuth(updatedAuth);
       saveState({ auth: updatedAuth, settings });
     },
@@ -190,7 +197,9 @@ export const AppProvider = ({ children }: { children: ReactNode }) => {
     clearState();
   }, []);
 
-  const restoreSettings = useCallback(() => {
+  const restoreSettings = useCallback(async () => {
+    await migrateAuthenticatedAccounts();
+
     const existing = loadState();
 
     if (existing.auth) {
diff --git a/src/hooks/useNotifications.ts b/src/hooks/useNotifications.ts
index a048338a..337ffc2b 100644
--- a/src/hooks/useNotifications.ts
+++ b/src/hooks/useNotifications.ts
@@ -13,7 +13,7 @@ import {
   markRepositoryNotificationsAsRead,
 } from '../utils/api/client';
 import { determineFailureType } from '../utils/api/errors';
-import { getTokenForHost } from '../utils/helpers';
+import { getAccountForHost } from '../utils/helpers';
 import {
   getAllNotifications,
   setTrayIconColor,
@@ -107,10 +107,10 @@ export const useNotifications = (): NotificationsState => {
     ) => {
       setStatus('loading');
 
-      const token = getTokenForHost(hostname, auth);
+      const account = getAccountForHost(hostname, auth);
 
       try {
-        await markNotificationThreadAsRead(id, hostname, token);
+        await markNotificationThreadAsRead(id, hostname, account.token);
 
         const updatedNotifications = removeNotification(
           settings,
@@ -138,10 +138,10 @@ export const useNotifications = (): NotificationsState => {
     ) => {
       setStatus('loading');
 
-      const token = getTokenForHost(hostname, auth);
+      const account = getAccountForHost(hostname, auth);
 
       try {
-        await markNotificationThreadAsDone(id, hostname, token);
+        await markNotificationThreadAsDone(id, hostname, account.token);
 
         const updatedNotifications = removeNotification(
           settings,
@@ -169,10 +169,10 @@ export const useNotifications = (): NotificationsState => {
     ) => {
       setStatus('loading');
 
-      const token = getTokenForHost(hostname, auth);
+      const account = getAccountForHost(hostname, auth);
 
       try {
-        await ignoreNotificationThreadSubscription(id, hostname, token);
+        await ignoreNotificationThreadSubscription(id, hostname, account.token);
         await markNotificationRead(auth, settings, id, hostname);
         setStatus('success');
       } catch (err) {
@@ -191,10 +191,14 @@ export const useNotifications = (): NotificationsState => {
     ) => {
       setStatus('loading');
 
-      const token = getTokenForHost(hostname, auth);
+      const account = getAccountForHost(hostname, auth);
 
       try {
-        await markRepositoryNotificationsAsRead(repoSlug, hostname, token);
+        await markRepositoryNotificationsAsRead(
+          repoSlug,
+          hostname,
+          account.token,
+        );
         const updatedNotifications = removeNotifications(
           repoSlug,
           notifications,
@@ -222,7 +226,8 @@ export const useNotifications = (): NotificationsState => {
 
       try {
         const accountIndex = notifications.findIndex(
-          (accountNotifications) => accountNotifications.hostname === hostname,
+          (accountNotifications) =>
+            accountNotifications.account.hostname === hostname,
         );
 
         if (accountIndex !== -1) {
@@ -238,7 +243,7 @@ export const useNotifications = (): NotificationsState => {
                 auth,
                 settings,
                 notification.id,
-                notifications[accountIndex].hostname,
+                notifications[accountIndex].account.hostname,
               ),
             ),
           );
diff --git a/src/routes/LoginWithOAuthApp.tsx b/src/routes/LoginWithOAuthApp.tsx
index f95ff345..63acee45 100644
--- a/src/routes/LoginWithOAuthApp.tsx
+++ b/src/routes/LoginWithOAuthApp.tsx
@@ -4,8 +4,7 @@ import {
   PersonIcon,
   SignInIcon,
 } from '@primer/octicons-react';
-import ipcRenderer from 'electron';
-import { type FC, useCallback, useContext, useEffect } from 'react';
+import { type FC, useCallback, useContext } from 'react';
 import { Form, type FormRenderProps } from 'react-final-form';
 import { useNavigate } from 'react-router-dom';
 import { Button } from '../components/fields/Button';
@@ -57,19 +56,9 @@ export const validate = (values: IValues): IFormErrors => {
 };
 
 export const LoginWithOAuthApp: FC = () => {
-  const {
-    auth: { enterpriseAccounts },
-    loginWithOAuthApp: loginEnterprise,
-  } = useContext(AppContext);
+  const { loginWithOAuthApp } = useContext(AppContext);
   const navigate = useNavigate();
 
-  useEffect(() => {
-    if (enterpriseAccounts.length) {
-      ipcRenderer.ipcRenderer.send('reopen-window');
-      navigate(-1);
-    }
-  }, [enterpriseAccounts]);
-
   const renderForm = (formProps: FormRenderProps) => {
     const { handleSubmit, submitting, pristine, values } = formProps;
 
@@ -129,7 +118,7 @@ export const LoginWithOAuthApp: FC = () => {
 
   const login = useCallback(async (data: IValues) => {
     try {
-      await loginEnterprise(data as LoginOAuthAppOptions);
+      await loginWithOAuthApp(data as LoginOAuthAppOptions);
     } catch (err) {
       // Skip
     }
diff --git a/src/routes/Notifications.tsx b/src/routes/Notifications.tsx
index b8b5f83d..626d938d 100644
--- a/src/routes/Notifications.tsx
+++ b/src/routes/Notifications.tsx
@@ -33,11 +33,11 @@ export const NotificationsRoute: FC = () => {
 
   return (
     <div className="flex flex-col flex-1 bg-white dark:bg-gray-dark">
-      {notifications.map((account) => (
+      {notifications.map((accountNotifications) => (
         <AccountNotifications
-          key={account.hostname}
-          hostname={account.hostname}
-          notifications={account.notifications}
+          key={accountNotifications.account.hostname}
+          hostname={accountNotifications.account.hostname}
+          notifications={accountNotifications.notifications}
           showAccountHostname={
             hasMultipleAccounts || settings.showAccountHostname
           }
diff --git a/src/types.ts b/src/types.ts
index 66d593e6..fcdcbed3 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -1,15 +1,37 @@
 import type { OcticonProps } from '@primer/octicons-react';
 import type { FC } from 'react';
 import type { Notification } from './typesGitHub';
-import type { EnterpriseAccount } from './utils/auth/types';
+import type {
+  AuthMethod,
+  EnterpriseAccount,
+  PlatformType,
+} from './utils/auth/types';
+
+export type Status = 'loading' | 'success' | 'error';
 
 export interface AuthState {
+  accounts: Account[];
+  /**
+   * @deprecated This attribute is deprecated and will be removed in a future release.
+   */
   token?: string;
-  enterpriseAccounts: EnterpriseAccount[];
-  user: GitifyUser | null;
+  /**
+   * @deprecated This attribute is deprecated and will be removed in a future release.
+   */
+  enterpriseAccounts?: EnterpriseAccount[];
+  /**
+   * @deprecated This attribute is deprecated and will be removed in a future release.
+   */
+  user?: GitifyUser | null;
 }
 
-export type Status = 'loading' | 'success' | 'error';
+export interface Account {
+  method: AuthMethod;
+  platform: PlatformType;
+  hostname: string;
+  token: string;
+  user: GitifyUser | null;
+}
 
 export type SettingsState = AppearanceSettingsState &
   NotificationSettingsState &
@@ -52,7 +74,7 @@ export type RadioGroupItem = {
 };
 
 export interface AccountNotifications {
-  hostname: string;
+  account: Account;
   notifications: Notification[];
 }
 
diff --git a/src/typesGitHub.ts b/src/typesGitHub.ts
index ea86008b..416b0164 100644
--- a/src/typesGitHub.ts
+++ b/src/typesGitHub.ts
@@ -1,3 +1,5 @@
+import type { Account } from './types';
+
 export type Reason =
   | 'approval_requested'
   | 'assign'
@@ -91,7 +93,9 @@ export type PullRequestReviewAuthorAssociation =
   | 'NONE'
   | 'OWNER';
 
-export interface Notification {
+export type Notification = GitHubNotification & GitifyNotification;
+
+export interface GitHubNotification {
   id: string;
   unread: boolean;
   reason: Reason;
@@ -101,8 +105,11 @@ export interface Notification {
   repository: Repository;
   url: string;
   subscription_url: string;
-  // TODO - rename this to apiBaseUrl
-  hostname: string; // This is not in the official GitHub API. We add this to make notification interactions easier.
+}
+
+// Note: This is not in the official GitHub API. We add this to make notification interactions easier.
+export interface GitifyNotification {
+  account: Account;
 }
 
 export type UserDetails = User & UserProfile;
diff --git a/src/utils/api/__mocks__/response-mocks.ts b/src/utils/api/__mocks__/response-mocks.ts
index dcaf441b..603a58fb 100644
--- a/src/utils/api/__mocks__/response-mocks.ts
+++ b/src/utils/api/__mocks__/response-mocks.ts
@@ -1,3 +1,7 @@
+import {
+  mockGitHubCloudAccount,
+  mockGitHubEnterpriseServerAccount,
+} from '../../../__mocks__/state-mocks';
 import type {
   Discussion,
   DiscussionAuthor,
@@ -7,7 +11,6 @@ import type {
   Repository,
   User,
 } from '../../../typesGitHub';
-import Constants from '../../constants';
 
 export const mockNotificationUser: User = {
   login: 'octocat',
@@ -35,7 +38,7 @@ export const mockNotificationUser: User = {
 // Repository : 'gitify-app/notifications-test'
 export const mockGitHubNotifications: Notification[] = [
   {
-    hostname: Constants.GITHUB_API_BASE_URL,
+    account: mockGitHubCloudAccount,
     id: '138661096',
     unread: true,
     reason: 'subscribed',
@@ -183,7 +186,7 @@ export const mockGitHubNotifications: Notification[] = [
       'https://api.github.com/notifications/threads/138661096/subscription',
   },
   {
-    hostname: Constants.GITHUB_API_BASE_URL,
+    account: mockGitHubCloudAccount,
     id: '148827438',
     unread: true,
     reason: 'author',
@@ -242,7 +245,7 @@ export const mockGitHubNotifications: Notification[] = [
 // Repository : 'myorg/notifications-test'
 export const mockEnterpriseNotifications: Notification[] = [
   {
-    hostname: 'https://github.gitify.io/api/v3',
+    account: mockGitHubEnterpriseServerAccount,
     id: '3',
     unread: true,
     reason: 'subscribed',
@@ -296,7 +299,7 @@ export const mockEnterpriseNotifications: Notification[] = [
       'https://github.gitify.io/api/v3/notifications/threads/4/subscription',
   },
   {
-    hostname: 'https://github.gitify.io/api/v3',
+    account: mockGitHubEnterpriseServerAccount,
     id: '4',
     unread: true,
     reason: 'subscribed',
diff --git a/src/utils/api/client.ts b/src/utils/api/client.ts
index ae9549a6..3c74a8bd 100644
--- a/src/utils/api/client.ts
+++ b/src/utils/api/client.ts
@@ -1,6 +1,6 @@
 import type { AxiosPromise } from 'axios';
 import { print } from 'graphql/language/printer';
-import type { SettingsState } from '../../types';
+import type { Account, SettingsState } from '../../types';
 import type {
   Commit,
   CommitComment,
@@ -52,15 +52,14 @@ export function headNotifications(
  * Endpoint documentation: https://docs.github.com/en/rest/activity/notifications#list-notifications-for-the-authenticated-user
  */
 export function listNotificationsForAuthenticatedUser(
-  hostname: string,
-  token: string,
+  account: Account,
   settings: SettingsState,
 ): AxiosPromise<Notification[]> {
-  const url = getGitHubAPIBaseUrl(hostname);
+  const url = getGitHubAPIBaseUrl(account.hostname);
   url.pathname += 'notifications';
   url.searchParams.append('participating', String(settings.participating));
 
-  return apiRequestAuth(url.toString(), 'GET', token);
+  return apiRequestAuth(url.toString(), 'GET', account.token);
 }
 
 /**
@@ -229,10 +228,9 @@ export async function getHtmlUrl(url: string, token: string): Promise<string> {
  */
 export async function searchDiscussions(
   notification: Notification,
-  token: string,
 ): AxiosPromise<GraphQLSearch<Discussion>> {
-  const url = getGitHubGraphQLUrl(notification.hostname);
-  return apiRequestAuth(url.toString(), 'POST', token, {
+  const url = getGitHubGraphQLUrl(notification.account.hostname);
+  return apiRequestAuth(url.toString(), 'POST', notification.account.token, {
     query: print(QUERY_SEARCH_DISCUSSIONS),
     variables: {
       queryStatement: formatAsGitHubSearchSyntax(
@@ -251,10 +249,9 @@ export async function searchDiscussions(
  */
 export async function getLatestDiscussion(
   notification: Notification,
-  token: string,
 ): Promise<Discussion | null> {
   try {
-    const response = await searchDiscussions(notification, token);
+    const response = await searchDiscussions(notification);
     return (
       response.data?.data.search.nodes.filter(
         (discussion) => discussion.title === notification.subject.title,
diff --git a/src/utils/auth/migration.ts b/src/utils/auth/migration.ts
new file mode 100644
index 00000000..d8afe6e9
--- /dev/null
+++ b/src/utils/auth/migration.ts
@@ -0,0 +1,85 @@
+import type { Account, AuthState } from '../../types';
+import Constants from '../constants';
+import { loadState, saveState } from '../storage';
+import { getUserData } from './utils';
+
+/**
+ * Migrate authenticated accounts from old data structure to new data structure (v5.7.0+).
+ *
+ * @deprecated We plan to remove this migration logic in a future major release.
+ */
+export async function migrateAuthenticatedAccounts() {
+  const existing = loadState();
+
+  if (hasAccountsToMigrate(existing.auth)) {
+    console.log('Commencing authenticated accounts migration');
+
+    const migratedAccounts = await convertAccounts(existing.auth);
+
+    saveState({
+      auth: { ...existing.auth, accounts: migratedAccounts },
+      settings: existing.settings,
+    });
+    console.log('Authenticated accounts migration complete');
+  }
+}
+
+export function hasAccountsToMigrate(existingAuthState: AuthState): boolean {
+  if (!existingAuthState) {
+    return false;
+  }
+
+  // Don't attempt migration if there are already accounts in the new structure
+  if (existingAuthState?.accounts?.length > 0) {
+    return false;
+  }
+
+  if (
+    existingAuthState?.token ||
+    existingAuthState?.enterpriseAccounts?.length > 0
+  ) {
+    return true;
+  }
+
+  return false;
+}
+
+export async function convertAccounts(
+  existingAuthState: AuthState,
+): Promise<Account[]> {
+  const migratedAccounts: Account[] = [];
+
+  if (existingAuthState?.token) {
+    const user = await getUserData(
+      existingAuthState.token,
+      Constants.DEFAULT_AUTH_OPTIONS.hostname,
+    );
+
+    migratedAccounts.push({
+      hostname: Constants.DEFAULT_AUTH_OPTIONS.hostname,
+      platform: 'GitHub Cloud',
+      method: 'Personal Access Token',
+      token: existingAuthState.token,
+      user: user,
+    });
+  }
+
+  if (existingAuthState?.enterpriseAccounts) {
+    for (const legacyEnterpriseAccount of existingAuthState.enterpriseAccounts) {
+      const user = await getUserData(
+        legacyEnterpriseAccount.token,
+        legacyEnterpriseAccount.hostname,
+      );
+
+      migratedAccounts.push({
+        hostname: legacyEnterpriseAccount.hostname,
+        platform: 'GitHub Enterprise Server',
+        method: 'OAuth App',
+        token: legacyEnterpriseAccount.token,
+        user: user,
+      });
+    }
+  }
+
+  return migratedAccounts;
+}
diff --git a/src/utils/auth/types.ts b/src/utils/auth/types.ts
index d2b72b7f..f51261d8 100644
--- a/src/utils/auth/types.ts
+++ b/src/utils/auth/types.ts
@@ -1,3 +1,7 @@
+export type AuthMethod = 'GitHub App' | 'Personal Access Token' | 'OAuth App';
+
+export type PlatformType = 'GitHub Cloud' | 'GitHub Enterprise Server';
+
 export interface LoginOAuthAppOptions {
   hostname: string;
   clientId: string;
@@ -19,6 +23,9 @@ export interface AuthTokenResponse {
   token: string;
 }
 
+/**
+ * @deprecated This type is deprecated and will be removed in a future release.
+ */
 export interface EnterpriseAccount {
   hostname: string;
   token: string;
diff --git a/src/utils/auth/utils.ts b/src/utils/auth/utils.ts
index c09dab27..05a80e95 100644
--- a/src/utils/auth/utils.ts
+++ b/src/utils/auth/utils.ts
@@ -5,8 +5,8 @@ import type { UserDetails } from '../../typesGitHub';
 import { getAuthenticatedUser } from '../api/client';
 import { apiRequest } from '../api/request';
 import { Constants } from '../constants';
-import { isEnterpriseHost } from '../helpers';
-import type { AuthResponse, AuthTokenResponse } from './types';
+import { getPlatformFromHostname } from '../helpers';
+import type { AuthMethod, AuthResponse, AuthTokenResponse } from './types';
 
 export const authGitHub = (
   authOptions = Constants.DEFAULT_AUTH_OPTIONS,
@@ -107,31 +107,26 @@ export const getToken = async (
   };
 };
 
-export const addAccount = (
+export function addAccount(
   auth: AuthState,
-  token,
-  hostname,
+  method: AuthMethod,
+  token: string,
+  hostname: string,
   user?: GitifyUser,
-): AuthState => {
-  if (!isEnterpriseHost(hostname)) {
-    return {
-      ...auth,
-      token,
-      user: user ?? null,
-    };
-  }
-
+): AuthState {
   return {
-    ...auth,
-    enterpriseAccounts: [
-      ...auth.enterpriseAccounts,
+    accounts: [
+      ...auth.accounts,
       {
-        token,
         hostname: hostname,
+        method: method,
+        platform: getPlatformFromHostname(hostname),
+        token: token,
+        user: user,
       },
     ],
   };
-};
+}
 
 export function getNewTokenURL(hostname: string): string {
   const date = format(new Date(), 'PP p');
diff --git a/src/utils/constants.ts b/src/utils/constants.ts
index c6a92a41..a7295819 100644
--- a/src/utils/constants.ts
+++ b/src/utils/constants.ts
@@ -24,6 +24,9 @@ export const Constants = {
 
   READ_CLASS_NAME: 'opacity-50 dark:opacity-50',
 
+  PILL_CLASS_NAME:
+    'rounded-full text-xs px-2 bg-gray-100 hover:bg-gray-200 dark:bg-gray-800 dark:hover:bg-gray-700',
+
   // GitHub Docs
   GITHUB_DOCS: {
     OAUTH_URL:
diff --git a/src/utils/helpers.ts b/src/utils/helpers.ts
index 310213a8..f175c8b4 100644
--- a/src/utils/helpers.ts
+++ b/src/utils/helpers.ts
@@ -1,8 +1,9 @@
 import { formatDistanceToNow, parseISO } from 'date-fns';
-import type { AuthState } from '../types';
+import type { Account, AuthState } from '../types';
 import type { Notification } from '../typesGitHub';
 import { openExternalLink } from '../utils/comms';
 import { getHtmlUrl, getLatestDiscussion } from './api/client';
+import type { PlatformType } from './auth/types';
 import { Constants } from './constants';
 import {
   getCheckSuiteAttributes,
@@ -11,23 +12,23 @@ import {
 } from './subject';
 
 export function isPersonalAccessTokenLoggedIn(auth: AuthState): boolean {
-  return auth.token != null;
+  return auth.accounts.some(
+    (account) => account.method === 'Personal Access Token',
+  );
 }
 
 export function isOAuthAppLoggedIn(auth: AuthState): boolean {
-  return auth.enterpriseAccounts?.length > 0;
+  return auth.accounts.some((account) => account.method === 'OAuth App');
 }
 
-export function getTokenForHost(hostname: string, auth: AuthState): string {
-  const isEnterprise = isEnterpriseHost(hostname);
-
-  if (isEnterprise) {
-    return auth.enterpriseAccounts.find(
-      (account) => account.hostname === hostname,
-    ).token;
-  }
+export function getAccountForHost(hostname: string, auth: AuthState): Account {
+  return auth.accounts.find((account) => hostname.endsWith(account.hostname));
+}
 
-  return auth.token;
+export function getPlatformFromHostname(hostname: string): PlatformType {
+  return hostname.endsWith(Constants.DEFAULT_AUTH_OPTIONS.hostname)
+    ? 'GitHub Cloud'
+    : 'GitHub Enterprise Server';
 }
 
 export function isEnterpriseHost(hostname: string): boolean {
@@ -35,11 +36,10 @@ export function isEnterpriseHost(hostname: string): boolean {
 }
 
 export function generateNotificationReferrerId(
-  notificationId: string,
-  userId: number,
+  notification: Notification,
 ): string {
   const buffer = Buffer.from(
-    `018:NotificationThread${notificationId}:${userId}`,
+    `018:NotificationThread${notification.id}:${notification.account.user.id}`,
   );
   return buffer.toString('base64');
 }
@@ -93,14 +93,11 @@ export function actionsURL(repositoryURL: string, filters: string[]): string {
   return url.toString().replace(/%2B/g, '+');
 }
 
-async function getDiscussionUrl(
-  notification: Notification,
-  token: string,
-): Promise<string> {
+async function getDiscussionUrl(notification: Notification): Promise<string> {
   const url = new URL(notification.repository.html_url);
   url.pathname += '/discussions';
 
-  const discussion = await getLatestDiscussion(notification, token);
+  const discussion = await getLatestDiscussion(notification);
 
   if (discussion) {
     url.href = discussion.url;
@@ -117,15 +114,19 @@ async function getDiscussionUrl(
 
 export async function generateGitHubWebUrl(
   notification: Notification,
-  auth: AuthState,
 ): Promise<string> {
   const url = new URL(notification.repository.html_url);
-  const token = getTokenForHost(notification.hostname, auth);
 
   if (notification.subject.latest_comment_url) {
-    url.href = await getHtmlUrl(notification.subject.latest_comment_url, token);
+    url.href = await getHtmlUrl(
+      notification.subject.latest_comment_url,
+      notification.account.token,
+    );
   } else if (notification.subject.url) {
-    url.href = await getHtmlUrl(notification.subject.url, token);
+    url.href = await getHtmlUrl(
+      notification.subject.url,
+      notification.account.token,
+    );
   } else {
     // Perform any specific notification type handling (only required for a few special notification scenarios)
     switch (notification.subject.type) {
@@ -133,7 +134,7 @@ export async function generateGitHubWebUrl(
         url.href = getCheckSuiteUrl(notification);
         break;
       case 'Discussion':
-        url.href = await getDiscussionUrl(notification, token);
+        url.href = await getDiscussionUrl(notification);
         break;
       case 'RepositoryInvitation':
         url.pathname += '/invitations';
@@ -148,7 +149,7 @@ export async function generateGitHubWebUrl(
 
   url.searchParams.set(
     'notification_referrer_id',
-    generateNotificationReferrerId(notification.id, auth.user?.id),
+    generateNotificationReferrerId(notification),
   );
 
   return url.toString();
@@ -174,16 +175,17 @@ export function formatNotificationUpdatedAt(
 ): string {
   const date = notification.last_read_at ?? notification.updated_at;
 
-  return formatDistanceToNow(parseISO(date), {
-    addSuffix: true,
-  });
+  try {
+    return formatDistanceToNow(parseISO(date), {
+      addSuffix: true,
+    });
+  } catch (e) {}
+
+  return '';
 }
 
-export async function openInBrowser(
-  notification: Notification,
-  auth: AuthState,
-) {
-  const url = await generateGitHubWebUrl(notification, auth);
+export async function openInBrowser(notification: Notification) {
+  const url = await generateGitHubWebUrl(notification);
 
   openExternalLink(url);
 }
diff --git a/src/utils/notifications.ts b/src/utils/notifications.ts
index d44bcc6e..33695a4a 100644
--- a/src/utils/notifications.ts
+++ b/src/utils/notifications.ts
@@ -1,14 +1,9 @@
 import { ipcRenderer } from 'electron';
 import type { AccountNotifications, AuthState, SettingsState } from '../types';
 import { Notification } from '../typesGitHub';
-import {
-  getTokenForHost,
-  isPersonalAccessTokenLoggedIn,
-  openInBrowser,
-} from '../utils/helpers';
+import { openInBrowser } from '../utils/helpers';
 import { listNotificationsForAuthenticatedUser } from './api/client';
 import { updateTrayIcon } from './comms';
-import Constants from './constants';
 import { isWindows } from './platform';
 import { getGitifySubjectDetails } from './subject';
 
@@ -32,21 +27,24 @@ export const triggerNativeNotifications = (
   auth: AuthState,
 ) => {
   const diffNotifications = newNotifications
-    .map((account) => {
+    .map((accountNotifications) => {
       const accountPreviousNotifications = previousNotifications.find(
-        (item) => item.hostname === account.hostname,
+        (item) =>
+          item.account.hostname === accountNotifications.account.hostname,
       );
 
       if (!accountPreviousNotifications) {
-        return account.notifications;
+        return accountNotifications.notifications;
       }
 
       const accountPreviousNotificationsIds =
         accountPreviousNotifications.notifications.map((item) => item.id);
 
-      const accountNewNotifications = account.notifications.filter((item) => {
-        return !accountPreviousNotificationsIds.includes(`${item.id}`);
-      });
+      const accountNewNotifications = accountNotifications.notifications.filter(
+        (item) => {
+          return !accountPreviousNotificationsIds.includes(`${item.id}`);
+        },
+      );
 
       return accountNewNotifications;
     })
@@ -94,7 +92,7 @@ export const raiseNativeNotification = (
   nativeNotification.onclick = () => {
     if (notifications.length === 1) {
       ipcRenderer.send('hide-window');
-      openInBrowser(notifications[0], auth);
+      openInBrowser(notifications[0]);
     } else {
       ipcRenderer.send('reopen-window');
     }
@@ -109,25 +107,12 @@ export const raiseSoundNotification = () => {
   audio.play();
 };
 
-function getGitHubNotifications(auth: AuthState, settings: SettingsState) {
-  if (!isPersonalAccessTokenLoggedIn(auth)) {
-    return;
-  }
-
-  return listNotificationsForAuthenticatedUser(
-    Constants.DEFAULT_AUTH_OPTIONS.hostname,
-    auth.token,
-    settings,
-  );
-}
-
-function getEnterpriseNotifications(auth: AuthState, settings: SettingsState) {
-  return auth.enterpriseAccounts.map((account) => {
-    return listNotificationsForAuthenticatedUser(
-      account.hostname,
-      account.token,
-      settings,
-    );
+function getNotifications(auth: AuthState, settings: SettingsState) {
+  return auth.accounts.map((account) => {
+    return {
+      account,
+      notifications: listNotificationsForAuthenticatedUser(account, settings),
+    };
   });
 }
 
@@ -135,21 +120,16 @@ export async function getAllNotifications(
   auth: AuthState,
   settings: SettingsState,
 ): Promise<AccountNotifications[]> {
-  const responses = await Promise.all([
-    getGitHubNotifications(auth, settings),
-    ...getEnterpriseNotifications(auth, settings),
-  ]);
+  const responses = await Promise.all([...getNotifications(auth, settings)]);
 
   const notifications: AccountNotifications[] = await Promise.all(
     responses
       .filter((response) => !!response)
       .map(async (accountNotifications) => {
-        const { hostname } = new URL(accountNotifications.config.url);
-
-        let notifications = accountNotifications.data.map(
+        let notifications = (await accountNotifications.notifications).data.map(
           (notification: Notification) => ({
             ...notification,
-            hostname,
+            account: accountNotifications.account,
           }),
         );
 
@@ -162,7 +142,7 @@ export async function getAllNotifications(
         notifications = filterNotifications(notifications, settings);
 
         return {
-          hostname,
+          account: accountNotifications.account,
           notifications: notifications,
         };
       }),
@@ -182,12 +162,8 @@ export async function enrichNotifications(
 
   const enrichedNotifications = await Promise.all(
     notifications.map(async (notification: Notification) => {
-      const token = getTokenForHost(notification.hostname, auth);
-
-      const additionalSubjectDetails = await getGitifySubjectDetails(
-        notification,
-        token,
-      );
+      const additionalSubjectDetails =
+        await getGitifySubjectDetails(notification);
 
       return {
         ...notification,
diff --git a/src/utils/remove-notification.ts b/src/utils/remove-notification.ts
index 4e93cbe5..03e0fcd1 100644
--- a/src/utils/remove-notification.ts
+++ b/src/utils/remove-notification.ts
@@ -14,7 +14,8 @@ export const removeNotification = (
   }
 
   const accountIndex = notifications.findIndex(
-    (accountNotifications) => accountNotifications.hostname === hostname,
+    (accountNotifications) =>
+      accountNotifications.account.hostname === hostname,
   );
 
   if (accountIndex !== -1) {
diff --git a/src/utils/remove-notifications.ts b/src/utils/remove-notifications.ts
index 2831e4f6..09ed0728 100644
--- a/src/utils/remove-notifications.ts
+++ b/src/utils/remove-notifications.ts
@@ -6,7 +6,8 @@ export const removeNotifications = (
   hostname: string,
 ): AccountNotifications[] => {
   const accountIndex = notifications.findIndex(
-    (accountNotifications) => accountNotifications.hostname === hostname,
+    (accountNotifications) =>
+      accountNotifications.account.hostname === hostname,
   );
 
   if (accountIndex !== -1) {
diff --git a/src/utils/subject.ts b/src/utils/subject.ts
index d899e7ea..ffcbe2c0 100644
--- a/src/utils/subject.ts
+++ b/src/utils/subject.ts
@@ -25,22 +25,21 @@ import {
 
 export async function getGitifySubjectDetails(
   notification: Notification,
-  token: string,
 ): Promise<GitifySubject> {
   try {
     switch (notification.subject.type) {
       case 'CheckSuite':
         return getGitifySubjectForCheckSuite(notification);
       case 'Commit':
-        return getGitifySubjectForCommit(notification, token);
+        return getGitifySubjectForCommit(notification);
       case 'Discussion':
-        return await getGitifySubjectForDiscussion(notification, token);
+        return await getGitifySubjectForDiscussion(notification);
       case 'Issue':
-        return await getGitifySubjectForIssue(notification, token);
+        return await getGitifySubjectForIssue(notification);
       case 'PullRequest':
-        return await getGitifySubjectForPullRequest(notification, token);
+        return await getGitifySubjectForPullRequest(notification);
       case 'Release':
-        return await getGitifySubjectForRelease(notification, token);
+        return await getGitifySubjectForRelease(notification);
       case 'WorkflowRun':
         return getGitifySubjectForWorkflowRun(notification);
       default:
@@ -115,18 +114,22 @@ function getGitifySubjectForCheckSuite(
 
 async function getGitifySubjectForCommit(
   notification: Notification,
-  token: string,
 ): Promise<GitifySubject> {
   let user: User;
 
   if (notification.subject.latest_comment_url) {
     const commitComment = (
-      await getCommitComment(notification.subject.latest_comment_url, token)
+      await getCommitComment(
+        notification.subject.latest_comment_url,
+        notification.account.token,
+      )
     ).data;
 
     user = commitComment.user;
   } else {
-    const commit = (await getCommit(notification.subject.url, token)).data;
+    const commit = (
+      await getCommit(notification.subject.url, notification.account.token)
+    ).data;
 
     user = commit.author;
   }
@@ -144,9 +147,8 @@ async function getGitifySubjectForCommit(
 
 async function getGitifySubjectForDiscussion(
   notification: Notification,
-  token: string,
 ): Promise<GitifySubject> {
-  const discussion = await getLatestDiscussion(notification, token);
+  const discussion = await getLatestDiscussion(notification);
   let discussionState: DiscussionStateType = 'OPEN';
 
   if (discussion) {
@@ -203,9 +205,10 @@ export function getLatestDiscussionComment(
 
 async function getGitifySubjectForIssue(
   notification: Notification,
-  token: string,
 ): Promise<GitifySubject> {
-  const issue = (await getIssue(notification.subject.url, token)).data;
+  const issue = (
+    await getIssue(notification.subject.url, notification.account.token)
+  ).data;
 
   let issueCommentUser: User;
 
@@ -213,7 +216,7 @@ async function getGitifySubjectForIssue(
     const issueComment = (
       await getIssueOrPullRequestComment(
         notification.subject.latest_comment_url,
-        token,
+        notification.account.token,
       )
     ).data;
     issueCommentUser = issueComment.user;
@@ -233,9 +236,10 @@ async function getGitifySubjectForIssue(
 
 async function getGitifySubjectForPullRequest(
   notification: Notification,
-  token: string,
 ): Promise<GitifySubject> {
-  const pr = (await getPullRequest(notification.subject.url, token)).data;
+  const pr = (
+    await getPullRequest(notification.subject.url, notification.account.token)
+  ).data;
 
   let prState: PullRequestStateType = pr.state;
   if (pr.merged) {
@@ -253,13 +257,13 @@ async function getGitifySubjectForPullRequest(
     const prComment = (
       await getIssueOrPullRequestComment(
         notification.subject.latest_comment_url,
-        token,
+        notification.account.token,
       )
     ).data;
     prCommentUser = prComment.user;
   }
 
-  const reviews = await getLatestReviewForReviewers(notification, token);
+  const reviews = await getLatestReviewForReviewers(notification);
 
   return {
     state: prState,
@@ -276,7 +280,6 @@ async function getGitifySubjectForPullRequest(
 
 export async function getLatestReviewForReviewers(
   notification: Notification,
-  token: string,
 ): Promise<GitifyPullRequestReview[]> | null {
   if (notification.subject.type !== 'PullRequest') {
     return null;
@@ -284,7 +287,7 @@ export async function getLatestReviewForReviewers(
 
   const prReviews = await getPullRequestReviews(
     `${notification.subject.url}/reviews`,
-    token,
+    notification.account.token,
   );
 
   if (!prReviews.data.length) {
@@ -328,9 +331,10 @@ export async function getLatestReviewForReviewers(
 
 async function getGitifySubjectForRelease(
   notification: Notification,
-  token: string,
 ): Promise<GitifySubject> {
-  const release = (await getRelease(notification.subject.url, token)).data;
+  const release = (
+    await getRelease(notification.subject.url, notification.account.token)
+  ).data;
 
   return {
     state: null,


Test patch:
diff --git a/src/components/__snapshots__/NotificationRow.test.tsx.snap b/src/components/__snapshots__/NotificationRow.test.tsx.snap
index 6b24aaae..58f5668d 100644
--- a/src/components/__snapshots__/NotificationRow.test.tsx.snap
+++ b/src/components/__snapshots__/NotificationRow.test.tsx.snap
@@ -83,61 +83,79 @@ exports[`components/NotificationRow.tsx rendering for notification comments coun
                   class="ml-1"
                   title="octocat approved these changes"
                 >
-                  <svg
-                    aria-label="octocat approved these changes"
-                    class="text-green-500"
-                    fill="currentColor"
-                    focusable="false"
-                    height="16"
-                    role="img"
-                    style="display: inline-block; user-select: none; vertical-align: text-bottom; overflow: visible;"
-                    viewBox="0 0 16 16"
-                    width="16"
+                  <button
+                    class="rounded-full text-xs px-2 bg-gray-100 hover:bg-gray-200 dark:bg-gray-800 dark:hover:bg-gray-700"
+                    type="button"
                   >
-                    <path
-                      d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z"
-                    />
-                  </svg>
+                    <svg
+                      aria-label="octocat approved these changes"
+                      class="mr-1 text-green-500"
+                      fill="currentColor"
+                      focusable="false"
+                      height="12"
+                      role="img"
+                      style="display: inline-block; user-select: none; vertical-align: text-bottom; overflow: visible;"
+                      viewBox="0 0 16 16"
+                      width="12"
+                    >
+                      <path
+                        d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z"
+                      />
+                    </svg>
+                    1
+                  </button>
                 </span>
                 <span
                   class="ml-1"
                   title="gitify-app requested changes"
                 >
-                  <svg
-                    aria-label="gitify-app requested changes"
-                    class="text-red-500"
-                    fill="currentColor"
-                    focusable="false"
-                    height="16"
-                    role="img"
-                    style="display: inline-block; user-select: none; vertical-align: text-bottom; overflow: visible;"
-                    viewBox="0 0 16 16"
-                    width="16"
+                  <button
+                    class="rounded-full text-xs px-2 bg-gray-100 hover:bg-gray-200 dark:bg-gray-800 dark:hover:bg-gray-700"
+                    type="button"
                   >
-                    <path
-                      d="M1 1.75C1 .784 1.784 0 2.75 0h7.586c.464 0 .909.184 1.237.513l2.914 2.914c.329.328.513.773.513 1.237v9.586A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25Zm1.75-.25a.25.25 0 0 0-.25.25v12.5c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V4.664a.25.25 0 0 0-.073-.177l-2.914-2.914a.25.25 0 0 0-.177-.073ZM8 3.25a.75.75 0 0 1 .75.75v1.5h1.5a.75.75 0 0 1 0 1.5h-1.5v1.5a.75.75 0 0 1-1.5 0V7h-1.5a.75.75 0 0 1 0-1.5h1.5V4A.75.75 0 0 1 8 3.25Zm-3 8a.75.75 0 0 1 .75-.75h4.5a.75.75 0 0 1 0 1.5h-4.5a.75.75 0 0 1-.75-.75Z"
-                    />
-                  </svg>
+                    <svg
+                      aria-label="gitify-app requested changes"
+                      class="mr-1 text-red-500"
+                      fill="currentColor"
+                      focusable="false"
+                      height="12"
+                      role="img"
+                      style="display: inline-block; user-select: none; vertical-align: text-bottom; overflow: visible;"
+                      viewBox="0 0 16 16"
+                      width="12"
+                    >
+                      <path
+                        d="M1 1.75C1 .784 1.784 0 2.75 0h7.586c.464 0 .909.184 1.237.513l2.914 2.914c.329.328.513.773.513 1.237v9.586A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25Zm1.75-.25a.25.25 0 0 0-.25.25v12.5c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V4.664a.25.25 0 0 0-.073-.177l-2.914-2.914a.25.25 0 0 0-.177-.073ZM8 3.25a.75.75 0 0 1 .75.75v1.5h1.5a.75.75 0 0 1 0 1.5h-1.5v1.5a.75.75 0 0 1-1.5 0V7h-1.5a.75.75 0 0 1 0-1.5h1.5V4A.75.75 0 0 1 8 3.25Zm-3 8a.75.75 0 0 1 .75-.75h4.5a.75.75 0 0 1 0 1.5h-4.5a.75.75 0 0 1-.75-.75Z"
+                      />
+                    </svg>
+                    1
+                  </button>
                 </span>
                 <span
                   class="ml-1"
                   title="1 comment"
                 >
-                  <svg
-                    aria-label="1 comment"
-                    class="text-gray-500 dark:text-gray-300"
-                    fill="currentColor"
-                    focusable="false"
-                    height="16"
-                    role="img"
-                    style="display: inline-block; user-select: none; vertical-align: text-bottom; overflow: visible;"
-                    viewBox="0 0 16 16"
-                    width="16"
+                  <button
+                    class="rounded-full text-xs px-2 bg-gray-100 hover:bg-gray-200 dark:bg-gray-800 dark:hover:bg-gray-700"
+                    type="button"
                   >
-                    <path
-                      d="M1 2.75C1 1.784 1.784 1 2.75 1h10.5c.966 0 1.75.784 1.75 1.75v7.5A1.75 1.75 0 0 1 13.25 12H9.06l-2.573 2.573A1.458 1.458 0 0 1 4 13.543V12H2.75A1.75 1.75 0 0 1 1 10.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h2a.75.75 0 0 1 .75.75v2.19l2.72-2.72a.749.749 0 0 1 .53-.22h4.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z"
-                    />
-                  </svg>
+                    <svg
+                      aria-label="1 comment"
+                      class="mr-1 text-gray-500 dark:text-gray-300"
+                      fill="currentColor"
+                      focusable="false"
+                      height="12"
+                      role="img"
+                      style="display: inline-block; user-select: none; vertical-align: text-bottom; overflow: visible;"
+                      viewBox="0 0 16 16"
+                      width="12"
+                    >
+                      <path
+                        d="M1 2.75C1 1.784 1.784 1 2.75 1h10.5c.966 0 1.75.784 1.75 1.75v7.5A1.75 1.75 0 0 1 13.25 12H9.06l-2.573 2.573A1.458 1.458 0 0 1 4 13.543V12H2.75A1.75 1.75 0 0 1 1 10.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h2a.75.75 0 0 1 .75.75v2.19l2.72-2.72a.749.749 0 0 1 .53-.22h4.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z"
+                      />
+                    </svg>
+                    1
+                  </button>
                 </span>
               </span>
             </span>
@@ -292,61 +310,79 @@ exports[`components/NotificationRow.tsx rendering for notification comments coun
                 class="ml-1"
                 title="octocat approved these changes"
               >
-                <svg
-                  aria-label="octocat approved these changes"
-                  class="text-green-500"
-                  fill="currentColor"
-                  focusable="false"
-                  height="16"
-                  role="img"
-                  style="display: inline-block; user-select: none; vertical-align: text-bottom; overflow: visible;"
-                  viewBox="0 0 16 16"
-                  width="16"
+                <button
+                  class="rounded-full text-xs px-2 bg-gray-100 hover:bg-gray-200 dark:bg-gray-800 dark:hover:bg-gray-700"
+                  type="button"
                 >
-                  <path
-                    d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z"
-                  />
-                </svg>
+                  <svg
+                    aria-label="octocat approved these changes"
+                    class="mr-1 text-green-500"
+                    fill="currentColor"
+                    focusable="false"
+                    height="12"
+                    role="img"
+                    style="display: inline-block; user-select: none; vertical-align: text-bottom; overflow: visible;"
+                    viewBox="0 0 16 16"
+                    width="12"
+                  >
+                    <path
+                      d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z"
+                    />
+                  </svg>
+                  1
+                </button>
               </span>
               <span
                 class="ml-1"
                 title="gitify-app requested changes"
               >
-                <svg
-                  aria-label="gitify-app requested changes"
-                  class="text-red-500"
-                  fill="currentColor"
-                  focusable="false"
-                  height="16"
-                  role="img"
-                  style="display: inline-block; user-select: none; vertical-align: text-bottom; overflow: visible;"
-                  viewBox="0 0 16 16"
-                  width="16"
+                <button
+                  class="rounded-full text-xs px-2 bg-gray-100 hover:bg-gray-200 dark:bg-gray-800 dark:hover:bg-gray-700"
+                  type="button"
                 >
-                  <path
-                    d="M1 1.75C1 .784 1.784 0 2.75 0h7.586c.464 0 .909.184 1.237.513l2.914 2.914c.329.328.513.773.513 1.237v9.586A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25Zm1.75-.25a.25.25 0 0 0-.25.25v12.5c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V4.664a.25.25 0 0 0-.073-.177l-2.914-2.914a.25.25 0 0 0-.177-.073ZM8 3.25a.75.75 0 0 1 .75.75v1.5h1.5a.75.75 0 0 1 0 1.5h-1.5v1.5a.75.75 0 0 1-1.5 0V7h-1.5a.75.75 0 0 1 0-1.5h1.5V4A.75.75 0 0 1 8 3.25Zm-3 8a.75.75 0 0 1 .75-.75h4.5a.75.75 0 0 1 0 1.5h-4.5a.75.75 0 0 1-.75-.75Z"
-                  />
-                </svg>
+                  <svg
+                    aria-label="gitify-app requested changes"
+                    class="mr-1 text-red-500"
+                    fill="currentColor"
+                    focusable="false"
+                    height="12"
+                    role="img"
+                    style="display: inline-block; user-select: none; vertical-align: text-bottom; overflow: visible;"
+                    viewBox="0 0 16 16"
+                    width="12"
+                  >
+                    <path
+                      d="M1 1.75C1 .784 1.784 0 2.75 0h7.586c.464 0 .909.184 1.237.513l2.914 2.914c.329.328.513.773.513 1.237v9.586A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25Zm1.75-.25a.25.25 0 0 0-.25.25v12.5c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V4.664a.25.25 0 0 0-.073-.177l-2.914-2.914a.25.25 0 0 0-.177-.073ZM8 3.25a.75.75 0 0 1 .75.75v1.5h1.5a.75.75 0 0 1 0 1.5h-1.5v1.5a.75.75 0 0 1-1.5 0V7h-1.5a.75.75 0 0 1 0-1.5h1.5V4A.75.75 0 0 1 8 3.25Zm-3 8a.75.75 0 0 1 .75-.75h4.5a.75.75 0 0 1 0 1.5h-4.5a.75.75 0 0 1-.75-.75Z"
+                    />
+                  </svg>
+                  1
+                </button>
               </span>
               <span
                 class="ml-1"
                 title="1 comment"
               >
-                <svg
-                  aria-label="1 comment"
-                  class="text-gray-500 dark:text-gray-300"
-                  fill="currentColor"
-                  focusable="false"
-                  height="16"
-                  role="img"
-                  style="display: inline-block; user-select: none; vertical-align: text-bottom; overflow: visible;"
-                  viewBox="0 0 16 16"
-                  width="16"
+                <button
+                  class="rounded-full text-xs px-2 bg-gray-100 hover:bg-gray-200 dark:bg-gray-800 dark:hover:bg-gray-700"
+                  type="button"
                 >
-                  <path
-                    d="M1 2.75C1 1.784 1.784 1 2.75 1h10.5c.966 0 1.75.784 1.75 1.75v7.5A1.75 1.75 0 0 1 13.25 12H9.06l-2.573 2.573A1.458 1.458 0 0 1 4 13.543V12H2.75A1.75 1.75 0 0 1 1 10.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h2a.75.75 0 0 1 .75.75v2.19l2.72-2.72a.749.749 0 0 1 .53-.22h4.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z"
-                  />
-                </svg>
+                  <svg
+                    aria-label="1 comment"
+                    class="mr-1 text-gray-500 dark:text-gray-300"
+                    fill="currentColor"
+                    focusable="false"
+                    height="12"
+                    role="img"
+                    style="display: inline-block; user-select: none; vertical-align: text-bottom; overflow: visible;"
+                    viewBox="0 0 16 16"
+                    width="12"
+                  >
+                    <path
+                      d="M1 2.75C1 1.784 1.784 1 2.75 1h10.5c.966 0 1.75.784 1.75 1.75v7.5A1.75 1.75 0 0 1 13.25 12H9.06l-2.573 2.573A1.458 1.458 0 0 1 4 13.543V12H2.75A1.75 1.75 0 0 1 1 10.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h2a.75.75 0 0 1 .75.75v2.19l2.72-2.72a.749.749 0 0 1 .53-.22h4.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z"
+                    />
+                  </svg>
+                  1
+                </button>
               </span>
             </span>
           </span>
@@ -558,61 +594,79 @@ exports[`components/NotificationRow.tsx rendering for notification comments coun
                   class="ml-1"
                   title="octocat approved these changes"
                 >
-                  <svg
-                    aria-label="octocat approved these changes"
-                    class="text-green-500"
-                    fill="currentColor"
-                    focusable="false"
-                    height="16"
-                    role="img"
-                    style="display: inline-block; user-select: none; vertical-align: text-bottom; overflow: visible;"
-                    viewBox="0 0 16 16"
-                    width="16"
+                  <button
+                    class="rounded-full text-xs px-2 bg-gray-100 hover:bg-gray-200 dark:bg-gray-800 dark:hover:bg-gray-700"
+                    type="button"
                   >
-                    <path
-                      d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z"
-                    />
-                  </svg>
+                    <svg
+                      aria-label="octocat approved these changes"
+                      class="mr-1 text-green-500"
+                      fill="currentColor"
+                      focusable="false"
+                      height="12"
+                      role="img"
+                      style="display: inline-block; user-select: none; vertical-align: text-bottom; overflow: visible;"
+                      viewBox="0 0 16 16"
+                      width="12"
+                    >
+                      <path
+                        d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z"
+                      />
+                    </svg>
+                    1
+                  </button>
                 </span>
                 <span
                   class="ml-1"
                   title="gitify-app requested changes"
                 >
-                  <svg
-                    aria-label="gitify-app requested changes"
-                    class="text-red-500"
-                    fill="currentColor"
-                    focusable="false"
-                    height="16"
-                    role="img"
-                    style="display: inline-block; user-select: none; vertical-align: text-bottom; overflow: visible;"
-                    viewBox="0 0 16 16"
-                    width="16"
+                  <button
+                    class="rounded-full text-xs px-2 bg-gray-100 hover:bg-gray-200 dark:bg-gray-800 dark:hover:bg-gray-700"
+                    type="button"
                   >
-                    <path
-                      d="M1 1.75C1 .784 1.784 0 2.75 0h7.586c.464 0 .909.184 1.237.513l2.914 2.914c.329.328.513.773.513 1.237v9.586A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25Zm1.75-.25a.25.25 0 0 0-.25.25v12.5c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V4.664a.25.25 0 0 0-.073-.177l-2.914-2.914a.25.25 0 0 0-.177-.073ZM8 3.25a.75.75 0 0 1 .75.75v1.5h1.5a.75.75 0 0 1 0 1.5h-1.5v1.5a.75.75 0 0 1-1.5 0V7h-1.5a.75.75 0 0 1 0-1.5h1.5V4A.75.75 0 0 1 8 3.25Zm-3 8a.75.75 0 0 1 .75-.75h4.5a.75.75 0 0 1 0 1.5h-4.5a.75.75 0 0 1-.75-.75Z"
-                    />
-                  </svg>
+                    <svg
+                      aria-label="gitify-app requested changes"
+                      class="mr-1 text-red-500"
+                      fill="currentColor"
+                      focusable="false"
+                      height="12"
+                      role="img"
+                      style="display: inline-block; user-select: none; vertical-align: text-bottom; overflow: visible;"
+                      viewBox="0 0 16 16"
+                      width="12"
+                    >
+                      <path
+                        d="M1 1.75C1 .784 1.784 0 2.75 0h7.586c.464 0 .909.184 1.237.513l2.914 2.914c.329.328.513.773.513 1.237v9.586A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25Zm1.75-.25a.25.25 0 0 0-.25.25v12.5c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V4.664a.25.25 0 0 0-.073-.177l-2.914-2.914a.25.25 0 0 0-.177-.073ZM8 3.25a.75.75 0 0 1 .75.75v1.5h1.5a.75.75 0 0 1 0 1.5h-1.5v1.5a.75.75 0 0 1-1.5 0V7h-1.5a.75.75 0 0 1 0-1.5h1.5V4A.75.75 0 0 1 8 3.25Zm-3 8a.75.75 0 0 1 .75-.75h4.5a.75.75 0 0 1 0 1.5h-4.5a.75.75 0 0 1-.75-.75Z"
+                      />
+                    </svg>
+                    1
+                  </button>
                 </span>
                 <span
                   class="ml-1"
                   title="2 comments"
                 >
-                  <svg
-                    aria-label="2 comments"
-                    class="text-gray-500 dark:text-gray-300"
-                    fill="currentColor"
-                    focusable="false"
-                    height="16"
-                    role="img"
-                    style="display: inline-block; user-select: none; vertical-align: text-bottom; overflow: visible;"
-                    viewBox="0 0 16 16"
-                    width="16"
+                  <button
+                    class="rounded-full text-xs px-2 bg-gray-100 hover:bg-gray-200 dark:bg-gray-800 dark:hover:bg-gray-700"
+                    type="button"
                   >
-                    <path
-                      d="M1 2.75C1 1.784 1.784 1 2.75 1h10.5c.966 0 1.75.784 1.75 1.75v7.5A1.75 1.75 0 0 1 13.25 12H9.06l-2.573 2.573A1.458 1.458 0 0 1 4 13.543V12H2.75A1.75 1.75 0 0 1 1 10.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h2a.75.75 0 0 1 .75.75v2.19l2.72-2.72a.749.749 0 0 1 .53-.22h4.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z"
-                    />
-                  </svg>
+                    <svg
+                      aria-label="2 comments"
+                      class="mr-1 text-gray-500 dark:text-gray-300"
+                      fill="currentColor"
+                      focusable="false"
+                      height="12"
+                      role="img"
+                      style="display: inline-block; user-select: none; vertical-align: text-bottom; overflow: visible;"
+                      viewBox="0 0 16 16"
+                      width="12"
+                    >
+                      <path
+                        d="M1 2.75C1 1.784 1.784 1 2.75 1h10.5c.966 0 1.75.784 1.75 1.75v7.5A1.75 1.75 0 0 1 13.25 12H9.06l-2.573 2.573A1.458 1.458 0 0 1 4 13.543V12H2.75A1.75 1.75 0 0 1 1 10.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h2a.75.75 0 0 1 .75.75v2.19l2.72-2.72a.749.749 0 0 1 .53-.22h4.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z"
+                      />
+                    </svg>
+                    2
+                  </button>
                 </span>
               </span>
             </span>
@@ -767,61 +821,79 @@ exports[`components/NotificationRow.tsx rendering for notification comments coun
                 class="ml-1"
                 title="octocat approved these changes"
               >
-                <svg
-                  aria-label="octocat approved these changes"
-                  class="text-green-500"
-                  fill="currentColor"
-                  focusable="false"
-                  height="16"
-                  role="img"
-                  style="display: inline-block; user-select: none; vertical-align: text-bottom; overflow: visible;"
-                  viewBox="0 0 16 16"
-                  width="16"
+                <button
+                  class="rounded-full text-xs px-2 bg-gray-100 hover:bg-gray-200 dark:bg-gray-800 dark:hover:bg-gray-700"
+                  type="button"
                 >
-                  <path
-                    d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z"
-                  />
-                </svg>
+                  <svg
+                    aria-label="octocat approved these changes"
+                    class="mr-1 text-green-500"
+                    fill="currentColor"
+                    focusable="false"
+                    height="12"
+                    role="img"
+                    style="display: inline-block; user-select: none; vertical-align: text-bottom; overflow: visible;"
+                    viewBox="0 0 16 16"
+                    width="12"
+                  >
+                    <path
+                      d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z"
+                    />
+                  </svg>
+                  1
+                </button>
               </span>
               <span
                 class="ml-1"
                 title="gitify-app requested changes"
               >
-                <svg
-                  aria-label="gitify-app requested changes"
-                  class="text-red-500"
-                  fill="currentColor"
-                  focusable="false"
-                  height="16"
-                  role="img"
-                  style="display: inline-block; user-select: none; vertical-align: text-bottom; overflow: visible;"
-                  viewBox="0 0 16 16"
-                  width="16"
+                <button
+                  class="rounded-full text-xs px-2 bg-gray-100 hover:bg-gray-200 dark:bg-gray-800 dark:hover:bg-gray-700"
+                  type="button"
                 >
-                  <path
-                    d="M1 1.75C1 .784 1.784 0 2.75 0h7.586c.464 0 .909.184 1.237.513l2.914 2.914c.329.328.513.773.513 1.237v9.586A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25Zm1.75-.25a.25.25 0 0 0-.25.25v12.5c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V4.664a.25.25 0 0 0-.073-.177l-2.914-2.914a.25.25 0 0 0-.177-.073ZM8 3.25a.75.75 0 0 1 .75.75v1.5h1.5a.75.75 0 0 1 0 1.5h-1.5v1.5a.75.75 0 0 1-1.5 0V7h-1.5a.75.75 0 0 1 0-1.5h1.5V4A.75.75 0 0 1 8 3.25Zm-3 8a.75.75 0 0 1 .75-.75h4.5a.75.75 0 0 1 0 1.5h-4.5a.75.75 0 0 1-.75-.75Z"
-                  />
-                </svg>
+                  <svg
+                    aria-label="gitify-app requested changes"
+                    class="mr-1 text-red-500"
+                    fill="currentColor"
+                    focusable="false"
+                    height="12"
+                    role="img"
+                    style="display: inline-block; user-select: none; vertical-align: text-bottom; overflow: visible;"
+                    viewBox="0 0 16 16"
+                    width="12"
+                  >
+                    <path
+                      d="M1 1.75C1 .784 1.784 0 2.75 0h7.586c.464 0 .909.184 1.237.513l2.914 2.914c.329.328.513.773.513 1.237v9.586A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25Zm1.75-.25a.25.25 0 0 0-.25.25v12.5c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V4.664a.25.25 0 0 0-.073-.177l-2.914-2.914a.25.25 0 0 0-.177-.073ZM8 3.25a.75.75 0 0 1 .75.75v1.5h1.5a.75.75 0 0 1 0 1.5h-1.5v1.5a.75.75 0 0 1-1.5 0V7h-1.5a.75.75 0 0 1 0-1.5h1.5V4A.75.75 0 0 1 8 3.25Zm-3 8a.75.75 0 0 1 .75-.75h4.5a.75.75 0 0 1 0 1.5h-4.5a.75.75 0 0 1-.75-.75Z"
+                    />
+                  </svg>
+                  1
+                </button>
               </span>
               <span
                 class="ml-1"
                 title="2 comments"
               >
-                <svg
-                  aria-label="2 comments"
-                  class="text-gray-500 dark:text-gray-300"
-                  fill="currentColor"
-                  focusable="false"
-                  height="16"
-                  role="img"
-                  style="display: inline-block; user-select: none; vertical-align: text-bottom; overflow: visible;"
-                  viewBox="0 0 16 16"
-                  width="16"
+                <button
+                  class="rounded-full text-xs px-2 bg-gray-100 hover:bg-gray-200 dark:bg-gray-800 dark:hover:bg-gray-700"
+                  type="button"
                 >
-                  <path
-                    d="M1 2.75C1 1.784 1.784 1 2.75 1h10.5c.966 0 1.75.784 1.75 1.75v7.5A1.75 1.75 0 0 1 13.25 12H9.06l-2.573 2.573A1.458 1.458 0 0 1 4 13.543V12H2.75A1.75 1.75 0 0 1 1 10.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h2a.75.75 0 0 1 .75.75v2.19l2.72-2.72a.749.749 0 0 1 .53-.22h4.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z"
-                  />
-                </svg>
+                  <svg
+                    aria-label="2 comments"
+                    class="mr-1 text-gray-500 dark:text-gray-300"
+                    fill="currentColor"
+                    focusable="false"
+                    height="12"
+                    role="img"
+                    style="display: inline-block; user-select: none; vertical-align: text-bottom; overflow: visible;"
+                    viewBox="0 0 16 16"
+                    width="12"
+                  >
+                    <path
+                      d="M1 2.75C1 1.784 1.784 1 2.75 1h10.5c.966 0 1.75.784 1.75 1.75v7.5A1.75 1.75 0 0 1 13.25 12H9.06l-2.573 2.573A1.458 1.458 0 0 1 4 13.543V12H2.75A1.75 1.75 0 0 1 1 10.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h2a.75.75 0 0 1 .75.75v2.19l2.72-2.72a.749.749 0 0 1 .53-.22h4.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z"
+                    />
+                  </svg>
+                  2
+                </button>
               </span>
             </span>
           </span>
@@ -1033,41 +1105,53 @@ exports[`components/NotificationRow.tsx rendering for notification comments coun
                   class="ml-1"
                   title="octocat approved these changes"
                 >
-                  <svg
-                    aria-label="octocat approved these changes"
-                    class="text-green-500"
-                    fill="currentColor"
-                    focusable="false"
-                    height="16"
-                    role="img"
-                    style="display: inline-block; user-select: none; vertical-align: text-bottom; overflow: visible;"
-                    viewBox="0 0 16 16"
-                    width="16"
+                  <button
+                    class="rounded-full text-xs px-2 bg-gray-100 hover:bg-gray-200 dark:bg-gray-800 dark:hover:bg-gray-700"
+                    type="button"
                   >
-                    <path
-                      d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z"
-                    />
-                  </svg>
+                    <svg
+                      aria-label="octocat approved these changes"
+                      class="mr-1 text-green-500"
+                      fill="currentColor"
+                      focusable="false"
+                      height="12"
+                      role="img"
+                      style="display: inline-block; user-select: none; vertical-align: text-bottom; overflow: visible;"
+                      viewBox="0 0 16 16"
+                      width="12"
+                    >
+                      <path
+                        d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z"
+                      />
+                    </svg>
+                    1
+                  </button>
                 </span>
                 <span
                   class="ml-1"
                   title="gitify-app requested changes"
                 >
-                  <svg
-                    aria-label="gitify-app requested changes"
-                    class="text-red-500"
-                    fill="currentColor"
-                    focusable="false"
-                    height="16"
-                    role="img"
-                    style="display: inline-block; user-select: none; vertical-align: text-bottom; overflow: visible;"
-                    viewBox="0 0 16 16"
-                    width="16"
+                  <button
+                    class="rounded-full text-xs px-2 bg-gray-100 hover:bg-gray-200 dark:bg-gray-800 dark:hover:bg-gray-700"
+                    type="button"
                   >
-                    <path
-                      d="M1 1.75C1 .784 1.784 0 2.75 0h7.586c.464 0 .909.184 1.237.513l2.914 2.914c.329.328.513.773.513 1.237v9.586A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25Zm1.75-.25a.25.25 0 0 0-.25.25v12.5c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V4.664a.25.25 0 0 0-.073-.177l-2.914-2.914a.25.25 0 0 0-.177-.073ZM8 3.25a.75.75 0 0 1 .75.75v1.5h1.5a.75.75 0 0 1 0 1.5h-1.5v1.5a.75.75 0 0 1-1.5 0V7h-1.5a.75.75 0 0 1 0-1.5h1.5V4A.75.75 0 0 1 8 3.25Zm-3 8a.75.75 0 0 1 .75-.75h4.5a.75.75 0 0 1 0 1.5h-4.5a.75.75 0 0 1-.75-.75Z"
-                    />
-                  </svg>
+                    <svg
+                      aria-label="gitify-app requested changes"
+                      class="mr-1 text-red-500"
+                      fill="currentColor"
+                      focusable="false"
+                      height="12"
+                      role="img"
+                      style="display: inline-block; user-select: none; vertical-align: text-bottom; overflow: visible;"
+                      viewBox="0 0 16 16"
+                      width="12"
+                    >
+                      <path
+                        d="M1 1.75C1 .784 1.784 0 2.75 0h7.586c.464 0 .909.184 1.237.513l2.914 2.914c.329.328.513.773.513 1.237v9.586A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25Zm1.75-.25a.25.25 0 0 0-.25.25v12.5c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V4.664a.25.25 0 0 0-.073-.177l-2.914-2.914a.25.25 0 0 0-.177-.073ZM8 3.25a.75.75 0 0 1 .75.75v1.5h1.5a.75.75 0 0 1 0 1.5h-1.5v1.5a.75.75 0 0 1-1.5 0V7h-1.5a.75.75 0 0 1 0-1.5h1.5V4A.75.75 0 0 1 8 3.25Zm-3 8a.75.75 0 0 1 .75-.75h4.5a.75.75 0 0 1 0 1.5h-4.5a.75.75 0 0 1-.75-.75Z"
+                      />
+                    </svg>
+                    1
+                  </button>
                 </span>
               </span>
             </span>
@@ -1222,41 +1306,53 @@ exports[`components/NotificationRow.tsx rendering for notification comments coun
                 class="ml-1"
                 title="octocat approved these changes"
               >
-                <svg
-                  aria-label="octocat approved these changes"
-                  class="text-green-500"
-                  fill="currentColor"
-                  focusable="false"
-                  height="16"
-                  role="img"
-                  style="display: inline-block; user-select: none; vertical-align: text-bottom; overflow: visible;"
-                  viewBox="0 0 16 16"
-                  width="16"
+                <button
+                  class="rounded-full text-xs px-2 bg-gray-100 hover:bg-gray-200 dark:bg-gray-800 dark:hover:bg-gray-700"
+                  type="button"
                 >
-                  <path
-                    d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z"
-                  />
-                </svg>
+                  <svg
+                    aria-label="octocat approved these changes"
+                    class="mr-1 text-green-500"
+                    fill="currentColor"
+                    focusable="false"
+                    height="12"
+                    role="img"
+                    style="display: inline-block; user-select: none; vertical-align: text-bottom; overflow: visible;"
+                    viewBox="0 0 16 16"
+                    width="12"
+                  >
+                    <path
+                      d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z"
+                    />
+                  </svg>
+                  1
+                </button>
               </span>
               <span
                 class="ml-1"
                 title="gitify-app requested changes"
               >
-                <svg
-                  aria-label="gitify-app requested changes"
-                  class="text-red-500"
-                  fill="currentColor"
-                  focusable="false"
-                  height="16"
-                  role="img"
-                  style="display: inline-block; user-select: none; vertical-align: text-bottom; overflow: visible;"
-                  viewBox="0 0 16 16"
-                  width="16"
+                <button
+                  class="rounded-full text-xs px-2 bg-gray-100 hover:bg-gray-200 dark:bg-gray-800 dark:hover:bg-gray-700"
+                  type="button"
                 >
-                  <path
-                    d="M1 1.75C1 .784 1.784 0 2.75 0h7.586c.464 0 .909.184 1.237.513l2.914 2.914c.329.328.513.773.513 1.237v9.586A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25Zm1.75-.25a.25.25 0 0 0-.25.25v12.5c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V4.664a.25.25 0 0 0-.073-.177l-2.914-2.914a.25.25 0 0 0-.177-.073ZM8 3.25a.75.75 0 0 1 .75.75v1.5h1.5a.75.75 0 0 1 0 1.5h-1.5v1.5a.75.75 0 0 1-1.5 0V7h-1.5a.75.75 0 0 1 0-1.5h1.5V4A.75.75 0 0 1 8 3.25Zm-3 8a.75.75 0 0 1 .75-.75h4.5a.75.75 0 0 1 0 1.5h-4.5a.75.75 0 0 1-.75-.75Z"
-                  />
-                </svg>
+                  <svg
+                    aria-label="gitify-app requested changes"
+                    class="mr-1 text-red-500"
+                    fill="currentColor"
+                    focusable="false"
+                    height="12"
+                    role="img"
+                    style="display: inline-block; user-select: none; vertical-align: text-bottom; overflow: visible;"
+                    viewBox="0 0 16 16"
+                    width="12"
+                  >
+                    <path
+                      d="M1 1.75C1 .784 1.784 0 2.75 0h7.586c.464 0 .909.184 1.237.513l2.914 2.914c.329.328.513.773.513 1.237v9.586A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25Zm1.75-.25a.25.25 0 0 0-.25.25v12.5c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V4.664a.25.25 0 0 0-.073-.177l-2.914-2.914a.25.25 0 0 0-.177-.073ZM8 3.25a.75.75 0 0 1 .75.75v1.5h1.5a.75.75 0 0 1 0 1.5h-1.5v1.5a.75.75 0 0 1-1.5 0V7h-1.5a.75.75 0 0 1 0-1.5h1.5V4A.75.75 0 0 1 8 3.25Zm-3 8a.75.75 0 0 1 .75-.75h4.5a.75.75 0 0 1 0 1.5h-4.5a.75.75 0 0 1-.75-.75Z"
+                    />
+                  </svg>
+                  1
+                </button>
               </span>
             </span>
           </span>
@@ -1462,41 +1558,53 @@ exports[`components/NotificationRow.tsx should render itself & its children 1`]
                   class="ml-1"
                   title="octocat approved these changes"
                 >
-                  <svg
-                    aria-label="octocat approved these changes"
-                    class="text-green-500"
-                    fill="currentColor"
-                    focusable="false"
-                    height="16"
-                    role="img"
-                    style="display: inline-block; user-select: none; vertical-align: text-bottom; overflow: visible;"
-                    viewBox="0 0 16 16"
-                    width="16"
+                  <button
+                    class="rounded-full text-xs px-2 bg-gray-100 hover:bg-gray-200 dark:bg-gray-800 dark:hover:bg-gray-700"
+                    type="button"
                   >
-                    <path
-                      d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z"
-                    />
-                  </svg>
+                    <svg
+                      aria-label="octocat approved these changes"
+                      class="mr-1 text-green-500"
+                      fill="currentColor"
+                      focusable="false"
+                      height="12"
+                      role="img"
+                      style="display: inline-block; user-select: none; vertical-align: text-bottom; overflow: visible;"
+                      viewBox="0 0 16 16"
+                      width="12"
+                    >
+                      <path
+                        d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z"
+                      />
+                    </svg>
+                    1
+                  </button>
                 </span>
                 <span
                   class="ml-1"
                   title="gitify-app requested changes"
                 >
-                  <svg
-                    aria-label="gitify-app requested changes"
-                    class="text-red-500"
-                    fill="currentColor"
-                    focusable="false"
-                    height="16"
-                    role="img"
-                    style="display: inline-block; user-select: none; vertical-align: text-bottom; overflow: visible;"
-                    viewBox="0 0 16 16"
-                    width="16"
+                  <button
+                    class="rounded-full text-xs px-2 bg-gray-100 hover:bg-gray-200 dark:bg-gray-800 dark:hover:bg-gray-700"
+                    type="button"
                   >
-                    <path
-                      d="M1 1.75C1 .784 1.784 0 2.75 0h7.586c.464 0 .909.184 1.237.513l2.914 2.914c.329.328.513.773.513 1.237v9.586A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25Zm1.75-.25a.25.25 0 0 0-.25.25v12.5c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V4.664a.25.25 0 0 0-.073-.177l-2.914-2.914a.25.25 0 0 0-.177-.073ZM8 3.25a.75.75 0 0 1 .75.75v1.5h1.5a.75.75 0 0 1 0 1.5h-1.5v1.5a.75.75 0 0 1-1.5 0V7h-1.5a.75.75 0 0 1 0-1.5h1.5V4A.75.75 0 0 1 8 3.25Zm-3 8a.75.75 0 0 1 .75-.75h4.5a.75.75 0 0 1 0 1.5h-4.5a.75.75 0 0 1-.75-.75Z"
-                    />
-                  </svg>
+                    <svg
+                      aria-label="gitify-app requested changes"
+                      class="mr-1 text-red-500"
+                      fill="currentColor"
+                      focusable="false"
+                      height="12"
+                      role="img"
+                      style="display: inline-block; user-select: none; vertical-align: text-bottom; overflow: visible;"
+                      viewBox="0 0 16 16"
+                      width="12"
+                    >
+                      <path
+                        d="M1 1.75C1 .784 1.784 0 2.75 0h7.586c.464 0 .909.184 1.237.513l2.914 2.914c.329.328.513.773.513 1.237v9.586A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25Zm1.75-.25a.25.25 0 0 0-.25.25v12.5c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V4.664a.25.25 0 0 0-.073-.177l-2.914-2.914a.25.25 0 0 0-.177-.073ZM8 3.25a.75.75 0 0 1 .75.75v1.5h1.5a.75.75 0 0 1 0 1.5h-1.5v1.5a.75.75 0 0 1-1.5 0V7h-1.5a.75.75 0 0 1 0-1.5h1.5V4A.75.75 0 0 1 8 3.25Zm-3 8a.75.75 0 0 1 .75-.75h4.5a.75.75 0 0 1 0 1.5h-4.5a.75.75 0 0 1-.75-.75Z"
+                      />
+                    </svg>
+                    1
+                  </button>
                 </span>
               </span>
             </span>
@@ -1645,41 +1753,53 @@ exports[`components/NotificationRow.tsx should render itself & its children 1`]
                 class="ml-1"
                 title="octocat approved these changes"
               >
-                <svg
-                  aria-label="octocat approved these changes"
-                  class="text-green-500"
-                  fill="currentColor"
-                  focusable="false"
-                  height="16"
-                  role="img"
-                  style="display: inline-block; user-select: none; vertical-align: text-bottom; overflow: visible;"
-                  viewBox="0 0 16 16"
-                  width="16"
+                <button
+                  class="rounded-full text-xs px-2 bg-gray-100 hover:bg-gray-200 dark:bg-gray-800 dark:hover:bg-gray-700"
+                  type="button"
                 >
-                  <path
-                    d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z"
-                  />
-                </svg>
+                  <svg
+                    aria-label="octocat approved these changes"
+                    class="mr-1 text-green-500"
+                    fill="currentColor"
+                    focusable="false"
+                    height="12"
+                    role="img"
+                    style="display: inline-block; user-select: none; vertical-align: text-bottom; overflow: visible;"
+                    viewBox="0 0 16 16"
+                    width="12"
+                  >
+                    <path
+                      d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z"
+                    />
+                  </svg>
+                  1
+                </button>
               </span>
               <span
                 class="ml-1"
                 title="gitify-app requested changes"
               >
-                <svg
-                  aria-label="gitify-app requested changes"
-                  class="text-red-500"
-                  fill="currentColor"
-                  focusable="false"
-                  height="16"
-                  role="img"
-                  style="display: inline-block; user-select: none; vertical-align: text-bottom; overflow: visible;"
-                  viewBox="0 0 16 16"
-                  width="16"
+                <button
+                  class="rounded-full text-xs px-2 bg-gray-100 hover:bg-gray-200 dark:bg-gray-800 dark:hover:bg-gray-700"
+                  type="button"
                 >
-                  <path
-                    d="M1 1.75C1 .784 1.784 0 2.75 0h7.586c.464 0 .909.184 1.237.513l2.914 2.914c.329.328.513.773.513 1.237v9.586A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25Zm1.75-.25a.25.25 0 0 0-.25.25v12.5c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V4.664a.25.25 0 0 0-.073-.177l-2.914-2.914a.25.25 0 0 0-.177-.073ZM8 3.25a.75.75 0 0 1 .75.75v1.5h1.5a.75.75 0 0 1 0 1.5h-1.5v1.5a.75.75 0 0 1-1.5 0V7h-1.5a.75.75 0 0 1 0-1.5h1.5V4A.75.75 0 0 1 8 3.25Zm-3 8a.75.75 0 0 1 .75-.75h4.5a.75.75 0 0 1 0 1.5h-4.5a.75.75 0 0 1-.75-.75Z"
-                  />
-                </svg>
+                  <svg
+                    aria-label="gitify-app requested changes"
+                    class="mr-1 text-red-500"
+                    fill="currentColor"
+                    focusable="false"
+                    height="12"
+                    role="img"
+                    style="display: inline-block; user-select: none; vertical-align: text-bottom; overflow: visible;"
+                    viewBox="0 0 16 16"
+                    width="12"
+                  >
+                    <path
+                      d="M1 1.75C1 .784 1.784 0 2.75 0h7.586c.464 0 .909.184 1.237.513l2.914 2.914c.329.328.513.773.513 1.237v9.586A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25Zm1.75-.25a.25.25 0 0 0-.25.25v12.5c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V4.664a.25.25 0 0 0-.073-.177l-2.914-2.914a.25.25 0 0 0-.177-.073ZM8 3.25a.75.75 0 0 1 .75.75v1.5h1.5a.75.75 0 0 1 0 1.5h-1.5v1.5a.75.75 0 0 1-1.5 0V7h-1.5a.75.75 0 0 1 0-1.5h1.5V4A.75.75 0 0 1 8 3.25Zm-3 8a.75.75 0 0 1 .75-.75h4.5a.75.75 0 0 1 0 1.5h-4.5a.75.75 0 0 1-.75-.75Z"
+                    />
+                  </svg>
+                  1
+                </button>
               </span>
             </span>
           </span>
@@ -1885,41 +2005,53 @@ exports[`components/NotificationRow.tsx should render itself & its children when
                   class="ml-1"
                   title="octocat approved these changes"
                 >
-                  <svg
-                    aria-label="octocat approved these changes"
-                    class="text-green-500"
-                    fill="currentColor"
-                    focusable="false"
-                    height="16"
-                    role="img"
-                    style="display: inline-block; user-select: none; vertical-align: text-bottom; overflow: visible;"
-                    viewBox="0 0 16 16"
-                    width="16"
+                  <button
+                    class="rounded-full text-xs px-2 bg-gray-100 hover:bg-gray-200 dark:bg-gray-800 dark:hover:bg-gray-700"
+                    type="button"
                   >
-                    <path
-                      d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z"
-                    />
-                  </svg>
+                    <svg
+                      aria-label="octocat approved these changes"
+                      class="mr-1 text-green-500"
+                      fill="currentColor"
+                      focusable="false"
+                      height="12"
+                      role="img"
+                      style="display: inline-block; user-select: none; vertical-align: text-bottom; overflow: visible;"
+                      viewBox="0 0 16 16"
+                      width="12"
+                    >
+                      <path
+                        d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z"
+                      />
+                    </svg>
+                    1
+                  </button>
                 </span>
                 <span
                   class="ml-1"
                   title="gitify-app requested changes"
                 >
-                  <svg
-                    aria-label="gitify-app requested changes"
-                    class="text-red-500"
-                    fill="currentColor"
-                    focusable="false"
-                    height="16"
-                    role="img"
-                    style="display: inline-block; user-select: none; vertical-align: text-bottom; overflow: visible;"
-                    viewBox="0 0 16 16"
-                    width="16"
+                  <button
+                    class="rounded-full text-xs px-2 bg-gray-100 hover:bg-gray-200 dark:bg-gray-800 dark:hover:bg-gray-700"
+                    type="button"
                   >
-                    <path
-                      d="M1 1.75C1 .784 1.784 0 2.75 0h7.586c.464 0 .909.184 1.237.513l2.914 2.914c.329.328.513.773.513 1.237v9.586A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25Zm1.75-.25a.25.25 0 0 0-.25.25v12.5c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V4.664a.25.25 0 0 0-.073-.177l-2.914-2.914a.25.25 0 0 0-.177-.073ZM8 3.25a.75.75 0 0 1 .75.75v1.5h1.5a.75.75 0 0 1 0 1.5h-1.5v1.5a.75.75 0 0 1-1.5 0V7h-1.5a.75.75 0 0 1 0-1.5h1.5V4A.75.75 0 0 1 8 3.25Zm-3 8a.75.75 0 0 1 .75-.75h4.5a.75.75 0 0 1 0 1.5h-4.5a.75.75 0 0 1-.75-.75Z"
-                    />
-                  </svg>
+                    <svg
+                      aria-label="gitify-app requested changes"
+                      class="mr-1 text-red-500"
+                      fill="currentColor"
+                      focusable="false"
+                      height="12"
+                      role="img"
+                      style="display: inline-block; user-select: none; vertical-align: text-bottom; overflow: visible;"
+                      viewBox="0 0 16 16"
+                      width="12"
+                    >
+                      <path
+                        d="M1 1.75C1 .784 1.784 0 2.75 0h7.586c.464 0 .909.184 1.237.513l2.914 2.914c.329.328.513.773.513 1.237v9.586A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25Zm1.75-.25a.25.25 0 0 0-.25.25v12.5c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V4.664a.25.25 0 0 0-.073-.177l-2.914-2.914a.25.25 0 0 0-.177-.073ZM8 3.25a.75.75 0 0 1 .75.75v1.5h1.5a.75.75 0 0 1 0 1.5h-1.5v1.5a.75.75 0 0 1-1.5 0V7h-1.5a.75.75 0 0 1 0-1.5h1.5V4A.75.75 0 0 1 8 3.25Zm-3 8a.75.75 0 0 1 .75-.75h4.5a.75.75 0 0 1 0 1.5h-4.5a.75.75 0 0 1-.75-.75Z"
+                      />
+                    </svg>
+                    1
+                  </button>
                 </span>
               </span>
             </span>
@@ -2068,41 +2200,53 @@ exports[`components/NotificationRow.tsx should render itself & its children when
                 class="ml-1"
                 title="octocat approved these changes"
               >
-                <svg
-                  aria-label="octocat approved these changes"
-                  class="text-green-500"
-                  fill="currentColor"
-                  focusable="false"
-                  height="16"
-                  role="img"
-                  style="display: inline-block; user-select: none; vertical-align: text-bottom; overflow: visible;"
-                  viewBox="0 0 16 16"
-                  width="16"
+                <button
+                  class="rounded-full text-xs px-2 bg-gray-100 hover:bg-gray-200 dark:bg-gray-800 dark:hover:bg-gray-700"
+                  type="button"
                 >
-                  <path
-                    d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z"
-                  />
-                </svg>
+                  <svg
+                    aria-label="octocat approved these changes"
+                    class="mr-1 text-green-500"
+                    fill="currentColor"
+                    focusable="false"
+                    height="12"
+                    role="img"
+                    style="display: inline-block; user-select: none; vertical-align: text-bottom; overflow: visible;"
+                    viewBox="0 0 16 16"
+                    width="12"
+                  >
+                    <path
+                      d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z"
+                    />
+                  </svg>
+                  1
+                </button>
               </span>
               <span
                 class="ml-1"
                 title="gitify-app requested changes"
               >
-                <svg
-                  aria-label="gitify-app requested changes"
-                  class="text-red-500"
-                  fill="currentColor"
-                  focusable="false"
-                  height="16"
-                  role="img"
-                  style="display: inline-block; user-select: none; vertical-align: text-bottom; overflow: visible;"
-                  viewBox="0 0 16 16"
-                  width="16"
+                <button
+                  class="rounded-full text-xs px-2 bg-gray-100 hover:bg-gray-200 dark:bg-gray-800 dark:hover:bg-gray-700"
+                  type="button"
                 >
-                  <path
-                    d="M1 1.75C1 .784 1.784 0 2.75 0h7.586c.464 0 .909.184 1.237.513l2.914 2.914c.329.328.513.773.513 1.237v9.586A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25Zm1.75-.25a.25.25 0 0 0-.25.25v12.5c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V4.664a.25.25 0 0 0-.073-.177l-2.914-2.914a.25.25 0 0 0-.177-.073ZM8 3.25a.75.75 0 0 1 .75.75v1.5h1.5a.75.75 0 0 1 0 1.5h-1.5v1.5a.75.75 0 0 1-1.5 0V7h-1.5a.75.75 0 0 1 0-1.5h1.5V4A.75.75 0 0 1 8 3.25Zm-3 8a.75.75 0 0 1 .75-.75h4.5a.75.75 0 0 1 0 1.5h-4.5a.75.75 0 0 1-.75-.75Z"
-                  />
-                </svg>
+                  <svg
+                    aria-label="gitify-app requested changes"
+                    class="mr-1 text-red-500"
+                    fill="currentColor"
+                    focusable="false"
+                    height="12"
+                    role="img"
+                    style="display: inline-block; user-select: none; vertical-align: text-bottom; overflow: visible;"
+                    viewBox="0 0 16 16"
+                    width="12"
+                  >
+                    <path
+                      d="M1 1.75C1 .784 1.784 0 2.75 0h7.586c.464 0 .909.184 1.237.513l2.914 2.914c.329.328.513.773.513 1.237v9.586A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25Zm1.75-.25a.25.25 0 0 0-.25.25v12.5c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V4.664a.25.25 0 0 0-.073-.177l-2.914-2.914a.25.25 0 0 0-.177-.073ZM8 3.25a.75.75 0 0 1 .75.75v1.5h1.5a.75.75 0 0 1 0 1.5h-1.5v1.5a.75.75 0 0 1-1.5 0V7h-1.5a.75.75 0 0 1 0-1.5h1.5V4A.75.75 0 0 1 8 3.25Zm-3 8a.75.75 0 0 1 .75-.75h4.5a.75.75 0 0 1 0 1.5h-4.5a.75.75 0 0 1-.75-.75Z"
+                    />
+                  </svg>
+                  1
+                </button>
               </span>
             </span>
           </span>
@@ -2314,41 +2458,53 @@ exports[`components/NotificationRow.tsx should render itself & its children with
                   class="ml-1"
                   title="octocat approved these changes"
                 >
-                  <svg
-                    aria-label="octocat approved these changes"
-                    class="text-green-500"
-                    fill="currentColor"
-                    focusable="false"
-                    height="16"
-                    role="img"
-                    style="display: inline-block; user-select: none; vertical-align: text-bottom; overflow: visible;"
-                    viewBox="0 0 16 16"
-                    width="16"
+                  <button
+                    class="rounded-full text-xs px-2 bg-gray-100 hover:bg-gray-200 dark:bg-gray-800 dark:hover:bg-gray-700"
+                    type="button"
                   >
-                    <path
-                      d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z"
-                    />
-                  </svg>
+                    <svg
+                      aria-label="octocat approved these changes"
+                      class="mr-1 text-green-500"
+                      fill="currentColor"
+                      focusable="false"
+                      height="12"
+                      role="img"
+                      style="display: inline-block; user-select: none; vertical-align: text-bottom; overflow: visible;"
+                      viewBox="0 0 16 16"
+                      width="12"
+                    >
+                      <path
+                        d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z"
+                      />
+                    </svg>
+                    1
+                  </button>
                 </span>
                 <span
                   class="ml-1"
                   title="gitify-app requested changes"
                 >
-                  <svg
-                    aria-label="gitify-app requested changes"
-                    class="text-red-500"
-                    fill="currentColor"
-                    focusable="false"
-                    height="16"
-                    role="img"
-                    style="display: inline-block; user-select: none; vertical-align: text-bottom; overflow: visible;"
-                    viewBox="0 0 16 16"
-                    width="16"
+                  <button
+                    class="rounded-full text-xs px-2 bg-gray-100 hover:bg-gray-200 dark:bg-gray-800 dark:hover:bg-gray-700"
+                    type="button"
                   >
-                    <path
-                      d="M1 1.75C1 .784 1.784 0 2.75 0h7.586c.464 0 .909.184 1.237.513l2.914 2.914c.329.328.513.773.513 1.237v9.586A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25Zm1.75-.25a.25.25 0 0 0-.25.25v12.5c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V4.664a.25.25 0 0 0-.073-.177l-2.914-2.914a.25.25 0 0 0-.177-.073ZM8 3.25a.75.75 0 0 1 .75.75v1.5h1.5a.75.75 0 0 1 0 1.5h-1.5v1.5a.75.75 0 0 1-1.5 0V7h-1.5a.75.75 0 0 1 0-1.5h1.5V4A.75.75 0 0 1 8 3.25Zm-3 8a.75.75 0 0 1 .75-.75h4.5a.75.75 0 0 1 0 1.5h-4.5a.75.75 0 0 1-.75-.75Z"
-                    />
-                  </svg>
+                    <svg
+                      aria-label="gitify-app requested changes"
+                      class="mr-1 text-red-500"
+                      fill="currentColor"
+                      focusable="false"
+                      height="12"
+                      role="img"
+                      style="display: inline-block; user-select: none; vertical-align: text-bottom; overflow: visible;"
+                      viewBox="0 0 16 16"
+                      width="12"
+                    >
+                      <path
+                        d="M1 1.75C1 .784 1.784 0 2.75 0h7.586c.464 0 .909.184 1.237.513l2.914 2.914c.329.328.513.773.513 1.237v9.586A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25Zm1.75-.25a.25.25 0 0 0-.25.25v12.5c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V4.664a.25.25 0 0 0-.073-.177l-2.914-2.914a.25.25 0 0 0-.177-.073ZM8 3.25a.75.75 0 0 1 .75.75v1.5h1.5a.75.75 0 0 1 0 1.5h-1.5v1.5a.75.75 0 0 1-1.5 0V7h-1.5a.75.75 0 0 1 0-1.5h1.5V4A.75.75 0 0 1 8 3.25Zm-3 8a.75.75 0 0 1 .75-.75h4.5a.75.75 0 0 1 0 1.5h-4.5a.75.75 0 0 1-.75-.75Z"
+                      />
+                    </svg>
+                    1
+                  </button>
                 </span>
               </span>
             </span>
@@ -2503,41 +2659,53 @@ exports[`components/NotificationRow.tsx should render itself & its children with
                 class="ml-1"
                 title="octocat approved these changes"
               >
-                <svg
-                  aria-label="octocat approved these changes"
-                  class="text-green-500"
-                  fill="currentColor"
-                  focusable="false"
-                  height="16"
-                  role="img"
-                  style="display: inline-block; user-select: none; vertical-align: text-bottom; overflow: visible;"
-                  viewBox="0 0 16 16"
-                  width="16"
+                <button
+                  class="rounded-full text-xs px-2 bg-gray-100 hover:bg-gray-200 dark:bg-gray-800 dark:hover:bg-gray-700"
+                  type="button"
                 >
-                  <path
-                    d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z"
-                  />
-                </svg>
+                  <svg
+                    aria-label="octocat approved these changes"
+                    class="mr-1 text-green-500"
+                    fill="currentColor"
+                    focusable="false"
+                    height="12"
+                    role="img"
+                    style="display: inline-block; user-select: none; vertical-align: text-bottom; overflow: visible;"
+                    viewBox="0 0 16 16"
+                    width="12"
+                  >
+                    <path
+                      d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z"
+                    />
+                  </svg>
+                  1
+                </button>
               </span>
               <span
                 class="ml-1"
                 title="gitify-app requested changes"
               >
-                <svg
-                  aria-label="gitify-app requested changes"
-                  class="text-red-500"
-                  fill="currentColor"
-                  focusable="false"
-                  height="16"
-                  role="img"
-                  style="display: inline-block; user-select: none; vertical-align: text-bottom; overflow: visible;"
-                  viewBox="0 0 16 16"
-                  width="16"
+                <button
+                  class="rounded-full text-xs px-2 bg-gray-100 hover:bg-gray-200 dark:bg-gray-800 dark:hover:bg-gray-700"
+                  type="button"
                 >
-                  <path
-                    d="M1 1.75C1 .784 1.784 0 2.75 0h7.586c.464 0 .909.184 1.237.513l2.914 2.914c.329.328.513.773.513 1.237v9.586A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25Zm1.75-.25a.25.25 0 0 0-.25.25v12.5c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V4.664a.25.25 0 0 0-.073-.177l-2.914-2.914a.25.25 0 0 0-.177-.073ZM8 3.25a.75.75 0 0 1 .75.75v1.5h1.5a.75.75 0 0 1 0 1.5h-1.5v1.5a.75.75 0 0 1-1.5 0V7h-1.5a.75.75 0 0 1 0-1.5h1.5V4A.75.75 0 0 1 8 3.25Zm-3 8a.75.75 0 0 1 .75-.75h4.5a.75.75 0 0 1 0 1.5h-4.5a.75.75 0 0 1-.75-.75Z"
-                  />
-                </svg>
+                  <svg
+                    aria-label="gitify-app requested changes"
+                    class="mr-1 text-red-500"
+                    fill="currentColor"
+                    focusable="false"
+                    height="12"
+                    role="img"
+                    style="display: inline-block; user-select: none; vertical-align: text-bottom; overflow: visible;"
+                    viewBox="0 0 16 16"
+                    width="12"
+                  >
+                    <path
+                      d="M1 1.75C1 .784 1.784 0 2.75 0h7.586c.464 0 .909.184 1.237.513l2.914 2.914c.329.328.513.773.513 1.237v9.586A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25Zm1.75-.25a.25.25 0 0 0-.25.25v12.5c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V4.664a.25.25 0 0 0-.073-.177l-2.914-2.914a.25.25 0 0 0-.177-.073ZM8 3.25a.75.75 0 0 1 .75.75v1.5h1.5a.75.75 0 0 1 0 1.5h-1.5v1.5a.75.75 0 0 1-1.5 0V7h-1.5a.75.75 0 0 1 0-1.5h1.5V4A.75.75 0 0 1 8 3.25Zm-3 8a.75.75 0 0 1 .75-.75h4.5a.75.75 0 0 1 0 1.5h-4.5a.75.75 0 0 1-.75-.75Z"
+                    />
+                  </svg>
+                  1
+                </button>
               </span>
             </span>
           </span>
diff --git a/src/context/App.test.tsx b/src/context/App.test.tsx
index 25a6201d..71dc830d 100644
--- a/src/context/App.test.tsx
+++ b/src/context/App.test.tsx
@@ -1,6 +1,5 @@
 import { act, fireEvent, render, waitFor } from '@testing-library/react';
 import { useContext } from 'react';
-
 import { mockAuth, mockSettings } from '../__mocks__/state-mocks';
 import { useNotifications } from '../hooks/useNotifications';
 import type { AuthState, SettingsState } from '../types';
@@ -133,6 +132,7 @@ describe('context/App.tsx', () => {
       expect(markNotificationReadMock).toHaveBeenCalledTimes(1);
       expect(markNotificationReadMock).toHaveBeenCalledWith(
         {
+          accounts: [],
           enterpriseAccounts: [],
           token: null,
           user: null,
@@ -165,7 +165,7 @@ describe('context/App.tsx', () => {
 
       expect(markNotificationDoneMock).toHaveBeenCalledTimes(1);
       expect(markNotificationDoneMock).toHaveBeenCalledWith(
-        { enterpriseAccounts: [], token: null, user: null },
+        { accounts: [], enterpriseAccounts: [], token: null, user: null },
         mockSettings,
         '123-456',
         'github.com',
@@ -194,7 +194,7 @@ describe('context/App.tsx', () => {
 
       expect(unsubscribeNotificationMock).toHaveBeenCalledTimes(1);
       expect(unsubscribeNotificationMock).toHaveBeenCalledWith(
-        { enterpriseAccounts: [], token: null, user: null },
+        { accounts: [], enterpriseAccounts: [], token: null, user: null },
         mockSettings,
         '123-456',
         'github.com',
@@ -228,7 +228,7 @@ describe('context/App.tsx', () => {
 
       expect(markRepoNotificationsMock).toHaveBeenCalledTimes(1);
       expect(markRepoNotificationsMock).toHaveBeenCalledWith(
-        { enterpriseAccounts: [], token: null, user: null },
+        { accounts: [], enterpriseAccounts: [], token: null, user: null },
         mockSettings,
         'gitify-app/notifications-test',
         'github.com',
@@ -262,13 +262,14 @@ describe('context/App.tsx', () => {
 
       expect(markRepoNotificationsDoneMock).toHaveBeenCalledTimes(1);
       expect(markRepoNotificationsDoneMock).toHaveBeenCalledWith(
-        { enterpriseAccounts: [], token: null, user: null },
+        { accounts: [], enterpriseAccounts: [], token: null, user: null },
         mockSettings,
         'gitify-app/notifications-test',
         'github.com',
       );
     });
   });
+
   describe('authentication methods', () => {
     const apiRequestAuthMock = jest.spyOn(apiRequests, 'apiRequestAuth');
     const fetchNotificationsMock = jest.fn();
@@ -370,7 +371,7 @@ describe('context/App.tsx', () => {
       });
 
       expect(saveStateMock).toHaveBeenCalledWith({
-        auth: { enterpriseAccounts: [], token: null, user: null },
+        auth: { accounts: [], enterpriseAccounts: [], token: null, user: null },
         settings: {
           participating: true,
           playSound: true,
@@ -415,7 +416,7 @@ describe('context/App.tsx', () => {
       expect(setAutoLaunchMock).toHaveBeenCalledWith(true);
 
       expect(saveStateMock).toHaveBeenCalledWith({
-        auth: { enterpriseAccounts: [], token: null, user: null },
+        auth: { accounts: [], enterpriseAccounts: [], token: null, user: null },
         settings: {
           participating: false,
           playSound: true,
diff --git a/src/hooks/useNotifications.test.ts b/src/hooks/useNotifications.test.ts
index 83f332d2..7d55d332 100644
--- a/src/hooks/useNotifications.test.ts
+++ b/src/hooks/useNotifications.test.ts
@@ -2,8 +2,11 @@ import { act, renderHook, waitFor } from '@testing-library/react';
 import axios, { AxiosError } from 'axios';
 import nock from 'nock';
 
-import { mockAuth, mockSettings, mockUser } from '../__mocks__/state-mocks';
-import type { AuthState } from '../types';
+import {
+  mockAuth,
+  mockGitHubCloudAccount,
+  mockSettings,
+} from '../__mocks__/state-mocks';
 import { mockNotificationUser } from '../utils/api/__mocks__/response-mocks';
 import { Errors } from '../utils/constants';
 import { useNotifications } from './useNotifications';
@@ -46,10 +49,12 @@ describe('hooks/useNotifications.ts', () => {
           expect(result.current.status).toBe('success');
         });
 
-        expect(result.current.notifications[0].hostname).toBe('api.github.com');
+        expect(result.current.notifications[0].account.hostname).toBe(
+          'github.com',
+        );
         expect(result.current.notifications[0].notifications.length).toBe(2);
 
-        expect(result.current.notifications[1].hostname).toBe(
+        expect(result.current.notifications[1].account.hostname).toBe(
           'github.gitify.io',
         );
         expect(result.current.notifications[1].notifications.length).toBe(2);
@@ -100,144 +105,8 @@ describe('hooks/useNotifications.ts', () => {
       });
     });
 
-    describe('enterprise', () => {
-      it('should fetch notifications with success - enterprise only', async () => {
-        const auth: AuthState = {
-          ...mockAuth,
-          token: null,
-        };
-
-        const notifications = [
-          { id: 1, title: 'This is a notification.' },
-          { id: 2, title: 'This is another one.' },
-        ];
-
-        nock('https://github.gitify.io/api/v3/')
-          .get('/notifications?participating=false')
-          .reply(200, notifications);
-
-        const { result } = renderHook(() => useNotifications());
-
-        act(() => {
-          result.current.fetchNotifications(auth, {
-            ...mockSettings,
-            detailedNotifications: false,
-          });
-        });
-
-        await waitFor(() => {
-          expect(result.current.status).toBe('success');
-        });
-
-        expect(result.current.notifications[0].hostname).toBe(
-          'github.gitify.io',
-        );
-        expect(result.current.notifications[0].notifications.length).toBe(2);
-      });
-
-      it('should fetch notifications with failure - enterprise only', async () => {
-        const auth: AuthState = {
-          ...mockAuth,
-          token: null,
-        };
-
-        nock('https://github.gitify.io/api/v3/')
-          .get('/notifications?participating=false')
-          .replyWithError({
-            code: AxiosError.ERR_BAD_REQUEST,
-            response: {
-              status: 400,
-              data: {
-                message: 'Oops! Something went wrong.',
-              },
-            },
-          });
-
-        const { result } = renderHook(() => useNotifications());
-
-        act(() => {
-          result.current.fetchNotifications(auth, mockSettings);
-        });
-
-        await waitFor(() => {
-          expect(result.current.status).toBe('error');
-        });
-      });
-    });
-
-    describe('github.com', () => {
-      it('should fetch notifications with success - github.com only', async () => {
-        const auth: AuthState = {
-          ...mockAuth,
-          enterpriseAccounts: [],
-          user: mockUser,
-        };
-
-        const notifications = [
-          { id: 1, title: 'This is a notification.' },
-          { id: 2, title: 'This is another one.' },
-        ];
-
-        nock('https://api.github.com')
-          .get('/notifications?participating=false')
-          .reply(200, notifications);
-
-        const { result } = renderHook(() => useNotifications());
-
-        act(() => {
-          result.current.fetchNotifications(auth, {
-            ...mockSettings,
-            detailedNotifications: false,
-          });
-        });
-
-        await waitFor(() => {
-          expect(result.current.status).toBe('success');
-        });
-
-        expect(result.current.notifications[0].hostname).toBe('api.github.com');
-        expect(result.current.notifications[0].notifications.length).toBe(2);
-      });
-
-      it('should fetch notifications with failures - github.com only', async () => {
-        const auth: AuthState = {
-          ...mockAuth,
-          enterpriseAccounts: [],
-        };
-
-        nock('https://api.github.com/')
-          .get('/notifications?participating=false')
-          .replyWithError({
-            code: AxiosError.ERR_BAD_REQUEST,
-            response: {
-              status: 400,
-              data: {
-                message: 'Oops! Something went wrong.',
-              },
-            },
-          });
-
-        const { result } = renderHook(() => useNotifications());
-
-        act(() => {
-          result.current.fetchNotifications(auth, mockSettings);
-        });
-
-        await waitFor(() => {
-          expect(result.current.status).toBe('error');
-          expect(result.current.errorDetails).toBe(Errors.UNKNOWN);
-        });
-      });
-    });
-
     describe('with detailed notifications', () => {
       it('should fetch notifications with success', async () => {
-        const auth: AuthState = {
-          ...mockAuth,
-          enterpriseAccounts: [],
-          user: mockUser,
-        };
-
         const notifications = [
           {
             id: 1,
@@ -393,10 +262,15 @@ describe('hooks/useNotifications.ts', () => {
         const { result } = renderHook(() => useNotifications());
 
         act(() => {
-          result.current.fetchNotifications(auth, {
-            ...mockSettings,
-            detailedNotifications: true,
-          });
+          result.current.fetchNotifications(
+            {
+              accounts: [mockGitHubCloudAccount],
+            },
+            {
+              ...mockSettings,
+              detailedNotifications: true,
+            },
+          );
         });
 
         expect(result.current.status).toBe('loading');
@@ -405,7 +279,9 @@ describe('hooks/useNotifications.ts', () => {
           expect(result.current.status).toBe('success');
         });
 
-        expect(result.current.notifications[0].hostname).toBe('api.github.com');
+        expect(result.current.notifications[0].account.hostname).toBe(
+          'github.com',
+        );
         expect(result.current.notifications[0].notifications.length).toBe(6);
       });
     });
@@ -443,7 +319,7 @@ describe('hooks/useNotifications.ts', () => {
         result.current.removeNotificationFromState(
           mockSettings,
           result.current.notifications[0].notifications[0].id,
-          result.current.notifications[0].hostname,
+          result.current.notifications[0].account.hostname,
         );
       });
 
diff --git a/src/routes/LoginWithOAuthApp.test.tsx b/src/routes/LoginWithOAuthApp.test.tsx
index fe7a4b24..cd062d6a 100644
--- a/src/routes/LoginWithOAuthApp.test.tsx
+++ b/src/routes/LoginWithOAuthApp.test.tsx
@@ -1,7 +1,6 @@
 import { fireEvent, render, screen } from '@testing-library/react';
-import { ipcRenderer, shell } from 'electron';
+import { shell } from 'electron';
 import { MemoryRouter } from 'react-router-dom';
-import { mockEnterpriseAccounts } from '../__mocks__/state-mocks';
 import { AppContext } from '../context/App';
 import type { AuthState } from '../types';
 import { LoginWithOAuthApp, validate } from './LoginWithOAuthApp';
@@ -16,15 +15,12 @@ describe('routes/LoginWithOAuthApp.tsx', () => {
   const openExternalMock = jest.spyOn(shell, 'openExternal');
 
   const mockAuth: AuthState = {
-    enterpriseAccounts: [],
-    user: null,
+    accounts: [],
   };
 
   beforeEach(() => {
     openExternalMock.mockReset();
     mockNavigate.mockReset();
-
-    jest.spyOn(ipcRenderer, 'send');
   });
 
   it('renders correctly', () => {
@@ -111,35 +107,6 @@ describe('routes/LoginWithOAuthApp.tsx', () => {
     });
   });
 
-  it('should receive a logged-in enterprise account', () => {
-    const { rerender } = render(
-      <AppContext.Provider value={{ auth: mockAuth }}>
-        <MemoryRouter>
-          <LoginWithOAuthApp />
-        </MemoryRouter>
-      </AppContext.Provider>,
-    );
-
-    rerender(
-      <AppContext.Provider
-        value={{
-          auth: {
-            enterpriseAccounts: mockEnterpriseAccounts,
-            user: null,
-          },
-        }}
-      >
-        <MemoryRouter>
-          <LoginWithOAuthApp />
-        </MemoryRouter>
-      </AppContext.Provider>,
-    );
-
-    expect(ipcRenderer.send).toHaveBeenCalledTimes(1);
-    expect(ipcRenderer.send).toHaveBeenCalledWith('reopen-window');
-    expect(mockNavigate).toHaveBeenNthCalledWith(1, -1);
-  });
-
   it('should render the form with errors', () => {
     render(
       <AppContext.Provider value={{ auth: mockAuth }}>
diff --git a/src/routes/Settings.test.tsx b/src/routes/Settings.test.tsx
index 768443c6..2edc874b 100644
--- a/src/routes/Settings.test.tsx
+++ b/src/routes/Settings.test.tsx
@@ -1,7 +1,12 @@
 import { act, fireEvent, render, screen } from '@testing-library/react';
 import { ipcRenderer, shell } from 'electron';
 import { MemoryRouter } from 'react-router-dom';
-import { mockAuth, mockSettings } from '../__mocks__/state-mocks';
+import {
+  mockAuth,
+  mockOAuthAccount,
+  mockPersonalAccessTokenAccount,
+  mockSettings,
+} from '../__mocks__/state-mocks';
 import { mockPlatform } from '../__mocks__/utils';
 import { AppContext } from '../context/App';
 import { SettingsRoute } from './Settings';
@@ -38,7 +43,7 @@ describe('routes/Settings.tsx', () => {
       await act(async () => {
         render(
           <AppContext.Provider
-            value={{ settings: mockSettings, auth: mockAuth }}
+            value={{ auth: mockAuth, settings: mockSettings }}
           >
             <MemoryRouter>
               <SettingsRoute />
@@ -55,8 +60,8 @@ describe('routes/Settings.tsx', () => {
         render(
           <AppContext.Provider
             value={{
-              settings: mockSettings,
               auth: mockAuth,
+              settings: mockSettings,
             }}
           >
             <MemoryRouter>
@@ -76,8 +81,8 @@ describe('routes/Settings.tsx', () => {
         render(
           <AppContext.Provider
             value={{
-              settings: mockSettings,
               auth: mockAuth,
+              settings: mockSettings,
               updateSetting,
             }}
           >
@@ -99,8 +104,8 @@ describe('routes/Settings.tsx', () => {
         render(
           <AppContext.Provider
             value={{
-              settings: mockSettings,
               auth: mockAuth,
+              settings: mockSettings,
               updateSetting,
             }}
           >
@@ -127,8 +132,8 @@ describe('routes/Settings.tsx', () => {
         render(
           <AppContext.Provider
             value={{
-              settings: mockSettings,
               auth: mockAuth,
+              settings: mockSettings,
               updateSetting,
             }}
           >
@@ -154,8 +159,8 @@ describe('routes/Settings.tsx', () => {
         render(
           <AppContext.Provider
             value={{
-              settings: mockSettings,
               auth: mockAuth,
+              settings: mockSettings,
               updateSetting,
             }}
           >
@@ -179,8 +184,8 @@ describe('routes/Settings.tsx', () => {
         render(
           <AppContext.Provider
             value={{
-              settings: mockSettings,
               auth: mockAuth,
+              settings: mockSettings,
               updateSetting,
             }}
           >
@@ -214,12 +219,12 @@ describe('routes/Settings.tsx', () => {
         render(
           <AppContext.Provider
             value={{
+              auth: mockAuth,
               settings: {
                 ...mockSettings,
                 detailedNotifications: false,
                 showBots: true,
               },
-              auth: mockAuth,
               updateSetting,
             }}
           >
@@ -255,12 +260,12 @@ describe('routes/Settings.tsx', () => {
         render(
           <AppContext.Provider
             value={{
+              auth: mockAuth,
               settings: {
                 ...mockSettings,
                 detailedNotifications: true,
                 showBots: true,
               },
-              auth: mockAuth,
               updateSetting,
             }}
           >
@@ -296,8 +301,8 @@ describe('routes/Settings.tsx', () => {
         render(
           <AppContext.Provider
             value={{
-              settings: mockSettings,
               auth: mockAuth,
+              settings: mockSettings,
               updateSetting,
             }}
           >
@@ -321,8 +326,8 @@ describe('routes/Settings.tsx', () => {
         render(
           <AppContext.Provider
             value={{
-              settings: mockSettings,
               auth: mockAuth,
+              settings: mockSettings,
               updateSetting,
             }}
           >
@@ -351,8 +356,8 @@ describe('routes/Settings.tsx', () => {
         render(
           <AppContext.Provider
             value={{
-              settings: mockSettings,
               auth: mockAuth,
+              settings: mockSettings,
               updateSetting,
             }}
           >
@@ -382,8 +387,8 @@ describe('routes/Settings.tsx', () => {
         render(
           <AppContext.Provider
             value={{
-              settings: mockSettings,
               auth: mockAuth,
+              settings: mockSettings,
               updateSetting,
             }}
           >
@@ -407,8 +412,8 @@ describe('routes/Settings.tsx', () => {
         render(
           <AppContext.Provider
             value={{
-              settings: mockSettings,
               auth: mockAuth,
+              settings: mockSettings,
               updateSetting,
             }}
           >
@@ -432,8 +437,8 @@ describe('routes/Settings.tsx', () => {
         render(
           <AppContext.Provider
             value={{
-              settings: mockSettings,
               auth: mockAuth,
+              settings: mockSettings,
               updateSetting,
             }}
           >
@@ -459,8 +464,8 @@ describe('routes/Settings.tsx', () => {
         render(
           <AppContext.Provider
             value={{
-              settings: mockSettings,
               auth: mockAuth,
+              settings: mockSettings,
             }}
           >
             <MemoryRouter>
@@ -484,8 +489,8 @@ describe('routes/Settings.tsx', () => {
           render(
             <AppContext.Provider
               value={{
+                auth: { accounts: [mockOAuthAccount] },
                 settings: mockSettings,
-                auth: { ...mockAuth, token: null },
               }}
             >
               <MemoryRouter>
@@ -514,8 +519,8 @@ describe('routes/Settings.tsx', () => {
           render(
             <AppContext.Provider
               value={{
+                auth: { accounts: [mockPersonalAccessTokenAccount] },
                 settings: mockSettings,
-                auth: { ...mockAuth, token: '1234' },
               }}
             >
               <MemoryRouter>
@@ -537,11 +542,8 @@ describe('routes/Settings.tsx', () => {
           render(
             <AppContext.Provider
               value={{
+                auth: { accounts: [mockPersonalAccessTokenAccount] },
                 settings: mockSettings,
-                auth: {
-                  ...mockAuth,
-                  enterpriseAccounts: [],
-                },
               }}
             >
               <MemoryRouter>
@@ -564,8 +566,8 @@ describe('routes/Settings.tsx', () => {
           render(
             <AppContext.Provider
               value={{
+                auth: { accounts: [mockOAuthAccount] },
                 settings: mockSettings,
-                auth: mockAuth,
               }}
             >
               <MemoryRouter>
@@ -585,8 +587,8 @@ describe('routes/Settings.tsx', () => {
         render(
           <AppContext.Provider
             value={{
-              settings: mockSettings,
               auth: mockAuth,
+              settings: mockSettings,
               logout: logoutMock,
             }}
           >
@@ -611,7 +613,10 @@ describe('routes/Settings.tsx', () => {
       await act(async () => {
         render(
           <AppContext.Provider
-            value={{ settings: mockSettings, auth: mockAuth }}
+            value={{
+              auth: mockAuth,
+              settings: mockSettings,
+            }}
           >
             <MemoryRouter>
               <SettingsRoute />
diff --git a/src/routes/__snapshots__/Settings.test.tsx.snap b/src/routes/__snapshots__/Settings.test.tsx.snap
index c0eb68f2..e95556fe 100644
--- a/src/routes/__snapshots__/Settings.test.tsx.snap
+++ b/src/routes/__snapshots__/Settings.test.tsx.snap
@@ -549,7 +549,6 @@ exports[`routes/Settings.tsx General should render itself & its children 1`] = `
       </button>
       <button
         class="hover:text-gray-500 py-1 px-2 my-1 mx-2 focus:outline-none"
-        hidden=""
         title="Login with OAuth App"
         type="button"
       >
diff --git a/src/utils/api/__snapshots__/client.test.ts.snap b/src/utils/api/__snapshots__/client.test.ts.snap
index f793d09c..7f0db319 100644
--- a/src/utils/api/__snapshots__/client.test.ts.snap
+++ b/src/utils/api/__snapshots__/client.test.ts.snap
@@ -3,7 +3,7 @@
 exports[`utils/api/client.ts getAuthenticatedUser should fetch authenticated user - enterprise 1`] = `
 {
   "Accept": "application/json",
-  "Authorization": "token yourAuthToken",
+  "Authorization": "token token-123-456",
   "Cache-Control": "no-cache",
   "Content-Type": "application/json",
 }
@@ -12,7 +12,7 @@ exports[`utils/api/client.ts getAuthenticatedUser should fetch authenticated use
 exports[`utils/api/client.ts getAuthenticatedUser should fetch authenticated user - github 1`] = `
 {
   "Accept": "application/json",
-  "Authorization": "token yourAuthToken",
+  "Authorization": "token token-123-456",
   "Cache-Control": "no-cache",
   "Content-Type": "application/json",
 }
@@ -21,7 +21,7 @@ exports[`utils/api/client.ts getAuthenticatedUser should fetch authenticated use
 exports[`utils/api/client.ts headNotifications should fetch notifications head - enterprise 1`] = `
 {
   "Accept": "application/json",
-  "Authorization": "token yourAuthToken",
+  "Authorization": "token token-123-456",
   "Cache-Control": "no-cache",
   "Content-Type": "application/json",
 }
@@ -30,7 +30,7 @@ exports[`utils/api/client.ts headNotifications should fetch notifications head -
 exports[`utils/api/client.ts headNotifications should fetch notifications head - github 1`] = `
 {
   "Accept": "application/json",
-  "Authorization": "token yourAuthToken",
+  "Authorization": "token token-123-456",
   "Cache-Control": "no-cache",
   "Content-Type": "application/json",
 }
@@ -39,7 +39,7 @@ exports[`utils/api/client.ts headNotifications should fetch notifications head -
 exports[`utils/api/client.ts ignoreNotificationThreadSubscription should ignore notification thread subscription - github 1`] = `
 {
   "Accept": "application/json",
-  "Authorization": "token yourAuthToken",
+  "Authorization": "token token-123-456",
   "Cache-Control": "no-cache",
   "Content-Type": "application/json",
 }
@@ -48,25 +48,25 @@ exports[`utils/api/client.ts ignoreNotificationThreadSubscription should ignore
 exports[`utils/api/client.ts ignoreNotificationThreadSubscription should ignore notification thread subscription- enterprise 1`] = `
 {
   "Accept": "application/json",
-  "Authorization": "token yourAuthToken",
+  "Authorization": "token token-123-456",
   "Cache-Control": "no-cache",
   "Content-Type": "application/json",
 }
 `;
 
-exports[`utils/api/client.ts listNotificationsForAuthenticatedUser should list notifications for user - enterprise 1`] = `
+exports[`utils/api/client.ts listNotificationsForAuthenticatedUser should list notifications for user - github cloud 1`] = `
 {
   "Accept": "application/json",
-  "Authorization": "token yourAuthToken",
+  "Authorization": "token token-123-456",
   "Cache-Control": "no-cache",
   "Content-Type": "application/json",
 }
 `;
 
-exports[`utils/api/client.ts listNotificationsForAuthenticatedUser should list notifications for user - github 1`] = `
+exports[`utils/api/client.ts listNotificationsForAuthenticatedUser should list notifications for user - github enterprise server 1`] = `
 {
   "Accept": "application/json",
-  "Authorization": "token yourAuthToken",
+  "Authorization": "token 1234568790",
   "Cache-Control": "no-cache",
   "Content-Type": "application/json",
 }
@@ -75,7 +75,7 @@ exports[`utils/api/client.ts listNotificationsForAuthenticatedUser should list n
 exports[`utils/api/client.ts markNotificationThreadAsDone should mark notification thread as done - github 1`] = `
 {
   "Accept": "application/json",
-  "Authorization": "token yourAuthToken",
+  "Authorization": "token token-123-456",
   "Cache-Control": "no-cache",
   "Content-Type": "application/json",
 }
@@ -84,7 +84,7 @@ exports[`utils/api/client.ts markNotificationThreadAsDone should mark notificati
 exports[`utils/api/client.ts markNotificationThreadAsDone should mark notification thread as done- enterprise 1`] = `
 {
   "Accept": "application/json",
-  "Authorization": "token yourAuthToken",
+  "Authorization": "token token-123-456",
   "Cache-Control": "no-cache",
   "Content-Type": "application/json",
 }
@@ -93,7 +93,7 @@ exports[`utils/api/client.ts markNotificationThreadAsDone should mark notificati
 exports[`utils/api/client.ts markNotificationThreadAsRead should mark notification thread as read - github 1`] = `
 {
   "Accept": "application/json",
-  "Authorization": "token yourAuthToken",
+  "Authorization": "token token-123-456",
   "Cache-Control": "no-cache",
   "Content-Type": "application/json",
 }
@@ -102,7 +102,7 @@ exports[`utils/api/client.ts markNotificationThreadAsRead should mark notificati
 exports[`utils/api/client.ts markNotificationThreadAsRead should mark notification thread as read- enterprise 1`] = `
 {
   "Accept": "application/json",
-  "Authorization": "token yourAuthToken",
+  "Authorization": "token token-123-456",
   "Cache-Control": "no-cache",
   "Content-Type": "application/json",
 }
@@ -111,7 +111,7 @@ exports[`utils/api/client.ts markNotificationThreadAsRead should mark notificati
 exports[`utils/api/client.ts markRepositoryNotificationsAsRead should mark repository notifications as read - enterprise 1`] = `
 {
   "Accept": "application/json",
-  "Authorization": "token yourAuthToken",
+  "Authorization": "token token-123-456",
   "Cache-Control": "no-cache",
   "Content-Type": "application/json",
 }
@@ -120,7 +120,7 @@ exports[`utils/api/client.ts markRepositoryNotificationsAsRead should mark repos
 exports[`utils/api/client.ts markRepositoryNotificationsAsRead should mark repository notifications as read - github 1`] = `
 {
   "Accept": "application/json",
-  "Authorization": "token yourAuthToken",
+  "Authorization": "token token-123-456",
   "Cache-Control": "no-cache",
   "Content-Type": "application/json",
 }
diff --git a/src/utils/api/client.test.ts b/src/utils/api/client.test.ts
index ee7b28e4..526866f8 100644
--- a/src/utils/api/client.test.ts
+++ b/src/utils/api/client.test.ts
@@ -1,4 +1,9 @@
 import axios, { type AxiosPromise, type AxiosResponse } from 'axios';
+import {
+  mockGitHubCloudAccount,
+  mockGitHubEnterpriseServerAccount,
+  mockToken,
+} from '../../__mocks__/state-mocks';
 import type { SettingsState } from '../../types';
 import {
   getAuthenticatedUser,
@@ -16,7 +21,6 @@ jest.mock('axios');
 
 const mockGitHubHostname = 'github.com';
 const mockEnterpriseHostname = 'example.com';
-const mockToken = 'yourAuthToken';
 const mockThreadId = '1234';
 const mockRepoSlug = 'gitify-app/notifications-test';
 
@@ -82,10 +86,9 @@ describe('utils/api/client.ts', () => {
       participating: true,
     };
 
-    it('should list notifications for user - github', async () => {
+    it('should list notifications for user - github cloud', async () => {
       await listNotificationsForAuthenticatedUser(
-        mockGitHubHostname,
-        mockToken,
+        mockGitHubCloudAccount,
         mockSettings as SettingsState,
       );
 
@@ -98,15 +101,14 @@ describe('utils/api/client.ts', () => {
       expect(axios.defaults.headers.common).toMatchSnapshot();
     });
 
-    it('should list notifications for user - enterprise', async () => {
+    it('should list notifications for user - github enterprise server', async () => {
       await listNotificationsForAuthenticatedUser(
-        mockEnterpriseHostname,
-        mockToken,
+        mockGitHubEnterpriseServerAccount,
         mockSettings as SettingsState,
       );
 
       expect(axios).toHaveBeenCalledWith({
-        url: 'https://example.com/api/v3/notifications?participating=true',
+        url: 'https://github.gitify.io/api/v3/notifications?participating=true',
         method: 'GET',
         data: {},
       });
diff --git a/src/utils/auth/auth.test.ts b/src/utils/auth/auth.test.ts
deleted file mode 100644
index 2a5810ec..00000000
--- a/src/utils/auth/auth.test.ts
+++ /dev/null
@@ -1,213 +0,0 @@
-import remote from '@electron/remote';
-import type { AxiosPromise, AxiosResponse } from 'axios';
-import type { AuthState } from '../../types';
-import * as apiRequests from '../api/request';
-import * as auth from './utils';
-import { getNewOAuthAppURL, getNewTokenURL } from './utils';
-
-const browserWindow = new remote.BrowserWindow();
-
-describe('utils/auth.tsx', () => {
-  describe('authGitHub', () => {
-    const loadURLMock = jest.spyOn(browserWindow, 'loadURL');
-
-    beforeEach(() => {
-      loadURLMock.mockReset();
-    });
-
-    it('should call authGitHub - success', async () => {
-      // Casting to jest.Mock avoids Typescript errors, where the spy is expected to match all the original
-      // function's typing. I might fix all that if the return type of this was actually used, or if I was
-      // writing this test for a new feature. Since I'm just upgrading Jest, jest.Mock is a nice escape hatch
-      (
-        jest.spyOn(browserWindow.webContents, 'on') as jest.Mock
-      ).mockImplementation((event, callback): void => {
-        if (event === 'will-redirect') {
-          const event = new Event('will-redirect');
-          callback(event, 'http://github.com/?code=123-456');
-        }
-      });
-
-      const res = await auth.authGitHub();
-
-      expect(res.authCode).toBe('123-456');
-
-      expect(
-        browserWindow.webContents.session.clearStorageData,
-      ).toHaveBeenCalledTimes(1);
-
-      expect(loadURLMock).toHaveBeenCalledTimes(1);
-      expect(loadURLMock).toHaveBeenCalledWith(
-        'https://github.com/login/oauth/authorize?client_id=FAKE_CLIENT_ID_123&scope=read:user,notifications,repo',
-      );
-
-      expect(browserWindow.destroy).toHaveBeenCalledTimes(1);
-    });
-
-    it('should call authGitHub - failure', async () => {
-      (
-        jest.spyOn(browserWindow.webContents, 'on') as jest.Mock
-      ).mockImplementation((event, callback): void => {
-        if (event === 'will-redirect') {
-          const event = new Event('will-redirect');
-          callback(event, 'http://www.github.com/?error=Oops');
-        }
-      });
-
-      await expect(async () => await auth.authGitHub()).rejects.toEqual(
-        "Oops! Something went wrong and we couldn't log you in using GitHub. Please try again.",
-      );
-      expect(loadURLMock).toHaveBeenCalledTimes(1);
-    });
-  });
-
-  describe('getToken', () => {
-    const authCode = '123-456';
-    const apiRequestMock = jest.spyOn(apiRequests, 'apiRequest');
-
-    it('should get a token - success', async () => {
-      const requestPromise = new Promise((resolve) =>
-        resolve({ data: { access_token: 'this-is-a-token' } } as AxiosResponse),
-      ) as AxiosPromise;
-
-      apiRequestMock.mockResolvedValueOnce(requestPromise);
-
-      const res = await auth.getToken(authCode);
-
-      expect(apiRequests.apiRequest).toHaveBeenCalledWith(
-        'https://github.com/login/oauth/access_token',
-        'POST',
-        {
-          client_id: 'FAKE_CLIENT_ID_123',
-          client_secret: 'FAKE_CLIENT_SECRET_123',
-          code: '123-456',
-        },
-      );
-      expect(res.token).toBe('this-is-a-token');
-      expect(res.hostname).toBe('github.com');
-    });
-
-    it('should get a token - failure', async () => {
-      const message = 'Something went wrong.';
-
-      const requestPromise = new Promise((_, reject) =>
-        reject({ data: { message } } as AxiosResponse),
-      ) as AxiosPromise;
-
-      apiRequestMock.mockResolvedValueOnce(requestPromise);
-
-      const call = async () => await auth.getToken(authCode);
-
-      await expect(call).rejects.toEqual({ data: { message } });
-    });
-  });
-
-  describe('addAccount', () => {
-    const mockAuth: AuthState = {
-      token: null,
-      enterpriseAccounts: [],
-      user: null,
-    };
-
-    it('should add a github.com account', () => {
-      const result = auth.addAccount(mockAuth, '123-456', 'github.com');
-
-      expect(result).toEqual({ ...mockAuth, token: '123-456' });
-    });
-
-    it('should add an enterprise account', () => {
-      const result = auth.addAccount(mockAuth, '123-456', 'github.gitify.io');
-
-      expect(result).toEqual({
-        ...mockAuth,
-        enterpriseAccounts: [
-          { hostname: 'github.gitify.io', token: '123-456' },
-        ],
-      });
-    });
-  });
-
-  describe('getNewTokenURL', () => {
-    it('should generate new PAT url - github cloud', () => {
-      expect(
-        getNewTokenURL('github.com').startsWith(
-          'https://github.com/settings/tokens/new',
-        ),
-      ).toBeTruthy();
-    });
-
-    it('should generate new PAT url - github server', () => {
-      expect(
-        getNewTokenURL('github.gitify.io').startsWith(
-          'https://github.gitify.io/settings/tokens/new',
-        ),
-      ).toBeTruthy();
-    });
-  });
-
-  describe('getNewOAuthAppURL', () => {
-    it('should generate new oauth app url - github cloud', () => {
-      expect(
-        getNewOAuthAppURL('github.com').startsWith(
-          'https://github.com/settings/applications/new',
-        ),
-      ).toBeTruthy();
-    });
-
-    it('should generate new oauth app url - github server', () => {
-      expect(
-        getNewOAuthAppURL('github.gitify.io').startsWith(
-          'https://github.gitify.io/settings/applications/new',
-        ),
-      ).toBeTruthy();
-    });
-  });
-
-  describe('isValidHostname', () => {
-    it('should validate hostname - github cloud', () => {
-      expect(auth.isValidHostname('github.com')).toBeTruthy();
-    });
-
-    it('should validate hostname - github enterprise server', () => {
-      expect(auth.isValidHostname('github.gitify.io')).toBeTruthy();
-    });
-
-    it('should invalidate hostname - empty', () => {
-      expect(auth.isValidHostname('')).toBeFalsy();
-    });
-
-    it('should invalidate hostname - invalid', () => {
-      expect(auth.isValidHostname('github')).toBeFalsy();
-    });
-  });
-
-  describe('isValidClientId', () => {
-    it('should validate client id - valid', () => {
-      expect(auth.isValidClientId('1234567890_ASDFGHJKL')).toBeTruthy();
-    });
-
-    it('should validate client id - empty', () => {
-      expect(auth.isValidClientId('')).toBeFalsy();
-    });
-
-    it('should validate client id - invalid', () => {
-      expect(auth.isValidClientId('1234567890asdfg')).toBeFalsy();
-    });
-  });
-
-  describe('isValidToken', () => {
-    it('should validate token - valid', () => {
-      expect(
-        auth.isValidToken('1234567890_asdfghjklPOIUYTREWQ0987654321'),
-      ).toBeTruthy();
-    });
-
-    it('should validate token - empty', () => {
-      expect(auth.isValidToken('')).toBeFalsy();
-    });
-
-    it('should validate token - invalid', () => {
-      expect(auth.isValidToken('1234567890asdfg')).toBeFalsy();
-    });
-  });
-});
diff --git a/src/utils/auth/migration.test.ts b/src/utils/auth/migration.test.ts
new file mode 100644
index 00000000..9a65101b
--- /dev/null
+++ b/src/utils/auth/migration.test.ts
@@ -0,0 +1,154 @@
+import axios from 'axios';
+import nock from 'nock';
+import { mockGitifyUser, mockToken } from '../../__mocks__/state-mocks';
+import type { AuthState } from '../../types';
+import Constants from '../constants';
+import {
+  convertAccounts,
+  hasAccountsToMigrate,
+  migrateAuthenticatedAccounts,
+} from './migration';
+
+describe('utils/auth/migration.ts', () => {
+  beforeEach(() => {
+    // axios will default to using the XHR adapter which can't be intercepted
+    // by nock. So, configure axios to use the node adapter.
+    axios.defaults.adapter = 'http';
+  });
+
+  describe('migrateAuthenticatedAccounts', () => {
+    it('migrate and save legacy accounts', async () => {
+      jest.spyOn(localStorage.__proto__, 'getItem').mockReturnValueOnce(
+        JSON.stringify({
+          auth: {
+            token: mockToken,
+          },
+          settings: { theme: 'DARK' },
+        }),
+      );
+
+      nock('https://api.github.com').get('/user').reply(200, mockGitifyUser);
+
+      jest.spyOn(localStorage.__proto__, 'setItem');
+
+      await migrateAuthenticatedAccounts();
+
+      expect(localStorage.setItem).toHaveBeenCalledTimes(1);
+    });
+  });
+
+  describe('hasAccountsToMigrate', () => {
+    it('should not migrate if none exist', () => {
+      expect(hasAccountsToMigrate(null)).toBe(false);
+    });
+
+    it('should not migrate if empty', () => {
+      expect(hasAccountsToMigrate({} as AuthState)).toBe(false);
+    });
+
+    it('should migrate if token exist', () => {
+      expect(
+        hasAccountsToMigrate({
+          token: mockToken,
+        } as AuthState),
+      ).toBe(true);
+    });
+
+    it('should migrate if token exist', () => {
+      expect(
+        hasAccountsToMigrate({
+          enterpriseAccounts: [
+            { hostname: 'github.gitify.io', token: mockToken },
+          ],
+        } as AuthState),
+      ).toBe(true);
+    });
+
+    it('should not if there are already accounts', () => {
+      expect(
+        hasAccountsToMigrate({
+          token: mockToken,
+          enterpriseAccounts: [
+            { hostname: 'github.gitify.io', token: mockToken },
+          ],
+          accounts: [{ hostname: 'github.com', token: mockToken }],
+        } as AuthState),
+      ).toBe(false);
+    });
+  });
+
+  describe('convertAccounts', () => {
+    it('should convert accounts - personal access token only', async () => {
+      nock('https://api.github.com').get('/user').reply(200, mockGitifyUser);
+
+      const result = await convertAccounts({
+        token: mockToken,
+        user: mockGitifyUser,
+      } as AuthState);
+
+      expect(result).toEqual([
+        {
+          hostname: Constants.DEFAULT_AUTH_OPTIONS.hostname,
+          platform: 'GitHub Cloud',
+          method: 'Personal Access Token',
+          token: mockToken,
+          user: mockGitifyUser,
+        },
+      ]);
+    });
+
+    it('should convert accounts - oauth app only', async () => {
+      nock('https://github.gitify.io/api/v3')
+        .get('/user')
+        .reply(200, mockGitifyUser);
+
+      const result = await convertAccounts({
+        enterpriseAccounts: [
+          { hostname: 'github.gitify.io', token: mockToken },
+        ],
+      } as AuthState);
+
+      expect(result).toEqual([
+        {
+          hostname: 'github.gitify.io',
+          platform: 'GitHub Enterprise Server',
+          method: 'OAuth App',
+          token: mockToken,
+          user: mockGitifyUser,
+        },
+      ]);
+    });
+
+    it('should convert accounts - combination', async () => {
+      nock('https://api.github.com').get('/user').reply(200, mockGitifyUser);
+      nock('https://github.gitify.io/api/v3')
+        .get('/user')
+        .reply(200, mockGitifyUser);
+
+      const result = await convertAccounts({
+        token: mockToken,
+        user: mockGitifyUser,
+        enterpriseAccounts: [
+          { hostname: 'github.gitify.io', token: mockToken },
+        ],
+      } as AuthState);
+
+      expect(result).toEqual([
+        {
+          hostname: Constants.DEFAULT_AUTH_OPTIONS.hostname,
+          platform: 'GitHub Cloud',
+          method: 'Personal Access Token',
+          token: mockToken,
+          user: mockGitifyUser,
+        },
+        {
+          hostname: 'github.gitify.io',
+          platform: 'GitHub Enterprise Server',
+          method: 'OAuth App',
+          token: mockToken,
+          user: mockGitifyUser,
+        },
+      ]);
+    });
+  });
+});
diff --git a/src/utils/auth/utils.test.ts b/src/utils/auth/utils.test.ts
index 46edb356..6afe6d7e 100644
--- a/src/utils/auth/utils.test.ts
+++ b/src/utils/auth/utils.test.ts
@@ -103,26 +103,91 @@ describe('utils/auth/utils.ts', () => {
   });
 
   describe('addAccount', () => {
-    const mockAuth: AuthState = {
-      token: null,
-      enterpriseAccounts: [],
-      user: null,
-    };
+    let mockAuthState: AuthState;
 
-    it('should add a github.com account', () => {
-      const result = auth.addAccount(mockAuth, '123-456', 'github.com');
+    beforeEach(() => {
+      mockAuthState = {
+        accounts: [],
+      };
+    });
+
+    describe('should add GitHub Cloud account', () => {
+      it('should add personal access token account', async () => {
+        const result = await auth.addAccount(
+          mockAuthState,
+          'Personal Access Token',
+          '123-456',
+          'github.com',
+        );
+
+        expect(result.accounts).toEqual([
+          {
+            hostname: 'github.com',
+            method: 'Personal Access Token',
+            platform: 'GitHub Cloud',
+            token: '123-456',
+            user: undefined,
+          },
+        ]);
+      });
 
-      expect(result).toEqual({ ...mockAuth, token: '123-456' });
+      it('should add oauth app account', async () => {
+        const result = await auth.addAccount(
+          mockAuthState,
+          'OAuth App',
+          '123-456',
+          'github.com',
+        );
+
+        expect(result.accounts).toEqual([
+          {
+            hostname: 'github.com',
+            method: 'OAuth App',
+            platform: 'GitHub Cloud',
+            token: '123-456',
+            user: undefined,
+          },
+        ]);
+      });
     });
 
-    it('should add an enterprise account', () => {
-      const result = auth.addAccount(mockAuth, '123-456', 'github.gitify.io');
+    describe('should add GitHub Enterprise Server account', () => {
+      it('should add personal access token account', async () => {
+        const result = await auth.addAccount(
+          mockAuthState,
+          'Personal Access Token',
+          '123-456',
+          'github.gitify.io',
+        );
+
+        expect(result.accounts).toEqual([
+          {
+            hostname: 'github.gitify.io',
+            method: 'Personal Access Token',
+            platform: 'GitHub Enterprise Server',
+            token: '123-456',
+            user: undefined,
+          },
+        ]);
+      });
 
-      expect(result).toEqual({
-        ...mockAuth,
-        enterpriseAccounts: [
-          { hostname: 'github.gitify.io', token: '123-456' },
-        ],
+      it('should add oauth app account', async () => {
+        const result = await auth.addAccount(
+          mockAuthState,
+          'OAuth App',
+          '123-456',
+          'github.gitify.io',
+        );
+
+        expect(result.accounts).toEqual([
+          {
+            hostname: 'github.gitify.io',
+            method: 'OAuth App',
+            platform: 'GitHub Enterprise Server',
+            token: '123-456',
+            user: undefined,
+          },
+        ]);
       });
     });
   });
diff --git a/src/utils/helpers.test.ts b/src/utils/helpers.test.ts
index a55a4f25..ebabde89 100644
--- a/src/utils/helpers.test.ts
+++ b/src/utils/helpers.test.ts
@@ -1,5 +1,9 @@
 import type { AxiosPromise, AxiosResponse } from 'axios';
-import { mockAuth, mockUser } from '../__mocks__/state-mocks';
+import {
+  mockOAuthAccount,
+  mockPersonalAccessTokenAccount,
+} from '../__mocks__/state-mocks';
+
 import type { SubjectType } from '../typesGitHub';
 import {
   mockGraphQLResponse,
@@ -8,8 +12,10 @@ import {
 import * as apiRequests from './api/request';
 import {
   formatForDisplay,
+  formatNotificationUpdatedAt,
   generateGitHubWebUrl,
   generateNotificationReferrerId,
+  getPlatformFromHostname,
   isEnterpriseHost,
   isOAuthAppLoggedIn,
   isPersonalAccessTokenLoggedIn,
@@ -19,14 +25,24 @@ describe('utils/helpers.ts', () => {
   describe('isPersonalAccessTokenLoggedIn', () => {
     it('logged in', () => {
       expect(
-        isPersonalAccessTokenLoggedIn({ ...mockAuth, token: '1234' }),
+        isPersonalAccessTokenLoggedIn({
+          accounts: [mockPersonalAccessTokenAccount],
+        }),
       ).toBe(true);
     });
 
     it('logged out', () => {
-      expect(isPersonalAccessTokenLoggedIn({ ...mockAuth, token: null })).toBe(
-        false,
-      );
+      expect(
+        isPersonalAccessTokenLoggedIn({
+          accounts: [],
+        }),
+      ).toBe(false);
+
+      expect(
+        isPersonalAccessTokenLoggedIn({
+          accounts: [mockOAuthAccount],
+        }),
+      ).toBe(false);
     });
   });
 
@@ -34,19 +50,32 @@ describe('utils/helpers.ts', () => {
     it('logged in', () => {
       expect(
         isOAuthAppLoggedIn({
-          ...mockAuth,
-          enterpriseAccounts: [{ hostname: 'github.gitify.io', token: '1234' }],
+          accounts: [mockOAuthAccount],
         }),
       ).toBe(true);
     });
 
     it('logged out', () => {
+      expect(isOAuthAppLoggedIn({ accounts: [] })).toBe(false);
+
       expect(
-        isOAuthAppLoggedIn({ ...mockAuth, enterpriseAccounts: null }),
+        isOAuthAppLoggedIn({ accounts: [mockPersonalAccessTokenAccount] }),
       ).toBe(false);
+    });
+  });
 
-      expect(isOAuthAppLoggedIn({ ...mockAuth, enterpriseAccounts: [] })).toBe(
-        false,
+  describe('getPlatformFromHostname', () => {
+    it('should return GitHub Cloud', () => {
+      expect(getPlatformFromHostname('github.com')).toBe('GitHub Cloud');
+      expect(getPlatformFromHostname('api.github.com')).toBe('GitHub Cloud');
+    });
+
+    it('should return GitHub Enterprise Server', () => {
+      expect(getPlatformFromHostname('github.gitify.app')).toBe(
+        'GitHub Enterprise Server',
+      );
+      expect(getPlatformFromHostname('api.github.gitify.app')).toBe(
+        'GitHub Enterprise Server',
       );
     });
   });
@@ -65,10 +94,7 @@ describe('utils/helpers.ts', () => {
 
   describe('generateNotificationReferrerId', () => {
     it('should generate the notification_referrer_id', () => {
-      const referrerId = generateNotificationReferrerId(
-        mockSingleNotification.id,
-        mockUser.id,
-      );
+      const referrerId = generateNotificationReferrerId(mockSingleNotification);
       expect(referrerId).toBe(
         'MDE4Ok5vdGlmaWNhdGlvblRocmVhZDEzODY2MTA5NjoxMjM0NTY3ODk=',
       );
@@ -105,19 +131,16 @@ describe('utils/helpers.ts', () => {
 
       apiRequestAuthMock.mockResolvedValue(requestPromise);
 
-      const result = await generateGitHubWebUrl(
-        {
-          ...mockSingleNotification,
-          subject: subject,
-        },
-        mockAuth,
-      );
+      const result = await generateGitHubWebUrl({
+        ...mockSingleNotification,
+        subject: subject,
+      });
 
       expect(apiRequestAuthMock).toHaveBeenCalledTimes(1);
       expect(apiRequestAuthMock).toHaveBeenCalledWith(
         subject.latest_comment_url,
         'GET',
-        mockAuth.token,
+        mockPersonalAccessTokenAccount.token,
       );
       expect(result).toBe(`${mockHtmlUrl}?${mockNotificationReferrer}`);
     });
@@ -140,19 +163,16 @@ describe('utils/helpers.ts', () => {
 
       apiRequestAuthMock.mockResolvedValue(requestPromise);
 
-      const result = await generateGitHubWebUrl(
-        {
-          ...mockSingleNotification,
-          subject: subject,
-        },
-        mockAuth,
-      );
+      const result = await generateGitHubWebUrl({
+        ...mockSingleNotification,
+        subject: subject,
+      });
 
       expect(apiRequestAuthMock).toHaveBeenCalledTimes(1);
       expect(apiRequestAuthMock).toHaveBeenCalledWith(
         subject.url,
         'GET',
-        mockAuth.token,
+        mockPersonalAccessTokenAccount.token,
       );
       expect(result).toBe(`${mockHtmlUrl}?${mockNotificationReferrer}`);
     });
@@ -166,13 +186,10 @@ describe('utils/helpers.ts', () => {
           type: 'CheckSuite' as SubjectType,
         };
 
-        const result = await generateGitHubWebUrl(
-          {
-            ...mockSingleNotification,
-            subject: subject,
-          },
-          mockAuth,
-        );
+        const result = await generateGitHubWebUrl({
+          ...mockSingleNotification,
+          subject: subject,
+        });
 
         expect(apiRequestAuthMock).toHaveBeenCalledTimes(0);
         expect(result).toBe(
@@ -188,13 +205,10 @@ describe('utils/helpers.ts', () => {
           type: 'CheckSuite' as SubjectType,
         };
 
-        const result = await generateGitHubWebUrl(
-          {
-            ...mockSingleNotification,
-            subject: subject,
-          },
-          mockAuth,
-        );
+        const result = await generateGitHubWebUrl({
+          ...mockSingleNotification,
+          subject: subject,
+        });
 
         expect(apiRequestAuthMock).toHaveBeenCalledTimes(0);
         expect(result).toBe(
@@ -210,13 +224,10 @@ describe('utils/helpers.ts', () => {
           type: 'CheckSuite' as SubjectType,
         };
 
-        const result = await generateGitHubWebUrl(
-          {
-            ...mockSingleNotification,
-            subject: subject,
-          },
-          mockAuth,
-        );
+        const result = await generateGitHubWebUrl({
+          ...mockSingleNotification,
+          subject: subject,
+        });
 
         expect(apiRequestAuthMock).toHaveBeenCalledTimes(0);
         expect(result).toBe(
@@ -232,13 +243,10 @@ describe('utils/helpers.ts', () => {
           type: 'CheckSuite' as SubjectType,
         };
 
-        const result = await generateGitHubWebUrl(
-          {
-            ...mockSingleNotification,
-            subject: subject,
-          },
-          mockAuth,
-        );
+        const result = await generateGitHubWebUrl({
+          ...mockSingleNotification,
+          subject: subject,
+        });
 
         expect(apiRequestAuthMock).toHaveBeenCalledTimes(0);
         expect(result).toBe(
@@ -254,13 +262,10 @@ describe('utils/helpers.ts', () => {
           type: 'CheckSuite' as SubjectType,
         };
 
-        const result = await generateGitHubWebUrl(
-          {
-            ...mockSingleNotification,
-            subject: subject,
-          },
-          mockAuth,
-        );
+        const result = await generateGitHubWebUrl({
+          ...mockSingleNotification,
+          subject: subject,
+        });
 
         expect(apiRequestAuthMock).toHaveBeenCalledTimes(0);
         expect(result).toBe(
@@ -276,13 +281,10 @@ describe('utils/helpers.ts', () => {
           type: 'CheckSuite' as SubjectType,
         };
 
-        const result = await generateGitHubWebUrl(
-          {
-            ...mockSingleNotification,
-            subject: subject,
-          },
-          mockAuth,
-        );
+        const result = await generateGitHubWebUrl({
+          ...mockSingleNotification,
+          subject: subject,
+        });
 
         expect(apiRequestAuthMock).toHaveBeenCalledTimes(0);
         expect(result).toBe(
@@ -298,13 +300,10 @@ describe('utils/helpers.ts', () => {
           type: 'CheckSuite' as SubjectType,
         };
 
-        const result = await generateGitHubWebUrl(
-          {
-            ...mockSingleNotification,
-            subject: subject,
-          },
-          mockAuth,
-        );
+        const result = await generateGitHubWebUrl({
+          ...mockSingleNotification,
+          subject: subject,
+        });
 
         expect(apiRequestAuthMock).toHaveBeenCalledTimes(0);
         expect(result).toBe(
@@ -330,13 +329,10 @@ describe('utils/helpers.ts', () => {
 
         apiRequestAuthMock.mockResolvedValue(requestPromise);
 
-        const result = await generateGitHubWebUrl(
-          {
-            ...mockSingleNotification,
-            subject: subject,
-          },
-          mockAuth,
-        );
+        const result = await generateGitHubWebUrl({
+          ...mockSingleNotification,
+          subject: subject,
+        });
 
         expect(apiRequestAuthMock).toHaveBeenCalledTimes(1);
         expect(result).toBe(
@@ -362,13 +358,10 @@ describe('utils/helpers.ts', () => {
 
         apiRequestAuthMock.mockResolvedValue(requestPromise);
 
-        const result = await generateGitHubWebUrl(
-          {
-            ...mockSingleNotification,
-            subject: subject,
-          },
-          mockAuth,
-        );
+        const result = await generateGitHubWebUrl({
+          ...mockSingleNotification,
+          subject: subject,
+        });
 
         expect(apiRequestAuthMock).toHaveBeenCalledTimes(1);
         expect(result).toBe(
@@ -390,13 +383,10 @@ describe('utils/helpers.ts', () => {
 
         apiRequestAuthMock.mockResolvedValue(requestPromise);
 
-        const result = await generateGitHubWebUrl(
-          {
-            ...mockSingleNotification,
-            subject: subject,
-          },
-          mockAuth,
-        );
+        const result = await generateGitHubWebUrl({
+          ...mockSingleNotification,
+          subject: subject,
+        });
 
         expect(apiRequestAuthMock).toHaveBeenCalledTimes(1);
         expect(result).toBe(
@@ -414,13 +404,10 @@ describe('utils/helpers.ts', () => {
         type: 'RepositoryInvitation' as SubjectType,
       };
 
-      const result = await generateGitHubWebUrl(
-        {
-          ...mockSingleNotification,
-          subject: subject,
-        },
-        mockAuth,
-      );
+      const result = await generateGitHubWebUrl({
+        ...mockSingleNotification,
+        subject: subject,
+      });
 
       expect(apiRequestAuthMock).toHaveBeenCalledTimes(0);
       expect(result).toBe(
@@ -437,13 +424,10 @@ describe('utils/helpers.ts', () => {
           type: 'WorkflowRun' as SubjectType,
         };
 
-        const result = await generateGitHubWebUrl(
-          {
-            ...mockSingleNotification,
-            subject: subject,
-          },
-          mockAuth,
-        );
+        const result = await generateGitHubWebUrl({
+          ...mockSingleNotification,
+          subject: subject,
+        });
 
         expect(apiRequestAuthMock).toHaveBeenCalledTimes(0);
         expect(result).toBe(
@@ -460,13 +444,10 @@ describe('utils/helpers.ts', () => {
           type: 'WorkflowRun' as SubjectType,
         };
 
-        const result = await generateGitHubWebUrl(
-          {
-            ...mockSingleNotification,
-            subject: subject,
-          },
-          mockAuth,
-        );
+        const result = await generateGitHubWebUrl({
+          ...mockSingleNotification,
+          subject: subject,
+        });
 
         expect(apiRequestAuthMock).toHaveBeenCalledTimes(0);
         expect(result).toBe(
@@ -482,13 +463,10 @@ describe('utils/helpers.ts', () => {
           type: 'WorkflowRun' as SubjectType,
         };
 
-        const result = await generateGitHubWebUrl(
-          {
-            ...mockSingleNotification,
-            subject: subject,
-          },
-          mockAuth,
-        );
+        const result = await generateGitHubWebUrl({
+          ...mockSingleNotification,
+          subject: subject,
+        });
 
         expect(apiRequestAuthMock).toHaveBeenCalledTimes(0);
         expect(result).toBe(
@@ -505,13 +483,10 @@ describe('utils/helpers.ts', () => {
         type: 'Issue' as SubjectType,
       };
 
-      const result = await generateGitHubWebUrl(
-        {
-          ...mockSingleNotification,
-          subject: subject,
-        },
-        mockAuth,
-      );
+      const result = await generateGitHubWebUrl({
+        ...mockSingleNotification,
+        subject: subject,
+      });
 
       expect(apiRequestAuthMock).toHaveBeenCalledTimes(0);
       expect(result).toBe(
@@ -534,5 +509,47 @@ describe('utils/helpers.ts', () => {
         'Not Planned Issue',
       );
     });
+
+    describe('formatNotificationUpdatedAt', () => {
+      it('should use last_read_at if available', () => {
+        const notification = {
+          ...mockSingleNotification,
+          last_read_at: '2021-06-23T16:00:00Z',
+          updated_at: '2021-06-23T17:00:00Z',
+        };
+
+        expect(formatNotificationUpdatedAt(notification)).toContain('ago');
+      });
+
+      it('should use updated_at if last_read_at is null', () => {
+        const notification = {
+          ...mockSingleNotification,
+          last_read_at: null,
+          updated_at: '2021-06-23T17:00:00Z',
+        };
+
+        expect(formatNotificationUpdatedAt(notification)).toContain('ago');
+      });
+
+      it('should return empty if all dates are null', () => {
+        const notification = {
+          ...mockSingleNotification,
+          last_read_at: null,
+          updated_at: null,
+        };
+
+        expect(formatNotificationUpdatedAt(notification)).toBe('');
+      });
+
+      it('should return empty if unable to parse dates', () => {
+        const notification = {
+          ...mockSingleNotification,
+          last_read_at: 'not an iso date',
+          updated_at: 'not an iso date',
+        };
+
+        expect(formatNotificationUpdatedAt(notification)).toBe('');
+      });
+    });
   });
 });
diff --git a/src/utils/notifications.test.ts b/src/utils/notifications.test.ts
index d8c03630..ac22a3a6 100644
--- a/src/utils/notifications.test.ts
+++ b/src/utils/notifications.test.ts
@@ -8,10 +8,7 @@ import { partialMockNotification } from '../__mocks__/partial-mocks';
 import { mockAuth, mockSettings } from '../__mocks__/state-mocks';
 import { defaultSettings } from '../context/App';
 import type { SettingsState } from '../types';
-import {
-  mockGitHubNotifications,
-  mockSingleNotification,
-} from './api/__mocks__/response-mocks';
+import { mockGitHubNotifications } from './api/__mocks__/response-mocks';
 import * as helpers from './helpers';
 import * as notificationsHelpers from './notifications';
 import { filterNotifications } from './notifications';
@@ -98,6 +95,7 @@ describe('utils/notifications.ts', () => {
     jest.spyOn(notificationsHelpers, 'raiseNativeNotification');
     jest.spyOn(notificationsHelpers, 'raiseSoundNotification');
 
+    notificationsHelpers.triggerNativeNotifications([], [], settings, mockAuth);
     notificationsHelpers.triggerNativeNotifications([], [], settings, mockAuth);
 
     expect(notificationsHelpers.raiseNativeNotification).not.toHaveBeenCalled();
@@ -109,15 +107,14 @@ describe('utils/notifications.ts', () => {
 
     const nativeNotification: Notification =
       notificationsHelpers.raiseNativeNotification(
-        [mockSingleNotification],
+        [mockGitHubNotifications[0]],
         mockAuth,
       );
     nativeNotification.onclick(null);
 
     expect(helpers.openInBrowser).toHaveBeenCalledTimes(1);
     expect(helpers.openInBrowser).toHaveBeenLastCalledWith(
-      mockSingleNotification,
-      mockAuth,
+      mockGitHubNotifications[0],
     );
     expect(ipcRenderer.send).toHaveBeenCalledWith('hide-window');
   });
diff --git a/src/utils/remove-notification.test.ts b/src/utils/remove-notification.test.ts
index 28789ac7..d0075cdf 100644
--- a/src/utils/remove-notification.test.ts
+++ b/src/utils/remove-notification.test.ts
@@ -6,7 +6,7 @@ import { removeNotification } from './remove-notification';
 
 describe('utils/remove-notification.ts', () => {
   const notificationId = mockSingleNotification.id;
-  const hostname = mockSingleAccountNotifications[0].hostname;
+  const hostname = mockSingleAccountNotifications[0].account.hostname;
 
   it('should remove a notification if it exists', () => {
     expect(mockSingleAccountNotifications[0].notifications.length).toBe(1);
diff --git a/src/utils/remove-notifications.test.ts b/src/utils/remove-notifications.test.ts
index 181a245f..729b0285 100644
--- a/src/utils/remove-notifications.test.ts
+++ b/src/utils/remove-notifications.test.ts
@@ -7,7 +7,7 @@ import { removeNotifications } from './remove-notifications';
 
 describe('utils/remove-notifications.ts', () => {
   const repoSlug = mockSingleNotification.repository.full_name;
-  const hostname = mockSingleAccountNotifications[0].hostname;
+  const hostname = mockSingleAccountNotifications[0].account.hostname;
 
   it("should remove a repo's notifications - single", () => {
     expect(mockSingleAccountNotifications[0].notifications.length).toBe(1);
diff --git a/src/utils/storage.test.ts b/src/utils/storage.test.ts
index f4cf81cf..42ca9a87 100644
--- a/src/utils/storage.test.ts
+++ b/src/utils/storage.test.ts
@@ -1,16 +1,39 @@
 import { mockSettings } from '../__mocks__/state-mocks';
+import Constants from './constants';
 import { clearState, loadState, saveState } from './storage';
 
 describe('utils/storage.ts', () => {
   it('should load the state from localstorage - existing', () => {
     jest.spyOn(localStorage.__proto__, 'getItem').mockReturnValueOnce(
       JSON.stringify({
-        auth: { token: '123-456' },
+        auth: {
+          accounts: [
+            {
+              hostname: Constants.DEFAULT_AUTH_OPTIONS.hostname,
+              platform: 'GitHub Cloud',
+              method: 'Personal Access Token',
+              token: '123-456',
+              user: null,
+            },
+          ],
+        },
         settings: { theme: 'DARK' },
       }),
     );
     const result = loadState();
-    expect(result.auth.token).toBe('123-456');
+
+    expect(result.auth.accounts).toEqual([
+      {
+        hostname: Constants.DEFAULT_AUTH_OPTIONS.hostname,
+        platform: 'GitHub Cloud',
+        method: 'Personal Access Token',
+        token: '123-456',
+        user: null,
+      },
+    ]);
+    expect(result.auth.token).toBeUndefined();
+    expect(result.auth.enterpriseAccounts).toBeUndefined();
+    expect(result.auth.user).toBeUndefined();
     expect(result.settings.theme).toBe('DARK');
   });
 
@@ -20,6 +43,7 @@ describe('utils/storage.ts', () => {
       .mockReturnValueOnce(JSON.stringify({}));
     const result = loadState();
     expect(result.auth).toBeUndefined();
+    expect(result.auth).toBeUndefined();
     expect(result.settings).toBeUndefined();
   });
 
@@ -27,9 +51,15 @@ describe('utils/storage.ts', () => {
     jest.spyOn(localStorage.__proto__, 'setItem');
     saveState({
       auth: {
-        token: '123-456',
-        enterpriseAccounts: [],
-        user: null,
+        accounts: [
+          {
+            hostname: Constants.DEFAULT_AUTH_OPTIONS.hostname,
+            platform: 'GitHub Cloud',
+            method: 'Personal Access Token',
+            token: '123-456',
+            user: null,
+          },
+        ],
       },
       settings: mockSettings,
     });
diff --git a/src/utils/subject.test.ts b/src/utils/subject.test.ts
index df893078..43419582 100644
--- a/src/utils/subject.test.ts
+++ b/src/utils/subject.test.ts
@@ -5,7 +5,6 @@ import {
   partialMockNotification,
   partialMockUser,
 } from '../__mocks__/partial-mocks';
-import { mockAuth } from '../__mocks__/state-mocks';
 import type {
   Discussion,
   DiscussionAuthor,
@@ -44,10 +43,7 @@ describe('utils/subject.ts', () => {
           type: 'CheckSuite',
         });
 
-        const result = await getGitifySubjectDetails(
-          mockNotification,
-          mockAuth.token,
-        );
+        const result = await getGitifySubjectDetails(mockNotification);
 
         expect(result).toEqual({
           state: 'cancelled',
@@ -61,10 +57,7 @@ describe('utils/subject.ts', () => {
           type: 'CheckSuite',
         });
 
-        const result = await getGitifySubjectDetails(
-          mockNotification,
-          mockAuth.token,
-        );
+        const result = await getGitifySubjectDetails(mockNotification);
 
         expect(result).toEqual({
           state: 'failure',
@@ -78,10 +71,7 @@ describe('utils/subject.ts', () => {
           type: 'CheckSuite',
         });
 
-        const result = await getGitifySubjectDetails(
-          mockNotification,
-          mockAuth.token,
-        );
+        const result = await getGitifySubjectDetails(mockNotification);
 
         expect(result).toEqual({
           state: 'failure',
@@ -95,10 +85,7 @@ describe('utils/subject.ts', () => {
           type: 'CheckSuite',
         });
 
-        const result = await getGitifySubjectDetails(
-          mockNotification,
-          mockAuth.token,
-        );
+        const result = await getGitifySubjectDetails(mockNotification);
 
         expect(result).toEqual({
           state: 'skipped',
@@ -112,10 +99,7 @@ describe('utils/subject.ts', () => {
           type: 'CheckSuite',
         });
 
-        const result = await getGitifySubjectDetails(
-          mockNotification,
-          mockAuth.token,
-        );
+        const result = await getGitifySubjectDetails(mockNotification);
 
         expect(result).toEqual({
           state: 'success',
@@ -129,10 +113,7 @@ describe('utils/subject.ts', () => {
           type: 'CheckSuite',
         });
 
-        const result = await getGitifySubjectDetails(
-          mockNotification,
-          mockAuth.token,
-        );
+        const result = await getGitifySubjectDetails(mockNotification);
 
         expect(result).toBeNull();
       });
@@ -143,10 +124,7 @@ describe('utils/subject.ts', () => {
           type: 'CheckSuite',
         });
 
-        const result = await getGitifySubjectDetails(
-          mockNotification,
-          mockAuth.token,
-        );
+        const result = await getGitifySubjectDetails(mockNotification);
 
         expect(result).toBeNull();
       });
@@ -171,10 +149,7 @@ describe('utils/subject.ts', () => {
           .get('/repos/gitify-app/notifications-test/comments/141012658')
           .reply(200, { user: mockCommenter });
 
-        const result = await getGitifySubjectDetails(
-          mockNotification,
-          mockAuth.token,
-        );
+        const result = await getGitifySubjectDetails(mockNotification);
 
         expect(result).toEqual({
           state: null,
@@ -201,10 +176,7 @@ describe('utils/subject.ts', () => {
           )
           .reply(200, { author: mockAuthor });
 
-        const result = await getGitifySubjectDetails(
-          mockNotification,
-          mockAuth.token,
-        );
+        const result = await getGitifySubjectDetails(mockNotification);
 
         expect(result).toEqual({
           state: null,
@@ -243,10 +215,7 @@ describe('utils/subject.ts', () => {
             },
           });
 
-        const result = await getGitifySubjectDetails(
-          mockNotification,
-          mockAuth.token,
-        );
+        const result = await getGitifySubjectDetails(mockNotification);
 
         expect(result).toEqual({
           state: 'ANSWERED',
@@ -271,10 +240,7 @@ describe('utils/subject.ts', () => {
             },
           });
 
-        const result = await getGitifySubjectDetails(
-          mockNotification,
-          mockAuth.token,
-        );
+        const result = await getGitifySubjectDetails(mockNotification);
 
         expect(result).toEqual({
           state: 'DUPLICATE',
@@ -299,10 +265,7 @@ describe('utils/subject.ts', () => {
             },
           });
 
-        const result = await getGitifySubjectDetails(
-          mockNotification,
-          mockAuth.token,
-        );
+        const result = await getGitifySubjectDetails(mockNotification);
 
         expect(result).toEqual({
           state: 'OPEN',
@@ -327,10 +290,7 @@ describe('utils/subject.ts', () => {
             },
           });
 
-        const result = await getGitifySubjectDetails(
-          mockNotification,
-          mockAuth.token,
-        );
+        const result = await getGitifySubjectDetails(mockNotification);
 
         expect(result).toEqual({
           state: 'OUTDATED',
@@ -355,10 +315,7 @@ describe('utils/subject.ts', () => {
             },
           });
 
-        const result = await getGitifySubjectDetails(
-          mockNotification,
-          mockAuth.token,
-        );
+        const result = await getGitifySubjectDetails(mockNotification);
 
         expect(result).toEqual({
           state: 'REOPENED',
@@ -383,10 +340,7 @@ describe('utils/subject.ts', () => {
             },
           });
 
-        const result = await getGitifySubjectDetails(
-          mockNotification,
-          mockAuth.token,
-        );
+        const result = await getGitifySubjectDetails(mockNotification);
 
         expect(result).toEqual({
           state: 'RESOLVED',
@@ -422,10 +376,7 @@ describe('utils/subject.ts', () => {
           .get('/repos/gitify-app/notifications-test/issues/comments/302888448')
           .reply(200, { user: mockCommenter });
 
-        const result = await getGitifySubjectDetails(
-          mockNotification,
-          mockAuth.token,
-        );
+        const result = await getGitifySubjectDetails(mockNotification);
 
         expect(result).toEqual({
           state: 'open',
@@ -447,10 +398,7 @@ describe('utils/subject.ts', () => {
           .get('/repos/gitify-app/notifications-test/issues/comments/302888448')
           .reply(200, { user: mockCommenter });
 
-        const result = await getGitifySubjectDetails(
-          mockNotification,
-          mockAuth.token,
-        );
+        const result = await getGitifySubjectDetails(mockNotification);
 
         expect(result).toEqual({
           state: 'closed',
@@ -476,10 +424,7 @@ describe('utils/subject.ts', () => {
           .get('/repos/gitify-app/notifications-test/issues/comments/302888448')
           .reply(200, { user: mockCommenter });
 
-        const result = await getGitifySubjectDetails(
-          mockNotification,
-          mockAuth.token,
-        );
+        const result = await getGitifySubjectDetails(mockNotification);
 
         expect(result).toEqual({
           state: 'completed',
@@ -505,10 +450,7 @@ describe('utils/subject.ts', () => {
           .get('/repos/gitify-app/notifications-test/issues/comments/302888448')
           .reply(200, { user: mockCommenter });
 
-        const result = await getGitifySubjectDetails(
-          mockNotification,
-          mockAuth.token,
-        );
+        const result = await getGitifySubjectDetails(mockNotification);
 
         expect(result).toEqual({
           state: 'not_planned',
@@ -534,10 +476,7 @@ describe('utils/subject.ts', () => {
           .get('/repos/gitify-app/notifications-test/issues/comments/302888448')
           .reply(200, { user: mockCommenter });
 
-        const result = await getGitifySubjectDetails(
-          mockNotification,
-          mockAuth.token,
-        );
+        const result = await getGitifySubjectDetails(mockNotification);
 
         expect(result).toEqual({
           state: 'reopened',
@@ -562,10 +501,7 @@ describe('utils/subject.ts', () => {
             user: mockAuthor,
           });
 
-        const result = await getGitifySubjectDetails(
-          mockNotification,
-          mockAuth.token,
-        );
+        const result = await getGitifySubjectDetails(mockNotification);
 
         expect(result).toEqual({
           state: 'open',
@@ -610,10 +546,7 @@ describe('utils/subject.ts', () => {
           .get('/repos/gitify-app/notifications-test/pulls/1/reviews')
           .reply(200, []);
 
-        const result = await getGitifySubjectDetails(
-          mockNotification,
-          mockAuth.token,
-        );
+        const result = await getGitifySubjectDetails(mockNotification);
 
         expect(result).toEqual({
           state: 'closed',
@@ -645,10 +578,7 @@ describe('utils/subject.ts', () => {
           .get('/repos/gitify-app/notifications-test/pulls/1/reviews')
           .reply(200, []);
 
-        const result = await getGitifySubjectDetails(
-          mockNotification,
-          mockAuth.token,
-        );
+        const result = await getGitifySubjectDetails(mockNotification);
 
         expect(result).toEqual({
           state: 'draft',
@@ -680,10 +610,7 @@ describe('utils/subject.ts', () => {
           .get('/repos/gitify-app/notifications-test/pulls/1/reviews')
           .reply(200, []);
 
-        const result = await getGitifySubjectDetails(
-          mockNotification,
-          mockAuth.token,
-        );
+        const result = await getGitifySubjectDetails(mockNotification);
 
         expect(result).toEqual({
           state: 'merged',
@@ -715,10 +642,7 @@ describe('utils/subject.ts', () => {
           .get('/repos/gitify-app/notifications-test/pulls/1/reviews')
           .reply(200, []);
 
-        const result = await getGitifySubjectDetails(
-          mockNotification,
-          mockAuth.token,
-        );
+        const result = await getGitifySubjectDetails(mockNotification);
 
         expect(result).toEqual({
           state: 'open',
@@ -749,10 +673,7 @@ describe('utils/subject.ts', () => {
           .get('/repos/gitify-app/notifications-test/pulls/1/reviews')
           .reply(200, []);
 
-        const result = await getGitifySubjectDetails(
-          mockNotification,
-          mockAuth.token,
-        );
+        const result = await getGitifySubjectDetails(mockNotification);
 
         expect(result).toEqual({
           state: 'open',
@@ -782,10 +703,7 @@ describe('utils/subject.ts', () => {
           .get('/repos/gitify-app/notifications-test/pulls/1/reviews')
           .reply(200, []);
 
-        const result = await getGitifySubjectDetails(
-          mockNotification,
-          mockAuth.token,
-        );
+        const result = await getGitifySubjectDetails(mockNotification);
 
         expect(result).toEqual({
           state: 'open',
@@ -830,10 +748,7 @@ describe('utils/subject.ts', () => {
               },
             ]);
 
-          const result = await getLatestReviewForReviewers(
-            mockNotification,
-            mockAuth.token,
-          );
+          const result = await getLatestReviewForReviewers(mockNotification);
 
           expect(result).toEqual([
             { state: 'APPROVED', users: ['reviewer-3', 'reviewer-1'] },
@@ -846,10 +761,7 @@ describe('utils/subject.ts', () => {
             .get('/repos/gitify-app/notifications-test/pulls/1/reviews')
             .reply(200, []);
 
-          const result = await getLatestReviewForReviewers(
-            mockNotification,
-            mockAuth.token,
-          );
+          const result = await getLatestReviewForReviewers(mockNotification);
 
           expect(result).toBeNull();
         });
@@ -857,10 +769,7 @@ describe('utils/subject.ts', () => {
         it('returns null when not a PR notification', async () => {
           mockNotification.subject.type = 'Issue';
 
-          const result = await getLatestReviewForReviewers(
-            mockNotification,
-            mockAuth.token,
-          );
+          const result = await getLatestReviewForReviewers(mockNotification);
 
           expect(result).toBeNull();
         });
@@ -881,10 +790,7 @@ describe('utils/subject.ts', () => {
           .get('/repos/gitify-app/notifications-test/releases/1')
           .reply(200, { author: mockAuthor });
 
-        const result = await getGitifySubjectDetails(
-          mockNotification,
-          mockAuth.token,
-        );
+        const result = await getGitifySubjectDetails(mockNotification);
 
         expect(result).toEqual({
           state: null,
@@ -905,10 +811,7 @@ describe('utils/subject.ts', () => {
           type: 'WorkflowRun',
         });
 
-        const result = await getGitifySubjectDetails(
-          mockNotification,
-          mockAuth.token,
-        );
+        const result = await getGitifySubjectDetails(mockNotification);
 
         expect(result).toEqual({
           state: 'waiting',
@@ -923,10 +826,7 @@ describe('utils/subject.ts', () => {
           type: 'WorkflowRun',
         });
 
-        const result = await getGitifySubjectDetails(
-          mockNotification,
-          mockAuth.token,
-        );
+        const result = await getGitifySubjectDetails(mockNotification);
 
         expect(result).toBeNull();
       });
@@ -937,10 +837,7 @@ describe('utils/subject.ts', () => {
           type: 'WorkflowRun',
         });
 
-        const result = await getGitifySubjectDetails(
-          mockNotification,
-          mockAuth.token,
-        );
+        const result = await getGitifySubjectDetails(mockNotification);
 
         expect(result).toBeNull();
       });
@@ -954,10 +851,7 @@ describe('utils/subject.ts', () => {
           type: 'RepositoryInvitation',
         });
 
-        const result = await getGitifySubjectDetails(
-          mockNotification,
-          mockAuth.token,
-        );
+        const result = await getGitifySubjectDetails(mockNotification);
 
         expect(result).toBeNull();
       });
@@ -980,7 +874,7 @@ describe('utils/subject.ts', () => {
           .get('/repos/gitify-app/notifications-test/issues/1')
           .replyWithError(mockError);
 
-        await getGitifySubjectDetails(mockNotification, mockAuth.token);
+        await getGitifySubjectDetails(mockNotification);
 
         expect(consoleErrorSpy).toHaveBeenCalledWith(
           'Error occurred while fetching details for Issue notification: This issue will throw an error',