refactor: simplify account standing and verification levels (#3130)

This commit is contained in:
Hampus authored and GitHub committed 2026-10-02 18:17:41 +02:00
1 parent 1abde06824
commit 1ec8f31253
488 files changed
+10390 -47117

No files matched your search

@@ -36,6 +36,7 @@ import * as TextCopyCommands from '@app/features/ui/commands/TextCopyCommands';
import * as ToastCommands from '@app/features/ui/commands/ToastCommands';
import MobileLayout from '@app/features/ui/state/MobileLayout';
import Users from '@app/features/user/state/Users';
import {blockIfAccountLimited} from '@app/features/user/utils/AccountLimitUtils';
import TtsUtils from '@app/features/voice/utils/VoiceTtsUtils';
import {
ChannelTypes,
@@ -632,6 +633,7 @@ export function requestMessageForward(
if (!currentUser) {
return;
}
if (blockIfAccountLimited()) return;
ModalCommands.push(
modal(() => (
<ForwardModal
@@ -3,7 +3,6 @@
import {
AccountTooNewBarrier,
DefaultBarrier,
NoPhoneNumberBarrier,
NotMemberLongEnoughBarrier,
SendMessageDisabledBarrier,
TimeoutBarrier,
@@ -47,8 +46,6 @@ export const VerificationBarrier = observer(({channel}: Props) => {
data-flx="channel.verification-barrier.not-member-long-enough-barrier"
/>
);
case VerificationFailureReason.NO_PHONE_NUMBER:
return <NoPhoneNumberBarrier data-flx="channel.verification-barrier.no-phone-number-barrier" />;
case VerificationFailureReason.SEND_MESSAGE_DISABLED:
return <SendMessageDisabledBarrier data-flx="channel.verification-barrier.send-message-disabled-barrier" />;
case VerificationFailureReason.TIMED_OUT:
@@ -2,9 +2,10 @@
import i18n from '@app/app/I18n';
import {GenericErrorModal} from '@app/features/app/components/alerts/GenericErrorModal';
import {failureCode} from '@app/features/platform/utils/ResponseInspection';
import {failureCode, failureMessage} from '@app/features/platform/utils/ResponseInspection';
import * as ModalCommands from '@app/features/ui/commands/ModalCommands';
import {modal} from '@app/features/ui/commands/ModalCommands';
import {ACCOUNT_LIMITED_NOTICE_DESCRIPTOR} from '@app/features/user/utils/AccountLimitUtils';
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {msg} from '@lingui/core/macro';
@@ -46,6 +47,9 @@ export function showGroupRecipientAddFailedModal(error: unknown): void {
case APIErrorCodes.RATE_LIMITED:
message = i18n._(RATE_LIMITED_DESCRIPTOR);
break;
case APIErrorCodes.ACCOUNT_LIMITED:
message = failureMessage(error) || i18n._(ACCOUNT_LIMITED_NOTICE_DESCRIPTOR);
break;
default:
message = i18n._(ADD_TO_GROUP_FAILED_MESSAGE_DESCRIPTOR);
break;
@@ -14,8 +14,10 @@ import {remFromPx} from '@app/features/theme/layout/RemFromPx';
import {Button} from '@app/features/ui/button/Button';
import * as ModalCommands from '@app/features/ui/commands/ModalCommands';
import {modal} from '@app/features/ui/commands/ModalCommands';
import {PhoneAddModal} from '@app/features/user/components/modals/PhoneAddModal';
import {BouncedEmailChangeModal} from '@app/features/user/components/modals/BouncedEmailChangeModal';
import {UserSettingsModal} from '@app/features/user/components/modals/UserSettingsModal';
import Users from '@app/features/user/state/Users';
import {ACCOUNT_LIMITED_NOTICE_DESCRIPTOR} from '@app/features/user/utils/AccountLimitUtils';
import {msg} from '@lingui/core/macro';
import {Trans, useLingui} from '@lingui/react/macro';
import {
@@ -23,7 +25,6 @@ import {
EnvelopeSimpleIcon,
InfoIcon,
MegaphoneSimpleIcon,
PhoneIcon,
ProhibitIcon,
ShieldWarningIcon,
TimerIcon,
@@ -181,13 +182,18 @@ export const UnverifiedEmailBarrier = observer(({onAction}: BarrierProps) => {
small={true}
onClick={() => {
onAction?.();
const bounced = Users.currentUser?.emailBounced === true;
ModalCommands.push(
modal(() => (
<UserSettingsModal
initialTab="account_security"
data-flx="channel.barriers.barrier-components.unverified-email-barrier.user-settings-modal"
/>
)),
modal(() =>
bounced ? (
<BouncedEmailChangeModal data-flx="channel.barriers.barrier-components.unverified-email-barrier.bounced-email-change-modal" />
) : (
<UserSettingsModal
initialTab="account_security"
data-flx="channel.barriers.barrier-components.unverified-email-barrier.user-settings-modal"
/>
),
),
);
}}
data-flx="channel.barriers.barrier-components.unverified-email-barrier.button.action"
@@ -245,34 +251,20 @@ export const NotMemberLongEnoughBarrier = observer(({initialTimeRemaining = 10 *
/>
);
});
export const NoPhoneNumberBarrier = observer(({onAction}: BarrierProps) => {
export const AccountLimitedBarrier = observer(() => {
const {i18n} = useLingui();
return (
<BarrierBase
message={<Trans>You need to verify a phone number to send messages in this community.</Trans>}
message={i18n._(ACCOUNT_LIMITED_NOTICE_DESCRIPTOR)}
icon={
<PhoneIcon
<InfoIcon
size={remFromPx(18)}
weight="fill"
data-flx="channel.barriers.barrier-components.no-phone-number-barrier.phone-icon"
data-flx="channel.barriers.barrier-components.account-limited-barrier.info-icon"
/>
}
action={
<Button
small={true}
onClick={() => {
onAction?.();
ModalCommands.push(
modal(() => (
<PhoneAddModal data-flx="channel.barriers.barrier-components.no-phone-number-barrier.phone-add-modal" />
)),
);
}}
data-flx="channel.barriers.barrier-components.no-phone-number-barrier.button.action"
>
<Trans>Verify phone</Trans>
</Button>
}
data-flx="channel.barriers.barrier-components.no-phone-number-barrier.barrier-base"
action={null}
data-flx="channel.barriers.barrier-components.account-limited-barrier.barrier-base"
/>
);
});
@@ -37,10 +37,7 @@ export const DeveloperToolsContextMenu: React.FC<{onClose: () => void}> = observ
<MenuGroup data-flx="channel.channel-header-components.developer-tools-context-menu.menu-group--3">
<GeneralDeveloperOptionsMenu data-flx="channel.channel-header-components.developer-tools-context-menu.general-developer-options-menu" />
<AccountPremiumMenu data-flx="channel.channel-header-components.developer-tools-context-menu.account-premium-menu" />
<MockingMenu
onClose={onClose}
data-flx="channel.channel-header-components.developer-tools-context-menu.mocking-menu"
/>
<MockingMenu data-flx="channel.channel-header-components.developer-tools-context-menu.mocking-menu" />
</MenuGroup>
<MenuGroup data-flx="channel.channel-header-components.developer-tools-context-menu.menu-group--4">
<NagbarsMenu data-flx="channel.channel-header-components.developer-tools-context-menu.nagbars-menu" />
@@ -221,34 +221,6 @@ const GEO_BLOCK_OVERLAY_DESCRIPTOR = msg({
message: 'Geo block overlay',
comment: 'Developer tools debug menu label. Internal-only surface for developers; translators may keep this terse.',
});
const REQUIRED_ACTIONS_OVERLAY_DESCRIPTOR = msg({
message: 'Required actions overlay',
comment: 'Developer tools debug menu label. Internal-only surface for developers; translators may keep this terse.',
});
const REQUIRED_ACTION_VARIANT_DESCRIPTOR = msg({
message: 'Required action variant',
comment: 'Developer tools debug menu label. Internal-only surface for developers; translators may keep this terse.',
});
const REQUIRED_ACTION_DEFAULT_TAB_DESCRIPTOR = msg({
message: 'Required action default tab',
comment: 'Developer tools debug menu label. Internal-only surface for developers; translators may keep this terse.',
});
const REQUIRED_ACTION_PHONE_STEP_DESCRIPTOR = msg({
message: 'Required action phone step',
comment: 'Developer tools debug menu label. Internal-only surface for developers; translators may keep this terse.',
});
const REQUIRED_ACTION_RESEND_LOADING_DESCRIPTOR = msg({
message: 'Required action resend loading',
comment: 'Developer tools debug menu label. Internal-only surface for developers; translators may keep this terse.',
});
const REQUIRED_ACTION_RESEND_OUTCOME_DESCRIPTOR = msg({
message: 'Required action resend outcome',
comment: 'Developer tools debug menu label. Internal-only surface for developers; translators may keep this terse.',
});
const REQUIRED_ACTION_REVERIFICATION_TEXT_DESCRIPTOR = msg({
message: 'Required action reverification text',
comment: 'Developer tools debug menu label. Internal-only surface for developers; translators may keep this terse.',
});
const NO_SEND_PERMISSION_DESCRIPTOR = msg({
message: 'No {sendMessagesPermission} permission',
comment: 'Developer tools debug menu label. Internal-only surface for developers; translators may keep this terse.',
@@ -324,10 +296,6 @@ const formatDeveloperOptionValue = <K extends keyof DeveloperOptionsState>(
case 'mockVerificationBarrier':
case 'mockMatureContentGateReason':
case 'mockMatureMediaGateReason':
case 'mockRequiredActionsMode':
case 'mockRequiredActionsSelectedTab':
case 'mockRequiredActionsPhoneStep':
case 'mockRequiredActionsResendOutcome':
case 'mockTitlebarPlatformOverride':
case 'mockUpdaterState':
return String(value).replace(/_/g, ' ');
@@ -448,20 +416,6 @@ export const getDeveloperOptionLabel = (key: keyof DeveloperOptionsState): Messa
return UK_GEO_DESCRIPTOR;
case 'mockGeoBlocked':
return GEO_BLOCK_OVERLAY_DESCRIPTOR;
case 'mockRequiredActionsOverlay':
return REQUIRED_ACTIONS_OVERLAY_DESCRIPTOR;
case 'mockRequiredActionsMode':
return REQUIRED_ACTION_VARIANT_DESCRIPTOR;
case 'mockRequiredActionsSelectedTab':
return REQUIRED_ACTION_DEFAULT_TAB_DESCRIPTOR;
case 'mockRequiredActionsPhoneStep':
return REQUIRED_ACTION_PHONE_STEP_DESCRIPTOR;
case 'mockRequiredActionsResending':
return REQUIRED_ACTION_RESEND_LOADING_DESCRIPTOR;
case 'mockRequiredActionsResendOutcome':
return REQUIRED_ACTION_RESEND_OUTCOME_DESCRIPTOR;
case 'mockRequiredActionsReverify':
return REQUIRED_ACTION_REVERIFICATION_TEXT_DESCRIPTOR;
case 'forceNoSendMessages':
return {...NO_SEND_PERMISSION_DESCRIPTOR, values: {sendMessagesPermission: SEND_MESSAGES_PERMISSION}};
case 'forceNoAttachFiles':
@@ -1,7 +1,6 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {ATTACH_FILES_PERMISSION, SEND_MESSAGES_PERMISSION} from '@app/features/app/config/I18nDisplayConstants';
import RequiredActionModal from '@app/features/auth/components/modals/RequiredActionModal';
import {
DeveloperOptionCheckbox,
DeveloperOptionRadioItems,
@@ -12,10 +11,6 @@ import {
getGiftDurationOptions,
getMatureContentChannelGateOptions,
getMatureContentMediaGateOptions,
getRequiredActionModeOptions,
getRequiredActionPhoneStepOptions,
getRequiredActionResendOutcomeOptions,
getRequiredActionTabOptions,
getVerificationBarrierOptions,
} from '@app/features/channel/components/channel_header_components/developer_tools/OptionPresets';
import {updateOption} from '@app/features/channel/components/channel_header_components/developer_tools/ResetOptions';
@@ -25,7 +20,6 @@ import {remFromPx} from '@app/features/theme/layout/RemFromPx';
import {MenuItem} from '@app/features/ui/action_menu/MenuItem';
import {MenuItemSlider} from '@app/features/ui/action_menu/MenuItemSlider';
import {MenuItemSubmenu} from '@app/features/ui/action_menu/MenuItemSubmenu';
import * as ModalCommands from '@app/features/ui/commands/ModalCommands';
import Users from '@app/features/user/state/Users';
import {msg, plural} from '@lingui/core/macro';
import {Trans, useLingui} from '@lingui/react/macro';
@@ -57,30 +51,10 @@ const SLOWMODE_TIME_REMAINING_DESCRIPTOR = msg({
message: 'Slowmode time remaining',
comment: 'Developer tools debug menu label. Internal-only surface for developers; translators may keep this terse.',
});
const REQUIRED_ACTION_FLOW_DESCRIPTOR = msg({
message: 'Required action flow',
comment: 'Developer tools debug menu label. Internal-only surface for developers; translators may keep this terse.',
});
const MOCK_VARIANT_DESCRIPTOR = msg({
message: 'Mock variant',
comment: 'Developer tools debug menu label. Internal-only surface for developers; translators may keep this terse.',
});
const MOCK_INCOMING_CALL_DESCRIPTOR = msg({
message: 'Mock incoming call',
comment: 'Developer tools debug menu label. Internal-only surface for developers; translators may keep this terse.',
});
const DEFAULT_TAB_DESCRIPTOR = msg({
message: 'Default tab',
comment: 'Developer tools debug menu label. Internal-only surface for developers; translators may keep this terse.',
});
const PHONE_STEP_DESCRIPTOR = msg({
message: 'Phone step',
comment: 'Developer tools debug menu label. Internal-only surface for developers; translators may keep this terse.',
});
const RESEND_OUTCOME_DESCRIPTOR = msg({
message: 'Resend outcome',
comment: 'Developer tools debug menu label. Internal-only surface for developers; translators may keep this terse.',
});
const CONTENT_SAFETY_GATES_DESCRIPTOR = msg({
message: 'Content safety gates',
comment: 'Developer tools debug menu label. Internal-only surface for developers; translators may keep this terse.',
@@ -190,71 +164,6 @@ const SlowmodeSimulationMenu: React.FC = observer(() => {
/>
);
});
const RequiredActionFlowMenu: React.FC<{onOpenOverlay: () => void}> = observer(({onOpenOverlay}) => {
const {i18n} = useLingui();
const shouldShowDefaultTab = DeveloperOptions.mockRequiredActionsMode === 'email_or_phone';
const shouldShowPhoneStep =
DeveloperOptions.mockRequiredActionsMode === 'phone' ||
(DeveloperOptions.mockRequiredActionsMode === 'email_or_phone' &&
DeveloperOptions.mockRequiredActionsSelectedTab === 'phone');
return (
<MenuItemSubmenu
label={i18n._(REQUIRED_ACTION_FLOW_DESCRIPTOR)}
render={() => (
<>
<DeveloperOptionRadioSubmenu
label={i18n._(MOCK_VARIANT_DESCRIPTOR)}
optionKey="mockRequiredActionsMode"
options={getRequiredActionModeOptions()}
data-flx="channel.channel-header-components.developer-tools-context-menu.required-action-flow-menu.developer-option-radio-submenu"
/>
<MenuItem
disabled={!Users.currentUser}
onClick={onOpenOverlay}
data-flx="channel.channel-header-components.developer-tools-context-menu.required-action-flow-menu.menu-item.open-overlay"
>
<Trans>Open overlay</Trans>
</MenuItem>
{shouldShowDefaultTab && (
<DeveloperOptionRadioSubmenu
label={i18n._(DEFAULT_TAB_DESCRIPTOR)}
optionKey="mockRequiredActionsSelectedTab"
options={getRequiredActionTabOptions()}
data-flx="channel.channel-header-components.developer-tools-context-menu.required-action-flow-menu.developer-option-radio-submenu--2"
/>
)}
{shouldShowPhoneStep && (
<DeveloperOptionRadioSubmenu
label={i18n._(PHONE_STEP_DESCRIPTOR)}
optionKey="mockRequiredActionsPhoneStep"
options={getRequiredActionPhoneStepOptions()}
data-flx="channel.channel-header-components.developer-tools-context-menu.required-action-flow-menu.developer-option-radio-submenu--3"
/>
)}
<DeveloperOptionCheckbox
optionKey="mockRequiredActionsReverify"
data-flx="channel.channel-header-components.developer-tools-context-menu.required-action-flow-menu.developer-option-checkbox"
>
<Trans>Use reverification text</Trans>
</DeveloperOptionCheckbox>
<DeveloperOptionCheckbox
optionKey="mockRequiredActionsResending"
data-flx="channel.channel-header-components.developer-tools-context-menu.required-action-flow-menu.developer-option-checkbox--2"
>
<Trans>Resend button loading</Trans>
</DeveloperOptionCheckbox>
<DeveloperOptionRadioSubmenu
label={i18n._(RESEND_OUTCOME_DESCRIPTOR)}
optionKey="mockRequiredActionsResendOutcome"
options={getRequiredActionResendOutcomeOptions()}
data-flx="channel.channel-header-components.developer-tools-context-menu.required-action-flow-menu.developer-option-radio-submenu--4"
/>
</>
)}
data-flx="channel.channel-header-components.developer-tools-context-menu.required-action-flow-menu.menu-item-submenu"
/>
);
});
const ContentSafetyGatesMenu: React.FC = () => {
const {i18n} = useLingui();
return (
@@ -344,20 +253,8 @@ const GiftInventoryFlowMenu: React.FC = observer(() => {
/>
);
});
export const MockingMenu: React.FC<{onClose: () => void}> = observer(({onClose}) => {
export const MockingMenu: React.FC = observer(() => {
const {i18n} = useLingui();
const openRequiredActionOverlay = () => {
ModalCommands.pushWithKeyAfterBottomSheetClose(
onClose,
ModalCommands.modal(() => (
<RequiredActionModal
mock={true}
data-flx="channel.channel-header-components.developer-tools-context-menu.open-required-action-overlay.required-action-modal"
/>
)),
'required-actions-mock',
);
};
return (
<MenuItemSubmenu
label={i18n._(MOCKED_PRODUCT_FLOWS_DESCRIPTOR)}
@@ -415,10 +312,6 @@ export const MockingMenu: React.FC<{onClose: () => void}> = observer(({onClose})
data-flx="channel.channel-header-components.developer-tools-context-menu.mocking-menu.menu-item-submenu.mock-incoming-call"
/>
<SlowmodeSimulationMenu data-flx="channel.channel-header-components.developer-tools-context-menu.mocking-menu.slowmode-simulation-menu" />
<RequiredActionFlowMenu
onOpenOverlay={openRequiredActionOverlay}
data-flx="channel.channel-header-components.developer-tools-context-menu.mocking-menu.required-action-flow-menu"
/>
<ContentSafetyGatesMenu data-flx="channel.channel-header-components.developer-tools-context-menu.mocking-menu.content-safety-gates-menu" />
<GiftInventoryFlowMenu data-flx="channel.channel-header-components.developer-tools-context-menu.mocking-menu.gift-inventory-flow-menu" />
</>
@@ -10,7 +10,6 @@ import {
NO_TIMER_DESCRIPTOR,
} from '@app/features/channel/components/channel_header_components/developer_tools/FormatHelpers';
import type {DeveloperOptionsState} from '@app/features/devtools/state/DeveloperOptions';
import {EMAIL_DESCRIPTOR} from '@app/features/i18n/utils/CommonMessageDescriptors';
import {UserPremiumTypes} from '@fluxer/constants/src/UserConstants';
import type {I18n} from '@lingui/core';
import {msg} from '@lingui/core/macro';
@@ -47,42 +46,14 @@ export const NOT_A_MEMBER_LONG_ENOUGH_DESCRIPTOR = msg({
message: 'Not a member long enough',
comment: 'Developer tools debug menu label. Internal-only surface for developers; translators may keep this terse.',
});
export const NO_PHONE_NUMBER_DESCRIPTOR = msg({
message: 'No phone number',
export const ACCOUNT_LIMITED_DESCRIPTOR = msg({
message: 'Account limited',
comment: 'Developer tools debug menu label. Internal-only surface for developers; translators may keep this terse.',
});
export const SEND_MESSAGES_DISABLED_DESCRIPTOR = msg({
message: 'Send messages disabled',
comment: 'Developer tools debug menu label. Internal-only surface for developers; translators may keep this terse.',
});
export const PHONE_DESCRIPTOR = msg({
message: 'Phone',
comment: 'Developer tools debug menu label. Internal-only surface for developers; translators may keep this terse.',
});
export const EMAIL_OR_PHONE_DESCRIPTOR = msg({
message: 'Email or phone',
comment: 'Developer tools debug menu label. Internal-only surface for developers; translators may keep this terse.',
});
export const ENTER_PHONE_DESCRIPTOR = msg({
message: 'Enter phone',
comment: 'Developer tools debug menu label. Internal-only surface for developers; translators may keep this terse.',
});
export const ENTER_CODE_DESCRIPTOR = msg({
message: 'Enter code',
comment: 'Developer tools debug menu label. Internal-only surface for developers; translators may keep this terse.',
});
export const SUCCESS_DESCRIPTOR = msg({
message: 'Success',
comment: 'Developer tools debug menu label. Internal-only surface for developers; translators may keep this terse.',
});
export const RATE_LIMITED_DESCRIPTOR = msg({
message: 'Rate limited',
comment: 'Developer tools debug menu label. Internal-only surface for developers; translators may keep this terse.',
});
export const SERVICE_ERROR_DESCRIPTOR = msg({
message: 'Service error',
comment: 'Developer tools debug menu label. Internal-only surface for developers; translators may keep this terse.',
});
export const GEO_RESTRICTED_DESCRIPTOR = msg({
message: 'Geo restricted',
comment: 'Developer tools debug menu label. Internal-only surface for developers; translators may keep this terse.',
@@ -137,7 +108,7 @@ export const getVerificationBarrierOptions = (): Array<
{value: 'unverified_email', label: UNVERIFIED_EMAIL_DESCRIPTOR},
{value: 'account_too_new', label: ACCOUNT_TOO_NEW_DESCRIPTOR},
{value: 'not_member_long', label: NOT_A_MEMBER_LONG_ENOUGH_DESCRIPTOR},
{value: 'no_phone', label: NO_PHONE_NUMBER_DESCRIPTOR},
{value: 'account_limited', label: ACCOUNT_LIMITED_DESCRIPTOR},
{value: 'send_message_disabled', label: SEND_MESSAGES_DISABLED_DESCRIPTOR},
];
export const getCountdownTimerOptions = (
@@ -151,32 +122,6 @@ export const getCountdownTimerOptions = (
{value: 300000, label: formatDurationMs(i18n, 300000)},
{value: 600000, label: formatDurationMs(i18n, 600000)},
];
export const getRequiredActionModeOptions = (): Array<
RadioMenuOption<DeveloperOptionsState['mockRequiredActionsMode']>
> => [
{value: 'email', label: EMAIL_DESCRIPTOR},
{value: 'phone', label: PHONE_DESCRIPTOR},
{value: 'email_or_phone', label: EMAIL_OR_PHONE_DESCRIPTOR},
];
export const getRequiredActionTabOptions = (): Array<
RadioMenuOption<DeveloperOptionsState['mockRequiredActionsSelectedTab']>
> => [
{value: 'email', label: EMAIL_DESCRIPTOR},
{value: 'phone', label: PHONE_DESCRIPTOR},
];
export const getRequiredActionPhoneStepOptions = (): Array<
RadioMenuOption<DeveloperOptionsState['mockRequiredActionsPhoneStep']>
> => [
{value: 'phone', label: ENTER_PHONE_DESCRIPTOR},
{value: 'code', label: ENTER_CODE_DESCRIPTOR},
];
export const getRequiredActionResendOutcomeOptions = (): Array<
RadioMenuOption<DeveloperOptionsState['mockRequiredActionsResendOutcome']>
> => [
{value: 'success', label: SUCCESS_DESCRIPTOR},
{value: 'rate_limited', label: RATE_LIMITED_DESCRIPTOR},
{value: 'server_error', label: SERVICE_ERROR_DESCRIPTOR},
];
export const getMatureContentChannelGateOptions = (): Array<
RadioMenuOption<DeveloperOptionsState['mockMatureContentGateReason']>
> => [
@@ -50,13 +50,6 @@ export const DEFAULT_DEVELOPER_OPTIONS = {
forceMatureMedia: false,
mockInUK: false,
mockGeoBlocked: false,
mockRequiredActionsOverlay: false,
mockRequiredActionsMode: 'email',
mockRequiredActionsSelectedTab: 'email',
mockRequiredActionsPhoneStep: 'phone',
mockRequiredActionsResending: false,
mockRequiredActionsResendOutcome: 'success',
mockRequiredActionsReverify: false,
forceNoSendMessages: false,
forceNoAttachFiles: false,
mockSlowmodeActive: false,
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {
AccountLimitedBarrier,
BlockedUserBarrier,
SystemDmBarrier,
UnclaimedDMBarrier,
@@ -78,6 +79,7 @@ export const DMChannelView = observer(({channelId}: DMChannelViewProps) => {
const recipient = recipientId ? Users.getUser(recipientId) : null;
const isRecipientBlocked = recipientId ? Relationships.isBlocked(recipientId) : false;
const isCurrentUserUnclaimed = !Users.currentUser?.isClaimed();
const isCurrentUserLimited = Users.currentUser?.accountLimited === true;
const isSystemDm = channel ? ChannelUtils.isSystemDmChannel(channel) : false;
const isDM = channel?.type === ChannelTypes.DM;
const isGroupDM = channel?.type === ChannelTypes.GROUP_DM;
@@ -494,6 +496,8 @@ export const DMChannelView = observer(({channelId}: DMChannelViewProps) => {
username={NicknameUtils.getNickname(recipient, null)}
data-flx="channel.channel-view.dm-channel-view.blocked-user-barrier"
/>
) : isCurrentUserLimited && !isPersonalNotes ? (
<AccountLimitedBarrier data-flx="channel.channel-view.dm-channel-view.account-limited-barrier" />
) : isCurrentUserUnclaimed && isDM && !isPersonalNotes && !isGroupDM ? (
<UnclaimedDMBarrier data-flx="channel.channel-view.dm-channel-view.unclaimed-dm-barrier" />
) : (
@@ -1,9 +1,9 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {
AccountLimitedBarrier,
AccountTooNewBarrier,
AnnouncementFollowBarrier,
NoPhoneNumberBarrier,
NotMemberLongEnoughBarrier,
SendMessageDisabledBarrier,
UnclaimedAccountBarrier,
@@ -40,6 +40,7 @@ import {Button} from '@app/features/ui/button/Button';
import MobileLayout from '@app/features/ui/state/MobileLayout';
import {Tooltip} from '@app/features/ui/tooltip/Tooltip';
import {isPwaOnMobileOrTablet} from '@app/features/ui/utils/PwaUtils';
import Users from '@app/features/user/state/Users';
import {CompactVoiceCallStreamHeaderInfo} from '@app/features/voice/components/CompactVoiceCallStreamHeaderInfo';
import {useVoiceCallFullscreenViewState} from '@app/features/voice/components/useVoiceCallAppFullscreen';
import {VoiceCallView} from '@app/features/voice/components/VoiceCallView';
@@ -389,9 +390,9 @@ export const GuildChannelView = observer(({channelId, guildId}: GuildChannelView
data-flx="channel.channel-view.guild-channel-view.render-chat-area.not-member-long-enough-barrier"
/>
);
case 'no_phone':
case 'account_limited':
return (
<NoPhoneNumberBarrier data-flx="channel.channel-view.guild-channel-view.render-chat-area.no-phone-number-barrier" />
<AccountLimitedBarrier data-flx="channel.channel-view.guild-channel-view.render-chat-area.account-limited-barrier" />
);
case 'send_message_disabled':
return (
@@ -408,6 +409,11 @@ export const GuildChannelView = observer(({channelId, guildId}: GuildChannelView
);
}
}
if (Users.currentUser?.accountLimited) {
return (
<AccountLimitedBarrier data-flx="channel.channel-view.guild-channel-view.render-chat-area.account-limited-barrier--2" />
);
}
return passesVerification ? (
renderComposer(inputSuppressed)
) : (
@@ -15,6 +15,7 @@ import {Logger} from '@app/features/platform/utils/AppLogger';
import {Button} from '@app/features/ui/button/Button';
import * as ToastCommands from '@app/features/ui/commands/ToastCommands';
import {Spinner} from '@app/features/ui/components/Spinner';
import {handleAccountLimitedError} from '@app/features/user/utils/AccountLimitUtils';
import * as WebhookCommands from '@app/features/webhook/commands/WebhookCommands';
import {FollowedChannelListItem} from '@app/features/webhook/components/FollowedChannelListItem';
import {WebhookListItem} from '@app/features/webhook/components/WebhookListItem';
@@ -106,6 +107,7 @@ const ChannelWebhooksTab: React.FC<{channelId: string}> = observer(({channelId})
void WebhookCommands.fetchChannelWebhooks({guildId: guildId!, channelId}).catch(() => {});
} catch (error) {
logger.error('Failed to create webhook', error);
if (handleAccountLimitedError(error)) return;
showChannelErrorModal({
title: i18n._(FAILED_TO_CREATE_WEBHOOK_DESCRIPTOR),
message: i18n._(TRY_AGAIN_IN_A_MOMENT_DESCRIPTOR),