bigbuffet-rw/app/soapbox/features/onboarding/steps/cover-photo-selection-step.tsx

157 lines
6 KiB
TypeScript
Raw Normal View History

2023-02-06 10:01:03 -08:00
import clsx from 'clsx';
import React from 'react';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
2022-04-12 06:52:04 -07:00
import { patchMe } from 'soapbox/actions/me';
2022-11-15 08:00:49 -08:00
import StillImage from 'soapbox/components/still-image';
2022-04-12 06:52:04 -07:00
import { Avatar, Button, Card, CardBody, Icon, Spinner, Stack, Text } from 'soapbox/components/ui';
import { useAppDispatch, useOwnAccount } from 'soapbox/hooks';
2022-12-20 08:34:53 -08:00
import toast from 'soapbox/toast';
import { isDefaultHeader } from 'soapbox/utils/accounts';
2022-11-15 12:46:23 -08:00
import resizeImage from 'soapbox/utils/resize-image';
2022-04-12 06:52:04 -07:00
import type { AxiosError } from 'axios';
const messages = defineMessages({
header: { id: 'account.header.alt', defaultMessage: 'Profile header' },
error: { id: 'onboarding.error', defaultMessage: 'An unexpected error occurred. Please try again or skip this step.' },
});
2022-04-12 06:52:04 -07:00
const CoverPhotoSelectionStep = ({ onNext }: { onNext: () => void }) => {
const intl = useIntl();
const dispatch = useAppDispatch();
2022-04-12 06:52:04 -07:00
const account = useOwnAccount();
const fileInput = React.useRef<HTMLInputElement>(null);
const [selectedFile, setSelectedFile] = React.useState<string | null>();
const [isSubmitting, setSubmitting] = React.useState<boolean>(false);
const [isDisabled, setDisabled] = React.useState<boolean>(true);
const isDefault = account ? isDefaultHeader(account.header) : false;
2022-04-12 06:52:04 -07:00
const openFilePicker = () => {
fileInput.current?.click();
};
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const maxPixels = 1920 * 1080;
2022-04-24 12:28:07 -07:00
const rawFile = event.target.files?.item(0);
if (!rawFile) return;
2022-04-12 06:52:04 -07:00
resizeImage(rawFile, maxPixels).then((file) => {
const url = file ? URL.createObjectURL(file) : account?.header as string;
setSelectedFile(url);
setSubmitting(true);
const formData = new FormData();
formData.append('header', file);
const credentials = dispatch(patchMe(formData));
Promise.all([credentials]).then(() => {
setDisabled(false);
setSubmitting(false);
onNext();
}).catch((error: AxiosError) => {
setSubmitting(false);
setDisabled(false);
setSelectedFile(null);
if (error.response?.status === 422) {
2022-12-20 08:34:53 -08:00
toast.error((error.response.data as any).error.replace('Validation failed: ', ''));
2022-04-12 06:52:04 -07:00
} else {
2022-12-20 08:34:53 -08:00
toast.error(messages.error);
2022-04-12 06:52:04 -07:00
}
});
}).catch(console.error);
};
return (
<Card variant='rounded' size='xl'>
<CardBody>
<div>
2023-02-01 14:13:42 -08:00
<div className='-mx-4 mb-4 border-b border-solid border-gray-200 pb-4 dark:border-gray-800 sm:-mx-10 sm:pb-10'>
2022-04-12 06:52:04 -07:00
<Stack space={2}>
<Text size='2xl' align='center' weight='bold'>
2022-04-12 11:25:53 -07:00
<FormattedMessage id='onboarding.header.title' defaultMessage='Pick a cover image' />
2022-04-12 06:52:04 -07:00
</Text>
<Text theme='muted' align='center'>
2022-04-12 11:25:53 -07:00
<FormattedMessage id='onboarding.header.subtitle' defaultMessage='This will be shown at the top of your profile.' />
2022-04-12 06:52:04 -07:00
</Text>
</Stack>
</div>
2023-02-01 14:13:42 -08:00
<div className='mx-auto sm:w-2/3 sm:pt-10 md:w-1/2'>
2022-04-12 06:52:04 -07:00
<Stack space={10}>
2023-02-01 14:13:42 -08:00
<div className='rounded-lg border border-solid border-gray-200 dark:border-gray-800'>
2022-04-12 06:52:04 -07:00
<div
role='button'
2023-02-01 14:13:42 -08:00
className='relative flex h-24 items-center justify-center rounded-t-md bg-gray-200 dark:bg-gray-800'
2022-04-12 06:52:04 -07:00
>
{selectedFile || account?.header && (
<StillImage
src={selectedFile || account.header}
alt={intl.formatMessage(messages.header)}
2023-02-01 14:13:42 -08:00
className='absolute inset-0 rounded-t-md object-cover'
2022-04-12 06:52:04 -07:00
/>
)}
{isSubmitting && (
<div
2023-02-01 14:13:42 -08:00
className='absolute inset-0 flex items-center justify-center rounded-t-md bg-white/80 dark:bg-primary-900/80'
2022-04-12 06:52:04 -07:00
>
<Spinner withText={false} />
</div>
)}
<button
onClick={openFilePicker}
type='button'
2023-02-06 10:01:03 -08:00
className={clsx({
'absolute -top-3 -right-3 p-1 bg-primary-600 rounded-full ring-2 ring-white dark:ring-primary-900 hover:bg-primary-700': true,
2022-04-12 06:52:04 -07:00
'opacity-50 pointer-events-none': isSubmitting,
})}
disabled={isSubmitting}
>
2023-02-01 14:13:42 -08:00
<Icon src={require('@tabler/icons/plus.svg')} className='h-5 w-5 text-white' />
2022-04-12 06:52:04 -07:00
</button>
<input type='file' className='hidden' ref={fileInput} onChange={handleFileChange} />
</div>
<div className='flex flex-col px-4 pb-4'>
{account && (
2023-02-01 14:13:42 -08:00
<Avatar src={account.avatar} size={64} className='-mt-8 mb-2 ring-2 ring-white dark:ring-primary-800' />
2022-04-12 06:52:04 -07:00
)}
<Text weight='bold' size='sm'>{account?.display_name}</Text>
<Text theme='muted' size='sm'>@{account?.username}</Text>
</div>
</div>
<Stack justifyContent='center' space={2}>
<Button block theme='primary' type='button' onClick={onNext} disabled={isDefault && isDisabled || isSubmitting}>
{isSubmitting ? (
<FormattedMessage id='onboarding.saving' defaultMessage='Saving…' />
) : (
<FormattedMessage id='onboarding.next' defaultMessage='Next' />
)}
2022-04-12 06:52:04 -07:00
</Button>
{isDisabled && (
<Button block theme='tertiary' type='button' onClick={onNext}>
2022-04-12 11:25:53 -07:00
<FormattedMessage id='onboarding.skip' defaultMessage='Skip for now' />
</Button>
2022-04-12 06:52:04 -07:00
)}
</Stack>
</Stack>
</div>
</div>
</CardBody>
</Card>
);
};
export default CoverPhotoSelectionStep;