diff --git a/apps/audius-client/packages/mobile/src/app/Drawers.tsx b/apps/audius-client/packages/mobile/src/app/Drawers.tsx
index f4b33bb40c6..e5203ae1559 100644
--- a/apps/audius-client/packages/mobile/src/app/Drawers.tsx
+++ b/apps/audius-client/packages/mobile/src/app/Drawers.tsx
@@ -18,6 +18,7 @@ import { EnablePushNotificationsDrawer } from 'app/components/enable-push-notifi
import { FeedFilterDrawer } from 'app/components/feed-filter-drawer'
import { ForgotPasswordDrawer } from 'app/components/forgot-password-drawer'
import { GatedContentUploadPromptDrawer } from 'app/components/gated-content-upload-prompt-drawer/GatedContentUploadPromptDrawer'
+import { InboxUnavailableDrawer } from 'app/components/inbox-unavailable-drawer/InboxUnavailableDrawer'
import { LockedContentDrawer } from 'app/components/locked-content-drawer'
import { OverflowMenuDrawer } from 'app/components/overflow-menu-drawer'
import { PlaybackRateDrawer } from 'app/components/playback-rate-drawer'
@@ -118,7 +119,8 @@ const nativeDrawersMap: { [DrawerName in Drawer]?: ComponentType } = {
GatedContentUploadPrompt: GatedContentUploadPromptDrawer,
ChatActions: ChatActionsDrawer,
BlockMessages: BlockMessagesDrawer,
- SupportersInfo: SupportersInfoDrawer
+ SupportersInfo: SupportersInfoDrawer,
+ InboxUnavailable: InboxUnavailableDrawer
}
const commonDrawers = Object.entries(commonDrawersMap) as [
diff --git a/apps/audius-client/packages/mobile/src/assets/images/iconMessageLocked.svg b/apps/audius-client/packages/mobile/src/assets/images/iconMessageLocked.svg
new file mode 100644
index 00000000000..c8a3b303043
--- /dev/null
+++ b/apps/audius-client/packages/mobile/src/assets/images/iconMessageLocked.svg
@@ -0,0 +1,4 @@
+
diff --git a/apps/audius-client/packages/mobile/src/components/inbox-unavailable-drawer/InboxUnavailableDrawer.tsx b/apps/audius-client/packages/mobile/src/components/inbox-unavailable-drawer/InboxUnavailableDrawer.tsx
new file mode 100644
index 00000000000..d1d8890043f
--- /dev/null
+++ b/apps/audius-client/packages/mobile/src/components/inbox-unavailable-drawer/InboxUnavailableDrawer.tsx
@@ -0,0 +1,260 @@
+import type { ReactNode } from 'react'
+import { useCallback, useMemo } from 'react'
+
+import type { User } from '@audius/common'
+import {
+ chatSelectors,
+ chatActions,
+ tippingActions,
+ cacheUsersSelectors,
+ ChatPermissionAction
+} from '@audius/common'
+import { View } from 'react-native'
+import { useDispatch, useSelector } from 'react-redux'
+
+import IconMessageLocked from 'app/assets/images/iconMessageLocked.svg'
+import IconTip from 'app/assets/images/iconTip.svg'
+import { Text, Button } from 'app/components/core'
+import { NativeDrawer } from 'app/components/drawer'
+import { useNavigation } from 'app/hooks/useNavigation'
+import { getData } from 'app/store/drawers/selectors'
+import { setVisibility } from 'app/store/drawers/slice'
+import { makeStyles, flexRowCentered } from 'app/styles'
+import { useColor } from 'app/utils/theme'
+
+import { UserBadges } from '../user-badges'
+
+const { unblockUser } = chatActions
+const { getCanChat } = chatSelectors
+const { getUser } = cacheUsersSelectors
+const { beginTip } = tippingActions
+
+const INBOX_UNAVAILABLE_MODAL_NAME = 'InboxUnavailable'
+
+const messages = {
+ title: 'Inbox Unavailable',
+ blockee: 'You cannot send messages to users you have blocked.',
+ tipGated: (displayName: ReactNode) => (
+ <>
+ {'You must send '}
+ {displayName}
+ {' a tip before you can send them messages.'}
+ >
+ ),
+ noAction: 'You can no longer send messages to ',
+ info: 'This will not affect their ability to view your profile or interact with your content.',
+ unblockUser: 'Unblock User',
+ learnMore: 'Learn More',
+ sendAudio: 'Send $AUDIO',
+ cancel: 'Cancel'
+}
+
+const useStyles = makeStyles(({ spacing, typography, palette }) => ({
+ drawer: {
+ marginTop: spacing(2),
+ marginBottom: spacing(5),
+ padding: spacing(3.5),
+ gap: spacing(4)
+ },
+ titleContainer: {
+ ...flexRowCentered(),
+ gap: spacing(3.5),
+ alignSelf: 'center'
+ },
+ title: {
+ fontSize: typography.fontSize.xl,
+ fontFamily: typography.fontByWeight.heavy,
+ color: palette.neutralLight2,
+ textTransform: 'uppercase',
+ lineHeight: typography.fontSize.xl * 1.25
+ },
+ infoContainer: {
+ display: 'flex',
+ flexDirection: 'row',
+ alignItems: 'center',
+ gap: spacing(4.5),
+ paddingVertical: spacing(2),
+ paddingHorizontal: spacing(4),
+ backgroundColor: palette.neutralLight9,
+ borderWidth: 1,
+ borderColor: palette.neutralLight7,
+ borderRadius: spacing(2)
+ },
+ callToActionText: {
+ color: palette.neutral,
+ fontSize: typography.fontSize.large,
+ fontFamily: typography.fontByWeight.medium,
+ lineHeight: typography.fontSize.large * 1.3,
+ textAlign: 'center'
+ },
+ button: {
+ padding: spacing(4),
+ height: spacing(12)
+ },
+ buttonText: {
+ color: palette.neutral,
+ fontSize: typography.fontSize.large,
+ fontFamily: typography.fontByWeight.bold
+ },
+ buttonTextWhite: {
+ color: palette.white
+ },
+ border: {
+ borderBottomWidth: 1,
+ borderBottomColor: palette.neutralLight8
+ }
+}))
+
+const mapActionToContent = (
+ callToAction: ChatPermissionAction,
+ styles: ReturnType,
+ user: User | null
+) => {
+ switch (callToAction) {
+ case ChatPermissionAction.NONE:
+ return (
+ <>
+ {messages.noAction}
+ {user ? (
+
+ ) : null}
+ >
+ )
+ case ChatPermissionAction.TIP:
+ return (
+ <>
+ {messages.tipGated(
+ user ? (
+
+ ) : null
+ )}
+ >
+ )
+ case ChatPermissionAction.UNBLOCK:
+ return <>{messages.blockee}>
+ default:
+ return null
+ }
+}
+
+export const InboxUnavailableDrawer = () => {
+ const dispatch = useDispatch()
+ const navigation = useNavigation()
+ const styles = useStyles()
+ const neutralLight2 = useColor('neutralLight2')
+
+ const { userId } = useSelector((state) => getData<'InboxUnavailable'>(state))
+ const user = useSelector((state) => getUser(state, { id: userId }))
+ const { callToAction } = useSelector((state) => getCanChat(state, userId))
+
+ const closeDrawer = useCallback(() => {
+ dispatch(
+ setVisibility({
+ drawer: 'InboxUnavailable',
+ visible: false
+ })
+ )
+ }, [dispatch])
+
+ const handleUnblockPress = useCallback(() => {
+ dispatch(unblockUser({ userId }))
+ closeDrawer()
+ }, [dispatch, userId, closeDrawer])
+
+ const handleLearnMorePress = useCallback(() => {
+ // TODO: Link to blog
+ closeDrawer()
+ }, [closeDrawer])
+
+ const handleTipPress = useCallback(() => {
+ dispatch(beginTip({ user, source: 'profile' }))
+ navigation.navigate('TipArtist')
+ closeDrawer()
+ }, [closeDrawer, dispatch, navigation, user])
+
+ const actionToButtonsMap = useMemo(() => {
+ return {
+ [ChatPermissionAction.NONE]: [
+ {
+ buttonTitle: messages.learnMore,
+ buttonPress: handleLearnMorePress,
+ buttonVariant: 'common'
+ }
+ ],
+ [ChatPermissionAction.TIP]: [
+ {
+ buttonTitle: messages.sendAudio,
+ buttonPress: handleTipPress,
+ buttonVariant: 'primary',
+ buttonIcon: IconTip,
+ buttonTextStyle: styles.buttonTextWhite
+ }
+ ],
+ [ChatPermissionAction.UNBLOCK]: [
+ {
+ buttonTitle: messages.unblockUser,
+ buttonPress: handleUnblockPress,
+ buttonVariant: 'primary',
+ buttonTextStyle: styles.buttonTextWhite
+ },
+ {
+ buttonTitle: messages.cancel,
+ buttonPress: closeDrawer,
+ buttonVariant: 'common'
+ }
+ ]
+ }
+ }, [
+ handleLearnMorePress,
+ handleTipPress,
+ styles.buttonTextWhite,
+ handleUnblockPress,
+ closeDrawer
+ ])
+
+ const content = mapActionToContent(callToAction, styles, user)
+
+ return (
+
+
+
+
+ {messages.title}
+
+
+ {content}
+ {actionToButtonsMap[callToAction].map(
+ ({
+ buttonTitle,
+ buttonPress,
+ buttonVariant,
+ buttonIcon,
+ buttonTextStyle
+ }) => (
+
+ )
+ )}
+
+
+ )
+}
diff --git a/apps/audius-client/packages/mobile/src/components/inbox-unavailable-drawer/index.ts b/apps/audius-client/packages/mobile/src/components/inbox-unavailable-drawer/index.ts
new file mode 100644
index 00000000000..12b19ce68eb
--- /dev/null
+++ b/apps/audius-client/packages/mobile/src/components/inbox-unavailable-drawer/index.ts
@@ -0,0 +1 @@
+export * from './InboxUnavailableDrawer'
diff --git a/apps/audius-client/packages/mobile/src/components/user-badges/UserBadges.tsx b/apps/audius-client/packages/mobile/src/components/user-badges/UserBadges.tsx
index 5a26293c867..f3dc3a544cb 100644
--- a/apps/audius-client/packages/mobile/src/components/user-badges/UserBadges.tsx
+++ b/apps/audius-client/packages/mobile/src/components/user-badges/UserBadges.tsx
@@ -1,3 +1,5 @@
+import type { ComponentType } from 'react'
+
import { useSelectTierInfo } from '@audius/common'
import type { User } from '@audius/common'
import type { ViewStyle, StyleProp, TextStyle } from 'react-native'
@@ -13,6 +15,7 @@ type UserBadgesProps = {
style?: StyleProp
nameStyle?: StyleProp
hideName?: boolean
+ as?: ComponentType
}
const styles = StyleSheet.create({
@@ -27,12 +30,19 @@ const styles = StyleSheet.create({
})
export const UserBadges = (props: UserBadgesProps) => {
- const { user, badgeSize = 14, style, nameStyle, hideName } = props
+ const {
+ user,
+ badgeSize = 14,
+ style,
+ nameStyle,
+ hideName,
+ as: Component = View
+ } = props
const { tier } = useSelectTierInfo(user.user_id)
const palette = useThemePalette()
return (
-
+
{hideName ? null : (
{user.name}
@@ -53,7 +63,7 @@ export const UserBadges = (props: UserBadgesProps) => {
height={badgeSize + 2}
width={badgeSize + 2}
/>
-
+
)
}
diff --git a/apps/audius-client/packages/mobile/src/screens/profile-screen/MessageLockedButton.tsx b/apps/audius-client/packages/mobile/src/screens/profile-screen/MessageLockedButton.tsx
new file mode 100644
index 00000000000..aaff8e382a0
--- /dev/null
+++ b/apps/audius-client/packages/mobile/src/screens/profile-screen/MessageLockedButton.tsx
@@ -0,0 +1,56 @@
+import { useCallback } from 'react'
+
+import type { User } from '@audius/common'
+import { useDispatch } from 'react-redux'
+
+import IconMessageLocked from 'app/assets/images/iconMessageLocked.svg'
+import { Button } from 'app/components/core'
+import { setVisibility } from 'app/store/drawers/slice'
+import { makeStyles } from 'app/styles'
+
+const messages = {
+ inboxUnavailable: 'Inbox Unavailable'
+}
+
+const useStyles = makeStyles(({ palette, spacing }) => ({
+ root: {
+ paddingHorizontal: 0,
+ height: spacing(8),
+ width: spacing(8),
+ marginRight: spacing(2),
+ borderColor: palette.neutralLight4
+ }
+}))
+
+type MessageLockedButtonProps = {
+ profile: Pick
+}
+
+export const MessageLockedButton = (props: MessageLockedButtonProps) => {
+ const styles = useStyles()
+ const { profile } = props
+ const { user_id: userId } = profile
+ const dispatch = useDispatch()
+
+ const handlePress = useCallback(() => {
+ dispatch(
+ setVisibility({
+ drawer: 'InboxUnavailable',
+ visible: true,
+ data: { userId }
+ })
+ )
+ }, [dispatch, userId])
+
+ return (
+
+ )
+}
diff --git a/apps/audius-client/packages/mobile/src/screens/profile-screen/ProfileInfo.tsx b/apps/audius-client/packages/mobile/src/screens/profile-screen/ProfileInfo.tsx
index 33d586a1e8f..cb2bc88214b 100644
--- a/apps/audius-client/packages/mobile/src/screens/profile-screen/ProfileInfo.tsx
+++ b/apps/audius-client/packages/mobile/src/screens/profile-screen/ProfileInfo.tsx
@@ -1,11 +1,16 @@
+import { useEffect } from 'react'
+
import {
accountSelectors,
+ chatSelectors,
+ chatActions,
+ profilePageSelectors,
FollowSource,
reachabilitySelectors,
FeatureFlags
} from '@audius/common'
import { View, Text } from 'react-native'
-import { useSelector } from 'react-redux'
+import { useDispatch, useSelector } from 'react-redux'
import { FollowButton, FollowsYouChip } from 'app/components/user'
import UserBadges from 'app/components/user-badges'
@@ -15,10 +20,14 @@ import { flexRowCentered, makeStyles } from 'app/styles'
import { EditProfileButton } from './EditProfileButton'
import { MessageButton } from './MessageButton'
+import { MessageLockedButton } from './MessageLockedButton'
import { SubscribeButton } from './SubscribeButton'
import { useSelectProfile } from './selectors'
const { getUserHandle } = accountSelectors
+const { getCanChat } = chatSelectors
+const { fetchBlockees, fetchBlockers, fetchPermissions } = chatActions
+const { getProfileUserId } = profilePageSelectors
const useStyles = makeStyles(({ typography, palette, spacing }) => ({
name: {
@@ -98,8 +107,25 @@ export const ProfileInfo = (props: ProfileInfoProps) => {
const isReachable = useSelector(getIsReachable)
const accountHandle = useSelector(getUserHandle)
const styles = useStyles()
+ const dispatch = useDispatch()
const { isEnabled: isChatEnabled } = useFeatureFlag(FeatureFlags.CHAT_ENABLED)
+ const profileUserId = useSelector((state) =>
+ getProfileUserId(state, params.handle)
+ )
+ const { canChat } = useSelector((state) => getCanChat(state, profileUserId))
+
+ useEffect(() => {
+ dispatch(fetchBlockees())
+ dispatch(fetchBlockers())
+ }, [dispatch, params, profileUserId])
+
+ useEffect(() => {
+ if (profileUserId) {
+ dispatch(fetchPermissions({ userIds: [profileUserId] }))
+ }
+ }, [dispatch, profileUserId])
+
const profile = useSelectProfile([
'user_id',
'name',
@@ -122,7 +148,13 @@ export const ProfileInfo = (props: ProfileInfoProps) => {
) : (
<>
- {isChatEnabled && !isOwner ? : null}
+ {isChatEnabled && !isOwner ? (
+ canChat ? (
+
+ ) : (
+
+ )
+ ) : null}
{does_current_user_follow ? (
) : null}
diff --git a/apps/audius-client/packages/mobile/src/store/drawers/slice.ts b/apps/audius-client/packages/mobile/src/store/drawers/slice.ts
index f843d0a8f66..0380c50beb4 100644
--- a/apps/audius-client/packages/mobile/src/store/drawers/slice.ts
+++ b/apps/audius-client/packages/mobile/src/store/drawers/slice.ts
@@ -26,6 +26,7 @@ export type Drawer =
| 'ProfileActions'
| 'BlockMessages'
| 'SupportersInfo'
+ | 'InboxUnavailable'
export type DrawerData = {
EnablePushNotifications: undefined
@@ -54,6 +55,7 @@ export type DrawerData = {
ProfileActions: undefined
BlockMessages: { userId: number }
SupportersInfo: undefined
+ InboxUnavailable: { userId: number }
}
export type DrawersState = { [drawer in Drawer]: boolean | 'closing' } & {
@@ -82,6 +84,7 @@ const initialState: DrawersState = {
ProfileActions: false,
BlockMessages: false,
SupportersInfo: false,
+ InboxUnavailable: false,
data: null
}