163 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
			
		
		
	
	
			163 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
| import { useState, useMemo, useCallback, createRef } from 'react';
 | |
| 
 | |
| import { useIntl, defineMessages, FormattedMessage } from 'react-intl';
 | |
| 
 | |
| import classNames from 'classnames';
 | |
| import { useHistory } from 'react-router-dom';
 | |
| 
 | |
| 
 | |
| import { useDispatch } from 'react-redux';
 | |
| 
 | |
| 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 { updateAccount } from 'flavours/glitch/actions/accounts';
 | |
| import { Button } from 'flavours/glitch/components/button';
 | |
| import { ColumnBackButton } from 'flavours/glitch/components/column_back_button';
 | |
| import { Icon } from 'flavours/glitch/components/icon';
 | |
| import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator';
 | |
| import { me } from 'flavours/glitch/initial_state';
 | |
| import { useAppSelector } from 'flavours/glitch/store';
 | |
| import { unescapeHTML } from 'flavours/glitch/utils/html';
 | |
| 
 | |
| const messages = defineMessages({
 | |
|   uploadHeader: { id: 'onboarding.profile.upload_header', defaultMessage: 'Upload profile header' },
 | |
|   uploadAvatar: { id: 'onboarding.profile.upload_avatar', defaultMessage: 'Upload profile picture' },
 | |
| });
 | |
| 
 | |
| const nullIfMissing = path => path.endsWith('missing.png') ? null : path;
 | |
| 
 | |
