Repository navigation
feat(ScheduledMessages): scheduled messages built on existing SDK patterns #3426
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
d5d7261
b38ef1c
890fdc8
ccc40ed
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,108 @@ | ||
| import { useEffect, useMemo, useState } from 'react'; | ||
| import type { OwnCapabilitiesState } from 'stream-chat'; | ||
| import { | ||
| asDynamicKey, | ||
| ListItemLayout, | ||
| Switch, | ||
| useNotificationApi, | ||
| useStateStore, | ||
| useTranslationContext, | ||
| } from 'stream-chat-react'; | ||
| import { | ||
| type ChannelManagementActionItem, | ||
| defaultChannelManagementActionSet, | ||
| useChannelDetailContext, | ||
| } from 'stream-chat-react/channel-detail'; | ||
|
|
||
| import { IconLock } from '../icons'; | ||
|
|
||
| const toError = (error: unknown) => | ||
| error instanceof Error ? error : new Error('An unknown error occurred'); | ||
|
|
||
| const selectCanFreeze = ({ ownCapabilities }: OwnCapabilitiesState) => ({ | ||
| canFreeze: !!ownCapabilities?.includes('freeze-channel'), | ||
| }); | ||
|
|
||
| const FreezeActionIcon = () => ( | ||
| <IconLock className='str-chat__channel-detail__action-icon' /> | ||
| ); | ||
|
|
||
| /** | ||
| * Freezes or unfreezes the channel, for a user whose role has the `UpdateChannelFrozen` | ||
| * permission. A frozen channel takes no new messages, which is also how a scheduled message | ||
| * can be made to fail with `channel_frozen`. The SDK ships no freeze action; it is demo code. | ||
| */ | ||
| const ChannelFreezeAction = () => { | ||
| const { channel } = useChannelDetailContext(); | ||
| const { addNotification } = useNotificationApi(); | ||
| const { t } = useTranslationContext(); | ||
| const { canFreeze } = useStateStore(channel.state, selectCanFreeze); | ||
| const [frozen, setFrozen] = useState(!!channel.data?.frozen); | ||
|
|
||
| // follows a freeze made elsewhere, such as in the dashboard | ||
| useEffect(() => { | ||
| setFrozen(!!channel.data?.frozen); | ||
| const { unsubscribe } = channel.on('channel.updated', ({ channel: data }) => { | ||
| if (data?.frozen !== undefined) setFrozen(data.frozen); | ||
| }); | ||
| return unsubscribe; | ||
| }, [channel]); | ||
|
|
||
| const TrailingSlot = useMemo(() => { | ||
| function ChannelFreezeSwitch() { | ||
| return <Switch on={frozen} presentation />; | ||
| } | ||
| return ChannelFreezeSwitch; | ||
| }, [frozen]); | ||
|
|
||
| if (!canFreeze) return null; | ||
|
|
||
| const toggleFrozen = async () => { | ||
| const nextFrozen = !frozen; | ||
| setFrozen(nextFrozen); | ||
|
Comment on lines
+60
to
+62
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Prevent overlapping freeze updates. Starting from an unfrozen channel, click twice before either request completes. The requests capture Add an in-flight guard and disable the button until the request completes. Reconcile the displayed state after failure.
|
||
| try { | ||
| await channel.updatePartial({ set: { frozen: nextFrozen } }); | ||
| } catch (error) { | ||
| setFrozen(!nextFrozen); | ||
| addNotification({ | ||
| context: { channel }, | ||
| emitter: 'ChannelFreezeAction', | ||
| error: toError(error), | ||
| message: nextFrozen | ||
| ? t( | ||
| asDynamicKey('viteExample.channelFreeze.freezeError.text'), | ||
| "Couldn't freeze the channel", | ||
| ) | ||
| : t( | ||
| asDynamicKey('viteExample.channelFreeze.unfreezeError.text'), | ||
| "Couldn't unfreeze the channel", | ||
| ), | ||
| severity: 'error', | ||
| type: 'api:channel:freeze:failed', | ||
| }); | ||
| } | ||
| }; | ||
|
|
||
| return ( | ||
| <ListItemLayout | ||
| LeadingIcon={FreezeActionIcon} | ||
| RootElement='button' | ||
| rootProps={{ | ||
| 'aria-pressed': frozen, | ||
| className: | ||
| 'str-chat__form__switch-field str-chat__channel-management-view-action', | ||
| onClick: toggleFrozen, | ||
| }} | ||
| title={t(asDynamicKey('viteExample.channelFreeze.title'), 'Freeze channel')} | ||
| TrailingSlot={TrailingSlot} | ||
| /> | ||
| ); | ||
| }; | ||
|
|
||
| /** The SDK's channel actions with the freeze switch after Mute chat. */ | ||
| export const channelManagementActionSetWithFreeze: ChannelManagementActionItem[] = | ||
| defaultChannelManagementActionSet.flatMap((action) => | ||
| action.type === 'muteChannel' | ||
| ? [action, { Component: ChannelFreezeAction, type: 'freezeChannel' }] | ||
| : [action], | ||
| ); | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Script executed:
rg -n -C 5 'availableSlots|bindingOf|openChannels' \ src/plugins/SlotLayout/workspaceNavigationAdapter.tsx git show release-v14:src/components/ChannelList/ChannelListHeader.tsxRepository: GetStream/stream-chat-react
Length of output: 4878
Script executed:
Repository: GetStream/stream-chat-react
Length of output: 41096
Script executed:
Repository: GetStream/stream-chat-react
Length of output: 29959
Web query:
GetStream stream-chat-react v14 ChannelListHeader HeaderEndContent channel useChatContext sourceResult:
Script executed:
Repository: GetStream/stream-chat-react
Length of output: 15655
Account for covered channels in the visibility gate.
If a layer covers the only channel,
openChannelsstill contains its base binding. The example can then show the toggle with no channel visible. Use layout state to check visible bindings, or document that applications must use their own selection state to preserve the v14 gate.