Rock: Email Gathering Modal - Form Templates#2453
Conversation
- Add condition to show email collection modal only for non-Pro users - Set up modal with title "Get 30+ Free Form Templates" - Include description and submit button text for the modal - Add the modal view to template parts array
- Replace hardcoded defaults with configurable parameters - Add conditional rendering for API URL section - Add null safety with null coalescing operators - Make button text customizable with fallbacks - Support custom default email addresses - Simplify argument handling
- Change subscribe_to_active_campaign() from private to public method - Add optional email parameter to allow custom email subscriptions - Update method to use provided email instead of only current user's email - Update documentation to reflect parameter changes - Remove empty line in test email array
- Add email modal elements to elements registry - Create showError.js utility for email validation errors - Add query parameter detection for automatically showing email modal - Update showModal.js to support displaying the leave email modal - Add FREE plan case to locked template modal handler
…ebase - Rename banner file from access-free-templates-banner.php to get-free-templates-banner.php - Update all function names from access_free_templates to get_free_templates - Rename CSS class from frm-access-free-templates-banner to frm-get-free-templates-banner - Update SCSS file name and import to match new naming convention - Rename banner image file to get-free-templates-banner.png - Update URL parameter from access-free-templates to free-templates - Fix indentation in dashboard controller - Update button text context for translation
- Add set_free_license_code() and get_free_license_code() methods to FrmFormTemplateApi - Update API URL to include free license parameter when code exists - Add free license code check to email modal condition in FrmFormTemplatesController - Hide get free templates banner when user already has free license code - Remove TODO comment after implementing free license code check
- Add ajax_get_free_templates() method to FrmFormTemplatesController - Subscribe user email to ActiveCampaign when requesting free templates - Set free license code flag after successful subscription - Reset template API cache to refresh available templates - Register wp_ajax_frm_get_free_templates hook in FrmHooksController
- Add .frm-text-left utility class for left text alignment - Add !important to .frm-text-grey-500 to increase CSS specificity
- Remove selectedTemplate from initial page state in form templates - Remove selectedTemplate assignment in useTemplateButtonListener - Add removeParamFromHistory() utility function to core/utils/url.js
- Add getFreeTemplatesListener.js with email validation and AJAX submission - Add banner button and modal button elements to elements registry - Integrate get free templates event listeners into main events system - Remove unused showConfirmEmailAddressError function - Handle success/error states and page reload after successful submission - Add URL parameter management for free templates flow
- Change query parameter from 'registered_for_free_templates' to 'registered-for-free-templates' - Update setupInitialView.js to use getSingleState instead of getState for better performance - Add automatic navigation to Available Templates category after registration - Remove unused imports and optimize element access patterns
- Add conditional check to only display count for non-available-templates categories - Keep count span element but leave it empty for available-templates category - Maintain consistent markup structure across all categories
- Create new counter.js utility for animating number transitions - Implement performance optimizations with frame drop detection - Add fallback to CSS transitions when browser performance is degraded - Export counter from core UI module - Update core index.js to export all UI components
- Extract available templates setup into dedicated functions - Add animation effect for template count after registration - Use the new counter utility for smooth number transitions - Remove redundant sidebar show() call - Improve code organization with separate helper functions
- Fix upsellBanner element selection with fallback mechanism - Show upsell banner alongside templates in category views - Add upsellBanner to both regular and available template displays
- Add backgroundHighlight CSS animation with primary color fade - Add frm-background-highlight class for 1000ms animation - Reorder template registration effects for better user experience - Add background highlight to available templates after counter animation - Improve visual feedback when users gain access to free templates
- Add proper event listener cleanup after background highlight animation - Prevent memory leaks by removing animationend listeners after completion - Update available templates category setup to use effects function - Comment out direct text content assignment for debugging
- Add needs_get_free_templates_banner() helper method to FrmFormTemplatesHelper - Replace duplicate banner visibility checks with centralized method - Add conditional rendering to dashboard banner display - Update form templates controller and index view to use new helper - Improve code maintainability by eliminating duplicate logic
- Add loading state to submit button during AJAX request - Extract showFailedToGetTemplates() function for error handling - Hide modal footer when displaying error messages - Consolidate duplicate error handling code - Set cursor to not-allowed and add loading class to button
Replace ternary operator with if condition in subscribe_to_active_campaign() to avoid unnecessary reassignment when email parameter is already provided. Only assign user's email when the email parameter is empty.
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (1)
classes/controllers/FrmFormTemplatesController.php (1)
51-56: Add @SInCE to the new constant docblockInclude a @SInCE tag for consistency with surrounding docs.
/** * The keys of the free templates. * - * @var array Unique keys for the free templates. + * @since x.x + * @var array Unique keys for the free templates. */
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
classes/controllers/FrmFormTemplatesController.php(4 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
classes/controllers/FrmFormTemplatesController.php (4)
classes/helpers/FrmFormTemplatesHelper.php (2)
FrmFormTemplatesHelper(17-251)needs_get_free_templates_banner(234-236)classes/helpers/FrmAppHelper.php (3)
FrmAppHelper(6-4622)permission_check(2188-2196)get_post_param(682-692)classes/models/FrmFormTemplateApi.php (2)
FrmFormTemplateApi(6-113)set_free_license_code(70-72)classes/helpers/FrmEmailCollectionHelper.php (2)
FrmEmailCollectionHelper(17-90)subscribe_to_active_campaign(27-57)
🪛 PHPMD (2.15.0)
classes/controllers/FrmFormTemplatesController.php
237-237: Avoid unused local variables such as '$leave_email_args'. (undefined)
(UnusedLocalVariable)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: PHP 7.4 tests in WP trunk
- GitHub Check: PHP 8 tests in WP trunk
- GitHub Check: Run PHP Syntax inspection (8.3)
- GitHub Check: Cypress
🔇 Additional comments (4)
classes/controllers/FrmFormTemplatesController.php (4)
236-243: Pass $leave_email_args explicitly to the modal partial (avoid hidden include-scope coupling / fix PHPMD warning)Pass args with the view part to make the contract explicit and silence PHPMD “UnusedLocalVariable”.
- $view_parts[] = 'modals/leave-email-modal.php'; + $view_parts[] = array( + 'file' => 'modals/leave-email-modal.php', + 'args' => $leave_email_args, + );Outside this file (classes/views/form-templates/modal.php), ensure the include loop supports array entries and resets $args per iteration:
foreach ( $view_parts as $part ) { if ( isset( $args ) ) { unset( $args ); } $file = is_array( $part ) ? $part['file'] : $part; if ( is_array( $part ) && isset( $part['args'] ) ) { $args = $part['args']; } require $view_path . $file; }
405-413: Server-side email validation looks goodNonce + capability checks, sanitize_email + is_email, and 400 response are correct.
723-724: Expose FREE_TEMPLATES_KEYS to JS — LGTMConsistent with how FEATURED_TEMPLATES_KEYS is exposed.
414-420: Call static method on the class, not via an instanceUsing instance::staticMethod is unconventional and confuses analyzers. Call the static on FrmFormTemplateApi.
self::$form_template_api = new FrmFormTemplateApi(); self::$form_template_api->reset_cached(); FrmEmailCollectionHelper::subscribe_to_active_campaign( $email ); - self::$form_template_api::set_free_license_code( '1' ); + FrmFormTemplateApi::set_free_license_code( '1' );Optional: if reset_cached() has a static variant or is unnecessary here, drop the instantiation altogether.
Rename FEATURED_TEMPLATES_KEYS to FEATURED_TEMPLATES_IDS and FREE_TEMPLATES_KEYS to FREE_TEMPLATES_IDS to better reflect that these contain template IDs rather than generic "keys". Update all references across controller, helper, and test files, along with documentation comments.
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (2)
classes/controllers/FrmFormTemplatesController.php (2)
394-421: AJAX: good validation; fix instance::static call and reset cache after setting code.Call the static method on the class and refresh cache after updating.
- self::$form_template_api = new FrmFormTemplateApi(); - self::$form_template_api->reset_cached(); - - FrmEmailCollectionHelper::subscribe_to_active_campaign( $email ); - self::$form_template_api::set_free_license_code( '1' ); + FrmEmailCollectionHelper::subscribe_to_active_campaign( $email ); + FrmFormTemplateApi::set_free_license_code( '1' ); + self::$form_template_api = new FrmFormTemplateApi(); + self::$form_template_api->reset_cached();
723-724: If you rename the constant, mirror it in JS variables.Keep server and client in sync.
- 'FEATURED_TEMPLATES_KEYS' => self::FEATURED_TEMPLATES_KEYS, - 'FREE_TEMPLATES_KEYS' => self::FREE_TEMPLATES_KEYS, + 'FEATURED_TEMPLATES_KEYS' => self::FEATURED_TEMPLATES_KEYS, + 'FREE_TEMPLATES_IDS' => self::FREE_TEMPLATES_IDS,
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
classes/controllers/FrmFormTemplatesController.php(5 hunks)classes/helpers/FrmEmailCollectionHelper.php(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
classes/controllers/FrmFormTemplatesController.php (4)
classes/helpers/FrmFormTemplatesHelper.php (2)
FrmFormTemplatesHelper(17-251)needs_get_free_templates_banner(234-236)classes/helpers/FrmAppHelper.php (3)
FrmAppHelper(6-4622)permission_check(2188-2196)get_post_param(682-692)classes/models/FrmFormTemplateApi.php (2)
FrmFormTemplateApi(6-113)set_free_license_code(70-72)classes/helpers/FrmEmailCollectionHelper.php (2)
FrmEmailCollectionHelper(17-92)subscribe_to_active_campaign(27-59)
🪛 PHPMD (2.15.0)
classes/controllers/FrmFormTemplatesController.php
237-237: Avoid unused local variables such as '$leave_email_args'. (undefined)
(UnusedLocalVariable)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Cypress
🔇 Additional comments (8)
classes/helpers/FrmEmailCollectionHelper.php (5)
69-91: Make fake-email detection sanitized, case-insensitive, and extensible.Prevents bypasses like Test@Example.com and allows site-specific blocks.
- public static function is_fake_email( $email ) { - if ( ! is_email( $email ) ) { + public static function is_fake_email( $email ) { + $email = is_string( $email ) ? sanitize_email( strtolower( $email ) ) : ''; + if ( ! is_email( $email ) ) { return true; } - $substrings = array( + $substrings = array( '@wpengine.local', '@example.com', '@localhost', '@local.dev', '@local.test', 'test@gmail.com', 'admin@gmail.com', ); - foreach ( $substrings as $substring ) { - if ( false !== strpos( $email, $substring ) ) { + $substrings = apply_filters( 'frm_fake_email_substrings', $substrings ); + + foreach ( $substrings as $substring ) { + if ( false !== stripos( $email, (string) $substring ) ) { return true; } } return false; }
15-16: Replace @SInCE x.x placeholders before release.Set the actual version numbers for new methods/classes.
27-35: Sanitize, validate, and normalize the email before use (lowercase).Ensure a clean, valid email is used for checks and external calls.
$user = wp_get_current_user(); - if ( ! $email ) { - $email = $user->user_email; - } - - if ( self::is_fake_email( $email ) ) { + if ( ! $email ) { + $email = $user->user_email; + } + $email = is_string( $email ) ? sanitize_email( $email ) : ''; + if ( empty( $email ) || ! is_email( $email ) ) { + return; + } + $email = strtolower( $email ); + if ( self::is_fake_email( $email ) ) { return; }
37-40: Sanitize first_name/last_name before sending off-site.Harden PII prior to transmission.
$user_id = $user->ID; - $first_name = get_user_meta( $user_id, 'first_name', true ); - $last_name = get_user_meta( $user_id, 'last_name', true ); + $first_name = get_user_meta( $user_id, 'first_name', true ); + $last_name = get_user_meta( $user_id, 'last_name', true ); + $first_name = is_string( $first_name ) ? sanitize_text_field( $first_name ) : ''; + $last_name = is_string( $last_name ) ? sanitize_text_field( $last_name ) : '';
41-58: Gate the sandbox endpoint via a filter; add timeout, headers, and non-blocking request.Prevents accidentally shipping sandbox URL; improves resiliency.
- wp_remote_post( - 'https://sandbox.formidableforms.com/api/wp-admin/admin-ajax.php?action=frm_forms_preview&form=subscribe-onboarding', - array( - 'body' => http_build_query( - array( - 'form_key' => 'subscribe-onboarding', - 'frm_action' => 'create', - 'form_id' => 5, - 'item_key' => '', - 'item_meta[0]' => '', - 'item_meta[15]' => $email, - 'item_meta[17]' => 'Source - FF Lite Plugin Onboarding', - 'item_meta[18]' => is_string( $first_name ) ? $first_name : '', - 'item_meta[19]' => is_string( $last_name ) ? $last_name : '', - ) - ), - ) - ); + $default_url = 'https://sandbox.formidableforms.com/api/wp-admin/admin-ajax.php?action=frm_forms_preview&form=subscribe-onboarding'; + $url = apply_filters( 'frm_email_collection_endpoint', $default_url ); + $body = array( + 'form_key' => 'subscribe-onboarding', + 'frm_action' => 'create', + 'form_id' => 5, + 'item_key' => '', + 'item_meta[0]' => '', + 'item_meta[15]' => $email, + 'item_meta[17]' => apply_filters( 'frm_email_collection_source', 'Source - FF Lite Plugin Onboarding' ), + 'item_meta[18]' => $first_name, + 'item_meta[19]' => $last_name, + ); + $args = array( + 'timeout' => 3, + 'blocking' => false, + 'headers' => array( 'Content-Type' => 'application/x-www-form-urlencoded; charset=utf-8' ), + 'body' => $body, + ); + $args = apply_filters( 'frm_email_collection_request_args', $args, $email, $first_name, $last_name ); + wp_remote_post( esc_url_raw( $url ), $args );classes/controllers/FrmFormTemplatesController.php (3)
23-31: LGTM on doc clarity.Docblocks for PAGE_SLUG and SCRIPT_HANDLE read cleanly.
51-57: Rename FREE_TEMPLATES_KEYS to FREE_TEMPLATES_IDS to avoid confusion with “key”.These are numeric IDs; rename for clarity and consistency.
- /** - * The keys of the free templates. - * - * @var array Unique keys for the free templates. - */ - const FREE_TEMPLATES_KEYS = array( 20872734, 28223640 ); + /** + * The IDs of the free templates. + * + * @var array Unique IDs for the free templates. + */ + const FREE_TEMPLATES_IDS = array( 20872734, 28223640 );Follow-ups (outside this file):
- Update FrmFormTemplatesHelper::needs_free_plan() to reference FrmFormTemplatesController::FREE_TEMPLATES_IDS.
- Update JS consumers accordingly (see comment on Lines 723-724).
#!/bin/bash rg -nP 'FREE_TEMPLATES_KEYS|FEATURED_TEMPLATES_KEYS'
236-243: Pass $leave_email_args explicitly to the partial (avoid hidden include-scope coupling).Fixes PHPMD “unused variable” and makes intent explicit.
if ( FrmFormTemplatesHelper::needs_get_free_templates_banner() ) { $leave_email_args = array( 'title' => esc_html__( 'Get 30+ Free Form Templates', 'formidable' ), 'description' => esc_html__( 'Just add your email address and you\'ll get 30+ free form templates to your account.', 'formidable' ), 'submit_button_text' => esc_html_x( 'Get Templates', 'get free templates modal submit button text', 'formidable' ), ); - $view_parts[] = 'modals/leave-email-modal.php'; + $view_parts[] = array( + 'file' => 'modals/leave-email-modal.php', + 'args' => $leave_email_args, + ); }Also update classes/views/form-templates/modal.php to accept array entries:
// replace the foreach in modal.php foreach ( $view_parts as $part ) { if ( isset( $args ) ) { unset( $args ); } $file = is_array( $part ) ? $part['file'] : $part; if ( is_array( $part ) && isset( $part['args'] ) ) { $args = $part['args']; } require $view_path . $file; }
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (1)
classes/controllers/FrmFormTemplatesController.php (1)
418-418: Fix instance method call on static method.Line 418 incorrectly calls a static method on an instance (
self::$form_template_api::set_free_license_code). This should call the static method directly on the class.Apply this diff to fix the static method call:
- self::$form_template_api::set_free_license_code( '1' ); + FrmFormTemplateApi::set_free_license_code( '1' );
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
classes/controllers/FrmFormTemplatesController.php(5 hunks)classes/helpers/FrmFormTemplatesHelper.php(2 hunks)js/src/form-templates/shared/constants.js(1 hunks)js/src/form-templates/ui/setupInitialView.js(3 hunks)js/src/form-templates/utils/validation.js(2 hunks)resources/scss/admin/frm_admin.scss(1 hunks)tests/phpunit/form-templates/test_FrmFormTemplatesControllerAjax.php(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- resources/scss/admin/frm_admin.scss
🧰 Additional context used
🧬 Code graph analysis (4)
js/src/form-templates/ui/setupInitialView.js (4)
js/src/core/factory/createPageState.js (1)
getSingleState(30-36)js/src/form-templates/ui/showHeaderCancelButton.js (1)
showHeaderCancelButton(16-19)js/src/form-templates/shared/pageState.js (2)
availableTemplatesCount(13-13)getElements(12-12)js/src/core/ui/counter.js (1)
counter(12-54)
classes/helpers/FrmFormTemplatesHelper.php (3)
classes/helpers/FrmAppHelper.php (2)
FrmAppHelper(6-4622)pro_is_installed(307-309)classes/models/FrmFormTemplateApi.php (2)
FrmFormTemplateApi(6-113)get_free_license_code(81-83)classes/controllers/FrmFormTemplatesController.php (1)
FrmFormTemplatesController(18-891)
js/src/form-templates/shared/constants.js (1)
js/src/form-templates/shared/pageState.js (1)
window(11-11)
classes/controllers/FrmFormTemplatesController.php (4)
classes/helpers/FrmFormTemplatesHelper.php (2)
FrmFormTemplatesHelper(17-251)needs_get_free_templates_banner(234-236)classes/helpers/FrmAppHelper.php (3)
FrmAppHelper(6-4622)permission_check(2188-2196)get_post_param(682-692)classes/models/FrmFormTemplateApi.php (2)
FrmFormTemplateApi(6-113)set_free_license_code(70-72)classes/helpers/FrmEmailCollectionHelper.php (2)
FrmEmailCollectionHelper(17-92)subscribe_to_active_campaign(27-59)
🪛 PHPMD (2.15.0)
classes/helpers/FrmFormTemplatesHelper.php
216-216: Avoid unused local variables such as '$args'. (undefined)
(UnusedLocalVariable)
classes/controllers/FrmFormTemplatesController.php
237-237: Avoid unused local variables such as '$leave_email_args'. (undefined)
(UnusedLocalVariable)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: PHP 8 tests in WP trunk
- GitHub Check: PHP 7.4 tests in WP trunk
- GitHub Check: Cypress
🔇 Additional comments (20)
js/src/form-templates/ui/setupInitialView.js (7)
5-6: LGTM!The imports are correctly updated to include the new
removeParamFromHistoryutility andcounterUI component, which align with their usage in the new URL parameter handling and animation logic.
13-13: LGTM!The import correctly updates from
FEATURED_TEMPLATES_KEYStoFEATURED_TEMPLATES_IDSand addsFREE_TEMPLATES_IDS, maintaining consistency with the broader refactoring across the codebase.
28-29: LGTM!The element destructuring adds the necessary UI elements (
availableTemplatesCategoryandextraTemplateCountElements) that are used in the new functionality.
39-39: LGTM!The call to
setupAvailableTemplatesCategoryproperly separates this concern into its own function, improving code organization.
42-42: LGTM!The extra template count update correctly uses the new
getSingleStatepattern for consistency with the rest of the state management.
55-74: LGTM!The
setupAvailableTemplatesCategoryfunction properly handles the conditional logic for the free templates flow. The implementation correctly checks for the URL parameter and either updates the count directly or triggers the effects chain.
76-111: LGTM!The
runAvailableTemplatesEffectsfunction implements a well-orchestrated sequence of UI effects:
- Triggers category expansion with a synthetic click event
- Animates the counter with appropriate timing
- Highlights non-free templates with proper cleanup
The animation event listener properly removes itself after completion to prevent memory leaks.
js/src/form-templates/utils/validation.js (2)
10-10: LGTM!The import correctly updates from
FEATURED_TEMPLATES_KEYStoFEATURED_TEMPLATES_IDS, maintaining consistency with the codebase-wide refactoring from keys to IDs.
60-61: LGTM!The
isFeaturedTemplatefunction correctly usesFEATURED_TEMPLATES_IDSand properly converts thedataset.idto a number for the membership check, maintaining the intended functionality while aligning with the ID-based approach.tests/phpunit/form-templates/test_FrmFormTemplatesControllerAjax.php (1)
27-27: LGTM!The test correctly updates to use
FEATURED_TEMPLATES_IDSinstead of the previousFEATURED_TEMPLATES_KEYS, maintaining consistency with the constant rename across the codebase.js/src/form-templates/shared/constants.js (1)
2-2: LGTM!The destructuring correctly updates to export
FEATURED_TEMPLATES_IDSandFREE_TEMPLATES_IDSinstead ofFEATURED_TEMPLATES_KEYS, maintaining consistency with the server-side constant changes and supporting the new free templates functionality.classes/helpers/FrmFormTemplatesHelper.php (4)
32-34: LGTM!The conditional override for the free plan correctly integrates with the template preparation logic, allowing the system to dynamically adjust template requirements based on user eligibility.
215-225: Apply previous review feedback: Pass $args explicitly to avoid PHPMD warning.The $args variable is flagged as unused by PHPMD since it's only referenced within the included view. This creates implicit coupling between the wrapper and the included file.
234-236: LGTM!The
needs_get_free_templates_bannermethod correctly checks both conditions (PRO not installed and no free license code) to determine when the banner should be displayed.
246-250: LGTM!The
needs_free_planmethod implements proper logic to determine when a template should be overridden to the free plan, checking all necessary conditions: banner eligibility, 'free' category membership, and exclusion fromFREE_TEMPLATES_IDS.classes/controllers/FrmFormTemplatesController.php (5)
41-56: LGTM!The constant updates correctly rename
FEATURED_TEMPLATES_KEYStoFEATURED_TEMPLATES_IDSand add the newFREE_TEMPLATES_IDSconstant. The documentation is updated appropriately, and the IDs match the expected template identifiers.
236-243: Apply previous review feedback: Pass $leave_email_args explicitly to avoid PHPMD coupling.The PHPMD warning indicates
$leave_email_argsis unused in this scope because it's only consumed by the included view through implicit scope coupling.
578-586: LGTM!The method correctly updates to iterate over
FEATURED_TEMPLATES_IDSinstead ofFEATURED_TEMPLATES_KEYS, maintaining the intended functionality while aligning with the ID-based approach.
720-732: LGTM!The JavaScript variables correctly expose
FEATURED_TEMPLATES_IDSandFREE_TEMPLATES_IDSto the frontend, replacing the previousFEATURED_TEMPLATES_KEYS. The data structure remains consistent and properly supports the new free templates functionality.
394-421: WP_Http::BAD_REQUEST usage is correct — no action needed.
Confirmed at classes/controllers/FrmFormTemplatesController.php:410; the constant returns HTTP 400 and the handler validates the email server-side (sanitize_email + is_email).
|
@Crabcyborg and @lauramekaj1 it looks like the E2E test needs to be updated since we changed our strategy for the initial free templates list. |
|
Thanks @shervElmi. The old tests are shown in the diff here https://github.com/Strategy11/formidable-forms/pull/2277/files @lauramekaj1 should be able to help with updating them. |
…-modal Update tests email gathering modal
Rock: Email Gathering Modal - Form Templates
Linear
Screen recording demo
https://share.cleanshot.com/VqNGfzmQ