diff --git a/sticker-creator/src/assets/locales/en/messages.json b/sticker-creator/src/assets/locales/en/messages.json index e51c9ed78..7bdf90027 100644 --- a/sticker-creator/src/assets/locales/en/messages.json +++ b/sticker-creator/src/assets/locales/en/messages.json @@ -308,6 +308,10 @@ "message": "Link copied", "description": "Text for the toast when a link for sharing is copied from the Sticker Creator" }, + "StickerCreator--Toasts--unsupportedFormat": { + "message": "Can’t add this image because the format is not supported", + "description": "Text for the toast when an image cannot be processed because it uses an unsupported file format" + }, "StickerCreator--StickerPreview--light": { "message": "My sticker in light theme", "description": "Text for the sticker preview for the light theme" diff --git a/sticker-creator/src/components/ArtFrame.tsx b/sticker-creator/src/components/ArtFrame.tsx index a2315e93f..ab7d6637e 100644 --- a/sticker-creator/src/components/ArtFrame.tsx +++ b/sticker-creator/src/components/ArtFrame.tsx @@ -38,7 +38,7 @@ export type OnPickEmojiOptions = Readonly<{ emoji: EmojiData; }>; -export type Props = Partial> & +export type Props = Partial> & Readonly<{ artType: ArtType; id?: string; @@ -65,6 +65,7 @@ export const ArtFrame = memo(function ArtFrame({ onRemove, onPickEmoji, onDrop, + onDropRejected, }: Props) { const i18n = useI18n(); const [emojiPickerOpen, setEmojiPickerOpen] = useState(false); @@ -187,10 +188,11 @@ export const ArtFrame = memo(function ArtFrame({ onMouseLeave={handleMouseEnter} /> ) : null} - {mode === 'add' && onDrop ? ( + {mode === 'add' && onDrop && onDropRejected ? ( diff --git a/sticker-creator/src/components/ArtGrid.tsx b/sticker-creator/src/components/ArtGrid.tsx index 586e14d1e..1811d390b 100644 --- a/sticker-creator/src/components/ArtGrid.tsx +++ b/sticker-creator/src/components/ArtGrid.tsx @@ -124,12 +124,17 @@ export function ArtGrid({ mode, showGuide }: Props): JSX.Element { [dispatch, artType] ); + const handleDropRejected = useCallback(() => { + dispatch(addToast({ key: 'StickerCreator--Toasts--unsupportedFormat' })); + }, [dispatch]); + if (list.length === 0) { return (
); @@ -144,6 +149,7 @@ export function ArtGrid({ mode, showGuide }: Props): JSX.Element { showGuide={showGuide} mode="add" onDrop={handleDrop} + onDropRejected={handleDropRejected} /> ); } diff --git a/sticker-creator/src/elements/DropZone.tsx b/sticker-creator/src/elements/DropZone.tsx index 90c7f58bf..26aeb2c4c 100644 --- a/sticker-creator/src/elements/DropZone.tsx +++ b/sticker-creator/src/elements/DropZone.tsx @@ -11,8 +11,9 @@ import { useStickerDropzone } from '../util/useStickerDropzone'; export type Props = { readonly inner?: boolean; readonly label: string; - onDrop(files: ReadonlyArray): unknown; - onDragActive?(active: boolean): unknown; + onDrop: (files: ReadonlyArray) => void; + onDragActive?: (active: boolean) => void; + onDropRejected: () => void; }; const getClassName = ({ inner }: Props, isDragActive: boolean) => { @@ -28,7 +29,7 @@ const getClassName = ({ inner }: Props, isDragActive: boolean) => { }; export function DropZone(props: Props): JSX.Element { - const { inner, label, onDrop, onDragActive } = props; + const { inner, label, onDrop, onDragActive, onDropRejected } = props; const i18n = useI18n(); const handleDrop = useCallback( @@ -40,8 +41,10 @@ export function DropZone(props: Props): JSX.Element { [onDrop] ); - const { getRootProps, getInputProps, isDragActive } = - useStickerDropzone(handleDrop); + const { getRootProps, getInputProps, isDragActive } = useStickerDropzone( + handleDrop, + onDropRejected + ); useEffect(() => { if (onDragActive) { diff --git a/sticker-creator/src/routes/art/MetaStage.tsx b/sticker-creator/src/routes/art/MetaStage.tsx index 280768be6..fa480bd58 100644 --- a/sticker-creator/src/routes/art/MetaStage.tsx +++ b/sticker-creator/src/routes/art/MetaStage.tsx @@ -11,7 +11,13 @@ import { useStickerDropzone } from '../../util/useStickerDropzone'; import { H2, Text } from '../../elements/Typography'; import { LabeledInput } from '../../elements/LabeledInput'; import { ConfirmModal } from '../../components/ConfirmModal'; -import { setCover, removeImage, setTitle, setAuthor } from '../../slices/art'; +import { + setCover, + removeImage, + setTitle, + setAuthor, + addToast, +} from '../../slices/art'; import { useArtType, useAllDataValid, @@ -47,8 +53,14 @@ export function MetaStage(): JSX.Element { [dispatch, artType] ); - const { getRootProps, getInputProps, isDragActive } = - useStickerDropzone(onDrop); + const onDropRejected = useCallback(() => { + dispatch(addToast({ key: 'StickerCreator--Toasts--errorProcessing' })); + }, [dispatch]); + + const { getRootProps, getInputProps, isDragActive } = useStickerDropzone( + onDrop, + onDropRejected + ); const onNext = useCallback(() => { setConfirming(true); diff --git a/sticker-creator/src/util/useStickerDropzone.ts b/sticker-creator/src/util/useStickerDropzone.ts index 7bb12486e..ac8b4f0ee 100644 --- a/sticker-creator/src/util/useStickerDropzone.ts +++ b/sticker-creator/src/util/useStickerDropzone.ts @@ -5,10 +5,12 @@ import type { DropzoneOptions } from 'react-dropzone'; import { useDropzone } from 'react-dropzone'; export const useStickerDropzone = ( - onDrop: DropzoneOptions['onDrop'] + onDrop: NonNullable, + onDropRejected: NonNullable ): ReturnType => useDropzone({ onDrop, + onDropRejected, accept: { 'image/png': ['.png'], 'image/webp': ['.webp'],