Skip to content
34 changes: 31 additions & 3 deletions src/components/AccountNotifications.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import React from 'react';
import React, { useContext, useEffect, useState } from 'react';
import { ChevronDownIcon, ChevronLeftIcon } from '@primer/octicons-react';

import { Notification } from '../typesGithub';
import { RepositoryNotifications } from './Repository';
import { AppContext } from '../context/App';

interface IProps {
hostname: string;
Expand All @@ -13,6 +14,18 @@ interface IProps {
export const AccountNotifications = (props: IProps) => {
const { hostname, showAccountHostname, notifications } = props;

const { groupBy } = useContext(AppContext);

const sortByDate = (notifications: Notification[][]): Notification[][] => {
// Create a copy of the notifications array before sorting
const sortedNotifications = [...notifications];
return sortedNotifications.sort((a, b) => {
const dateA = new Date(a[0].updated_at).getTime();
const dateB = new Date(b[0].updated_at).getTime();
return dateB - dateA;
});
};

const groupedNotifications = Object.values(
notifications.reduce(
(acc: { [key: string]: Notification[] }, notification) => {
Expand All @@ -25,6 +38,22 @@ export const AccountNotifications = (props: IProps) => {
),
);

const [sortedNotifications, setSortedNotifications] =
useState<Notification[][]>(groupedNotifications);

// Function to sort notifications based on the selected grouping type
const sortNotifications = () => {
const newSortedNotifications =
groupBy?.groupType === 'date'
? sortByDate(groupedNotifications)
: groupedNotifications;
setSortedNotifications(newSortedNotifications);
};

useEffect(() => {
sortNotifications();
}, [groupBy?.groupType]); // Run the effect when groupBy.groupType changes

const Chevron = notifications.length > 0 ? ChevronDownIcon : ChevronLeftIcon;

return (
Expand All @@ -37,9 +66,8 @@ export const AccountNotifications = (props: IProps) => {
</div>
)}

{Object.values(groupedNotifications).map((repoNotifications) => {
{sortedNotifications.map((repoNotifications) => {
const repoSlug = repoNotifications[0].repository.full_name;

return (
<RepositoryNotifications
key={repoSlug}
Expand Down
6 changes: 6 additions & 0 deletions src/components/NotificationRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export const NotificationRow: React.FC<IProps> = ({
markNotification,
markNotificationDone,
unsubscribeNotification,
groupBy,
} = useContext(AppContext);

const pressTitle = useCallback(() => {
Expand Down Expand Up @@ -80,6 +81,11 @@ export const NotificationRow: React.FC<IProps> = ({
onClick={() => pressTitle()}
role="main"
>
{groupBy?.groupType === 'date' && (
<div className="mb-1 text-sm whitespace-nowrap overflow-ellipsis overflow-hidden">
{notification.repository.full_name}
</div>
)}
<div className="mb-1 text-sm whitespace-nowrap overflow-ellipsis overflow-hidden">
{notification.subject.title}
</div>
Expand Down
38 changes: 34 additions & 4 deletions src/components/Repository.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,14 @@ describe('components/Repository.tsx', () => {

it('should render itself & its children', () => {
const tree = TestRenderer.create(
<AppContext.Provider value={{}}>
<AppContext.Provider
value={{
groupBy: {
groupType: 'repository',
setGroupType: () => 'repository',
},
}}
>
<RepositoryNotifications {...props} />
</AppContext.Provider>,
);
Expand All @@ -39,7 +46,14 @@ describe('components/Repository.tsx', () => {

it('should open the browser when clicking on the repo name', () => {
const { getByText } = render(
<AppContext.Provider value={{}}>
<AppContext.Provider
value={{
groupBy: {
groupType: 'repository',
setGroupType: () => 'repository',
},
}}
>
<RepositoryNotifications {...props} />
</AppContext.Provider>,
);
Expand All @@ -54,7 +68,15 @@ describe('components/Repository.tsx', () => {

it('should mark a repo as read', function () {
const { getByTitle } = render(
<AppContext.Provider value={{ markRepoNotifications }}>
<AppContext.Provider
value={{
markRepoNotifications,
groupBy: {
groupType: 'repository',
setGroupType: () => 'repository',
},
}}
>
<RepositoryNotifications {...props} />
</AppContext.Provider>,
);
Expand All @@ -69,7 +91,15 @@ describe('components/Repository.tsx', () => {

it('should mark a repo as done', function () {
const { getByTitle } = render(
<AppContext.Provider value={{ markRepoNotificationsDone }}>
<AppContext.Provider
value={{
markRepoNotificationsDone,
groupBy: {
groupType: 'repository',
setGroupType: () => 'repository',
},
}}
>
<RepositoryNotifications {...props} />
</AppContext.Provider>,
);
Expand Down
48 changes: 25 additions & 23 deletions src/components/Repository.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export const RepositoryNotifications: React.FC<IProps> = ({
repoNotifications,
hostname,
}) => {
const { markRepoNotifications, markRepoNotificationsDone } =
const { markRepoNotifications, markRepoNotificationsDone, groupBy } =
useContext(AppContext);

const openBrowser = useCallback(() => {
Expand All @@ -40,32 +40,34 @@ export const RepositoryNotifications: React.FC<IProps> = ({

return (
<>
<div className="flex py-2 px-3 bg-gray-100 dark:bg-gray-darker dark:text-white group">
<div className="flex flex-1 space-x-3 items-center mt-0 text-sm font-medium overflow-hidden overflow-ellipsis whitespace-nowrap">
<img className="rounded w-5 h-5" src={avatarUrl} />
<span onClick={openBrowser}>{repoName}</span>
</div>
{groupBy?.groupType === 'repository' && (
<div className="flex py-2 px-3 bg-gray-100 dark:bg-gray-darker dark:text-white">
<div className="flex flex-1 space-x-3 items-center mt-0 text-sm font-medium overflow-hidden overflow-ellipsis whitespace-nowrap">
<img className="rounded w-5 h-5" src={avatarUrl} />
<span onClick={openBrowser}>{repoName}</span>
</div>

<div className="flex justify-center items-center gap-2 opacity-0 group-hover:opacity-80 transition-opacity">
<button
className="focus:outline-none h-full hover:text-green-500"
title="Mark Repository as Done"
onClick={markRepoAsDone}
>
<CheckIcon size={16} aria-label="Mark Repository as Done" />
</button>
<div className="flex justify-center items-center gap-2">
<button
className="focus:outline-none h-full hover:text-green-500"
title="Mark Repository as Done"
onClick={markRepoAsDone}
>
<CheckIcon size={16} aria-label="Mark Repository as Done" />
</button>

<div className="w-[14px]" />
<div className="w-[14px]" />

<button
className="focus:outline-none h-full hover:text-green-500"
title="Mark Repository as Read"
onClick={markRepoAsRead}
>
<ReadIcon size={14} aria-label="Mark Repository as Read" />
</button>
<button
className="focus:outline-none h-full hover:text-green-500"
title="Mark Repository as Read"
onClick={markRepoAsRead}
>
<ReadIcon size={14} aria-label="Mark Repository as Read" />
</button>
</div>
</div>
</div>
)}

<TransitionGroup>
{repoNotifications.map((obj) => (
Expand Down
50 changes: 47 additions & 3 deletions src/components/Sidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { BellIcon } from '@primer/octicons-react';
import { BellIcon, CheckIcon, FilterIcon } from '@primer/octicons-react';
import { ipcRenderer } from 'electron';
import React, { useCallback, useContext, useMemo } from 'react';
import React, { useCallback, useContext, useMemo, useState } from 'react';
import { useNavigate, useLocation } from 'react-router-dom';

import { Logo } from '../components/Logo';
Expand All @@ -16,7 +16,9 @@ export const Sidebar: React.FC = () => {
const location = useLocation();

const { isLoggedIn } = useContext(AppContext);
const { notifications, fetchNotifications } = useContext(AppContext);
const { notifications, fetchNotifications, groupBy } = useContext(AppContext);

const [dropdownOpen, setDropdownOpen] = useState<boolean>(false);

const onOpenBrowser = useCallback(() => {
openExternalLink(`https://github.com/${Constants.REPO_SLUG}`);
Expand Down Expand Up @@ -59,6 +61,48 @@ export const Sidebar: React.FC = () => {
<BellIcon size={12} />
{notificationsCount > 0 && notificationsCount}
</div>

<div className="flex items-center">
<div
className={`flex items-center cursor-pointer text-white ${dropdownOpen ? 'z-20' : ''}`}
onClick={() => setDropdownOpen(!dropdownOpen)}
>
<FilterIcon size={12} />
</div>
{dropdownOpen && (
<div
className="fixed inset-0 z-10 flex items-center justify-center"
onClick={() => setDropdownOpen(false)}
>
<div className="absolute top-28 left-0 w-28 border border-gray-300 rounded shadow-md bg-[#161b22]">
<button
onClick={() => {
groupBy.setGroupType('repository');
setDropdownOpen(false);
}}
className="w-full px-2 py-2 bg-[#161b22] text-white dark:hover:bg-gray-darker rounded text-sm flex justify-center items-center"
>
{groupBy?.groupType === 'repository' && (
<CheckIcon className="text-white w-4 mr-2" />
)}
Repository
</button>
<button
onClick={() => {
groupBy.setGroupType('date');
setDropdownOpen(false);
}}
className="w-full px-2 py-2 bg-[#161b22] text-white dark:hover:bg-gray-darker rounded text-sm flex justify-center items-center"
>
{groupBy?.groupType === 'date' && (
<CheckIcon className="text-white w-4 mr-2" />
)}
Date
</button>
</div>
</div>
)}
</div>
</div>

<div className="py-4 px-3">
Expand Down
4 changes: 2 additions & 2 deletions src/components/__snapshots__/Repository.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
exports[`components/Repository.tsx should render itself & its children 1`] = `
[
<div
className="flex py-2 px-3 bg-gray-100 dark:bg-gray-darker dark:text-white group"
className="flex py-2 px-3 bg-gray-100 dark:bg-gray-darker dark:text-white"
>
<div
className="flex flex-1 space-x-3 items-center mt-0 text-sm font-medium overflow-hidden overflow-ellipsis whitespace-nowrap"
Expand All @@ -19,7 +19,7 @@ exports[`components/Repository.tsx should render itself & its children 1`] = `
</span>
</div>
<div
className="flex justify-center items-center gap-2 opacity-0 group-hover:opacity-80 transition-opacity"
className="flex justify-center items-center gap-2"
>
<button
className="focus:outline-none h-full hover:text-green-500"
Expand Down
62 changes: 62 additions & 0 deletions src/components/__snapshots__/Sidebar.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,37 @@ exports[`components/Sidebar.tsx should render itself & its children (logged in)
</svg>
4
</div>
<div
className="flex items-center"
>
<div
className="flex items-center cursor-pointer text-white "
onClick={[Function]}
>
<svg
aria-hidden="true"
className="octicon octicon-filter"
fill="currentColor"
focusable="false"
height={12}
role="img"
style={
{
"display": "inline-block",
"overflow": "visible",
"userSelect": "none",
"verticalAlign": "text-bottom",
}
}
viewBox="0 0 16 16"
width={12}
>
<path
d="M.75 3h14.5a.75.75 0 0 1 0 1.5H.75a.75.75 0 0 1 0-1.5ZM3 7.75A.75.75 0 0 1 3.75 7h8.5a.75.75 0 0 1 0 1.5h-8.5A.75.75 0 0 1 3 7.75Zm3 4a.75.75 0 0 1 .75-.75h2.5a.75.75 0 0 1 0 1.5h-2.5a.75.75 0 0 1-.75-.75Z"
/>
</svg>
</div>
</div>
</div>
<div
className="py-4 px-3"
Expand Down Expand Up @@ -201,6 +232,37 @@ exports[`components/Sidebar.tsx should render itself & its children (logged out)
</svg>
4
</div>
<div
className="flex items-center"
>
<div
className="flex items-center cursor-pointer text-white "
onClick={[Function]}
>
<svg
aria-hidden="true"
className="octicon octicon-filter"
fill="currentColor"
focusable="false"
height={12}
role="img"
style={
{
"display": "inline-block",
"overflow": "visible",
"userSelect": "none",
"verticalAlign": "text-bottom",
}
}
viewBox="0 0 16 16"
width={12}
>
<path
d="M.75 3h14.5a.75.75 0 0 1 0 1.5H.75a.75.75 0 0 1 0-1.5ZM3 7.75A.75.75 0 0 1 3.75 7h8.5a.75.75 0 0 1 0 1.5h-8.5A.75.75 0 0 1 3 7.75Zm3 4a.75.75 0 0 1 .75-.75h2.5a.75.75 0 0 1 0 1.5h-2.5a.75.75 0 0 1-.75-.75Z"
/>
</svg>
</div>
</div>
</div>
<div
className="py-4 px-3"
Expand Down
Loading