Skip to content
Merged
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
4 changes: 4 additions & 0 deletions src/components/NotificationRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,12 @@ import { NotificationHeader } from './notification/NotificationHeader';

interface INotificationRow {
notification: Notification;
isRead?: boolean;
}

export const NotificationRow: FC<INotificationRow> = ({
notification,
isRead = false,
}: INotificationRow) => {
const {
settings,
Expand Down Expand Up @@ -56,6 +58,7 @@ export const NotificationRow: FC<INotificationRow> = ({
removeNotificationFromState,
settings,
]);

const unsubscribeFromThread = (event: MouseEvent<HTMLElement>) => {
// Don't trigger onClick of parent element.
event.stopPropagation();
Expand Down Expand Up @@ -88,6 +91,7 @@ export const NotificationRow: FC<INotificationRow> = ({
animateExit &&
'translate-x-full opacity-0 transition duration-[350ms] ease-in-out',
showAsRead && Opacity.READ,
isRead && Opacity.READ,
)}
>
<div
Expand Down
10 changes: 7 additions & 3 deletions src/components/RepositoryNotifications.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { act, fireEvent, render, screen } from '@testing-library/react';
import { mockGitHubCloudAccount } from '../__mocks__/state-mocks';
import { mockGitHubCloudAccount, mockSettings } from '../__mocks__/state-mocks';
import { AppContext } from '../context/App';
import type { Link } from '../types';
import {
Expand Down Expand Up @@ -57,7 +57,9 @@ describe('components/Repository.tsx', () => {

it('should mark a repo as read', () => {
render(
<AppContext.Provider value={{ markRepoNotificationsRead }}>
<AppContext.Provider
value={{ settings: { ...mockSettings }, markRepoNotificationsRead }}
>
<RepositoryNotifications {...props} />
</AppContext.Provider>,
);
Expand All @@ -71,7 +73,9 @@ describe('components/Repository.tsx', () => {

it('should mark a repo as done', () => {
render(
<AppContext.Provider value={{ markRepoNotificationsDone }}>
<AppContext.Provider
value={{ settings: { ...mockSettings }, markRepoNotificationsDone }}
>
<RepositoryNotifications {...props} />
</AppContext.Provider>,
);
Expand Down
51 changes: 28 additions & 23 deletions src/components/RepositoryNotifications.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,7 @@ import {
MarkGithubIcon,
ReadIcon,
} from '@primer/octicons-react';
import {
type FC,
type MouseEvent,
useCallback,
useContext,
useState,
} from 'react';
import { type FC, type MouseEvent, useContext, useState } from 'react';
import { AppContext } from '../context/App';
import { Opacity, Size } from '../types';
import type { Notification } from '../typesGitHub';
Expand All @@ -31,22 +25,15 @@ export const RepositoryNotifications: FC<IRepositoryNotifications> = ({
repoName,
repoNotifications,
}) => {
const { markRepoNotificationsRead, markRepoNotificationsDone } =
const { settings, markRepoNotificationsRead, markRepoNotificationsDone } =
useContext(AppContext);

const markRepoAsRead = useCallback(() => {
markRepoNotificationsRead(repoNotifications[0]);
}, [repoNotifications, markRepoNotificationsRead]);

const markRepoAsDone = useCallback(() => {
markRepoNotificationsDone(repoNotifications[0]);
}, [repoNotifications, markRepoNotificationsDone]);

const avatarUrl = repoNotifications[0].repository.owner.avatar_url;

const [animateExit, setAnimateExit] = useState(false);
const [showAsRead, setShowAsRead] = useState(false);
const [showRepositoryNotifications, setShowRepositoryNotifications] =
useState(true);

const avatarUrl = repoNotifications[0].repository.owner.avatar_url;

const toggleRepositoryNotifications = () => {
setShowRepositoryNotifications(!showRepositoryNotifications);
};
Expand All @@ -68,7 +55,9 @@ export const RepositoryNotifications: FC<IRepositoryNotifications> = ({
<div
className={cn(
'flex flex-1 gap-4 items-center truncate text-sm font-medium',
Opacity.MEDIUM,
animateExit &&
'translate-x-full opacity-0 transition duration-[350ms] ease-in-out',
showAsRead ? Opacity.READ : Opacity.MEDIUM,
)}
>
<AvatarIcon
Expand All @@ -94,13 +83,25 @@ export const RepositoryNotifications: FC<IRepositoryNotifications> = ({
title="Mark Repository as Done"
icon={CheckIcon}
size={Size.MEDIUM}
onClick={markRepoAsDone}
onClick={(event: MouseEvent<HTMLElement>) => {
// Don't trigger onClick of parent element.
event.stopPropagation();
setAnimateExit(!settings.delayNotificationState);
setShowAsRead(settings.delayNotificationState);
markRepoNotificationsDone(repoNotifications[0]);
}}
/>
<InteractionButton
title="Mark Repository as Read"
icon={ReadIcon}
size={Size.SMALL}
onClick={markRepoAsRead}
onClick={(event: MouseEvent<HTMLElement>) => {
// Don't trigger onClick of parent element.
event.stopPropagation();
setAnimateExit(!settings.delayNotificationState);
setShowAsRead(settings.delayNotificationState);
markRepoNotificationsRead(repoNotifications[0]);
}}
/>
<InteractionButton
title={toggleRepositoryNotificationsLabel}
Expand All @@ -113,7 +114,11 @@ export const RepositoryNotifications: FC<IRepositoryNotifications> = ({

{showRepositoryNotifications &&
repoNotifications.map((notification) => (
<NotificationRow key={notification.id} notification={notification} />
<NotificationRow
key={notification.id}
notification={notification}
isRead={showAsRead}
/>
))}
</>
);
Expand Down
5 changes: 4 additions & 1 deletion src/hooks/useNotifications.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ export const useNotifications = (): NotificationsState => {
);

const markRepoNotificationsRead = useCallback(
async (_state: GitifyState, notification: Notification) => {
async (state: GitifyState, notification: Notification) => {
setStatus('loading');

const repoSlug = notification.repository.full_name;
Expand All @@ -166,7 +166,9 @@ export const useNotifications = (): NotificationsState => {
hostname,
notification.account.token,
);

const updatedNotifications = removeNotifications(
state.settings,
notification,
notifications,
);
Expand Down Expand Up @@ -209,6 +211,7 @@ export const useNotifications = (): NotificationsState => {
}

const updatedNotifications = removeNotifications(
state.settings,
notification,
notifications,
);
Expand Down
15 changes: 15 additions & 0 deletions src/utils/remove-notifications.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
mockAccountNotifications,
mockSingleAccountNotifications,
} from '../__mocks__/notifications-mocks';
import { mockSettings } from '../__mocks__/state-mocks';
import { mockSingleNotification } from './api/__mocks__/response-mocks';
import { removeNotifications } from './remove-notifications';

Expand All @@ -10,6 +11,7 @@ describe('utils/remove-notifications.ts', () => {
expect(mockSingleAccountNotifications[0].notifications.length).toBe(1);

const result = removeNotifications(
mockSettings,
mockSingleNotification,
mockSingleAccountNotifications,
);
Expand All @@ -22,11 +24,24 @@ describe('utils/remove-notifications.ts', () => {
expect(mockAccountNotifications[1].notifications.length).toBe(2);

const result = removeNotifications(
mockSettings,
mockSingleNotification,
mockAccountNotifications,
);

expect(result[0].notifications.length).toBe(0);
expect(result[1].notifications.length).toBe(2);
});

it('should skip notification removal if delay state enabled', () => {
expect(mockSingleAccountNotifications[0].notifications.length).toBe(1);

const result = removeNotifications(
{ ...mockSettings, delayNotificationState: true },
mockSingleNotification,
mockSingleAccountNotifications,
);

expect(result[0].notifications.length).toBe(1);
});
});
7 changes: 6 additions & 1 deletion src/utils/remove-notifications.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import type { AccountNotifications } from '../types';
import type { AccountNotifications, SettingsState } from '../types';
import type { Notification } from '../typesGitHub';
import { getAccountUUID } from './auth/utils';

export function removeNotifications(
settings: SettingsState,
notification: Notification,
notifications: AccountNotifications[],
): AccountNotifications[] {
if (settings.delayNotificationState) {
return notifications;
}

const repoSlug = notification.repository.full_name;

const accountIndex = notifications.findIndex(
Expand Down