Profile editing: Utilize new API (#37990)

This commit is contained in:
Echo authored and GitHub committed 2026-02-26 13:55:10 +00:00
1 parent 51b81b3ce9
commit d18a47b6a7
11 files changed
+283 -101

No files matched your search

@@ -4,12 +4,11 @@ import type { ChangeEventHandler, FC } from 'react';
import { defineMessages, useIntl } from 'react-intl';
import { TextArea } from '@/mastodon/components/form_fields';
import { LoadingIndicator } from '@/mastodon/components/loading_indicator';
import { insertEmojiAtPosition } from '@/mastodon/features/emoji/utils';
import type { BaseConfirmationModalProps } from '@/mastodon/features/ui/components/confirmation_modals';
import { ConfirmationModal } from '@/mastodon/features/ui/components/confirmation_modals';
import { useAccount } from '@/mastodon/hooks/useAccount';
import { useCurrentAccountId } from '@/mastodon/hooks/useAccountId';
import { patchProfile } from '@/mastodon/reducers/slices/profile_edit';
import { useAppDispatch, useAppSelector } from '@/mastodon/store';
import classes from '../styles.module.scss';
@@ -38,10 +37,11 @@ export const BioModal: FC<BaseConfirmationModalProps> = ({ onClose }) => {
const titleId = useId();
const counterId = useId();
const textAreaRef = useRef<HTMLTextAreaElement>(null);
const accountId = useCurrentAccountId();
const account = useAccount(accountId);
const [newBio, setNewBio] = useState(account?.note_plain ?? '');
const { profile: { bio } = {}, isPending } = useAppSelector(
(state) => state.profileEdit,
);
const [newBio, setNewBio] = useState(bio ?? '');
const handleChange: ChangeEventHandler<HTMLTextAreaElement> = useCallback(
(event) => {
setNewBio(event.currentTarget.value);
@@ -55,19 +55,22 @@ export const BioModal: FC<BaseConfirmationModalProps> = ({ onClose }) => {
});
}, []);
if (!account) {
return <LoadingIndicator />;
}
const dispatch = useAppDispatch();
const handleSave = useCallback(() => {
if (!isPending) {
void dispatch(patchProfile({ note: newBio })).then(onClose);
}
}, [dispatch, isPending, newBio, onClose]);
return (
<ConfirmationModal
title={intl.formatMessage(
account.note_plain ? messages.editTitle : messages.addTitle,
)}
title={intl.formatMessage(bio ? messages.editTitle : messages.addTitle)}
titleId={titleId}
confirm={intl.formatMessage(messages.save)}
onConfirm={onClose} // To be implemented
onConfirm={handleSave}
onClose={onClose}
updating={isPending}
disabled={newBio.length > MAX_BIO_LENGTH}
noFocusButton
>
<div className={classes.inputWrapper}>
@@ -7,8 +7,8 @@ import { TextInput } from '@/mastodon/components/form_fields';
import { insertEmojiAtPosition } from '@/mastodon/features/emoji/utils';
import type { BaseConfirmationModalProps } from '@/mastodon/features/ui/components/confirmation_modals';
import { ConfirmationModal } from '@/mastodon/features/ui/components/confirmation_modals';
import { useAccount } from '@/mastodon/hooks/useAccount';
import { useCurrentAccountId } from '@/mastodon/hooks/useAccountId';
import { patchProfile } from '@/mastodon/reducers/slices/profile_edit';
import { useAppDispatch, useAppSelector } from '@/mastodon/store';
import classes from '../styles.module.scss';
@@ -37,10 +37,11 @@ export const NameModal: FC<BaseConfirmationModalProps> = ({ onClose }) => {
const titleId = useId();
const counterId = useId();
const inputRef = useRef<HTMLInputElement>(null);
const accountId = useCurrentAccountId();
const account = useAccount(accountId);
const [newName, setNewName] = useState(account?.display_name ?? '');
const { profile: { displayName } = {}, isPending } = useAppSelector(
(state) => state.profileEdit,
);
const [newName, setNewName] = useState(displayName ?? '');
const handleChange: ChangeEventHandler<HTMLInputElement> = useCallback(
(event) => {
setNewName(event.currentTarget.value);
@@ -54,13 +55,22 @@ export const NameModal: FC<BaseConfirmationModalProps> = ({ onClose }) => {
});
}, []);
const dispatch = useAppDispatch();
const handleSave = useCallback(() => {
if (!isPending) {
void dispatch(patchProfile({ display_name: newName })).then(onClose);
}
}, [dispatch, isPending, newName, onClose]);
return (
<ConfirmationModal
title={intl.formatMessage(messages.editTitle)}
titleId={titleId}
confirm={intl.formatMessage(messages.save)}
onConfirm={onClose} // To be implemented
onConfirm={handleSave}
onClose={onClose}
updating={isPending}
disabled={newName.length > MAX_NAME_LENGTH}
noCloseOnConfirm
noFocusButton
>
@@ -14,7 +14,11 @@ import {
fetchFeaturedTags,
fetchSuggestedTags,
} from '@/mastodon/reducers/slices/profile_edit';
import { useAppDispatch, useAppSelector } from '@/mastodon/store';
import {
createAppSelector,
useAppDispatch,
useAppSelector,
} from '@/mastodon/store';
import { AccountEditColumn, AccountEditEmptyColumn } from './components/column';
import { AccountEditItemList } from './components/item_list';
@@ -28,14 +32,23 @@ const messages = defineMessages({
},
});
const selectTags = createAppSelector(
[(state) => state.profileEdit],
(profileEdit) => ({
tags: profileEdit.tags ?? [],
tagSuggestions: profileEdit.tagSuggestions ?? [],
isLoading: !profileEdit.tags || !profileEdit.tagSuggestions,
isPending: profileEdit.isPending,
}),
);
export const AccountEditFeaturedTags: FC = () => {
const accountId = useCurrentAccountId();
const account = useAccount(accountId);
const intl = useIntl();
const { tags, tagSuggestions, isLoading, isPending } = useAppSelector(
(state) => state.profileEdit,
);
const { tags, tagSuggestions, isLoading, isPending } =
useAppSelector(selectTags);
const dispatch = useAppDispatch();
useEffect(() => {
@@ -7,13 +7,17 @@ import { useHistory } from 'react-router-dom';
import type { ModalType } from '@/mastodon/actions/modal';
import { openModal } from '@/mastodon/actions/modal';
import { AccountBio } from '@/mastodon/components/account_bio';
import { Avatar } from '@/mastodon/components/avatar';
import { DisplayNameSimple } from '@/mastodon/components/display_name/simple';
import { CustomEmojiProvider } from '@/mastodon/components/emoji/context';
import { EmojiHTML } from '@/mastodon/components/emoji/html';
import { useElementHandledLink } from '@/mastodon/components/status/handled_link';
import { useAccount } from '@/mastodon/hooks/useAccount';
import { useCurrentAccountId } from '@/mastodon/hooks/useAccountId';
import { autoPlayGif } from '@/mastodon/initial_state';
import { fetchFeaturedTags } from '@/mastodon/reducers/slices/profile_edit';
import {
fetchFeaturedTags,
fetchProfile,
} from '@/mastodon/reducers/slices/profile_edit';
import { useAppDispatch, useAppSelector } from '@/mastodon/store';
import { AccountEditColumn, AccountEditEmptyColumn } from './components/column';
@@ -82,11 +86,10 @@ export const AccountEdit: FC = () => {
const dispatch = useAppDispatch();
const { tags: featuredTags, isLoading: isTagsLoading } = useAppSelector(
(state) => state.profileEdit,
);
const { profile, tags = [] } = useAppSelector((state) => state.profileEdit);
useEffect(() => {
void dispatch(fetchFeaturedTags());
void dispatch(fetchProfile());
}, [dispatch]);
const handleOpenModal = useCallback(
@@ -107,14 +110,20 @@ export const AccountEdit: FC = () => {
history.push('/profile/featured_tags');
}, [history]);
if (!accountId || !account) {
// Normally we would use the account emoji, but we want all custom emojis to be available to render after editing.
const emojis = useAppSelector((state) => state.custom_emojis);
const htmlHandlers = useElementHandledLink({
hashtagAccountId: profile?.id,
});
if (!accountId || !account || !profile) {
return <AccountEditEmptyColumn notFound={!accountId} />;
}
const headerSrc = autoPlayGif ? account.header : account.header_static;
const hasName = !!account.display_name;
const hasBio = !!account.note_plain;
const hasTags = !isTagsLoading && featuredTags.length > 0;
const headerSrc = autoPlayGif ? profile.header : profile.headerStatic;
const hasName = !!profile.displayName;
const hasBio = !!profile.bio;
const hasTags = tags.length > 0;
return (
<AccountEditColumn
@@ -128,62 +137,64 @@ export const AccountEdit: FC = () => {
<Avatar account={account} size={80} className={classes.avatar} />
</header>
<AccountEditSection
title={messages.displayNameTitle}
description={messages.displayNamePlaceholder}
showDescription={!hasName}
buttons={
<EditButton
onClick={handleNameEdit}
item={messages.displayNameTitle}
edit={hasName}
/>
}
>
<DisplayNameSimple account={account} />
</AccountEditSection>
<CustomEmojiProvider emojis={emojis}>
<AccountEditSection
title={messages.displayNameTitle}
description={messages.displayNamePlaceholder}
showDescription={!hasName}
buttons={
<EditButton
onClick={handleNameEdit}
item={messages.displayNameTitle}
edit={hasName}
/>
}
>
<EmojiHTML htmlString={profile.displayName} {...htmlHandlers} />
</AccountEditSection>
<AccountEditSection
title={messages.bioTitle}
description={messages.bioPlaceholder}
showDescription={!hasBio}
buttons={
<EditButton
onClick={handleBioEdit}
item={messages.bioTitle}
edit={hasBio}
/>
}
>
<AccountBio accountId={accountId} />
</AccountEditSection>
<AccountEditSection
title={messages.bioTitle}
description={messages.bioPlaceholder}
showDescription={!hasBio}
buttons={
<EditButton
onClick={handleBioEdit}
item={messages.bioTitle}
edit={hasBio}
/>
}
>
<EmojiHTML htmlString={profile.bio} {...htmlHandlers} />
</AccountEditSection>
<AccountEditSection
title={messages.customFieldsTitle}
description={messages.customFieldsPlaceholder}
showDescription
/>
<AccountEditSection
title={messages.customFieldsTitle}
description={messages.customFieldsPlaceholder}
showDescription
/>
<AccountEditSection
title={messages.featuredHashtagsTitle}
description={messages.featuredHashtagsPlaceholder}
showDescription={!hasTags}
buttons={
<EditButton
onClick={handleFeaturedTagsEdit}
edit={hasTags}
item={messages.featuredHashtagsItem}
/>
}
>
{featuredTags.map((tag) => `#${tag.name}`).join(', ')}
</AccountEditSection>
<AccountEditSection
title={messages.featuredHashtagsTitle}
description={messages.featuredHashtagsPlaceholder}
showDescription={!hasTags}
buttons={
<EditButton
onClick={handleFeaturedTagsEdit}
edit={hasTags}
item={messages.featuredHashtagsItem}
/>
}
>
{tags.map((tag) => `#${tag.name}`).join(', ')}
</AccountEditSection>
<AccountEditSection
title={messages.profileTabTitle}
description={messages.profileTabSubtitle}
showDescription
/>
<AccountEditSection
title={messages.profileTabTitle}
description={messages.profileTabSubtitle}
showDescription
/>
</CustomEmojiProvider>
</AccountEditColumn>
);
};