diff --git a/ts/components/SendStoryModal.tsx b/ts/components/SendStoryModal.tsx index bf2b1b8f43..335e41de67 100644 --- a/ts/components/SendStoryModal.tsx +++ b/ts/components/SendStoryModal.tsx @@ -56,7 +56,7 @@ export type PropsType = { onDistributionListCreated: ( name: string, viewerUuids: Array - ) => unknown; + ) => Promise; onSelectedStoryList: (options: { conversationId: string; distributionId: string | undefined; @@ -67,7 +67,7 @@ export type PropsType = { conversationIds: Array ) => unknown; signalConnections: Array; - toggleGroupsForStorySend: (cids: Array) => unknown; + toggleGroupsForStorySend: (cids: Array) => Promise; mostRecentActiveStoryTimestampByGroupOrDistributionList: Record< string, number @@ -419,9 +419,17 @@ export function SendStoryModal({ candidateConversations={candidateConversations} getPreferredBadge={getPreferredBadge} i18n={i18n} - onCreateList={(name, uuids) => { + onCreateList={async (name, uuids) => { + const newDistributionListId = await onDistributionListCreated( + name, + uuids + ); + setSelectedContacts([]); - onDistributionListCreated(name, uuids); + setSelectedListIds( + listIds => new Set([...listIds, newDistributionListId]) + ); + setPage(Page.SendStory); }} onViewersUpdated={uuids => { @@ -480,9 +488,10 @@ export function SendStoryModal({ aria-label={i18n('ok')} className="SendStoryModal__ok" disabled={!chosenGroupIds.size} - onClick={() => { - toggleGroupsForStorySend(Array.from(chosenGroupIds)); + onClick={async () => { + await toggleGroupsForStorySend(Array.from(chosenGroupIds)); setChosenGroupIds(new Set()); + setSelectedGroupIds(chosenGroupIds); setPage(Page.SendStory); }} type="button" diff --git a/ts/components/StoriesSettingsModal.tsx b/ts/components/StoriesSettingsModal.tsx index 9a9fe22e85..c86f443661 100644 --- a/ts/components/StoriesSettingsModal.tsx +++ b/ts/components/StoriesSettingsModal.tsx @@ -54,7 +54,7 @@ export type PropsType = { onDistributionListCreated: ( name: string, viewerUuids: Array - ) => unknown; + ) => Promise; onHideMyStoriesFrom: (viewerUuids: Array) => unknown; onRemoveMembers: (listId: string, uuids: Array) => unknown; onRepliesNReactionsChanged: ( diff --git a/ts/hooks/useBoundActions.ts b/ts/hooks/useBoundActions.ts index c1121294ef..c74b6e956f 100644 --- a/ts/hooks/useBoundActions.ts +++ b/ts/hooks/useBoundActions.ts @@ -2,16 +2,45 @@ // SPDX-License-Identifier: AGPL-3.0-only import type { ActionCreatorsMapObject } from 'redux'; +import type { ThunkAction } from 'redux-thunk'; import { bindActionCreators } from 'redux'; import { useDispatch } from 'react-redux'; import { useMemo } from 'react'; +/* eslint-disable @typescript-eslint/no-explicit-any */ + +/** + * Type-level function from an action creator (which may be ThunkAction creator) to a + * bound action creator. + * + * binding a thunk action creator changes it from: + * (params) => ThunkAction + * to: + * (params) => R + * + * a regular action creator's type is unchanged + */ +type BoundActionCreator = A extends ( + ...params: infer P +) => ThunkAction + ? (...params: P) => R + : A; + +export type BoundActionCreatorsMapObject = { + [Property in keyof T]: BoundActionCreator; +}; + export const useBoundActions = ( actions: T -): T => { +): BoundActionCreatorsMapObject => { const dispatch = useDispatch(); return useMemo(() => { - return bindActionCreators(actions, dispatch); + // bindActionCreators from redux has the wrong type when using thunk actions + // so we cast to the correct type + return bindActionCreators( + actions, + dispatch + ) as any as BoundActionCreatorsMapObject; }, [actions, dispatch]); }; diff --git a/ts/state/ducks/audioPlayer.ts b/ts/state/ducks/audioPlayer.ts index fd9a3f2a7e..aa158a3f00 100644 --- a/ts/state/ducks/audioPlayer.ts +++ b/ts/state/ducks/audioPlayer.ts @@ -2,6 +2,7 @@ // SPDX-License-Identifier: AGPL-3.0-only import type { ThunkAction } from 'redux-thunk'; +import type { BoundActionCreatorsMapObject } from '../../hooks/useBoundActions'; import { useBoundActions } from '../../hooks/useBoundActions'; import { Sound } from '../../util/Sound'; import * as Errors from '../../types/errors'; @@ -106,7 +107,8 @@ export const actions = { setIsPlaying, }; -export const useActions = (): typeof actions => useBoundActions(actions); +export const useActions = (): BoundActionCreatorsMapObject => + useBoundActions(actions); function setCurrentTime(value: number): CurrentTimeUpdated { globalMessageAudio.currentTime = value; diff --git a/ts/state/ducks/audioRecorder.ts b/ts/state/ducks/audioRecorder.ts index ae3c83099c..675af80430 100644 --- a/ts/state/ducks/audioRecorder.ts +++ b/ts/state/ducks/audioRecorder.ts @@ -10,6 +10,7 @@ import type { StateType as RootStateType } from '../reducer'; import { fileToBytes } from '../../util/fileToBytes'; import { recorder } from '../../services/audioRecorder'; import { stringToMIMEType } from '../../types/MIME'; +import type { BoundActionCreatorsMapObject } from '../../hooks/useBoundActions'; import { useBoundActions } from '../../hooks/useBoundActions'; export enum ErrorDialogAudioRecorderType { @@ -76,7 +77,8 @@ export const actions = { startRecording, }; -export const useActions = (): typeof actions => useBoundActions(actions); +export const useActions = (): BoundActionCreatorsMapObject => + useBoundActions(actions); function startRecording(): ThunkAction< void, diff --git a/ts/state/ducks/conversations.ts b/ts/state/ducks/conversations.ts index 7bd4f3b9cf..8cbcb7a7ad 100644 --- a/ts/state/ducks/conversations.ts +++ b/ts/state/ducks/conversations.ts @@ -83,6 +83,7 @@ import { SelectedMessageSource, } from './conversationsEnums'; import { markViewed as messageUpdaterMarkViewed } from '../../services/MessageUpdater'; +import type { BoundActionCreatorsMapObject } from '../../hooks/useBoundActions'; import { useBoundActions } from '../../hooks/useBoundActions'; import type { NoopActionType } from './noop'; @@ -935,8 +936,9 @@ export const actions = { verifyConversationsStoppingSend, }; -export const useConversationsActions = (): typeof actions => - useBoundActions(actions); +export const useConversationsActions = (): BoundActionCreatorsMapObject< + typeof actions +> => useBoundActions(actions); function filterAvatarData( avatars: ReadonlyArray, @@ -2621,7 +2623,7 @@ function addMemberToGroup( function toggleGroupsForStorySend( conversationIds: Array -): ThunkAction { +): ThunkAction, RootStateType, unknown, NoopActionType> { return async dispatch => { await Promise.all( conversationIds.map(async conversationId => { diff --git a/ts/state/ducks/emojis.ts b/ts/state/ducks/emojis.ts index f74163815d..4fd5f54bfc 100644 --- a/ts/state/ducks/emojis.ts +++ b/ts/state/ducks/emojis.ts @@ -5,6 +5,7 @@ import { take, uniq } from 'lodash'; import type { ThunkAction } from 'redux-thunk'; import type { EmojiPickDataType } from '../../components/emoji/EmojiPicker'; import dataInterface from '../../sql/Client'; +import type { BoundActionCreatorsMapObject } from '../../hooks/useBoundActions'; import { useBoundActions } from '../../hooks/useBoundActions'; const { updateEmojiUsage } = dataInterface; @@ -31,7 +32,8 @@ export const actions = { useEmoji, }; -export const useActions = (): typeof actions => useBoundActions(actions); +export const useActions = (): BoundActionCreatorsMapObject => + useBoundActions(actions); function onUseEmoji({ shortName, diff --git a/ts/state/ducks/globalModals.ts b/ts/state/ducks/globalModals.ts index 49801539c9..819e8a041c 100644 --- a/ts/state/ducks/globalModals.ts +++ b/ts/state/ducks/globalModals.ts @@ -12,6 +12,7 @@ import type { UUIDStringType } from '../../types/UUID'; import * as SingleServePromise from '../../services/singleServePromise'; import { getMessageById } from '../../messages/getMessageById'; import { getMessagePropsSelector } from '../selectors/message'; +import type { BoundActionCreatorsMapObject } from '../../hooks/useBoundActions'; import { longRunningTaskWrapper } from '../../util/longRunningTaskWrapper'; import { useBoundActions } from '../../hooks/useBoundActions'; import { isGroupV1 } from '../../util/whatTypeOfConversation'; @@ -215,8 +216,9 @@ export const actions = { closeGV2MigrationDialog, }; -export const useGlobalModalActions = (): typeof actions => - useBoundActions(actions); +export const useGlobalModalActions = (): BoundActionCreatorsMapObject< + typeof actions +> => useBoundActions(actions); function hideContactModal(): HideContactModalActionType { return { diff --git a/ts/state/ducks/items.ts b/ts/state/ducks/items.ts index c75c619937..6f5b4c91a8 100644 --- a/ts/state/ducks/items.ts +++ b/ts/state/ducks/items.ts @@ -6,6 +6,7 @@ import { v4 as getGuid } from 'uuid'; import type { ThunkAction } from 'redux-thunk'; import type { StateType as RootStateType } from '../reducer'; import * as storageShim from '../../shims/storage'; +import type { BoundActionCreatorsMapObject } from '../../hooks/useBoundActions'; import { useBoundActions } from '../../hooks/useBoundActions'; import type { ConversationColorType, @@ -93,7 +94,8 @@ export const actions = { resetItems, }; -export const useActions = (): typeof actions => useBoundActions(actions); +export const useActions = (): BoundActionCreatorsMapObject => + useBoundActions(actions); function putItem( key: K, diff --git a/ts/state/ducks/linkPreviews.ts b/ts/state/ducks/linkPreviews.ts index d06753cadf..6336868c52 100644 --- a/ts/state/ducks/linkPreviews.ts +++ b/ts/state/ducks/linkPreviews.ts @@ -12,6 +12,7 @@ import type { } from '../../types/LinkPreview'; import { assignWithNoUnnecessaryAllocation } from '../../util/assignWithNoUnnecessaryAllocation'; import { maybeGrabLinkPreview } from '../../services/LinkPreview'; +import type { BoundActionCreatorsMapObject } from '../../hooks/useBoundActions'; import { useBoundActions } from '../../hooks/useBoundActions'; // State @@ -84,8 +85,9 @@ export const actions = { removeLinkPreview, }; -export const useLinkPreviewActions = (): typeof actions => - useBoundActions(actions); +export const useLinkPreviewActions = (): BoundActionCreatorsMapObject< + typeof actions +> => useBoundActions(actions); // Reducer diff --git a/ts/state/ducks/preferredReactions.ts b/ts/state/ducks/preferredReactions.ts index 50e7865e66..683d660d10 100644 --- a/ts/state/ducks/preferredReactions.ts +++ b/ts/state/ducks/preferredReactions.ts @@ -6,6 +6,7 @@ import { omit } from 'lodash'; import * as log from '../../logging/log'; import * as Errors from '../../types/errors'; import { replaceIndex } from '../../util/replaceIndex'; +import type { BoundActionCreatorsMapObject } from '../../hooks/useBoundActions'; import { useBoundActions } from '../../hooks/useBoundActions'; import type { StateType as RootStateType } from '../reducer'; import { DEFAULT_PREFERRED_REACTION_EMOJI_SHORT_NAMES } from '../../reactions/constants'; @@ -97,7 +98,8 @@ export const actions = { selectDraftEmojiToBeReplaced, }; -export const useActions = (): typeof actions => useBoundActions(actions); +export const useActions = (): BoundActionCreatorsMapObject => + useBoundActions(actions); function cancelCustomizePreferredReactionsModal(): CancelCustomizePreferredReactionsModalActionType { return { type: CANCEL_CUSTOMIZE_PREFERRED_REACTIONS_MODAL }; diff --git a/ts/state/ducks/stories.ts b/ts/state/ducks/stories.ts index a7e9c8cb86..0a494972c4 100644 --- a/ts/state/ducks/stories.ts +++ b/ts/state/ducks/stories.ts @@ -51,6 +51,7 @@ import { isGroup } from '../../util/whatTypeOfConversation'; import { isNotNil } from '../../util/isNotNil'; import { isStory } from '../../messages/helpers'; import { sendStoryMessage as doSendStoryMessage } from '../../util/sendStoryMessage'; +import type { BoundActionCreatorsMapObject } from '../../hooks/useBoundActions'; import { useBoundActions } from '../../hooks/useBoundActions'; import { verifyStoryListMembers as doVerifyStoryListMembers } from '../../util/verifyStoryListMembers'; import { viewSyncJobQueue } from '../../jobs/viewSyncJobQueue'; @@ -1317,7 +1318,9 @@ export const actions = { setStorySending, }; -export const useStoriesActions = (): typeof actions => useBoundActions(actions); +export const useStoriesActions = (): BoundActionCreatorsMapObject< + typeof actions +> => useBoundActions(actions); // Reducer diff --git a/ts/state/ducks/storyDistributionLists.ts b/ts/state/ducks/storyDistributionLists.ts index 860d73195f..e1eca03f9d 100644 --- a/ts/state/ducks/storyDistributionLists.ts +++ b/ts/state/ducks/storyDistributionLists.ts @@ -14,6 +14,7 @@ import { UUID } from '../../types/UUID'; import { deleteStoryForEveryone } from '../../util/deleteStoryForEveryone'; import { replaceIndex } from '../../util/replaceIndex'; import { storageServiceUploadJob } from '../../services/storage'; +import type { BoundActionCreatorsMapObject } from '../../hooks/useBoundActions'; import { useBoundActions } from '../../hooks/useBoundActions'; // State @@ -156,7 +157,12 @@ function createDistributionList( memberUuids: Array, storageServiceDistributionListRecord?: StoryDistributionWithMembersType, shouldSave = true -): ThunkAction { +): ThunkAction< + Promise, + RootStateType, + string, + CreateListActionType +> { return async dispatch => { const storyDistribution: StoryDistributionWithMembersType = { allowsReplies: true, @@ -188,6 +194,8 @@ function createDistributionList( name: storyDistribution.name, }, }); + + return storyDistribution.id; }; } @@ -483,8 +491,8 @@ export const actions = { updateStoryViewers, }; -export const useStoryDistributionListsActions = (): typeof actions => - useBoundActions(actions); +export const useStoryDistributionListsActions = + (): BoundActionCreatorsMapObject => useBoundActions(actions); // Reducer diff --git a/ts/state/ducks/toast.ts b/ts/state/ducks/toast.ts index 4621fb6133..87271207b9 100644 --- a/ts/state/ducks/toast.ts +++ b/ts/state/ducks/toast.ts @@ -1,6 +1,7 @@ // Copyright 2021 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only +import type { BoundActionCreatorsMapObject } from '../../hooks/useBoundActions'; import { useBoundActions } from '../../hooks/useBoundActions'; import type { ReplacementValuesType } from '../../types/Util'; @@ -94,7 +95,9 @@ export const actions = { showToast, }; -export const useToastActions = (): typeof actions => useBoundActions(actions); +export const useToastActions = (): BoundActionCreatorsMapObject< + typeof actions +> => useBoundActions(actions); // Reducer