Skip to content

Commit

Permalink
Merge pull request #1573 from ecency/feature/contact-pinning
Browse files Browse the repository at this point in the history
Chats: added contacts pinning
  • Loading branch information
feruzm authored Mar 15, 2024
2 parents 5d04db4 + a6fb6c2 commit efccd02
Show file tree
Hide file tree
Showing 8 changed files with 213 additions and 49 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"start:prod": "NODE_ENV=production node build/server.js"
},
"dependencies": {
"@ecency/ns-query": "^1.1.6",
"@ecency/ns-query": "^1.1.7",
"@ecency/render-helper": "^2.2.30",
"@ecency/render-helper-amp": "^1.1.0",
"@emoji-mart/data": "^1.1.2",
Expand Down
1 change: 1 addition & 0 deletions src/common/features/chats/components/chat-message-box.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ export default function ChatsMessagesBox(props: Props) {
}}
>
<ChatsMessagesHeader
contact={props.currentContact}
channel={props.channel}
username={props.community?.name ?? props.currentContact?.name ?? ""}
history={props.history}
Expand Down
44 changes: 42 additions & 2 deletions src/common/features/chats/components/chat-messages-header.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useContext, useMemo } from "react";
import React, { useContext, useEffect, useMemo } from "react";
import { History } from "history";
import ChatsCommunityDropdownMenu from "./chats-community-actions";
import UserAvatar from "../../../components/user-avatar";
Expand All @@ -8,16 +8,20 @@ import { Button } from "@ui/button";
import {
Channel,
ChatContext,
DirectContact,
formattedUserName,
useChannelsQuery,
useKeysQuery
useKeysQuery,
usePinContact
} from "@ecency/ns-query";
import { ChatSidebarSavedMessagesAvatar } from "./chats-sidebar/chat-sidebar-saved-messages-avatar";
import { _t } from "../../../i18n";
import { error, success } from "../../../components/feedback";

interface Props {
username: string;
channel?: Channel;
contact?: DirectContact;
history: History;
}

