-
-
Notifications
You must be signed in to change notification settings - Fork 69
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Added image upload handling to CTA Card #1421
Changes from 2 commits
58a0371
35722e7
d835bd7
4a05742
5c18f35
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -1,6 +1,6 @@ | ||||||||||||||||||||||||||||||||||||||||||||
import CardContext from '../context/CardContext'; | ||||||||||||||||||||||||||||||||||||||||||||
import KoenigComposerContext from '../context/KoenigComposerContext.jsx'; | ||||||||||||||||||||||||||||||||||||||||||||
import React from 'react'; | ||||||||||||||||||||||||||||||||||||||||||||
import React, {useRef} from 'react'; | ||||||||||||||||||||||||||||||||||||||||||||
import {$getNodeByKey} from 'lexical'; | ||||||||||||||||||||||||||||||||||||||||||||
import {ActionToolbar} from '../components/ui/ActionToolbar.jsx'; | ||||||||||||||||||||||||||||||||||||||||||||
import {CtaCard} from '../components/ui/cards/CtaCard'; | ||||||||||||||||||||||||||||||||||||||||||||
|
@@ -26,14 +26,18 @@ export const CallToActionNodeComponent = ({ | |||||||||||||||||||||||||||||||||||||||||||
}) => { | ||||||||||||||||||||||||||||||||||||||||||||
const [editor] = useLexicalComposerContext(); | ||||||||||||||||||||||||||||||||||||||||||||
const {isEditing, isSelected, setEditing} = React.useContext(CardContext); | ||||||||||||||||||||||||||||||||||||||||||||
const {cardConfig} = React.useContext(KoenigComposerContext); | ||||||||||||||||||||||||||||||||||||||||||||
const {fileUploader, cardConfig} = React.useContext(KoenigComposerContext); | ||||||||||||||||||||||||||||||||||||||||||||
const [showSnippetToolbar, setShowSnippetToolbar] = React.useState(false); | ||||||||||||||||||||||||||||||||||||||||||||
const handleToolbarEdit = (event) => { | ||||||||||||||||||||||||||||||||||||||||||||
event.preventDefault(); | ||||||||||||||||||||||||||||||||||||||||||||
event.stopPropagation(); | ||||||||||||||||||||||||||||||||||||||||||||
setEditing(true); | ||||||||||||||||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
const fileInputRef = useRef(null); | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
const imageUploader = fileUploader.useFileUpload('image'); | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
const toggleShowButton = (event) => { | ||||||||||||||||||||||||||||||||||||||||||||
editor.update(() => { | ||||||||||||||||||||||||||||||||||||||||||||
const node = $getNodeByKey(nodeKey); | ||||||||||||||||||||||||||||||||||||||||||||
|
@@ -76,6 +80,28 @@ export const CallToActionNodeComponent = ({ | |||||||||||||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
const handleImageChange = async (files) => { | ||||||||||||||||||||||||||||||||||||||||||||
const result = await imageUploader.upload(files); | ||||||||||||||||||||||||||||||||||||||||||||
// reset original src so it can be replaced with preview and upload progress | ||||||||||||||||||||||||||||||||||||||||||||
editor.update(() => { | ||||||||||||||||||||||||||||||||||||||||||||
const node = $getNodeByKey(nodeKey); | ||||||||||||||||||||||||||||||||||||||||||||
node.imageUrl = result?.[0].url; | ||||||||||||||||||||||||||||||||||||||||||||
node.hasImage = true; | ||||||||||||||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
const onFileChange = async (e) => { | ||||||||||||||||||||||||||||||||||||||||||||
handleImageChange(e.target.files); | ||||||||||||||||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+94
to
+96
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Add file validation before upload. The file input handler should validate the files before uploading. const onFileChange = async (e) => {
+ const files = e.target.files;
+ if (!files || files.length === 0) {
+ return;
+ }
+
+ const file = files[0];
+ const validTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
+ if (!validTypes.includes(file.type)) {
+ editor.update(() => {
+ const node = $getNodeByKey(nodeKey);
+ node.uploadError = 'Invalid file type. Please upload an image.';
+ });
+ return;
+ }
+
handleImageChange(e.target.files);
}; 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
const onRemoveMedia = () => { | ||||||||||||||||||||||||||||||||||||||||||||
editor.update(() => { | ||||||||||||||||||||||||||||||||||||||||||||
const node = $getNodeByKey(nodeKey); | ||||||||||||||||||||||||||||||||||||||||||||
node.imageUrl = ''; | ||||||||||||||||||||||||||||||||||||||||||||
node.hasImage = false; | ||||||||||||||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
return ( | ||||||||||||||||||||||||||||||||||||||||||||
<> | ||||||||||||||||||||||||||||||||||||||||||||
<CtaCard | ||||||||||||||||||||||||||||||||||||||||||||
|
@@ -91,17 +117,21 @@ export const CallToActionNodeComponent = ({ | |||||||||||||||||||||||||||||||||||||||||||
hasSponsorLabel={hasSponsorLabel} | ||||||||||||||||||||||||||||||||||||||||||||
htmlEditor={htmlEditor} | ||||||||||||||||||||||||||||||||||||||||||||
imageSrc={imageUrl} | ||||||||||||||||||||||||||||||||||||||||||||
imageUploader={imageUploader} | ||||||||||||||||||||||||||||||||||||||||||||
isEditing={isEditing} | ||||||||||||||||||||||||||||||||||||||||||||
isSelected={isSelected} | ||||||||||||||||||||||||||||||||||||||||||||
layout={layout} | ||||||||||||||||||||||||||||||||||||||||||||
setEditing={setEditing} | ||||||||||||||||||||||||||||||||||||||||||||
setFileInputRef={ref => fileInputRef.current = ref} | ||||||||||||||||||||||||||||||||||||||||||||
showButton={showButton} | ||||||||||||||||||||||||||||||||||||||||||||
text={textValue} | ||||||||||||||||||||||||||||||||||||||||||||
updateButtonText={handleButtonTextChange} | ||||||||||||||||||||||||||||||||||||||||||||
updateButtonUrl={handleButtonUrlChange} | ||||||||||||||||||||||||||||||||||||||||||||
updateHasSponsorLabel={handleHasSponsorLabelChange} | ||||||||||||||||||||||||||||||||||||||||||||
updateLayout={() => {}} | ||||||||||||||||||||||||||||||||||||||||||||
updateShowButton={toggleShowButton} | ||||||||||||||||||||||||||||||||||||||||||||
onFileChange={onFileChange} | ||||||||||||||||||||||||||||||||||||||||||||
onRemoveMedia={onRemoveMedia} | ||||||||||||||||||||||||||||||||||||||||||||
/> | ||||||||||||||||||||||||||||||||||||||||||||
<ActionToolbar | ||||||||||||||||||||||||||||||||||||||||||||
data-kg-card-toolbar="button" | ||||||||||||||||||||||||||||||||||||||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,9 @@ | ||
import path from 'path'; | ||
import {assertHTML, focusEditor, html, initialize, insertCard} from '../../utils/e2e'; | ||
import {expect, test} from '@playwright/test'; | ||
import {fileURLToPath} from 'url'; | ||
const __filename = fileURLToPath(import.meta.url); | ||
const __dirname = path.dirname(__filename); | ||
|
||
test.describe('Call To Action Card', async () => { | ||
let page; | ||
|
@@ -205,4 +209,24 @@ test.describe('Call To Action Card', async () => { | |
await expect(page.locator(firstChildSelector)).toHaveClass(new RegExp(color.expectedClass)); | ||
} | ||
}); | ||
|
||
test('can add and remove CTA Card image', async function () { | ||
const filePath = path.relative(process.cwd(), __dirname + `/../fixtures/large-image.jpeg`); | ||
|
||
await focusEditor(page); | ||
await insertCard(page, {cardName: 'call-to-action'}); | ||
|
||
const fileChooserPromise = page.waitForEvent('filechooser'); | ||
|
||
await page.click('[data-testid="media-upload-placeholder"]'); | ||
|
||
const fileChooser = await fileChooserPromise; | ||
await fileChooser.setFiles([filePath]); | ||
|
||
const imgLocator = page.locator('[data-kg-card="call-to-action"] img[src^="blob:"]'); | ||
const imgElement = await imgLocator.first(); | ||
await expect(imgElement).toHaveAttribute('src', /blob:/); | ||
await page.click('[data-testid="media-upload-remove"]'); | ||
await expect(imgLocator).not.toBeVisible(); | ||
}); | ||
Comment on lines
+213
to
+231
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Add test cases for error scenarios. The image upload tests should cover error scenarios such as:
Here's an example test case to add: test('shows error for invalid file type', async function () {
const filePath = path.relative(process.cwd(), __dirname + '/../fixtures/invalid.txt');
await focusEditor(page);
await insertCard(page, {cardName: 'call-to-action'});
const fileChooserPromise = page.waitForEvent('filechooser');
await page.click('[data-testid="media-upload-placeholder"]');
const fileChooser = await fileChooserPromise;
await fileChooser.setFiles([filePath]);
await expect(page.locator('[data-testid="upload-error"]')).toBeVisible();
await expect(page.locator('[data-testid="upload-error"]')).toHaveText('Invalid file type. Please upload an image.');
}); |
||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add error handling and loading state management.
The image upload implementation should include:
Consider this implementation:
📝 Committable suggestion