From c7065341b4d142b88ca61801192f958e104a41c2 Mon Sep 17 00:00:00 2001
From: Maria Rushkova <66270386+mrushkova@users.noreply.github.com>
Date: Wed, 8 Dec 2021 10:08:24 +0100
Subject: [PATCH] Add activity center (#142)
---
.../src/components/ActivityCenter.tsx | 427 ++++++++++++++++++
.../src/components/Buttons/buttonStyle.tsx | 1 +
.../src/components/Channels/Channel.tsx | 1 +
.../components/Chat/ChatMessageContent.tsx | 5 +-
.../src/components/Chat/ChatMessages.tsx | 73 ++-
.../src/components/Chat/ChatTopbar.tsx | 113 +++--
.../src/components/Icons/ActivityIcon.tsx | 25 +
.../src/components/Icons/CheckIcon.tsx | 17 +-
.../src/components/Icons/ClearIcon.tsx | 14 +-
.../src/components/Icons/HideIcon.tsx | 24 +
.../react-chat/src/components/Icons/Icon.ts | 16 +
.../src/components/Icons/ReadIcon.tsx | 43 ++
.../components/Icons/ReplyActivityIcon.tsx | 19 +
.../src/components/Icons/ShowIcon.tsx | 27 ++
.../src/components/Members/Member.tsx | 2 +-
.../src/components/Modals/ProfileModal.tsx | 19 +-
.../react-chat/src/components/ReactChat.tsx | 13 +-
.../src/contexts/activityProvider.tsx | 29 ++
packages/react-chat/src/models/Activity.ts | 20 +
packages/react-chat/src/styles/themes.ts | 3 +
20 files changed, 809 insertions(+), 82 deletions(-)
create mode 100644 packages/react-chat/src/components/ActivityCenter.tsx
create mode 100644 packages/react-chat/src/components/Icons/ActivityIcon.tsx
create mode 100644 packages/react-chat/src/components/Icons/HideIcon.tsx
create mode 100644 packages/react-chat/src/components/Icons/Icon.ts
create mode 100644 packages/react-chat/src/components/Icons/ReadIcon.tsx
create mode 100644 packages/react-chat/src/components/Icons/ReplyActivityIcon.tsx
create mode 100644 packages/react-chat/src/components/Icons/ShowIcon.tsx
create mode 100644 packages/react-chat/src/contexts/activityProvider.tsx
create mode 100644 packages/react-chat/src/models/Activity.ts
diff --git a/packages/react-chat/src/components/ActivityCenter.tsx b/packages/react-chat/src/components/ActivityCenter.tsx
new file mode 100644
index 0000000..7d73d1e
--- /dev/null
+++ b/packages/react-chat/src/components/ActivityCenter.tsx
@@ -0,0 +1,427 @@
+import React, { useMemo, useRef, useState } from "react";
+import styled from "styled-components";
+
+import { useActivities } from "../contexts/activityProvider";
+import { useMessengerContext } from "../contexts/messengerProvider";
+import { useClickOutside } from "../hooks/useClickOutside";
+import { Activity } from "../models/Activity";
+import { equalDate } from "../utils/equalDate";
+
+import { buttonStyles } from "./Buttons/buttonStyle";
+import {
+ ContentWrapper,
+ DateSeparator,
+ MessageHeaderWrapper,
+ MessageOuterWrapper,
+ MessageText,
+ TimeWrapper,
+ UserAddress,
+ UserName,
+ UserNameWrapper,
+} from "./Chat/ChatMessages";
+import { ContactMenu } from "./Form/ContactMenu";
+import { CheckSvg } from "./Icons/CheckIcon";
+import { ClearSvg } from "./Icons/ClearIcon";
+import { GroupIcon } from "./Icons/GroupIcon";
+import { HideIcon } from "./Icons/HideIcon";
+import { Icon } from "./Icons/Icon";
+import { MoreIcon } from "./Icons/MoreIcon";
+import { ReadIcon } from "./Icons/ReadIcon";
+import { ReplyIcon } from "./Icons/ReplyActivityIcon";
+import { ShowIcon } from "./Icons/ShowIcon";
+import { UntrustworthIcon } from "./Icons/UntrustworthIcon";
+import { UserIcon } from "./Icons/UserIcon";
+import { textMediumStyles, textSmallStyles } from "./Text";
+
+const today = new Date();
+
+type ActivityMessageProps = {
+ activity: Activity;
+};
+
+function ActivityMessage({ activity }: ActivityMessageProps) {
+ const { contacts } = useMessengerContext();
+
+ const [showMenu, setShowMenu] = useState(false);
+
+ const type = activity.type;
+
+ const contact = useMemo(
+ () => contacts[activity.user],
+ [activity.user, contacts]
+ );
+
+ return (
+
+
+ {equalDate(activity.date, today)
+ ? "Today"
+ : activity.date.toLocaleDateString()}
+
+
+
+ <>
+
+
+
+
+
+
+
+
+ {" "}
+ {contact.customName ?? activity.user.slice(0, 10)}
+
+ {contact.customName && (
+
+ {activity.user.slice(0, 5)}...{activity.user.slice(-3)}
+
+ )}
+ {contact.isUntrustworthy && }
+
+
+ {activity.date.toLocaleString("en-US", {
+ hour: "numeric",
+ minute: "numeric",
+ hour12: true,
+ })}
+
+
+ {type === "request" && (
+
+ Contact request
+ {activity.requestType === "outcome"
+ ? ` to ${activity.user.slice(0, 10)}`
+ : ": "}
+
+ )}
+
+ {activity.message?.content ||
+ (activity.requestType === "income" && activity.request)}
+
+ {type === "mention" &&
+ activity.channel &&
+ activity.channel.type !== "dm" && (
+
+ {activity.channel.type === "group" ? : "#"}{" "}
+ {` ${activity.channel.name.slice(0, 10)}`}
+
+ )}
+ {type === "reply" && activity.quote && (
+
+ {activity.quote.image && (
+ Posted an image in
+ )}
+
+ {activity.quote.content}
+
+
+ )}
+
+ >
+ {type === "request" &&
+ !activity.status &&
+ activity.requestType === "income" && (
+ <>
+ {
+ activity.isRead = true;
+ activity.status = "accepted";
+ }}
+ className="accept"
+ >
+
+
+ {
+ activity.isRead = true;
+ activity.status = "declined";
+ }}
+ className="decline"
+ >
+
+
+ {
+ setShowMenu((e) => !e);
+ }}
+ >
+ {showMenu && }
+
+
+ >
+ )}
+ {type === "request" && activity.status === "accepted" && (
+ Accepted
+ )}
+ {type === "request" && activity.status === "declined" && (
+ Declined
+ )}
+ {type === "request" && activity.status === "sent" && (
+ Sent
+ )}
+ {type !== "request" && (
+ {
+ activity.isRead = true;
+ }}
+ className={`${activity.isRead && "read"}`}
+ >
+
+
+ )}
+
+
+ );
+}
+
+interface ActivityCenterProps {
+ setShowActivityCenter: (val: boolean) => void;
+}
+
+export function ActivityCenter({ setShowActivityCenter }: ActivityCenterProps) {
+ const { activities } = useActivities();
+ const { contacts } = useMessengerContext();
+
+ const ref = useRef(null);
+ useClickOutside(ref, () => setShowActivityCenter(false));
+
+ const shownActivities = useMemo(
+ () =>
+ activities.filter(
+ (activity) => !contacts?.[activity.user]?.blocked ?? true
+ ),
+ [contacts, activities, activities.length]
+ );
+
+ const [hideRead, setHideRead] = useState(false);
+
+ const [filter, setFilter] = useState("");
+
+ const filteredActivities = shownActivities.filter((activity) =>
+ filter
+ ? activity.type === filter
+ : hideRead
+ ? activity.isRead !== true
+ : activity
+ );
+
+ return (
+
+
+
+ setFilter("")}>All
+ setFilter("mention")}>Mentions
+ setFilter("reply")}>Replies
+ setFilter("request")}>
+ Contact requests
+
+
+
+ {
+ shownActivities.map((activity) => (activity.isRead = true));
+ }}
+ >
+
+
+ setHideRead(!hideRead)}>
+ {hideRead ? : }
+
+
+
+ {filteredActivities.length > 0 ? (
+
+ {filteredActivities.map((activity) => (
+
+ ))}
+
+ ) : (
+ Notifications will appear here
+ )}
+
+ );
+}
+
+const ActivityBlock = styled.div`
+ width: 600px;
+ height: 770px;
+ display: flex;
+ flex-direction: column;
+ background: ${({ theme }) => theme.bodyBackgroundColor};
+ box-shadow: 0px 12px 24px rgba(0, 34, 51, 0.1);
+ border-radius: 8px;
+ position: absolute;
+ top: 48px;
+ right: 8px;
+ z-index: 100;
+`;
+
+const ActivityFilter = styled.div`
+ display: flex;
+ justify-content: space-between;
+ padding: 13px 16px;
+`;
+
+const Filters = styled.div`
+ display: flex;
+`;
+
+const FilterBtn = styled.button`
+ ${buttonStyles}
+ ${textSmallStyles}
+
+ padding: 10px 12px;
+ background: ${({ theme }) => theme.bodyBackgroundColor};
+
+ & + & {
+ margin-left: 8px;
+ }
+
+ &:hover {
+ background: ${({ theme }) => theme.buttonBgHover};
+ }
+
+ &:focus {
+ background: ${({ theme }) => theme.buttonBg};
+ }
+`;
+
+const ActivityBtn = styled.button`
+ width: 32px;
+ height: 32px;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ border-radius: 8px;
+ align-self: center;
+
+ &:hover {
+ background: ${({ theme }) => theme.buttonBgHover};
+ }
+
+ &.read {
+ &:hover {
+ background: ${({ theme }) => theme.bodyBackgroundColor};
+ }
+ }
+
+ &.accept {
+ &:hover {
+ background: rgba(78, 188, 96, 0.1);
+ }
+ }
+
+ &.decline {
+ &:hover {
+ background: rgba(255, 45, 85, 0.1);
+ }
+ }
+
+ & + & {
+ margin-left: 8px;
+ }
+`;
+
+const Activities = styled.div`
+ display: flex;
+ flex-direction: column;
+ width: 100%;
+ overflow: auto;
+`;
+
+const EmptyActivities = styled.div`
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ flex: 1;
+ width: 100%;
+ color: ${({ theme }) => theme.secondary};
+`;
+
+const ActivityDate = styled(DateSeparator)`
+ justify-content: flex-start;
+ padding: 8px 16px;
+ margin: 0;
+`;
+
+const MessageWrapper = styled.div`
+ width: 100%;
+ display: flex;
+ align-items: flex-start;
+ padding: 8px 16px;
+
+ &.unread {
+ background: ${({ theme }) => theme.buttonBgHover};
+ }
+`;
+
+const ActivityText = styled(MessageText)`
+ white-space: unset;
+ margin-bottom: 8px;
+`;
+
+const Tag = styled.div`
+ width: fit-content;
+ max-width: 200px;
+ display: flex;
+ align-items: center;
+
+ border: 1px solid ${({ theme }) => theme.secondary};
+ border-radius: 11px;
+ padding: 0 6px;
+ cursor: pointer;
+
+ font-weight: 500;
+ color: ${({ theme }) => theme.secondary};
+ ${textSmallStyles}
+
+ & > span {
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ overflow: hidden;
+ }
+`;
+
+const ContextHeading = styled.p`
+ font-style: italic;
+ color: ${({ theme }) => theme.secondary};
+ flex-shrink: 0;
+ ${textMediumStyles}
+`;
+
+const RequestStatus = styled.p`
+ font-weight: 500;
+ align-self: center;
+ text-align: end;
+ color: ${({ theme }) => theme.secondary};
+ ${textSmallStyles}
+
+ &.accepted {
+ color: ${({ theme }) => theme.greenColor};
+ }
+
+ &.declined {
+ color: ${({ theme }) => theme.redColor};
+ }
+`;
+
+const ActivityContent = styled(ContentWrapper)`
+ max-width: calc(100% - 80px);
+ flex: 1;
+`;
+
+const Btns = styled.div`
+ display: flex;
+ align-items: center;
+`;
+
+const ReplyWrapper = styled.div`
+ max-width: 100%;
+ display: flex;
+ align-items: center;
+
+ & > p {
+ margin-right: 4px;
+ }
+`;
diff --git a/packages/react-chat/src/components/Buttons/buttonStyle.tsx b/packages/react-chat/src/components/Buttons/buttonStyle.tsx
index cf461a4..70a7157 100644
--- a/packages/react-chat/src/components/Buttons/buttonStyle.tsx
+++ b/packages/react-chat/src/components/Buttons/buttonStyle.tsx
@@ -2,6 +2,7 @@ import { css } from "styled-components";
export const buttonStyles = css`
border-radius: 8px;
+ font-family: "Inter";
font-weight: 500;
font-size: 15px;
line-height: 22px;
diff --git a/packages/react-chat/src/components/Channels/Channel.tsx b/packages/react-chat/src/components/Channels/Channel.tsx
index 09b6ae5..4193f18 100644
--- a/packages/react-chat/src/components/Channels/Channel.tsx
+++ b/packages/react-chat/src/components/Channels/Channel.tsx
@@ -188,6 +188,7 @@ const NotificationBagde = styled.div`
border-radius: 50%;
font-size: 12px;
line-height: 16px;
+ font-weight: 500;
background-color: ${({ theme }) => theme.notificationColor};
color: ${({ theme }) => theme.bodyBackgroundColor};
display: flex;
diff --git a/packages/react-chat/src/components/Chat/ChatMessageContent.tsx b/packages/react-chat/src/components/Chat/ChatMessageContent.tsx
index 2937002..0dd8676 100644
--- a/packages/react-chat/src/components/Chat/ChatMessageContent.tsx
+++ b/packages/react-chat/src/components/Chat/ChatMessageContent.tsx
@@ -24,7 +24,10 @@ function Mention({ id, setMentioned }: MentionProps) {
const identity = useIdentity();
if (!contact) return <>{id}>;
- if (contact.id === utils.bufToHex(identity.publicKey)) setMentioned(true);
+
+ useEffect(() => {
+ if (contact.id === utils.bufToHex(identity.publicKey)) setMentioned(true);
+ }, [contact.id]);
return (
setShowMenu(!showMenu)}>
diff --git a/packages/react-chat/src/components/Chat/ChatMessages.tsx b/packages/react-chat/src/components/Chat/ChatMessages.tsx
index d3ce6b6..3145300 100644
--- a/packages/react-chat/src/components/Chat/ChatMessages.tsx
+++ b/packages/react-chat/src/components/Chat/ChatMessages.tsx
@@ -1,14 +1,19 @@
import React, { useEffect, useMemo, useRef, useState } from "react";
+import { utils } from "status-communities/dist/cjs";
import styled from "styled-components";
+import { useActivities } from "../../contexts/activityProvider";
+import { useIdentity } from "../../contexts/identityProvider";
import { useMessengerContext } from "../../contexts/messengerProvider";
import { useModal } from "../../contexts/modalProvider";
import { useChatScrollHandle } from "../../hooks/useChatScrollHandle";
import { Reply } from "../../hooks/useReply";
+import { ChannelData } from "../../models/ChannelData";
import { ChatMessage } from "../../models/ChatMessage";
import { equalDate } from "../../utils";
import { EmptyChannel } from "../Channels/EmptyChannel";
import { ContactMenu } from "../Form/ContactMenu";
+import { Icon } from "../Icons/Icon";
import { LoadingIcon } from "../Icons/LoadingIcon";
import { QuoteSvg } from "../Icons/QuoteIcon";
import { ReactionSvg } from "../Icons/ReactionIcon";
@@ -27,6 +32,7 @@ const today = new Date();
type ChatUiMessageProps = {
idx: number;
message: ChatMessage;
+ channel: ChannelData;
prevMessage: ChatMessage;
setImage: (img: string) => void;
setLink: (link: string) => void;
@@ -36,6 +42,7 @@ type ChatUiMessageProps = {
function ChatUiMessage({
message,
+ channel,
idx,
prevMessage,
setImage,
@@ -44,6 +51,9 @@ function ChatUiMessage({
quote,
}: ChatUiMessageProps) {
const { contacts } = useMessengerContext();
+ const { setActivities } = useActivities();
+ const identity = useIdentity();
+
const contact = useMemo(
() => contacts[message.sender],
[message.sender, contacts]
@@ -51,6 +61,34 @@ function ChatUiMessage({
const [showMenu, setShowMenu] = useState(false);
const [mentioned, setMentioned] = useState(false);
+ useEffect(() => {
+ if (mentioned)
+ setActivities((prev) => [
+ ...prev,
+ {
+ id: message.date.getTime().toString() + message.content,
+ type: "mention",
+ date: message.date,
+ user: message.sender,
+ message: message,
+ channel: channel,
+ },
+ ]);
+ if (quote && quote.sender === utils.bufToHex(identity.publicKey))
+ setActivities((prev) => [
+ ...prev,
+ {
+ id: message.date.getTime().toString() + message.content,
+ type: "reply",
+ date: message.date,
+ user: message.sender,
+ message: message,
+ channel: channel,
+ quote: quote,
+ },
+ ]);
+ }, [mentioned, message, quote]);
+
return (
{(idx === 0 || !equalDate(prevMessage.date, message.date)) && (
@@ -190,6 +228,7 @@ export function ChatMessages({ setReply }: ChatMessagesProps) {
theme.tertiary};
margin-right: 4px;
@@ -331,7 +356,7 @@ export const UserAddress = styled.p`
}
`;
-const TimeWrapper = styled.div`
+export const TimeWrapper = styled.div`
font-size: 10px;
line-height: 14px;
letter-spacing: 0.2px;
@@ -340,7 +365,7 @@ const TimeWrapper = styled.div`
margin-left: 4px;
`;
-const MessageText = styled.div`
+export const MessageText = styled.div`
overflow-wrap: anywhere;
width: 100%;
white-space: pre-wrap;
diff --git a/packages/react-chat/src/components/Chat/ChatTopbar.tsx b/packages/react-chat/src/components/Chat/ChatTopbar.tsx
index 910110f..8f9b72b 100644
--- a/packages/react-chat/src/components/Chat/ChatTopbar.tsx
+++ b/packages/react-chat/src/components/Chat/ChatTopbar.tsx
@@ -1,11 +1,14 @@
import React, { useState } from "react";
import styled from "styled-components";
+import { useActivities } from "../../contexts/activityProvider";
import { useMessengerContext } from "../../contexts/messengerProvider";
import { useNarrow } from "../../contexts/narrowProvider";
+import { ActivityCenter } from "../ActivityCenter";
import { Channel } from "../Channels/Channel";
import { Community } from "../Community";
import { ChannelMenu } from "../Form/ChannelMenu";
+import { ActivityIcon } from "../Icons/ActivityIcon";
import { MembersIcon } from "../Icons/MembersIcon";
import { MoreIcon } from "../Icons/MoreIcon";
import { CommunitySkeleton } from "../Skeleton/CommunitySkeleton";
@@ -31,8 +34,10 @@ export function ChatTopbar({
setEditGroup,
}: ChatTopbarProps) {
const { messenger, activeChannel, communityData } = useMessengerContext();
+ const { activities } = useActivities();
const narrow = useNarrow();
const [showChannelMenu, setShowChannelMenu] = useState(false);
+ const [showActivityCenter, setShowActivityCenter] = useState(false);
return (
{!narrow && (
-
+
-
+
)}
- setShowChannelMenu(!showChannelMenu)}>
+ setShowChannelMenu(!showChannelMenu)}>
-
+
+
+ setShowActivityCenter(!showActivityCenter)}
+ className="activity"
+ >
+
+ {activities.length > 0 && (
+ {activities.length}
+ )}
+
+
{!messenger && !communityData && }
{showChannelMenu && (
@@ -80,10 +96,26 @@ export function ChatTopbar({
setEditGroup={setEditGroup}
/>
)}
+ {showActivityCenter && (
+
+ )}
);
}
+const Topbar = styled.div`
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 5px 8px;
+ background: ${({ theme }) => theme.bodyBackgroundColor};
+ position: relative;
+
+ &.narrow {
+ width: 100%;
+ }
+`;
+
const ChannelWrapper = styled.div`
display: flex;
align-items: center;
@@ -98,19 +130,6 @@ const SkeletonWrapper = styled.div`
padding: 8px;
`;
-const Topbar = styled.div`
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 5px 8px;
- background: ${({ theme }) => theme.bodyBackgroundColor};
- position: relative;
-
- &.narrow {
- width: 100%;
- }
-`;
-
const CommunityWrap = styled.div`
padding-right: 10px;
margin-right: 16px;
@@ -139,35 +158,61 @@ const MenuWrapper = styled.div`
align-items: center;
`;
-const MemberBtn = styled.button`
+const ActivityWrapper = styled.div`
+ padding-left: 10px;
+ margin-left: 10px;
+ position: relative;
+
+ &:before {
+ content: "";
+ position: absolute;
+ left: 0;
+ top: 50%;
+ width: 2px;
+ height: 24px;
+ transform: translateY(-50%);
+ border-radius: 1px;
+ background: ${({ theme }) => theme.primary};
+ opacity: 0.1;
+ }
+`;
+
+const TopBtn = styled.button`
width: 32px;
height: 32px;
border-radius: 8px;
padding: 0;
&:hover {
- background: ${({ theme }) => theme.border};
+ background: ${({ theme }) => theme.sectionBackgroundColor};
}
&:active,
&.active {
background: ${({ theme }) => theme.inputColor};
}
-`;
-const MoreBtn = styled.button`
- width: 32px;
- height: 32px;
- border-radius: 8px;
- padding: 0;
- margin: 0 8px;
-
- &:hover {
- background: ${({ theme }) => theme.border};
- }
-
- &:active,
- &.active {
- background: ${({ theme }) => theme.inputColor};
+ &.activity {
+ &:hover {
+ background: ${({ theme }) => theme.bodyBackgroundColor};
+ }
}
`;
+
+const NotificationBagde = styled.div`
+ width: 18px;
+ height: 18px;
+ position: absolute;
+ top: -2px;
+ right: -2px;
+ border-radius: 50%;
+ font-size: 12px;
+ line-height: 16px;
+ font-weight: 500;
+ background-color: ${({ theme }) => theme.notificationColor};
+ color: ${({ theme }) => theme.bodyBackgroundColor};
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-shrink: 0;
+`;
diff --git a/packages/react-chat/src/components/Icons/ActivityIcon.tsx b/packages/react-chat/src/components/Icons/ActivityIcon.tsx
new file mode 100644
index 0000000..68ec4f3
--- /dev/null
+++ b/packages/react-chat/src/components/Icons/ActivityIcon.tsx
@@ -0,0 +1,25 @@
+import React from "react";
+import styled from "styled-components";
+
+export const ActivityIcon = () => {
+ return (
+
+
+
+ );
+};
+
+const Icon = styled.svg`
+ fill: ${({ theme }) => theme.primary};
+`;
diff --git a/packages/react-chat/src/components/Icons/CheckIcon.tsx b/packages/react-chat/src/components/Icons/CheckIcon.tsx
index 6367008..2a8fd0e 100644
--- a/packages/react-chat/src/components/Icons/CheckIcon.tsx
+++ b/packages/react-chat/src/components/Icons/CheckIcon.tsx
@@ -9,11 +9,10 @@ type CheckSvgProps = {
export function CheckSvg({ width, height, className }: CheckSvgProps) {
return (
-
+
);
}
@@ -31,12 +30,14 @@ export const CheckIcon = () => {
return ;
};
-const Icon = styled(CheckSvg)`
- & > path {
- fill: ${({ theme }) => theme.tertiary};
- }
+const Icon = styled.svg`
+ fill: ${({ theme }) => theme.tertiary};
- &:hover > path {
+ &:hover {
fill: ${({ theme }) => theme.bodyBackgroundColor};
}
+
+ &.accept {
+ fill: ${({ theme }) => theme.greenColor};
+ }
`;
diff --git a/packages/react-chat/src/components/Icons/ClearIcon.tsx b/packages/react-chat/src/components/Icons/ClearIcon.tsx
index e31f940..c20abab 100644
--- a/packages/react-chat/src/components/Icons/ClearIcon.tsx
+++ b/packages/react-chat/src/components/Icons/ClearIcon.tsx
@@ -9,7 +9,7 @@ type ClearSvgProps = {
export function ClearSvg({ height, width, className }: ClearSvgProps) {
return (
-
-
+
);
}
@@ -30,10 +30,8 @@ export const ClearIcon = () => {
return ;
};
-const Icon = styled(ClearSvg)`
- & > path {
- fill: ${({ theme }) => theme.tertiary};
- }
+const Icon = styled.svg`
+ fill: ${({ theme }) => theme.tertiary};
&.profile {
fill: ${({ theme }) => theme.secondary};
@@ -47,7 +45,7 @@ const Icon = styled(ClearSvg)`
fill: ${({ theme }) => theme.bodyBackgroundColor};
}
- &:hover > path {
- fill: ${({ theme }) => theme.bodyBackgroundColor};
+ &.decline {
+ fill: ${({ theme }) => theme.redColor};
}
`;
diff --git a/packages/react-chat/src/components/Icons/HideIcon.tsx b/packages/react-chat/src/components/Icons/HideIcon.tsx
new file mode 100644
index 0000000..967e090
--- /dev/null
+++ b/packages/react-chat/src/components/Icons/HideIcon.tsx
@@ -0,0 +1,24 @@
+import React from "react";
+import styled from "styled-components";
+
+export const HideIcon = () => (
+
+
+
+
+
+);
+
+const Icon = styled.svg`
+ fill: ${({ theme }) => theme.tertiary};
+`;
diff --git a/packages/react-chat/src/components/Icons/Icon.ts b/packages/react-chat/src/components/Icons/Icon.ts
new file mode 100644
index 0000000..e9ff55d
--- /dev/null
+++ b/packages/react-chat/src/components/Icons/Icon.ts
@@ -0,0 +1,16 @@
+import styled from "styled-components";
+
+export const Icon = styled.div`
+ width: 40px;
+ height: 40px;
+ display: flex;
+ justify-content: center;
+ align-items: end;
+ border-radius: 50%;
+ background-color: #bcbdff;
+ background-size: contain;
+ background-position: center;
+ flex-shrink: 0;
+ position: relative;
+ cursor: pointer;
+`;
diff --git a/packages/react-chat/src/components/Icons/ReadIcon.tsx b/packages/react-chat/src/components/Icons/ReadIcon.tsx
new file mode 100644
index 0000000..5c18d16
--- /dev/null
+++ b/packages/react-chat/src/components/Icons/ReadIcon.tsx
@@ -0,0 +1,43 @@
+import React from "react";
+import styled from "styled-components";
+
+interface ReadIconProps {
+ isRead?: boolean;
+}
+
+export const ReadIcon = ({ isRead }: ReadIconProps) => {
+ return (
+
+
+
+
+
+ );
+};
+
+const Icon = styled.svg`
+ fill: ${({ theme }) => theme.tertiary};
+
+ &.read {
+ fill: ${({ theme }) => theme.secondary};
+ }
+`;
diff --git a/packages/react-chat/src/components/Icons/ReplyActivityIcon.tsx b/packages/react-chat/src/components/Icons/ReplyActivityIcon.tsx
new file mode 100644
index 0000000..815d582
--- /dev/null
+++ b/packages/react-chat/src/components/Icons/ReplyActivityIcon.tsx
@@ -0,0 +1,19 @@
+import React from "react";
+import styled from "styled-components";
+
+export const ReplyIcon = () => (
+
+
+
+);
+
+const Icon = styled.svg`
+ fill: ${({ theme }) => theme.secondary};
+ flex-shrink: 0;
+`;
diff --git a/packages/react-chat/src/components/Icons/ShowIcon.tsx b/packages/react-chat/src/components/Icons/ShowIcon.tsx
new file mode 100644
index 0000000..22eb5ae
--- /dev/null
+++ b/packages/react-chat/src/components/Icons/ShowIcon.tsx
@@ -0,0 +1,27 @@
+import React from "react";
+import styled from "styled-components";
+
+export const ShowIcon = () => (
+
+
+
+
+);
+
+const Icon = styled.svg`
+ fill: ${({ theme }) => theme.tertiary};
+`;
diff --git a/packages/react-chat/src/components/Members/Member.tsx b/packages/react-chat/src/components/Members/Member.tsx
index 222848e..175e562 100644
--- a/packages/react-chat/src/components/Members/Member.tsx
+++ b/packages/react-chat/src/components/Members/Member.tsx
@@ -3,8 +3,8 @@ import styled from "styled-components";
import { useMessengerContext } from "../../contexts/messengerProvider";
import { Contact } from "../../models/Contact";
-import { Icon } from "../Chat/ChatMessages";
import { ContactMenu } from "../Form/ContactMenu";
+import { Icon } from "../Icons/Icon";
import { UserIcon } from "../Icons/UserIcon";
interface MemberProps {
diff --git a/packages/react-chat/src/components/Modals/ProfileModal.tsx b/packages/react-chat/src/components/Modals/ProfileModal.tsx
index d68544d..153c30f 100644
--- a/packages/react-chat/src/components/Modals/ProfileModal.tsx
+++ b/packages/react-chat/src/components/Modals/ProfileModal.tsx
@@ -2,6 +2,7 @@ import React, { useEffect, useMemo, useState } from "react";
import { bufToHex } from "status-communities/dist/cjs/utils";
import styled from "styled-components";
+import { useActivities } from "../../contexts/activityProvider";
import { useIdentity } from "../../contexts/identityProvider";
import { useModal } from "../../contexts/modalProvider";
import { useManageContact } from "../../hooks/useManageContact";
@@ -40,6 +41,8 @@ export const ProfileModal = () => {
[props]
);
+ const { setActivities } = useActivities();
+
const identity = useIdentity();
const isUser = useMemo(
() => id === bufToHex(identity.publicKey),
@@ -47,12 +50,14 @@ export const ProfileModal = () => {
);
const [renaming, setRenaming] = useState(renamingState ?? false);
+
useEffect(() => {
setRenaming(renamingState ?? false);
}, [renamingState]);
const [request, setRequest] = useState("");
const [requestCreation, setRequestCreation] = useState(requestState ?? false);
+
useEffect(() => {
setRequestCreation(requestState ?? false);
}, [requestState]);
@@ -183,7 +188,19 @@ export const ProfileModal = () => {
{
- setIsUserFriend(true),
+ setActivities((prev) => [
+ ...prev,
+ {
+ id: id + request,
+ type: "request",
+ isRead: true,
+ date: new Date(),
+ user: id,
+ request: request,
+ requestType: "outcome",
+ status: "sent",
+ },
+ ]),
setRequestCreation(false),
setRequest("");
}}
diff --git a/packages/react-chat/src/components/ReactChat.tsx b/packages/react-chat/src/components/ReactChat.tsx
index 9482598..c1bdaa9 100644
--- a/packages/react-chat/src/components/ReactChat.tsx
+++ b/packages/react-chat/src/components/ReactChat.tsx
@@ -2,6 +2,7 @@ import React, { useRef } from "react";
import { ThemeProvider } from "styled-components";
import styled from "styled-components";
+import { ActivityProvider } from "../contexts/activityProvider";
import { FetchMetadataProvider } from "../contexts/fetchMetadataProvider";
import { ModalProvider } from "../contexts/modalProvider";
import { NarrowProvider } from "../contexts/narrowProvider";
@@ -28,11 +29,13 @@ export function ReactChat({
-
-
-
-
-
+
+
+
+
+
+
+
diff --git a/packages/react-chat/src/contexts/activityProvider.tsx b/packages/react-chat/src/contexts/activityProvider.tsx
new file mode 100644
index 0000000..ac26347
--- /dev/null
+++ b/packages/react-chat/src/contexts/activityProvider.tsx
@@ -0,0 +1,29 @@
+import React, { createContext, useContext, useState } from "react";
+
+import { Activity } from "../models/Activity";
+
+const ActivityContext = createContext<{
+ activities: Activity[];
+ setActivities: React.Dispatch>;
+}>({
+ activities: [],
+ setActivities: () => undefined,
+});
+
+export function useActivities() {
+ return useContext(ActivityContext);
+}
+
+interface ActivityProviderProps {
+ children: React.ReactNode;
+}
+
+export function ActivityProvider({ children }: ActivityProviderProps) {
+ const [activities, setActivities] = useState([]);
+ return (
+
+ );
+}
diff --git a/packages/react-chat/src/models/Activity.ts b/packages/react-chat/src/models/Activity.ts
new file mode 100644
index 0000000..d02113c
--- /dev/null
+++ b/packages/react-chat/src/models/Activity.ts
@@ -0,0 +1,20 @@
+import { ChannelData } from "./ChannelData";
+import { ChatMessage } from "./ChatMessage";
+
+export type Activity = {
+ id: string;
+ type: "mention" | "request" | "reply";
+ isRead?: boolean;
+ date: Date;
+ user: string;
+ message?: ChatMessage;
+ channel?: ChannelData;
+ request?: string;
+ requestType?: "outcome" | "income";
+ status?: "sent" | "accepted" | "declined" | "blocked";
+ quote?: ChatMessage;
+};
+
+export type Activities = {
+ [id: string]: Activity;
+};
diff --git a/packages/react-chat/src/styles/themes.ts b/packages/react-chat/src/styles/themes.ts
index 704c906..0122db9 100644
--- a/packages/react-chat/src/styles/themes.ts
+++ b/packages/react-chat/src/styles/themes.ts
@@ -19,6 +19,7 @@ export type Theme = {
skeletonLight: string;
skeletonDark: string;
redColor: string;
+ greenColor: string;
mentionColor: string;
mentionHover: string;
mentionBg: string;
@@ -46,6 +47,7 @@ export const lightTheme: Theme = {
skeletonLight: "#F6F8FA",
skeletonDark: "#E9EDF1",
redColor: "#FF2D55",
+ greenColor: "#4EBC60",
mentionColor: "#0DA4C9",
mentionHover: "#BDE7F2",
mentionBg: "#E5F8FD",
@@ -73,6 +75,7 @@ export const darkTheme: Theme = {
skeletonLight: "#2E2F31",
skeletonDark: "#141414",
redColor: "#FF5C7B",
+ greenColor: "#60C370",
mentionColor: "#51D0F0",
mentionHover: "#004E60",
mentionBg: "#004050",