Expand All @@ -30,6 +34,25 @@ export default function ChatsMessagesHeader(props: Props) {

const isActiveUser = useMemo(() => receiverPubKey === publicKey, [publicKey, receiverPubKey]);

const {
mutateAsync: pinContact,
isLoading: isContactPinning,
isSuccess: isPinned,
isError: isPinFailed
} = usePinContact();

useEffect(() => {
if (isPinned) {
success(_t("g.success"));
}
}, [isPinned]);

useEffect(() => {
if (isPinFailed) {
error(_t("g.error"));
}
}, [isPinFailed]);

const formattedName = (username: string) => {
if (username && !username.startsWith("@")) {
const community = channels?.find((channel) => channel.communityName === username);
Expand Down Expand Up @@ -70,6 +93,23 @@ export default function ChatsMessagesHeader(props: Props) {
<ChatsCommunityDropdownMenu channel={props.channel} />
</div>
)}
{props.contact && (
<div className="flex items-center justify-center">
<Button
size="sm"
appearance="gray-link"
disabled={isContactPinning}
onClick={(e: { stopPropagation: () => void }) => {
e.stopPropagation();
if (!isContactPinning) {
pinContact({ contact: props.contact!, pinned: !props.contact!.pinned });
}
}}
>
{props.contact.pinned ? _t("chat.unpin") : _t("chat.pin")}
</Button>
</div>
)}
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ export function ChatPopupHeader({
<ChatsDropdownMenu
history={history!}
channel={channel}
contact={directContact}
onManageChatKey={() => setRevealPrivateKey(!revealPrivateKey)}
/>
</div>
Expand Down
52 changes: 49 additions & 3 deletions src/common/features/chats/components/chats-dropdown-menu.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,33 @@
import React from "react";
import React, { useEffect } from "react";
import { History } from "history";
import { chatKeySvg, chatLeaveSvg, extendedView, kebabMenuSvg, keySvg } from "../../../img/svg";
import {
chatKeySvg,
chatLeaveSvg,
extendedView,
kebabMenuSvg,
keySvg,
pinSvg
} from "../../../img/svg";
import { _t } from "../../../i18n";
import { Dropdown, DropdownItemWithIcon, DropdownMenu, DropdownToggle } from "@ui/dropdown";
import { Button } from "@ui/button";
import { history } from "../../../store";
import { useLocation } from "react-router";
import { Channel, useLeaveCommunityChannel, useLogoutFromChats } from "@ecency/ns-query";
import {
Channel,
DirectContact,
useLeaveCommunityChannel,
useLogoutFromChats,
usePinContact
} from "@ecency/ns-query";
import { error, success } from "../../../components/feedback";

interface Props {
history: History | null;
onManageChatKey?: () => void;
currentUser?: string;
channel?: Channel;
contact?: DirectContact;
}

const ChatsDropdownMenu = (props: Props) => {
Expand All @@ -22,6 +37,24 @@ const ChatsDropdownMenu = (props: Props) => {
const { mutateAsync: leaveChannel, isLoading: isLeavingLoading } = useLeaveCommunityChannel(
props.channel
);
const {
mutateAsync: pinContact,
isLoading: isContactPinning,
isSuccess: isPinned,
isError: isPinFailed
} = usePinContact();

useEffect(() => {
if (isPinned) {
success(_t("g.success"));
}
}, [isPinned]);

useEffect(() => {
if (isPinFailed) {
error(_t("g.error"));
}
}, [isPinFailed]);

const handleExtendedView = () => {
history?.push("/chats");
Expand All @@ -34,6 +67,19 @@ const ChatsDropdownMenu = (props: Props) => {
{kebabMenuSvg}
</Button>
<DropdownMenu align="right">
{props.contact && (
<DropdownItemWithIcon
icon={pinSvg}
label={props.contact.pinned ? _t("chat.unpin") : _t("chat.pin")}
disabled={isContactPinning}
onClick={(e: { stopPropagation: () => void }) => {
e.stopPropagation();
if (!isContactPinning) {
pinContact({ contact: props.contact!, pinned: !props.contact!.pinned });
}
}}
/>
)}
{!location.pathname.startsWith("/chats") && (
<DropdownItemWithIcon
icon={extendedView}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useContext, useMemo } from "react";
import React, { useContext, useEffect, useMemo, useState } from "react";
import UserAvatar from "../../../../components/user-avatar";
import { classNameObject } from "../../../../helper/class-name-object";
import {
Expand All @@ -8,15 +8,19 @@ import {
useGetPublicKeysQuery,
useKeysQuery,
useLastMessageQuery,
usePinContact,
useUnreadCountQuery
} from "@ecency/ns-query";
import { _t } from "../../../../i18n";
import Tooltip from "../../../../components/tooltip";
import { informationOutlineSvg } from "../../../../img/svg";
import { informationOutlineSvg, pinSvg } from "../../../../img/svg";
import { Button } from "@ui/button";
import { Link } from "react-router-dom";
import { Badge } from "@ui/badge";
import { ChatSidebarSavedMessagesAvatar } from "./chat-sidebar-saved-messages-avatar";
import { Dropdown, DropdownItemWithIcon, DropdownMenu } from "@ui/dropdown";
import { error, success } from "../../../../components/feedback";
import { history } from "../../../../store";
import useDebounce from "react-use/lib/useDebounce";

interface Props {
contact: DirectContact;
Expand All @@ -28,6 +32,9 @@ export function ChatSidebarDirectContact({ contact, onClick, isLink = true }: Pr
const { receiverPubKey, setReceiverPubKey, revealPrivateKey, setRevealPrivateKey } =
useContext(ChatContext);

const [holdStarted, setHoldStarted] = useState(false);
const [showContextMenu, setShowContextMenu] = useState(false);

const { publicKey } = useKeysQuery();
const { data: contactKeys, isLoading: isContactKeysLoading } = useGetPublicKeysQuery(
contact.name
Expand All @@ -43,6 +50,36 @@ export function ChatSidebarDirectContact({ contact, onClick, isLink = true }: Pr
);
const isActiveUser = useMemo(() => contact.pubkey === publicKey, [publicKey, contact]);

const {
mutateAsync: pinContact,
isLoading: isContactPinning,
isSuccess: isPinned,
isError: isPinFailed
} = usePinContact();

useEffect(() => {
if (isPinned) {
success(_t("g.success"));
}
}, [isPinned]);

useEffect(() => {
if (isPinFailed) {
error(_t("g.error"));
}
}, [isPinFailed]);

useDebounce(
() => {
if (holdStarted) {
setHoldStarted(false);
setShowContextMenu(true);
}
},
500,
[holdStarted]
);

const content = (
<>
<div
Expand Down Expand Up @@ -75,8 +112,9 @@ export function ChatSidebarDirectContact({ contact, onClick, isLink = true }: Pr
</Tooltip>
</div>
)}
<div className="font-semibold truncate dark:text-white">
<div className="font-semibold truncate dark:text-white flex items-center gap-2">
{isActiveUser ? _t("chat.saved-messages") : contact.name}
{contact.pinned && <span className="rotate-45 opacity-25 w-3.5">{pinSvg}</span>}
</div>
</div>
<div className="text-xs text-gray-500">{lastMessageDate}</div>
Expand All @@ -94,40 +132,74 @@ export function ChatSidebarDirectContact({ contact, onClick, isLink = true }: Pr
</>
);

return isLink ? (
<Link
to="/chats"
className={classNameObject({
"flex items-center text-dark-200 gap-3 p-3 border-b border-[--border-color] last:border-0 hover:bg-gray-100 dark:hover:bg-gray-800 cursor-pointer":
true,
"bg-gray-100 dark:bg-gray-800": receiverPubKey === contact.pubkey
})}
onClick={() => {
setReceiverPubKey(contact.pubkey);
if (revealPrivateKey) {
setRevealPrivateKey(false);
}
onClick?.();
}}
>
{content}
</Link>
) : (
<div
className={classNameObject({
"flex items-center text-dark-200 gap-3 p-3 border-b border-[--border-color] last:border-0 hover:bg-gray-100 dark:hover:bg-gray-800 cursor-pointer":
true,
"bg-gray-100 dark:bg-gray-800": receiverPubKey === contact.pubkey
})}
onClick={() => {
setReceiverPubKey(contact.pubkey);
if (revealPrivateKey) {
setRevealPrivateKey(false);
}
onClick?.();
}}
return (
<Dropdown
closeOnClickOutside={true}
show={showContextMenu}
setShow={(v) => setShowContextMenu(v)}
>
{content}
</div>
<div
onMouseDown={() => setHoldStarted(true)}
onMouseUp={() => {
setHoldStarted(false);

setTimeout(() => {
if (!holdStarted) {
return;
}

if (isLink) {
history?.push("/chats");
}

setReceiverPubKey(contact.pubkey);
if (revealPrivateKey) {
setRevealPrivateKey(false);
}
onClick?.();
}, 500);
}}
onTouchStart={() => setHoldStarted(true)}
onTouchEnd={() => {
setHoldStarted(false);
console.log("touch ended");

setTimeout(() => {
if (!holdStarted) {
return;
}

if (isLink) {
history?.push("/chats");
}

setReceiverPubKey(contact.pubkey);
if (revealPrivateKey) {
setRevealPrivateKey(false);
}
onClick?.();
}, 500);
}}
onContextMenu={(e) => {
e.stopPropagation();
e.preventDefault();
setShowContextMenu(true);
}}
className={classNameObject({
"flex items-center text-dark-200 gap-3 p-3 border-b border-[--border-color] last:border-0 hover:bg-gray-100 dark:hover:bg-gray-800 cursor-pointer":
true,
"bg-gray-100 dark:bg-gray-800": receiverPubKey === contact.pubkey
})}
>
{content}
</div>
<DropdownMenu className="top-[70%]">
<DropdownItemWithIcon
icon={pinSvg}
label={contact.pinned ? _t("chat.unpin") : _t("chat.pin")}
onClick={() => pinContact({ contact, pinned: !contact.pinned })}
/>
</DropdownMenu>
</Dropdown>
);
}
6 changes: 5 additions & 1 deletion src/common/i18n/locales/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,9 @@
"retry": "Retry",
"message": "Message",
"actions": "Actions",
"restore": "Restore"
"restore": "Restore",
"success": "Success",
"error": "Error"
},
"confirm": {
"title": "Are you sure?",
Expand Down Expand Up @@ -1928,6 +1930,8 @@
"hidden-messages-management": "Hidden messages management",
"saved-messages": "Saved messages",
"fetch-error": "While you were away, network change detected. Reload page now to fetch new data",
"pin": "Pin",
"unpin": "Unpin",
"welcome": {
"title": "Welcome to Chats",
"description": "Create or import an account start using Chats",
Expand Down
Loading

0 comments on commit efccd02

Please sign in to comment.