Skip to content

Commit c8bf857

Browse files
committed
feat: add skeleton loading for payment request components
Add wc-block-components-skeleton__element class to all payment request containers in both classic and block checkout for better UX during component loading. Changes: - Classic checkout: Add skeleton class in PHP templates - Block checkout: Use React state to manage skeleton class - Ensure skeleton class is added when containers are created dynamically - Standardize fieldset classes across all payment methods - Remove skeleton class after 1000ms in block checkout
1 parent 3db424c commit c8bf857

12 files changed

+159
-94
lines changed

assets/css/monei-blocks-checkout.css

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,10 @@
5858

5959
/* Payment Request Container */
6060
.monei-payment-request-container {
61-
min-height: 3.125em;
61+
height: 3.125em;
6262
justify-content: center;
63-
background: #fff;
6463
width: 100%;
64+
border-radius: 0.25em;
6565
margin: 0.5em 0 0;
6666
}
6767

@@ -150,7 +150,6 @@
150150
margin: 0 0 1em 0 !important;
151151
}
152152

153-
154153
/* Place Order Button States */
155154
.wc-block-components-checkout-place-order-button:disabled {
156155
cursor: not-allowed;
@@ -168,5 +167,4 @@
168167
.monei-payment-request-container {
169168
min-height: 3.75em;
170169
}
171-
172170
}

assets/css/monei-classic-checkout.css

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,11 +104,10 @@ label .monei-icons {
104104

105105
/* Payment Request Container */
106106
.monei-payment-request-container {
107-
min-height: 50px;
107+
height: 50px;
108108
justify-content: center;
109-
background: #fff;
110109
width: 100%;
111-
margin: 8px 0 0;
110+
border-radius: 4px;
112111
}
113112

114113
/* Label Container */

assets/js/components/monei-apple-google-component.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ export const MoneiAppleGoogleContent = ( props ) => {
5656
const isInitializedRef = useRef( false );
5757
const [ isConfirming, setIsConfirming ] = useState( false );
5858
const [ error, setError ] = useState( '' );
59+
const [ isLoading, setIsLoading ] = useState( false );
5960

6061
// Subscribe to cart totals
6162
const cartTotals = useSelect( ( select ) => {
@@ -101,6 +102,9 @@ export const MoneiAppleGoogleContent = ( props ) => {
101102
return;
102103
}
103104

105+
// Show skeleton loading state
106+
setIsLoading( true );
107+
104108
// Clear container
105109
container.innerHTML = '';
106110

@@ -131,6 +135,11 @@ export const MoneiAppleGoogleContent = ( props ) => {
131135

132136
paymentRequest.render( container );
133137
paymentRequestRef.current = paymentRequest;
138+
139+
// Remove skeleton loading state after rendering
140+
setTimeout( () => {
141+
setIsLoading( false );
142+
}, 1000 );
134143
},
135144
[ moneiData, setError, buttonManager ]
136145
);
@@ -298,7 +307,9 @@ export const MoneiAppleGoogleContent = ( props ) => {
298307
) }
299308
<div
300309
id="payment-request-container"
301-
className="monei-payment-request-container"
310+
className={ `monei-payment-request-container${
311+
isLoading ? ' wc-block-components-skeleton__element' : ''
312+
}` }
302313
>
303314
{ /* Payment button will be rendered here */ }
304315
</div>

assets/js/helpers/monei-card-input-hooks.js

Lines changed: 42 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
const { useState, useEffect, useRef, useCallback } = wp.element;
1+
const { useState, useEffect, useRef, useCallback, useMemo } = wp.element;
22

33
/**
44
* Hook for managing cardholder name validation
@@ -8,7 +8,10 @@ const { useState, useEffect, useRef, useCallback } = wp.element;
88
* @return {Object}
99
*/
1010
export const useCardholderName = ( config = {} ) => {
11-
const pattern = config.pattern || /^[A-Za-zÀ-ú\s-]{5,50}$/;
11+
const pattern = useMemo(
12+
() => config.pattern || /^[A-Za-zÀ-ú\s-]{5,50}$/,
13+
[ config.pattern ]
14+
);
1215
const [ value, setValue ] = useState( '' );
1316
const [ error, setError ] = useState( '' );
1417
const [ touched, setTouched ] = useState( false );
@@ -74,6 +77,41 @@ export const useMoneiCardInput = ( config ) => {
7477
const containerRef = useRef( null );
7578
const hasInitialized = useRef( false );
7679

80+
/**
81+
* Create payment token
82+
*/
83+
const createToken = useCallback( async () => {
84+
if ( ! cardInputRef.current || ! monei?.createToken ) {
85+
setError( 'Card input not initialized' );
86+
return null;
87+
}
88+
89+
setIsCreatingToken( true );
90+
setError( '' );
91+
92+
try {
93+
const result = await monei.createToken( cardInputRef.current );
94+
95+
if ( result.error ) {
96+
const errorMessage =
97+
result.error.message ||
98+
( typeof result.error === 'string'
99+
? result.error
100+
: 'Token creation failed' );
101+
setError( errorMessage );
102+
return null;
103+
}
104+
105+
setToken( result.token );
106+
return result.token;
107+
} catch ( err ) {
108+
setError( err.message || 'Token creation failed' );
109+
return null;
110+
} finally {
111+
setIsCreatingToken( false );
112+
}
113+
}, [] );
114+
77115
/**
78116
* Initialize MONEI Card Input
79117
*/
@@ -149,42 +187,7 @@ export const useMoneiCardInput = ( config ) => {
149187
setError( err.message || 'Failed to initialize card input' );
150188
setIsReady( false );
151189
}
152-
}, [ config ] );
153-
154-
/**
155-
* Create payment token
156-
*/
157-
const createToken = useCallback( async () => {
158-
if ( ! cardInputRef.current || ! monei?.createToken ) {
159-
setError( 'Card input not initialized' );
160-
return null;
161-
}
162-
163-
setIsCreatingToken( true );
164-
setError( '' );
165-
166-
try {
167-
const result = await monei.createToken( cardInputRef.current );
168-
169-
if ( result.error ) {
170-
const errorMessage =
171-
result.error.message ||
172-
( typeof result.error === 'string'
173-
? result.error
174-
: 'Token creation failed' );
175-
setError( errorMessage );
176-
return null;
177-
}
178-
179-
setToken( result.token );
180-
return result.token;
181-
} catch ( err ) {
182-
setError( err.message || 'Token creation failed' );
183-
return null;
184-
} finally {
185-
setIsCreatingToken( false );
186-
}
187-
}, [] );
190+
}, [ config, createToken ] );
188191

