Skip to content

Commit

Permalink
Merge pull request #3780 from tloncorp/hm/web-match-mobile-activity-tab
Browse files Browse the repository at this point in the history
activity: web match mobile bell tab
  • Loading branch information
arthyn authored Jul 22, 2024
2 parents 13c6de3 + b685507 commit 1a45423
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 44 deletions.
47 changes: 47 additions & 0 deletions apps/tlon-web/src/components/Settings/Settings.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import { useMutation } from '@tanstack/react-query';
import { ActivityAction, ActivityUpdate } from '@tloncorp/shared/dist/urbit';
import cn from 'classnames';
import { useCallback } from 'react';
import { Link, useLocation } from 'react-router-dom';

import api from '@/api';
import LoadingSpinner from '@/components/LoadingSpinner/LoadingSpinner';
import { useIsMobile } from '@/logic/useMedia';
import { activityAction } from '@/state/activity';
import {
Theme,
useCalm,
Expand Down Expand Up @@ -44,9 +50,50 @@ export default function Settings() {
usePutEntryMutation({ bucket: window.desk, key: 'logActivity' });
const { mutate: resetAnalyticsId, status: resetAnalyticsIdStatus } =
useResetAnalyticsIdMutation();
const { mutate: markRead, isLoading } = useMutation({
mutationFn: async () => {
await api.trackedPoke<ActivityAction, ActivityUpdate>(
activityAction({
read: {
source: { base: null },
action: { all: { time: null, deep: true } },
},
}),
{ app: 'activity', path: '/v4' },
(event) => 'activity' in event
);

await new Promise((res) => setTimeout(res, 1000));
},
});
const isMarkReadPending = isLoading;
const markAllRead = useCallback(async () => {
markRead();
}, []);

return (
<>
<div className="card">
<div className="flex flex-col items-start space-y-2">
<h2 className="text-lg font-semibold">Activity</h2>
<p className="text-gray-600">
Mark all channels, DMs, and threads read (aka get rid of dots)
</p>
<button
disabled={isMarkReadPending}
className={cn('small-button whitespace-nowrap text-sm min-w-10', {
'bg-gray-400 text-gray-800': isMarkReadPending,
})}
onClick={markAllRead}
>
{isMarkReadPending ? (
<LoadingSpinner className="h-4 w-4" />
) : (
`Mark Everything as Read`
)}
</button>
</div>
</div>
<div className="card">
<div className="flex flex-col">
<h2 className="mb-2 text-lg font-semibold">Blocked Users</h2>
Expand Down
2 changes: 1 addition & 1 deletion apps/tlon-web/src/components/VolumeSetting.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export default function VolumeSetting({ source }: { source: Source }) {
);

return (
<div className="space-y-4 min-w-[400px]">
<div className="space-y-4 flex-1 sm:flex-none sm:min-w-[400px]">
<Setting
on={currentUnreads}
name="Show Unread Indicator"
Expand Down
61 changes: 22 additions & 39 deletions apps/tlon-web/src/notifications/Notifications.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
import { ActivityBundle, ActivitySummary } from '@tloncorp/shared/dist/urbit';
import { ViewProps } from '@tloncorp/shared/dist/urbit/groups';
import cn from 'classnames';
import { PropsWithChildren, useCallback } from 'react';
import { PropsWithChildren, useCallback, useEffect } from 'react';
import { Helmet } from 'react-helmet';
import { Virtuoso } from 'react-virtuoso';

import LoadingSpinner from '@/components/LoadingSpinner/LoadingSpinner';
import MobileHeader from '@/components/MobileHeader';
import ReconnectingSpinner from '@/components/ReconnectingSpinner';
import WelcomeCard from '@/components/WelcomeCard';
import { useBottomPadding } from '@/logic/position';
import { useIsMobile } from '@/logic/useMedia';
import { makePrettyDay, randomElement, randomIntInRange } from '@/logic/utils';
import { useMarkReadMutation } from '@/state/activity';
import {
emptySummary,
useActivity,
useOptimisticMarkRead,
} from '@/state/activity';

import Notification from './Notification';
import { useNotifications } from './useNotifications';
Expand Down Expand Up @@ -91,48 +94,30 @@ function getKey(i: number, { bundle }: NotificationItem): string {
export default function Notifications({ title }: NotificationsProps) {
const isMobile = useIsMobile();
const { paddingBottom } = useBottomPadding();
const { loaded, notifications, activity, fetchNextPage, hasNextPage } =
const { loaded, notifications, fetchNextPage, hasNextPage } =
useNotifications();
const { mutate, isLoading } = useMarkReadMutation(true);
const isMarkReadPending = isLoading;
const hasUnreads = activity['notify-count'] > 0;
const { activity, isLoading } = useActivity();
const markRead = useOptimisticMarkRead('base');

useEffect(() => {
const hasActivity = Object.keys(activity).length > 0;
const base = activity['base'] || emptySummary;
const baseIsRead =
base.count === 0 &&
base.unread === null &&
!base.notify &&
base['notify-count'] === 0;
if (hasActivity && !baseIsRead && !isLoading) {
markRead();
}
}, [activity, isLoading]);

const getMore = useCallback(() => {
if (hasNextPage) {
fetchNextPage();
}
}, [hasNextPage]);

const markAllRead = useCallback(async () => {
mutate({ source: { base: null } });
}, []);

const MarkAsRead = (
<button
disabled={isMarkReadPending || !hasUnreads}
className={cn('small-button whitespace-nowrap text-sm', {
'bg-gray-400 text-gray-800': isMarkReadPending || !hasUnreads,
})}
onClick={markAllRead}
>
{isMarkReadPending ? (
<LoadingSpinner className="h-4 w-4" />
) : (
`Mark Everything as Read`
)}
</button>
);

const MobileMarkAsRead = (
<button
disabled={isMarkReadPending || !hasUnreads}
className="whitespace-nowrap text-[17px] font-normal text-gray-800"
onClick={markAllRead}
>
Read Everything
</button>
);

return (
<>
{isMobile && (
Expand All @@ -141,7 +126,6 @@ export default function Notifications({ title }: NotificationsProps) {
action={
<div className="flex h-12 items-center justify-end space-x-2">
<ReconnectingSpinner />
{hasUnreads && MobileMarkAsRead}
</div>
}
/>
Expand All @@ -163,7 +147,6 @@ export default function Notifications({ title }: NotificationsProps) {
<WelcomeCard />
<div className="mb-6 flex w-full items-center justify-between">
<h2 className="text-lg font-bold">Activity</h2>
{hasUnreads && MarkAsRead}
</div>
</div>
)}
Expand Down
31 changes: 27 additions & 4 deletions apps/tlon-web/src/state/activity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ function optimisticActivityUpdate(d: Activity, source: string): Activity {
count: Math.min(0, old.count - (old.unread?.count || 0)),
'notify-count':
old.unread && old.unread.notify
? Math.min(old['notify-count'] - old.unread.count)
? Math.min(0, old['notify-count'] - old.unread.count)
: old['notify-count'],
},
};
Expand Down Expand Up @@ -367,21 +367,23 @@ export function useMarkReadMutation(recursive = false) {
const mutationFn = async (variables: {
source: Source;
action?: ReadAction;
}) => {
await api.poke(
}) =>
api.poke(
activityAction({
read: {
source: variables.source,
action: variables.action || { all: { time: null, deep: recursive } },
},
})
);
};

return useMutation({
mutationFn,
onMutate: async (variables) => {
const current = queryClient.getQueryData<Activity>(unreadsKey());
variables.action = variables.action || {
all: { time: null, deep: recursive },
};
queryClient.setQueryData<Activity>(unreadsKey(), (d) => {
if (d === undefined) {
return undefined;
Expand Down Expand Up @@ -610,3 +612,24 @@ export function useCombinedGroupUnreads() {
};
}, defaultUnread);
}

export function useOptimisticMarkRead(source: string) {
return useCallback(() => {
queryClient.setQueryData<Activity>(unreadsKey(), (d) => {
if (d === undefined) {
return undefined;
}

return {
...d,
[source]: {
...d[source],
unread: null,
count: 0,
notify: false,
'notify-count': 0,
},
};
});
}, [source]);
}

0 comments on commit 1a45423

Please sign in to comment.