import { useState, useMemo, useCallback, createRef } from 'react'; import { useIntl, defineMessages, FormattedMessage } from 'react-intl'; import classNames from 'classnames'; import { Helmet } from 'react-helmet'; import { useHistory } from 'react-router-dom'; import Toggle from 'react-toggle'; import AddPhotoAlternateIcon from '@/material-icons/400-24px/add_photo_alternate.svg?react'; import EditIcon from '@/material-icons/400-24px/edit.svg?react'; import PersonIcon from '@/material-icons/400-24px/person.svg?react'; import { updateAccount } from 'mastodon/actions/accounts'; import { Button } from 'mastodon/components/button'; import { Column } from 'mastodon/components/column'; import { ColumnHeader } from 'mastodon/components/column_header'; import { Icon } from 'mastodon/components/icon'; import { LoadingIndicator } from 'mastodon/components/loading_indicator'; import { me } from 'mastodon/initial_state'; import { useAppSelector, useAppDispatch } from 'mastodon/store'; import { unescapeHTML } from 'mastodon/utils/html'; const messages = defineMessages({ title: { id: 'onboarding.profile.title', defaultMessage: 'Profile setup', }, uploadHeader: { id: 'onboarding.profile.upload_header', defaultMessage: 'Upload profile header', }, uploadAvatar: { id: 'onboarding.profile.upload_avatar', defaultMessage: 'Upload profile picture', }, }); const nullIfMissing = (path: string) => path.endsWith('missing.png') ? null : path; interface ApiAccountErrors { display_name?: unknown; note?: unknown; avatar?: unknown; header?: unknown; } export const Profile: React.FC<{ multiColumn?: boolean; }> = ({ multiColumn }) => { const account = useAppSelector((state) => me ? state.accounts.get(me) : undefined, ); const [displayName, setDisplayName] = useState(account?.display_name ?? ''); const [note, setNote] = useState( account ? (unescapeHTML(account.note) ?? '') : '', ); const [avatar, setAvatar] = useState(); const [header, setHeader] = useState(); const [discoverable, setDiscoverable] = useState(true); const [isSaving, setIsSaving] = useState(false); const [errors, setErrors] = useState(); const avatarFileRef = createRef(); const headerFileRef = createRef(); const dispatch = useAppDispatch(); const intl = useIntl(); const history = useHistory(); const handleDisplayNameChange = useCallback( (e: React.ChangeEvent) => { setDisplayName(e.target.value); }, [setDisplayName], ); const handleNoteChange = useCallback( (e: React.ChangeEvent) => { setNote(e.target.value); }, [setNote], ); const handleDiscoverableChange = useCallback( (e: React.ChangeEvent) => { setDiscoverable(e.target.checked); }, [setDiscoverable], ); const handleAvatarChange = useCallback( (e: React.ChangeEvent) => { setAvatar(e.target.files?.[0]); }, [setAvatar], ); const handleHeaderChange = useCallback( (e: React.ChangeEvent) => { setHeader(e.target.files?.[0]); }, [setHeader], ); const avatarPreview = useMemo( () => avatar ? URL.createObjectURL(avatar) : nullIfMissing(account?.avatar ?? 'missing.png'), [avatar, account], ); const headerPreview = useMemo( () => header ? URL.createObjectURL(header) : nullIfMissing(account?.header ?? 'missing.png'), [header, account], ); const handleSubmit = useCallback(() => { setIsSaving(true); dispatch( updateAccount({ displayName, note, avatar, header, discoverable, indexable: discoverable, }), ) .then(() => { history.push('/start/follows'); return ''; }) // eslint-disable-next-line @typescript-eslint/use-unknown-in-catch-callback-variable .catch((err) => { // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access if (err.response) { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access const { details }: { details: ApiAccountErrors } = err.response.data; setErrors(details); } setIsSaving(false); }); }, [dispatch, displayName, note, avatar, header, discoverable, history]); return (