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

notifications: added boris notifications

This commit is contained in:
Fedor Katurov 2023-03-18 16:09:53 +06:00
parent d56bfe968d
commit 2ecda12513
13 changed files with 61 additions and 22 deletions

View file

@ -3,6 +3,7 @@ import { NotificationItem } from '~/types/notifications';
export interface ApiGetNotificationSettingsResponse { export interface ApiGetNotificationSettingsResponse {
enabled: boolean; enabled: boolean;
flow: boolean; flow: boolean;
boris: boolean;
comments: boolean; comments: boolean;
send_telegram: boolean; send_telegram: boolean;
show_indicator: boolean; show_indicator: boolean;

View file

@ -1,5 +1,7 @@
import { FC } from 'react'; import { FC } from 'react';
import classNames from 'classnames';
import { Anchor } from '~/components/common/Anchor'; import { Anchor } from '~/components/common/Anchor';
import { Avatar } from '~/components/common/Avatar'; import { Avatar } from '~/components/common/Avatar';
import { InlineUsername } from '~/components/common/InlineUsername'; import { InlineUsername } from '~/components/common/InlineUsername';
@ -11,11 +13,12 @@ import styles from './styles.module.scss';
interface NotificationCommentProps { interface NotificationCommentProps {
item: NotificationItem; item: NotificationItem;
isNew?: boolean;
} }
const NotificationComment: FC<NotificationCommentProps> = ({ item }) => ( const NotificationComment: FC<NotificationCommentProps> = ({ item, isNew }) => (
<Anchor href={item.url} className={styles.link}> <Anchor href={item.url} className={styles.link}>
<div className={styles.message}> <div className={classNames(styles.message, { [styles.new]: isNew })}>
<div className={styles.icon}> <div className={styles.icon}>
<Avatar <Avatar
size={32} size={32}
@ -31,11 +34,13 @@ const NotificationComment: FC<NotificationCommentProps> = ({ item }) => (
<InlineUsername>{item.user.username}</InlineUsername> <InlineUsername>{item.user.username}</InlineUsername>
</span> </span>
<span>-</span> <span>-</span>
{!!item.thumbnail && (
<Square <Square
className={styles.item_image} className={styles.item_image}
size={16} size={16}
image={getURLFromString(item.thumbnail, 'avatar')} image={getURLFromString(item.thumbnail, 'avatar')}
/> />
)}
<div className={styles.item_title}>{item.title}</div> <div className={styles.item_title}>{item.title}</div>
</b> </b>

View file

@ -12,6 +12,18 @@
display: grid; display: grid;
grid-template-columns: 32px auto; grid-template-columns: 32px auto;
column-gap: 0; column-gap: 0;
position: relative;
&.new::after {
content: ' ';
position: absolute;
top: $gap;
right: $gap;
background: $color_danger;
width: 8px;
height: 8px;
border-radius: 4px;
}
} }
.content { .content {
@ -49,7 +61,6 @@
.item_title { .item_title {
flex: 1; flex: 1;
padding-left: 5px;
text-overflow: ellipsis; text-overflow: ellipsis;
overflow: hidden; overflow: hidden;
white-space: nowrap; white-space: nowrap;
@ -58,6 +69,7 @@
.item_image { .item_image {
flex: 0 0 16px; flex: 0 0 16px;
border-radius: 2px; border-radius: 2px;
margin-right: 5px;
} }
.time { .time {

View file

@ -1,5 +1,7 @@
import { FC, useMemo } from 'react'; import { FC, useMemo } from 'react';
import classNames from 'classnames';
import { NodeThumbnail } from '~/components/node/NodeThumbnail'; import { NodeThumbnail } from '~/components/node/NodeThumbnail';
import { NotificationItem } from '~/types/notifications'; import { NotificationItem } from '~/types/notifications';
import { getPrettyDate } from '~/utils/dom'; import { getPrettyDate } from '~/utils/dom';
@ -8,9 +10,10 @@ import styles from './styles.module.scss';
interface NotificationNodeProps { interface NotificationNodeProps {
item: NotificationItem; item: NotificationItem;
isNew?: boolean;
} }
const NotificationNode: FC<NotificationNodeProps> = ({ item }) => { const NotificationNode: FC<NotificationNodeProps> = ({ item, isNew }) => {
const thumbnail = useMemo( const thumbnail = useMemo(
() => ({ () => ({
title: item.title, title: item.title,
@ -21,7 +24,7 @@ const NotificationNode: FC<NotificationNodeProps> = ({ item }) => {
); );
return ( return (
<div className={styles.card}> <div className={classNames(styles.card, { [styles.new]: isNew })}>
<div className={styles.image}> <div className={styles.image}>
<NodeThumbnail item={thumbnail} /> <NodeThumbnail item={thumbnail} />
</div> </div>

View file

@ -8,6 +8,17 @@
justify-content: center; justify-content: center;
align-items: center; align-items: center;
padding: $gap/2; padding: $gap/2;
&.new::after {
content: ' ';
position: absolute;
top: $gap;
right: $gap;
background: $color_danger;
width: 8px;
height: 8px;
border-radius: 4px;
}
} }
.text { .text {

View file

@ -9,8 +9,6 @@ import { Toggle } from '~/components/input/Toggle';
import { useNotificationSettingsForm } from '~/hooks/notifications/useNotificationSettingsForm'; import { useNotificationSettingsForm } from '~/hooks/notifications/useNotificationSettingsForm';
import { NotificationSettings } from '~/types/notifications'; import { NotificationSettings } from '~/types/notifications';
import styles from './styles.module.scss';
interface NotificationSettingsFormProps { interface NotificationSettingsFormProps {
value: NotificationSettings; value: NotificationSettings;
onSubmit: (val: Partial<NotificationSettings>) => Promise<unknown>; onSubmit: (val: Partial<NotificationSettings>) => Promise<unknown>;
@ -65,6 +63,8 @@ const NotificationSettingsForm: FC<NotificationSettingsFormProps> = ({
<InputRow input={toggle('comments', !values.enabled)}> <InputRow input={toggle('comments', !values.enabled)}>
Комментарии Комментарии
</InputRow> </InputRow>
<InputRow input={toggle('boris', !values.enabled)}>Борис</InputRow>
</Group> </Group>
</Zone> </Zone>

View file

@ -1,7 +0,0 @@
@import 'src/styles/variables';
.grid {
display: grid;
grid-auto-flow: row;
row-gap: $gap;
}

View file

@ -1,6 +1,7 @@
import { FC, useCallback, useEffect } from 'react'; import { FC, useCallback, useEffect } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { isAfter, parseISO } from 'date-fns';
import { Button } from '~/components/input/Button'; import { Button } from '~/components/input/Button';
import { InputRow } from '~/components/input/InputRow'; import { InputRow } from '~/components/input/InputRow';
@ -17,7 +18,7 @@ interface NotificationListProps {}
const NotificationList: FC<NotificationListProps> = () => { const NotificationList: FC<NotificationListProps> = () => {
const { isLoading, items } = useNotificationsList(); const { isLoading, items } = useNotificationsList();
const { enabled, toggleEnabled } = useNotifications(); const { enabled, toggleEnabled, lastSeen } = useNotifications();
const { markAsRead } = useNotifications(); const { markAsRead } = useNotifications();
useEffect(() => { useEffect(() => {
@ -25,11 +26,16 @@ const NotificationList: FC<NotificationListProps> = () => {
}, []); }, []);
const renderItem = useCallback((item: NotificationItem) => { const renderItem = useCallback((item: NotificationItem) => {
const isNew =
!lastSeen ||
!item.created_at ||
isAfter(parseISO(item.created_at), lastSeen);
switch (item.type) { switch (item.type) {
case NotificationType.Comment: case NotificationType.Comment:
return <NotificationComment item={item} />; case NotificationType.Boris:
return <NotificationComment item={item} isNew={isNew} />;
case NotificationType.Node: case NotificationType.Node:
return <NotificationNode item={item} />; return <NotificationNode item={item} isNew={isNew} />;
default: default:
return null; return null;
} }

View file

@ -54,5 +54,7 @@ export const useNotificationSettings = () => {
update, update,
loading: isLoading, loading: isLoading,
toggleEnabled, toggleEnabled,
lastSeen,
lastDate,
}; };
}; };

View file

@ -10,6 +10,7 @@ import { useFormAutoSubmit } from '../useFormAutosubmit';
const validationSchema = object({ const validationSchema = object({
enabled: boolean().default(false), enabled: boolean().default(false),
flow: boolean().default(false), flow: boolean().default(false),
boris: boolean().default(false),
comments: boolean().default(false), comments: boolean().default(false),
sendTelegram: boolean().default(false), sendTelegram: boolean().default(false),
showIndicator: boolean().default(false), showIndicator: boolean().default(false),

View file

@ -14,11 +14,13 @@ export interface NotificationItem {
export enum NotificationType { export enum NotificationType {
Node = 'node', Node = 'node',
Comment = 'comment', Comment = 'comment',
Boris = 'boris',
} }
export interface NotificationSettings { export interface NotificationSettings {
enabled: boolean; enabled: boolean;
flow: boolean; flow: boolean;
boris: boolean;
comments: boolean; comments: boolean;
sendTelegram: boolean; sendTelegram: boolean;
showIndicator: boolean; showIndicator: boolean;

View file

@ -8,6 +8,7 @@ export const notificationSettingsFromRequest = (
): NotificationSettings => ({ ): NotificationSettings => ({
enabled: req.enabled, enabled: req.enabled,
flow: req.flow, flow: req.flow,
boris: req.boris,
comments: req.comments, comments: req.comments,
sendTelegram: req.send_telegram, sendTelegram: req.send_telegram,
showIndicator: req.show_indicator, showIndicator: req.show_indicator,
@ -20,6 +21,7 @@ export const notificationSettingsToRequest = (
): ApiUpdateNotificationSettingsRequest => ({ ): ApiUpdateNotificationSettingsRequest => ({
enabled: req.enabled, enabled: req.enabled,
flow: req.flow, flow: req.flow,
boris: req.boris,
comments: req.comments, comments: req.comments,
send_telegram: req.sendTelegram, send_telegram: req.sendTelegram,
show_indicator: req.showIndicator, show_indicator: req.showIndicator,

View file

@ -14,6 +14,7 @@ const defaultValue = {
toggleEnabled: () => {}, toggleEnabled: () => {},
markAsRead: () => {}, markAsRead: () => {},
refresh: () => Promise.resolve() as Promise<unknown>, refresh: () => Promise.resolve() as Promise<unknown>,
lastSeen: null as Date | null | undefined,
}; };
const NotificationContext = createContext(defaultValue); const NotificationContext = createContext(defaultValue);