1
0
Fork 0
mirror of https://github.com/muerwre/vault-frontend.git synced 2025-04-24 20:36:40 +07:00

notifications: for nodes

This commit is contained in:
Fedor Katurov 2023-03-16 16:37:43 +06:00
parent 14bf5be65f
commit d9544e917b
13 changed files with 156 additions and 27 deletions

View file

@ -1,10 +1,9 @@
import React, { FC, MouseEventHandler } from 'react'; import { FC, MouseEventHandler } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { Anchor } from '~/components/common/Anchor'; import { Anchor } from '~/components/common/Anchor';
import { Icon } from '~/components/input/Icon'; import { NodeThumbnail } from '~/components/node/NodeThumbnail';
import { NodeRelatedItem } from '~/components/node/NodeRelatedItem';
import { URLS } from '~/constants/urls'; import { URLS } from '~/constants/urls';
import { INode } from '~/types'; import { INode } from '~/types';
import { getPrettyDate } from '~/utils/dom'; import { getPrettyDate } from '~/utils/dom';
@ -31,7 +30,7 @@ const FlowRecentItem: FC<IProps> = ({ node, has_new, onClick }) => {
[styles.lab]: !node.is_promoted, [styles.lab]: !node.is_promoted,
})} })}
> >
<NodeRelatedItem item={node} /> <NodeThumbnail item={node} />
</div> </div>
<div className={styles.info}> <div className={styles.info}>

View file

@ -3,7 +3,7 @@ import React, { FC, ReactElement } from 'react';
import { Hoverable } from '~/components/common/Hoverable'; import { Hoverable } from '~/components/common/Hoverable';
import { SubTitle } from '~/components/common/SubTitle'; import { SubTitle } from '~/components/common/SubTitle';
import { Group } from '~/components/containers/Group'; import { Group } from '~/components/containers/Group';
import { NodeRelatedItem } from '~/components/node/NodeRelatedItem'; import { NodeThumbnail } from '~/components/node/NodeThumbnail';
import { INode } from '~/types'; import { INode } from '~/types';
import styles from './styles.module.scss'; import styles from './styles.module.scss';
@ -21,7 +21,7 @@ const NodeRelated: FC<IProps> = ({ title, items }) => {
<div className={styles.grid}> <div className={styles.grid}>
{items.map((item) => ( {items.map((item) => (
<Hoverable key={item.id} className={styles.item}> <Hoverable key={item.id} className={styles.item}>
<NodeRelatedItem item={item} /> <NodeThumbnail item={item} />
</Hoverable> </Hoverable>
))} ))}
</div> </div>

View file

@ -3,7 +3,7 @@ import React, { FC, memo } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { Group } from '~/components/containers/Group'; import { Group } from '~/components/containers/Group';
import cell_style from '~/components/node/NodeRelatedItem/styles.module.scss'; import cell_style from '~/components/node/NodeThumbnail/styles.module.scss';
import { Placeholder } from '~/components/placeholders/Placeholder'; import { Placeholder } from '~/components/placeholders/Placeholder';
import { range } from '~/utils/ramda'; import { range } from '~/utils/ramda';

View file

@ -8,13 +8,17 @@ import { Icon } from '~/components/input/Icon';
import { imagePresets } from '~/constants/urls'; import { imagePresets } from '~/constants/urls';
import { useColorGradientFromString } from '~/hooks/color/useColorGradientFromString'; import { useColorGradientFromString } from '~/hooks/color/useColorGradientFromString';
import { useGotoNode } from '~/hooks/node/useGotoNode'; import { useGotoNode } from '~/hooks/node/useGotoNode';
import { INode } from '~/types';
import { getURL, getURLFromString } from '~/utils/dom'; import { getURL, getURLFromString } from '~/utils/dom';
import styles from './styles.module.scss'; import styles from './styles.module.scss';
type IProps = { type NodeThumbnailProps = {
item: Partial<INode>; item: {
thumbnail?: string;
title?: string;
is_promoted?: boolean;
id?: number;
};
}; };
type CellSize = 'small' | 'medium' | 'large'; type CellSize = 'small' | 'medium' | 'large';
@ -33,7 +37,7 @@ const getTitleLetters = (title?: string): string => {
: words[0].substr(0, 2).toUpperCase(); : words[0].substr(0, 2).toUpperCase();
}; };
const NodeRelatedItem: FC<IProps> = memo(({ item }) => { const NodeThumbnail: FC<NodeThumbnailProps> = memo(({ item }) => {
const onClick = useGotoNode(item.id); const onClick = useGotoNode(item.id);
const [is_loaded, setIsLoaded] = useState(false); const [is_loaded, setIsLoaded] = useState(false);
const [width, setWidth] = useState(0); const [width, setWidth] = useState(0);
@ -118,4 +122,4 @@ const NodeRelatedItem: FC<IProps> = memo(({ item }) => {
); );
}); });
export { NodeRelatedItem }; export { NodeThumbnail };

