diff --git a/pkg/interface/src/views/apps/notifications/Archive.tsx b/pkg/interface/src/views/apps/notifications/Archive.tsx
new file mode 100644
index 0000000000..e4df3c2bbb
--- /dev/null
+++ b/pkg/interface/src/views/apps/notifications/Archive.tsx
@@ -0,0 +1,31 @@
+import { Box } from '@tlon/indigo-react';
+import React, { useEffect } from 'react';
+import useHarkState, { HarkState } from '~/logic/state/hark';
+import { Notification } from './notification';
+
+const selArchive = (s: HarkState) => s.archive;
+
+export function Archive() {
+ const archive = useHarkState(selArchive);
+ const keys = archive.keys();
+
+ useEffect(() => {
+ useHarkState.getState().getMore();
+ }, []);
+
+ return (
+
+ {keys.map(key =>
+ Object.entries(archive.get(key)!)
+ .sort(([, a], [, b]) => b.time - a.time)
+ .map(([binId, n]) => (
+
+ ))
+ )}
+
+ );
+}
diff --git a/pkg/interface/src/views/apps/notifications/NewBox.tsx b/pkg/interface/src/views/apps/notifications/NewBox.tsx
new file mode 100644
index 0000000000..56fb99ea4f
--- /dev/null
+++ b/pkg/interface/src/views/apps/notifications/NewBox.tsx
@@ -0,0 +1,58 @@
+import { Box, Center, Col, Text } from '@tlon/indigo-react';
+import React from 'react';
+import useHarkState, { HarkState } from '~/logic/state/hark';
+import { harkBinToId, HarkLid, Timebox } from '../../../../../npm/api/dist';
+import { Notification } from './notification';
+
+const unseenLid = { unseen: null };
+const seenLid = { seen: null };
+const selUnseen = (s: HarkState) => s.unseen;
+const selSeen = (s: HarkState) => s.seen;
+export function NewBox() {
+ const seen = useHarkState(selSeen);
+ const unseen = useHarkState(selUnseen);
+ const empty = Object.keys(seen).length + Object.keys(unseen).length === 0;
+
+ return (
+
+ {empty ? (
+
+ All clear!
+
+ ) : (
+ <>
+
+
+ >
+ )}
+
+ );
+}
+
+function Lid({
+ lid,
+ timebox,
+ title
+}: {
+ lid: HarkLid;
+ timebox: Timebox;
+ title: string;
+}) {
+ if(Object.keys(timebox).length === 0) {
+ return null;
+ }
+ return (
+ <>
+
+ {title}
+
+
+ {Object.entries(timebox)
+ .sort(([, a], [, b]) => b.time - a.time)
+ .map(([binId, n]) => (
+
+ ))}
+
+ >
+ );
+}
diff --git a/pkg/interface/src/views/apps/notifications/inbox.tsx b/pkg/interface/src/views/apps/notifications/inbox.tsx
deleted file mode 100644
index c0490d8654..0000000000
--- a/pkg/interface/src/views/apps/notifications/inbox.tsx
+++ /dev/null
@@ -1,135 +0,0 @@
-import { Col } from '@tlon/indigo-react';
-import {
- IndexedNotification,
- JoinRequests,
- Notifications,
- seen,
- Timebox,
- unixToDa
-} from '@urbit/api';
-import { BigInteger } from 'big-integer';
-import _ from 'lodash';
-import f from 'lodash/fp';
-import moment from 'moment';
-import React, { useEffect } from 'react';
-import { getNotificationKey } from '~/logic/lib/hark';
-import { daToUnix } from '~/logic/lib/util';
-import useHarkState from '~/logic/state/hark';
-import { Invites } from './invites';
-import { Notification } from './notification';
-import airlock from '~/logic/api';
-
-type DatedTimebox = [BigInteger, Timebox];
-
-function filterNotification(groups: string[]) {
- if (groups.length === 0) {
- return () => true;
- }
- return (n: IndexedNotification) => {
- if ('graph' in n.index) {
- const { group } = n.index.graph;
- return groups.findIndex(g => group === g) !== -1;
- } else if ('group' in n.index) {
- const { group } = n.index.group;
- return groups.findIndex(g => group === g) !== -1;
- }
- return true;
- };
-}
-
-export default function Inbox(props: {
- archive: Notifications;
- showArchive?: boolean;
- filter: string[];
- pendingJoin: JoinRequests;
-}) {
- useEffect(() => {
- let hasSeen = false;
- setTimeout(() => {
- hasSeen = true;
- }, 3000);
- return () => {
- if (hasSeen) {
- airlock.poke(seen());
- }
- };
- }, []);
-
- const ready = useHarkState(
- s => Object.keys(s.unreads.graph).length > 0
- );
-
- const getMore = useHarkState(s => s.getMore);
-
- const notificationState = useHarkState(state => state.notifications);
- const unreadNotes = useHarkState(s => s.unreadNotes);
- const archivedNotifications = useHarkState(state => state.archivedNotifications);
-
- const notifications =
- Array.from(props.showArchive ? archivedNotifications : notificationState) || [];
-
- const notificationsByDay = f.flow(
- f.map(([date, nots]) => [
- date,
- nots.filter(filterNotification(props.filter))
- ]),
- f.groupBy(([d]) => {
- const date = moment(daToUnix(d));
- if (moment().subtract(6, 'hours').isBefore(date)) {
- return 'latest';
- } else {
- return date.format('YYYYMMDD');
- }
- })
- )(notifications);
-
- const notificationsByDayMap = new Map(
- Object.keys(notificationsByDay).map((timebox) => {
- return [timebox, notificationsByDay[timebox]];
- })
- );
-
- const date = unixToDa(Date.now());
-
- return (
-
-
-
- );
-}
-
-function sortTimeboxes([a]: DatedTimebox, [b]: DatedTimebox) {
- return b.subtract(a);
-}
-
-function sortIndexedNotification(
- { notification: a }: IndexedNotification,
- { notification: b }: IndexedNotification
-) {
- return b.time - a.time;
-}
-
-function DaySection({
- timeboxes,
- unread = false
-}) {
- const lent = timeboxes.map(([,nots]) => nots.length).reduce(f.add, 0);
- if (lent === 0 || timeboxes.length === 0) {
- return null;
- }
-
- return (
- <>
- {_.map(timeboxes.sort(sortTimeboxes), ([date, nots], i: number) =>
- _.map(nots.sort(sortIndexedNotification), (not, j: number) => (
-
- ))
- )}
- >
- );
-}
diff --git a/pkg/interface/src/views/apps/notifications/notification.tsx b/pkg/interface/src/views/apps/notifications/notification.tsx
index b9fbbf5d64..43f9b03d0a 100644
--- a/pkg/interface/src/views/apps/notifications/notification.tsx
+++ b/pkg/interface/src/views/apps/notifications/notification.tsx
@@ -1,64 +1,111 @@
-import { Box, Button, Icon, Row } from '@tlon/indigo-react';
+import { Box, Col, Text, Button, Icon, Row } from '@tlon/indigo-react';
import {
- GraphNotificationContents,
- GroupNotificationContents,
- IndexedNotification
+ HarkLid,
+ harkLidToId,
+ harkBinToId,
+ Notification as INotification,
+ HarkContent
} from '@urbit/api';
import { BigInteger } from 'big-integer';
-import React, { ReactNode, useCallback } from 'react';
-import { getNotificationKey } from '~/logic/lib/hark';
+import React, { useCallback } from 'react';
import { useHovering } from '~/logic/lib/util';
import useLocalState from '~/logic/state/local';
import { StatelessAsyncAction } from '~/views/components/StatelessAsyncAction';
import { SwipeMenu } from '~/views/components/SwipeMenu';
-import { GraphNotification } from './graph';
-import { GroupNotification } from './group';
import useHarkState from '~/logic/state/hark';
-import shallow from 'zustand/shallow';
+import { map, take } from 'lodash';
+import { Mention } from '~/views/components/MentionText';
+import { PropFunc } from '~/types';
+import { useHistory } from 'react-router-dom';
+import { getNotificationRedirect } from '~/logic/lib/notificationRedirects';
export interface NotificationProps {
- notification: IndexedNotification;
+ notification: INotification;
time: BigInteger;
unread: boolean;
}
-export function NotificationWrapper(props: {
- time?: BigInteger;
- read?: boolean;
- notification?: IndexedNotification;
- children: ReactNode;
+const MAX_CONTENTS = 5;
+
+interface NotificationTextProps extends PropFunc {
+ contents: HarkContent[];
+}
+const NotificationText = ({ contents, ...rest }: NotificationTextProps) => {
+ return (
+ <>
+ {contents.map((content, idx) => {
+ if ('ship' in content) {
+ return (
+
+ );
+ }
+ return {content.text};
+ })}
+ >
+ );
+};
+
+export function Notification(props: {
+ lid: HarkLid;
+ notification: INotification;
}) {
- const { time, notification, children, read = false } = props;
+ const { notification, lid } = props;
+ const read = !('unseen' in lid);
+ const key = `${harkLidToId(lid)}-${harkBinToId(notification.bin)}`;
+ const history = useHistory();
const isMobile = useLocalState(s => s.mobile);
- const [archive, readNote] = useHarkState(s => [s.archive, s.readNote], shallow);
-
- const onArchive = useCallback(async (e) => {
- e.stopPropagation();
- if (!notification) {
- return;
- }
- await archive(notification.index, time);
- }, [time, notification]);
-
- const onClick = (e: any) => {
- if (!notification || read) {
- return;
- }
- return readNote(notification.index);
- };
+ const onArchive = useCallback(
+ async (e) => {
+ e.stopPropagation();
+ if (!notification) {
+ return;
+ }
+ useHarkState.getState().archiveNote(notification.bin, lid);
+ },
+ [notification, lid]
+ );
const { hovering, bind } = useHovering();
+ const contents = map(notification.body, 'content').filter(
+ c => c.length > 0
+ );
+ const first = notification.body[0];
+ if (!first) {
+ // should be unreachable
+ return null;
+ }
+
+ const onClick = (e: any) => {
+ const redirect = getNotificationRedirect(first.link);
+ if(redirect) {
+ history.push(redirect);
+ } else {
+ console.log('no redirect');
+ }
+ };
return (
+
}
@@ -74,7 +121,24 @@ export function NotificationWrapper(props: {
p={2}
{...bind}
>
- {children}
+
+
+
+
+
+ {take(contents, MAX_CONTENTS).map((content, i) => (
+
+
+
+ ))}
+
+ {contents.length > MAX_CONTENTS ? (
+
+ and {contents.length - MAX_CONTENTS} more
+
+ ) : null}
+
+
- {notification && (
+ {!('time' in lid) && (
);
}
-
-export function Notification(props: NotificationProps) {
- const { notification, unread } = props;
- const { contents, time } = notification.notification;
-
- const wrapperProps = {
- notification,
- read: !unread,
- time: props.time
- };
-
- if ('graph' in notification.index) {
- const index = notification.index.graph;
- const c: GraphNotificationContents = (contents as any).graph;
-
- return (
-
-
-
- );
- }
- if ('group' in notification.index) {
- const index = notification.index.group;
- const c: GroupNotificationContents = (contents as any).group;
- return (
-
-
-
- );
- }
-
- return null;
-}
diff --git a/pkg/interface/src/views/apps/notifications/notifications.tsx b/pkg/interface/src/views/apps/notifications/notifications.tsx
index 26d9fe1e41..9a48d903aa 100644
--- a/pkg/interface/src/views/apps/notifications/notifications.tsx
+++ b/pkg/interface/src/views/apps/notifications/notifications.tsx
@@ -1,29 +1,50 @@
-import { Box, Col, Icon, Row, Text } from '@tlon/indigo-react';
-import React, { ReactElement, useCallback, useRef } from 'react';
+import { Action, Box, Col, Icon, Row, Text } from '@tlon/indigo-react';
+import React, { ReactElement, ReactNode, useRef } from 'react';
import Helmet from 'react-helmet';
-import { Link, Route, Switch } from 'react-router-dom';
-import useGroupState from '~/logic/state/group';
+import { Link, Route, Switch, useHistory, useLocation } from 'react-router-dom';
import useHarkState from '~/logic/state/hark';
import { Body } from '~/views/components/Body';
import { StatelessAsyncAction } from '~/views/components/StatelessAsyncAction';
import { useTutorialModal } from '~/views/components/useTutorialModal';
-import Inbox from './inbox';
-import airlock from '~/logic/api';
-import { readAll } from '@urbit/api';
+import { Archive } from './Archive';
+import { NewBox } from './NewBox';
const baseUrl = '/~notifications';
+export function NavLink({
+ href,
+ children
+}: {
+ href: string;
+ children: ReactNode;
+}) {
+ const location = useLocation();
+ const { push } = useHistory();
+
+ const isActive = href === location.pathname;
+
+ const onClick = () => {
+ push(href);
+ };
+
+ return (
+
+ {children}
+
+ );
+}
+
export default function NotificationsScreen(props: any): ReactElement {
const relativePath = (p: string) => baseUrl + p;
- const pendingJoin = useGroupState(s => s.pendingJoin);
- const onReadAll = useCallback(async () => {
- await airlock.poke(readAll());
- }, []);
-
const anchorRef = useRef(null);
useTutorialModal('notifications', true, anchorRef);
const notificationsCount = useHarkState(state => state.notificationsCount);
+ const onReadAll = async () => {};
return (
- { notificationsCount ? `(${String(notificationsCount) }) `: '' }Landscape - Notifications
+
+ {notificationsCount ? `(${String(notificationsCount)}) ` : ''}
+ Groups - Notifications
+
@@ -46,22 +70,29 @@ export default function NotificationsScreen(props: any): ReactElement {
borderBottom={1}
borderBottomColor="lightGray"
>
-
-
- Notifications
-
-
-
- Mark All Read
-
+ Notifications
+
+
+ New
+ Archive
+
+
+ { (false as boolean) ? (
+
+ Mark All Read
+
+ ) : null}
@@ -69,11 +100,9 @@ export default function NotificationsScreen(props: any): ReactElement {
- {!view && }
+ { view === 'archive' ? (
+
+ ) : }
>