Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,6 @@
"@octokit/openapi-types": "27.0.0",
"@octokit/plugin-paginate-rest": "14.0.0",
"@octokit/plugin-rest-endpoint-methods": "17.0.0",
"@octokit/plugin-retry": "8.1.0",
"@octokit/request": "10.0.11",
"@octokit/request-error": "7.1.0",
"@octokit/types": "16.0.0",
Expand All @@ -97,6 +96,7 @@
"@primer/react": "38.29.0",
"@swc-contrib/plugin-graphql-codegen-client-preset": "0.26.1",
"@tailwindcss/vite": "4.3.1",
"@tanstack/react-query": "5.90.21",
"@testing-library/jest-dom": "6.9.1",
"@testing-library/react": "16.3.2",
"@testing-library/user-event": "14.6.1",
Expand Down
39 changes: 18 additions & 21 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 15 additions & 7 deletions src/main/handlers/tray.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,6 @@ vi.mock('electron', () => ({
ipcMain: {
on: (...args: unknown[]) => onMock(...args),
} satisfies Pick<Electron.IpcMain, 'on'>,
net: {
isOnline: vi.fn().mockReturnValue(true),
} satisfies Pick<Electron.Net, 'isOnline'>,
}));

describe('main/handlers/tray.ts', () => {
Expand Down Expand Up @@ -53,7 +50,7 @@ describe('main/handlers/tray.ts', () => {
const updateColorHandler = onMock.mock.calls.find(
(call: unknown[]) => call[0] === EVENTS.UPDATE_ICON_COLOR,
)?.[1];
updateColorHandler?.({}, 5);
updateColorHandler?.({}, { notificationsCount: 5, isOnline: true });

expect(menubar.tray.setImage).not.toHaveBeenCalled();
});
Expand All @@ -64,7 +61,7 @@ describe('main/handlers/tray.ts', () => {
const updateColorHandler = onMock.mock.calls.find(
(call: unknown[]) => call[0] === EVENTS.UPDATE_ICON_COLOR,
)?.[1];
updateColorHandler?.({}, 0);
updateColorHandler?.({}, { notificationsCount: 0, isOnline: true });

expect(menubar.tray.setImage).toHaveBeenCalledWith(TrayIcons.idle);
});
Expand All @@ -75,18 +72,29 @@ describe('main/handlers/tray.ts', () => {
const updateColorHandler = onMock.mock.calls.find(
(call: unknown[]) => call[0] === EVENTS.UPDATE_ICON_COLOR,
)?.[1];
updateColorHandler?.({}, 3);
updateColorHandler?.({}, { notificationsCount: 3, isOnline: true });

expect(menubar.tray.setImage).toHaveBeenCalledWith(TrayIcons.active);
});

it('sets offline icon when offline', () => {
registerTrayHandlers(menubar);

const updateColorHandler = onMock.mock.calls.find(
(call: unknown[]) => call[0] === EVENTS.UPDATE_ICON_COLOR,
)?.[1];
updateColorHandler?.({}, { notificationsCount: 0, isOnline: false });

expect(menubar.tray.setImage).toHaveBeenCalledWith(TrayIcons.offline);
});

it('sets error icon when notifications count is negative', () => {
registerTrayHandlers(menubar);

const updateColorHandler = onMock.mock.calls.find(
(call: unknown[]) => call[0] === EVENTS.UPDATE_ICON_COLOR,
)?.[1];
updateColorHandler?.({}, -1);
updateColorHandler?.({}, { notificationsCount: -1, isOnline: true });

expect(menubar.tray.setImage).toHaveBeenCalledWith(TrayIcons.error);
});
Expand Down
8 changes: 3 additions & 5 deletions src/main/handlers/tray.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
import { net } from 'electron';
import type { Menubar } from 'electron-menubar';

import { EVENTS } from '../../shared/events';
import { EVENTS, type ITrayColorUpdate } from '../../shared/events';

import { onMainEvent } from '../events';
import { TrayIcons } from '../icons';

let shouldUseAlternateIdleIcon = false;
let shouldUseUnreadActiveIcon = true;

// TODO: Refactor to use ITrayColorUpdate type
function setIdleIcon(mb: Menubar): void {
if (shouldUseAlternateIdleIcon) {
mb.tray.setImage(TrayIcons.idleAlternate);
Expand Down Expand Up @@ -57,9 +55,9 @@ export function registerTrayHandlers(mb: Menubar): void {
/**
* Update the tray icon based on the current notification count.
*/
onMainEvent(EVENTS.UPDATE_ICON_COLOR, (_, notificationsCount: number) => {
onMainEvent(EVENTS.UPDATE_ICON_COLOR, (_, { notificationsCount, isOnline }: ITrayColorUpdate) => {
if (!mb.tray.isDestroyed()) {
if (!net.isOnline()) {
if (!isOnline) {
setOfflineIcon(mb);
return;
}
Expand Down
7 changes: 5 additions & 2 deletions src/preload/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ class MockNotification {
(global as unknown as { Notification: unknown }).Notification = MockNotification;

interface TestApi {
tray: { updateColor: (n?: number) => void };
tray: { updateColor: (n?: number, isOnline?: boolean) => void };
openExternalLink: (u: string, f: boolean) => void;
app: { version: () => Promise<string>; show?: () => void; hide?: () => void };
raiseNativeNotification: (t: string, b: string, u?: string) => unknown;
Expand Down Expand Up @@ -93,7 +93,10 @@ describe('preload/index', () => {

api.tray.updateColor(-1);

expect(sendMainEventMock).toHaveBeenNthCalledWith(1, EVENTS.UPDATE_ICON_COLOR, -1);
expect(sendMainEventMock).toHaveBeenNthCalledWith(1, EVENTS.UPDATE_ICON_COLOR, {
isOnline: true,
notificationsCount: -1,
});
});

it('openExternalLink sends event with payload', async () => {
Expand Down
5 changes: 3 additions & 2 deletions src/preload/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,10 @@ export const api = {
* Pass a negative number to set the error state color.
*
* @param notificationsCount - Number of unread notifications.
* @param isOnline - Whether the application is currently online.
*/
updateColor: (notificationsCount = 0) => {
sendMainEvent(EVENTS.UPDATE_ICON_COLOR, notificationsCount);
updateColor: (notificationsCount = 0, isOnline = true) => {
sendMainEvent(EVENTS.UPDATE_ICON_COLOR, { notificationsCount, isOnline });
},

/**
Expand Down
35 changes: 26 additions & 9 deletions src/renderer/App.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { useEffect } from 'react';
import { Navigate, Route, HashRouter as Router, Routes, useLocation } from 'react-router-dom';

import { BaseStyles, ThemeProvider } from '@primer/react';

import { AppProvider } from './context/App';
import { useAppContext } from './hooks/useAppContext';
import { QueryClientProvider } from '@tanstack/react-query';

import { AccountsRoute } from './routes/Accounts';
import { AccountScopesRoute } from './routes/AccountScopes';
import { FiltersRoute } from './routes/Filters';
Expand All @@ -14,25 +15,41 @@ import { GitHubLoginWithPersonalAccessTokenRoute } from './routes/github/LoginWi
import { LoginRoute } from './routes/Login';
import { NotificationsRoute } from './routes/Notifications';
import { SettingsRoute } from './routes/Settings';
import { useAccountsStore } from './stores';
import { initializeStoreSubscriptions } from './stores/subscriptions';

import { GlobalEffects } from './components/GlobalEffects';
import { GlobalShortcuts } from './components/GlobalShortcuts';

import './App.css';
import { AppLayout } from './components/layout/AppLayout';

import { queryClient } from './utils/api/queryClient';
import { migrateLegacyStoreToZustand } from './utils/core/storage';

// Run migration from legacy local storage to Zustand stores
migrateLegacyStoreToZustand();

function RequireAuth({ children }: { children: React.ReactNode }) {
const location = useLocation();

const { isLoggedIn } = useAppContext();
const isLoggedIn = useAccountsStore((s) => s.isLoggedIn());

return isLoggedIn ? children : <Navigate replace state={{ from: location }} to="/login" />;
}

export const App = () => {
// Initialize store subscriptions with proper cleanup
useEffect(() => {
const cleanup = initializeStoreSubscriptions();
return cleanup;
}, []);

return (
<ThemeProvider>
<BaseStyles>
<AppProvider>
<QueryClientProvider client={queryClient}>
<ThemeProvider>
<BaseStyles>
<GlobalEffects />
<Router>
<GlobalShortcuts />
<AppLayout>
Expand Down Expand Up @@ -94,8 +111,8 @@ export const App = () => {
</Routes>
</AppLayout>
</Router>
</AppProvider>
</BaseStyles>
</ThemeProvider>
</BaseStyles>
</ThemeProvider>
</QueryClientProvider>
);
};
12 changes: 0 additions & 12 deletions src/renderer/__helpers__/__mocks__/@octokit/core.ts

This file was deleted.

This file was deleted.

This file was deleted.

2 changes: 0 additions & 2 deletions src/renderer/__helpers__/__mocks__/@octokit/plugin-retry.ts

This file was deleted.

Loading