-
Notifications
You must be signed in to change notification settings - Fork 57
2 - Block components and hoc (PIWOO-761) #1084
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: PIWOO-752-blocks-refactor
Are you sure you want to change the base?
Changes from all commits
9850dbb
719433e
3b202d5
fdc6035
2036b9e
3eaabf4
450a764
923040b
4fdf474
dad29fb
5070fb0
45ab39e
1950def
df5359a
e582e81
609e50d
ccb36d8
8c26920
6424ea6
5fb7a07
909af64
f490d8a
f211260
daa8aa7
5a467d1
efd23a9
8d1d750
123778c
394b87f
cc7dfa1
b745bc4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
export const Label = ({item}) => { | ||
console.log(item.label.title, item.label.icon); // Debugging purposes | ||
return ( | ||
<> | ||
<span style={{marginRight: '1em'}}>{item.label.title}</span> | ||
{item.label.icon && <img src={item.label.icon} alt=""/>} | ||
</> | ||
); | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
import CreditCardComponent from './paymentMethods/CreditCardComponent'; | ||
import DefaultComponent from './paymentMethods/DefaultComponent'; | ||
import PaymentFieldsComponent from './paymentMethods/PaymentFieldsComponent'; | ||
import withMollieStore from '../hoc/withMollieStore'; | ||
|
||
/** | ||
* Factory function to create appropriate payment component with store connection | ||
* Maps payment method names to their corresponding components with proper configuration | ||
* @param {Object} item | ||
* @param {Object} commonProps | ||
*/ | ||
export const createPaymentComponent = ( item, commonProps ) => { | ||
if ( ! item || ! item.name ) { | ||
return <div>Loading payment methods...</div>; | ||
} | ||
|
||
switch ( item.name ) { | ||
case 'mollie_wc_gateway_creditcard': | ||
const CreditCardWithStore = withMollieStore( CreditCardComponent ); | ||
return <CreditCardWithStore { ...commonProps } />; | ||
|
||
case 'mollie_wc_gateway_billie': | ||
const BillieFieldsWithStore = withMollieStore( | ||
PaymentFieldsComponent | ||
); | ||
return ( | ||
<BillieFieldsWithStore | ||
{ ...commonProps } | ||
fieldConfig={ { | ||
showCompany: true, | ||
companyRequired: true, | ||
companyLabel: item.companyPlaceholder || 'Company name', | ||
} } | ||
/> | ||
); | ||
|
||
case 'mollie_wc_gateway_in3': | ||
const In3FieldsWithStore = withMollieStore( | ||
PaymentFieldsComponent | ||
); | ||
return ( | ||
<In3FieldsWithStore | ||
{ ...commonProps } | ||
fieldConfig={ { | ||
showPhone: true, | ||
showBirthdate: true, | ||
phoneRequired: true, | ||
birthdateRequired: true, | ||
phoneLabel: item.phoneLabel || 'Phone', | ||
birthdateLabel: | ||
item.birthdatePlaceholder || 'Birthdate', | ||
} } | ||
/> | ||
); | ||
|
||
case 'mollie_wc_gateway_riverty': | ||
const RivertyFieldsWithStore = withMollieStore( | ||
PaymentFieldsComponent | ||
); | ||
return ( | ||
<RivertyFieldsWithStore | ||
{ ...commonProps } | ||
fieldConfig={ { | ||
showPhone: true, | ||
showBirthdate: true, | ||
phoneRequired: true, | ||
birthdateRequired: true, | ||
phoneLabel: item.phoneLabel || 'Phone', | ||
birthdateLabel: | ||
item.birthdatePlaceholder || 'Birthdate', | ||
} } | ||
/> | ||
); | ||
|
||
default: | ||
const DefaultWithStore = withMollieStore( DefaultComponent ); | ||
return <DefaultWithStore { ...commonProps } />; | ||
} | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
import { MOLLIE_STORE_KEY } from '../store'; | ||
import { createPaymentComponent } from './PaymentComponentFactory'; | ||
|
||
/** | ||
* Main Mollie Component - Orchestrates payment method rendering | ||
* Handles common payment processing and delegates specific logic to child components | ||
* @param {Object} props | ||
*/ | ||
export const PaymentMethodContentRenderer = ( props ) => { | ||
const { useEffect } = wp.element; | ||
const { useSelect } = wp.data; | ||
|
||
const { | ||
activePaymentMethod, | ||
billing, | ||
item, | ||
jQuery, | ||
emitResponse, | ||
eventRegistration, | ||
requiredFields, | ||
shippingData, | ||
isPhoneFieldVisible, | ||
} = props; | ||
|
||
const { responseTypes } = emitResponse; | ||
const { onPaymentSetup } = eventRegistration; | ||
|
||
// Redux store selectors - only for payment processing | ||
const selectedIssuer = useSelect( | ||
( select ) => select( MOLLIE_STORE_KEY ).getSelectedIssuer(), | ||
[] | ||
); | ||
const inputPhone = useSelect( | ||
( select ) => select( MOLLIE_STORE_KEY ).getInputPhone(), | ||
[] | ||
); | ||
const inputBirthdate = useSelect( | ||
( select ) => select( MOLLIE_STORE_KEY ).getInputBirthdate(), | ||
[] | ||
); | ||
const inputCompany = useSelect( | ||
( select ) => select( MOLLIE_STORE_KEY ).getInputCompany(), | ||
[] | ||
); | ||
|
||
const issuerKey = | ||
'mollie-payments-for-woocommerce_issuer_' + activePaymentMethod; | ||
|
||
// Main payment processing - stays centralized for all payment methods | ||
useEffect( () => { | ||
const onProcessingPayment = () => { | ||
const data = { | ||
payment_method: activePaymentMethod, | ||
payment_method_title: item.title, | ||
[ issuerKey ]: selectedIssuer, | ||
billing_phone: inputPhone, | ||
billing_company_billie: inputCompany, | ||
billing_birthdate: inputBirthdate, | ||
cardToken: '', | ||
}; | ||
const tokenVal = jQuery( '.mollie-components > input' ).val(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If this is all we need jQuery for, should we not think about doing it in vanilla JS instead? A more thorough solution would be to expose this type of data through te redux store and then consume it via There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right, using the token from the store is done in a later PR but I've also expanded on the use of the store there after your suggestion, I think I can also use in the payment fields, I will update those as well. Removing jQuery is also a job started later but I will take a look in case i missed somewhere. Thanks! |
||
if ( tokenVal ) { | ||
data.cardToken = tokenVal; | ||
} | ||
return { | ||
type: responseTypes.SUCCESS, | ||
meta: { | ||
paymentMethodData: data, | ||
}, | ||
}; | ||
}; | ||
|
||
const unsubscribePaymentProcessing = | ||
onPaymentSetup( onProcessingPayment ); | ||
return () => { | ||
unsubscribePaymentProcessing(); | ||
}; | ||
}, [ | ||
selectedIssuer, | ||
onPaymentSetup, | ||
inputPhone, | ||
inputCompany, | ||
inputBirthdate, | ||
activePaymentMethod, | ||
issuerKey, | ||
item.title, | ||
jQuery, | ||
responseTypes.SUCCESS, | ||
] ); | ||
|
||
// Prepare common props for child components | ||
const commonProps = { | ||
item, | ||
jQuery, | ||
useEffect, | ||
billing, | ||
shippingData, | ||
eventRegistration, | ||
requiredFields, | ||
isPhoneFieldVisible, | ||
activePaymentMethod, | ||
}; | ||
|
||
return createPaymentComponent( item, commonProps ); | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import { PaymentMethodContentRenderer } from './PaymentMethodContentRenderer'; | ||
import { Label } from './Label'; | ||
|
||
const molliePaymentMethod = ( | ||
item, | ||
jQuery, | ||
requiredFields, | ||
isPhoneFieldVisible | ||
) => { | ||
return { | ||
name: item.name, | ||
label: <Label item={ item } />, | ||
content: ( | ||
<PaymentMethodContentRenderer | ||
item={ item } | ||
jQuery={ jQuery } | ||
requiredFields={ requiredFields } | ||
isPhoneFieldVisible={ isPhoneFieldVisible } | ||
/> | ||
), | ||
edit: <div>{ item.edit }</div>, | ||
paymentMethodId: item.paymentMethodId, | ||
canMakePayment: () => { | ||
//only the methods that return is available on backend will be loaded here so we show them | ||
return true; | ||
}, | ||
ariaLabel: item.ariaLabel, | ||
supports: { | ||
features: item.supports, | ||
}, | ||
}; | ||
}; | ||
export default molliePaymentMethod; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
export const BirthdateField = ( { label, value, onChange } ) => { | ||
const handleChange = ( e ) => onChange( e.target.value ); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it would be an optimization to declare this function outside of the component (so it does not get reinitialized everytime we re-render) As an alternative, perhaps look into There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I checked some docs, cause makes sense what you say, but if I understood right, declaring the function outside or using useCallback only helps if we pass it down to children or use it as a dependency in an effect. In our case the handler is only attached to a native (not a React child), so its identity doesn’t cause any extra renders. And if taking out I would need a way to pass the prop to that function. But I will add a new task to check in the parent components if the child components can be memoized. |
||
const className = | ||
'wc-block-components-text-input wc-block-components-address-form__billing-birthdate'; | ||
|
||
return ( | ||
<div className="custom-input"> | ||
<label htmlFor="billing-birthdate">{ label }</label> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The jsx tag isn't closed properly here, right? Also this can probably be a short tag There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, no, I think is closed right, is not a void element, but I will take a look at all the other elements around |
||
<input | ||
type="date" | ||
className={ className } | ||
name="billing-birthdate" | ||
id="billing-birthdate" | ||
value={ value } | ||
onChange={ handleChange } | ||
/> | ||
</div> | ||
); | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
export const CompanyField = ( { label, value, onChange } ) => { | ||
const handleChange = ( e ) => onChange( e.target.value ); | ||
const className = | ||
'wc-block-components-text-input wc-block-components-address-form__billing_company_billie'; | ||
|
||
return ( | ||
<div className="custom-input"> | ||
<label htmlFor="billing_company_billie">{ label }</label> | ||
<input | ||
type="text" | ||
className={ className } | ||
name="billing_company_billie" | ||
id="billing_company_billie" | ||
value={ value } | ||
onChange={ handleChange } | ||
/> | ||
</div> | ||
); | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export const CreditCardField = ( { content } ) => { | ||
return <div dangerouslySetInnerHTML={ { __html: content } } />; | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
export const IssuerSelect = ( { | ||
issuerKey, | ||
issuers, | ||
selectedIssuer, | ||
updateIssuer, | ||
} ) => { | ||
const handleChange = ( e ) => updateIssuer( e.target.value ); | ||
|
||
return ( | ||
<select | ||
name={ issuerKey } | ||
dangerouslySetInnerHTML={ { __html: issuers } } | ||
value={ selectedIssuer } | ||
onChange={ handleChange } | ||
/> | ||
); | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
export const PhoneField = ( { id, label, value, onChange, placeholder } ) => { | ||
const handleChange = ( e ) => onChange( e.target.value ); | ||
const className = `wc-block-components-text-input wc-block-components-address-form__${ id }`; | ||
|
||
return ( | ||
<div className="custom-input"> | ||
<label htmlFor={ id }>{ label }</label> | ||
<input | ||
type="tel" | ||
className={ className } | ||
name={ id } | ||
id={ id } | ||
value={ value } | ||
onChange={ handleChange } | ||
placeholder={ placeholder } | ||
/> | ||
</div> | ||
); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think these should be imports - at the very least they should not be assigned in the render function
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, I will tackle this in another PR