feat(integrations): add Power Coupons integration#192
Conversation
Implement Power Coupons for WooCommerce as a new Pro action integration. Includes backend controller with AJAX routes for authorization and coupon retrieval, and complete frontend UI with field mapping, action selection (create, update, delete, toggle), and coupon picker. Supports dynamic field mapping with required/optional field validation.
Reworked the PowerCoupons integration UI by extracting utility controls into a new `PowerCouponsActions` component and centralizing utility option/default config in `staticData`. Removed coupon list refresh/selection flows and shifted update/delete/toggle actions to require `coupon_code` field mapping, with field-map normalization ensuring required fields stay present. Backend routes/controller cleanup removed the unused refresh endpoint, and API payload prep now strips unsupported identifier fields (`coupon_id`/`id`) before sending requests.
Update the PowerCoupons validation message to ask users to map a coupon code, and tidy the field-map normalization formatting in the integration layout.
There was a problem hiding this comment.
Code Review
This pull request introduces a new integration for 'Power Coupons for WooCommerce', adding backend controllers, API helpers, and routes, alongside frontend components for authorization, field mapping, actions, and editing. Feedback on the changes highlights two key improvements: first, simplifying the dependency array of the useEffect hook in PowerCouponsIntegLayout.jsx to prevent potential infinite loops and excessive re-renders caused by updating state properties inside the effect; second, adding validation and a snackbar error message in PowerCoupons.jsx when the integration name is cleared to prevent silent transition blocks.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| useEffect(() => { | ||
| if (!action || !FIELD_MAP[action]) { | ||
| return | ||
| } | ||
|
|
||
| const normalizedFields = FIELD_MAP[action] | ||
| const normalizedFieldMap = normalizeFieldMap(powerCouponsConf?.field_map, normalizedFields) | ||
| const normalizedUtilities = normalizeUtilities( | ||
| action, | ||
| powerCouponsConf?.utilities, | ||
| powerCouponsConf?.field_map | ||
| ) | ||
| const fieldsChanged = | ||
| JSON.stringify(powerCouponsConf?.powerCouponsFields || []) !== JSON.stringify(normalizedFields) | ||
| const fieldMapChanged = | ||
| JSON.stringify(powerCouponsConf?.field_map || []) !== JSON.stringify(normalizedFieldMap) | ||
| const utilitiesChanged = | ||
| JSON.stringify(powerCouponsConf?.utilities || {}) !== JSON.stringify(normalizedUtilities) | ||
|
|
||
| if (!fieldsChanged && !fieldMapChanged && !utilitiesChanged) { | ||
| return | ||
| } | ||
|
|
||
| setPowerCouponsConf(prevConf => | ||
| create(prevConf, draftConf => { | ||
| draftConf.powerCouponsFields = normalizedFields | ||
| draftConf.field_map = normalizedFieldMap | ||
| draftConf.utilities = normalizedUtilities | ||
| }) | ||
| ) | ||
| }, [ | ||
| action, | ||
| powerCouponsConf?.field_map, | ||
| powerCouponsConf?.powerCouponsFields, | ||
| powerCouponsConf?.utilities, | ||
| setPowerCouponsConf | ||
| ]) |
There was a problem hiding this comment.
The useEffect hook has powerCouponsConf?.field_map, powerCouponsConf?.powerCouponsFields, and powerCouponsConf?.utilities in its dependency array, but it also updates these exact same state properties inside the effect. This creates a risk of infinite loops, excessive re-renders, and cursor jumping/lag when typing custom values or changing field mappings.
Since normalization is only required when the action changes or on initial mount, the dependency array should be simplified to only depend on action and setPowerCouponsConf.
useEffect(() => {
if (!action || !FIELD_MAP[action]) {
return
}
setPowerCouponsConf(prevConf =>
create(prevConf, draftConf => {
draftConf.powerCouponsFields = FIELD_MAP[action]
draftConf.field_map = normalizeFieldMap(draftConf.field_map, FIELD_MAP[action])
draftConf.utilities = normalizeUtilities(action, draftConf.utilities, draftConf.field_map)
})
)
}, [action, setPowerCouponsConf])References
- When editing an integration, ensure that non-persisted configuration fields (like static fields) are rebuilt on mount from the saved action without modifying the saved field mappings, to prevent empty options and component crashes.
There was a problem hiding this comment.
Implemented. The normalization effect now only depends on the selected action and setter, and reads the current draft state inside the state updater before deciding whether anything needs to change.
setPowerCouponsConf(prevConf =>
create(prevConf, draftConf => {
const normalizedFields = FIELD_MAP[action]
const normalizedFieldMap = normalizeFieldMap(draftConf.field_map, normalizedFields)
const normalizedUtilities = normalizeUtilities(action, draftConf.utilities, draftConf.field_map)
if (!fieldsChanged && !fieldMapChanged && !utilitiesChanged) {
return
}
draftConf.powerCouponsFields = normalizedFields
draftConf.field_map = normalizedFieldMap
draftConf.utilities = normalizedUtilities
})
)| if (powerCouponsConf.name !== '' && powerCouponsConf.field_map.length > 0) { | ||
| setStep(val) | ||
| } |
There was a problem hiding this comment.
If the user clears the integration name and clicks "Next", the step transition is silently blocked without displaying any error message. This can be confusing for the user. We should validate that the integration name is not empty and show a snackbar error message if it is.
if (!powerCouponsConf.name) {
setSnackbar({
show: true,
msg: __('Integration name cannot be empty.', 'bit-integrations')
})
return
}
if (powerCouponsConf.field_map.length > 0) {
setStep(val)
}References
- Use falsy checks (e.g., !value) instead of strict equality checks against undefined when validating configuration or form fields, to ensure that null or empty string values are also correctly identified as incomplete.
There was a problem hiding this comment.
Implemented. Empty integration names now show a snackbar message instead of silently blocking the step transition.
if (!powerCouponsConf.name) {
setSnackbar({
show: true,
msg: __('Integration name cannot be empty.', 'bit-integrations')
})
return
}
✅ WordPress Plugin Check Report
📊 ReportAll checks passed! No errors or warnings found. 🤖 Generated by WordPress Plugin Check Action • Learn more about Plugin Check |
Description
This PR adds the Power Coupons for WooCommerce integration to Bit Integrations. It introduces backend authorization and execution support, plus the frontend setup flow for creating, updating, deleting, and toggling Power Coupons coupon settings.
Motivation & Context
Users need a way to connect form or trigger data to Power Coupons coupon operations from Bit Integrations. This integration enables coupon automation through Pro-backed hooks while keeping setup configurable from the existing integration builder UI.
Related Links: (if applicable)
Type of Change
Key Changes
Backend
Frontend
Assets
Checklist
Changelog