PluginProbe
Booking Calendar / 11.9
Booking Calendar v11.9
11.9 11.8.4 11.8.3 11.8.2 11.8.1 11.8 11.7 11.6.1 11.6 11.5 11.4.3 11.4.2 11.4.1 11.4 11.3 11.2.1 11.2 11.1 11.0 10.15.7 10.15.6 10.1.3 10.10 10.10.1 10.10.2 All 205 releases
← All changes | includes/page-appointment-services/appointment_services__page.php +258 -61 11.5 → 11.9 View file →
@@ -16,11 +16,9 @@
16 16 return;
17 17 }
18 18
19 19 $is_resources_page_with_mode_guide = function_exists( 'wpbc_is_resources_page' )
20 - && wpbc_is_resources_page()
21 - && function_exists( 'wpbc_is_11_5_features_enabled' )
22 - && wpbc_is_11_5_features_enabled();
20 + && wpbc_is_resources_page();
23 21
24 22 if ( $is_resources_page_with_mode_guide ) {
25 23 wp_enqueue_style(
26 24 'wpbc-appointment-provider-tools',
@@ -33,10 +31,10 @@
33 31 if ( ! wpbc_appointment_services__is_page() ) {
34 32 return;
35 33 }
36 34
37 - WPBC_UI_Listing::enqueue_styles();
38 - wp_enqueue_style( 'wpbc-appointment-services-page', trailingslashit( plugins_url( '', __FILE__ ) ) . '_out/appointment_services_page.css', array( 'wpbc-ui-listing' ), WP_BK_VERSION_NUM );
35 + WPBC_UI_Catalog::enqueue_styles();
36 + wp_enqueue_style( 'wpbc-appointment-services-page', trailingslashit( plugins_url( '', __FILE__ ) ) . '_out/appointment_services_page.css', array( 'wpbc-ui-catalog' ), WP_BK_VERSION_NUM );
39 37 }
40 38 add_action( 'wpbc_enqueue_css_files', 'wpbc_appointment_services_enqueue_css', 66 );
41 39
42 40
@@ -42,11 +40,10 @@
42 40
43 41 /**
44 42 * Resolve the Service that should be selected when the catalog first opens.
45 43 *
46 - * An explicit `service_id` request always wins. The first owner-visible active
47 - * Service is selected automatically only on the Service and Provider Setup
48 - * Wizard step, leaving ordinary Services-page visits unchanged.
44 + * An explicit `service_id` request opens that Service. Ordinary catalog visits
45 + * do not infer a selection from Setup Wizard state.
49 46 *
50 47 * @return int Initial Service ID, or zero when the inspector should stay closed.
51 48 */
52 49 function wpbc_appointment_services_get_initial_service_id() {
@@ -56,37 +53,187 @@
56 53 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only selection state.
57 54 ? absint( wp_unslash( $_GET['service_id'] ) )
58 55 : 0;
59 56
60 - if ( $requested_service_id ) {
61 - return $requested_service_id;
57 + return $requested_service_id;
58 +}
59 +
60 +/**
61 + * Migrate released Service listing preferences into the shared catalog namespace.
62 + *
63 + * The migration runs only when the new site-local preference payload has never
64 + * been created. Existing shared preferences, including an explicit reset, are
65 + * never replaced by older listing values.
66 + *
67 + * @param array<string,mixed> $configuration Registered Service catalog configuration.
68 + *
69 + * @return void
70 + */
71 +function wpbc_appointment_services_migrate_catalog_preferences( $configuration ) {
72 + $user_id = get_current_user_id();
73 + $namespace = WPBC_UI_Catalog_Preferences::get_namespace( 'appointment_services_catalog' );
74 + if ( ! $user_id || '' === $namespace || false !== get_user_option( $namespace, $user_id ) ) {
75 + return;
62 76 }
63 77
64 - if (
65 - ! function_exists( 'wpbc_setup_wizard_page__is_active_step' )
66 - || ! wpbc_setup_wizard_page__is_active_step( 'service_provider' )
67 - || ! function_exists( 'wpbc_appointment_services_repository' )
68 - ) {
69 - return 0;
78 + $legacy_preferences = get_user_option( 'wpbc_admin_listing_preferences', $user_id );
79 + $legacy_preferences = is_array( $legacy_preferences ) && isset( $legacy_preferences['appointment_services_catalog'] ) && is_array( $legacy_preferences['appointment_services_catalog'] )
80 + ? $legacy_preferences['appointment_services_catalog']
81 + : array();
82 + if ( empty( $legacy_preferences ) ) {
83 + return;
70 84 }
71 85
72 - $services = wpbc_appointment_services_repository()->list_items(
86 + $request_values = array_intersect_key(
87 + $legacy_preferences,
73 88 array(
74 - 'status' => 'active',
75 - 'sort_by' => 'service_id',
76 - 'sort_order' => 'asc',
77 - 'limit' => 1,
89 + 'items_per_page' => true,
90 + 'sort_by' => true,
91 + 'sort_order' => true,
92 + 'column_order' => true,
78 93 )
79 94 );
95 + if ( isset( $legacy_preferences['visible_fields'] ) && is_array( $legacy_preferences['visible_fields'] ) ) {
96 + $request_values['visible_columns'] = $legacy_preferences['visible_fields'];
97 + }
98 + $legacy_filters = isset( $legacy_preferences['filters'] ) && is_array( $legacy_preferences['filters'] )
99 + ? $legacy_preferences['filters']
100 + : array();
101 + $service_request = WPBC_Appointment_Services_Catalog_Request::create(
102 + array(
103 + 'status' => isset( $legacy_filters['status'] ) ? $legacy_filters['status'] : 'all',
104 + 'resource_id' => isset( $legacy_filters['resource_id'] ) ? $legacy_filters['resource_id'] : 0,
105 + )
106 + );
80 107
81 - if ( is_wp_error( $services ) || empty( $services[0]['service_id'] ) ) {
82 - return 0;
108 + $request = WPBC_UI_Catalog_Request::create( $configuration, $request_values );
109 + if ( ! is_wp_error( $request ) && ! is_wp_error( $service_request ) ) {
110 + WPBC_UI_Catalog_Preferences::save(
111 + 'appointment_services_catalog',
112 + $request,
113 + array(
114 + 'status' => $service_request->get( 'status', 'all' ),
115 + 'resource_id' => $service_request->get( 'resource_id', 0 ),
116 + ),
117 + $user_id,
118 + 1
119 + );
83 120 }
121 +}
84 122
85 - return absint( $services[0]['service_id'] );
123 +/**
124 + * Return validated initial URL overrides for the Services catalog.
125 + *
126 + * @return array<string,mixed> Shared catalog URL overrides.
127 + */
128 +function wpbc_appointment_services_get_initial_catalog_overrides() {
129 + $parameter_map = array(
130 + 'page_number' => 'catalog_page',
131 + 'items_per_page' => 'catalog_per_page',
132 + 'sort_by' => 'catalog_sort',
133 + 'sort_order' => 'catalog_order',
134 + 'search' => 'catalog_search',
135 + 'visible_columns' => 'catalog_columns',
136 + 'column_order' => 'catalog_column_order',
137 + 'template_pack' => 'template_pack',
138 + );
139 + $url_overrides = array();
140 + foreach ( $parameter_map as $request_key => $parameter_name ) {
141 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only initial catalog state.
142 + if ( ! isset( $_GET[ $parameter_name ] ) ) {
143 + continue;
144 + }
145 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Validated by WPBC_UI_Catalog_Request.
146 + $request_value = wp_unslash( $_GET[ $parameter_name ] );
147 + if ( in_array( $request_key, array( 'visible_columns', 'column_order' ), true ) && is_scalar( $request_value ) ) {
148 + $request_value = '' === trim( (string) $request_value ) ? array() : explode( ',', (string) $request_value );
149 + }
150 + $url_overrides[ $request_key ] = $request_value;
151 + }
152 +
153 + return $url_overrides;
86 154 }
87 155
156 +/**
157 + * Build the shared Services catalog browser configuration.
158 + *
159 + * @return array<string,mixed> Browser-safe Service and catalog configuration.
160 + */
161 +function wpbc_appointment_services_get_catalog_client_config() {
162 + $catalog_id = 'appointment_services_catalog';
163 + $domain_configuration = wpbc_appointment_services_get_catalog_config();
164 + $configuration = WPBC_UI_Catalog_Registry::get_instance()->get_configuration( $catalog_id );
165 + if ( empty( $configuration ) ) {
166 + return array();
167 + }
88 168
169 + wpbc_appointment_services_migrate_catalog_preferences( $configuration );
170 + $stored_preferences = WPBC_UI_Catalog_Preferences::load( $catalog_id );
171 + $shared_request = WPBC_UI_Catalog_Request::create(
172 + $configuration,
173 + array( 'request_id' => 1 ),
174 + $stored_preferences,
175 + wpbc_appointment_services_get_initial_catalog_overrides(),
176 + true
177 + );
178 + $service_values = array(
179 + 'status' => isset( $stored_preferences['status'] ) ? $stored_preferences['status'] : 'all',
180 + 'resource_id' => isset( $stored_preferences['resource_id'] ) ? $stored_preferences['resource_id'] : 0,
181 + );
182 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only initial Provider filter.
183 + if ( isset( $_GET['provider_id'] ) ) {
184 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Validated by the Service request object.
185 + $service_values['resource_id'] = wp_unslash( $_GET['provider_id'] );
186 + }
187 + $service_request = WPBC_Appointment_Services_Catalog_Request::create( $service_values );
188 + $auto_load = ! is_wp_error( $shared_request ) && ! is_wp_error( $service_request );
189 + if ( ! $auto_load ) {
190 + $shared_request = WPBC_UI_Catalog_Request::create( $configuration, array( 'request_id' => 1 ) );
191 + if ( is_wp_error( $shared_request ) ) {
192 + return array();
193 + }
194 + $initial_error = is_wp_error( $service_request ) ? $service_request : new WP_Error( 'wpbc_appointment_services_invalid_request', __( 'The Services request is invalid.', 'booking' ) );
195 + $initial_response = WPBC_UI_Catalog_Response::from_wp_error( $catalog_id, 1, $initial_error );
196 + $service_request = WPBC_Appointment_Services_Catalog_Request::create();
197 + } else {
198 + $empty_response = WPBC_UI_Catalog_Response::create_empty( $catalog_id, $shared_request );
199 + $initial_response = is_wp_error( $empty_response )
200 + ? WPBC_UI_Catalog_Response::from_wp_error( $catalog_id, 1, $empty_response )
201 + : $empty_response->to_array();
202 + }
203 +
204 + $initial_request = $shared_request->to_array();
205 + $initial_request['status'] = $service_request->get( 'status', 'all' );
206 + $initial_request['resource_id'] = $service_request->get( 'resource_id', 0 );
207 + $client_configuration = WPBC_UI_Catalog::get_client_configuration( $catalog_id, 'wpbc_appointment_services_catalog', $initial_request, $initial_response );
208 + $client_configuration['ajax_url'] = admin_url( 'admin-ajax.php' );
209 + $client_configuration['auto_load'] = $auto_load;
210 + $client_configuration['nonce'] = wp_create_nonce( $configuration['nonce_name'] );
211 + $default_request = WPBC_UI_Catalog_Request::create( $configuration );
212 + if ( is_wp_error( $default_request ) ) {
213 + return array();
214 + }
215 + $client_configuration['default_request'] = $default_request->to_array();
216 + $client_configuration['default_request']['status'] = 'all';
217 + $client_configuration['default_request']['resource_id'] = 0;
218 + $client_configuration['url_parameters'] = array(
219 + 'page_number' => 'catalog_page',
220 + 'items_per_page' => 'catalog_per_page',
221 + 'sort_by' => 'catalog_sort',
222 + 'sort_order' => 'catalog_order',
223 + 'search' => 'catalog_search',
224 + 'visible_columns' => 'catalog_columns',
225 + 'column_order' => 'catalog_column_order',
226 + 'template_pack' => 'template_pack',
227 + 'resource_id' => 'provider_id',
228 + );
229 + $client_configuration['filters'] = $domain_configuration['filters'];
230 + $client_configuration['actions'] = $domain_configuration['actions'];
231 +
232 + return $client_configuration;
233 +}
234 +
235 +
89 236 /**
90 237 * Enqueue and configure the AJAX Appointment Services management client.
91 238 *
92 239 * @param string $where_to_load Booking Calendar asset context.
@@ -96,31 +243,51 @@
96 243 function wpbc_appointment_services_enqueue_js( $where_to_load ) {
97 244 if ( ! in_array( $where_to_load, array( 'admin', 'both' ), true ) || ! wpbc_appointment_services__is_page() ) {
98 245 return;
99 246 }
247 + WPBC_UI_Catalog::enqueue_scripts();
100 248 wpbc_load_js__required_for_media_upload();
101 - $service_listing = wpbc_appointment_services_get_catalog_listing();
102 249 $selected_service_id = wpbc_appointment_services_get_initial_service_id();
250 + $default_provider_ids = array();
251 +
252 + if ( ! class_exists( 'wpdev_bk_personal' ) ) {
253 + $default_provider_id = function_exists( 'wpbc_get_default_resource' ) ? absint( wpbc_get_default_resource() ) : 1;
254 + if ( ! $default_provider_id ) {
255 + $default_provider_id = 1;
256 + }
257 +
258 + $default_provider_ids[] = $default_provider_id;
259 + }
260 +
103 261 wp_enqueue_script( 'wpbc-appointment-services-page', trailingslashit( plugins_url( '', __FILE__ ) ) . '_out/appointment_services_page.js', array(
104 262 'jquery',
105 263 'wpbc_all',
264 + 'wpbc-ui-catalog',
265 + 'wpbc-ui-catalog-actions',
106 266 ), WP_BK_VERSION_NUM, array( 'in_footer' => WPBC_JS_IN_FOOTER ) );
107 267
108 268 wp_localize_script( 'wpbc-appointment-services-page', 'wpbc_appointment_services_config', array(
109 - 'ajax_url' => admin_url( 'admin-ajax.php' ),
110 - 'nonce' => wp_create_nonce( 'wpbc_appointment_services_ajax_nonce' ),
111 - 'actions' => array(
269 + 'ajax_url' => admin_url( 'admin-ajax.php' ),
270 + 'nonce' => wp_create_nonce( 'wpbc_appointment_services_ajax_nonce' ),
271 + 'actions' => array(
112 272 'list' => 'WPBC_AJX_APPOINTMENT_SERVICES_LIST',
113 273 'load' => 'WPBC_AJX_APPOINTMENT_SERVICE_LOAD',
114 274 'save' => 'WPBC_AJX_APPOINTMENT_SERVICE_SAVE',
115 275 'duplicate' => 'WPBC_AJX_APPOINTMENT_SERVICE_DUPLICATE',
116 276 'archive' => 'WPBC_AJX_APPOINTMENT_SERVICE_ARCHIVE',
277 + 'inline_schema' => 'WPBC_AJX_APPOINTMENT_SERVICES_INLINE_SCHEMA',
278 + 'bulk_contract' => 'WPBC_AJX_APPOINTMENT_SERVICES_BULK_CONTRACT',
279 + 'preview' => 'WPBC_AJX_APPOINTMENT_SERVICES_CATALOG_PREVIEW',
280 + 'apply' => 'WPBC_AJX_APPOINTMENT_SERVICES_CATALOG_APPLY',
281 + 'delete_preview' => 'WPBC_AJX_APPOINTMENT_SERVICES_DELETE_PREVIEW',
282 + 'delete_apply' => 'WPBC_AJX_APPOINTMENT_SERVICES_DELETE_APPLY',
117 283 ),
118 - 'selected_id' => $selected_service_id,
119 - 'focus_section' => wpbc_appointment_services_get_requested_focus_section(),
120 - 'pricing_available' => wpbc_appointment_services_is_pricing_available(),
121 - 'listing' => $service_listing->get_client_settings(),
122 - 'i18n' => array(
284 + 'selected_id' => $selected_service_id,
285 + 'focus_section' => wpbc_appointment_services_get_requested_focus_section(),
286 + 'default_provider_ids' => $default_provider_ids,
287 + 'pricing_available' => wpbc_appointment_services_is_pricing_available(),
288 + 'catalog' => wpbc_appointment_services_get_catalog_client_config(),
289 + 'i18n' => array(
123 290 'loading' => __( 'Loading Services', 'booking' ) . '...',
124 291 'empty' => __( 'No Services yet', 'booking' ),
125 292 'empty_help' => __( 'Create the first Service to define what customers can book.', 'booking' ),
126 293 'no_providers' => __( 'No Providers available', 'booking' ),
@@ -126,15 +293,27 @@
126 293 'no_providers' => __( 'No Providers available', 'booking' ),
127 294 'no_providers_help'=> __( 'Create a Provider before assigning Services and accepting Appointments.', 'booking' ),
128 295 'not_connected' => __( 'The Services database is not ready. Reload this page as an administrator or reactivate Booking Calendar to finish the database upgrade.', 'booking' ),
129 296 'load_failed' => __( 'Services could not be loaded.', 'booking' ),
297 + 'operation_failed' => __( 'The Service editor could not be opened. Please reload the page and try again.', 'booking' ),
130 298 'save_failed' => __( 'The Service could not be saved.', 'booking' ),
131 299 'duplicate_failed' => __( 'The Service could not be duplicated.', 'booking' ),
132 300 'archive_failed' => __( 'The Service could not be archived.', 'booking' ),
301 + 'preview_failed' => __( 'The Service changes could not be reviewed.', 'booking' ),
302 + 'apply_failed' => __( 'The Service changes could not be applied.', 'booking' ),
303 + 'delete_preview_failed' => __( 'The Service deletion could not be reviewed.', 'booking' ),
304 + 'delete_apply_failed' => __( 'The selected Services could not be permanently deleted.', 'booking' ),
133 305 'confirm_archive' => __( 'Archive this Service?', 'booking' ),
134 306 'confirm_discard' => __( 'Discard unsaved Service changes?', 'booking' ),
135 307 'untitled' => __( 'Untitled Service', 'booking' ),
136 308 'new_service' => __( 'New Service', 'booking' ),
309 + 'create_service_title' => __( 'Add Service', 'booking' ),
310 + 'create_service_description' => __( 'Create a Service without leaving the catalog.', 'booking' ),
311 + 'edit_service_title' => __( 'Edit Service', 'booking' ),
312 + 'edit_service_description' => __( 'Update this Service without leaving the catalog.', 'booking' ),
313 + 'inspector_context_new' => __( 'New', 'booking' ),
314 + /* translators: %d: Service ID. */
315 + 'inspector_context_id' => __( 'ID: %d', 'booking' ),
137 316 'active' => __( 'Active', 'booking' ),
138 317 'draft' => __( 'Draft', 'booking' ),
139 318 'archived' => __( 'Archived', 'booking' ),
140 319 'no_provider' => __( 'No Providers assigned', 'booking' ),
@@ -164,8 +343,27 @@
164 343 'service_thumbnail_tooltip' => __( 'Title: %1$s — Description: %2$s', 'booking' ),
165 344 'no_description' => __( 'No description', 'booking' ),
166 345 'edit' => __( 'Edit Service', 'booking' ),
167 346 'archive' => __( 'Archive Service', 'booking' ),
347 + /* translators: %s: Service title. */
348 + 'select_service' => __( 'Select %s', 'booking' ),
349 + 'select_all' => __( 'Select all visible Services', 'booking' ),
350 + 'editing_rows' => __( 'Editing rows', 'booking' ),
351 + /* translators: %s: Number of changed Service rows. */
352 + 'changed_rows' => __( '%s changed rows', 'booking' ),
353 + 'changed' => __( 'Changed', 'booking' ),
354 + 'inline_help' => __( 'Edit safe fields on this page, then review all changes before applying them.', 'booking' ),
355 + 'inline_schema_failed' => __( 'Safe inline Service fields could not be loaded.', 'booking' ),
356 + 'bulk_contract_failed' => __( 'Safe fields could not be determined for this Service selection.', 'booking' ),
357 + 'cancel' => __( 'Cancel', 'booking' ),
358 + 'review_changes' => __( 'Review changes', 'booking' ),
359 + 'apply_changes' => __( 'Apply changes', 'booking' ),
360 + 'bulk_edit_title' => __( 'Edit selected Services', 'booking' ),
361 + 'bulk_edit_description' => __( 'Enable only the fields that should change for every selected Service.', 'booking' ),
362 + 'inline_review_title' => __( 'Review inline changes', 'booking' ),
363 + 'bulk_review_title' => __( 'Review bulk changes', 'booking' ),
364 + 'review_confirmation' => __( 'Confirm the row-specific changes before applying them.', 'booking' ),
365 + 'review_description' => __( 'No Service will change until you choose Apply changes.', 'booking' ),
168 366 'showing' => __( 'Showing %1$s–%2$s of %3$s Services', 'booking' ),
169 367 ),
170 368 'currency_symbol' => wpbc_appointment_services_is_pricing_available() && function_exists( 'wpbc_get_currency_symbol' ) ? wpbc_get_currency_symbol() : '',
171 369 'weekdays' => array(
@@ -181,8 +379,23 @@
181 379 }
182 380 add_action( 'wpbc_enqueue_js_files', 'wpbc_appointment_services_enqueue_js', 66 );
183 381
184 382 /**
383 + * Print Service-owned catalog templates on the Services page only.
384 + *
385 + * @return void
386 + */
387 +function wpbc_appointment_services_print_catalog_templates() {
388 + static $templates_printed = false;
389 + if ( $templates_printed || ! wpbc_appointment_services__is_page() ) {
390 + return;
391 + }
392 + $templates_printed = true;
393 + WPBC_UI_Catalog::print_templates( 'appointment_services_catalog' );
394 +}
395 +add_action( 'admin_footer', 'wpbc_appointment_services_print_catalog_templates', 40 );
396 +
397 +/**
185 398 * Render the global Add Service action in the Booking Calendar top toolbar.
186 399 *
187 400 * Creating a Service is a page-level action, while saving remains contextual
188 401 * to the Service editor in the right inspector.
@@ -201,9 +414,9 @@
201 414 return;
202 415 }
203 416 ?>
204 417 <div class="wpbc_ui_el__buttons_group wpbc_appointment_services__top_toolbar">
205 - <button type="button" class="button button-secondary wpbc_appointment_services__add" disabled>
418 + <button type="button" class="button button-primary wpbc_appointment_services__add" disabled>
206 419 <span class="wpbc-bi-plus-lg" aria-hidden="true"></span>
207 420 <?php esc_html_e( 'Add Service', 'booking' ); ?>
208 421 </button>
209 422 </div>
@@ -241,11 +454,12 @@
241 454 return array(
242 455 'appointment_services' => array(
243 456 'is_show_top_path' => true,
244 457 'is_show_top_navigation' => false,
245 - 'left_navigation__default_view_mode' => 'compact',
458 + 'left_navigation__default_view_mode' => '', // FixIn:11.7.1.1: 'compact',
246 459 'right_vertical_sidebar__is_show' => true,
247 460 'right_vertical_sidebar__default_view_mode' => 'none',
461 + 'right_vertical_sidebar__content_click_collapse_mode' => 'none',
248 462 'right_vertical_sidebar_compact__is_show' => true,
249 463 'top_path' => array( 'root_title' => false ),
250 464 'top_path_title' => __( 'Services', 'booking' ),
251 465 'title' => __( 'Services', 'booking' ),
@@ -352,15 +566,15 @@
352 566 <span class="wpbc_appointment_services__provider_total"><span><?php
353 567 esc_html_e( 'Providers', 'booking' ); ?></span><strong data-provider-count>0</strong></span>
354 568 </div>
355 569 <div class="wpbc_appointment_services__filter_controls">
356 - <div class="wpbc_appointment_services__search"><span class="wpbc-bi-search"
357 - aria-hidden="true"></span><label
358 - class="screen-reader-text" for="wpbc_service_search"><?php
359 - esc_html_e( 'Search Services', 'booking' ); ?></label><input type="search"
360 - id="wpbc_service_search"
361 - placeholder="<?php
362 - esc_attr_e( 'Search Services', 'booking' ); ?>"/>
570 + <div class="wpbc_appointment_services__search">
571 + <span class="wpbc-bi-search" aria-hidden="true"></span>
572 + <label class="screen-reader-text" for="wpbc_service_search"><?php esc_html_e( 'Search Services', 'booking' ); ?></label>
573 + <input type="search" id="wpbc_service_search" placeholder="<?php esc_attr_e( 'Search Services', 'booking' ); ?>" autocomplete="off" data-wpbc-ui-catalog-search />
574 + <button type="button" class="wpbc_appointment_services__search_clear" aria-label="<?php esc_attr_e( 'Clear Service search', 'booking' ); ?>" data-wpbc-ui-catalog-search-clear data-wpbc-appointment-services-search-clear hidden>
575 + <span class="wpbc-bi-x-lg" aria-hidden="true"></span>
576 + </button>
363 577 </div>
364 578 <label class="screen-reader-text" for="wpbc_service_provider_filter"><?php
365 579 esc_html_e( 'Filter by Provider', 'booking' ); ?></label>
366 580 <select id="wpbc_service_provider_filter">
@@ -381,27 +595,10 @@
381 595 <?php if ( $can_manage_providers ) : ?>
382 596 <a class="button button-secondary" href="<?php echo esc_url( admin_url( 'admin.php?page=wpbc-resources' ) ); ?>"><?php esc_html_e( 'Manage Providers', 'booking' ); ?></a>
383 597 <?php endif; ?>
384 598 </div>
385 - <div class="wpbc_appointment_services__loading is-visible" role="status" aria-hidden="false"
386 - data-wpbc-appointment-services-loader="1">
387 - <div class="wpbc_spins_loading_container">
388 - <div class="wpbc_booking_form_spin_loader">
389 - <div class="wpbc_spins_loader_wrapper">
390 - <div class="wpbc_spin_loader_one_new"></div>
391 - </div>
392 - </div>
393 - <span><?php
394 - esc_html_e( 'Loading Services', 'booking' ); ?> ...</span></div>
395 - </div>
396 - <?php wpbc_appointment_services_get_catalog_listing()->render(); ?>
397 - <div class="wpbc_appointment_services__empty" hidden><span class="wpbc-bi-grid"
398 - aria-hidden="true"></span>
399 - <h2><?php
400 - esc_html_e( 'No Services yet', 'booking' ); ?></h2>
401 - <p><?php
402 - esc_html_e( 'Create the first Service to define what customers can book.', 'booking' ); ?></p>
403 - </div>
599 + <div id="wpbc_appointment_services_catalog"></div>
600 + <noscript><p><?php esc_html_e( 'JavaScript is required to manage Services.', 'booking' ); ?></p></noscript>
404 601 </div>
405 602 </div>
406 603 <?php
407 604 }