View file

@ -1,6 +1,7 @@
import { FC } from 'react'; import { FC } from 'react';
import { Anchor } from '~/components/common/Anchor'; import { Anchor } from '~/components/common/Anchor';
import { Avatar } from '~/components/common/Avatar';
import { InlineUsername } from '~/components/common/InlineUsername'; import { InlineUsername } from '~/components/common/InlineUsername';
import { Square } from '~/components/common/Square'; import { Square } from '~/components/common/Square';
import { NotificationItem } from '~/types/notifications'; import { NotificationItem } from '~/types/notifications';
@ -16,8 +17,10 @@ const NotificationComment: FC<NotificationCommentProps> = ({ item }) => (
<Anchor href={item.url} className={styles.link}> <Anchor href={item.url} className={styles.link}>
<div className={styles.message}> <div className={styles.message}>
<div className={styles.icon}> <div className={styles.icon}>
<Square <Avatar
image={getURLFromString(item.user.photo, 'avatar')} size={32}
url={item.user?.photo}
username={item.user?.username}
className={styles.circle} className={styles.circle}
/> />
</div> </div>
@ -25,9 +28,17 @@ const NotificationComment: FC<NotificationCommentProps> = ({ item }) => (
<div className={styles.content}> <div className={styles.content}>
<b className={styles.title}> <b className={styles.title}>
<span> <span>
<InlineUsername>{item.user.username}</InlineUsername>: <InlineUsername>{item.user.username}</InlineUsername>
</span> </span>
<span>-</span>
<Square
className={styles.item_image}
size={16}
image={getURLFromString(item.thumbnail, 'avatar')}
/>
<div className={styles.item_title}>{item.title}</div>
</b> </b>
<div className={styles.text}> <div className={styles.text}>
<div <div
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{

View file

@ -8,7 +8,6 @@
.message { .message {
font: $font_14_regular; font: $font_14_regular;
line-height: 1.3em; line-height: 1.3em;
padding: $gap/2 $gap/2 $gap/4 $gap/2;
min-height: calc(1.3em * 3 + $gap); min-height: calc(1.3em * 3 + $gap);
display: grid; display: grid;
grid-template-columns: 32px auto; grid-template-columns: 32px auto;
@ -18,15 +17,16 @@
.content { .content {
background: $content_bg; background: $content_bg;
padding: 5px; padding: 5px;
border-radius: 0 4px 4px 4px; border-radius: 0 $radius $radius $radius;
position: relative; position: relative;
min-width: 0;
&:before { &:before {
content: ' '; content: ' ';
position: absolute; position: absolute;
top: $gap; top: 8px;
right: 100%; right: 100%;
@include arrow_left(10px, $content_bg); @include arrow_left(8px, $content_bg);
} }
} }
@ -36,8 +36,28 @@
} }
.title { .title {
font: $font_14_semibold; font: $font_14_medium;
margin-bottom: $gap / 2; margin-bottom: $gap / 2;
display: flex;
flex-direction: row;
align-items: center;
& > * {
padding-right: 5px;
}
}
.item_title {
flex: 1;
padding-left: 5px;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
.item_image {
flex: 0 0 16px;
border-radius: 2px;
} }
.time { .time {
@ -47,6 +67,6 @@
color: $gray_75; color: $gray_75;
} }
.circle { div.circle {
border-radius: 4px 0 0 4px; border-radius: 4px 0 0 4px;
} }

View file

@ -0,0 +1,39 @@
import { FC, useMemo } from 'react';
import { NodeThumbnail } from '~/components/node/NodeThumbnail';
import { NotificationItem } from '~/types/notifications';
import { getPrettyDate } from '~/utils/dom';
import styles from './styles.module.scss';
interface NotificationNodeProps {
item: NotificationItem;
}
const NotificationNode: FC<NotificationNodeProps> = ({ item }) => {
const thumbnail = useMemo(
() => ({
title: item.title,
thumbnail: item.thumbnail,
is_promoted: true,
}),
[item],
);
return (
<div className={styles.card}>
<div className={styles.image}>
<NodeThumbnail item={thumbnail} />
</div>
<div className={styles.text}>
<div className={styles.title}>{item.title || '...'}</div>
<div className={styles.user}>
~{item.user.username}, {getPrettyDate(item.created_at)}
</div>
</div>
</div>
);
};
export { NotificationNode };

View file

@ -0,0 +1,36 @@
@import 'src/styles/variables';
.card {
background-color: $content_bg;
display: flex;
flex-direction: row;
border-radius: $radius;
justify-content: center;
align-items: center;
padding: $gap/2;
}
.text {
flex: 1;
min-width: 0;
padding-left: $gap;
margin-top: -0.2em;
}
.title {
font-size: 1.2em;
font-weight: bold;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
text-transform: capitalize;
}
.user {
font-size: 0.7em;
color: $gray_50;
}
.image {
flex: 0 0 48px;
}

View file

@ -1,4 +1,4 @@
import { FC, useEffect } from 'react'; import { FC, useCallback, useEffect } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
@ -6,7 +6,9 @@ import { Button } from '~/components/input/Button';
import { InputRow } from '~/components/input/InputRow'; import { InputRow } from '~/components/input/InputRow';
import { LoaderScreen } from '~/components/input/LoaderScreen'; import { LoaderScreen } from '~/components/input/LoaderScreen';
import { NotificationComment } from '~/components/notifications/NotificationComment'; import { NotificationComment } from '~/components/notifications/NotificationComment';
import { NotificationNode } from '~/components/notifications/NotificationNode';
import { useNotificationsList } from '~/hooks/notifications/useNotificationsList'; import { useNotificationsList } from '~/hooks/notifications/useNotificationsList';
import { NotificationItem, NotificationType } from '~/types/notifications';
import { useNotifications } from '~/utils/providers/NotificationProvider'; import { useNotifications } from '~/utils/providers/NotificationProvider';
import styles from './styles.module.scss'; import styles from './styles.module.scss';
@ -22,6 +24,17 @@ const NotificationList: FC<NotificationListProps> = () => {
return () => markAsRead(); return () => markAsRead();
}, []); }, []);
const renderItem = useCallback((item: NotificationItem) => {
switch (item.type) {
case NotificationType.Comment:
return <NotificationComment item={item} />;
case NotificationType.Node:
return <NotificationNode item={item} />;
default:
return null;
}
}, []);
if (isLoading) { if (isLoading) {
return <LoaderScreen align="top" />; return <LoaderScreen align="top" />;
} }
@ -46,7 +59,7 @@ const NotificationList: FC<NotificationListProps> = () => {
<div className={styles.items}> <div className={styles.items}>
{items?.map((item) => ( {items?.map((item) => (
<div className={styles.item} key={item.created_at}> <div className={styles.item} key={item.created_at}>
<NotificationComment item={item} /> {renderItem(item)}
</div> </div>
))} ))}
</div> </div>

View file

@ -47,8 +47,9 @@
.item { .item {
@include row_shadow; @include row_shadow;
padding-right: $gap; padding: $gap / 2 $gap $gap / 2 $gap / 2;
transition: background-color 0.25s; transition: background-color 0.25s;
min-width: 0;
&:hover { &:hover {
background-color: $content_bg_lighter; background-color: $content_bg_lighter;

View file

@ -2,10 +2,15 @@ import { useCallback } from 'react';
import { URLS } from '~/constants/urls'; import { URLS } from '~/constants/urls';
import { useNavigation } from '~/hooks/navigation/useNavigation'; import { useNavigation } from '~/hooks/navigation/useNavigation';
import { INode } from '~/types';
// useGotoNode returns fn, that navigates to node // useGotoNode returns fn, that navigates to node
export const useGotoNode = (id: INode['id']) => { export const useGotoNode = (id?: number) => {
const { push } = useNavigation(); const { push } = useNavigation();
return useCallback(() => push(URLS.NODE_URL(id)), [push, id]); return useCallback(() => {
if (!id) {
return;
}
push(URLS.NODE_URL(id));
}, [push, id]);
}; };

View file

@ -4,6 +4,7 @@ export interface NotificationItem {
id: number; id: number;
url: string; url: string;
type: NotificationType; type: NotificationType;
title: string;
text: string; text: string;
user: ShallowUser; user: ShallowUser;
thumbnail: string; thumbnail: string;