diff --git a/packages/common/src/store/pages/chat/sagas.ts b/packages/common/src/store/pages/chat/sagas.ts index aceb4188e2..6bbaa8803c 100644 --- a/packages/common/src/store/pages/chat/sagas.ts +++ b/packages/common/src/store/pages/chat/sagas.ts @@ -373,7 +373,7 @@ function* doSetMessageReaction(action: ReturnType) { } function* doCreateChat(action: ReturnType) { - const { userIds, skipNavigation } = action.payload + const { userIds, skipNavigation, presetMessage } = action.payload const { track, make } = yield* getContext('analytics') try { const audiusSdk = yield* getContext('audiusSdk') @@ -387,7 +387,7 @@ function* doCreateChat(action: ReturnType) { // Optimistically go to the chat. If we fail to create it, we'll toast if (!skipNavigation) { - yield* put(goToChat({ chatId })) + yield* put(goToChat({ chatId, presetMessage })) } try { diff --git a/packages/common/src/store/pages/chat/slice.ts b/packages/common/src/store/pages/chat/slice.ts index 0f8fb9d280..fc11c42b95 100644 --- a/packages/common/src/store/pages/chat/slice.ts +++ b/packages/common/src/store/pages/chat/slice.ts @@ -128,7 +128,11 @@ const slice = createSlice({ reducers: { createChat: ( _state, - _action: PayloadAction<{ userIds: ID[]; skipNavigation?: boolean }> + _action: PayloadAction<{ + userIds: ID[] + skipNavigation?: boolean + presetMessage?: string + }> ) => { // triggers saga }, @@ -153,7 +157,10 @@ const slice = createSlice({ delete state.optimisticUnreadMessagesCount }, fetchUnreadMessagesCountFailed: (_state) => {}, - goToChat: (_state, _action: PayloadAction<{ chatId?: string }>) => { + goToChat: ( + _state, + _action: PayloadAction<{ chatId?: string; presetMessage?: string }> + ) => { // triggers saga }, fetchLatestChats: (state) => { diff --git a/packages/common/src/store/ui/create-chat-modal/slice.ts b/packages/common/src/store/ui/create-chat-modal/slice.ts index de56b2adc6..88bf23c9d2 100644 --- a/packages/common/src/store/ui/create-chat-modal/slice.ts +++ b/packages/common/src/store/ui/create-chat-modal/slice.ts @@ -7,6 +7,9 @@ export type CreateChatModalState = { const initialState: CreateChatModalState = {} +// This is only really relevant on Web, where we don't have the ability to route the URL to the modal +// On mobile, the ChatUserListScreen is a screen and we can pass the presetMessage in as a param, +// and navigation is handled nicely, no need for a cancel action. const slice = createSlice({ name: 'application/ui/createChatModal', initialState, diff --git a/packages/mobile/src/components/share-drawer/ShareDrawer.tsx b/packages/mobile/src/components/share-drawer/ShareDrawer.tsx index db065e9a0a..d3f05027ef 100644 --- a/packages/mobile/src/components/share-drawer/ShareDrawer.tsx +++ b/packages/mobile/src/components/share-drawer/ShareDrawer.tsx @@ -3,7 +3,6 @@ import React, { useCallback, useRef } from 'react' import { accountSelectors, collectionsSocialActions, - createChatModalActions, FeatureFlags, shareModalUISelectors, shareSoundToTiktokModalActions, @@ -43,7 +42,6 @@ const { shareUser } = usersSocialActions const { shareTrack } = tracksSocialActions const { shareCollection } = collectionsSocialActions const { getAccountUser } = accountSelectors -const { setState: setCreateChatModalState } = createChatModalActions export const shareToastTimeout = 1500 @@ -108,14 +106,10 @@ export const ShareDrawer = () => { const handleShareToDirectMessage = useCallback(async () => { if (!content) return - navigation.navigate('ChatUserList') - dispatch( - setCreateChatModalState({ - // Just care about the link - presetMessage: getContentUrl(content) - }) - ) - }, [content, dispatch, navigation]) + navigation.navigate('ChatUserList', { + presetMessage: getContentUrl(content) + }) + }, [content, navigation]) const handleShareToTwitter = useCallback(async () => { if (!content) return diff --git a/packages/mobile/src/screens/app-screen/AppTabScreen.tsx b/packages/mobile/src/screens/app-screen/AppTabScreen.tsx index 9155f46a71..ef08b961dc 100644 --- a/packages/mobile/src/screens/app-screen/AppTabScreen.tsx +++ b/packages/mobile/src/screens/app-screen/AppTabScreen.tsx @@ -108,9 +108,14 @@ export type AppTabScreenParamList = { EditTrack: { id: ID } WalletConnect: undefined ChatList: undefined - ChatUserList: undefined + ChatUserList: + | { + presetMessage?: string + } + | undefined Chat: { chatId: string + presetMessage?: string } StripeOnrampEmbed: { clientSecret: string } } diff --git a/packages/mobile/src/screens/chat-screen/ChatScreen.tsx b/packages/mobile/src/screens/chat-screen/ChatScreen.tsx index 9d1b1e2ea1..c9e34f93ef 100644 --- a/packages/mobile/src/screens/chat-screen/ChatScreen.tsx +++ b/packages/mobile/src/screens/chat-screen/ChatScreen.tsx @@ -244,7 +244,7 @@ export const ChatScreen = () => { const navigation = useNavigation() const { params } = useRoute<'Chat'>() - const { chatId } = params + const { chatId, presetMessage } = params const url = `/chat/${encodeUrlName(chatId ?? '')}` const hasScrolledToUnreadTag = useRef(false) @@ -686,6 +686,7 @@ export const ChatScreen = () => { diff --git a/packages/mobile/src/screens/chat-screen/ChatTextInput.tsx b/packages/mobile/src/screens/chat-screen/ChatTextInput.tsx index ba31a7a1ee..a68d3288cb 100644 --- a/packages/mobile/src/screens/chat-screen/ChatTextInput.tsx +++ b/packages/mobile/src/screens/chat-screen/ChatTextInput.tsx @@ -1,10 +1,6 @@ import { useCallback, useState } from 'react' -import { - chatActions, - createChatModalSelectors, - playerSelectors -} from '@audius/common' +import { chatActions, playerSelectors } from '@audius/common' import { Platform, Pressable } from 'react-native' import { useDispatch, useSelector } from 'react-redux' @@ -17,7 +13,6 @@ import { useThemeColors } from 'app/utils/theme' const { sendMessage } = chatActions const { getHasTrack } = playerSelectors -const { getPresetMessage } = createChatModalSelectors const messages = { startNewMessage: ' Start a New Message' @@ -54,18 +49,19 @@ const useStyles = makeStyles(({ spacing, palette, typography }) => ({ type ChatTextInputProps = { chatId: string + presetMessage?: string onMessageSent: () => void } export const ChatTextInput = ({ chatId, + presetMessage, onMessageSent }: ChatTextInputProps) => { const styles = useStyles() const dispatch = useDispatch() const { primary, primaryDark2 } = useThemeColors() - const presetMessage = useSelector(getPresetMessage) const [inputMessage, setInputMessage] = useState(presetMessage ?? '') const hasLength = inputMessage.length > 0 const hasCurrentlyPlayingTrack = useSelector(getHasTrack) diff --git a/packages/mobile/src/screens/chat-screen/ChatUserListItem.tsx b/packages/mobile/src/screens/chat-screen/ChatUserListItem.tsx index 1fc1c14a64..1b55406da1 100644 --- a/packages/mobile/src/screens/chat-screen/ChatUserListItem.tsx +++ b/packages/mobile/src/screens/chat-screen/ChatUserListItem.tsx @@ -144,9 +144,13 @@ const ctaToTextMap: Record = { type ChatUserListItemProps = { userId: number + presetMessage?: string } -export const ChatUserListItem = ({ userId }: ChatUserListItemProps) => { +export const ChatUserListItem = ({ + userId, + presetMessage +}: ChatUserListItemProps) => { const styles = useStyles() const dispatch = useDispatch() const user = useSelector((state) => getUser(state, { id: userId })) @@ -158,9 +162,9 @@ export const ChatUserListItem = ({ userId }: ChatUserListItemProps) => { const handlePress = useCallback(() => { if (user?.user_id) { Keyboard.dismiss() - dispatch(createChat({ userIds: [user.user_id] })) + dispatch(createChat({ userIds: [user.user_id], presetMessage })) } - }, [dispatch, user?.user_id]) + }, [dispatch, presetMessage, user?.user_id]) const handleNotPermittedPress = useCallback(() => { if (user?.user_id) { diff --git a/packages/mobile/src/screens/chat-screen/ChatUserListScreen.tsx b/packages/mobile/src/screens/chat-screen/ChatUserListScreen.tsx index a6b508fdfc..d15e4c8bb2 100644 --- a/packages/mobile/src/screens/chat-screen/ChatUserListScreen.tsx +++ b/packages/mobile/src/screens/chat-screen/ChatUserListScreen.tsx @@ -25,6 +25,7 @@ import IconSearch from 'app/assets/images/iconSearch.svg' import MagnifyingGlass from 'app/assets/images/leftPointingMagnifyingGlass.png' import { Screen, ScreenContent, Text, TextInput } from 'app/components/core' import LoadingSpinner from 'app/components/loading-spinner' +import { useRoute } from 'app/hooks/useRoute' import { makeStyles } from 'app/styles' import { ChatUserListItem } from './ChatUserListItem' @@ -187,7 +188,8 @@ export const ChatUserListScreen = () => { const [query, setQuery] = useState('') const [inputValue, setInputValue] = useState('') const dispatch = useDispatch() - + const { params } = useRoute<'ChatUserList'>() + const presetMessage = params?.presetMessage const defaultUserList = useDefaultUserList() const queryUserList = useQueryUserList(query) @@ -284,7 +286,10 @@ export const ChatUserListScreen = () => { maintainVisibleContentPosition={{ minIndexForVisible: 0 }} data={users} renderItem={({ item }) => ( - + )} keyExtractor={(user: User) => user.handle} contentContainerStyle={styles.flatListContainer} diff --git a/packages/mobile/src/store/chat/sagas.ts b/packages/mobile/src/store/chat/sagas.ts index fffe57ce30..5673256454 100644 --- a/packages/mobile/src/store/chat/sagas.ts +++ b/packages/mobile/src/store/chat/sagas.ts @@ -8,7 +8,7 @@ const { goToChat } = chatActions function* watchGoToChat() { yield takeLatest(goToChat, function* (action: ReturnType) { const { - payload: { chatId } + payload: { chatId, presetMessage } } = action if (navigationRef.isReady()) { if (!chatId) { @@ -16,7 +16,7 @@ function* watchGoToChat() { navigationRef.navigate('ChatList') } else { // @ts-ignore navigationRef is not parametrized correctly (PAY-1141) - navigationRef.navigate('Chat', { chatId }) + navigationRef.navigate('Chat', { chatId, presetMessage }) } } }) diff --git a/packages/web/src/components/search-users-modal/SearchUsersModal.tsx b/packages/web/src/components/search-users-modal/SearchUsersModal.tsx index 464a6f2ede..8ffc80ea36 100644 --- a/packages/web/src/components/search-users-modal/SearchUsersModal.tsx +++ b/packages/web/src/components/search-users-modal/SearchUsersModal.tsx @@ -57,6 +57,7 @@ type SearchUsersModalProps = { renderEmpty?: () => ReactNode renderUser: (user: User, closeParentModal: () => void) => ReactNode onCancel?: () => void + onClose?: () => void } export const SearchUsersModal = (props: SearchUsersModalProps) => { @@ -72,6 +73,7 @@ export const SearchUsersModal = (props: SearchUsersModalProps) => { }, renderUser, renderEmpty = () => null, + onClose, onCancel } = props const dispatch = useDispatch() @@ -102,7 +104,8 @@ export const SearchUsersModal = (props: SearchUsersModalProps) => { const handleClose = useCallback(() => { setIsVisible(false) setQuery('') - }, [setIsVisible]) + onClose?.() + }, [setIsVisible, onClose]) const handleCancel = useCallback(() => { handleClose() diff --git a/packages/web/src/pages/chat-page/ChatPage.tsx b/packages/web/src/pages/chat-page/ChatPage.tsx index 2c7a97a6c3..3d1c17c683 100644 --- a/packages/web/src/pages/chat-page/ChatPage.tsx +++ b/packages/web/src/pages/chat-page/ChatPage.tsx @@ -30,7 +30,13 @@ const messages = { messages: 'Messages' } -export const ChatPage = ({ currentChatId }: { currentChatId?: string }) => { +export const ChatPage = ({ + currentChatId, + presetMessage +}: { + currentChatId?: string + presetMessage?: string +}) => { const dispatch = useDispatch() const { isEnabled: isChatEnabled } = useFlag(FeatureFlags.CHAT_ENABLED) const { firstOtherUser, canSendMessage } = useCanSendMessage(currentChatId) @@ -113,6 +119,7 @@ export const ChatPage = ({ currentChatId }: { currentChatId?: string }) => { ) : null} diff --git a/packages/web/src/pages/chat-page/ChatPageProvider.tsx b/packages/web/src/pages/chat-page/ChatPageProvider.tsx index 1845032113..47477268be 100644 --- a/packages/web/src/pages/chat-page/ChatPageProvider.tsx +++ b/packages/web/src/pages/chat-page/ChatPageProvider.tsx @@ -6,12 +6,23 @@ import { ChatPage as DesktopChatPage } from './ChatPage' import { SkeletonChatPage as MobileChatPage } from './components/mobile/SkeletonChatPage' export const ChatPageProvider = ({ - match -}: RouteComponentProps<{ id?: string }>) => { + match, + location +}: RouteComponentProps< + { id?: string }, + any, + { presetMessage?: string } | undefined +>) => { const currentChatId = match.params.id + const presetMessage = location.state?.presetMessage const isMobile = useIsMobile() if (isMobile) { return } - return + return ( + + ) } diff --git a/packages/web/src/pages/chat-page/components/ChatComposer.tsx b/packages/web/src/pages/chat-page/components/ChatComposer.tsx index a45eb0ba4a..8ec61b7bad 100644 --- a/packages/web/src/pages/chat-page/components/ChatComposer.tsx +++ b/packages/web/src/pages/chat-page/components/ChatComposer.tsx @@ -8,18 +8,16 @@ import { useEffect } from 'react' -import { chatActions, createChatModalSelectors } from '@audius/common' +import { chatActions } from '@audius/common' import { IconButton, IconSend } from '@audius/stems' import cn from 'classnames' import { useDispatch } from 'react-redux' import { TextAreaV2 } from 'components/data-entry/TextAreaV2' -import { useSelector } from 'utils/reducer' import styles from './ChatComposer.module.css' const { sendMessage } = chatActions -const { getPresetMessage } = createChatModalSelectors const messages = { sendMessage: 'Send Message', @@ -30,6 +28,7 @@ const ENTER_KEY = 'Enter' export type ChatComposerProps = ComponentPropsWithoutRef<'div'> & { chatId?: string + presetMessage?: string onMessageSent: () => void } @@ -50,10 +49,9 @@ export const ChatSendButton = ({ disabled }: ChatSendButtonProps) => { } export const ChatComposer = (props: ChatComposerProps) => { - const { chatId, onMessageSent } = props + const { chatId, presetMessage, onMessageSent } = props const dispatch = useDispatch() - const presetMessage = useSelector(getPresetMessage) - const [value, setValue] = useState(presetMessage) + const [value, setValue] = useState(presetMessage ?? '') const ref = useRef(null) const chatIdRef = useRef(chatId) diff --git a/packages/web/src/pages/chat-page/components/CreateChatModal.tsx b/packages/web/src/pages/chat-page/components/CreateChatModal.tsx index 2a851bc744..6cb99e840f 100644 --- a/packages/web/src/pages/chat-page/components/CreateChatModal.tsx +++ b/packages/web/src/pages/chat-page/components/CreateChatModal.tsx @@ -28,7 +28,7 @@ const messages = { const { getAccountUser } = accountSelectors const { fetchBlockers } = chatActions -const { getOnCancelAction } = createChatModalSelectors +const { getOnCancelAction, getPresetMessage } = createChatModalSelectors const { setState } = createChatModalActions const CREATE_CHAT_MODAL = 'CreateChat' @@ -38,6 +38,7 @@ export const CreateChatModal = () => { const currentUser = useSelector(getAccountUser) const [isVisible, setIsVisible] = useModalState(CREATE_CHAT_MODAL) const onCancelAction = useSelector(getOnCancelAction) + const presetMessage = useSelector(getPresetMessage) const [user, setUser] = useState() const [showInboxUnavailableModal, setShowInboxUnavailableModal] = useState(false) @@ -46,6 +47,10 @@ export const CreateChatModal = () => { followersUserListSelectors.getUserList ) + const handleClose = useCallback(() => { + dispatch(setState({})) + }, [dispatch]) + const handleCancel = useCallback(() => { if (onCancelAction) { dispatch(onCancelAction) @@ -104,9 +109,11 @@ export const CreateChatModal = () => { user={user} openInboxUnavailableModal={handleOpenInboxUnavailableModal} closeParentModal={closeParentModal} + presetMessage={presetMessage} /> )} renderEmpty={() => } + onClose={handleClose} onCancel={handleCancel} /> {user ? ( diff --git a/packages/web/src/pages/chat-page/components/CreateChatUserResult.tsx b/packages/web/src/pages/chat-page/components/CreateChatUserResult.tsx index a739365c80..7bcf476ba0 100644 --- a/packages/web/src/pages/chat-page/components/CreateChatUserResult.tsx +++ b/packages/web/src/pages/chat-page/components/CreateChatUserResult.tsx @@ -44,6 +44,7 @@ type UserResultComposeProps = { user: User closeParentModal: () => void openInboxUnavailableModal: (user: User) => void + presetMessage?: string } const { getUserId } = accountSelectors @@ -93,7 +94,8 @@ const renderCustomChip = (callToAction: ChatPermissionAction) => { export const MessageUserSearchResult = (props: UserResultComposeProps) => { const dispatch = useDispatch() - const { user, closeParentModal, openInboxUnavailableModal } = props + const { user, closeParentModal, openInboxUnavailableModal, presetMessage } = + props const currentUserId = useSelector(getUserId) const supportingMap = useSelector(getOptimisticSupporting) const supportersMap = useSelector(getOptimisticSupporters) @@ -107,7 +109,7 @@ export const MessageUserSearchResult = (props: UserResultComposeProps) => { const handleComposeClicked = useCallback(() => { if (canCreateChat) { closeParentModal() - dispatch(createChat({ userIds: [user.user_id] })) + dispatch(createChat({ userIds: [user.user_id], presetMessage })) } else { openInboxUnavailableModal(user) } @@ -116,7 +118,8 @@ export const MessageUserSearchResult = (props: UserResultComposeProps) => { user, canCreateChat, openInboxUnavailableModal, - closeParentModal + closeParentModal, + presetMessage ]) const handleVisitClicked = useCallback(() => { diff --git a/packages/web/src/store/application/ui/chat/sagas.ts b/packages/web/src/store/application/ui/chat/sagas.ts index 6f5a8875b5..dcd75c9ded 100644 --- a/packages/web/src/store/application/ui/chat/sagas.ts +++ b/packages/web/src/store/application/ui/chat/sagas.ts @@ -10,12 +10,12 @@ const { goToChat } = chatActions function* watchGoToChat() { yield takeLatest(goToChat, function* (action: ReturnType) { const { - payload: { chatId } + payload: { chatId, presetMessage } } = action if (!chatId) { yield* put(pushRoute(CHATS_PAGE)) } else { - yield* put(pushRoute(chatPage(chatId))) + yield* put(pushRoute(chatPage(chatId), { presetMessage })) } }) }