Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions public/locales/en/upload-image-modal.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"removeImage": "Remove Image",
"optional": "(Optional)",
"altTextRequired": "Alt text is required",
"sourceRequired": "Please enter an image URL or upload an image",
"dropTitle": "Drag & drop to upload",
"dropMeta": "Max size: {{maxSize}}MB Recommended: 1200 x 800 px Formats: jpg/jpeg, png",
"uploadFailed": "Image upload failed",
Expand Down
1 change: 1 addition & 0 deletions public/locales/zh-TW/upload-image-modal.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"removeImage": "移除圖片",
"optional": "(選填)",
"altTextRequired": "請輸入替代文字",
"sourceRequired": "請填寫圖片 URL 或上傳圖片",
"dropTitle": "拖曳檔案上傳",
"dropMeta": "大小限制:{{maxSize}}MB 建議尺寸:1200 x 800 px 支援格式:jpg/jpeg, png",
"uploadFailed": "圖片上傳失敗",
Expand Down
29 changes: 24 additions & 5 deletions src/components/image-upload-modal.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import RadioGroup from '@/components/core/radio-group';

const MAX_FILE_SIZE_MB = 10;

const ImageSource = ({ onChange }) => {
const ImageSource = ({ onChange, error, onErrorClear }) => {
const [uploadFile, setUploadFile] = useState(null);
const [uploadError, setUploadError] = useState(false);
const [embedUrl, setEmbedUrl] = useState('');
Expand Down Expand Up @@ -48,6 +48,7 @@ const ImageSource = ({ onChange }) => {
if (!file) return;

resetSourceState();
onErrorClear?.();

if (file.size > MAX_FILE_SIZE_MB * 1024 * 1024) {
setUploadError(true);
Expand Down Expand Up @@ -91,6 +92,7 @@ const ImageSource = ({ onChange }) => {
if (!trimmed) return;

resetSourceState({ resetFileInput: true });
onErrorClear?.();

if (!isValidUrl(trimmed)) {
setEmbedError(true);
Expand Down Expand Up @@ -260,14 +262,18 @@ const ImageSource = ({ onChange }) => {
id="embed-url"
aria-describedby="embed-url-hint"
value={embedUrl}
onChange={(val) => setEmbedUrl(val)}
onChange={(val) => {
setEmbedUrl(val);
if (error) onErrorClear?.();
}}
onKeyDown={(e) => {
if (e.key === 'Enter') {
e.preventDefault();
handleEmbed();
}
}}
placeholder={t('embedUrlPlaceholder')}
error={error}
/>
</div>
<PrimaryButton size="l" onClick={handleEmbed} className="shrink-0">
Expand All @@ -281,10 +287,13 @@ const ImageSource = ({ onChange }) => {

ImageSource.propTypes = {
onChange: PropTypes.func.isRequired,
error: PropTypes.string,
onErrorClear: PropTypes.func,
};

const ImageUploadModal = ({ isOpen, onClose, onConfirm }) => {
const [imageSource, setImageSource] = useState(null);
const [sourceError, setSourceError] = useState('');
const [altText, setAltText] = useState('');
const [altTextError, setAltTextError] = useState('');
const [caption, setCaption] = useState('');
Expand All @@ -296,6 +305,7 @@ const ImageUploadModal = ({ isOpen, onClose, onConfirm }) => {

const resetForm = () => {
setImageSource(null);
setSourceError('');
setAltText('');
setAltTextError('');
setCaption('');
Expand All @@ -310,9 +320,11 @@ const ImageUploadModal = ({ isOpen, onClose, onConfirm }) => {
};

const handleConfirm = () => {
if (!imageSource) return; // TODO: ask designer where to show image validation error

let valid = true;
if (!imageSource) {
setSourceError(t('sourceRequired'));
valid = false;
}
if (!altText.trim()) {
setAltTextError(t('altTextRequired'));
valid = false;
Expand Down Expand Up @@ -350,7 +362,14 @@ const ImageUploadModal = ({ isOpen, onClose, onConfirm }) => {
>
<div className="flex flex-col gap-6">
{/* Image source */}
<ImageSource onChange={setImageSource} />
<ImageSource
onChange={(src) => {
setImageSource(src);
if (src) setSourceError('');
}}
error={sourceError}
onErrorClear={() => setSourceError('')}
/>

{/* Alt text */}
<TextInput
Expand Down
Loading