Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 | 22x 22x 22x 22x 22x 22x 22x 7x 7x 5x 1x 4x 4x 4x 4x 2x 2x 2x 2x 2x | import { useDispatch, useSelect } from '@wordpress/data';
import { useState } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import { AlertDialog, Stack } from '@wordpress/ui';
import { STORE_ID } from 'store';
import CardLink from './card-link';
/**
* Edit + Restore-default link pair for a `Singleton_Template_Cpt`-backed
* editor flow on the PHP side. The two consumers — the experimental
* blocks-powered Overlay (SEARCH-216) and the classic-theme Search
* template route — share an identical shape: one config blob describing
* the editor URL / postType / isCustomized state, one "Edit …" link,
* one "Restore default" link that opens a destructive confirm dialog,
* and an AJAX DELETE that posts a notice on success / failure.
*
* Local state (`justReset`, `isResetConfirmOpen`) is
* scoped here so two of these components can coexist on a single page
* (e.g. Embedded card + Overlay card both customized) without their
* reset flags cross-contaminating.
*
* @param {object} props - Props.
* @param {object} props.config - The `{enabled, editorUrl, postType, isCustomized}` blob from the matching singleton-template selector.
* @param {string} props.editLabel - Visible label for the "Edit …" link.
* @param {string} props.restoreConfirmMessage - Body copy for the destructive confirm dialog.
* @param {string} props.successMessage - Notice copy posted after a successful reset.
* @param {string} props.errorMessage - Notice copy posted when the AJAX reset fails (used as a fallback when the server doesn't supply an error message).
* @param {boolean} props.linksDisabled - Mirrors the card's "is this experience inactive" state — disables both links the same way the inactive card's commit-overlay button does.
* @return {import('react').Element} - The link pair + reset confirm dialog.
*/
export default function SingletonTemplateActions( {
config,
editLabel,
restoreConfirmMessage,
successMessage,
errorMessage,
linksDisabled,
} ) {
// Local override after a successful reset: the server-side
// `isCustomized` stays true in the initial-state blob the page was
// rendered with, so we hide the "Restore default" link client-side
// once the AJAX DELETE returns. Cleared if the admin opens the
// editor again (which would lazy-create a fresh singleton).
const [ justReset, setJustReset ] = useState( false );
const [ isResetConfirmOpen, setResetConfirmOpen ] = useState( false );
const { successNotice, errorNotice } = useDispatch( STORE_ID );
// Read the wpcom-origin-prefixed API root + nonce from the dashboard
// store directly (same selectors `wrapped-dashboard.jsx` hands to
// `restApi.setWpcomOriginApiUrl()` / `setApiNonce()` at boot). Building
// the URL here — rather than delegating to a helper in the shared
// `@automattic/jetpack-api` package — keeps this fix contained inside
// the Search package, so the wpcom-origin DELETE doesn't pull a
// monorepo-wide rebuild of every consumer of jetpack-api.
const { wpcomOriginApiUrl, apiNonce } = useSelect(
select => ( {
wpcomOriginApiUrl: select( STORE_ID ).getWpcomOriginApiUrl(),
apiNonce: select( STORE_ID ).getAPINonce(),
} ),
[]
);
const restoreLabel = __( 'Restore default', 'jetpack-search-pkg' );
return (
<>
<Stack
direction="row"
gap="sm"
align="start"
className="jp-search-experience-option__actions"
>
{ /*
The edit link always renders so the affordance stays
visible as a muted CTA on inactive cards too — matches the
FSE "Edit search template" / "Insert pattern" pattern on
the Embedded card. Disabled when (a) the card isn't the
active experience yet (the surrounding card's inactive
state) or (b) the PHP side withheld `config.editorUrl` for
non-admins — `CardLink` renders an inert `<span>` in that
state, so a non-admin who somehow lands on this page
doesn't get a live `<a href="#">` that navigates nowhere.
*/ }
<CardLink
label={ editLabel }
href={ config.editorUrl || '#' }
disabled={ linksDisabled || ! config.editorUrl }
// Re-show "Restore default" on the admin's return from
// the editor: the click implies a fresh singleton is
// about to be (re-)created on the server, so the
// previously-set `justReset` flag no longer reflects
// reality.
onClick={ () => setJustReset( false ) }
/>
{ config.postType && config.isCustomized && ! justReset && (
<CardLink
label={ restoreLabel }
href="#"
disabled={ linksDisabled }
onClick={ event => {
// Destructive — open the confirm dialog instead of
// running the DELETE directly. The dialog's
// confirm handler is the one that fires the request.
event.preventDefault();
setResetConfirmOpen( true );
} }
/>
) }
</Stack>
<AlertDialog.Root
open={ isResetConfirmOpen }
onOpenChange={ setResetConfirmOpen }
onConfirm={ async () => {
// Defensive guard: the dialog can only open via the
// `isCustomized && postType` CardLink above, so `postType`
// is non-null in practice — avoid firing a DELETE against
// `undefined` if the gating logic ever shifts.
if ( ! config.postType || ! wpcomOriginApiUrl ) {
return;
}
try {
// Build the URL against `wpcomOriginApiUrl` (not
// apiFetch's default /wp-json/ root) so the request
// reaches the Jetpack route on WordPress.com Simple
// sites, where Jetpack-registered routes are only
// exposed under wp-json/wpcom-origin/. The nonce
// header matches what the rest of the Search dashboard
// sends via restApi for the same reason.
const url = `${ wpcomOriginApiUrl }jetpack/v4/search/templates/${ encodeURIComponent(
config.postType
) }`;
const response = await fetch( url, {
method: 'DELETE',
credentials: 'same-origin',
headers: { 'X-WP-Nonce': apiNonce ?? '' },
} );
if ( ! response.ok ) {
const data = await response.json().catch( () => null );
throw new Error( data?.message || errorMessage );
}
setJustReset( true );
successNotice( successMessage );
} catch ( error ) {
errorNotice( error?.message || errorMessage );
}
} }
>
<AlertDialog.Popup
intent="irreversible"
title={ restoreLabel }
description={ restoreConfirmMessage }
confirmButtonText={ restoreLabel }
/>
</AlertDialog.Root>
</>
);
}
|