diff --git a/src/components/MessageComposer/MessageComposerActions.tsx b/src/components/MessageComposer/MessageComposerActions.tsx index 207b3b44d..198d85957 100644 --- a/src/components/MessageComposer/MessageComposerActions.tsx +++ b/src/components/MessageComposer/MessageComposerActions.tsx @@ -18,8 +18,11 @@ import { useInertWhenHidden } from '../Accessibility'; import { useCanScheduleMessage } from '../ScheduledMessages/hooks/useCanScheduleMessage'; import { ScheduleMessageButton as DefaultScheduleMessageButton } from '../ScheduledMessages/ScheduleMessageButton'; -const messageComposerStateSelector = ({ editedMessage }: MessageComposerState) => ({ +const messageComposerStateSelector = ({ editedMessage, + editedScheduledMessage, +}: MessageComposerState) => ({ + isEditing: !!editedMessage || !!editedScheduledMessage, }); const textComposerStateSelector = ({ command, text }: TextComposerState) => ({ @@ -41,7 +44,7 @@ export const MessageComposerActions = () => { } = useComponentContext(); const canScheduleMessage = useCanScheduleMessage(); - const { editedMessage } = useStateStore( + const { isEditing } = useStateStore( messageComposer.state, messageComposerStateSelector, ); @@ -83,7 +86,7 @@ export const MessageComposerActions = () => { ) : ( - {editedMessage || command ? : } + {isEditing || command ? : } ); @@ -103,7 +106,7 @@ export const MessageComposerActions = () => { if (isCooldownActive) { content = ; - } else if (contentIsEmpty && !editedMessage && !command && recordingEnabled) { + } else if (contentIsEmpty && !isEditing && !command && recordingEnabled) { content = ; } diff --git a/src/components/MessageComposer/MessageComposerUI.tsx b/src/components/MessageComposer/MessageComposerUI.tsx index ecb03aad8..04bf26ce7 100644 --- a/src/components/MessageComposer/MessageComposerUI.tsx +++ b/src/components/MessageComposer/MessageComposerUI.tsx @@ -36,13 +36,16 @@ import { } from 'stream-chat'; import { CommandChip as DefaultCommandChip } from './CommandChip'; import { GeolocationPreview } from './AttachmentPreviewList/GeolocationPreview'; +import { EditedScheduledMessagePreview as DefaultEditedScheduledMessagePreview } from '../ScheduledMessages/EditedScheduledMessagePreview'; import { ScheduledMessagesBanner as DefaultScheduledMessagesBanner } from '../ScheduledMessages/ScheduledMessagesBanner'; const messageComposerStateSelector = ({ editedMessage, + editedScheduledMessage, quotedMessage, }: MessageComposerState) => ({ editedMessage, + editedScheduledMessage, quotedMessage, }); @@ -68,13 +71,14 @@ const MessageComposerPreviews = () => { const { AttachmentPreviewList = DefaultAttachmentPreviewList, EditedMessagePreview = DefaultEditedMessagePreview, + EditedScheduledMessagePreview = DefaultEditedScheduledMessagePreview, LinkPreviewList = DefaultLinkPreviewList, QuotedMessagePreview = DefaultQuotedMessagePreview, VoiceRecordingPreviewSlot = DefaultVoiceRecordingPreviewSlot, } = useComponentContext(); const messageComposer = useMessageComposerController(); - const { editedMessage, quotedMessage } = useStateStore( + const { editedMessage, editedScheduledMessage, quotedMessage } = useStateStore( messageComposer.state, messageComposerStateSelector, ); @@ -100,25 +104,39 @@ const MessageComposerPreviews = () => { attachments.length === 0 && linkPreviews.length === 0 && !location && - !editedMessage + !editedMessage && + !editedScheduledMessage ) return null; + let leadingPreview = ; + + if (editedMessage) { + leadingPreview = ( +
+ { + restorePreEditSnapshot(messageComposer); + }} + /> +
+ ); + } else if (editedScheduledMessage) { + leadingPreview = ( +
+ +
+ ); + } + // todo: pass the entity arrays from here so that the preview lists do not have to subscribe to the composer state changes too? return (
- {editedMessage ? ( -
- { - restorePreEditSnapshot(messageComposer); - }} - /> -
- ) : ( - - )} + {leadingPreview} diff --git a/src/components/MessageComposer/hooks/useSubmitHandler.ts b/src/components/MessageComposer/hooks/useSubmitHandler.ts index 092b6f92f..cea7dc308 100644 --- a/src/components/MessageComposer/hooks/useSubmitHandler.ts +++ b/src/components/MessageComposer/hooks/useSubmitHandler.ts @@ -2,9 +2,11 @@ import { useCallback } from 'react'; import { type LocalMessage, type Message, MessageComposer } from 'stream-chat'; import { useMessageComposerController } from './useMessageComposerController'; import { useChannelActionContext } from '../../../context/ChannelActionContext'; +import { useChatContext } from '../../../context/ChatContext'; import { useTranslationContext } from '../../../context/TranslationContext'; import { useNotificationApi } from '../../Notifications'; import { discardPreEditSnapshot } from '../preEditSnapshot'; +import { reloadOnScheduledMessageConflict } from '../../ScheduledMessages/utils/scheduledMessageErrors'; import type { MessageComposerProps } from '../MessageComposer'; @@ -61,6 +63,7 @@ const asNewMessage = { const { overrideSubmitHandler } = props; + const { client } = useChatContext('useSubmitHandler'); const { addNotification } = useNotificationApi(); const { editMessage, sendMessage } = useChannelActionContext('useSubmitHandler'); const { t } = useTranslationContext('useSubmitHandler'); @@ -69,6 +72,16 @@ export const useSubmitHandler = (props: MessageComposerProps) => { const handleSubmit = useCallback( async (event?: React.BaseSyntheticEvent) => { event?.preventDefault(); + + if (messageComposer.editedScheduledMessage) { + await messageComposer + .updateScheduledMessage() + .catch((error: Error) => + reloadOnScheduledMessageConflict(client.scheduledMessages, error), + ); + return; + } + const composition = await messageComposer.compose(); if (!composition || !composition.message) return; @@ -150,6 +163,7 @@ export const useSubmitHandler = (props: MessageComposerProps) => { }, [ addNotification, + client, editMessage, messageComposer, overrideSubmitHandler, diff --git a/src/components/ScheduledMessages/DeleteScheduledMessageAlert.tsx b/src/components/ScheduledMessages/DeleteScheduledMessageAlert.tsx new file mode 100644 index 000000000..03d6871f6 --- /dev/null +++ b/src/components/ScheduledMessages/DeleteScheduledMessageAlert.tsx @@ -0,0 +1,50 @@ +import React from 'react'; + +import { useTranslationContext } from '../../context'; +import { Button } from '../Button'; +import { Alert } from '../Dialog'; +import type { DeleteScheduledMessageAlertProps } from './types'; + +export const DeleteScheduledMessageAlert = ({ + channelName, + onCancel, + onDelete, +}: DeleteScheduledMessageAlertProps) => { + const { t } = useTranslationContext(); + + const description = channelName + ? t("This message to {{ name }} won't be sent. This can't be undone.", { + name: channelName, + }) + : t("This message won't be sent. This can't be undone."); + + return ( + + + + + + + + ); +}; diff --git a/src/components/ScheduledMessages/EditedScheduledMessagePreview.tsx b/src/components/ScheduledMessages/EditedScheduledMessagePreview.tsx new file mode 100644 index 000000000..5686605a2 --- /dev/null +++ b/src/components/ScheduledMessages/EditedScheduledMessagePreview.tsx @@ -0,0 +1,26 @@ +import React, { useMemo } from 'react'; + +import { useTranslationContext } from '../../context'; +import { QuotedMessagePreviewUI } from '../MessageComposer/QuotedMessagePreview'; +import type { EditedScheduledMessagePreviewProps } from './types'; +import { toStoredScheduledMessagePreview } from './utils'; + +export const EditedScheduledMessagePreview = ({ + onCancel, + scheduledMessage, +}: EditedScheduledMessagePreviewProps) => { + const { t } = useTranslationContext(); + + const previewMessage = useMemo( + () => toStoredScheduledMessagePreview(scheduledMessage), + [scheduledMessage], + ); + + return ( + + ); +}; diff --git a/src/components/ScheduledMessages/RescheduleMessageDialog.tsx b/src/components/ScheduledMessages/RescheduleMessageDialog.tsx new file mode 100644 index 000000000..e8b017b83 --- /dev/null +++ b/src/components/ScheduledMessages/RescheduleMessageDialog.tsx @@ -0,0 +1,77 @@ +import React, { useCallback, useMemo } from 'react'; + +import { useChatContext, useTranslationContext } from '../../context'; +import { useNotificationApi } from '../Notifications'; +import { + SCHEDULED_MESSAGE_ACTIONS_EMITTER, + ScheduledMessageNotificationType, +} from './constants'; +import { ScheduleTimeDialog } from './ScheduleTimeDialog'; +import type { RescheduleMessageDialogProps } from './types'; +import { + getScheduledMessageChannel, + reloadOnScheduledMessageConflict, + toStoredScheduledMessagePreview, +} from './utils'; + +export const RescheduleMessageDialog = ({ + close, + scheduledMessage, +}: RescheduleMessageDialogProps) => { + const { t } = useTranslationContext(); + const { client } = useChatContext('RescheduleMessageDialog'); + const { addNotification } = useNotificationApi(); + + const { id, scheduled_at } = scheduledMessage; + const { scheduledMessages: manager } = client; + + const channel = useMemo( + () => getScheduledMessageChannel(client, scheduledMessage), + [client, scheduledMessage], + ); + const previewMessage = useMemo( + () => toStoredScheduledMessagePreview(scheduledMessage), + [scheduledMessage], + ); + const initialScheduledAt = new Date(scheduled_at); + const errorMessage = t("Couldn't reschedule the message. Try again"); + const scheduledTime = t('timestamp/ScheduledMessageSendTime', { + timestamp: initialScheduledAt, + }); + + const reschedule = useCallback( + async (scheduledAt: Date) => { + try { + await manager.updateScheduledMessage(id, { + scheduled_at: scheduledAt.toISOString(), + }); + } catch (error) { + reloadOnScheduledMessageConflict(manager, error); + addNotification({ + emitter: SCHEDULED_MESSAGE_ACTIONS_EMITTER, + error: error as Error, + message: errorMessage, + severity: 'error', + type: ScheduledMessageNotificationType.RescheduleFailed, + }); + + throw error; + } + }, + [addNotification, errorMessage, id, manager], + ); + + return ( + + ); +}; diff --git a/src/components/ScheduledMessages/ScheduleMessageDialog.tsx b/src/components/ScheduledMessages/ScheduleMessageDialog.tsx index 315884933..7c8d02573 100644 --- a/src/components/ScheduledMessages/ScheduleMessageDialog.tsx +++ b/src/components/ScheduledMessages/ScheduleMessageDialog.tsx @@ -1,152 +1,26 @@ -import React, { useCallback } from 'react'; -import type { TFunction } from 'i18next'; +import React from 'react'; -import { useComponentContextIcons, useTranslationContext } from '../../context'; -import { useChannelDisplayName } from '../ChannelListItem/hooks/useChannelDisplayName'; -import { Prompt } from '../Dialog'; -import { TextInput } from '../Form'; +import { useTranslationContext } from '../../context'; import { useMessageComposerController } from '../MessageComposer/hooks/useMessageComposerController'; -import { QuotedMessagePreviewUI } from '../MessageComposer/QuotedMessagePreview'; -import { ScheduleMessageNoticeSeverity, ScheduleTimeError } from './constants'; -import { - useIsConnectionOnline, - useScheduledMessagePreview, - useScheduleMessageSubmit, - useScheduleTimeForm, -} from './hooks'; -import { ScheduleMessageDialogNotice } from './ScheduleMessageDialogNotice'; +import { useScheduledMessagePreview, useScheduleMessage } from './hooks'; +import { ScheduleTimeDialog } from './ScheduleTimeDialog'; import type { ScheduleMessageDialogProps } from './types'; -import { - formatTimeZoneOffset, - fromDateInputValue, - openNativePicker, - toDateInputValue, -} from './utils'; - -const translateScheduleTimeError = (t: TFunction, timeError?: ScheduleTimeError) => { - if (timeError === ScheduleTimeError.TooSoon) return t('Choose a later time'); - - if (timeError === ScheduleTimeError.TooFar) return t('Choose an earlier time'); - - return undefined; -}; export const ScheduleMessageDialog = ({ close }: ScheduleMessageDialogProps) => { const { t } = useTranslationContext(); - const { IconCalendar, IconCaretDown, IconClock } = useComponentContextIcons(); const { channel } = useMessageComposerController(); - const channelDisplayName = useChannelDisplayName(channel); const previewMessage = useScheduledMessagePreview(); - const isOnline = useIsConnectionOnline(); - const { - bounds, - dateValue, - onDateChange, - onTimeChange, - scheduledAt, - timeError, - timeValue, - } = useScheduleTimeForm(); - const { hasFailed, isScheduling, submit } = useScheduleMessageSubmit(close); - - const canSchedule = !!scheduledAt && !timeError && isOnline && !isScheduling; - const timeErrorMessage = translateScheduleTimeError(t, timeError); - const timeZone = formatTimeZoneOffset(scheduledAt ?? bounds.earliest); - const selectedDate = fromDateInputValue(dateValue); - const dateLabel = selectedDate - ? t('timestamp/ScheduleDate', { timestamp: selectedDate }) - : undefined; - const authorLabel = channelDisplayName - ? t('Message to {{ name }}', { name: channelDisplayName }) - : t('Schedule message'); - - const scheduleAtPickedTime = useCallback(() => { - if (scheduledAt) submit(scheduledAt); - }, [scheduledAt, submit]); + const scheduleMessage = useScheduleMessage(); return ( - - - - - {!isOnline && ( - - {t("You're offline. Reconnect to schedule this message.")} - - )} - {hasFailed && ( - - {t("Couldn't schedule message. Try again")} - - )} -
- - - - {dateLabel} - - - } - max={toDateInputValue(bounds.latest)} - min={toDateInputValue(bounds.earliest)} - onChange={onDateChange} - onClick={openNativePicker} - required - trailing={} - type='date' - value={dateValue} - /> - } - onChange={onTimeChange} - onClick={openNativePicker} - required - trailing={} - type='time' - value={timeValue} - /> -
- - {t('Sent in your time zone ({{ timeZone }})', { timeZone })} - -
- - - - {t('Cancel')} - - - {t('Schedule')} - - - -
+ ); }; diff --git a/src/components/ScheduledMessages/ScheduleTimeDialog.tsx b/src/components/ScheduledMessages/ScheduleTimeDialog.tsx new file mode 100644 index 000000000..648641472 --- /dev/null +++ b/src/components/ScheduledMessages/ScheduleTimeDialog.tsx @@ -0,0 +1,162 @@ +import React, { useCallback } from 'react'; +import type { TFunction } from 'i18next'; + +import { useComponentContextIcons, useTranslationContext } from '../../context'; +import { useChannelDisplayName } from '../ChannelListItem/hooks/useChannelDisplayName'; +import { Prompt } from '../Dialog'; +import { TextInput } from '../Form'; +import { QuotedMessagePreviewUI } from '../MessageComposer/QuotedMessagePreview'; +import { ScheduleMessageNoticeSeverity, ScheduleTimeError } from './constants'; +import { + useIsConnectionOnline, + useScheduleTimeForm, + useScheduleTimeSubmit, +} from './hooks'; +import { ScheduleMessageDialogNotice } from './ScheduleMessageDialogNotice'; +import type { ScheduleTimeDialogProps } from './types'; +import { + formatTimeZoneOffset, + fromDateInputValue, + openNativePicker, + toDateInputValue, +} from './utils'; + +const translateScheduleTimeError = (t: TFunction, timeError?: ScheduleTimeError) => { + if (timeError === ScheduleTimeError.TooSoon) return t('Choose a later time'); + + if (timeError === ScheduleTimeError.TooFar) return t('Choose an earlier time'); + + return undefined; +}; + +export const ScheduleTimeDialog = ({ + channel, + close, + errorMessage, + initialScheduledAt, + previewLabel, + previewMessage, + submit: submitScheduledAt, + submitLabel, + title, +}: ScheduleTimeDialogProps) => { + const { t } = useTranslationContext(); + const { IconCalendar, IconCaretDown, IconClock } = useComponentContextIcons(); + + const channelDisplayName = useChannelDisplayName(channel); + const isOnline = useIsConnectionOnline(); + const { + bounds, + dateValue, + onDateChange, + onTimeChange, + scheduledAt, + timeError, + timeValue, + } = useScheduleTimeForm({ channel, initialScheduledAt }); + const { hasFailed, isScheduling, submit } = useScheduleTimeSubmit( + submitScheduledAt, + close, + ); + + const canSchedule = !!scheduledAt && !timeError && isOnline && !isScheduling; + const timeErrorMessage = translateScheduleTimeError(t, timeError); + const timeZone = formatTimeZoneOffset(scheduledAt ?? bounds.earliest); + const selectedDate = fromDateInputValue(dateValue); + const dateLabel = selectedDate + ? t('timestamp/ScheduleDate', { timestamp: selectedDate }) + : undefined; + const messageToLabel = channelDisplayName + ? t('Message to {{ name }}', { name: channelDisplayName }) + : title; + const authorLabel = previewLabel ?? messageToLabel; + + const scheduleAtPickedTime = useCallback(() => { + if (scheduledAt) submit(scheduledAt); + }, [scheduledAt, submit]); + + return ( + + + + + {!isOnline && ( + + {t("You're offline. Reconnect to schedule this message.")} + + )} + {hasFailed && ( + + {errorMessage} + + )} +
+ + + + {dateLabel} + + + } + max={toDateInputValue(bounds.latest)} + min={toDateInputValue(bounds.earliest)} + onChange={onDateChange} + onClick={openNativePicker} + required + trailing={} + type='date' + value={dateValue} + /> + } + onChange={onTimeChange} + onClick={openNativePicker} + required + trailing={} + type='time' + value={timeValue} + /> +
+ + {t('Sent in your time zone ({{ timeZone }})', { timeZone })} + +
+ + + + {t('Cancel')} + + + {submitLabel ?? t('Schedule')} + + + +
+ ); +}; diff --git a/src/components/ScheduledMessages/ScheduledMessageActions.tsx b/src/components/ScheduledMessages/ScheduledMessageActions.tsx new file mode 100644 index 000000000..bf2f26ab1 --- /dev/null +++ b/src/components/ScheduledMessages/ScheduledMessageActions.tsx @@ -0,0 +1,163 @@ +import clsx from 'clsx'; +import React, { useCallback, useMemo, useRef } from 'react'; + +import { + useChatContext, + useComponentContext, + useComponentContextIcons, + useTranslationContext, +} from '../../context'; +import { useChannelDisplayName } from '../ChannelListItem/hooks/useChannelDisplayName'; +import { Button } from '../Button'; +import { ContextMenu, useDialogIsOpen, useDialogOnNearestManager } from '../Dialog'; +import { GlobalModal } from '../Modal'; +import { SCHEDULED_MESSAGE_ACTIONS_MENU_ID, ScheduledMessageAction } from './constants'; +import { DeleteScheduledMessageAlert } from './DeleteScheduledMessageAlert'; +import { useScheduledMessageActionHandlers, useScheduleMessageDialog } from './hooks'; +import { RescheduleMessageDialog } from './RescheduleMessageDialog'; +import { ScheduledMessageActionsMenu } from './ScheduledMessageActionsMenu'; +import type { ScheduledMessageActionsProps } from './types'; +import { getScheduledMessageActions, hasScheduledMessageEditButton } from './utils'; + +export const ScheduledMessageActions = ({ + onEdit, + scheduledMessage, +}: ScheduledMessageActionsProps) => { + const { t } = useTranslationContext(); + const { IconEdit, IconMore } = useComponentContextIcons(); + const { ContextMenu: ContextMenuComponent = ContextMenu, Modal = GlobalModal } = + useComponentContext(); + + const { client } = useChatContext('ScheduledMessageActions'); + const channelName = useChannelDisplayName(client.activeChannels[scheduledMessage.cid]); + + const { copyText, deleteScheduledMessage, isPending, sendNow } = + useScheduledMessageActionHandlers(scheduledMessage); + const menuDialogId = `${SCHEDULED_MESSAGE_ACTIONS_MENU_ID}-${scheduledMessage.id}`; + const { dialog: menuDialog, dialogManager } = useDialogOnNearestManager({ + id: menuDialogId, + }); + const isMenuOpen = useDialogIsOpen(menuDialogId, dialogManager?.id); + + const menuButtonRef = useRef(null); + + const rescheduleDialog = useScheduleMessageDialog(menuButtonRef); + const deleteAlert = useScheduleMessageDialog(menuButtonRef); + + const actions = useMemo( + () => getScheduledMessageActions(scheduledMessage, { canEdit: !!onEdit }), + [onEdit, scheduledMessage], + ); + const hasEditButton = hasScheduledMessageEditButton(scheduledMessage, actions); + const menuActions = useMemo( + () => + hasEditButton + ? actions.filter((action) => action !== ScheduledMessageAction.Edit) + : actions, + [actions, hasEditButton], + ); + + const toggleMenu = useCallback(() => menuDialog.toggle(), [menuDialog]); + + const editScheduledMessage = useCallback( + () => onEdit?.(scheduledMessage), + [onEdit, scheduledMessage], + ); + + const sendScheduledMessageNow = useCallback(() => { + sendNow(); + }, [sendNow]); + + const confirmDelete = useCallback(() => { + deleteAlert.closeDialog(); + deleteScheduledMessage(); + }, [deleteAlert, deleteScheduledMessage]); + + return ( +
+ + {hasEditButton && ( + + )} + + + + + {rescheduleDialog.isDialogOpen && ( + + )} + + + {deleteAlert.isDialogOpen && ( + + )} + +
+ ); +}; diff --git a/src/components/ScheduledMessages/ScheduledMessageActionsMenu.tsx b/src/components/ScheduledMessages/ScheduledMessageActionsMenu.tsx new file mode 100644 index 000000000..7a2a4093a --- /dev/null +++ b/src/components/ScheduledMessages/ScheduledMessageActionsMenu.tsx @@ -0,0 +1,98 @@ +import React, { Fragment, useMemo } from 'react'; + +import { useComponentContextIcons, useTranslationContext } from '../../context'; +import { ContextMenuButton, useContextMenuContext } from '../Dialog'; +import { ScheduledMessageAction } from './constants'; +import type { ScheduledMessageActionsMenuProps } from './types'; + +export const ScheduledMessageActionsMenu = ({ + actions, + onCopy, + onDelete, + onEdit, + onReschedule, + onSendNow, +}: ScheduledMessageActionsMenuProps) => { + const { t } = useTranslationContext(); + const { IconClock, IconCopy, IconDelete, IconEdit, IconSend } = + useComponentContextIcons(); + const { closeMenu } = useContextMenuContext(); + + const menuItems = useMemo( + () => ({ + [ScheduledMessageAction.Copy]: { + Icon: IconCopy, + label: t('Copy text'), + onSelect: onCopy, + }, + [ScheduledMessageAction.Delete]: { + Icon: IconDelete, + label: t('Delete'), + onSelect: onDelete, + }, + [ScheduledMessageAction.Edit]: { + Icon: IconEdit, + label: t('Edit'), + onSelect: onEdit, + }, + [ScheduledMessageAction.Reschedule]: { + Icon: IconClock, + label: t('Reschedule'), + onSelect: onReschedule, + }, + [ScheduledMessageAction.SendNow]: { + Icon: IconSend, + label: t('Send now'), + onSelect: onSendNow, + }, + }), + [ + IconClock, + IconCopy, + IconDelete, + IconEdit, + IconSend, + onCopy, + onDelete, + onEdit, + onReschedule, + onSendNow, + t, + ], + ); + + return ( + <> + {actions.map((action, index) => { + const { Icon, label, onSelect } = menuItems[action]; + const isDelete = action === ScheduledMessageAction.Delete; + const hasSeparator = isDelete && index > 1; + + const selectAction = () => { + closeMenu(); + onSelect(); + }; + + return ( + + {hasSeparator && ( +
+ )} + + {label} + + + ); + })} + + ); +}; diff --git a/src/components/ScheduledMessages/ScheduledMessageItem.tsx b/src/components/ScheduledMessages/ScheduledMessageItem.tsx index 5d1275f22..3c51433fe 100644 --- a/src/components/ScheduledMessages/ScheduledMessageItem.tsx +++ b/src/components/ScheduledMessages/ScheduledMessageItem.tsx @@ -5,11 +5,15 @@ import { useChatContext, useTranslationContext } from '../../context'; import { ChannelAvatar } from '../Avatar'; import { useChannelPreviewInfo } from '../ChannelListItem/hooks/useChannelPreviewInfo'; import { SummarizedMessagePreview } from '../SummarizedMessagePreview'; +import { ScheduledMessageActions } from './ScheduledMessageActions'; import { ScheduledMessageItemStatus } from './ScheduledMessageItemStatus'; import type { ScheduledMessageItemProps } from './types'; import { isFailedScheduledMessage, toStoredScheduledMessagePreview } from './utils'; -export const ScheduledMessageItem = ({ scheduledMessage }: ScheduledMessageItemProps) => { +export const ScheduledMessageItem = ({ + onEdit, + scheduledMessage, +}: ScheduledMessageItemProps) => { const { t } = useTranslationContext(); const { client } = useChatContext(); @@ -39,6 +43,7 @@ export const ScheduledMessageItem = ({ scheduledMessage }: ScheduledMessageItemP +