189192
/**
190193
* Reset card input
@@ -207,7 +210,7 @@ export const useMoneiCardInput = ( config ) => {
207210
}, 500 );
208211
return () => clearTimeout( timer );
209212
}
210-
}, [] );
213+
}, [ initializeCardInput ] );
211214

212215
// Cleanup on unmount
213216
useEffect( () => {

assets/js/monei-apple-google-classic.js

Lines changed: 42 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,14 @@
1515
}
1616

1717
if ( wc_monei_form.is_apple_selected() ) {
18+
wc_monei_form.init_checkout_apple_google();
1819
wc_monei_form.init_apple_google_pay();
1920
}
2021
} );
2122
// On Pay for order form.
2223
$( 'form#order_review' ).on( 'click', function () {
2324
if ( wc_monei_form.is_apple_selected() ) {
25+
wc_monei_form.init_checkout_apple_google();
2426
wc_monei_form.init_apple_google_pay();
2527
}
2628
} );
@@ -81,12 +83,14 @@
8183
this.form = this.$order_pay_form;
8284

8385
if ( wc_monei_form.is_apple_selected() ) {
86+
wc_monei_form.init_checkout_apple_google();
8487
wc_monei_form.init_apple_google_pay();
8588
}
8689

8790
$( 'input[name="payment_method"]' ).on( 'change', function () {
8891
// Check if the apple google pay method is selected
8992
if ( wc_monei_form.is_apple_selected() ) {
93+
wc_monei_form.init_checkout_apple_google();
9094
wc_monei_form.init_apple_google_pay();
9195
}
9296
} );
@@ -108,6 +112,7 @@
108112
},
109113
on_payment_selected() {
110114
if ( wc_monei_form.is_apple_selected() ) {
115+
wc_monei_form.init_checkout_apple_google();
111116
wc_monei_form.init_apple_google_pay();
112117
// Apple/Google Pay initialized
113118
if ( wc_monei_form.is_checkout ) {
@@ -130,40 +135,12 @@
130135
return $( '#payment_method_monei_apple_google' ).is( ':checked' );
131136
},
132137
init_apple_google_pay() {
133-
// Check if container exists, create if needed (for order-pay page)
134-
let container = document.getElementById(
135-
'payment-request-container'
136-
);
137-
if ( ! container ) {
138-
// Create container structure if it doesn't exist
139-
const paymentMethodLi = document
140-
.querySelector( '#payment_method_monei_apple_google' )
141-
?.closest( 'li' );
142-
if ( ! paymentMethodLi ) {
143-
return;
144-
}
145-
146-
// Create the container structure
147-
const fieldset = document.createElement( 'fieldset' );
148-
fieldset.id = 'wc-monei_apple_google-payment-request-form';
149-
fieldset.className = 'wc-payment-request-form';
150-
fieldset.style.background = 'transparent';
151-
fieldset.style.border = 'none';
152-
153-
const formDiv = document.createElement( 'div' );
154-
formDiv.id = 'payment-request-form';
155-
156-
container = document.createElement( 'div' );
157-
container.id = 'payment-request-container';
158-
159-
formDiv.appendChild( container );
160-
fieldset.appendChild( formDiv );
161-
paymentMethodLi.appendChild( fieldset );
162-
}
163-
164138
// If checkout is updated (and monei was initiated already), ex, selecting new shipping methods, checkout is re-render by the ajax call.
165139
// and we need to reset the counter in order to initiate again the monei component.
166140
if ( wc_monei_form.$payment_request_container ) {
141+
const container = document.getElementById(
142+
'payment-request-container'
143+
);
167144
// Reset if stored container differs from current (recreated) OR current is empty
168145
if (
169146
wc_monei_form.$payment_request_container !== container ||
@@ -193,6 +170,39 @@
193170
// We already init the button.
194171
this.init_apple_counter++;
195172
},
173+
init_checkout_apple_google() {
174+
// Check if container exists, create if needed (for order-pay page)
175+
let container = document.getElementById(
176+
'payment-request-container'
177+
);
178+
if ( ! container ) {
179+
// Create container structure if it doesn't exist
180+
const paymentMethodLi = document
181+
.querySelector( '#payment_method_monei_apple_google' )
182+
?.closest( 'li' );
183+
if ( ! paymentMethodLi ) {
184+
return;
185+
}
186+
187+
// Create the container structure
188+
const fieldset = document.createElement( 'fieldset' );
189+
fieldset.id = 'wc-monei_apple_google-payment-request-form';
190+
fieldset.className =
191+
'monei-fieldset monei-payment-request-fieldset';
192+
193+
container = document.createElement( 'div' );
194+
container.id = 'payment-request-container';
195+
container.className =
196+
'monei-payment-request-container wc-block-components-skeleton__element';
197+
198+
fieldset.appendChild( container );
199+
paymentMethodLi.appendChild( fieldset );
200+
} else {
201+
// Ensure existing container has the correct class
202+
container.className =
203+
'monei-payment-request-container wc-block-components-skeleton__element';
204+
}
205+
},
196206
init_apple_google_component() {
197207
if ( window.paymentRequest ) {
198208
window.paymentRequest.close();
@@ -221,7 +231,7 @@
221231
$( '#place_order' ).prop( 'disabled', false );
222232
wc_monei_form.create_hidden_input(
223233
'monei_payment_request_token',
224-
'payment-request-form',
234+
'wc-monei_apple_google-payment-request-form',
225235
token
226236
);
227237
// Once Token is created, submit form.

assets/js/monei-bizum-classic.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,8 @@
173173

174174
container = document.createElement( 'div' );
175175
container.id = 'bizum-container';
176+
container.className =
177+
'monei-payment-request-container wc-block-components-skeleton__element';
176178

177179
fieldset.appendChild( container );
178180
paymentMethodLi.appendChild( fieldset );

assets/js/monei-block-checkout-bizum.js

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
// State for confirmation overlay
1717
const [ isConfirming, setIsConfirming ] = useState( false );
1818
const [ error, setError ] = useState( '' );
19+
const [ isLoading, setIsLoading ] = useState( false );
1920

2021
// Use useRef to persist values across re-renders
2122
const requestTokenRef = useRef( null );
@@ -105,6 +106,9 @@
105106
return;
106107
}
107108

109+
// Show skeleton loading state
110+
setIsLoading( true );
111+
108112
// Clear container
109113
container.innerHTML = '';
110114

@@ -144,6 +148,11 @@
144148
} );
145149

146150
currentBizumInstanceRef.current.render( container );
151+
152+
// Remove skeleton loading state after rendering
153+
setTimeout( () => {
154+
setIsLoading( false );
155+
}, 1000 );
147156
};
148157

149158
/**
@@ -174,6 +183,8 @@
174183
// Clear container
175184
const container = document.getElementById( 'bizum-container' );
176185
if ( container ) {
186+
// Show skeleton loading state
187+
setIsLoading( true );
177188
container.innerHTML = '';
178189
}
179190

@@ -217,6 +228,11 @@
217228
} );
218229

219230
currentBizumInstanceRef.current.render( container );
231+
232+
// Remove skeleton loading state after rendering
233+
setTimeout( () => {
234+
setIsLoading( false );
235+
}, 100 );
220236
}
221237
};
222238

@@ -370,7 +386,11 @@
370386
) }
371387
<div
372388
id="bizum-container"
373-
className="monei-payment-request-container"
389+
className={ `monei-payment-request-container${
390+
isLoading
391+
? ' wc-block-components-skeleton__element'
392+
: ''
393+
}` }
374394
>
375395
{ /* Bizum button will be inserted here */ }
376396
</div>

0 commit comments

Comments
 (0)