diff --git a/packages/react/src/views/ChatLayout/ChatLayout.js b/packages/react/src/views/ChatLayout/ChatLayout.js index d3202a4fa..9424705ad 100644 --- a/packages/react/src/views/ChatLayout/ChatLayout.js +++ b/packages/react/src/views/ChatLayout/ChatLayout.js @@ -106,24 +106,18 @@ const ChatLayout = () => { ]); const getPinnedMessages = useCallback(async () => { - if (isUserAuthenticated && showPinned) { + if (!isUserAuthenticated && !anonymousMode) { + return; + } + if (isUserAuthenticated) { try { - if (!isUserAuthenticated && !anonymousMode) { - return; - } const { messages } = await RCInstance.getPinnedMessages(); setPinnedMessages(messages); } catch (e) { console.error(e); } } - }, [ - isUserAuthenticated, - anonymousMode, - RCInstance, - showPinned, - setPinnedMessages, - ]); + }, [isUserAuthenticated, anonymousMode, RCInstance, setPinnedMessages]); useEffect(() => { getStarredMessages(); @@ -131,7 +125,7 @@ const ChatLayout = () => { useEffect(() => { getPinnedMessages(); - }, [getPinnedMessages, showSidebar, showPinned]); + }, [getPinnedMessages]); return ( { /> )} + +
diff --git a/packages/react/src/views/PinnedMessageBanner/PinnedMessageBanner.js b/packages/react/src/views/PinnedMessageBanner/PinnedMessageBanner.js new file mode 100644 index 000000000..3b822d895 --- /dev/null +++ b/packages/react/src/views/PinnedMessageBanner/PinnedMessageBanner.js @@ -0,0 +1,134 @@ +import React, { useState, useEffect, useCallback } from 'react'; +import { Box, Icon, useTheme } from '@embeddedchat/ui-elements'; +import usePinnedMessageStore from '../../store/pinnedMessageStore'; +import { useRCContext } from '../../context/RCInstance'; +import getPinnedMessageBannerStyles from './PinnedMessageBanner.styles'; + +const SESSION_KEY = 'ec-pinned-banner-dismissed'; + +const getDismissedRooms = () => { + try { + return JSON.parse(sessionStorage.getItem(SESSION_KEY) || '[]'); + } catch { + return []; + } +}; + +const PinnedMessageBanner = () => { + const { ECOptions, RCInstance } = useRCContext(); + const { theme } = useTheme(); + const styles = getPinnedMessageBannerStyles({ theme }); + + const pinnedMessages = usePinnedMessageStore((state) => state.pinnedMessages); + + const [currentIndex, setCurrentIndex] = useState(0); + const [dismissed, setDismissed] = useState(false); + + const roomId = RCInstance?.rid; + + useEffect(() => { + setCurrentIndex(0); + const dismissedRooms = getDismissedRooms(); + setDismissed(dismissedRooms.includes(roomId)); + }, [roomId]); + + useEffect(() => { + if (currentIndex >= pinnedMessages.length && pinnedMessages.length > 0) { + setCurrentIndex(pinnedMessages.length - 1); + } + }, [pinnedMessages.length, currentIndex]); + + const handlePrev = useCallback(() => { + setCurrentIndex((i) => (i > 0 ? i - 1 : pinnedMessages.length - 1)); + }, [pinnedMessages.length]); + + const handleNext = useCallback(() => { + setCurrentIndex((i) => (i < pinnedMessages.length - 1 ? i + 1 : 0)); + }, [pinnedMessages.length]); + + const handleClick = useCallback(() => { + const msg = pinnedMessages[currentIndex]; + if (!msg) return; + const el = document.getElementById(`ec-message-body-${msg._id}`); + if (el) { + el.scrollIntoView({ behavior: 'smooth', block: 'center' }); + el.style.transition = 'background-color 0.3s ease'; + el.style.backgroundColor = `${theme.colors.primary}33`; + setTimeout(() => { + el.style.backgroundColor = ''; + }, 1500); + } + }, [pinnedMessages, currentIndex, theme.colors.primary]); + + const handleDismiss = useCallback(() => { + const dismissedRooms = getDismissedRooms(); + if (!dismissedRooms.includes(roomId)) { + dismissedRooms.push(roomId); + sessionStorage.setItem(SESSION_KEY, JSON.stringify(dismissedRooms)); + } + setDismissed(true); + }, [roomId]); + + if (ECOptions?.showPinnedBanner === false) return null; + if (dismissed) return null; + if (!pinnedMessages || pinnedMessages.length === 0) return null; + + const msg = pinnedMessages[currentIndex]; + const snippet = + msg?.msg?.length > 80 ? `${msg.msg.slice(0, 80)}...` : msg?.msg || ''; + const author = msg?.u?.username || ''; + const total = pinnedMessages.length; + + return ( + + + + Pinned message + + {author ? `${author}: ` : ''} + {snippet} + + + + {total > 1 && ( + <> + + + {currentIndex + 1} / {total} + + + + )} + + + + ); +}; + +export default PinnedMessageBanner; diff --git a/packages/react/src/views/PinnedMessageBanner/PinnedMessageBanner.styles.js b/packages/react/src/views/PinnedMessageBanner/PinnedMessageBanner.styles.js new file mode 100644 index 000000000..3f105505b --- /dev/null +++ b/packages/react/src/views/PinnedMessageBanner/PinnedMessageBanner.styles.js @@ -0,0 +1,85 @@ +import { css } from '@emotion/react'; + +const getPinnedMessageBannerStyles = ({ theme }) => ({ + banner: css` + display: flex; + align-items: center; + background-color: ${theme.colors.primary}1a; + border-left: 3px solid ${theme.colors.primary}; + padding: 0.35rem 0.75rem; + width: 100%; + box-sizing: border-box; + gap: 0.5rem; + min-height: 2.5rem; + position: relative; + z-index: 1100; + `, + pinIcon: css` + flex-shrink: 0; + color: ${theme.colors.primary}; + `, + content: css` + flex: 1; + min-width: 0; + cursor: pointer; + overflow: hidden; + `, + label: css` + font-size: 0.65rem; + font-weight: 600; + color: ${theme.colors.primary}; + text-transform: uppercase; + letter-spacing: 0.04em; + line-height: 1.2; + `, + text: css` + font-size: 0.8rem; + color: ${theme.colors.foreground}; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + line-height: 1.4; + `, + controls: css` + display: flex; + align-items: center; + gap: 0.25rem; + flex-shrink: 0; + `, + counter: css` + font-size: 0.7rem; + color: ${theme.colors.secondary}; + white-space: nowrap; + `, + navButton: css` + background: none; + border: none; + padding: 0.2rem; + cursor: pointer; + color: ${theme.colors.secondary}; + display: flex; + align-items: center; + border-radius: 3px; + &:hover { + background-color: ${theme.colors.primary}33; + color: ${theme.colors.primary}; + } + `, + closeButton: css` + background: none; + border: none; + padding: 0.2rem; + cursor: pointer; + color: ${theme.colors.secondary}; + display: flex; + align-items: center; + border-radius: 3px; + margin-left: 0.25rem; + &:hover { + background-color: ${theme.colors.danger}33; + color: ${theme.colors.danger}; + } + `, +}); + +export default getPinnedMessageBannerStyles; diff --git a/packages/react/src/views/PinnedMessageBanner/index.js b/packages/react/src/views/PinnedMessageBanner/index.js new file mode 100644 index 000000000..7dc253406 --- /dev/null +++ b/packages/react/src/views/PinnedMessageBanner/index.js @@ -0,0 +1 @@ +export { default } from './PinnedMessageBanner';