| export const Profile = () => {
 | |
|   const account = useAppSelector(state => state.getIn(['accounts', me]));
 | |
|   const [displayName, setDisplayName] = useState(account.get('display_name'));
 | |
|   const [note, setNote] = useState(unescapeHTML(account.get('note')));
 | |
|   const [avatar, setAvatar] = useState(null);
 | |
|   const [header, setHeader] = useState(null);
 | |
|   const [discoverable, setDiscoverable] = useState(account.get('discoverable'));
 | |
|   const [isSaving, setIsSaving] = useState(false);
 | |
|   const [errors, setErrors] = useState();
 | |
|   const avatarFileRef = createRef();
 | |
|   const headerFileRef = createRef();
 | |
|   const dispatch = useDispatch();
 | |
|   const intl = useIntl();
 | |
|   const history = useHistory();
 | |
| 
 | |
|   const handleDisplayNameChange = useCallback(e => {
 | |
|     setDisplayName(e.target.value);
 | |
|   }, [setDisplayName]);
 | |
| 
 | |
|   const handleNoteChange = useCallback(e => {
 | |
|     setNote(e.target.value);
 | |
|   }, [setNote]);
 | |
| 
 | |
|   const handleDiscoverableChange = useCallback(e => {
 | |
|     setDiscoverable(e.target.checked);
 | |
|   }, [setDiscoverable]);
 | |
| 
 | |
|   const handleAvatarChange = useCallback(e => {
 | |
|     setAvatar(e.target?.files?.[0]);
 | |
|   }, [setAvatar]);
 | |
| 
 | |
|   const handleHeaderChange = useCallback(e => {
 | |
|     setHeader(e.target?.files?.[0]);
 | |
|   }, [setHeader]);
 | |
| 
 | |
|   const avatarPreview = useMemo(() => avatar ? URL.createObjectURL(avatar) : nullIfMissing(account.get('avatar')), [avatar, account]);
 | |
|   const headerPreview = useMemo(() => header ? URL.createObjectURL(header) : nullIfMissing(account.get('header')), [header, account]);
 | |
| 
 | |
|   const handleSubmit = useCallback(() => {
 | |
|     setIsSaving(true);
 | |
| 
 | |
|     dispatch(updateAccount({
 | |
|       displayName,
 | |
|       note,
 | |
|       avatar,
 | |
|       header,
 | |
|       discoverable,
 | |
|       indexable: discoverable,
 | |
|     })).then(() => history.push('/start/follows')).catch(err => {
 | |
|       setIsSaving(false);
 | |
|       setErrors(err.response.data.details);
 | |
|     });
 | |
|   }, [dispatch, displayName, note, avatar, header, discoverable, history]);
 | |
| 
 | |
|   return (
 | |
|     <>
 | |
|       <ColumnBackButton />
 | |
| 
 | |
|       <div className='scrollable privacy-policy'>
 | |
|         <div className='column-title'>
 | |
|           <h3><FormattedMessage id='onboarding.profile.title' defaultMessage='Profile setup' /></h3>
 | |
|           <p><FormattedMessage id='onboarding.profile.lead' defaultMessage='You can always complete this later in the settings, where even more customization options are available.' /></p>
 | |
|         </div>
 | |
| 
 | |
|         <div className='simple_form'>
 | |
|           <div className='onboarding__profile'>
 | |
|             <label className={classNames('app-form__header-input', { selected: !!headerPreview, invalid: !!errors?.header })} title={intl.formatMessage(messages.uploadHeader)}>
 | |
|               <input
 | |
|                 type='file'
 | |
|                 hidden
 | |
|                 ref={headerFileRef}
 | |
|                 accept='image/*'
 | |
|                 onChange={handleHeaderChange}
 | |
|               />
 | |
| 
 | |
|               {headerPreview && <img src={headerPreview} alt='' />}
 | |
| 
 | |
|               <Icon icon={headerPreview ? EditIcon : AddPhotoAlternateIcon} />
 | |
|             </label>
 | |
| 
 | |
|             <label className={classNames('app-form__avatar-input', { selected: !!avatarPreview, invalid: !!errors?.avatar })} title={intl.formatMessage(messages.uploadAvatar)}>
 | |
|               <input
 | |
|                 type='file'
 | |
|                 hidden
 | |
|                 ref={avatarFileRef}
 | |
|                 accept='image/*'
 | |
|                 onChange={handleAvatarChange}
 | |
|               />
 | |
| 
 | |
|               {avatarPreview && <img src={avatarPreview} alt='' />}
 | |
| 
 | |
|               <Icon icon={avatarPreview ? EditIcon : AddPhotoAlternateIcon} />
 | |
|             </label>
 | |
|           </div>
 | |
| 
 | |
|           <div className={classNames('input with_block_label', { field_with_errors: !!errors?.display_name })}>
 | |
|             <label htmlFor='display_name'><FormattedMessage id='onboarding.profile.display_name' defaultMessage='Display name' /></label>
 | |
|             <span className='hint'><FormattedMessage id='onboarding.profile.display_name_hint' defaultMessage='Your full name or your fun name…' /></span>
 | |
|             <div className='label_input'>
 | |
|               <input id='display_name' type='text' value={displayName} onChange={handleDisplayNameChange} maxLength={30} />
 | |
|             </div>
 | |
|           </div>
 | |
| 
 | |
|           <div className={classNames('input with_block_label', { field_with_errors: !!errors?.note })}>
 | |
|             <label htmlFor='note'><FormattedMessage id='onboarding.profile.note' defaultMessage='Bio' /></label>
 | |
|             <span className='hint'><FormattedMessage id='onboarding.profile.note_hint' defaultMessage='You can @mention other people or #hashtags…' /></span>
 | |
|             <div className='label_input'>
 | |
|               <textarea id='note' value={note} onChange={handleNoteChange} maxLength={500} />
 | |
|             </div>
 | |
|           </div>
 | |
| 
 | |
|           <label className='app-form__toggle'>
 | |
|             <div className='app-form__toggle__label'>
 | |
|               <strong><FormattedMessage id='onboarding.profile.discoverable' defaultMessage='Make my profile discoverable' /></strong> <span className='recommended'><FormattedMessage id='recommended' defaultMessage='Recommended' /></span>
 | |
|               <span className='hint'><FormattedMessage id='onboarding.profile.discoverable_hint' defaultMessage='When you opt in to discoverability on Mastodon, your posts may appear in search results and trending, and your profile may be suggested to people with similar interests to you.' /></span>
 | |
|             </div>
 | |
| 
 | |
|             <div className='app-form__toggle__toggle'>
 | |
|               <div>
 | |
|                 <Toggle checked={discoverable} onChange={handleDiscoverableChange} />
 | |
|               </div>
 | |
|             </div>
 | |
|           </label>
 | |
|         </div>
 | |
| 
 | |
|         <div className='onboarding__footer'>
 | |
|           <Button block onClick={handleSubmit} disabled={isSaving}>{isSaving ? <LoadingIndicator /> : <FormattedMessage id='onboarding.profile.save_and_continue' defaultMessage='Save and continue' />}</Button>
 | |
|         </div>
 | |
|       </div>
 | |
|     </>
 | |
|   );
 | |
| };
 |