diff --git a/examples/vite/src/ChannelPreviewOverlay/ChannelPreviewOverlay.scss b/examples/vite/src/ChannelPreviewOverlay/ChannelPreviewOverlay.scss new file mode 100644 index 000000000..6a6626df3 --- /dev/null +++ b/examples/vite/src/ChannelPreviewOverlay/ChannelPreviewOverlay.scss @@ -0,0 +1,87 @@ +.app-channel-preview-overlay { + position: absolute; + inset: 0; + z-index: 3; + display: flex; + align-items: center; + justify-content: center; + backdrop-filter: blur(5px); + background: rgba(255, 255, 255, 0.75); + padding: 12px 0; + + .str-chat__theme-dark & { + background: rgba(0, 0, 0, 0.75); + } +} + +.app-channel-preview-overlay__content { + position: relative; + display: flex; + flex-direction: column; + align-items: center; + padding: 40px 48px; + overscroll-behavior: contain; + + &::before { + content: ''; + position: absolute; + inset: -120px; + border-radius: 50%; + background: radial-gradient( + circle, + rgba(255, 255, 255, 0.9) 0%, + rgba(255, 255, 255, 0.85) 40%, + rgba(255, 255, 255, 0) 70% + ); + filter: blur(20px); + z-index: -1; + + .str-chat__theme-dark & { + background: radial-gradient( + circle, + rgba(0, 0, 0, 0.6) 0%, + rgba(0, 0, 0, 0.55) 40%, + rgba(0, 0, 0, 0) 70% + ); + } + } + + .str-chat__icon { + width: 32px; + height: 32px; + color: var(--str-chat__text-tertiary); + } +} + +.app-channel-preview-overlay__text { + display: flex; + flex-direction: column; + align-items: center; + text-align: center; + gap: var(--str-chat__spacing-xxs); + margin-block: var(--str-chat__spacing-sm) var(--str-chat__spacing-xl); + + p { + margin: 0; + } +} + +.app-channel-preview-overlay__title { + font: var(--str-chat__font-heading-xs); + color: var(--str-chat__text-color); +} + +.app-channel-preview-overlay__description { + font: var(--str-chat__font-caption-default); + color: var(--str-chat__text-secondary); + max-width: 200px; +} + +.app-channel-preview-overlay__join-button { + width: 106px; + + .str-chat__loading-indicator { + height: 20px; + width: 20px; + } +} diff --git a/examples/vite/src/ChannelPreviewOverlay/ChannelPreviewOverlay.tsx b/examples/vite/src/ChannelPreviewOverlay/ChannelPreviewOverlay.tsx new file mode 100644 index 000000000..fcb1b52dc --- /dev/null +++ b/examples/vite/src/ChannelPreviewOverlay/ChannelPreviewOverlay.tsx @@ -0,0 +1,84 @@ +import { useCallback, useState } from 'react'; +import type { ChannelMemberResponse } from 'stream-chat'; +import { + Button, + IconMessageBubbles, + LoadingIndicator, + useChannelMembersState, + useChannelStateContext, + useChatContext, + useNotificationApi, +} from 'stream-chat-react'; + +import './ChannelPreviewOverlay.scss'; + +export const useChannelMembershipState = () => { + const { client } = useChatContext(); + const { channel } = useChannelStateContext(); + const members = useChannelMembersState(channel); + const membership = members[client.userID!] as ChannelMemberResponse | undefined; + + const isMember = typeof membership?.channel_role === 'string'; + const canJoin = channel.data?.own_capabilities?.includes('join-channel'); + + return { canJoin, channel, client, isMember }; +}; + +export const ChannelPreviewOverlay = () => { + const { canJoin, channel, client, isMember } = useChannelMembershipState(); + const { addNotification } = useNotificationApi(); + const [joining, setJoining] = useState(false); + + const handleJoin = useCallback(async () => { + setJoining(true); + try { + await channel.addMembers([client.userID!]); + } catch (error) { + addNotification({ + emitter: 'ChannelPreviewOverlay', + incident: { + domain: 'api', + entity: 'channel', + operation: 'join', + }, + message: 'Failed to join the group', + severity: 'error', + error: error instanceof Error ? error : new Error(String(error)), + }); + } finally { + setJoining(false); + } + }, [addNotification, channel, client.userID]); + + if (isMember) return null; + + return ( +
+
+ +
+

+ {canJoin ? "You're previewing this group" : 'This is a private group'} +

+

+ {canJoin + ? 'Join to send messages and follow the conversation' + : 'It is not possible to join this group'} +

+
+ {canJoin && ( + + )} +
+
+ ); +}; diff --git a/examples/vite/src/ChatLayout/Panels.tsx b/examples/vite/src/ChatLayout/Panels.tsx index 99ab7b2cc..c6e44c284 100644 --- a/examples/vite/src/ChatLayout/Panels.tsx +++ b/examples/vite/src/ChatLayout/Panels.tsx @@ -27,11 +27,7 @@ import { useAppSettingsSelector } from '../AppSettings/state'; import { DESKTOP_LAYOUT_BREAKPOINT } from './constants.ts'; import { SidebarResizeHandle, ThreadResizeHandle } from './Resize.tsx'; import { ReturnToSkipNavigation } from '../AccessibilityNavigation/ReturnToSkipNavigation.tsx'; -import { - PublicChannelComposerBanner, - PublicChannelOverlay, - usePublicChannelState, -} from '../PublicChannelOverlay/PublicChannelOverlay.tsx'; +import { ChannelPreviewOverlay } from '../ChannelPreviewOverlay/ChannelPreviewOverlay.tsx'; import { useSidebar } from './SidebarContext.tsx'; import { ThreadStateSync } from './Sync.tsx'; @@ -66,23 +62,6 @@ const ChannelThreadPanel = () => { ); }; -const MessageComposerOrBanner = () => { - const { canJoin, isMember } = usePublicChannelState(); - - if (!isMember && !canJoin) return ; - - return ( - - ); -}; - const ResponsiveChannelPanels = () => { const { thread } = useChannelStateContext('ResponsiveChannelPanels'); const isThreadOpen = !!thread; @@ -105,8 +84,15 @@ const ResponsiveChannelPanels = () => { )} - - + +