diff --git a/website/public/locales/en/message.json b/website/public/locales/en/message.json index c65b1cfade..a531f0a4cf 100644 --- a/website/public/locales/en/message.json +++ b/website/public/locales/en/message.json @@ -1,5 +1,6 @@ { "copy_message_id": "Copy message ID", + "copy_message_link": "Copy message link", "label_action": "Label", "label_title": "Label", "message_deleted": "Message deleted", diff --git a/website/src/components/Messages/MessageEmojiButton.stories.tsx b/website/src/components/Messages/MessageEmojiButton.stories.tsx index d74836d500..b083c9660f 100644 --- a/website/src/components/Messages/MessageEmojiButton.stories.tsx +++ b/website/src/components/Messages/MessageEmojiButton.stories.tsx @@ -8,8 +8,20 @@ export default { component: MessageEmojiButton, }; -const Template = ({ emoji, count, checked }: { emoji: string; count: number; checked?: boolean }) => { - return ; +const Template = ({ + emoji, + count, + checked, + showCount, +}: { + emoji: string; + count: number; + checked?: boolean; + showCount: boolean; +}) => { + return ( + + ); }; export const Default = Template.bind({}); @@ -17,6 +29,7 @@ Default.args = { emoji: "+1", count: 7, checked: false, + showCount: true, }; export const BigNumber = Template.bind({}); @@ -24,6 +37,7 @@ BigNumber.args = { emoji: "+1", count: 999, checked: false, + showCount: true, }; export const Checked = Template.bind({}); @@ -31,4 +45,5 @@ Checked.args = { emoji: "+1", count: 2, checked: true, + showCount: true, }; diff --git a/website/src/components/Messages/MessageEmojiButton.tsx b/website/src/components/Messages/MessageEmojiButton.tsx index b6aab5bc7a..f140a789d9 100644 --- a/website/src/components/Messages/MessageEmojiButton.tsx +++ b/website/src/components/Messages/MessageEmojiButton.tsx @@ -6,9 +6,10 @@ interface MessageEmojiButtonProps { emoji: MessageEmoji; checked?: boolean; onClick: () => void; + showCount: boolean; } -export const MessageEmojiButton = ({ emoji, checked, onClick }: MessageEmojiButtonProps) => { +export const MessageEmojiButton = ({ emoji, checked, onClick, showCount }: MessageEmojiButtonProps) => { const EmojiIcon = emojiIcons.get(emoji.name); if (!EmojiIcon) return <>; return ( @@ -22,7 +23,7 @@ export const MessageEmojiButton = ({ emoji, checked, onClick }: MessageEmojiButt padding="0" > - {emoji.count > 0 && {emoji.count}} + {emoji.count > 0 && showCount && {emoji.count}} ); }; diff --git a/website/src/components/Messages/MessageTableEntry.tsx b/website/src/components/Messages/MessageTableEntry.tsx index 236602e392..51db6d7c5e 100644 --- a/website/src/components/Messages/MessageTableEntry.tsx +++ b/website/src/components/Messages/MessageTableEntry.tsx @@ -15,14 +15,14 @@ import { useToast, } from "@chakra-ui/react"; import { boolean } from "boolean"; -import { ClipboardList, Copy, Flag, MessageSquare, MoreHorizontal, Slash, Trash, User } from "lucide-react"; +import { ClipboardList, Copy, Flag, Link, MessageSquare, MoreHorizontal, Slash, Trash, User } from "lucide-react"; import { useRouter } from "next/router"; -import { useSession } from "next-auth/react"; import { useTranslation } from "next-i18next"; import { useCallback, useEffect, useMemo, useState } from "react"; import { LabelMessagePopup } from "src/components/Messages/LabelPopup"; import { MessageEmojiButton } from "src/components/Messages/MessageEmojiButton"; import { ReportPopup } from "src/components/Messages/ReportPopup"; +import { useHasRole } from "src/hooks/auth/useHasRole"; import { del, post, put } from "src/lib/api"; import { colors } from "src/styles/Theme/colors"; import { Message, MessageEmojis } from "src/types/Conversation"; @@ -116,6 +116,7 @@ export function MessageTableEntry({ message, enabled, highlight }: MessageTableE key={emoji} emoji={{ name: emoji, count }} checked={emojiState.user_emojis.includes(emoji)} + showCount={emojiState.user_emojis.filter((emoji) => emoji === "+1" || emoji === "-1").length > 0} onClick={() => react(emoji, !emojiState.user_emojis.includes(emoji))} /> ); @@ -170,12 +171,12 @@ const MessageActions = ({ }) => { const toast = useToast(); const { t } = useTranslation(["message", "common"]); - const id = message.id; - const displayId = id.slice(0, CHAR_COUNT) + "..." + id.slice(-CHAR_COUNT); - const { trigger: deleteMessage } = useSWRMutation(`/api/admin/delete_message/${message.id}`, del); + const { id } = message; + const { trigger: deleteMessage } = useSWRMutation(`/api/admin/delete_message/${id}`, del); - const { trigger: stopTree } = useSWRMutation(`/api/admin/stop_tree/${message.id}`, put, { + const { trigger: stopTree } = useSWRMutation(`/api/admin/stop_tree/${id}`, put, { onSuccess: () => { + const displayId = id.slice(0, CHAR_COUNT) + "..." + id.slice(-CHAR_COUNT); toast({ title: t("common:success"), description: t("tree_stopped", { id: displayId }), @@ -186,9 +187,6 @@ const MessageActions = ({ }, }); - const { data: session } = useSession(); - const isAdmin = session?.user?.role === "admin"; - const handleDelete = async () => { await deleteMessage(); mutate((key) => typeof key === "string" && key.startsWith("/api/messages"), undefined, { revalidate: true }); @@ -198,8 +196,10 @@ const MessageActions = ({ stopTree(); }; - const handleCopyId = () => { - navigator.clipboard.writeText(message.id); + const handleCopy = (text: string) => { + navigator.clipboard.writeText(text); + const displayId = text.slice(0, CHAR_COUNT) + "..." + text.slice(-CHAR_COUNT); + toast({ title: t("common:copied"), description: displayId, @@ -209,6 +209,8 @@ const MessageActions = ({ }); }; + const isAdmin = useHasRole("admin"); + return ( @@ -230,13 +232,20 @@ const MessageActions = ({ {t("report_action")} - }> + }> {t("open_new_tab_action")} + + handleCopy(`${window.location.protocol}://${window.location.host}/messages/${id}`)} + icon={} + > + {t("copy_message_link")} + {!!isAdmin && ( <> - }> + handleCopy(id)} icon={}> {t("copy_message_id")} }> diff --git a/website/src/hooks/auth/useHasRole.ts b/website/src/hooks/auth/useHasRole.ts new file mode 100644 index 0000000000..d9879d29ec --- /dev/null +++ b/website/src/hooks/auth/useHasRole.ts @@ -0,0 +1,8 @@ +import { useSession } from "next-auth/react"; +import { Role } from "src/components/RoleSelect"; + +export const useHasRole = (role: Role) => { + const { data: session } = useSession(); + + return session?.user?.role === role; +}; diff --git a/website/src/pages/messages/[id]/index.tsx b/website/src/pages/messages/[id]/index.tsx index e50e60cf67..0ef2a61224 100644 --- a/website/src/pages/messages/[id]/index.tsx +++ b/website/src/pages/messages/[id]/index.tsx @@ -59,7 +59,7 @@ export const getServerSideProps: GetServerSideProps<{ id: string }, { id: string props: { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion id: params!.id, - ...(await serverSideTranslations(locale, ["common", "message", "labelling"])), + ...(await serverSideTranslations(locale, ["common", "message", "labelling", "side_menu"])), }, });