Skip to content

Forms: Display uploaded files with thumbnail preview on confirmation page#46753

Closed
vianasw wants to merge 9 commits intotrunkfrom
add/forms-response-file-upload
Closed

Forms: Display uploaded files with thumbnail preview on confirmation page#46753
vianasw wants to merge 9 commits intotrunkfrom
add/forms-response-file-upload

Conversation

@vianasw
Copy link
Copy Markdown
Contributor

@vianasw vianasw commented Jan 24, 2026

Summary

Implements RESPONSE-014: Display uploaded files on the form submission confirmation page with thumbnail/icon previews.

  • Display uploaded files with icon, filename, and file size on confirmation page
  • For AJAX submissions, preserve client-side preview (blob URLs for images, icon SVGs for other file types)
  • Match thumbnail appearance with form field preview (46px circular)
  • Show appropriate file type icons for non-image files (PDF, DOC, etc.)
  • Remove non-functional download link (requires auth that front-end users don't have)

Changes

PHP (class-contact-form.php, class-feedback-field.php):

  • Add file field rendering to confirmation template
  • Include previewUrl, iconUrl, and hasPreview in file data
  • Use conditional display for thumbnail vs SVG fallback icon

JavaScript (view.js):

  • Add captureFilePreviews() to capture preview URLs from DOM before AJAX submission
  • Update getFiles() to include preview data for confirmation display

CSS (grunion.scss):

  • Add styles for file list display with grid layout
  • Add thumbnail styles matching form field preview (46px circular)
  • Support both background-image (images) and mask-image (file type icons)

Test plan

  • Upload an image file, submit form → confirmation shows circular thumbnail preview
  • Upload a PDF file, submit form → confirmation shows PDF icon
  • Upload a non-image file (DOC, XLS, etc.), submit form → shows appropriate file type icon
  • Verify filename and file size display correctly
  • Verify layout matches design (icon/thumbnail on left, name above size)

🤖 Generated with Claude Code

vianasw and others added 4 commits January 21, 2026 18:34
…mation page

Updated the post-submission confirmation page to properly render file upload
fields with:
- Document icon for visual identification
- Filename as clickable download link
- File size in human-readable format

Changes:
- Added get_render_web_value() support for file fields in Feedback_Field
- Added getFiles() function in view.js for client-side file data extraction
- Added get_files() static method in Contact_Form for PHP template rendering
- Added field-files template section in HTML output
- Added CSS styles for file display components

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Change the file upload display from horizontal inline layout to vertical
stacking with the icon on the left and filename/size stacked vertically.
This matches the design specification where the filename appears as an
underlined link with the file size below it in smaller muted text.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
For AJAX form submissions, capture file preview data (blob URLs for
images, icon SVG URLs for other files) before submission and preserve
them for the confirmation page display.

- Add captureFilePreviews() to capture preview URLs from DOM
- Update getFiles() to include previewUrl, iconUrl, and hasPreview
- Add thumbnail element to confirmation template with conditional display
- Add CSS for thumbnail with background-image and mask-image support
- Image files show actual thumbnail preview
- Non-image files show appropriate file type icon (PDF, DOC, etc.)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Match thumbnail size (46px) with form field preview
- Use circular border-radius (50%) to match form field style
- Remove download link from filename (requires auth that front-end users don't have)
- Display filename as plain text instead of anchor

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings January 24, 2026 13:57
@vianasw vianasw added the [Status] Needs Review This PR is ready for review. label Jan 24, 2026
@vianasw vianasw self-assigned this Jan 24, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jan 24, 2026

Are you an Automattician? Please test your changes on all WordPress.com environments to help mitigate accidental explosions.

  • To test on WoA, go to the Plugins menu on a WoA dev site. Click on the "Upload" button and follow the upgrade flow to be able to upload, install, and activate the Jetpack Beta plugin. Once the plugin is active, go to Jetpack > Jetpack Beta, select your plugin (Jetpack), and enable the add/forms-response-file-upload branch.
  • To test on Simple, run the following command on your sandbox:
bin/jetpack-downloader test jetpack add/forms-response-file-upload

Interested in more tips and information?

  • In your local development environment, use the jetpack rsync command to sync your changes to a WoA dev blog.
  • Read more about our development workflow here: PCYsg-eg0-p2
  • Figure out when your changes will be shipped to customers here: PCYsg-eg5-p2

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jan 24, 2026

Thank you for your PR!

When contributing to Jetpack, we have a few suggestions that can help us test and review your patch:

  • ✅ Include a description of your PR changes.
  • ✅ Add a "[Status]" label (In Progress, Needs Review, ...).
  • 🔴 Add testing instructions.
  • 🔴 Specify whether this PR includes any changes to data or privacy.
  • ✅ Add changelog entries to affected projects

This comment will be updated as you work on your PR and make changes. If you think that some of those checks are not needed for your PR, please explain why you think so. Thanks for cooperation 🤖


🔴 Action required: Please include detailed testing steps, explaining how to test your change, like so:

## Testing instructions:

* Go to '..'
*

🔴 Action required: We would recommend that you add a section to the PR description to specify whether this PR includes any changes to data or privacy, like so:

## Does this pull request change what data or activity we track or use?

My PR adds *x* and *y*.

Follow this PR Review Process:

  1. Ensure all required checks appearing at the bottom of this PR are passing.
  2. Make sure to test your changes on all platforms that it applies to. You're responsible for the quality of the code you ship.
  3. You can use GitHub's Reviewers functionality to request a review.
  4. When it's reviewed and merged, you will be pinged in Slack to deploy the changes to WordPress.com simple once the build is done.

If you have questions about anything, reach out in #jetpack-developers for guidance!

@github-actions github-actions Bot added [Feature] Forms [Package] Forms [Status] Needs Author Reply We need more details from you. This label will be auto-added until the PR meets all requirements. and removed [Status] Needs Review This PR is ready for review. labels Jan 24, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements display of uploaded files on the form submission confirmation page with thumbnail/icon previews. For AJAX submissions, client-side preview URLs (blob URLs for images, icon SVGs for other file types) are captured from the DOM and preserved for display. Non-AJAX submissions show a generic file icon.

Changes:

  • Added file preview capture mechanism for AJAX submissions to preserve blob URLs and icon URLs
  • Created file display template with 46px circular thumbnails matching the form field preview appearance
  • Added conditional rendering logic to show thumbnail previews when available or fallback icons otherwise

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
projects/packages/forms/src/modules/form/view.js Added captureFilePreviews() to capture preview URLs from DOM, getFiles() to format file data with preview information, and integration with form submission flow
projects/packages/forms/src/contact-form/css/grunion.scss Added styles for file list display with grid layout, 46px circular thumbnails, and support for both image previews and file type icons
projects/packages/forms/src/contact-form/class-feedback-field.php Added file field handling in get_render_web_value() to return structured array with file metadata
projects/packages/forms/src/contact-form/class-contact-form.php Added get_files() method and updated confirmation template rendering to include file display with thumbnails and fallback icons
projects/packages/forms/changelog/add-forms-response-file-upload Added changelog entry for the feature

<template data-wp-each--file="context.submission.files">
<div class="field-file">
<div class="field-file__thumbnail" data-wp-style--background-image="context.file.previewUrl" data-wp-style--mask-image="context.file.iconUrl" data-wp-bind--hidden="!context.file.hasPreview"></div>
<svg class="field-file__icon" data-wp-bind--hidden="context.file.hasPreview" width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
Copy link

Copilot AI Jan 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The SVG file icon should include aria-hidden="true" to indicate it's decorative, since the file information is already conveyed through the adjacent text elements (file name and size). This prevents screen readers from announcing unnecessary information.

Suggested change
<svg class="field-file__icon" data-wp-bind--hidden="context.file.hasPreview" width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<svg class="field-file__icon" data-wp-bind--hidden="context.file.hasPreview" width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">

Copilot uses AI. Check for mistakes.
Comment on lines +1686 to +1690
$html .= '<svg class="field-file__icon" data-wp-bind--hidden="context.file.hasPreview" width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"';
$html .= $has_preview ? ' hidden' : '';
$html .= '>';
$html .= '<path d="M14 2H6C4.9 2 4 2.9 4 4V20C4 21.1 4.89 22 5.99 22H18C19.1 22 20 21.1 20 20V8L14 2ZM18 20H6V4H13V9H18V20Z" fill="currentColor"/>';
$html .= '</svg>';
Copy link

Copilot AI Jan 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The SVG file icon should include aria-hidden="true" to indicate it's decorative, since the file information is already conveyed through the adjacent text elements (file name and size). This prevents screen readers from announcing unnecessary information.

Copilot uses AI. Check for mistakes.
Significance: patch
Type: added

Forms: Display uploaded files with icon, filename link, and size on confirmation page.
Copy link

Copilot AI Jan 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The changelog mentions "filename link" but according to the PR description, the non-functional download link was removed. The changelog should be updated to reflect that files are displayed with icon, filename, and size (without a link).

Copilot uses AI. Check for mistakes.
Comment on lines +232 to +253
const getFiles = value => {
if ( value?.type === 'file' && value?.files ) {
return value.files.map( file => {
const fileName = file.name ?? '';
const preview = capturedFilePreviews.get( fileName );
const hasPreview = !! ( preview?.previewUrl || preview?.iconUrl );

return {
name: fileName,
size: file.size ?? '',
url: file.url ?? '',
// Include preview data if available (for AJAX submissions)
previewUrl: preview?.previewUrl ?? null,
iconUrl: preview?.iconUrl ?? null,
// Boolean flag for easier binding evaluation
hasPreview,
};
} );
}

return null;
};
Copy link

Copilot AI Jan 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The getFiles function is missing JSDoc documentation. Other similar functions in this file (like getImages, getUrl, captureFilePreviews) have JSDoc comments that describe their parameters and return values. Following the established pattern in this file, this function should include JSDoc documentation describing its purpose, the value parameter, and the return type.

Copilot generated this review using guidance from repository custom instructions.
Comment on lines +232 to +253
const getFiles = value => {
if ( value?.type === 'file' && value?.files ) {
return value.files.map( file => {
const fileName = file.name ?? '';
const preview = capturedFilePreviews.get( fileName );
const hasPreview = !! ( preview?.previewUrl || preview?.iconUrl );

return {
name: fileName,
size: file.size ?? '',
url: file.url ?? '',
// Include preview data if available (for AJAX submissions)
previewUrl: preview?.previewUrl ?? null,
iconUrl: preview?.iconUrl ?? null,
// Boolean flag for easier binding evaluation
hasPreview,
};
} );
}

return null;
};
Copy link

Copilot AI Jan 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new getFiles function lacks test coverage. Given that this file contains comprehensive tests for the similar getImages function (lines 159-311), test coverage should be added for getFiles following the same pattern. Tests should cover: returning null for non-file values, extracting file metadata correctly, handling missing properties gracefully, and verifying the hasPreview flag logic.

Copilot uses AI. Check for mistakes.
Comment on lines +215 to +221
if ( fileName ) {
previews.set( fileName, {
// For images, the background-image contains the blob URL
previewUrl: backgroundImage && backgroundImage !== 'none' ? backgroundImage : null,
// For non-images, the mask-image contains the icon SVG URL
iconUrl: maskImage && maskImage !== 'none' ? maskImage : null,
} );
Copy link

Copilot AI Jan 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The captureFilePreviews function uses filename as the Map key, which could cause issues if a user uploads multiple files with the same name. When multiple files share the same filename, only the preview data from the last file processed will be stored, causing earlier files with the same name to lose their preview data. Consider using a more unique identifier (e.g., combining filename with array index or using the clientFileId if accessible from the DOM) to ensure each file's preview is captured correctly.

Copilot uses AI. Check for mistakes.
Comment on lines +594 to +595
// Capture file preview URLs before submission (blob URLs for images, icon URLs for other files)
capturedFilePreviews = captureFilePreviews( context.formHash );
Copy link

Copilot AI Jan 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The capturedFilePreviews Map stores references to blob URLs (in previewUrl) that are never cleaned up. While the blob URLs themselves are created and managed by the file-field module (which does revoke them on file removal), storing additional references to these URLs in capturedFilePreviews could prevent proper garbage collection. Consider clearing the capturedFilePreviews Map after the form submission is complete (either in the success or error path) to avoid potential memory leaks, especially on pages where users might submit forms multiple times without a page refresh.

Copilot uses AI. Check for mistakes.
Comment on lines +1680 to +1693
$html .= '<div data-wp-each-child class="field-file">';
// Thumbnail for AJAX submissions (has preview data)
$html .= '<div class="field-file__thumbnail" data-wp-style--background-image="context.file.previewUrl" data-wp-style--mask-image="context.file.iconUrl" data-wp-bind--hidden="!context.file.hasPreview"';
$html .= $has_preview ? '' : ' hidden';
$html .= '></div>';
// SVG fallback for non-AJAX submissions
$html .= '<svg class="field-file__icon" data-wp-bind--hidden="context.file.hasPreview" width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"';
$html .= $has_preview ? ' hidden' : '';
$html .= '>';
$html .= '<path d="M14 2H6C4.9 2 4 2.9 4 4V20C4 21.1 4.89 22 5.99 22H18C19.1 22 20 21.1 20 20V8L14 2ZM18 20H6V4H13V9H18V20Z" fill="currentColor"/>';
$html .= '</svg>';
$html .= '<span class="field-file__name" data-wp-text="context.file.name">' . esc_html( $file_name ) . '</span>';
$html .= '<span class="field-file__size" data-wp-text="context.file.size">' . esc_html( $file_size ) . '</span>';
$html .= '</div>';
Copy link

Copilot AI Jan 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The file display elements lack proper accessibility attributes. The file name and size information should be associated with the thumbnail/icon for screen reader users. Consider wrapping the file information in a semantic structure (e.g., adding role="group" to the .field-file container with an aria-label describing the file), or adding aria-label attributes to the thumbnail/icon elements that include the file name and size.

Copilot uses AI. Check for mistakes.
Comment on lines +1605 to +1612
<div class="field-file">
<div class="field-file__thumbnail" data-wp-style--background-image="context.file.previewUrl" data-wp-style--mask-image="context.file.iconUrl" data-wp-bind--hidden="!context.file.hasPreview"></div>
<svg class="field-file__icon" data-wp-bind--hidden="context.file.hasPreview" width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14 2H6C4.9 2 4 2.9 4 4V20C4 21.1 4.89 22 5.99 22H18C19.1 22 20 21.1 20 20V8L14 2ZM18 20H6V4H13V9H18V20Z" fill="currentColor"/>
</svg>
<span class="field-file__name" data-wp-text="context.file.name"></span>
<span class="field-file__size" data-wp-text="context.file.size"></span>
</div>
Copy link

Copilot AI Jan 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same accessibility issue exists here: file display elements lack proper accessibility attributes. The file name and size information should be associated with the thumbnail/icon for screen reader users. Consider adding role="group" to the .field-file container with an aria-label describing the file, or add aria-label attributes to the thumbnail/icon elements.

Copilot uses AI. Check for mistakes.
vianasw and others added 5 commits January 24, 2026 15:49
The SVG icons are decorative since file information is conveyed
through adjacent text elements (filename and size). Adding
aria-hidden="true" prevents screen readers from announcing
unnecessary information.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The download link was removed since it requires authentication that
front-end users don't have. Updated changelog to reflect that files
are displayed with icon, filename, and size (without a link).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Following the established pattern in this file for similar functions
like captureFilePreviews, getImages, and getUrl.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Following the established pattern for getImages and getUrl tests:
- Returns null for non-file values
- Extracts file metadata correctly
- Handles missing properties gracefully
- Verifies hasPreview flag logic with captured preview data

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Clears the Map storing blob URL references after form submission
completes (success or error) to avoid potential memory leaks on
pages where users might submit forms multiple times without refresh.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@vianasw vianasw added [Status] Needs Review This PR is ready for review. and removed [Status] Needs Author Reply We need more details from you. This label will be auto-added until the PR meets all requirements. labels Jan 26, 2026
@github-actions github-actions Bot added [Status] Needs Author Reply We need more details from you. This label will be auto-added until the PR meets all requirements. and removed [Status] Needs Review This PR is ready for review. labels Jan 26, 2026
@vianasw vianasw closed this Jan 26, 2026
@github-actions github-actions Bot removed the [Status] Needs Author Reply We need more details from you. This label will be auto-added until the PR meets all requirements. label Jan 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants