Forms: add standalone single response page#49827
Conversation
|
Are you an Automattician? Please test your changes on all WordPress.com environments to help mitigate accidental explosions.
Interested in more tips and information?
|
|
Thank you for your PR! When contributing to Jetpack, we have a few suggestions that can help us test and review your patch:
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 🤖 Follow this PR Review Process:
If you have questions about anything, reach out in #jetpack-developers for guidance! |
Code Coverage SummaryCoverage changed in 1 file.
|
- Breadcrumb form crumb links to the form's filtered responses; expose a read-only `form_id` REST field (0 for classic/embedded forms) to drive it - Add prev/next navigation (arrows + arrow-key shortcuts), hidden on mobile - Consolidate all actions into the three-dot dropdown; drop Print and the Response ID block - Consistent column width, uniform background on mobile, and fix the doubled bottom border on the last field - Guard arrow-key navigation: only preventDefault when navigating, and ignore it while typing or while the file-preview modal is open
There was a problem hiding this comment.
Pull request overview
Adds a new wp-build route under the Jetpack Forms responses dashboard to render a standalone, full-page view for an individual feedback response (including breadcrumbs, actions, and response content), and extends the feedback REST payload/type to expose the associated reusable form ID.
Changes:
- Added a new
/response/$responseIdwp-build route bundle (stage, loader, breadcrumbs, actions, navigation hook, and styles). - Extended the feedback REST schema/response and TS types to include
form_idfor tying responses back to ajetpack_formpost. - Added a Forms package changelog entry for the new screen.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| projects/packages/forms/src/types/index.ts | Adds form_id to the FormResponse TS interface. |
| projects/packages/forms/src/contact-form/class-contact-form-endpoint.php | Exposes form_id in the feedback REST schema and response payload. |
| projects/packages/forms/routes/response/use-navigation.ts | Adds prev/next navigation logic based on the inbox data loader ordering. |
| projects/packages/forms/routes/response/style.scss | Introduces standalone single-response page styling (layout, breadcrumbs, header nav). |
| projects/packages/forms/routes/response/stage.tsx | Implements the standalone single-response page UI (loading/not-found/content + file preview modal). |
| projects/packages/forms/routes/response/route.tsx | Adds a route loader to preload the response record (collection fields format). |
| projects/packages/forms/routes/response/page-actions.tsx | Adds a top-bar actions dropdown for single-response operations. |
| projects/packages/forms/routes/response/package.json | Registers the new route path and declares its route bundle dependencies. |
| projects/packages/forms/routes/response/breadcrumbs.tsx | Implements custom breadcrumbs (including optional form-scoped crumb via form_id). |
| projects/packages/forms/changelog/add-forms-single-response-page | Declares the user-facing changelog entry for the new standalone screen. |
| const actions = useMemo( () => getActions( { navigate, searchParams: {} } ), [ navigate ] ); | ||
| const currentView = VIEW_BY_STATUS[ response.status ] || 'inbox'; | ||
|
|
||
| const runAction = useCallback( | ||
| async ( action: Action, navigateAway: boolean ) => { | ||
| await action.callback?.( [ response ], { registry } ); | ||
| if ( navigateAway ) { | ||
| navigate( { to: `/responses/${ currentView }` } ); | ||
| } | ||
| }, | ||
| [ response, registry, navigate, currentView ] | ||
| ); |
| const groups: Control[][] = [ [ toggleRead ], statusControls ]; | ||
|
|
| try { | ||
| await resolveSelect( 'core' ).getEntityRecord( 'postType', 'feedback', id, { | ||
| fields_format: 'collection', | ||
| } ); | ||
| } catch { | ||
| // Swallow fetch errors (e.g. 404 for a missing response) so the stage | ||
| // can render its own "not found" state instead of the router error boundary. | ||
| } |
| <ResponseMeta response={ response } /> | ||
|
|
||
| <ResponseFieldsIterator fields={ response.fields } onFilePreview={ handleFilePreview } /> | ||
| </div> |
| Significance: minor | ||
| Type: added | ||
|
|
||
| Forms: add a standalone full-page view for a single form response. |
| search={ { sourceId: String( response.form_id ) } as unknown as never } | ||
| className="jp-forms__single-response-breadcrumbs__link" | ||
| > | ||
| { formTitle } |
There was a problem hiding this comment.
Is it worth to add a getter for the actual form name when it's a managed form? Otherwise, this title usually fallbacks to the post/page title.
CGastrell
left a comment
There was a problem hiding this comment.
Approve ✅
Verified locally: tsgo, eslint, wp-build (route registers correctly), php -l — all clean. Backend is additive/safe: get_form_id() already exists and is used in the same method, (int) casts the classic-form null to 0, and the schema test (assertArrayHasKey) isn't affected. Nice isolated diff.
One non-blocking suggestion — the breadcrumb uses response.entry_title, which is the embedding page's title, not the form's name. The crumb links to a list whose header shows the actual jetpack_form post title, so the two can disagree. Align it the same way the header does:
const formName = useSelect( select => {
if ( ! response?.form_id ) return '';
const rec = select( coreStore ).getEntityRecord( 'postType', 'jetpack_form', response.form_id );
return rec ? decodeEntities( rec.title?.rendered || '' ) : '';
}, [ response?.form_id ] );
// pass `formName || formTitle` to <SingleResponseBreadcrumbs>Only affects managed forms — classic responses have form_id = 0, so the crumb stays hidden and nothing changes. Fine to defer to the inbox-wiring follow-up.
- Breadcrumb shows the managed form's actual title (jetpack_form post name) instead of the embedding page title, matching the linked list's header - Changelog: use a "Responses:" prefix instead of the package name
Proposed changes
Adds a standalone, full-page view for a single Jetpack Forms response in the modern wp-build responses dashboard (
routes/responsesapp, pagejetpack-forms-responses-wp-admin).routes/response/→ path/response/$responseId(reached atadmin.php?page=jetpack-forms-responses-wp-admin&p=/response/<id>).Forms / <form> / #<id>) where the form crumb links to that form's filtered responses (shown only when the response is tied to ajetpack_formpost); prev/next navigation (arrows + arrow-key shortcuts, hidden on mobile); and a single three-dot menu holding all actions (Mark as read/unread,Spam/Trash— orRestore/Deleteby status — andEdit form). Status-changing actions navigate back to the relevant responses list.Reuse: the response body reuses the existing inspector components (
ResponseMeta,ResponseFieldsIterator,PreviewFile,ResponseNavigation), the page chrome (FormsPage), the inbox data loader (useInboxData), and the shared action factory (getActions). Only the route, the full-page layout, the breadcrumb/action bar, and the navigation hook are net-new.Backend: one additive, read-only REST field —
form_idon thefeedbackendpoint (thejetpack_formpost ID, or0for classic forms) — used to decide whether to render and link the form breadcrumb.Out of scope (intentionally deferred): the Activity card (private notes + system-event timeline) and linking the page from the inbox.
Known seams for a follow-up (left as-is to keep this diff isolated): the "mark as read on view" effect now exists in three spots and could be extracted into a shared hook;
getActionslives underroutes/responses/but is consumed as a package-level utility; and the status→view map is duplicated. None are blockers for a not-yet-linked screen.Related product discussion/links
Implements the provided single-response design (Activity card excluded for this first pass).
Does this pull request change what data or activity we track or use?
No new tracking or storage. The only backend change is one additive, read-only REST field (
form_id) on the existingfeedbackendpoint, derived from the post's existing parent — no new data is collected. Uses existingfeedbackrecords, the existing/wp/v2/feedback/{id}/readendpoint, and the existing action callbacks.Testing instructions
wp post list --post_type=feedback).…/wp-admin/admin.php?page=jetpack-forms-responses-wp-admin&p=/response/<id>(the modern dashboard is on by default)./response/99999999) and confirm the graceful "Response not found" state.Verified on a Jurassic Ninja site with a form containing all field types and several varied submissions. This is a net-new screen, so there is no "before".
Screenshots
Captured on a live Jurassic Ninja site (desktop at 1440×900, mobile at 390×844).