diff --git a/package.json b/package.json index 6d56998a399..61095decacb 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/common/features/chats/components/chat-message-box.tsx b/src/common/features/chats/components/chat-message-box.tsx index e82ae2854e1..8238938383f 100644 --- a/src/common/features/chats/components/chat-message-box.tsx +++ b/src/common/features/chats/components/chat-message-box.tsx @@ -67,6 +67,7 @@ export default function ChatsMessagesBox(props: Props) { }} > 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); @@ -70,6 +93,23 @@ export default function ChatsMessagesHeader(props: Props) { )} + {props.contact && ( +
+ +
+ )} ); } diff --git a/src/common/features/chats/components/chat-popup/chat-popup-header.tsx b/src/common/features/chats/components/chat-popup/chat-popup-header.tsx index 64767438123..30f358ff135 100644 --- a/src/common/features/chats/components/chat-popup/chat-popup-header.tsx +++ b/src/common/features/chats/components/chat-popup/chat-popup-header.tsx @@ -148,6 +148,7 @@ export function ChatPopupHeader({ setRevealPrivateKey(!revealPrivateKey)} /> diff --git a/src/common/features/chats/components/chats-dropdown-menu.tsx b/src/common/features/chats/components/chats-dropdown-menu.tsx index 3ee2877a0e2..15952c85d85 100644 --- a/src/common/features/chats/components/chats-dropdown-menu.tsx +++ b/src/common/features/chats/components/chats-dropdown-menu.tsx @@ -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) => { @@ -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"); @@ -34,6 +67,19 @@ const ChatsDropdownMenu = (props: Props) => { {kebabMenuSvg} + {props.contact && ( + void }) => { + e.stopPropagation(); + if (!isContactPinning) { + pinContact({ contact: props.contact!, pinned: !props.contact!.pinned }); + } + }} + /> + )} {!location.pathname.startsWith("/chats") && ( 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 = ( <>
)} -
+
{isActiveUser ? _t("chat.saved-messages") : contact.name} + {contact.pinned && {pinSvg}}
{lastMessageDate}
@@ -94,40 +132,74 @@ export function ChatSidebarDirectContact({ contact, onClick, isLink = true }: Pr ); - return isLink ? ( - { - setReceiverPubKey(contact.pubkey); - if (revealPrivateKey) { - setRevealPrivateKey(false); - } - onClick?.(); - }} - > - {content} - - ) : ( -
{ - setReceiverPubKey(contact.pubkey); - if (revealPrivateKey) { - setRevealPrivateKey(false); - } - onClick?.(); - }} + return ( + setShowContextMenu(v)} > - {content} -
+
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} +
+ + pinContact({ contact, pinned: !contact.pinned })} + /> + + ); } diff --git a/src/common/i18n/locales/en-US.json b/src/common/i18n/locales/en-US.json index 9f548b2887a..a4fc1b82b39 100644 --- a/src/common/i18n/locales/en-US.json +++ b/src/common/i18n/locales/en-US.json @@ -88,7 +88,9 @@ "retry": "Retry", "message": "Message", "actions": "Actions", - "restore": "Restore" + "restore": "Restore", + "success": "Success", + "error": "Error" }, "confirm": { "title": "Are you sure?", @@ -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", diff --git a/yarn.lock b/yarn.lock index 21d8820d601..1b2485c877b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1381,10 +1381,10 @@ resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70" integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw== -"@ecency/ns-query@^1.1.6": - version "1.1.6" - resolved "https://registry.yarnpkg.com/@ecency/ns-query/-/ns-query-1.1.6.tgz#ed21b73d5135373f1a83ce0d4babf303121d0b53" - integrity sha512-Mhdk9t7BDO1GO3Nq3UgBv7VEcIh84XBBXF92QKPT9Emx1vIl3foUajP1e/nrnIL4mV4XVBEyFJnn10yvXbB2FQ== +"@ecency/ns-query@^1.1.7": + version "1.1.7" + resolved "https://registry.yarnpkg.com/@ecency/ns-query/-/ns-query-1.1.7.tgz#9c7c6dacf858badf7d0651031170fcab6f9d2cd1" + integrity sha512-sNPdFd+hvWGftulBrryeCtxCXJ4LzoidRaWSnYcrjxknd7W3dUZoc1OubGBI8lo45i/9k/tXweqiEjDlQZXO2g== dependencies: "@tanstack/react-query" "^4.29.7" axios "^0.21.2"