fix(app): stop own profile preview trapping mobile web (#3463)

This commit is contained in:
Hampus authored and GitHub committed 2026-10-14 16:04:09 +02:00
1 parent d7bbe933fa
commit 381699623e
4 files changed
+66 -38

No files matched your search

@@ -766,6 +766,7 @@ export const UserProfileModal: UserProfileModalComponent = observer(
return ( return (
<Modal.Root <Modal.Root
size="medium" size="medium"
centered
initialFocusRef={autoFocusNote ? noteRef : undefined} initialFocusRef={autoFocusNote ? noteRef : undefined}
className={userProfileModalStyles.modalRoot} className={userProfileModalStyles.modalRoot}
data-flx="user.user-profile-modal.modal-root" data-flx="user.user-profile-modal.modal-root"
@@ -30,7 +30,10 @@ import * as UserCommands from '@app/features/user/commands/UserCommands';
import * as UserProfileCommands from '@app/features/user/commands/UserProfileCommands'; import * as UserProfileCommands from '@app/features/user/commands/UserProfileCommands';
import styles from '@app/features/user/components/modals/tabs/MyProfileTab.module.css'; import styles from '@app/features/user/components/modals/tabs/MyProfileTab.module.css';
import {AccentColorPicker} from '@app/features/user/components/modals/tabs/my_profile_tab/AccentColorPicker'; import {AccentColorPicker} from '@app/features/user/components/modals/tabs/my_profile_tab/AccentColorPicker';
import {AvatarUploader} from '@app/features/user/components/modals/tabs/my_profile_tab/AvatarUploader'; import {
AvatarUploader,
useChangeAvatarAction,
} from '@app/features/user/components/modals/tabs/my_profile_tab/AvatarUploader';
import {BannerUploader} from '@app/features/user/components/modals/tabs/my_profile_tab/BannerUploader'; import {BannerUploader} from '@app/features/user/components/modals/tabs/my_profile_tab/BannerUploader';
import {BIO_MARKDOWN_PARSER_FLAGS, BioEditor} from '@app/features/user/components/modals/tabs/my_profile_tab/BioEditor'; import {BIO_MARKDOWN_PARSER_FLAGS, BioEditor} from '@app/features/user/components/modals/tabs/my_profile_tab/BioEditor';
import {UsernameSection} from '@app/features/user/components/modals/tabs/my_profile_tab/MyProfileTabUsernameSection'; import {UsernameSection} from '@app/features/user/components/modals/tabs/my_profile_tab/MyProfileTabUsernameSection';
@@ -615,6 +618,7 @@ const MyProfileTabComponent = observer(function MyProfileTabComponent({
form.setValue('avatar', null); form.setValue('avatar', null);
transitionAvatarAsset({type: 'asset.cleared'}); transitionAvatarAsset({type: 'asset.cleared'});
}, [form, transitionAvatarAsset]); }, [form, transitionAvatarAsset]);
const {openChangeAvatar} = useChangeAvatarAction({onAvatarChange: handleAvatarChange});
const handleBannerChange = useCallback( const handleBannerChange = useCallback(
(base64: string) => { (base64: string) => {
form.setValue('banner', base64); form.setValue('banner', base64);
@@ -674,6 +678,7 @@ const MyProfileTabComponent = observer(function MyProfileTabComponent({
const guilds = Guilds.getGuilds(); const guilds = Guilds.getGuilds();
const selectedGuild = selectedGuildId ? guilds.find((g) => g.id === selectedGuildId) : null; const selectedGuild = selectedGuildId ? guilds.find((g) => g.id === selectedGuildId) : null;
const isPerGuildProfileCustomizationDisabled = isPerGuildProfile && !hasPerGuildProfiles; const isPerGuildProfileCustomizationDisabled = isPerGuildProfile && !hasPerGuildProfiles;
const isAvatarCustomizationDisabled = isProfileCustomizationLocked || isPerGuildProfileCustomizationDisabled;
const isPronounsDisabled = isProfileCustomizationLocked; const isPronounsDisabled = isProfileCustomizationLocked;
const profileCustomizationDescription = isProfileEmailLocked const profileCustomizationDescription = isProfileEmailLocked
? isPerGuildProfile ? isPerGuildProfile
@@ -840,7 +845,7 @@ const MyProfileTabComponent = observer(function MyProfileTabComponent({
hasAvatar={hasAvatar} hasAvatar={hasAvatar}
onAvatarChange={handleAvatarChange} onAvatarChange={handleAvatarChange}
onAvatarClear={handleAvatarClear} onAvatarClear={handleAvatarClear}
disabled={isProfileCustomizationLocked || isPerGuildProfileCustomizationDisabled} disabled={isAvatarCustomizationDisabled}
disableModeSelection={isProfileCustomizationLocked} disableModeSelection={isProfileCustomizationLocked}
isPerGuildProfile={isPerGuildProfile} isPerGuildProfile={isPerGuildProfile}
errorMessage={form.formState.errors.avatar?.message} errorMessage={form.formState.errors.avatar?.message}
@@ -934,6 +939,9 @@ const MyProfileTabComponent = observer(function MyProfileTabComponent({
}} }}
ignoreGuildAvatarInPreview={isPerGuildProfile && avatarAsset.mode === 'inherit'} ignoreGuildAvatarInPreview={isPerGuildProfile && avatarAsset.mode === 'inherit'}
ignoreGuildBannerInPreview={isPerGuildProfile && bannerAsset.mode === 'inherit'} ignoreGuildBannerInPreview={isPerGuildProfile && bannerAsset.mode === 'inherit'}
onAvatarClick={
mobileLayout.enabled && !isAvatarCustomizationDisabled ? openChangeAvatar : undefined
}
data-flx="user.my-profile-tab.my-profile-tab-component.profile-preview" data-flx="user.my-profile-tab.my-profile-tab-component.profile-preview"
/> />
</div> </div>
@@ -116,47 +116,22 @@ interface AvatarUploaderProps {
onAvatarModeChange?: (mode: AvatarMode) => void; onAvatarModeChange?: (mode: AvatarMode) => void;
} }
export const AvatarUploader = observer( interface ChangeAvatarActionOptions {
({ onAvatarChange: (base64: string) => void;
hasAvatar, requireAnimatedAvatarEntitlement?: boolean;
}
export function useChangeAvatarAction({
onAvatarChange, onAvatarChange,
onAvatarClear,
disabled,
disableModeSelection,
requireAnimatedAvatarEntitlement = true, requireAnimatedAvatarEntitlement = true,
isPerGuildProfile, }: ChangeAvatarActionOptions) {
errorMessage,
avatarMode = 'inherit',
onAvatarModeChange,
}: AvatarUploaderProps) => {
const PREMIUM_PRODUCT_NAME = getPremiumProductName();
const {i18n} = useLingui(); const {i18n} = useLingui();
const PREMIUM_PRODUCT_NAME = getPremiumProductName();
const hasAnimatedAvatarEntitlement = isLimitToggleEnabled( const hasAnimatedAvatarEntitlement = isLimitToggleEnabled(
{feature_animated_avatar: LimitResolver.resolve({key: 'feature_animated_avatar', fallback: 0})}, {feature_animated_avatar: LimitResolver.resolve({key: 'feature_animated_avatar', fallback: 0})},
'feature_animated_avatar', 'feature_animated_avatar',
); );
const canUploadAnimatedAvatar = !requireAnimatedAvatarEntitlement || hasAnimatedAvatarEntitlement; const canUploadAnimatedAvatar = !requireAnimatedAvatarEntitlement || hasAnimatedAvatarEntitlement;
const getAvatarModeOptions = useCallback(
() => [
{
value: 'inherit' as AvatarMode,
name: i18n._(USE_GLOBAL_PROFILE_DESCRIPTOR),
desc: i18n._(SHOW_YOUR_GLOBAL_PROFILE_AVATAR_IN_THIS_COMMUNITY_DESCRIPTOR),
},
{
value: 'custom' as AvatarMode,
name: i18n._(USE_CUSTOM_IMAGE_DESCRIPTOR),
desc: i18n._(UPLOAD_A_CUSTOM_AVATAR_FOR_THIS_COMMUNITY_DESCRIPTOR),
disabled: !canUploadAnimatedAvatar,
},
{
value: 'unset' as AvatarMode,
name: i18n._(DON_T_SHOW_DESCRIPTOR),
desc: i18n._(SHOW_DEFAULT_AVATAR_IGNORING_YOUR_GLOBAL_PROFILE_DESCRIPTOR),
},
],
[canUploadAnimatedAvatar, i18n],
);
const processAvatarFile = useCallback( const processAvatarFile = useCallback(
async (file: File) => { async (file: File) => {
if (file.size > GlobalLimits.getAvatarMaxSize()) { if (file.size > GlobalLimits.getAvatarMaxSize()) {
@@ -189,8 +164,8 @@ export const AvatarUploader = observer(
<> <>
<p data-flx="user.my-profile-tab.avatar-uploader.handle-avatar-upload.p"> <p data-flx="user.my-profile-tab.avatar-uploader.handle-avatar-upload.p">
<Trans> <Trans>
Animated avatars ({ANIMATED_AVATAR_FORMATS}) are exclusively available to{' '} Animated avatars ({ANIMATED_AVATAR_FORMATS}) are exclusively available to {PREMIUM_PRODUCT_NAME}{' '}
{PREMIUM_PRODUCT_NAME} subscribers. subscribers.
</Trans> </Trans>
</p> </p>
<p <p
@@ -335,6 +310,48 @@ export const AvatarUploader = observer(
onSelectGif: (gif) => void handleSelectAvatarGif(gif), onSelectGif: (gif) => void handleSelectAvatarGif(gif),
}); });
}, [canUploadAnimatedAvatar, handlePickAvatarFile, handleSelectAvatarGif, i18n]); }, [canUploadAnimatedAvatar, handlePickAvatarFile, handleSelectAvatarGif, i18n]);
return {canUploadAnimatedAvatar, openChangeAvatar: handleAvatarUpload};
}
export const AvatarUploader = observer(
({
hasAvatar,
onAvatarChange,
onAvatarClear,
disabled,
disableModeSelection,
requireAnimatedAvatarEntitlement = true,
isPerGuildProfile,
errorMessage,
avatarMode = 'inherit',
onAvatarModeChange,
}: AvatarUploaderProps) => {
const {i18n} = useLingui();
const {canUploadAnimatedAvatar, openChangeAvatar: handleAvatarUpload} = useChangeAvatarAction({
onAvatarChange,
requireAnimatedAvatarEntitlement,
});
const getAvatarModeOptions = useCallback(
() => [
{
value: 'inherit' as AvatarMode,
name: i18n._(USE_GLOBAL_PROFILE_DESCRIPTOR),
desc: i18n._(SHOW_YOUR_GLOBAL_PROFILE_AVATAR_IN_THIS_COMMUNITY_DESCRIPTOR),
},
{
value: 'custom' as AvatarMode,
name: i18n._(USE_CUSTOM_IMAGE_DESCRIPTOR),
desc: i18n._(UPLOAD_A_CUSTOM_AVATAR_FOR_THIS_COMMUNITY_DESCRIPTOR),
disabled: !canUploadAnimatedAvatar,
},
{
value: 'unset' as AvatarMode,
name: i18n._(DON_T_SHOW_DESCRIPTOR),
desc: i18n._(SHOW_DEFAULT_AVATAR_IGNORING_YOUR_GLOBAL_PROFILE_DESCRIPTOR),
},
],
[canUploadAnimatedAvatar, i18n],
);
const handleModeChange = useCallback( const handleModeChange = useCallback(
(mode: AvatarMode) => { (mode: AvatarMode) => {
if (mode === 'custom') { if (mode === 'custom') {
@@ -65,6 +65,7 @@ interface ProfilePreviewProps {
showMessageButton?: boolean; showMessageButton?: boolean;
showPreviewLabel?: boolean; showPreviewLabel?: boolean;
previewCustomStatus?: CustomStatus | null; previewCustomStatus?: CustomStatus | null;
onAvatarClick?: () => void;
} }
export const ProfilePreview: React.FC<ProfilePreviewProps> = observer( export const ProfilePreview: React.FC<ProfilePreviewProps> = observer(
@@ -90,6 +91,7 @@ export const ProfilePreview: React.FC<ProfilePreviewProps> = observer(
showMessageButton = true, showMessageButton = true,
showPreviewLabel = true, showPreviewLabel = true,
previewCustomStatus, previewCustomStatus,
onAvatarClick,
}) => { }) => {
const {i18n} = useLingui(); const {i18n} = useLingui();
const previewOverrides = useMemo<ProfilePreviewOverrides>( const previewOverrides = useMemo<ProfilePreviewOverrides>(
@@ -245,7 +247,7 @@ export const ProfilePreview: React.FC<ProfilePreviewProps> = observer(
avatarUrl={finalAvatarUrl} avatarUrl={finalAvatarUrl}
hoverAvatarUrl={finalHoverAvatarUrl} hoverAvatarUrl={finalHoverAvatarUrl}
isClickable={true} isClickable={true}
onAvatarClick={openMockProfile} onAvatarClick={onAvatarClick ?? openMockProfile}
data-flx="user.profile.profile-preview.profile-card-banner" data-flx="user.profile.profile-preview.profile-card-banner"
/> />
<UserProfileBadges <UserProfileBadges