diff --git a/projects/packages/forms/changelog/update-forms-dashboard-dataview-actions b/projects/packages/forms/changelog/update-forms-dashboard-dataview-actions
new file mode 100644
index 0000000000000..8d4b839cec0f8
--- /dev/null
+++ b/projects/packages/forms/changelog/update-forms-dashboard-dataview-actions
@@ -0,0 +1,4 @@
+Significance: patch
+Type: changed
+
+Forms: Update dataview actions
diff --git a/projects/packages/forms/src/dashboard/components/response-actions/index.tsx b/projects/packages/forms/src/dashboard/components/response-actions/index.tsx
index 8493f56a5c355..be0a1758f3073 100644
--- a/projects/packages/forms/src/dashboard/components/response-actions/index.tsx
+++ b/projects/packages/forms/src/dashboard/components/response-actions/index.tsx
@@ -20,9 +20,10 @@ import {
* Types
*/
import type { FormResponse } from '../../../types';
+import type { Registry } from '../../inbox/dataviews/types';
type ResponseNavigationProps = {
- onActionComplete?: ( FormResponse ) => void;
+ onActionComplete?: ( response: FormResponse ) => void;
response: FormResponse;
};
@@ -37,7 +38,7 @@ const ResponseActions = ( {
const [ isDeleting, setIsDeleting ] = useState( false );
const [ isTogglingReadStatus, setIsTogglingReadStatus ] = useState( false );
- const registry = useRegistry();
+ const registry = useRegistry() as unknown as Registry;
const handleMarkAsSpam = useCallback( async () => {
onActionComplete?.( response );
diff --git a/projects/packages/forms/src/dashboard/inbox/dataviews/actions.js b/projects/packages/forms/src/dashboard/inbox/dataviews/actions.tsx
similarity index 93%
rename from projects/packages/forms/src/dashboard/inbox/dataviews/actions.js
rename to projects/packages/forms/src/dashboard/inbox/dataviews/actions.tsx
index ad7ab4268346c..dc299925d6327 100644
--- a/projects/packages/forms/src/dashboard/inbox/dataviews/actions.js
+++ b/projects/packages/forms/src/dashboard/inbox/dataviews/actions.tsx
@@ -1,3 +1,6 @@
+/**
+ * External dependencies
+ */
import jetpackAnalytics from '@automattic/jetpack-analytics';
import apiFetch from '@wordpress/api-fetch';
import { Icon } from '@wordpress/components';
@@ -5,10 +8,17 @@ import { store as coreStore } from '@wordpress/core-data';
import { __, _n, sprintf } from '@wordpress/i18n';
import { seen, unseen, trash, backup, commentContent } from '@wordpress/icons';
import { store as noticesStore } from '@wordpress/notices';
+/**
+ * Internal dependencies
+ */
import { notSpam, spam } from '../../icons';
import { store as dashboardStore } from '../../store';
import { updateMenuCounter, updateMenuCounterOptimistically } from '../utils';
import { defaultView } from './views';
+/**
+ * Types
+ */
+import type { Action, QueryParams, Registry } from './types';
/**
* Helper function to extract count-relevant query params from the current query.
@@ -16,8 +26,9 @@ import { defaultView } from './views';
* @param {object} currentQuery - The current query from the store.
* @return {object} Query params relevant for count caching.
*/
-const getCountQueryParams = currentQuery => {
- const queryParams = {};
+const getCountQueryParams = ( currentQuery: QueryParams ): QueryParams => {
+ const queryParams: QueryParams = {};
+
if ( currentQuery?.search ) {
queryParams.search = currentQuery.search;
}
@@ -33,6 +44,7 @@ const getCountQueryParams = currentQuery => {
if ( currentQuery?.is_unread !== undefined ) {
queryParams.is_unread = currentQuery.is_unread;
}
+
return queryParams;
};
@@ -45,11 +57,11 @@ const getCountQueryParams = currentQuery => {
* @param {string} statusBeingRemovedFrom - The status items are being removed from ('trash', 'spam', or 'inbox').
*/
const invalidateCacheAndNavigate = (
- registry,
- currentQuery,
- queryParams,
- statusBeingRemovedFrom
-) => {
+ registry: Registry,
+ currentQuery: QueryParams,
+ queryParams: QueryParams,
+ statusBeingRemovedFrom: string
+): void => {
// Invalidate counts to ensure accurate totals
registry.dispatch( dashboardStore ).invalidateCounts();
@@ -78,23 +90,40 @@ const invalidateCacheAndNavigate = (
}
};
+// TODO: We should probably have better error messages in case of failure.
+const getGenericErrorMessage = ( numberOfErrors: number ): string => {
+ return numberOfErrors === 1
+ ? __( 'An error occurred.', 'jetpack-forms' )
+ : sprintf(
+ /* translators: %d: the number of responses. */
+ _n(
+ 'An error occurred for %d response.',
+ 'An error occurred for %d responses.',
+ numberOfErrors,
+ 'jetpack-forms'
+ ),
+ numberOfErrors
+ );
+};
+
export const BULK_ACTIONS = {
markAsSpam: 'mark_as_spam',
markAsNotSpam: 'mark_as_not_spam',
};
-export const viewAction = {
+export const viewAction: Action = {
id: 'view-response',
- icon: ,
isPrimary: true,
- label: __( 'View response', 'jetpack-forms' ),
+ icon: ,
+ label: __( 'View', 'jetpack-forms' ),
modalHeader: __( 'Response', 'jetpack-forms' ),
};
-export const editFormAction = {
+export const editFormAction: Action = {
id: 'edit-form',
- label: __( 'Edit form', 'jetpack-forms' ),
+ isPrimary: false,
icon: ,
+ label: __( 'Edit form', 'jetpack-forms' ),
isEligible: item => !! item?.edit_form_url,
supportsBulk: false,
async callback( items ) {
@@ -102,7 +131,9 @@ export const editFormAction = {
action: 'edit-form',
multiple: false,
} );
+
const [ item ] = items;
+
if ( item?.edit_form_url ) {
const url = new URL( item.edit_form_url, window.location.origin );
// redirect to the form edit page
@@ -111,33 +142,19 @@ export const editFormAction = {
},
};
-// TODO: We should probably have better error messages in case of failure.
-const getGenericErrorMessage = numberOfErrors => {
- return numberOfErrors.length === 1
- ? __( 'An error occurred.', 'jetpack-forms' )
- : sprintf(
- /* translators: %d: the number of responses. */
- _n(
- 'An error occurred for %d response.',
- 'An error occurred for %d responses.',
- numberOfErrors,
- 'jetpack-forms'
- ),
- numberOfErrors
- );
-};
-
-export const markAsSpamAction = {
+export const markAsSpamAction: Action = {
id: 'mark-as-spam',
- label: __( 'Mark as spam', 'jetpack-forms' ),
+ isPrimary: true,
+ icon: ,
+ label: __( 'Spam', 'jetpack-forms' ),
isEligible: item => item.status !== 'spam',
supportsBulk: true,
- icon: ,
async callback( items, { registry } ) {
jetpackAnalytics.tracks.recordEvent( 'jetpack_forms_inbox_action_click', {
action: 'mark-as-spam',
multiple: items.length > 1,
} );
+
const { createSuccessNotice, createErrorNotice } = registry.dispatch( noticesStore );
const { saveEntityRecord } = registry.dispatch( coreStore );
const { updateCountsOptimistically } = registry.dispatch( dashboardStore );
@@ -149,10 +166,13 @@ export const markAsSpamAction = {
updateCountsOptimistically( item.status, 'spam', 1, queryParams );
} );
- const promises = await Promise.allSettled(
+ const promises = ( await Promise.allSettled(
items.map( ( { id } ) => saveEntityRecord( 'postType', 'feedback', { id, status: 'spam' } ) )
- );
- const itemsUpdated = promises.filter( ( { status } ) => status === 'fulfilled' );
+ ) ) as PromiseSettledResult< { id: string } >[];
+
+ const itemsUpdated = promises.filter(
+ ( { status } ) => status === 'fulfilled'
+ ) as PromiseFulfilledResult< { id: string } >[];
// If there is at least one successful update, invalidate the cache and navigate if needed
if ( itemsUpdated.length ) {
@@ -178,6 +198,7 @@ export const markAsSpamAction = {
),
items.length
);
+
createSuccessNotice( successMessage, {
type: 'snackbar',
id: 'mark-as-spam-action',
@@ -194,8 +215,10 @@ export const markAsSpamAction = {
// There is at least one failure.
const numberOfErrors = promises.filter( ( { status } ) => status === 'rejected' ).length;
const errorMessage = getGenericErrorMessage( numberOfErrors );
+
createErrorNotice( errorMessage, { type: 'snackbar' } );
}
+
// Make the REST request which performs the `contact_form_akismet` `ham` action.
if ( itemsUpdated.length ) {
registry.dispatch( dashboardStore ).doBulkAction(
@@ -206,17 +229,19 @@ export const markAsSpamAction = {
},
};
-export const markAsNotSpamAction = {
+export const markAsNotSpamAction: Action = {
id: 'mark-as-not-spam',
+ isPrimary: true,
+ icon: ,
label: __( 'Not spam', 'jetpack-forms' ),
isEligible: item => item.status === 'spam',
supportsBulk: true,
- icon: ,
async callback( items, { registry } ) {
jetpackAnalytics.tracks.recordEvent( 'jetpack_forms_inbox_action_click', {
action: 'mark-as-not-spam',
multiple: items.length > 1,
} );
+
const { createSuccessNotice, createErrorNotice } = registry.dispatch( noticesStore );
const { saveEntityRecord } = registry.dispatch( coreStore );
const { updateCountsOptimistically } = registry.dispatch( dashboardStore );
@@ -228,12 +253,15 @@ export const markAsNotSpamAction = {
updateCountsOptimistically( 'spam', 'publish', 1, queryParams );
} );
- const promises = await Promise.allSettled(
+ const promises = ( await Promise.allSettled(
items.map( ( { id } ) =>
saveEntityRecord( 'postType', 'feedback', { id, status: 'publish' } )
)
- );
- const itemsUpdated = promises.filter( ( { status } ) => status === 'fulfilled' );
+ ) ) as PromiseSettledResult< { id: string } >[];
+
+ const itemsUpdated = promises.filter(
+ ( { status } ) => status === 'fulfilled'
+ ) as PromiseFulfilledResult< { id: string } >[];
// If there is at least one successful update, invalidate the cache and navigate if needed
if ( itemsUpdated.length ) {
@@ -255,6 +283,7 @@ export const markAsNotSpamAction = {
),
items.length
);
+
createSuccessNotice( successMessage, {
type: 'snackbar',
id: 'mark-as-not-spam-action',
@@ -271,6 +300,7 @@ export const markAsNotSpamAction = {
// There is at least one failure.
const numberOfErrors = promises.filter( ( { status } ) => status === 'rejected' ).length;
const errorMessage = getGenericErrorMessage( numberOfErrors );
+
createErrorNotice( errorMessage, { type: 'snackbar' } );
}
// Make the REST request which performs the `contact_form_akismet` `ham` action.
@@ -283,17 +313,19 @@ export const markAsNotSpamAction = {
},
};
-export const restoreAction = {
+export const restoreAction: Action = {
id: 'restore',
+ isPrimary: true,
+ icon: ,
label: __( 'Restore', 'jetpack-forms' ),
isEligible: item => item.status === 'trash',
supportsBulk: true,
- icon: ,
async callback( items, { registry } ) {
jetpackAnalytics.tracks.recordEvent( 'jetpack_forms_inbox_action_click', {
action: 'restore',
multiple: items.length > 1,
} );
+
const { saveEntityRecord } = registry.dispatch( coreStore );
const { createSuccessNotice, createErrorNotice } = registry.dispatch( noticesStore );
const { updateCountsOptimistically } = registry.dispatch( dashboardStore );
@@ -310,6 +342,7 @@ export const restoreAction = {
saveEntityRecord( 'postType', 'feedback', { id, status: 'publish' } )
)
);
+
const itemsUpdated = promises.filter( ( { status } ) => status === 'fulfilled' );
// If there is at least one successful update, invalidate the cache and navigate if needed
@@ -331,6 +364,7 @@ export const restoreAction = {
),
items.length
);
+
createSuccessNotice( successMessage, {
type: 'snackbar',
id: 'restore-action',
@@ -343,27 +377,31 @@ export const restoreAction = {
},
],
} );
+
return;
}
+
// There is at least one failure.
const numberOfErrors = promises.filter( ( { status } ) => status === 'rejected' ).length;
const errorMessage = getGenericErrorMessage( numberOfErrors );
+
createErrorNotice( errorMessage, { type: 'snackbar' } );
},
};
-export const moveToTrashAction = {
+export const moveToTrashAction: Action = {
id: 'move-to-trash',
- label: __( 'Move to trash', 'jetpack-forms' ),
- isEligible: item => item.status !== 'trash',
isPrimary: true,
- supportsBulk: true,
icon: ,
+ label: __( 'Trash', 'jetpack-forms' ),
+ isEligible: item => item.status !== 'trash',
+ supportsBulk: true,
async callback( items, { registry } ) {
jetpackAnalytics.tracks.recordEvent( 'jetpack_forms_inbox_action_click', {
action: 'move-to-trash',
multiple: items.length > 1,
} );
+
const { deleteEntityRecord } = registry.dispatch( coreStore );
const { createSuccessNotice, createErrorNotice } = registry.dispatch( noticesStore );
const { updateCountsOptimistically } = registry.dispatch( dashboardStore );
@@ -406,6 +444,7 @@ export const moveToTrashAction = {
),
items.length
);
+
createSuccessNotice( successMessage, {
type: 'snackbar',
id: 'move-to-trash-action',
@@ -418,26 +457,31 @@ export const moveToTrashAction = {
},
],
} );
+
return;
}
+
// There is at least one failure.
const numberOfErrors = promises.filter( ( { status } ) => status === 'rejected' ).length;
const errorMessage = getGenericErrorMessage( numberOfErrors );
+
createErrorNotice( errorMessage, { type: 'snackbar' } );
},
};
-export const deleteAction = {
+export const deleteAction: Action = {
id: 'delete',
- label: __( 'Delete permanently', 'jetpack-forms' ),
+ isPrimary: true,
+ icon: ,
+ label: __( 'Delete', 'jetpack-forms' ),
isEligible: item => item.status === 'trash',
supportsBulk: true,
- icon: ,
async callback( items, { registry } ) {
jetpackAnalytics.tracks.recordEvent( 'jetpack_forms_inbox_action_click', {
action: 'delete',
multiple: items.length > 1,
} );
+
const { deleteEntityRecord } = registry.dispatch( coreStore );
const { invalidateFilters, updateCountsOptimistically } = registry.dispatch( dashboardStore );
const { getCurrentQuery } = registry.select( dashboardStore );
@@ -454,12 +498,15 @@ export const deleteAction = {
deleteEntityRecord( 'postType', 'feedback', id, { force: true }, { throwOnError: true } )
)
);
+
const itemsUpdated = promises.filter( ( { status } ) => status === 'fulfilled' );
+
// If there is at least one successful update, invalidate the cache for filters.
if ( itemsUpdated.length ) {
invalidateFilters();
invalidateCacheAndNavigate( registry, getCurrentQuery(), queryParams, 'trash' );
}
+
if ( itemsUpdated.length === items.length ) {
// Every request was successful.
const successMessage =
@@ -475,6 +522,7 @@ export const deleteAction = {
),
items.length
);
+
createSuccessNotice( successMessage, { type: 'snackbar', id: 'move-to-trash-action' } );
// Update the URL to remove references to deleted items.
@@ -497,27 +545,30 @@ export const deleteAction = {
const hashString = hashParams.toString();
window.location.hash = hashString ? `${ hashBase }?${ hashString }` : hashBase;
+
return;
}
// There is at least one failure.
const numberOfErrors = promises.filter( ( { status } ) => status === 'rejected' ).length;
const errorMessage = getGenericErrorMessage( numberOfErrors );
+
createErrorNotice( errorMessage, { type: 'snackbar' } );
},
};
-export const markAsReadAction = {
+export const markAsReadAction: Action = {
id: 'mark-as-read',
+ isPrimary: false,
+ icon: ,
label: __( 'Mark as read', 'jetpack-forms' ),
isEligible: item => item.is_unread,
supportsBulk: true,
- icon: ,
async callback( items, { registry } ) {
jetpackAnalytics.tracks.recordEvent( 'jetpack_forms_inbox_action_click', {
action: 'mark-as-read',
multiple: items.length > 1,
} );
- // const { receiveEntityRecords, editEntityRecord } = registry.dispatch( coreStore );
+
const { editEntityRecord } = registry.dispatch( coreStore );
const { getEntityRecord } = registry.select( coreStore );
const { createSuccessNotice, createErrorNotice } = registry.dispatch( noticesStore );
@@ -562,6 +613,7 @@ export const markAsReadAction = {
updateMenuCounterOptimistically( 1 );
}
}
+
throw new Error( 'Failed to mark as read' );
} );
} )
@@ -571,9 +623,11 @@ export const markAsReadAction = {
if ( promises.some( ( { status } ) => status === 'fulfilled' ) ) {
invalidateCounts();
// Mark successfully updated records as invalid instead of removing from view
+
const updatedIds = items
.filter( ( _, index ) => promises[ index ]?.status === 'fulfilled' )
.map( item => item.id );
+
markRecordsAsInvalid( updatedIds );
}
@@ -592,6 +646,7 @@ export const markAsReadAction = {
),
items.length
);
+
createSuccessNotice( successMessage, {
type: 'snackbar',
id: 'mark-as-read-action',
@@ -604,26 +659,31 @@ export const markAsReadAction = {
},
],
} );
+
return;
}
+
// There is at least one failure.
const numberOfErrors = promises.filter( ( { status } ) => status === 'rejected' ).length;
const errorMessage = getGenericErrorMessage( numberOfErrors );
+
createErrorNotice( errorMessage, { type: 'snackbar' } );
},
};
-export const markAsUnreadAction = {
+export const markAsUnreadAction: Action = {
id: 'mark-as-unread',
+ isPrimary: false,
+ icon: ,
label: __( 'Mark as unread', 'jetpack-forms' ),
isEligible: item => ! item.is_unread,
supportsBulk: true,
- icon: ,
async callback( items, { registry } ) {
jetpackAnalytics.tracks.recordEvent( 'jetpack_forms_inbox_action_click', {
action: 'mark-as-unread',
multiple: items.length > 1,
} );
+
const { editEntityRecord } = registry.dispatch( coreStore );
const { getEntityRecord } = registry.select( coreStore );
const { createSuccessNotice, createErrorNotice } = registry.dispatch( noticesStore );
@@ -668,10 +728,12 @@ export const markAsUnreadAction = {
updateMenuCounterOptimistically( -1 );
}
}
+
throw new Error( 'Failed to mark as unread' );
} );
} )
);
+
if ( promises.every( ( { status } ) => status === 'fulfilled' ) ) {
// Invalidate counts cache to ensure counts are refetched and stay accurate
invalidateCounts();
@@ -692,6 +754,7 @@ export const markAsUnreadAction = {
),
items.length
);
+
createSuccessNotice( successMessage, {
type: 'snackbar',
id: 'mark-as-unread-action',
@@ -704,11 +767,14 @@ export const markAsUnreadAction = {
},
],
} );
+
return;
}
+
// There is at least one failure.
const numberOfErrors = promises.filter( ( { status } ) => status === 'rejected' ).length;
const errorMessage = getGenericErrorMessage( numberOfErrors );
+
createErrorNotice( errorMessage, { type: 'snackbar' } );
},
};
diff --git a/projects/packages/forms/src/dashboard/inbox/dataviews/index.js b/projects/packages/forms/src/dashboard/inbox/dataviews/index.js
index 99f55321c7bcf..e7b7fd638a834 100644
--- a/projects/packages/forms/src/dashboard/inbox/dataviews/index.js
+++ b/projects/packages/forms/src/dashboard/inbox/dataviews/index.js
@@ -6,14 +6,16 @@ import {
ExternalLink,
// eslint-disable-next-line @wordpress/no-unsafe-wp-apis
__experimentalHStack as HStack,
+ Modal,
} from '@wordpress/components';
-import { useResizeObserver } from '@wordpress/compose';
+import { useResizeObserver, useViewportMatch } from '@wordpress/compose';
import { DataViews } from '@wordpress/dataviews/wp';
import { dateI18n, getSettings as getDateSettings } from '@wordpress/date';
import { useCallback, useMemo, useState } from '@wordpress/element';
import { decodeEntities } from '@wordpress/html-entities';
import { __ } from '@wordpress/i18n';
import { Icon, globe } from '@wordpress/icons';
+import clsx from 'clsx';
import { useEffect } from 'react';
import { useSearchParams } from 'react-router';
/**
@@ -89,6 +91,30 @@ export default function InboxView() {
);
const isMobile = containerWidth <= MOBILE_BREAKPOINT;
const selectedResponses = searchParams.get( 'r' );
+ const isMobileViewport = useViewportMatch( 'medium', '<' );
+ const [ isResponseModalOpen, setIsResponseModalOpen ] = useState( false );
+ const [ responseModal, setResponseModal ] = useState( null );
+
+ const closeResponseModal = useCallback( () => {
+ setIsResponseModalOpen( false );
+ setResponseModal( null );
+ }, [ setIsResponseModalOpen, setResponseModal ] );
+
+ const openResponseModal = useCallback(
+ item => {
+ const content = ;
+
+ setResponseModal( content );
+ setIsResponseModalOpen( true );
+ },
+ [ setIsResponseModalOpen, closeResponseModal, setResponseModal ]
+ );
+
+ useEffect( () => {
+ if ( ! isMobileViewport ) {
+ closeResponseModal();
+ }
+ }, [ isMobileViewport, closeResponseModal ] );
useEffect( () => {
return setupSidebarWidthObserver();
@@ -228,8 +254,22 @@ export default function InboxView() {
item.author_name || item.author_email || item.author_url || item.ip
);
const defaultImage = item.author_name || item.author_email ? 'initials' : 'mp';
+
+ const handleClick = isMobileViewport ? () => openResponseModal( item ) : undefined;
+
return (
-
+
{},
+ role: 'button',
+ tabIndex: 0,
+ } ) }
+ >
{ item.is_unread && (
{
- const _actions = [
- markAsReadAction,
- markAsUnreadAction,
- markAsSpamAction,
- markAsNotSpamAction,
- moveToTrashAction,
- editFormAction,
- restoreAction,
- deleteAction,
- ];
- if ( isMobile ) {
- _actions.unshift( {
- ...viewAction,
- RenderModal: ( { items, closeModal } ) => {
- jetpackAnalytics.tracks.recordEvent( 'jetpack_forms_inbox_action_click', {
- action: 'view-response',
- multiple: items.length > 1,
- } );
- const [ item ] = items;
- return ;
- },
- hideModalHeader: true,
- } );
- } else {
- _actions.unshift( {
- ...viewAction,
- callback( items ) {
- jetpackAnalytics.tracks.recordEvent( 'jetpack_forms_inbox_action_click', {
- action: 'view-response',
- multiple: items.length > 1,
- } );
- const [ item ] = items;
- const selectedId = item.id.toString();
- const selectionWithoutSelectedId = selection.filter( id => id !== selectedId );
- onChangeSelection( [ ...selectionWithoutSelectedId, selectedId ] );
- },
- } );
+ const mobileViewAction = {
+ ...viewAction,
+ RenderModal: ( { items, closeModal } ) => {
+ jetpackAnalytics.tracks.recordEvent( 'jetpack_forms_inbox_action_click', {
+ action: 'view-response',
+ multiple: items.length > 1,
+ } );
+
+ const [ item ] = items;
+
+ return ;
+ },
+ hideModalHeader: true,
+ };
+
+ const desktopViewAction = {
+ ...viewAction,
+ callback( items ) {
+ jetpackAnalytics.tracks.recordEvent( 'jetpack_forms_inbox_action_click', {
+ action: 'view-response',
+ multiple: items.length > 1,
+ } );
+
+ const [ item ] = items;
+ const selectedId = item.id.toString();
+ const selectionWithoutSelectedId = selection.filter( id => id !== selectedId );
+
+ onChangeSelection( [ ...selectionWithoutSelectedId, selectedId ] );
+ },
+ };
+
+ const viewResponseAction = isMobile ? mobileViewAction : desktopViewAction;
+
+ const primaryActions = [ viewResponseAction ];
+ const secondaryActions = [ markAsUnreadAction, editFormAction ];
+
+ switch ( statusFilter ) {
+ case 'trash':
+ return [ ...primaryActions, restoreAction, deleteAction, ...secondaryActions ];
+ case 'spam':
+ return [ ...primaryActions, markAsNotSpamAction, moveToTrashAction, ...secondaryActions ];
+ default:
+ return [
+ ...primaryActions,
+ markAsReadAction,
+ markAsSpamAction,
+ moveToTrashAction,
+ ...secondaryActions,
+ ];
}
- return _actions;
- }, [ isMobile, onChangeSelection, selection ] );
+ }, [ isMobile, onChangeSelection, selection, statusFilter ] );
const resetPage = useCallback( () => {
view.page = 1;
@@ -410,6 +467,15 @@ export default function InboxView() {
/>
}
/>
+ { isResponseModalOpen && (
+
+ { responseModal }
+
+ ) }
{
+ // Notices store actions
+ createSuccessNotice: (
+ message: string,
+ options: { type?: string; id?: string; actions?: { label: string; onClick: () => void }[] }
+ ) => void;
+ createErrorNotice: (
+ message: string,
+ options: { type?: string; id?: string; actions?: { label: string; onClick: () => void }[] }
+ ) => void;
+
+ // Core store actions
+ saveEntityRecord: (
+ kind: string,
+ name: string,
+ record: Record< string, unknown >
+ ) => Promise< void >;
+ deleteEntityRecord: (
+ kind: string,
+ name: string,
+ recordId: number,
+ query: Record< string, unknown >,
+ options?: { throwOnError?: boolean }
+ ) => Promise< void >;
+ editEntityRecord: (
+ kind: string,
+ name: string,
+ recordId: number,
+ edits: Record< string, unknown >
+ ) => Promise< void >;
+
+ // Dashboard store actions
+ updateCountsOptimistically: (
+ status: string,
+ newStatus: string,
+ count: number,
+ queryParams: QueryParams
+ ) => void;
+ doBulkAction: ( ids: string[], action: string ) => void;
+ invalidateFilters: () => void;
+ invalidateCounts: () => void;
+ markRecordsAsInvalid: ( ids: number[] ) => void;
+ setCurrentQuery: ( queryParams: QueryParams ) => void;
+ };
+ select: ( store: StoreDescriptor ) => {
+ // Dashboard store select actions
+ getCurrentQuery: () => QueryParams;
+ getTrashCount: ( queryParams: QueryParams ) => number;
+ getSpamCount: ( queryParams: QueryParams ) => number;
+ getInboxCount: ( queryParams: QueryParams ) => number;
+
+ // Core store select actions
+ getEntityRecord: (
+ kind: string,
+ name: string,
+ recordId: number
+ ) => Record< string, unknown > | undefined;
+ };
+};
+
+export type Action = {
+ id: string;
+ isPrimary: boolean;
+ icon: React.ReactNode;
+ label: string;
+ modalHeader?: string;
+ isEligible?: ( item: FormResponse ) => boolean;
+ supportsBulk?: boolean;
+ callback?: ( items: FormResponse[], { registry }: { registry: Registry } ) => Promise< void >;
+};
diff --git a/projects/packages/forms/src/dashboard/inbox/style.scss b/projects/packages/forms/src/dashboard/inbox/style.scss
index 90142c4ec0fb5..154536f0e2acf 100644
--- a/projects/packages/forms/src/dashboard/inbox/style.scss
+++ b/projects/packages/forms/src/dashboard/inbox/style.scss
@@ -350,6 +350,10 @@
display: flex;
align-items: center;
gap: 12px;
+
+ &--mobile {
+ text-decoration: underline;
+ }
}
.jp-forms__inbox__unread-indicator {
diff --git a/projects/packages/forms/src/types/index.ts b/projects/packages/forms/src/types/index.ts
index 4438476a52421..83b019fb6484d 100644
--- a/projects/packages/forms/src/types/index.ts
+++ b/projects/packages/forms/src/types/index.ts
@@ -114,6 +114,8 @@ export interface FormResponse {
is_unread: boolean;
/** The fields of the response. */
fields: Record< string, unknown >;
+ /** The URL to edit the form that the response was submitted to. */
+ edit_form_url: string;
}
/**