diff --git a/packages/mint-components/.changeset/cash-reward-redemption.md b/packages/mint-components/.changeset/cash-reward-redemption.md new file mode 100644 index 0000000000..08e276b488 --- /dev/null +++ b/packages/mint-components/.changeset/cash-reward-redemption.md @@ -0,0 +1,7 @@ +--- +"@saasquatch/mint-components": minor +--- + +Add cash reward redemption flow, including the partner info modal, banking info +form validation-error messaging, and widget verification steps for the +tax-and-cash experience. diff --git a/packages/mint-components/package-lock.json b/packages/mint-components/package-lock.json index 78abc83003..866759e73a 100644 --- a/packages/mint-components/package-lock.json +++ b/packages/mint-components/package-lock.json @@ -1,16 +1,16 @@ { "name": "@saasquatch/mint-components", - "version": "2.2.0", + "version": "2.3.0-15", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@saasquatch/mint-components", - "version": "2.2.0", + "version": "2.3.0-15", "hasInstallScript": true, "license": "MIT", "dependencies": { - "@raisins/stencil-docs-target": "^1.2.1", + "@raisins/stencil-docs-target": "^1.2.2-0", "@saasquatch/component-boilerplate": "^1.6.10", "@saasquatch/dom-context-hooks": "^1.0.5", "@saasquatch/shoelace": "^1.0.0", @@ -3491,18 +3491,18 @@ } }, "node_modules/@raisins/schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@raisins/schema/-/schema-1.2.1.tgz", - "integrity": "sha512-rNOdlKwfCoggGvFeYpgq9lW1TTecOfrFNkXk49Ag1PI+mkeE8ZfnPQrw52QjiC2OhHTovkrFu3h08TLD4bw6ug==", + "version": "1.2.2-0", + "resolved": "https://registry.npmjs.org/@raisins/schema/-/schema-1.2.2-0.tgz", + "integrity": "sha512-OV3Bxeuw9gph0JsQGvk0w3sZBItaLHLAr7v64/hk3svgt6K3ugeNcvo8Id23KMYDuu6i5PL1Vsje1rSiDsR/Ug==", "license": "MIT" }, "node_modules/@raisins/stencil-docs-target": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@raisins/stencil-docs-target/-/stencil-docs-target-1.2.1.tgz", - "integrity": "sha512-PpqOjikCZAU/0YbrYotI7kKOw1DqzbJ4jvOW0PD+eKkdAAa6bBrpTfQxkD89/mPu0cTqBi75ltu3usjtjE0OJg==", + "version": "1.2.2-0", + "resolved": "https://registry.npmjs.org/@raisins/stencil-docs-target/-/stencil-docs-target-1.2.2-0.tgz", + "integrity": "sha512-LMlGGE8CbD3fTsnPlTh3K36eXUCmx1HIpeykmEf6/46N0x5Wif4f5QR7FY9tDHqc5o2mGmmPUe/qDCgBum2eMQ==", "license": "MIT", "dependencies": { - "@raisins/schema": "^1.2.1" + "@raisins/schema": "^1.2.2-0" }, "engines": { "node": ">=10" @@ -20052,16 +20052,16 @@ } }, "@raisins/schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@raisins/schema/-/schema-1.2.1.tgz", - "integrity": "sha512-rNOdlKwfCoggGvFeYpgq9lW1TTecOfrFNkXk49Ag1PI+mkeE8ZfnPQrw52QjiC2OhHTovkrFu3h08TLD4bw6ug==" + "version": "1.2.2-0", + "resolved": "https://registry.npmjs.org/@raisins/schema/-/schema-1.2.2-0.tgz", + "integrity": "sha512-OV3Bxeuw9gph0JsQGvk0w3sZBItaLHLAr7v64/hk3svgt6K3ugeNcvo8Id23KMYDuu6i5PL1Vsje1rSiDsR/Ug==" }, "@raisins/stencil-docs-target": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@raisins/stencil-docs-target/-/stencil-docs-target-1.2.1.tgz", - "integrity": "sha512-PpqOjikCZAU/0YbrYotI7kKOw1DqzbJ4jvOW0PD+eKkdAAa6bBrpTfQxkD89/mPu0cTqBi75ltu3usjtjE0OJg==", + "version": "1.2.2-0", + "resolved": "https://registry.npmjs.org/@raisins/stencil-docs-target/-/stencil-docs-target-1.2.2-0.tgz", + "integrity": "sha512-LMlGGE8CbD3fTsnPlTh3K36eXUCmx1HIpeykmEf6/46N0x5Wif4f5QR7FY9tDHqc5o2mGmmPUe/qDCgBum2eMQ==", "requires": { - "@raisins/schema": "^1.2.1" + "@raisins/schema": "^1.2.2-0" } }, "@rollup/plugin-alias": { diff --git a/packages/mint-components/package.json b/packages/mint-components/package.json index 4aa03ef7cc..51c7bc0104 100644 --- a/packages/mint-components/package.json +++ b/packages/mint-components/package.json @@ -1,7 +1,7 @@ { "name": "@saasquatch/mint-components", "title": "Mint Components", - "version": "2.2.0", + "version": "2.3.0-15", "description": "A minimal design library with components for referral and loyalty experiences. Built with Shoelace components by Saasquatch.", "icon": "https://res.cloudinary.com/saasquatch/image/upload/v1652219900/squatch-assets/For_Mint.svg", "raisins": "docs/raisins.json", @@ -83,7 +83,7 @@ "workbox-build": "4.3.1" }, "dependencies": { - "@raisins/stencil-docs-target": "^1.2.1", + "@raisins/stencil-docs-target": "^1.2.2-0", "@saasquatch/component-boilerplate": "^1.6.10", "@saasquatch/dom-context-hooks": "^1.0.5", "@saasquatch/shoelace": "^1.0.0", diff --git a/packages/mint-components/src/components.d.ts b/packages/mint-components/src/components.d.ts index 549fafb9c2..ad339ee734 100644 --- a/packages/mint-components/src/components.d.ts +++ b/packages/mint-components/src/components.d.ts @@ -34,6 +34,7 @@ import { NavigationMenuViewProps } from "./components/sqm-navigation-menu/sqm-na import { NavigationSidebarViewProps } from "./components/sqm-navigation-sidebar/sqm-navigation-sidebar-view"; import { NavigationSidebarItemViewProps } from "./components/sqm-navigation-sidebar-item/sqm-navigation-sidebar-item-view"; import { UsePagination } from "./components/sqm-pagination/usePagination"; +import { PartnerInfoModalResult } from "./components/sqm-partner-info-modal/usePartnerInfoModal"; import { PasswordFieldViewDemoProps } from "./components/sqm-password-field/usePasswordField"; import { PayoutButtonScrollViewProps } from "./components/sqm-payout-button-scroll/sqm-payout-button-scroll-view"; import { PayoutStatusAlertViewProps } from "./components/tax-and-cash/sqm-payout-status-alert/sqm-payout-status-alert-view"; @@ -66,6 +67,12 @@ export namespace Components { "renderLabel": (idx: any) => Promise; } interface SqmBankingInfoForm { + /** + * Error messages for the agency code field. Supports error codes: empty, alphanumeric, tooShort + * @uiName Agency code error + * @uiWidget textArea + */ + "agencyCodeError": string; /** * @uiName Agency code field label */ @@ -75,18 +82,42 @@ export namespace Components { * @uiName Back button label */ "backButton": string; + /** + * Error messages for the bank account number / IBAN field. Supports error codes: empty, invalidUk, invalid, ibanEmpty, ibanAlphanumeric, ibanInvalid, ibanCountryMismatch + * @uiName Bank account number / IBAN error + * @uiWidget textArea + */ + "bankAccountNumberError": string; /** * @uiName Bank account number field label */ "bankAccountNumberLabel": string; + /** + * Error messages for the bank account type field. Supports error codes: empty + * @uiName Bank account type error + * @uiWidget textArea + */ + "bankAccountTypeError": string; /** * @uiName Bank account type field label */ "bankAccountTypeLabel": string; + /** + * Error messages for the bank address field. Supports error codes: empty + * @uiName Bank address error + * @uiWidget textArea + */ + "bankAddressError": string; /** * @uiName Bank address field label */ "bankAddressLabel": string; + /** + * Error messages for the bank city field. Supports error codes: empty + * @uiName Bank city error + * @uiWidget textArea + */ + "bankCityError": string; /** * @uiName Bank city field label */ @@ -95,14 +126,32 @@ export namespace Components { * @uiName Bank country field label */ "bankLocationLabel": string; + /** + * Error messages for the bank name field. Supports error codes: empty + * @uiName Bank name error + * @uiWidget textArea + */ + "bankNameError": string; /** * @uiName Bank name field label */ "bankNameLabel": string; + /** + * Error messages for the bank postal code field. Supports error codes: empty + * @uiName Bank postal code error + * @uiWidget textArea + */ + "bankPostalCodeError": string; /** * @uiName Bank postal code field label */ "bankPostalCodeLabel": string; + /** + * Error messages for the bank province/state field. Supports error codes: empty + * @uiName Bank province/state error + * @uiWidget textArea + */ + "bankStateError": string; /** * @uiName Bank province/state field label */ @@ -111,14 +160,36 @@ export namespace Components { * @uiName Beneficiary account field description */ "beneficiaryAccountNameDescription": string; + /** + * Error messages for the beneficiary / account holder name field. Supports error codes: empty, invalidCharacters, numeric, tooLong, nonEnglish, businessNameMismatch, nameMismatch, businessPayeeMismatch, payeeMismatch + * @uiName Beneficiary account name error + * @uiWidget textArea + */ + "beneficiaryAccountNameError": string; /** * @uiName Beneficiary account field label */ "beneficiaryAccountNameLabel": string; + /** + * Error messages for the branch code field. Supports error codes: invalid + * @uiName Branch code error + * @uiWidget textArea + */ + "branchCodeError": string; /** * @uiName Branch code field label */ "branchCodeLabel": string; + /** + * Error messages for the branch name field. Supports error codes: empty + * @uiName Branch name error + * @uiWidget textArea + */ + "branchNameError": string; + /** + * @uiName Branch name field label + */ + "branchNameLabel": string; /** * One of three options listed for the classification field * @uiName Business classification option @@ -132,6 +203,16 @@ export namespace Components { * @uiName Classification CPF field label */ "classificationCPFLabel": string; + /** + * Error messages for the classification code field. Supports error codes: empty, invalidKzt + * @uiName Classification code error + * @uiWidget textArea + */ + "classificationCodeError": string; + /** + * @uiName Classification code field label + */ + "classificationCodeLabel": string; /** * @uiName Classification entity field label */ @@ -239,6 +320,12 @@ export namespace Components { * @uiName Information modal title */ "modalTitle": string; + /** + * Error messages for the patronymic name field. Supports error codes: empty, alphanumeric + * @uiName Patronymic name error + * @uiWidget textArea + */ + "patronymicNameError": string; /** * @uiName Patronymic name field label */ @@ -248,6 +335,12 @@ export namespace Components { * @uiName PayPal email field label */ "payPalInputLabel": string; + /** + * Error messages for the payment day field. Supports error codes: empty, invalid + * @uiName Payment day error + * @uiWidget textArea + */ + "paymentDayError": string; /** * Label text for the payment day select option for the fifteenth of the month * @uiName Fifteenth of month payday option @@ -283,11 +376,29 @@ export namespace Components { * @uiName Fixed day payment schedule option */ "paymentScheduleFixedDay": string; + /** + * Error messages for the payment threshold field. Supports error codes: empty, invalid + * @uiName Payment threshold error + * @uiWidget textArea + */ + "paymentThresholdError": string; /** * Participant use this field to select the balance at which they want to be paid * @uiName Payment threshold field label */ "paymentThresholdSelectLabel": string; + /** + * Error messages for the PayPal email field. Supports error codes: empty, unsupportedCurrency, invalidEmail, verificationIncomplete + * @uiName PayPal email error + * @uiWidget textArea + */ + "paypalEmailError": string; + /** + * Error messages for the routing code / sort code / BSB field. Supports error codes: invalidBsb, invalidSortCode, empty, invalid + * @uiName Routing code error + * @uiWidget textArea + */ + "routingCodeError": string; /** * @uiName Routing code field label */ @@ -305,6 +416,12 @@ export namespace Components { * @uiName Support link text */ "supportLink": string; + /** + * Error messages for the SWIFT / BIC code field. Supports error codes: empty, alphanumeric, invalid + * @uiName SWIFT code error + * @uiWidget textArea + */ + "swiftCodeError": string; /** * @uiName SWIFT code field label */ @@ -318,6 +435,12 @@ export namespace Components { * @uiName Page description */ "taxAndPayoutsDescription": string; + /** + * Error messages for the tax payer ID / classification entity field. Supports error codes: empty, emptyAr, emptyKr, alphanumeric, alphanumericAr, alphanumericKr, invalid, invalidAr, invalidKr, invalidKzt, cnpjTooShort, cpfTooShort + * @uiName Tax payer ID error + * @uiWidget textArea + */ + "taxPayerIdError": string; /** * @uiName Taxpayer ID field label */ @@ -335,6 +458,12 @@ export namespace Components { * @uiName Verify email header */ "verifyEmailHeaderText": string; + /** + * Error messages for the VO code field. Supports error codes: empty, alphanumeric + * @uiName VO code error + * @uiWidget textArea + */ + "voCodeError": string; /** * @uiName VO code field label */ @@ -2192,6 +2321,110 @@ export namespace Components { */ "paginationText": string; } + interface SqmPartnerInfoModal { + /** + * Edit the property called terms and conditions text to change what's displayed for {termsAndConditionsLink}. + * @uiName Terms and conditions checkbox + */ + "allowBankingCollection": string; + /** + * @uiName Confirm button label + */ + "confirmButtonLabel": string; + /** + * @uiName Country label + */ + "countryLabel": string; + /** + * @uiName Currency label + */ + "currencyLabel": string; + /** + * @undocumented + * @uiType object + */ + "demoData"?: DemoData; + /** + * Description for existing partner confirmation + * @uiName Existing partner description + * @uiWidget textArea + */ + "descriptionExistingPartner": string; + /** + * Description for new partner setup + * @uiName New partner description + * @uiWidget textArea + */ + "descriptionNewPartner": string; + /** + * Used to render in another modal. + * @undocumented + */ + "inModal": boolean; + /** + * @uiName Missing fields error text + * @uiWidget textArea + */ + "missingFieldsErrorText": string; + /** + * Header text when user has no existing partner + * @uiName New partner header + * @uiWidget textArea + */ + "modalHeader": string; + /** + * Header text when user has an existing partner + * @uiName Existing partner header + * @uiWidget textArea + */ + "modalHeaderExistingPartner": string; + /** + * @uiName Network error text + * @uiWidget textArea + */ + "networkErrorText": string; + /** + * @uiName Search country placeholder + */ + "searchCountryPlaceholder": string; + /** + * @uiName Search currency placeholder + */ + "searchCurrencyPlaceholder": string; + /** + * @undocumented + * @componentState { "title": "Connected (hidden)", "default": true, "props": { "states": { "open": false } } } + * @componentState { "title": "New partner", "props": { "states": { "open": true, "isExistingPartner": false } } } + * @componentState { "title": "Existing partner", "props": { "states": { "open": true, "isExistingPartner": true, "countryCode": "US", "currency": "USD" } } } + */ + "stateController": string; + /** + * @uiName Submit button label + */ + "submitButtonLabel": string; + /** + * Support description for existing partner confirmation + * @uiName Existing partner support description + * @uiWidget textArea + */ + "supportDescriptionExistingPartner": string; + /** + * Support description for existing partner confirmation + * @uiName Existing partner support description + */ + "supportLink": string; + /** + * The link text that appears in the terms and conditions checkbox + * @uiName Terms and conditions text + * @uiWidget textArea + */ + "termsAndConditionsLabel": string; + /** + * The link that appears in the terms and conditions checkbox + * @uiName Terms and conditions link + */ + "termsAndConditionsLink": string; + } interface SqmPasswordField { /** * @undocumented @@ -5962,11 +6195,6 @@ export namespace Components { * @uiGroup Step 1 Properties */ "step1_currency": string; - /** - * @uiName Currency field help text - * @uiGroup Step 1 Properties - */ - "step1_currencyHelpText": string; /** * @uiName Email field label * @uiGroup Step 1 Properties @@ -6285,6 +6513,11 @@ export namespace Components { * @uiGroup Step 4 Properties */ "step4_branchCodeLabel": string; + /** + * @uiName Branch name field label + * @uiGroup Step 4 Properties + */ + "step4_branchNameLabel": string; /** * One of three options listed for the classification field * @uiName Business classification option @@ -6301,6 +6534,11 @@ export namespace Components { * @uiGroup Step 4 Properties */ "step4_classificationCPFLabel": string; + /** + * @uiName Classification code field label + * @uiGroup Step 4 Properties + */ + "step4_classificationCodeLabel": string; /** * @uiName Classification entity field label * @uiGroup Step 4 Properties @@ -7047,10 +7285,6 @@ export namespace Components { * @uiName Currency field label */ "currency": string; - /** - * @uiName Currency field help text - */ - "currencyHelpText": string; /** * @undocumented * @uiType object @@ -7240,6 +7474,78 @@ export namespace Components { * @uiGroup Code Verification Step */ "codeStep_verifyText": string; + /** + * @uiName Confirm button label + * @uiGroup Partner Creation Step + */ + "createPartnerStep_confirmButtonLabel": string; + /** + * @uiName Country label + * @uiGroup Partner Creation Step + */ + "createPartnerStep_countryLabel": string; + /** + * @uiName Currency label + * @uiGroup Partner Creation Step + */ + "createPartnerStep_currencyLabel": string; + /** + * @uiName Existing partner description + * @uiGroup Partner Creation Step + * @uiWidget textArea + */ + "createPartnerStep_descriptionExistingPartner": string; + /** + * @uiName New partner description + * @uiGroup Partner Creation Step + * @uiWidget textArea + */ + "createPartnerStep_descriptionNewPartner": string; + /** + * @uiName Missing fields error text + * @uiGroup Partner Creation Step + * @uiWidget textArea + */ + "createPartnerStep_missingFieldsErrorText": string; + /** + * @uiName New partner header + * @uiGroup Partner Creation Step + * @uiWidget textArea + */ + "createPartnerStep_modalHeader": string; + /** + * @uiName Existing partner header + * @uiGroup Partner Creation Step + * @uiWidget textArea + */ + "createPartnerStep_modalHeaderExistingPartner": string; + /** + * @uiName Network error text + * @uiGroup Partner Creation Step + * @uiWidget textArea + */ + "createPartnerStep_networkErrorText": string; + /** + * @uiName Search country placeholder + * @uiGroup Partner Creation Step + */ + "createPartnerStep_searchCountryPlaceholder": string; + /** + * @uiName Search currency placeholder + * @uiGroup Partner Creation Step + */ + "createPartnerStep_searchCurrencyPlaceholder": string; + /** + * @uiName Submit button label + * @uiGroup Partner Creation Step + */ + "createPartnerStep_submitButtonLabel": string; + /** + * @uiName Existing partner support description + * @uiGroup Partner Creation Step + * @uiWidget textArea + */ + "createPartnerStep_supportDescriptionExistingPartner": string; /** * @uiName Email input label * @uiGroup Email Verification Step @@ -7271,19 +7577,15 @@ export namespace Components { */ "emailStep_verifyEmailHeaderText": string; /** - * @uiName General verify widget description text + * @uiName General widget header text with partner creation * @uiGroup General Text */ - "general_verifyEmailDescription": string; - /** - * @uiName General verify widget header text - * @uiGroup General Text - */ - "general_verifyEmailHeader": string; + "general_widgetHeaderWithPartnerCreation": string; /** * @undocumented * @componentState { "title": "Step 1: Enter email", "props": { "showCode": false }, "dependencies": ["sqm-email-verification"], "uiGroup": "Email Verification Step" } * @componentState { "title": "Step 2: Enter code", "props": { "showCode": true }, "dependencies": ["sqm-code-verification"], "uiGroup": "Code Verification Step" } + * @componentState { "title": "Step 3: Create Partner", "props": { "showPartnerModal": true }, "dependencies": ["sqm-partner-info-modal"], "uiGroup": "Partner Creation Step" } */ "stateController": string; } @@ -7591,6 +7893,12 @@ declare global { prototype: HTMLSqmPaginationElement; new (): HTMLSqmPaginationElement; }; + interface HTMLSqmPartnerInfoModalElement extends Components.SqmPartnerInfoModal, HTMLStencilElement { + } + var HTMLSqmPartnerInfoModalElement: { + prototype: HTMLSqmPartnerInfoModalElement; + new (): HTMLSqmPartnerInfoModalElement; + }; interface HTMLSqmPasswordFieldElement extends Components.SqmPasswordField, HTMLStencilElement { } var HTMLSqmPasswordFieldElement: { @@ -8121,6 +8429,7 @@ declare global { "sqm-navigation-sidebar": HTMLSqmNavigationSidebarElement; "sqm-navigation-sidebar-item": HTMLSqmNavigationSidebarItemElement; "sqm-pagination": HTMLSqmPaginationElement; + "sqm-partner-info-modal": HTMLSqmPartnerInfoModalElement; "sqm-password-field": HTMLSqmPasswordFieldElement; "sqm-payout-button-scroll": HTMLSqmPayoutButtonScrollElement; "sqm-payout-details-card": HTMLSqmPayoutDetailsCardElement; @@ -8207,6 +8516,12 @@ declare namespace LocalJSX { interface RaisinsPlopTarget { } interface SqmBankingInfoForm { + /** + * Error messages for the agency code field. Supports error codes: empty, alphanumeric, tooShort + * @uiName Agency code error + * @uiWidget textArea + */ + "agencyCodeError"?: string; /** * @uiName Agency code field label */ @@ -8216,18 +8531,42 @@ declare namespace LocalJSX { * @uiName Back button label */ "backButton"?: string; + /** + * Error messages for the bank account number / IBAN field. Supports error codes: empty, invalidUk, invalid, ibanEmpty, ibanAlphanumeric, ibanInvalid, ibanCountryMismatch + * @uiName Bank account number / IBAN error + * @uiWidget textArea + */ + "bankAccountNumberError"?: string; /** * @uiName Bank account number field label */ "bankAccountNumberLabel"?: string; + /** + * Error messages for the bank account type field. Supports error codes: empty + * @uiName Bank account type error + * @uiWidget textArea + */ + "bankAccountTypeError"?: string; /** * @uiName Bank account type field label */ "bankAccountTypeLabel"?: string; + /** + * Error messages for the bank address field. Supports error codes: empty + * @uiName Bank address error + * @uiWidget textArea + */ + "bankAddressError"?: string; /** * @uiName Bank address field label */ "bankAddressLabel"?: string; + /** + * Error messages for the bank city field. Supports error codes: empty + * @uiName Bank city error + * @uiWidget textArea + */ + "bankCityError"?: string; /** * @uiName Bank city field label */ @@ -8236,14 +8575,32 @@ declare namespace LocalJSX { * @uiName Bank country field label */ "bankLocationLabel"?: string; + /** + * Error messages for the bank name field. Supports error codes: empty + * @uiName Bank name error + * @uiWidget textArea + */ + "bankNameError"?: string; /** * @uiName Bank name field label */ "bankNameLabel"?: string; + /** + * Error messages for the bank postal code field. Supports error codes: empty + * @uiName Bank postal code error + * @uiWidget textArea + */ + "bankPostalCodeError"?: string; /** * @uiName Bank postal code field label */ "bankPostalCodeLabel"?: string; + /** + * Error messages for the bank province/state field. Supports error codes: empty + * @uiName Bank province/state error + * @uiWidget textArea + */ + "bankStateError"?: string; /** * @uiName Bank province/state field label */ @@ -8252,14 +8609,36 @@ declare namespace LocalJSX { * @uiName Beneficiary account field description */ "beneficiaryAccountNameDescription"?: string; + /** + * Error messages for the beneficiary / account holder name field. Supports error codes: empty, invalidCharacters, numeric, tooLong, nonEnglish, businessNameMismatch, nameMismatch, businessPayeeMismatch, payeeMismatch + * @uiName Beneficiary account name error + * @uiWidget textArea + */ + "beneficiaryAccountNameError"?: string; /** * @uiName Beneficiary account field label */ "beneficiaryAccountNameLabel"?: string; + /** + * Error messages for the branch code field. Supports error codes: invalid + * @uiName Branch code error + * @uiWidget textArea + */ + "branchCodeError"?: string; /** * @uiName Branch code field label */ "branchCodeLabel"?: string; + /** + * Error messages for the branch name field. Supports error codes: empty + * @uiName Branch name error + * @uiWidget textArea + */ + "branchNameError"?: string; + /** + * @uiName Branch name field label + */ + "branchNameLabel"?: string; /** * One of three options listed for the classification field * @uiName Business classification option @@ -8273,6 +8652,16 @@ declare namespace LocalJSX { * @uiName Classification CPF field label */ "classificationCPFLabel"?: string; + /** + * Error messages for the classification code field. Supports error codes: empty, invalidKzt + * @uiName Classification code error + * @uiWidget textArea + */ + "classificationCodeError"?: string; + /** + * @uiName Classification code field label + */ + "classificationCodeLabel"?: string; /** * @uiName Classification entity field label */ @@ -8380,6 +8769,12 @@ declare namespace LocalJSX { * @uiName Information modal title */ "modalTitle"?: string; + /** + * Error messages for the patronymic name field. Supports error codes: empty, alphanumeric + * @uiName Patronymic name error + * @uiWidget textArea + */ + "patronymicNameError"?: string; /** * @uiName Patronymic name field label */ @@ -8389,6 +8784,12 @@ declare namespace LocalJSX { * @uiName PayPal email field label */ "payPalInputLabel"?: string; + /** + * Error messages for the payment day field. Supports error codes: empty, invalid + * @uiName Payment day error + * @uiWidget textArea + */ + "paymentDayError"?: string; /** * Label text for the payment day select option for the fifteenth of the month * @uiName Fifteenth of month payday option @@ -8424,11 +8825,29 @@ declare namespace LocalJSX { * @uiName Fixed day payment schedule option */ "paymentScheduleFixedDay"?: string; + /** + * Error messages for the payment threshold field. Supports error codes: empty, invalid + * @uiName Payment threshold error + * @uiWidget textArea + */ + "paymentThresholdError"?: string; /** * Participant use this field to select the balance at which they want to be paid * @uiName Payment threshold field label */ "paymentThresholdSelectLabel"?: string; + /** + * Error messages for the PayPal email field. Supports error codes: empty, unsupportedCurrency, invalidEmail, verificationIncomplete + * @uiName PayPal email error + * @uiWidget textArea + */ + "paypalEmailError"?: string; + /** + * Error messages for the routing code / sort code / BSB field. Supports error codes: invalidBsb, invalidSortCode, empty, invalid + * @uiName Routing code error + * @uiWidget textArea + */ + "routingCodeError"?: string; /** * @uiName Routing code field label */ @@ -8446,6 +8865,12 @@ declare namespace LocalJSX { * @uiName Support link text */ "supportLink"?: string; + /** + * Error messages for the SWIFT / BIC code field. Supports error codes: empty, alphanumeric, invalid + * @uiName SWIFT code error + * @uiWidget textArea + */ + "swiftCodeError"?: string; /** * @uiName SWIFT code field label */ @@ -8459,6 +8884,12 @@ declare namespace LocalJSX { * @uiName Page description */ "taxAndPayoutsDescription"?: string; + /** + * Error messages for the tax payer ID / classification entity field. Supports error codes: empty, emptyAr, emptyKr, alphanumeric, alphanumericAr, alphanumericKr, invalid, invalidAr, invalidKr, invalidKzt, cnpjTooShort, cpfTooShort + * @uiName Tax payer ID error + * @uiWidget textArea + */ + "taxPayerIdError"?: string; /** * @uiName Taxpayer ID field label */ @@ -8476,6 +8907,12 @@ declare namespace LocalJSX { * @uiName Verify email header */ "verifyEmailHeaderText"?: string; + /** + * Error messages for the VO code field. Supports error codes: empty, alphanumeric + * @uiName VO code error + * @uiWidget textArea + */ + "voCodeError"?: string; /** * @uiName VO code field label */ @@ -10328,6 +10765,110 @@ declare namespace LocalJSX { */ "paginationText"?: string; } + interface SqmPartnerInfoModal { + /** + * Edit the property called terms and conditions text to change what's displayed for {termsAndConditionsLink}. + * @uiName Terms and conditions checkbox + */ + "allowBankingCollection"?: string; + /** + * @uiName Confirm button label + */ + "confirmButtonLabel"?: string; + /** + * @uiName Country label + */ + "countryLabel"?: string; + /** + * @uiName Currency label + */ + "currencyLabel"?: string; + /** + * @undocumented + * @uiType object + */ + "demoData"?: DemoData; + /** + * Description for existing partner confirmation + * @uiName Existing partner description + * @uiWidget textArea + */ + "descriptionExistingPartner"?: string; + /** + * Description for new partner setup + * @uiName New partner description + * @uiWidget textArea + */ + "descriptionNewPartner"?: string; + /** + * Used to render in another modal. + * @undocumented + */ + "inModal"?: boolean; + /** + * @uiName Missing fields error text + * @uiWidget textArea + */ + "missingFieldsErrorText"?: string; + /** + * Header text when user has no existing partner + * @uiName New partner header + * @uiWidget textArea + */ + "modalHeader"?: string; + /** + * Header text when user has an existing partner + * @uiName Existing partner header + * @uiWidget textArea + */ + "modalHeaderExistingPartner"?: string; + /** + * @uiName Network error text + * @uiWidget textArea + */ + "networkErrorText"?: string; + /** + * @uiName Search country placeholder + */ + "searchCountryPlaceholder"?: string; + /** + * @uiName Search currency placeholder + */ + "searchCurrencyPlaceholder"?: string; + /** + * @undocumented + * @componentState { "title": "Connected (hidden)", "default": true, "props": { "states": { "open": false } } } + * @componentState { "title": "New partner", "props": { "states": { "open": true, "isExistingPartner": false } } } + * @componentState { "title": "Existing partner", "props": { "states": { "open": true, "isExistingPartner": true, "countryCode": "US", "currency": "USD" } } } + */ + "stateController"?: string; + /** + * @uiName Submit button label + */ + "submitButtonLabel"?: string; + /** + * Support description for existing partner confirmation + * @uiName Existing partner support description + * @uiWidget textArea + */ + "supportDescriptionExistingPartner"?: string; + /** + * Support description for existing partner confirmation + * @uiName Existing partner support description + */ + "supportLink"?: string; + /** + * The link text that appears in the terms and conditions checkbox + * @uiName Terms and conditions text + * @uiWidget textArea + */ + "termsAndConditionsLabel"?: string; + /** + * The link that appears in the terms and conditions checkbox + * @uiName Terms and conditions link + */ + "termsAndConditionsLink"?: string; + } interface SqmPasswordField { /** * @undocumented @@ -14074,11 +14615,6 @@ declare namespace LocalJSX { * @uiGroup Step 1 Properties */ "step1_currency"?: string; - /** - * @uiName Currency field help text - * @uiGroup Step 1 Properties - */ - "step1_currencyHelpText"?: string; /** * @uiName Email field label * @uiGroup Step 1 Properties @@ -14397,6 +14933,11 @@ declare namespace LocalJSX { * @uiGroup Step 4 Properties */ "step4_branchCodeLabel"?: string; + /** + * @uiName Branch name field label + * @uiGroup Step 4 Properties + */ + "step4_branchNameLabel"?: string; /** * One of three options listed for the classification field * @uiName Business classification option @@ -14413,6 +14954,11 @@ declare namespace LocalJSX { * @uiGroup Step 4 Properties */ "step4_classificationCPFLabel"?: string; + /** + * @uiName Classification code field label + * @uiGroup Step 4 Properties + */ + "step4_classificationCodeLabel"?: string; /** * @uiName Classification entity field label * @uiGroup Step 4 Properties @@ -15158,10 +15704,6 @@ declare namespace LocalJSX { * @uiName Currency field label */ "currency"?: string; - /** - * @uiName Currency field help text - */ - "currencyHelpText"?: string; /** * @undocumented * @uiType object @@ -15351,6 +15893,78 @@ declare namespace LocalJSX { * @uiGroup Code Verification Step */ "codeStep_verifyText"?: string; + /** + * @uiName Confirm button label + * @uiGroup Partner Creation Step + */ + "createPartnerStep_confirmButtonLabel"?: string; + /** + * @uiName Country label + * @uiGroup Partner Creation Step + */ + "createPartnerStep_countryLabel"?: string; + /** + * @uiName Currency label + * @uiGroup Partner Creation Step + */ + "createPartnerStep_currencyLabel"?: string; + /** + * @uiName Existing partner description + * @uiGroup Partner Creation Step + * @uiWidget textArea + */ + "createPartnerStep_descriptionExistingPartner"?: string; + /** + * @uiName New partner description + * @uiGroup Partner Creation Step + * @uiWidget textArea + */ + "createPartnerStep_descriptionNewPartner"?: string; + /** + * @uiName Missing fields error text + * @uiGroup Partner Creation Step + * @uiWidget textArea + */ + "createPartnerStep_missingFieldsErrorText"?: string; + /** + * @uiName New partner header + * @uiGroup Partner Creation Step + * @uiWidget textArea + */ + "createPartnerStep_modalHeader"?: string; + /** + * @uiName Existing partner header + * @uiGroup Partner Creation Step + * @uiWidget textArea + */ + "createPartnerStep_modalHeaderExistingPartner"?: string; + /** + * @uiName Network error text + * @uiGroup Partner Creation Step + * @uiWidget textArea + */ + "createPartnerStep_networkErrorText"?: string; + /** + * @uiName Search country placeholder + * @uiGroup Partner Creation Step + */ + "createPartnerStep_searchCountryPlaceholder"?: string; + /** + * @uiName Search currency placeholder + * @uiGroup Partner Creation Step + */ + "createPartnerStep_searchCurrencyPlaceholder"?: string; + /** + * @uiName Submit button label + * @uiGroup Partner Creation Step + */ + "createPartnerStep_submitButtonLabel"?: string; + /** + * @uiName Existing partner support description + * @uiGroup Partner Creation Step + * @uiWidget textArea + */ + "createPartnerStep_supportDescriptionExistingPartner"?: string; /** * @uiName Email input label * @uiGroup Email Verification Step @@ -15382,19 +15996,15 @@ declare namespace LocalJSX { */ "emailStep_verifyEmailHeaderText"?: string; /** - * @uiName General verify widget description text - * @uiGroup General Text - */ - "general_verifyEmailDescription"?: string; - /** - * @uiName General verify widget header text + * @uiName General widget header text with partner creation * @uiGroup General Text */ - "general_verifyEmailHeader"?: string; + "general_widgetHeaderWithPartnerCreation"?: string; /** * @undocumented * @componentState { "title": "Step 1: Enter email", "props": { "showCode": false }, "dependencies": ["sqm-email-verification"], "uiGroup": "Email Verification Step" } * @componentState { "title": "Step 2: Enter code", "props": { "showCode": true }, "dependencies": ["sqm-code-verification"], "uiGroup": "Code Verification Step" } + * @componentState { "title": "Step 3: Create Partner", "props": { "showPartnerModal": true }, "dependencies": ["sqm-partner-info-modal"], "uiGroup": "Partner Creation Step" } */ "stateController"?: string; } @@ -15456,6 +16066,7 @@ declare namespace LocalJSX { "sqm-navigation-sidebar": SqmNavigationSidebar; "sqm-navigation-sidebar-item": SqmNavigationSidebarItem; "sqm-pagination": SqmPagination; + "sqm-partner-info-modal": SqmPartnerInfoModal; "sqm-password-field": SqmPasswordField; "sqm-payout-button-scroll": SqmPayoutButtonScroll; "sqm-payout-details-card": SqmPayoutDetailsCard; @@ -15591,6 +16202,7 @@ declare module "@stencil/core" { "sqm-navigation-sidebar": LocalJSX.SqmNavigationSidebar & JSXBase.HTMLAttributes; "sqm-navigation-sidebar-item": LocalJSX.SqmNavigationSidebarItem & JSXBase.HTMLAttributes; "sqm-pagination": LocalJSX.SqmPagination & JSXBase.HTMLAttributes; + "sqm-partner-info-modal": LocalJSX.SqmPartnerInfoModal & JSXBase.HTMLAttributes; "sqm-password-field": LocalJSX.SqmPasswordField & JSXBase.HTMLAttributes; "sqm-payout-button-scroll": LocalJSX.SqmPayoutButtonScroll & JSXBase.HTMLAttributes; "sqm-payout-details-card": LocalJSX.SqmPayoutDetailsCard & JSXBase.HTMLAttributes; diff --git a/packages/mint-components/src/components/sqm-form-message/readme.md b/packages/mint-components/src/components/sqm-form-message/readme.md index 1dc9ea8c02..d6233be57c 100644 --- a/packages/mint-components/src/components/sqm-form-message/readme.md +++ b/packages/mint-components/src/components/sqm-form-message/readme.md @@ -26,6 +26,7 @@ - [sqm-indirect-tax-form](../tax-and-cash/sqm-indirect-tax-form) - [sqm-instant-access-registration](../sqm-instant-access-registration) - [sqm-lead-form](../sqm-lead-form) + - [sqm-partner-info-modal](../sqm-partner-info-modal) - [sqm-payout-status-alert](../tax-and-cash/sqm-payout-status-alert) - [sqm-portal-change-marketing](../sqm-portal-change-marketing) - [sqm-portal-change-password](../sqm-portal-change-password) @@ -64,6 +65,7 @@ graph TD; sqm-indirect-tax-form --> sqm-form-message sqm-instant-access-registration --> sqm-form-message sqm-lead-form --> sqm-form-message + sqm-partner-info-modal --> sqm-form-message sqm-payout-status-alert --> sqm-form-message sqm-portal-change-marketing --> sqm-form-message sqm-portal-change-password --> sqm-form-message diff --git a/packages/mint-components/src/components/sqm-partner-info-modal/ImpactConnectionStatus.feature b/packages/mint-components/src/components/sqm-partner-info-modal/ImpactConnectionStatus.feature new file mode 100644 index 0000000000..cb237916f2 --- /dev/null +++ b/packages/mint-components/src/components/sqm-partner-info-modal/ImpactConnectionStatus.feature @@ -0,0 +1,30 @@ +@owner:andy @author:andy +Feature: Partner Connection Status + + Background: + Given user is on the Microsite or a widget that contains the cash form + + Scenario: New participants with no publisher submits partner modal + Given a participant with no linked Impact publisher + When they submit the partner info modal with valid country, currency, and T&C consent + Then an Impact publisher is created for the participant + And impactConnection.connectionStatus resolves to "STARTED" + + Scenario: New participants with an existing publisher submits partner modal + Given a participant whose email is already linked to an Impact publisher + When they submit the partner info modal with valid country, currency, and T&C consent + Then the existing Impact publisher is used and not updated + And impactConnection.connectionStatus resolves to "STARTED" + + Scenario: User has not filled out User Info Form + Given the user has a publisher + And the publisher does not contain billing data + When they submit the user User Info Form with their billing information + Then impactConnection.connectionStatus resolves to "COMPLETED" + + Scenario: User has pre-existing partner which completed user info form + Given the user has a publisher + And the publisher contains complete billing data + Then the user info form is skipped + And the data is unchanged + And impactConnection.connectionStatus resolves to "COMPLETED" diff --git a/packages/mint-components/src/components/sqm-partner-info-modal/PartnerInfoModal.stories.tsx b/packages/mint-components/src/components/sqm-partner-info-modal/PartnerInfoModal.stories.tsx new file mode 100644 index 0000000000..7d32e8ac2a --- /dev/null +++ b/packages/mint-components/src/components/sqm-partner-info-modal/PartnerInfoModal.stories.tsx @@ -0,0 +1,210 @@ +import { h } from "@stencil/core"; +import { + PartnerInfoModalView, + PartnerInfoModalViewProps, +} from "./sqm-partner-info-modal-view"; + +export default { + title: "Components/Partner Info Modal", +}; + +const demoCountries = [ + { countryCode: "US", displayName: "United States" }, + { countryCode: "CA", displayName: "Canada" }, + { countryCode: "GB", displayName: "United Kingdom" }, + { countryCode: "AU", displayName: "Australia" }, + { countryCode: "DE", displayName: "Germany" }, + { countryCode: "FR", displayName: "France" }, + { countryCode: "JP", displayName: "Japan" }, +]; + +const demoCurrencies = [ + { currencyCode: "USD", displayName: "US Dollar" }, + { currencyCode: "CAD", displayName: "Canadian Dollar" }, + { currencyCode: "GBP", displayName: "British Pound" }, + { currencyCode: "EUR", displayName: "Euro" }, + { currencyCode: "AUD", displayName: "Australian Dollar" }, +]; + +const noopCallbacks = { + onCountryChange: (e: any) => console.log("Country changed:", e), + onCurrencyChange: (e: any) => console.log("Currency changed:", e), + onCheckboxChange: (e: any) => console.log("Checkbox changed:", e), + setCountrySearch: (v: string) => console.log("Country search:", v), + setCurrencySearch: (v: string) => console.log("Currency search:", v), + onSubmit: () => console.log("Submit"), + onClose: () => console.log("Close"), +}; + +const defaultText = { + modalHeader: "Let's get you ready for rewards", + modalHeaderExistingPartner: "We found an existing account", + descriptionNewPartner: + "Confirm your country and currency now to get your future rewards faster.", + descriptionExistingPartner: + "We noticed you are already an Impact.com partner, please confirm your information.", + supportDescriptionExistingPartner: + "If this is a mistake, please contact Support or sign up for this referral program with a different email.", + supportLink: "Support", + countryLabel: "Country", + currencyLabel: "Currency", + submitButtonLabel: "Submit", + confirmButtonLabel: "Confirm", + searchCountryPlaceholder: "Search for a country", + searchCurrencyPlaceholder: "Search for a currency", + bankingCollectionText: "", + allowBankingCollection: + "I have read the {termsAndConditionsLink} and allow impact.com to collect my tax and banking information", + termsAndConditionsLabel: "terms and conditions", + termsAndConditionsLink: + "https://terms.advocate.impact.com/PayoutTermsAndConditions.html", +}; + +const defaultProps: PartnerInfoModalViewProps = { + states: { + open: true, + loading: false, + submitting: false, + isExistingPartner: false, + countryCode: null, + currency: null, + error: "", + success: false, + filteredCountries: demoCountries, + filteredCurrencies: demoCurrencies, + allowBankingCollection: false, + disabled: false, + }, + callbacks: noopCallbacks, + text: defaultText, +}; + +export const NewPartnerEmpty = () => { + return ; +}; + +export const NewPartnerPrefilled = () => { + const props: PartnerInfoModalViewProps = { + ...defaultProps, + states: { + ...defaultProps.states, + countryCode: "US", + currency: "", + }, + }; + return ; +}; + +export const NewPartnerFullySelected = () => { + const props: PartnerInfoModalViewProps = { + ...defaultProps, + states: { + ...defaultProps.states, + countryCode: "US", + currency: "USD", + }, + }; + return ; +}; + +export const ExistingPartnerConfirm = () => { + const props: PartnerInfoModalViewProps = { + ...defaultProps, + states: { + ...defaultProps.states, + isExistingPartner: true, + countryCode: "CA", + currency: "CAD", + }, + }; + return ; +}; + +export const ExistingPartnerEmpty = () => { + const props: PartnerInfoModalViewProps = { + ...defaultProps, + states: { + ...defaultProps.states, + isExistingPartner: true, + countryCode: "", + currency: "", + }, + }; + return ; +}; + +export const Submitting = () => { + const props: PartnerInfoModalViewProps = { + ...defaultProps, + states: { + ...defaultProps.states, + countryCode: "GB", + currency: "GBP", + submitting: true, + }, + }; + return ; +}; + +export const WithError = () => { + const props: PartnerInfoModalViewProps = { + ...defaultProps, + states: { + ...defaultProps.states, + countryCode: "US", + currency: "USD", + error: + "An error occurred while creating your partner account. Please try again.", + }, + }; + return ; +}; + +export const ValidationError = () => { + const props: PartnerInfoModalViewProps = { + ...defaultProps, + states: { + ...defaultProps.states, + countryCode: "", + currency: "", + error: "Please select both a country and currency.", + }, + }; + return ; +}; + +export const Closed = () => { + const props: PartnerInfoModalViewProps = { + ...defaultProps, + states: { + ...defaultProps.states, + open: false, + }, + }; + return ; +}; + +export const SQMComponentExistingPartner = () => { + return ( + + ); +}; + +export const SQMComponentNewPartner = () => { + return ( + + ); +}; diff --git a/packages/mint-components/src/components/sqm-partner-info-modal/readme.md b/packages/mint-components/src/components/sqm-partner-info-modal/readme.md new file mode 100644 index 0000000000..7d353f085c --- /dev/null +++ b/packages/mint-components/src/components/sqm-partner-info-modal/readme.md @@ -0,0 +1,57 @@ +# sqm-partner-info-modal + + + + + + +## Properties + +| Property | Attribute | Description | Type | Default | +| ----------------------------------- | -------------------------------------- | ----------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `allowBankingCollection` | `allow-banking-collection` | Edit the property called terms and conditions text to change what's displayed for {termsAndConditionsLink}. | `string` | `"I have read the {termsAndConditionsLink} and allow impact.com to collect my tax and banking information"` | +| `confirmButtonLabel` | `confirm-button-label` | | `string` | `"Confirm"` | +| `countryLabel` | `country-label` | | `string` | `"Country"` | +| `currencyLabel` | `currency-label` | | `string` | `"Currency"` | +| `demoData` | -- | | `{ states?: { open: boolean; loading: boolean; submitting: boolean; isExistingPartner: boolean; countryCode: string; currency: string; error: string; success: boolean; filteredCountries: { countryCode: string; displayName: string; }[]; filteredCurrencies: { currencyCode: string; displayName: string; }[]; allowBankingCollection: boolean; disabled: boolean; }; }` | `undefined` | +| `descriptionExistingPartner` | `description-existing-partner` | Description for existing partner confirmation | `string` | `"We found an account with this email on our referral program provider, impact.com. Please confirm your country and currency now to get your future rewards faster."` | +| `descriptionNewPartner` | `description-new-partner` | Description for new partner setup | `string` | `"Confirm your country and currency now to get your future rewards faster."` | +| `inModal` | `in-modal` | Used to render in another modal. | `boolean` | `false` | +| `missingFieldsErrorText` | `missing-fields-error-text` | | `string` | `"Please fill out all fields."` | +| `modalHeader` | `modal-header` | Header text when user has no existing partner | `string` | `"Let's get you ready for rewards"` | +| `modalHeaderExistingPartner` | `modal-header-existing-partner` | Header text when user has an existing partner | `string` | `"We found an existing account"` | +| `networkErrorText` | `network-error-text` | | `string` | `"An error occurred. Please try again."` | +| `searchCountryPlaceholder` | `search-country-placeholder` | | `string` | `"Search for a country"` | +| `searchCurrencyPlaceholder` | `search-currency-placeholder` | | `string` | `"Search for a currency"` | +| `stateController` | `state-controller` | | `string` | `'{"sqm-partner-info-modal":{"states":{"open":false}}}'` | +| `submitButtonLabel` | `submit-button-label` | | `string` | `"Submit"` | +| `supportDescriptionExistingPartner` | `support-description-existing-partner` | Support description for existing partner confirmation | `string` | `"If this is a mistake, please contact {supportLink} or sign up for this referral program with a different email."` | +| `supportLink` | `support-link` | Support description for existing partner confirmation | `string` | `"Support"` | +| `termsAndConditionsLabel` | `terms-and-conditions-label` | The link text that appears in the terms and conditions checkbox | `string` | `"terms and conditions"` | +| `termsAndConditionsLink` | `terms-and-conditions-link` | The link that appears in the terms and conditions checkbox | `string` | `"https://terms.advocate.impact.com/PayoutTermsAndConditions.html"` | + + +## Dependencies + +### Used by + + - [sqm-stencilbook](../sqm-stencilbook) + - [sqm-widget-verification](../sqm-widget-verification) + +### Depends on + +- [sqm-form-message](../sqm-form-message) + +### Graph +```mermaid +graph TD; + sqm-partner-info-modal --> sqm-form-message + sqm-form-message --> sqm-skeleton + sqm-stencilbook --> sqm-partner-info-modal + sqm-widget-verification --> sqm-partner-info-modal + style sqm-partner-info-modal fill:#f9f,stroke:#333,stroke-width:4px +``` + +---------------------------------------------- + +*Built with [StencilJS](https://stenciljs.com/)* diff --git a/packages/mint-components/src/components/sqm-partner-info-modal/sqm-partner-info-modal-view.tsx b/packages/mint-components/src/components/sqm-partner-info-modal/sqm-partner-info-modal-view.tsx new file mode 100644 index 0000000000..ca6046dc9c --- /dev/null +++ b/packages/mint-components/src/components/sqm-partner-info-modal/sqm-partner-info-modal-view.tsx @@ -0,0 +1,303 @@ +import { h } from "@stencil/core"; +import { createStyleSheet } from "../../styling/JSS"; +import { intl } from "../../global/global"; + +export interface PartnerInfoModalViewProps { + states: { + open: boolean; + loading: boolean; + submitting: boolean; + isExistingPartner: boolean; + countryCode: string; + currency: string; + error: string; + success: boolean; + filteredCountries: { countryCode: string; displayName: string }[]; + filteredCurrencies: { currencyCode: string; displayName: string }[]; + allowBankingCollection: boolean; + disabled: boolean; + }; + callbacks: { + onCountryChange: (e: any) => void; + onCurrencyChange: (e: any) => void; + onCheckboxChange: (e: any) => void; + setCurrencySearch: (c: any) => void; + setCountrySearch: (c: any) => void; + onSubmit: () => void; + onClose: () => void; + onInitialFocus?: (e: any) => void; + }; + text: { + modalHeader: string; + descriptionNewPartner: string; + descriptionExistingPartner: string; + countryLabel: string; + currencyLabel: string; + submitButtonLabel: string; + confirmButtonLabel: string; + searchCountryPlaceholder: string; + searchCurrencyPlaceholder: string; + supportDescriptionExistingPartner: string; + supportLink: string; + modalHeaderExistingPartner: string; + allowBankingCollection: string; + termsAndConditionsLabel: string; + termsAndConditionsLink: string; + }; +} + +const style = { + Dialog: { + "&::part(panel)": { + maxWidth: "480px", + }, + "&::part(body)": { + padding: "0 var(--sl-spacing-x-large)", + fontSize: "var(--sl-font-size-small)", + overflow: "visible", + }, + "&::part(overlay)": { + background: "var(--sl-overlay-background-color)", + }, + }, + DialogTitle: { + fontSize: "var(--sl-font-size-x-large)", + fontWeight: "600", + padding: "var(--sl-spacing-large) 0 0 0", + margin: "0", + }, + FormFields: { + display: "flex", + flexDirection: "column", + gap: "var(--sl-spacing-medium)", + marginTop: "var(--sl-spacing-large)", + }, + ErrorMessage: { + marginTop: "var(--sl-spacing-x-small)", + }, + SearchInput: { + "&::part(base)": { + border: "none", + borderBottom: "1px solid var(--sl-color-neutral-300)", + borderRadius: "0", + }, + }, + SelectMenu: { + maxHeight: "300px", + }, + DescriptionContainer: { + display: "flex", + flexDirection: "column", + gap: "var(--sl-spacing-medium)", + margin: "0", + "& > p": { + margin: "0", + }, + }, + CheckboxWrapper: { + display: "flex", + justifyContent: "flex-start", + flexDirection: "column", + }, + Checkbox: { + "&::part(label)": { + fontSize: "var(--sl-font-size-small)", + }, + }, + SubmitButton: { + width: "100%", + marginTop: "var(--sl-spacing-large)", + "&::part(base)": { + color: "var(--sqm-primary-button-color)", + backgroundColor: "var(--sqm-primary-button-background)", + border: "var(--sqm-primary-button-color-border)", + borderRadius: "var(--sqm-primary-button-radius)", + }, + }, +}; + +export function PartnerInfoModalContentView(props: PartnerInfoModalViewProps) { + const { states, callbacks, text } = props; + const sheet = createStyleSheet(style); + const styleString = sheet.toString(); + + const supportMessage = intl.formatMessage( + { + id: "supportDescriptionExistingPartner", + defaultMessage: text.supportDescriptionExistingPartner, + }, + { + supportLink: ( + + {text.supportLink} + + ), + }, + ); + + const description = states.isExistingPartner ? ( + +

{text.descriptionExistingPartner}

+

{supportMessage}

+
+ ) : ( +

+ {text.descriptionNewPartner} +

+ ); + + const buttonLabel = states.isExistingPartner + ? text.confirmButtonLabel + : text.submitButtonLabel; + + const bankingCollectionText = intl.formatMessage( + { + id: "bankingCollectionText", + defaultMessage: text.allowBankingCollection, + }, + { + termsAndConditionsLink: ( + + {text.termsAndConditionsLabel} + + ), + }, + ); + + return ( +
+ +
+ {description} + + e.stopPropagation()} + onSl-input={(e: any) => { + callbacks.setCountrySearch(e.target?.value); + }} + /> +
+ {states.filteredCountries?.map((c) => ( + {c.displayName} + ))} +
+
+ + + e.stopPropagation()} + onSl-input={(e: any) => + callbacks.setCurrencySearch(e.target?.value) + } + /> +
+ {states.filteredCurrencies?.map((c) => ( + + {c.currencyCode} - {c.displayName} + + ))} +
+
+
+ + {bankingCollectionText} + +
+
+ {states.error && ( +
+ +

{states.error}

+
+
+ )} + + + {buttonLabel} + +
+ ); +} + +export function PartnerInfoModalView(props: PartnerInfoModalViewProps) { + const { states, text, callbacks } = props; + const sheet = createStyleSheet(style); + const styleString = sheet.toString(); + + if (!states.open) return
; + + const modalHeader = states.isExistingPartner + ? text.modalHeaderExistingPartner + : text.modalHeader; + + return ( +
+ + { + e.preventDefault(); + }} + onSl-hide={(e: any) => { + if (e.target?.tagName === "SL-DIALOG") { + e.preventDefault(); + } + }} + onSl-initial-focus={callbacks.onInitialFocus} + > +

{modalHeader}

+ +
+
+ ); +} diff --git a/packages/mint-components/src/components/sqm-partner-info-modal/sqm-partner-info-modal.feature b/packages/mint-components/src/components/sqm-partner-info-modal/sqm-partner-info-modal.feature new file mode 100644 index 0000000000..4ebc4d05aa --- /dev/null +++ b/packages/mint-components/src/components/sqm-partner-info-modal/sqm-partner-info-modal.feature @@ -0,0 +1,158 @@ +@owner:andy @author:andy +Feature: Partner Info Modal — country, currency, and T&C collection + As a cash-program operator + I want a participant's country, currency, and Tax-&-Banking consent collected + before they can access cash features + So that the partner record created in impact.com is valid from the start. + + Background: + Given a participant whose `impactConnection.connectionStatus` is "NOT_STARTED" + + @motivating + Scenario: Modal is shown when the connection has not been started + Given the user data has loaded + And `impactConnection.connectionStatus` is "NOT_STARTED" + Then the partner-info modal is open + + @motivating + Scenario: Modal is hidden once the connection has been started + Given `impactConnection.connectionStatus` does not equal "NOT_STARTED" + Then the partner-info modal is not rendered + + @minutia + Scenario: Modal is hidden while user data is still loading + Given the GET_USER_PARTNER_INFO query has not resolved yet + Then the partner-info modal is not rendered + + @minutia + Scenario: Modal cannot be dismissed by the user + Given the partner-info modal is open + When the user presses Escape, clicks the overlay, or otherwise tries to close the dialog + Then the close is prevented (sl-request-close + sl-hide are intercepted) + And the modal stays open until a successful submission + + @motivating + Scenario Outline: Country defaults to the locale-derived country code for a new participant + Given the participant has no linked Impact publisher + And the resolved locale is + Then the "Country" select is prefilled with + And the "Country" select is enabled + And the "Currency" select starts empty and is enabled + + Examples: + | locale | countryCode | + | en_US | US | + | fr_CA | CA | + | en_GB | GB | + + @motivating + Scenario: New participant with no connection + Given a new participant with no linked publisher + When they open the "Country" select + Then they see only countries returned by `impactPayoutCountries` + When they select a country + Then the "Currency" select is reset to empty + And the "Currency" select shows only currencies supported by the selected country + + @motivating + Scenario: User has existing partner and modal prefills country + currency + Given the participant is already linked to an Impact publisher + And that publisher has a `countryCode` of "US" and a `currency` of "USD" + When the modal opens + Then the "Country" select is prefilled with "US" and disabled + And the "Currency" select is prefilled with "USD" and disabled + And the modal header uses the existing-partner copy + And the description uses the existing-partner copy (including the support email link) + And the primary button label uses the existing-partner "Confirm" copy + + @motivating + Scenario: Existing-partner with linked publisher missing country/currency falls back to the new-partner defaults + Given the participant is linked to an existing publisher + But the publisher has no `countryCode` or `currency` + Then the "Country" select defaults to the locale-derived country code (and is enabled) + And the "Currency" select starts empty (and is enabled) + And the "Confirm" button submits the locale/user selections via `startImpactConnection` + + @motivating + Scenario: Tax-and-banking checkbox is required to submit + Given the partner-info modal is open + And the "Country" select has a value + And the "Currency" select has a value + But the "Tax and banking" checkbox is unchecked + Then the primary button is disabled + When the user checks the "Tax and banking" checkbox + Then the primary button becomes enabled + + @minutia + Scenario: Submit button is disabled + Given the partner-info modal is open + When any one of (country, currency, allowBankingCollection) is missing + Then the submit button is disabled + + @motivating + Scenario: Submitting the modal starts the Impact connection and closes the modal + Given the user has selected country "US", currency "USD", and checked the T&C checkbox + When they click the primary button + Then the modal calls the `startImpactConnection` mutation with + | user.id | the participant id | + | user.accountId | the participant accountId | + | firstName | from the user record | + | lastName | from the user record | + | countryCode | "US" | + | currency | "USD" | + And on success `impactConnection.connectionStatus` changes from "NOT_STARTED" to "STARTED" + And the modal closes + + @motivating + Scenario: Existing-partner submission attaches the participant to the linked publisher + Given the participant is linked to an existing publisher with country "CA" and currency "CAD" + And the T&C checkbox is checked + When they click "Confirm" + Then `startImpactConnection` is called with the publisher's country "CA" and currency "CAD" + And on success the connection advances to "STARTED" and the modal closes + + @motivating + Scenario Outline: Submission failure leaves the modal open with a recoverable error + Given the partner-info modal is open with all fields valid + When `startImpactConnection` fails with + Then the modal stays open + And `error` is set to + And the an impactConnection is not created + + Examples: + | failure | errorText | + | a thrown / network error | the `networkErrorText` prop | + | a result with `success: false` + errors | the joined `validationErrors[].message` strings | + | a result with `success: false` no errors | the `networkErrorText` prop | + + @minutia + Scenario: All inputs are disabled while submitting + Given the partner-info modal is open + When the user clicks the primary button and the mutation is in flight + Then the "Country", "Currency", and "Tax and banking" inputs are all disabled + And the primary button shows a loading spinner + + @minutia + Scenario: Country select has a search input that filters the displayed countries + Given the "Country" select is open + When the user types into the search input + Then the displayed countries is filtered to countries whose `displayName` contains the search text (case-insensitive) + + @minutia + Scenario: Currency select has a search input that filters the displayed currencies + Given the "Currency" select is open + When the user types into the search input + Then the displayed currencies is filtered to currencies whose `currencyCode` contains the search text (case-insensitive) + + @motivating + Scenario Outline: A user is filling out the form and selects their currency + Given a user with countryCode + When they open the currency dropdown + Then only are displayed + And USD, AUD, EUR, GBP are displayed regardless of country code + + Examples: + | countryCode | currencies | + | USA | USD, AUD, EUR, GBP | + | CAN | USD, AUD, EUR, GBP, CAN | + | IND | USD, AUD, EUR, GBP, INR | diff --git a/packages/mint-components/src/components/sqm-partner-info-modal/sqm-partner-info-modal.tsx b/packages/mint-components/src/components/sqm-partner-info-modal/sqm-partner-info-modal.tsx new file mode 100644 index 0000000000..96a266f67e --- /dev/null +++ b/packages/mint-components/src/components/sqm-partner-info-modal/sqm-partner-info-modal.tsx @@ -0,0 +1,271 @@ +import { isDemo } from "@saasquatch/component-boilerplate"; +import { useState, withHooks } from "@saasquatch/stencil-hooks"; +import { Component, Prop, h } from "@stencil/core"; +import deepmerge from "deepmerge"; +import { DemoData } from "../../global/demo"; +import { parseStates } from "../../utils/parseStates"; +import { getProps } from "../../utils/utils"; +import { + PartnerInfoModalContentView, + PartnerInfoModalView, + PartnerInfoModalViewProps, +} from "./sqm-partner-info-modal-view"; +import { + PartnerInfoModalResult, + usePartnerInfoModal, +} from "./usePartnerInfoModal"; + +/** + * @uiName Partner Info Modal + * @exampleGroup Tax and Cash + * @validParents ["sqm-portal-container", "sqm-portal-frame", "div", "sqm-divided-layout", "sqb-program-section", "sqb-conditional-section"] + * @example Partner Info Modal - + * @hidden + */ +@Component({ + tag: "sqm-partner-info-modal", + shadow: true, +}) +export class PartnerInfoModal { + /** + * Header text when user has no existing partner + * + * @uiName New partner header + * @uiWidget textArea + */ + @Prop() + modalHeader: string = "Let's get you ready for rewards"; + + /** + * Header text when user has an existing partner + * + * @uiName Existing partner header + * @uiWidget textArea + */ + @Prop() + modalHeaderExistingPartner: string = "We found an existing account"; + + /** + * Description for new partner setup + * + * @uiName New partner description + * @uiWidget textArea + */ + @Prop() + descriptionNewPartner: string = + "Confirm your country and currency now to get your future rewards faster."; + + /** + * Description for existing partner confirmation + * + * @uiName Existing partner description + * @uiWidget textArea + */ + @Prop() + descriptionExistingPartner: string = + "We found an account with this email on our referral program provider, impact.com. Please confirm your country and currency now to get your future rewards faster."; + + /** + * Support description for existing partner confirmation + * + * @uiName Existing partner support description + * @uiWidget textArea + */ + @Prop() + supportDescriptionExistingPartner: string = + "If this is a mistake, please contact {supportLink} or sign up for this referral program with a different email."; + + /** + * Support description for existing partner confirmation + * + * @uiName Existing partner support description + */ + @Prop() + supportLink: string = "Support"; + + /** + * Edit the property called terms and conditions text to change what's displayed for {termsAndConditionsLink}. + * @uiName Terms and conditions checkbox + */ + @Prop() allowBankingCollection: string = + "I have read the {termsAndConditionsLink} and allow impact.com to collect my tax and banking information."; + /** + * The link text that appears in the terms and conditions checkbox + * @uiName Terms and conditions text + * @uiWidget textArea + */ + @Prop() termsAndConditionsLabel: string = "terms and conditions"; + /** + * The link that appears in the terms and conditions checkbox + * @uiName Terms and conditions link + */ + @Prop() termsAndConditionsLink: string = + "https://terms.advocate.impact.com/PayoutTermsAndConditions.html"; + /** + * @uiName Country label + */ + @Prop() + countryLabel: string = "Country"; + + /** + * @uiName Currency label + */ + @Prop() + currencyLabel: string = "Currency"; + + /** + * @uiName Submit button label + */ + @Prop() + submitButtonLabel: string = "Submit"; + + /** + * @uiName Confirm button label + */ + @Prop() + confirmButtonLabel: string = "Confirm"; + + /** + * @uiName Search country placeholder + */ + @Prop() + searchCountryPlaceholder: string = "Search for a country"; + + /** + * @uiName Search currency placeholder + */ + @Prop() + searchCurrencyPlaceholder: string = "Search for a currency"; + + /** + * @uiName Network error text + * @uiWidget textArea + */ + @Prop() + networkErrorText: string = "An error occurred. Please try again."; + + /** + * @uiName Missing fields error text + * @uiWidget textArea + */ + @Prop() + missingFieldsErrorText: string = "Please fill out all fields."; + + /** + * Used to render in another modal. + * @undocumented + */ + @Prop() inModal: boolean = false; + + /** + * @undocumented + * @componentState { "title": "Connected (hidden)", "default": true, "props": { "states": { "open": false } } } + * @componentState { "title": "New partner", "props": { "states": { "open": true, "isExistingPartner": false } } } + * @componentState { "title": "Existing partner", "props": { "states": { "open": true, "isExistingPartner": true, "countryCode": "US", "currency": "USD" } } } + */ + @Prop() stateController: string = + '{"sqm-partner-info-modal":{"states":{"open":false}}}'; + + /** + * @undocumented + * @uiType object + */ + @Prop() demoData?: DemoData; + + constructor() { + withHooks(this); + } + disconnectedCallback() {} + + getTextProps() { + return getProps(this); + } + + render() { + const props = isDemo() + ? useDemoPartnerInfoModal(this) + : usePartnerInfoModal(this); + + if (this.inModal) { + return ; + } + + return ; + } +} + +function useDemoPartnerInfoModal( + props: PartnerInfoModal, +): PartnerInfoModalViewProps { + const [countryCode, setCountryCode] = useState("US"); + const [currency, setCurrency] = useState(""); + const [error, setError] = useState(""); + const [allowBankingCollection, setAllowBankingCollection] = useState(false); + + const parsed = parseStates(props.stateController); + const stateOverride = parsed?.["sqm-partner-info-modal"] || parsed || {}; + + // @ts-ignore + return deepmerge( + { + states: { + open: false, + loading: false, + submitting: false, + isExistingPartner: false, + countryCode, + currency, + error, + success: false, + filteredCountries: [], + filteredCurrencies: [], + allowBankingCollection, + disabled: false, + }, + callbacks: { + onCountryChange: (e: any) => { + const value = e?.detail?.item?.__value; + if (value) { + setCountryCode(value); + setCurrency(""); + } + }, + onCurrencyChange: (e: any) => { + const value = e?.detail?.item?.__value; + if (value) setCurrency(value); + }, + onCheckboxChange: (e: any) => { + setAllowBankingCollection(e.target.checked); + }, + setCountrySearch: () => {}, + setCurrencySearch: () => {}, + onSubmit: () => { + if (!countryCode || !currency) { + setError(props.missingFieldsErrorText); + return; + } + setError(""); + }, + onClose: () => {}, + onInitialFocus: (e: any) => e.preventDefault(), + }, + text: { + modalHeader: props.modalHeader, + descriptionNewPartner: props.descriptionNewPartner, + descriptionExistingPartner: props.descriptionExistingPartner, + countryLabel: props.countryLabel, + currencyLabel: props.currencyLabel, + submitButtonLabel: props.submitButtonLabel, + confirmButtonLabel: props.confirmButtonLabel, + searchCountryPlaceholder: props.searchCountryPlaceholder, + searchCurrencyPlaceholder: props.searchCurrencyPlaceholder, + supportDescriptionExistingPartner: + props.supportDescriptionExistingPartner, + supportLink: props.supportLink, + modalHeaderExistingPartner: props.modalHeaderExistingPartner, + }, + }, + props.demoData || stateOverride, + { arrayMerge: (_, a) => a }, + ); +} diff --git a/packages/mint-components/src/components/sqm-partner-info-modal/usePartnerInfoModal.tsx b/packages/mint-components/src/components/sqm-partner-info-modal/usePartnerInfoModal.tsx new file mode 100644 index 0000000000..f6ba8572e8 --- /dev/null +++ b/packages/mint-components/src/components/sqm-partner-info-modal/usePartnerInfoModal.tsx @@ -0,0 +1,349 @@ +import { + useLocale, + useMutation, + useQuery, + useSetParent, +} from "@saasquatch/component-boilerplate"; +import { useEffect, useMemo, useState } from "@saasquatch/universal-hooks"; +import { gql } from "graphql-request"; +import { PartnerInfoModal } from "./sqm-partner-info-modal"; +import { PartnerInfoModalViewProps } from "./sqm-partner-info-modal-view"; +import { StartImpactConnectionResult } from "../tax-and-cash/sqm-indirect-tax-form/useIndirectTaxForm"; +import { TAX_FORM_UPDATED_EVENT_KEY } from "../tax-and-cash/eventKeys"; +import { PARTNER_CREATED_NAMESPACE } from "../sqm-widget-verification/keys"; +import { + GET_FINANCE_NETWORK_SETTINGS, + FinanceNetworkSettingsQuery, +} from "../tax-and-cash/data"; + +export const GET_USER_PARTNER_INFO = gql` + query getUserPartnerInfo { + user: viewer { + ... on User { + id + accountId + firstName + lastName + email + countryCode + customFields + impactConnection { + connected + connectionStatus + publisher { + id + countryCode + currency + } + } + } + } + } +`; + +export const GET_COUNTRIES = gql` + query getCountries { + impactPayoutCountries(limit: 1000) { + data { + countryCode + displayName + } + } + } +`; + +export const GET_CURRENCIES = gql` + query currencies($locale: RSLocale) { + currencies(limit: 300) { + data { + displayName(locale: $locale) + currencyCode + } + } + } +`; + +const START_IMPACT_CONNECTION = gql` + mutation startImpactConnection($vars: ImpactConnectionInput!) { + startImpactConnection(impactConnectionInput: $vars) { + success + validationErrors { + field + message + } + user { + id + accountId + impactConnection { + connected + publisher { + brandedSignup + requiredTaxDocumentType + currentTaxDocument { + type + status + } + } + } + } + } + } +`; + +export type TaxCountry = { + countryCode: string; + displayName: string; +}; + +export type CountriesQuery = { + impactPayoutCountries: { + data: TaxCountry[]; + }; +}; + +export function usePartnerInfoModal( + props: PartnerInfoModal, +): PartnerInfoModalViewProps { + const locale = useLocale(); + + const setPartnerCreated = useSetParent(PARTNER_CREATED_NAMESPACE); + + const { + data: userData, + loading: userLoading, + refetch, + } = useQuery(GET_USER_PARTNER_INFO, {}); + + const user = userData?.user; + + const { data: currenciesData, loading: currenciesLoading } = useQuery( + GET_CURRENCIES, + { variables: { locale } }, + ); + + const { data: countriesData, loading: countriesLoading } = useQuery( + GET_COUNTRIES, + {}, + ); + + // No pre-filled country, use locale to determine countryCode instead + const [countryCode, setCountryCode] = useState( + user?.impactConnection?.publisher?.countryCode || + locale.replace(/^.*_/, ""), + ); + + const [currency, setCurrency] = useState( + user?.impactConnection?.publisher?.currency || "", + ); + + const { data: financeNetworkData } = useQuery( + GET_FINANCE_NETWORK_SETTINGS, + { + variables: { filter: countryCode ? { countryCode_eq: countryCode } : {} }, + }, + ); + + const [startImpactConnection, { loading: connectLoading }] = + useMutation(START_IMPACT_CONNECTION); + + const [allowBankingCollection, setAllowBankingCollection] = useState(false); + + const countries = useMemo(() => { + const data = countriesData?.impactPayoutCountries?.data; + if (!data) return []; + return [...data].sort((a: TaxCountry, b: TaxCountry) => { + if (a.countryCode === "US") return -1; + if (b.countryCode === "US") return 1; + return a.displayName.localeCompare(b.displayName); + }); + }, [countriesData]); + + const isExistingPartner = !!user?.impactConnection?.publisher; + + const _currencies = useMemo(() => { + // if isExistingPartner, grab all currencies since select is disabled anyways + if (isExistingPartner) { + return currenciesData?.currencies?.data || []; + } + + const allValidCurrencies = + financeNetworkData?.impactFinanceNetworkSettings?.data?.reduce( + (agg, settings) => { + if (countryCode && settings.countryCode !== countryCode) return agg; + const c = currenciesData?.currencies?.data?.find( + (cur) => cur.currencyCode === settings.currency, + ); + if (!c) return agg; + if (agg.find((cur) => cur.currencyCode === settings.currency)) + return agg; + return [...agg, c]; + }, + [], + ); + return allValidCurrencies || []; + }, [financeNetworkData, currenciesData, countryCode, isExistingPartner]); + + const currencies = useMemo( + () => + [..._currencies].sort((a, b) => + a.displayName.localeCompare(b.displayName), + ), + [_currencies], + ); + + const [countrySearch, setCountrySearch] = useState(""); + const [currencySearch, setCurrencySearch] = useState(""); + const [filteredCountries, setFilteredCountries] = useState(countries || []); + const [filteredCurrencies, setFilteredCurrencies] = useState( + currencies || [], + ); + + const [error, setError] = useState(""); + const [success, setSuccess] = useState(false); + + useEffect(() => { + const publisher = user?.impactConnection?.publisher; + if (!userData || !publisher) return; + setCountryCode(publisher.countryCode); + setCurrency(publisher.currency); + }, [userData, user]); + + useEffect(() => { + if (!countries?.length) return; + if (countrySearch.trim() === "") { + setFilteredCountries(countries || []); + } else { + setFilteredCountries( + countries.filter((c) => + c.displayName.toLowerCase().includes(countrySearch.toLowerCase()), + ) || [], + ); + } + }, [countrySearch, countries]); + + useEffect(() => { + if (!currencies?.length) return; + if (currencySearch.trim() === "") { + setFilteredCurrencies(currencies || []); + } else { + setFilteredCurrencies( + currencies.filter((c) => + c.currencyCode.toLowerCase().includes(currencySearch.toLowerCase()), + ) || [], + ); + } + }, [currencySearch, currencies]); + + const impactConnection = user?.impactConnection; + + function onCountryChange(e: any) { + const value = e.detail?.item?.__value; + if (!value) return; + setCountryCode(value); + setCurrency(""); + setError(""); + } + + function onCurrencyChange(e: any) { + const value = e.detail?.item?.__value; + if (!value) return; + setCurrency(value); + setError(""); + } + + function onCheckboxChange(e: any) { + const checked = e.target.checked; + setAllowBankingCollection(checked); + } + + async function onSubmit() { + if (!allowBankingCollection || !countryCode || !currency) { + setError(props.missingFieldsErrorText); + return; + } + setError(""); + + if (!user) { + setError(props.networkErrorText); + return; + } + + try { + const vars = { + user: { + id: user.id, + accountId: user.accountId, + }, + firstName: user.firstName, + lastName: user.lastName, + countryCode, + currency, + }; + + const result = await startImpactConnection({ vars }); + + if (!result || (result as Error)?.message) { + setError(props.networkErrorText); + return; + } + + const connectionResult = (result as StartImpactConnectionResult) + .startImpactConnection; + + if (!connectionResult?.success) { + const validationMsg = connectionResult?.validationErrors + ?.map((e) => e.message) + .join(". "); + setError(validationMsg || props.networkErrorText); + console.error( + "Failed to create Impact connection:", + connectionResult?.validationErrors, + ); + return; + } + + window.dispatchEvent(new Event(TAX_FORM_UPDATED_EVENT_KEY)); + + await refetch(); + setPartnerCreated?.(true); + setSuccess(true); + } catch (e) { + console.error("Partner creation error:", e); + setError(props.networkErrorText); + } + } + + const showModal = + !success && + !userLoading && + impactConnection?.connectionStatus === "NOT_STARTED"; + + return { + states: { + open: showModal, + loading: userLoading || countriesLoading || currenciesLoading, + submitting: connectLoading, + isExistingPartner, + countryCode, + currency, + error, + success, + filteredCountries: filteredCountries || [], + filteredCurrencies: filteredCurrencies || [], + allowBankingCollection, + disabled: userLoading || connectLoading, + }, + callbacks: { + onCountryChange, + onCurrencyChange, + onCheckboxChange, + setCurrencySearch, + setCountrySearch, + onSubmit, + onClose: () => setSuccess(true), + }, + text: props.getTextProps(), + }; +} + +export type PartnerInfoModalResult = ReturnType; diff --git a/packages/mint-components/src/components/sqm-referral-table/ReferralTable.stories.tsx b/packages/mint-components/src/components/sqm-referral-table/ReferralTable.stories.tsx index 7acb343e81..40f822f22f 100644 --- a/packages/mint-components/src/components/sqm-referral-table/ReferralTable.stories.tsx +++ b/packages/mint-components/src/components/sqm-referral-table/ReferralTable.stories.tsx @@ -103,6 +103,7 @@ const baseReward: Reward = { }, ], }, + rewardedCash: false, }; // Reward Status Cases diff --git a/packages/mint-components/src/components/sqm-referral-table/ReferralTableRewardsCell.stories.tsx b/packages/mint-components/src/components/sqm-referral-table/ReferralTableRewardsCell.stories.tsx index a8d99a611d..e2e312df87 100644 --- a/packages/mint-components/src/components/sqm-referral-table/ReferralTableRewardsCell.stories.tsx +++ b/packages/mint-components/src/components/sqm-referral-table/ReferralTableRewardsCell.stories.tsx @@ -88,6 +88,7 @@ const cashPayoutSentReward: Reward = { statuses: ["AVAILABLE"], globalRewardKey: "Key", rewardRedemptionTransactions: null, + rewardedCash: true, }; const nullExpiresIn = { diff --git a/packages/mint-components/src/components/sqm-referral-table/TaxAndCashReferralTable.stories.tsx b/packages/mint-components/src/components/sqm-referral-table/TaxAndCashReferralTable.stories.tsx index 02468d93df..6a3c51c092 100644 --- a/packages/mint-components/src/components/sqm-referral-table/TaxAndCashReferralTable.stories.tsx +++ b/packages/mint-components/src/components/sqm-referral-table/TaxAndCashReferralTable.stories.tsx @@ -12,7 +12,8 @@ import { PendingTaxReview, PendingNewTaxForm, PendingTaxSubmission, - PendingPartnerCreation, + PartnerNotCreatedSetupNotStarted, + PartnerCreatedSetupStartedButIncomplete, PendingW9, CashReward, } from "./TaxAndCashReferralTableRewardsCell.stories"; @@ -94,7 +95,7 @@ const taxAndCashTableProps = { , , , - , + , ], [ , diff --git a/packages/mint-components/src/components/sqm-referral-table/TaxAndCashReferralTableRewardsCell.stories.tsx b/packages/mint-components/src/components/sqm-referral-table/TaxAndCashReferralTableRewardsCell.stories.tsx index 1aac4f4307..f7b9da8979 100644 --- a/packages/mint-components/src/components/sqm-referral-table/TaxAndCashReferralTableRewardsCell.stories.tsx +++ b/packages/mint-components/src/components/sqm-referral-table/TaxAndCashReferralTableRewardsCell.stories.tsx @@ -1,18 +1,13 @@ import { h } from "@stencil/core"; -import { DateTime } from "luxon"; import { Reward, ImpactConnection } from "../../saasquatch"; export default { title: "Components/Tax And Cash Referral Table Rewards Cell", }; -function getDays() { - return DateTime.now().toMillis() + 600000000; -} - -function getMonths() { - return DateTime.now().toMillis() + 10000000000; -} +// Reusable timestamps (current date is May 2026) +const PAST = 1640995200000; // Jan 1, 2022 +const FUTURE = 2779257600000; // ~2058 const cashReward: Reward = { id: "1234", @@ -20,251 +15,366 @@ const cashReward: Reward = { value: 50, unit: "USD", name: "test", - dateScheduledFor: getDays(), - dateExpires: getMonths(), - dateCancelled: 134400, - dateRedeemed: 0, + dateScheduledFor: null, + dateExpires: null, + dateCancelled: null, + dateRedeemed: null, fuelTankCode: null, fuelTankType: null, currency: "USD", prettyValue: "$50.00", - statuses: ["AVAILABLE"], + statuses: [], globalRewardKey: "Key", rewardRedemptionTransactions: null, partnerFundsTransfer: null, + rewardedCash: true, }; -const taxConnection: ImpactConnection = { +// ============================================================ +// Reusable tax connections +// ============================================================ + +const fullySetupTaxConnection: ImpactConnection = { connected: true, taxHandlingEnabled: true, publisher: { requiredTaxDocumentType: "W9", + currentTaxDocument: { + status: "ACTIVE", + type: "W9", + dateCreated: PAST, + }, withdrawalSettings: { paymentMethod: "BANK_TRANSFER" }, payoutsAccount: null, + }, +}; + +const connectedNoWithdrawalSettings: ImpactConnection = { + connected: true, + taxHandlingEnabled: true, + publisher: { + requiredTaxDocumentType: "W9", currentTaxDocument: { status: "ACTIVE", type: "W9", - dateCreated: 1627427794891, + dateCreated: PAST, }, + withdrawalSettings: null, + payoutsAccount: null, }, }; -const defaultPFT: Reward["partnerFundsTransfer"] = { - id: "ID1234", - status: null, - dateCreated: null, - dateScheduled: null, - dateTransferred: null, +const notConnectedTaxConnection: ImpactConnection = { + connected: false, + taxHandlingEnabled: true, + publisher: null, }; -const defaultTaxDocument: ImpactConnection["publisher"]["currentTaxDocument"] = - { - status: "NOT_VERIFIED", - type: "W9", - dateCreated: DateTime.now().toMillis() - 1000000, - }; - const defaultProps = { statusText: "{status, select, AVAILABLE {Available} CANCELLED {Cancelled} PENDING {Pending} PENDING_REVIEW {Pending} PAYOUT_APPROVED {Payout Approved} PROCESSING {Payment Processing} PAYOUT_FAILED {Payout Failed} PAYOUT_CANCELLED {Payout Cancelled} PENDING_TAX_REVIEW {Pending} PENDING_NEW_TAX_FORM {Pending} PENDING_TAX_SUBMISSION {Pending} PENDING_PARTNER_CREATION {Pending} DENIED {Denied} EXPIRED {Expired} REDEEMED {Redeemed} other {Not available} }", statusLongText: - "{status, select, AVAILABLE {Reward expiring on} CANCELLED {Reward cancelled on} PENDING {Available on} PENDING_REVIEW {Pending since} PAYOUT_APPROVED {Processing until {scheduledPayoutDate}. Payout is then scheduled based on your settings.} PAYOUT_FAILED {Payout failed due to a fulfillment issue and is currently being retried.} PAYOUT_CANCELLED {If you think this is a mistake, contact our Support team.} PENDING_TAX_REVIEW {Awaiting tax form review} PENDING_NEW_TAX_FORM {Invalid tax form. Submit a new form to receive your rewards.} PROCESSING {Processing until {scheduledPayoutDate}. Payout is then scheduled based on your settings.} PENDING_TAX_SUBMISSION {Submit your tax documents to receive your rewards} PENDING_PARTNER_CREATION {Complete your tax and cash payout setup to receive your rewards} DENIED {Denied on} EXPIRED {Reward expired on} other {Not available} }", + "{status, select, AVAILABLE {Reward expiring on} CANCELLED {Reward cancelled on} PENDING {Available on} PENDING_REVIEW {Pending since} PAYOUT_APPROVED {Processing until {scheduledPayoutDate}. Payout is then scheduled based on your settings.} PAYOUT_FAILED {Payout failed due to a fulfillment issue and is currently being retried.} PAYOUT_CANCELLED {If you think this is a mistake, contact our Support team.} PENDING_TAX_REVIEW {Awaiting tax form review} PENDING_NEW_TAX_FORM {Invalid tax form. Submit a new form to receive your rewards.} PROCESSING {Processing until {scheduledPayoutDate}. Payout is then scheduled based on your settings.} PENDING_TAX_SUBMISSION {Submit your tax documents to receive your rewards} PENDING_PARTNER_CREATION {Complete cash payout setup to receive your rewards} DENIED {Denied on} EXPIRED {Reward expired on} other {Not available} }", rewardReceivedText: "Reward received on", hideDetails: false, }; -export const CashReward = () => { - return ( - - ); -}; +// ============================================================ +// STATE PRECEDENCE LADDER — one story per rule +// (See referral-table-rewards-column-new.feature § 2) +// ============================================================ -export const PayoutApproved = () => { - return ( - - ); -}; +// 1. Fraud check denied the referral → state is DENIED +export const RewardsCellFraudDenied = () => ( + +); -export const PayoutProcessing = () => { - return ( - - ); -}; +// 2. Fraud check is still pending review → state is PENDING_REVIEW +export const RewardsCellFraudPendingReview = () => ( + +); -export const PayoutFailed = () => { - return ( - - ); -}; +// 3a. Cash reward but the user has not connected an Impact partner → shows the partner-setup prompt +export const RewardsCellPartnerNotCreatedSetupNotStarted = () => ( + +); -export const PayoutCancelled = () => { - return ( - ( + - ); -}; + }, + ]} + taxConnection={connectedNoWithdrawalSettings} + {...defaultProps} + > +); -export const PendingTaxReview = () => { - return ( - ( + - ); -}; + }, + ]} + taxConnection={fullySetupTaxConnection} + {...defaultProps} + > +); -export const PendingNewTaxForm = () => { - return ( - ( + - ); -}; + }, + ]} + taxConnection={fullySetupTaxConnection} + {...defaultProps} + > +); -export const PendingTaxSubmission = () => { - return ( - ( + +); + +// 7. Partner funds transfer has been transferred → state is PAYOUT_APPROVED +export const RewardsCellPayoutApproved = () => ( + - ); -}; + }, + ]} + taxConnection={fullySetupTaxConnection} + {...defaultProps} + > +); -export const PendingPartnerCreation = () => { - return ( - ( + +); + +// 9. Reward is pending US_TAX and the partner is not connected → shows the partner-setup prompt +export const RewardsCellUsTaxPartnerNotConnected = () => ( + +); + +// 10. Partner is connected but has not submitted any tax documents → shows the submit-tax-docs prompt +export const RewardsCellPendingTaxSubmission = () => ( + +); + +// 11. Partner's tax document is inactive (rejected) → shows the submit-a-new-form prompt +export const RewardsCellPendingNewTaxForm = () => ( + - ); -}; + }, + }} + {...defaultProps} + > +); -export const PendingW9 = () => { - return ( - ( + - ); -}; + }, + }} + {...defaultProps} + > +); + +// 13. Partner has an active tax form but has not configured withdrawal settings → shows the partner-setup prompt +export const RewardsCellExistingTaxFormNoWithdrawalSettings = () => ( + +); + +// 14. Reward has MISSING_PAYOUT_CONFIGURATION pending reason → shows the partner-setup prompt +export const RewardsCellMissingPayoutConfiguration = () => ( + +); + +// 18. Reward does not match any precedence rule → state is undefined (Not available) +export const RewardsCellNoMatchingRule = () => ( + +); + +// ============================================================ +// Aliases kept for backwards compatibility with existing references +// ============================================================ + +export const CashReward = RewardsCellPayoutApproved; +export const PayoutApproved = RewardsCellPayoutApproved; +export const PayoutProcessing = RewardsCellPayoutProcessing; +export const PayoutFailed = RewardsCellPayoutFailed; +export const PayoutCancelled = RewardsCellPayoutCancelled; +export const PendingTaxReview = RewardsCellPendingTaxReview; +export const PendingNewTaxForm = RewardsCellPendingNewTaxForm; +export const PendingTaxSubmission = RewardsCellPendingTaxSubmission; +export const PartnerNotCreatedSetupNotStarted = + RewardsCellPartnerNotCreatedSetupNotStarted; +export const PartnerCreatedSetupStartedButIncomplete = + RewardsCellPartnerCreatedSetupStartedButIncomplete; +export const PendingW9 = RewardsCellPendingW9Required; diff --git a/packages/mint-components/src/components/sqm-referral-table/cells/sqm-referral-table-rewards-cell.tsx b/packages/mint-components/src/components/sqm-referral-table/cells/sqm-referral-table-rewards-cell.tsx index 45478a51a7..a02ea60a24 100644 --- a/packages/mint-components/src/components/sqm-referral-table/cells/sqm-referral-table-rewards-cell.tsx +++ b/packages/mint-components/src/components/sqm-referral-table/cells/sqm-referral-table-rewards-cell.tsx @@ -17,7 +17,7 @@ export class ReferralTableRewardsCell { @Prop() statusText: string = "{status, select, AVAILABLE {Available} CANCELLED {Cancelled} PENDING {Pending} PENDING_REVIEW {Pending} PAYOUT_APPROVED {Payout Approved} PROCESSING {Payment Processing} PAYOUT_FAILED {Payout Failed} PAYOUT_CANCELLED {Payout Cancelled} PENDING_TAX_REVIEW {Pending} PENDING_NEW_TAX_FORM {Pending} PENDING_TAX_SUBMISSION {Pending} PENDING_PARTNER_CREATION {Pending} DENIED {Denied} EXPIRED {Expired} REDEEMED {Redeemed} other {Not available} }"; @Prop() statusLongText: string = - "{status, select, AVAILABLE {Reward expiring on} CANCELLED {Reward cancelled on} PENDING {Available on} PENDING_REVIEW {Pending since} PAYOUT_APPROVED {Processing until {scheduledPayoutDate}. Payout is then scheduled based on your settings.} PAYOUT_FAILED {Payout failed due to a fulfillment issue and is currently being retried.} PAYOUT_CANCELLED {If you think this is a mistake, contact our Support team.} PENDING_TAX_REVIEW {Awaiting tax form review} PENDING_NEW_TAX_FORM {Invalid tax form. Submit a new form to receive your rewards.} PROCESSING {Processing until {scheduledPayoutDate}. Payout is then scheduled based on your settings.} PENDING_TAX_SUBMISSION {Submit your tax documents to receive your rewards} PENDING_PARTNER_CREATION {Complete your tax and cash payout setup to receive your rewards} DENIED {Denied on} EXPIRED {Reward expired on} other {Not available} }"; + "{status, select, AVAILABLE {Reward expiring on} CANCELLED {Reward cancelled on} PENDING {Available on} PENDING_REVIEW {Pending since} PAYOUT_APPROVED {Processing until {scheduledPayoutDate}. Payout is then scheduled based on your settings.} PAYOUT_FAILED {Payout failed due to a fulfillment issue and is currently being retried.} PAYOUT_CANCELLED {If you think this is a mistake, contact our Support team.} PENDING_TAX_REVIEW {Awaiting tax form review} PENDING_NEW_TAX_FORM {Invalid tax form. Submit a new form to receive your rewards.} PROCESSING {Processing until {scheduledPayoutDate}. Payout is then scheduled based on your settings.} PENDING_TAX_SUBMISSION {Submit your tax documents to receive your rewards} PENDING_PARTNER_CREATION {Complete your cash payout setup to receive your rewards} DENIED {Denied on} EXPIRED {Reward expired on} other {Not available} }"; @Prop() fuelTankText: string; @Prop() rewardReceivedText: string; @Prop() expiringText: string; @@ -171,12 +171,24 @@ export class ReferralTableRewardsCell { return "DENIED"; } + const isCashReward = reward.rewardedCash; + if (isCashReward) { + if ( + !this.taxConnection?.connected || + (this.taxConnection?.connected && + !this.taxConnection?.publisher?.withdrawalSettings) + ) + return "PENDING_PARTNER_CREATION"; + } + const partnerFundsStatus = reward.partnerFundsTransfer?.status; if (reward.partnerFundsTransfer) { if (partnerFundsStatus === "REVERSED") return "PAYOUT_CANCELLED"; if (partnerFundsStatus === "OVERDUE") return "PAYOUT_FAILED"; + } + if (reward.partnerFundsTransfer) { if ( reward.partnerFundsTransfer.dateScheduled && reward.partnerFundsTransfer.dateScheduled > Date.now() @@ -213,6 +225,19 @@ export class ReferralTableRewardsCell { if (!taxConnection?.publisher?.withdrawalSettings) return "PENDING_PARTNER_CREATION"; } + + // Fallback: when rewardStatus() forced PENDING because the user is + // connected but hasn't set up withdrawal settings (no pendingReasons + // were returned by the API, e.g. for credit rewards with a PFT). + if (reward.rewardedCash) { + if ( + !taxConnection?.connected || + (taxConnection?.connected && + !taxConnection?.publisher?.withdrawalSettings) + ) + return "PENDING_PARTNER_CREATION"; + } + if (reward?.pendingReasons?.includes("MISSING_PAYOUT_CONFIGURATION")) { return "PENDING_PARTNER_CREATION"; } @@ -347,7 +372,7 @@ export class ReferralTableRewardsCell { )} - {state === "PAYOUT_APPROVED" && ( + {state === "" && (
{statusText}
@@ -357,6 +382,11 @@ export class ReferralTableRewardsCell { {statusText} )} + {state === "PAYOUT_APPROVED" && ( +
+ {statusText} +
+ )} {state === "PAYOUT_FAILED" && (
{statusText} diff --git a/packages/mint-components/src/components/sqm-referral-table/columns/referral-table-rewards-column.feature b/packages/mint-components/src/components/sqm-referral-table/columns/referral-table-rewards-column.feature index 982a5d960f..0f682645d2 100644 --- a/packages/mint-components/src/components/sqm-referral-table/columns/referral-table-rewards-column.feature +++ b/packages/mint-components/src/components/sqm-referral-table/columns/referral-table-rewards-column.feature @@ -2,11 +2,38 @@ Feature: Referral Table Reward Column Shows the reward associated with each referral - Background: + Background: Given the status column is included in the referral table And at least one referral exists + @motivating + Scenario Outline: State precedence ladder + Given a reward, its referral's fraud state, and the user's tax connection + Then is produced + And the , , and is determined in the following : + Examples: + | order | rule | resultingState | resultingBadgeText | resultingDescriptionBody | + | 1 | referral.fraudData.moderationStatus is "DENIED" | DENIED | Denied | Denied on Jan 1, 2026. Contact support. | + | 2 | referral.fraudData.moderationStatus is "PENDING" | PENDING_REVIEW | Pending | Pending since Jan 1, 2026 | + | 3 | reward.rewardedCash is true AND impactConnection is NOT connected | PENDING_PARTNER_CREATION | Pending | Complete your tax and cash payout setup to receive your rewards | + | 4 | reward.rewardedCash is true AND impactConnection is connected AND publisher.withdrawalSettings is missing | PENDING_PARTNER_CREATION | Pending | Complete your tax and cash payout setup to receive your rewards | + | 5 | partnerFundsTransfer.status is "REVERSED" | PAYOUT_CANCELLED | Payout Cancelled | If you think this is a mistake, contact our Support team. | + | 6 | partnerFundsTransfer.status is "OVERDUE" | PAYOUT_FAILED | Payout Failed | Payout failed due to a fulfillment issue and is currently being retried. | + | 7 | partnerFundsTransfer.dateScheduled is in the future | PROCESSING | Payment Processing | Processing until Jan 1, 2026. Payout is then scheduled based on your settings. | + | 8 | partnerFundsTransfer.status is "TRANSFERRED" / "NOT_YET_DUE", or dateScheduled has passed without REVERSED/OVERDUE | PAYOUT_APPROVED | Payout Approved | Processing until Jan 1, 2026. Payout is then scheduled based on your settings. | + | 9 | reward.pendingReasons includes "US_TAX" AND impactConnection.taxHandlingEnabled is false | PENDING | Pending | Available on Jan 1, 2026 (if dateScheduledFor set; else no body line) | + | 10 | reward.pendingReasons includes "US_TAX" AND impactConnection.connected is false | PENDING_PARTNER_CREATION | Pending | Complete your tax and cash payout setup to receive your rewards | + | 11 | reward.pendingReasons includes "US_TAX" AND publisher.requiredTaxDocumentType is set AND publisher.currentTaxDocument is missing | PENDING_TAX_SUBMISSION | Pending | Submit your tax documents to receive your rewards | + | 12 | reward.pendingReasons includes "US_TAX" AND publisher.currentTaxDocument.status is "INACTIVE" / "INVALID_W9_ELECTRONIC_DOCUMENT" / "INVALID_W9_ELECTRONIC_DOCUMENT_CHECK_INTERNAL" | PENDING_NEW_TAX_FORM | Pending | Invalid tax form. Submit a new form to receive your rewards. | + | 13 | reward.pendingReasons includes "US_TAX" AND publisher.currentTaxDocument.status is "NOT_VERIFIED" | PENDING_TAX_REVIEW | Pending | Awaiting tax form review | + | 14 | reward.pendingReasons includes "US_TAX" AND publisher.currentTaxDocument.status is "ACTIVE" AND publisher.withdrawalSettings missing | PENDING_PARTNER_CREATION | Pending | Complete your tax and cash payout setup to receive your rewards | + | 15 | reward.pendingReasons includes "MISSING_PAYOUT_CONFIGURATION" | PENDING_PARTNER_CREATION | Pending | Complete your tax and cash payout setup to receive your rewards | + | 16 | statuses includes "REDEEMED" | REDEEMED | Redeemed | (no state-specific body line) | + | 17 | statuses includes "CANCELLED" | CANCELLED | Cancelled | Reward cancelled on Jan 1, 2026 | + | 18 | statuses includes "EXPIRED" | EXPIRED | Expired | Reward expired on Jan 1, 2026 | + | 19 | statuses includes "PENDING" | PENDING | Pending (or "Pending for {date}" overlay if dateScheduledFor set) | Available on Jan 1, 2026 (if dateScheduledFor set; else no body line) | + | 20 | statuses includes "AVAILABLE" | AVAILABLE | Available (plus info pill "{expiringText} {relative}" if dateExpires set) | Reward expiring on Jan 1, 2026 | @motivating @ui Scenario Outline: The referral reward and its status are shown for each referral @@ -15,24 +42,24 @@ Feature: Referral Table Reward Column And the status of each reward is displayed as a pill in the cell And rewards of have a pill with the text - Examples: - | status | pillColour | statusText | - | Available | success | Available | - | Pending | warning | Pending | - | Pending Fraud Review | warning | Pending | - | Pending Tax Review | warning | Pending | - | Pending New Tax Form | warning | Pending | - | Pending Tax Submission | warning | Pending | - | Pending Partner Creation | warning | Pending | - | Cancelled | danger | Cancelled | - | Payout Overdue | danger | Payout Failed | - | Payout Reversed | danger | Payout Cancelled | - | Expired | danger | Expired | - | Denied | danger | Denied | - | Redeemed | primary | Redeemed | - | Payout Transferred | primary | Payout Approved | - | Payout Not Yet Due | primary | Payout Approved | - | Payout Processing | primary | Payment Processing | + Examples: + | status | pillColour | statusText | + | Available | success | Available | + | Pending | warning | Pending | + | Pending Fraud Review | warning | Pending | + | Pending Tax Review | warning | Pending | + | Pending New Tax Form | warning | Pending | + | Pending Tax Submission | warning | Pending | + | Pending Partner Creation | warning | Pending | + | Cancelled | danger | Cancelled | + | Payout Overdue | danger | Payout Failed | + | Payout Reversed | danger | Payout Cancelled | + | Expired | danger | Expired | + | Denied | danger | Denied | + | Redeemed | primary | Redeemed | + | Payout Transferred | primary | Payout Approved | + | Payout Not Yet Due | primary | Payout Approved | + | Payout Processing | primary | Payment Processing | @motivating Scenario: The pending period of a referral reward is shown inside the pill if scheduled @@ -41,11 +68,11 @@ Feature: Referral Table Reward Column Then the status pill of the reward will contain the text "Pending for " And the date is localized to the user's locale - Examples: + Examples: | relativeTime | - | 2 days | - | 1 week | - | 3 months | + | 2 days | + | 1 week | + | 3 months | @motivating Scenario: The expiry date of a reward is shown in a secondary pill @@ -54,10 +81,10 @@ Feature: Referral Table Reward Column Then an additional info pill will appear next to the status pill with the text "Expiring in " And the date is localized to the user's locale - Examples: + Examples: | relativeTime | - | 2 days | - | 1 week | + | 2 days | + | 1 week | @motivating @ui Scenario Outline: Expanding the reward cell shows specific detailed status messages @@ -67,28 +94,28 @@ Feature: Referral Table Reward Column Then the detail text displays Examples: Payout States - | status | detailMessage | - | Payout Approved | Processing until {date}. Payout is then scheduled based on your settings. | - | Payout Failed | Payout failed due to a fulfillment issue and is currently being retried. | - | Payout Cancelled | If you think this is a mistake, contact our Support team. | - | Processing | Processing until {date}. Payout is then scheduled based on your settings. | + | status | detailMessage | + | Payout Approved | Processing until {date}. Payout is then scheduled based on your settings. | + | Payout Failed | Payout failed due to a fulfillment issue and is currently being retried. | + | Payout Cancelled | If you think this is a mistake, contact our Support team. | + | Processing | Processing until {date}. Payout is then scheduled based on your settings. | Examples: Tax Compliance States - | status | detailMessage | - | Pending Tax Review | Awaiting tax form review | - | Pending New Tax Form | Invalid tax form. Submit a new form to receive your rewards. | - | Pending Tax Submission | Submit your tax documents to receive your rewards | - | Pending Partner Creation | Complete your tax and cash payout setup to receive your rewards | + | status | detailMessage | + | Pending Tax Review | Awaiting tax form review | + | Pending New Tax Form | Invalid tax form. Submit a new form to receive your rewards. | + | Pending Tax Submission | Submit your tax documents to receive your rewards | + | Pending Partner Creation | Complete your tax and cash payout setup to receive your rewards | Examples: Standard States - | status | detailMessage | - | Available | Reward expiring on | - | Cancelled | Reward cancelled on | - | Pending | Available on | - | Pending Review | Pending since | - | Denied | Denied on | - | Expired | Reward expired on | - | Redeemed | Redeemed | + | status | detailMessage | + | Available | Reward expiring on | + | Cancelled | Reward cancelled on | + | Pending | Available on | + | Pending Review | Pending since | + | Denied | Denied on | + | Expired | Reward expired on | + | Redeemed | Redeemed | @motivating Scenario: Fuel Tank codes are displayed in the expanded view @@ -114,9 +141,9 @@ Feature: Referral Table Reward Column And the status is displayed in a pill Examples: - | pftState | status | text | pillColour | - | transfer date is in the future | PROCESSING | Payment Processing | primary | - | successfully transferred to payment provider | PAYOUT_TRANSFERRED | Payout Approved | primary | - | approved but payout scheduled date not yet arrived | PAYOUT_NOT_YET_DUE | Payout Approved | primary | - | failed due to fulfillment issue and retrying | PAYOUT_OVERDUE | Payout Failed | danger | - | reversed or cancelled after being processed | PAYOUT_REVERSED | Payout Cancelled | danger | + | pftState | status | text | pillColour | + | transfer date is in the future | PROCESSING | Payment Processing | primary | + | successfully transferred to payment provider | PAYOUT_TRANSFERRED | Payout Approved | primary | + | approved but payout scheduled date not yet arrived | PAYOUT_NOT_YET_DUE | Payout Approved | primary | + | failed due to fulfillment issue and retrying | PAYOUT_OVERDUE | Payout Failed | danger | + | reversed or cancelled after being processed | PAYOUT_REVERSED | Payout Cancelled | danger | diff --git a/packages/mint-components/src/components/sqm-rewards-table/RewardsTableCell.stories.tsx b/packages/mint-components/src/components/sqm-rewards-table/RewardsTableCell.stories.tsx index a8d3655d1d..40a46962af 100644 --- a/packages/mint-components/src/components/sqm-rewards-table/RewardsTableCell.stories.tsx +++ b/packages/mint-components/src/components/sqm-rewards-table/RewardsTableCell.stories.tsx @@ -138,6 +138,7 @@ const cashReward = { }, exchangedRewardRedemptionTransaction: null, pendingReasons: [], + rewardedCash: true, }; const taxConnection: ImpactConnection = { diff --git a/packages/mint-components/src/components/sqm-rewards-table/TaxAndCashRewardsTable.stories.tsx b/packages/mint-components/src/components/sqm-rewards-table/TaxAndCashRewardsTable.stories.tsx index b90c7b5af8..bfc5907082 100644 --- a/packages/mint-components/src/components/sqm-rewards-table/TaxAndCashRewardsTable.stories.tsx +++ b/packages/mint-components/src/components/sqm-rewards-table/TaxAndCashRewardsTable.stories.tsx @@ -5,13 +5,13 @@ import { StatusCellPayoutSent, StatusCellPayoutFailed, StatusCellPendingNewTaxForm, - StatusCellPendingPartnerCreation, StatusCellPendingTaxReview, StatusCellPendingTaxSubmission, StatusCellPayoutProcessing, CashReward, StatusCellPendingW9, StatusCellPayoutCancelled, + StatusCellPartnerNotCreatedSetupNotStarted, } from "./TaxAndCashRewardsTableCell.stories"; export default { @@ -84,7 +84,7 @@ const r_pending_tax_review = [ const r_pending_partner_creation = [ , - , + , , , ]; diff --git a/packages/mint-components/src/components/sqm-rewards-table/TaxAndCashRewardsTableCell.stories.tsx b/packages/mint-components/src/components/sqm-rewards-table/TaxAndCashRewardsTableCell.stories.tsx index 994442ef8f..4e6ae0ed0b 100644 --- a/packages/mint-components/src/components/sqm-rewards-table/TaxAndCashRewardsTableCell.stories.tsx +++ b/packages/mint-components/src/components/sqm-rewards-table/TaxAndCashRewardsTableCell.stories.tsx @@ -5,6 +5,10 @@ export default { title: "Components/Tax And Cash Rewards Table Cell", }; +// Reusable timestamps (current date is May 2026) +const PAST = 1640995200000; // Jan 1, 2022 +const FUTURE = 2779257600000; // ~2058 + const cashReward = { id: "68c34fd98a6cb4f5f8394084", type: "CREDIT", @@ -24,13 +28,13 @@ const cashReward = { name: "Cash", }, name: null, - dateCreated: 1757630425085, - dateScheduledFor: 1757631025115, - dateGiven: 1757630737115, + dateCreated: PAST, + dateScheduledFor: null, + dateGiven: PAST, dateExpires: null, dateCancelled: null, - dateRedeemed: 1757630737115, - dateModified: 1757630737115, + dateRedeemed: null, + dateModified: PAST, rewardSource: "MANUAL" as const, fuelTankCode: null, fuelTankType: null, @@ -65,225 +69,385 @@ const cashReward = { }, referral: null, description: null, - statuses: ["REDEEMED"], + statuses: [], rewardRedemptionTransactions: { data: null, }, exchangedRewardRedemptionTransaction: null, pendingReasons: [], + rewardedCash: true, }; -const pending = { - statuses: ["PENDING"], -}; - -const payoutSent = { - statuses: ["PAYOUT_APPROVED"], -}; -const payoutFailed = { - statuses: ["PAYOUT_FAILED"], -}; -const payoutCancelled = { - statuses: ["PAYOUT_CANCELLED"], - dateCancelled: 1355612521321, -}; +// ============================================================ +// Reusable tax connections +// ============================================================ -const processingPFT = { - partnerFundsTransfer: { - id: "123", - status: null, - dateCreated: 1355612521321, - dateScheduled: 2779257600000, - dateTransferred: null, +const fullySetupTaxConnection: ImpactConnection = { + connected: true, + taxHandlingEnabled: true, + connectionStatus: "COMPLETED", + publisher: { + requiredTaxDocumentType: "W9", + currentTaxDocument: { + status: "ACTIVE", + type: "W9", + dateCreated: PAST, + }, + withdrawalSettings: { paymentMethod: "BANK_TRANSFER" }, + payoutsAccount: null, }, }; -const taxConnection: ImpactConnection = { +const connectedNoWithdrawalSettings: ImpactConnection = { connected: true, taxHandlingEnabled: true, + connectionStatus: "COMPLETED", publisher: { - requiredTaxDocumentType: "W8BEN", + requiredTaxDocumentType: "W9", currentTaxDocument: { - status: "NOT_VERIFIED", - type: "W8BEN", - dateCreated: 321321487, - }, - withdrawalSettings: { - paymentMethod: "BANK_TRANSFER", + status: "ACTIVE", + type: "W9", + dateCreated: PAST, }, + withdrawalSettings: null, payoutsAccount: null, }, }; -export const CashReward = () => { - return ( - - ); +const notConnectedTaxConnection: ImpactConnection = { + connected: false, + taxHandlingEnabled: true, + connectionStatus: "NOT_STARTED", + publisher: null, }; -export const StatusCellPendingTaxReview = () => { - return ( - - ); -}; +// ============================================================ +// Reward Cell (kept from original) +// ============================================================ -export const StatusCellPendingNewTaxForm = () => { - return ( - - ); -}; +export const CashReward = () => ( + +); -export const StatusCellPendingTaxSubmission = () => { - return ( - - ); -}; +// ============================================================ +// STATUS PRECEDENCE LADDER — one story per rule +// (See sqm-rewards-table-status-column-new.feature § 2) +// ============================================================ -export const StatusCellPendingPartnerCreation = () => { - return ( - - ); -}; +// 1. Fraud check denied the referral → status is Denied +export const StatusCellFraudDenied = () => ( + +); -export const StatusCellPendingW9 = () => { - return ( - - ); -}; +// 2. Fraud check is still pending review → status is Pending Review +export const StatusCellFraudPendingReview = () => ( + +); -export const StatusCellPayoutSent = () => { - return ( - ( + +); + +// 4. Partner funds transfer is overdue → status is Payout Failed +export const StatusCellPayoutFailed = () => ( + +); + +// 5. Connected partner has not finished withdrawal settings setup → status is Pending +export const StatusCellPartnerCreatedSetupStartedButIncomplete = () => ( + +); + +// Reward is pending US_TAX and the partner has not been created yet → shows the partner-setup prompt +export const StatusCellPartnerNotCreatedSetupNotStarted = () => ( + +); + +// 6. Reward has MISSING_PAYOUT_CONFIGURATION pending reason and no withdrawal settings → status is Pending +export const StatusCellMissingPayoutConfiguration = () => ( + +); + +// 7. Partner funds transfer is scheduled for a future date → status is Processing +export const StatusCellPayoutProcessing = () => ( + +); + +// 8. Partner funds transfer has been transferred → status is Payout Approved +export const StatusCellPayoutApproved = () => ( + +); + +// 9. Reward has a cancellation date set → status is Cancelled +export const StatusCellRewardCancelled = () => ( + +); + +// 10. Reward statuses include EXPIRED → status is Expired +export const StatusCellRewardExpired = () => ( + +); + +// 11. Reward is pending and scheduled for a future date → status is Pending with the scheduled date shown +export const StatusCellPendingScheduled = () => ( + +); + +// 12. Reward does not match any precedence rule → no badge text, no description +export const StatusCellNoMatchingRule = () => ( + +); + +// ============================================================ +// US_TAX getTaxPendingReasons branches (Pending Description Resolution § 5) +// ============================================================ + +// Reward is pending US_TAX but tax handling is disabled → shows "W-9 required" +export const StatusCellPendingW9Required = () => ( + +); + +// Partner is connected but has not submitted any tax documents → shows the submit-tax-docs prompt +export const StatusCellPendingTaxSubmission = () => ( + +); + +// Partner's tax document is inactive (rejected) → shows the submit-a-new-form prompt +export const StatusCellPendingNewTaxForm = () => ( + - ); -}; + }, + }} + > +); -export const StatusCellPayoutFailed = () => { - return ( - ( + - ); -}; + }, + }} + > +); -export const StatusCellPayoutProcessing = () => { - return ( - - ); -}; +// Partner has an active tax form but has not configured withdrawal settings → shows the partner-setup prompt +export const StatusCellExistingTaxFormNoWithdrawalSettings = () => ( + +); -export const StatusCellPayoutCancelled = () => { - return ( - - ); -}; +// Partner is connected, no tax form is required, but withdrawal settings are not submitted → shows the partner-setup prompt +export const StatusCellConnectedNoTaxRequiredNoWithdrawalSettings = () => ( + +); + +// Aliases kept for the table-level stories file +export const StatusCellPayoutSent = StatusCellPayoutApproved; +export const StatusCellPendingW9 = StatusCellPendingW9Required; diff --git a/packages/mint-components/src/components/sqm-rewards-table/cells/readme.md b/packages/mint-components/src/components/sqm-rewards-table/cells/readme.md index 7a516afdd9..e771957ab7 100644 --- a/packages/mint-components/src/components/sqm-rewards-table/cells/readme.md +++ b/packages/mint-components/src/components/sqm-rewards-table/cells/readme.md @@ -17,7 +17,7 @@ | `payoutFailed` | `payout-failed` | | `string` | `"Payout failed due to a fulfillment issue and is currently being retried."` | | `payoutProcessing` | `payout-processing` | | `string` | `"Processing until {date}. Payout is then scheduled based on your settings."` | | `pendingNewTaxForm` | `pending-new-tax-form` | | `string` | `"Invalid tax form. Submit a new form to receive your rewards."` | -| `pendingPartnerCreation` | `pending-partner-creation` | | `string` | `"Complete your tax and cash payout setup to receive your rewards."` | +| `pendingPartnerCreation` | `pending-partner-creation` | | `string` | `"Complete your cash payout setup to receive your rewards."` | | `pendingReviewText` | `pending-review-text` | | `string` | `"Awaiting review"` | | `pendingScheduled` | `pending-scheduled` | | `string` | `"Until"` | | `pendingTaxReview` | `pending-tax-review` | | `string` | `"Awaiting tax form review."` | diff --git a/packages/mint-components/src/components/sqm-rewards-table/cells/sqm-rewards-table-status-cell.tsx b/packages/mint-components/src/components/sqm-rewards-table/cells/sqm-rewards-table-status-cell.tsx index 7f2c42ca67..86d8740741 100644 --- a/packages/mint-components/src/components/sqm-rewards-table/cells/sqm-rewards-table-status-cell.tsx +++ b/packages/mint-components/src/components/sqm-rewards-table/cells/sqm-rewards-table-status-cell.tsx @@ -81,7 +81,7 @@ export class RewardTableStatusCell { @Prop() pendingTaxSubmission: string = "Submit your tax documents to receive your rewards."; @Prop() pendingPartnerCreation: string = - "Complete your tax and cash payout setup to receive your rewards."; + "Complete your cash payout setup to receive your rewards."; @Prop() pendingScheduled: string = "Until"; @Prop() pendingUnhandled: string = "Fulfillment error"; @Prop() pendingReviewText: string = "Awaiting review"; @@ -101,16 +101,28 @@ export class RewardTableStatusCell { const integrationOrFueltankReward = reward.type === "INTEGRATION" || reward.type === "FUELTANK"; + // AL: TODO Scott said we should change this to use pendingReasons instead of fraudStatus - probably dont want to change this for backwards compatibility const fraudStatus = reward.referral?.fraudData?.moderationStatus; if (fraudStatus === "DENIED") return "DENIED"; if (fraudStatus === "PENDING") return "PENDING_REVIEW"; + const isCashReward = reward.rewardedCash; + + // pft can now be created before withdrawal settings exist + if (isCashReward) { + if ( + !this.taxConnection?.connected || + (this.taxConnection?.connected && + !this.taxConnection?.publisher?.withdrawalSettings) + ) + return "PENDING"; + } + const partnerTransferStatus = reward.partnerFundsTransfer?.status; if (reward.partnerFundsTransfer) { if (partnerTransferStatus === "REVERSED") return "PAYOUT_CANCELLED"; if (partnerTransferStatus === "OVERDUE") return "PAYOUT_FAILED"; - if ( reward.partnerFundsTransfer.dateScheduled && reward.partnerFundsTransfer.dateScheduled > Date.now() @@ -171,13 +183,26 @@ export class RewardTableStatusCell { if (status === "INACTIVE") return this.pendingNewTaxForm; if (status === "NOT_VERIFIED") return this.pendingTaxReview; } - if (!taxConnection?.publisher?.withdrawalSettings) + if (!taxConnection?.publisher?.withdrawalSettings) { return this.pendingPartnerCreation; + } } if (reward?.pendingReasons?.includes("MISSING_PAYOUT_CONFIGURATION")) { return this.pendingPartnerCreation; } + // Fallback: when rewardStatus() forced PENDING because the user is + // connected but hasn't set up withdrawal settings (no pendingReasons + // were returned by the API, e.g. for credit rewards with a PFT). + if (reward.rewardedCash) { + if ( + !taxConnection?.connected || + (taxConnection?.connected && + !taxConnection?.publisher?.withdrawalSettings) + ) + return this.pendingPartnerCreation; + } + return ""; } @@ -223,11 +248,11 @@ export class RewardTableStatusCell { }, { date: DateTime.fromMillis( - this.reward.partnerFundsTransfer.dateScheduled, + this.reward.partnerFundsTransfer.dateScheduled ) ?.setLocale(luxonLocale(this.locale)) .toLocaleString(DateTime.DATE_MED), - }, + } ); } } @@ -240,7 +265,7 @@ export class RewardTableStatusCell { { id: "statusMessage", defaultMessage: this.statusText }, { status: rewardStatus, - }, + } ); const badgeType = this.getBadgeType(rewardStatus); @@ -271,8 +296,8 @@ export class RewardTableStatusCell { rewardStatus === "PENDING_REVIEW" ? this.pendingReviewText : rewardStatus === "DENIED" - ? this.deniedText - : null; + ? this.deniedText + : null; const getBadgeCSSClass = () => { switch (rewardStatus) { @@ -322,7 +347,7 @@ export class RewardTableStatusCell { const taxReason = prop.getTaxPendingReasons( prop.reward, - prop.taxConnection, + prop.taxConnection ); return [ diff --git a/packages/mint-components/src/components/sqm-rewards-table/columns/sqm-rewards-table-status-column.feature b/packages/mint-components/src/components/sqm-rewards-table/columns/sqm-rewards-table-status-column.feature index af4cbd3b08..e8e7aae672 100644 --- a/packages/mint-components/src/components/sqm-rewards-table/columns/sqm-rewards-table-status-column.feature +++ b/packages/mint-components/src/components/sqm-rewards-table/columns/sqm-rewards-table-status-column.feature @@ -41,6 +41,50 @@ Feature: Reward Table Status Column | PENDING_PARTNER_CREATION | Pending | warning | | DENIED | Denied | danger | + @motivating + Scenario Outline: Status precedence ladder + Given a reward, its referral fraud state, the user's Impact tax connection + Then is produced + And the , , and is determined in the following : + + Examples: + | order | rule | resultingStatus | resultingBadgeText | resultingDescription | + | 1 | referral.fraudData.moderationStatus is "DENIED" | DENIED | Denied | Detected self-referral | + | 2 | referral.fraudData.moderationStatus is "PENDING" | PENDING_REVIEW | Pending | Awaiting review | + | 3 | reward.rewardedCash is true AND impactConnection is NOT connected | PENDING | Pending | Complete your cash payout setup to receive your rewards. | + | 4 | reward.rewardedCash is true AND impactConnection is connected AND publisher.withdrawalSettings is missing | PENDING | Pending | Complete your cash payout setup to receive your rewards. | + | 5 | partnerFundsTransfer.status is "REVERSED" | PAYOUT_CANCELLED | Payout Cancelled | If you think this is a mistake, contact our Support team. | + | 6 | partnerFundsTransfer.status is "OVERDUE" | PAYOUT_FAILED | Payout Failed | Payout failed due to a fulfillment issue and is currently being retried. | + | 7 | reward.pendingReasons includes "MISSING_PAYOUT_CONFIGURATION" AND publisher.withdrawalSettings is missing | PENDING | Pending | Complete your cash payout setup to receive your rewards. | + | 8 | partnerFundsTransfer.dateScheduled is in the future | PROCESSING | Payment Processing | Processing until Jan 1, 2026. Payout is then scheduled based on your settings. | + | 9 | partnerFundsTransfer.status is "TRANSFERRED" / "NOT_YET_DUE", or dateScheduled has passed without REVERSED/OVERDUE | PAYOUT_APPROVED | Payout Approved | Payout approved and scheduled for payment based on your settings. | + | 10 | reward.dateCancelled is set | CANCELLED | Cancelled | Jan 1, 2026 | + | 11 | reward.statuses includes "EXPIRED" | EXPIRED | Expired | Jan 1, 2026 | + | 12 | reward.statuses includes "PENDING" | PENDING | Pending | Until Jan 1, 2026 | + | 13 | statuses includes "REDEEMED" | REDEEMED | Redeemed | Jan 1, 2026 | + | 14 | statuses includes "AVAILABLE" | AVAILABLE | Available | Expires Jan 1, 2026 | + | 15 | statuses includes "CANCELLED" | CANCELLED | Cancelled | Jan 1, 2026 | +# This spec should probably be inserted in to the above spec after rule 9 because it gets invoked when the reward state is pending +# and does not hit the rules that output pending from above + + @motivating + Scenario Outline: PENDING description precedence ladder + Given a PENDING reward, its pendingReasons, and the user's Impact tax connection + Then is produced + And the , , and is determined in the following order: + + Examples: + | order | rule | resultingStatus | resultingBadgeText | resultingDescription | + | 1 | pendingReasons includes "US_TAX" AND impactConnection.taxHandlingEnabled is false | PENDING | Pending | W-9 required | + | 2 | pendingReasons includes "US_TAX" AND impactConnection is NOT connected | PENDING | Pending | Complete your tax and cash payout setup to receive your rewards. | + | 3 | pendingReasons includes "US_TAX" AND publisher.requiredTaxDocumentType is set AND publisher.currentTaxDocument is missing | PENDING | Pending | Submit your tax documents to receive your rewards. | + | 4 | pendingReasons includes "US_TAX" AND publisher.requiredTaxDocumentType is set AND publisher.currentTaxDocument.status is "INACTIVE" | PENDING | Pending | Invalid tax form. Submit a new form to receive your rewards. | + | 5 | pendingReasons includes "US_TAX" AND publisher.requiredTaxDocumentType is set AND publisher.currentTaxDocument.status is "NOT_VERIFIED" | PENDING | Pending | Awaiting tax form review. | + | 6 | pendingReasons includes "US_TAX" AND publisher.withdrawalSettings is missing | PENDING | Pending | Complete your tax and cash payout setup to receive your rewards. | + | 7 | pendingReasons includes "MISSING_PAYOUT_CONFIGURATION" | PENDING | Pending | Complete your tax and cash payout setup to receive your rewards. | + | 8 | reward.rewardedCash is true AND impactConnection is NOT connected (fallback when no pendingReason returned) | PENDING | Pending | Complete your tax and cash payout setup to receive your rewards. | + | 9 | reward.rewardedCash is true AND impactConnection is connected AND publisher.withdrawalSettings is missing (fallback) | PENDING | Pending | Complete your tax and cash payout setup to receive your rewards. | + @motivating Scenario Outline: Reward status related information is displayed under status pills Given a user @@ -58,21 +102,21 @@ Feature: Reward Table Status Column | expired reward | | | cancelled reward | | | pending reward with a scheduled date | Until | - + Examples: Tax & Payout Rewards - | reward | text | - | pending reward due to W9 | W-9 required | - | pending reward due to fulfillment error | Fulfillment error | - | reward pending review of referral | Awaiting review | - | pending reward due to no connected Impact partner | Complete your tax and cash payout setup to receive your rewards. | - | pending reward due to an invalid tax document | Invalid tax form. Submit a new form to receive your rewards. | - | pending reward due to user required to submit a tax document | Submit your tax documents to receive your rewards. | - | pending reward due to tax document being in review | Awaiting tax form review. | - | reward whose payout is processing | Processing until . Payout is then scheduled based on your settings. | - | reward whose payout failed | Payout failed due to a fulfillment issue and is currently being retried. | - | reward whose payout was approved | Payout approved and scheduled for payment based on your settings. | - | reward whose payout was cancelled | If you think this is a mistake, contact our Support team. | - | cancelled reward from denied referral | Detected self-referral | + | reward | text | + | pending reward due to W9 | W-9 required | + | pending reward due to fulfillment error | Fulfillment error | + | reward pending review of referral | Awaiting review | + | pending reward due to no connected Impact partner | Complete your tax and cash payout setup to receive your rewards. | + | pending reward due to an invalid tax document | Invalid tax form. Submit a new form to receive your rewards. | + | pending reward due to user required to submit a tax document | Submit your tax documents to receive your rewards. | + | pending reward due to tax document being in review | Awaiting tax form review. | + | reward whose payout is processing | Processing until . Payout is then scheduled based on your settings. | + | reward whose payout failed | Payout failed due to a fulfillment issue and is currently being retried. | + | reward whose payout was approved | Payout approved and scheduled for payment based on your settings. | + | reward whose payout was cancelled | If you think this is a mistake, contact our Support team. | + | cancelled reward from denied referral | Detected self-referral | @minutia Scenario Outline: Tax-related reward statuses are based on the user's Impact tax connection @@ -128,9 +172,9 @@ Feature: Reward Table Status Column And under the pill is Examples: - | pftState | status | text | pillColour | description | - | transfer date is in the future | PROCESSING | Processing | warning | Processing until . Payout is then scheduled based on your settings. | - | successfully transferred to payment provider | PAYOUT_TRANSFERRED | Payout Approved | primary | Payout approved and scheduled for payment based on your settings. | - | approved but payout scheduled date not yet arrived | PAYOUT_NOT_YET_DUE | Payout Approved | primary | Payout approved and scheduled for payment based on your settings. | - | failed due to fulfillment issue and retrying | PAYOUT_OVERDUE | Payout Failed | danger | Payout failed due to a fulfillment issue and is currently being retried. | - | reversed or cancelled after being processed | PAYOUT_REVERSED | Payout Cancelled | danger | If you think this is a mistake, contact our Support team. | \ No newline at end of file + | pftState | status | text | pillColour | description | + | transfer date is in the future | PROCESSING | Processing | warning | Processing until . Payout is then scheduled based on your settings. | + | successfully transferred to payment provider | PAYOUT_TRANSFERRED | Payout Approved | primary | Payout approved and scheduled for payment based on your settings. | + | approved but payout scheduled date not yet arrived | PAYOUT_NOT_YET_DUE | Payout Approved | primary | Payout approved and scheduled for payment based on your settings. | + | failed due to fulfillment issue and retrying | PAYOUT_OVERDUE | Payout Failed | danger | Payout failed due to a fulfillment issue and is currently being retried. | + | reversed or cancelled after being processed | PAYOUT_REVERSED | Payout Cancelled | danger | If you think this is a mistake, contact our Support team. | diff --git a/packages/mint-components/src/components/sqm-rewards-table/useRewardsTable.tsx b/packages/mint-components/src/components/sqm-rewards-table/useRewardsTable.tsx index 12019131cd..8d68cd9dbf 100644 --- a/packages/mint-components/src/components/sqm-rewards-table/useRewardsTable.tsx +++ b/packages/mint-components/src/components/sqm-rewards-table/useRewardsTable.tsx @@ -60,6 +60,7 @@ const GET_REWARDS = gql` fuelTankCode fuelTankType currency + rewardedCash prettyValue(locale: $locale) prettyValueNumber: prettyValue( formatType: NUMBER_FORMATTED diff --git a/packages/mint-components/src/components/sqm-stencilbook/readme.md b/packages/mint-components/src/components/sqm-stencilbook/readme.md index 7fc4ab2103..23b09fe749 100644 --- a/packages/mint-components/src/components/sqm-stencilbook/readme.md +++ b/packages/mint-components/src/components/sqm-stencilbook/readme.md @@ -104,6 +104,8 @@ - [sqm-lead-input-field](../sqm-lead-form) - [sqm-lead-dropdown-field](../sqm-lead-form) - [sqm-lead-form](../sqm-lead-form) +- [sqm-partner-info-modal](../sqm-partner-info-modal) +- [sqm-widget-verification](../sqm-widget-verification) - [sqm-user-attribute](../sqm-user-attribute) ### Graph @@ -206,6 +208,8 @@ graph TD; sqm-stencilbook --> sqm-lead-input-field sqm-stencilbook --> sqm-lead-dropdown-field sqm-stencilbook --> sqm-lead-form + sqm-stencilbook --> sqm-partner-info-modal + sqm-stencilbook --> sqm-widget-verification sqm-stencilbook --> sqm-user-attribute sqm-form-message --> sqm-skeleton sqm-portal-register --> sqm-form-message @@ -286,6 +290,10 @@ graph TD; sqm-payout-status-alert --> sqm-form-message sqm-lead-form --> sqm-form-message sqm-lead-form --> sqm-lead-input-field + sqm-partner-info-modal --> sqm-form-message + sqm-widget-verification --> sqm-partner-info-modal + sqm-widget-verification --> sqm-code-verification + sqm-widget-verification --> sqm-email-verification sqm-user-attribute --> sqm-skeleton style sqm-stencilbook fill:#f9f,stroke:#333,stroke-width:4px ``` diff --git a/packages/mint-components/src/components/sqm-stencilbook/sqm-stencilbook.tsx b/packages/mint-components/src/components/sqm-stencilbook/sqm-stencilbook.tsx index 73686e013b..8d188327e4 100644 --- a/packages/mint-components/src/components/sqm-stencilbook/sqm-stencilbook.tsx +++ b/packages/mint-components/src/components/sqm-stencilbook/sqm-stencilbook.tsx @@ -97,6 +97,7 @@ import * as UserName from "../sqm-user-name/UserName.stories"; import * as WidgetCodeVerification from "../sqm-widget-verification/sqm-code-verification/WidgetCodeVerification.stories"; import * as WidgetEmailVerification from "../sqm-widget-verification/sqm-email-verification/WidgetEmailVerification.stories"; import * as BankingInfoForm from "../tax-and-cash/BankingForm.stories"; +import * as TaxAndCashDashboardView from "../tax-and-cash/sqm-tax-and-cash-dashboard/TaxAndCashDashboardView.stories"; import * as TaxForm from "../tax-and-cash/TaxForm.stories"; import * as PayoutDetailsCard from "../tax-and-cash/sqm-payout-details-card/PayoutDetailsCard.stories"; import * as PayoutStatusAlert from "../tax-and-cash/sqm-payout-status-alert/PayoutStatusAlert.stories"; @@ -108,6 +109,8 @@ import * as LeadFormDropdownField from "../sqm-lead-form/LeadFormDropdownField.s import * as LeadCheckboxField from "../sqm-lead-form/LeadCheckboxField.stories"; import * as Skeleton from "../sqm-skeleton/Skeleton.stories"; import * as UserInfoFormView from "../tax-and-cash/sqm-user-info-form/UserInfoFormView.stories"; +import * as PartnerInfoModal from "../sqm-partner-info-modal/PartnerInfoModal.stories"; +import * as WidgetVerification from "../sqm-widget-verification/WidgetVerification.stories"; import * as UserAttribute from "../sqm-user-attribute/UserAttribute.stories"; import { ShadowViewAddon } from "../../ShadowViewAddon"; @@ -222,6 +225,9 @@ const stories = [ TaxAndCashRewardsTable, TaxAndCashReferralTableRewardsCell, TaxAndCashReferralTable, + TaxAndCashDashboardView, + PartnerInfoModal, + WidgetVerification, UserAttribute, ]; diff --git a/packages/mint-components/src/components/sqm-user-attribute/readme.md b/packages/mint-components/src/components/sqm-user-attribute/readme.md index ead6002003..6c04bd65bb 100644 --- a/packages/mint-components/src/components/sqm-user-attribute/readme.md +++ b/packages/mint-components/src/components/sqm-user-attribute/readme.md @@ -13,7 +13,7 @@ | `demoData` | -- | | `{ loading?: boolean; value?: string; fontSize?: number; color?: string; fontWeight?: number; }` | `undefined` | | `fontSize` | `font-size` | Number in pixels. | `number` | `undefined` | | `fontWeight` | `font-weight` | Font weight | `number` | `undefined` | -| `value` | `value` | The custom field key to display. | `string` | `undefined` | +| `value` | `value` | The custom field key to display. | `number \| string` | `undefined` | ## Dependencies diff --git a/packages/mint-components/src/components/sqm-widget-verification/WidgetVerification.stories.tsx b/packages/mint-components/src/components/sqm-widget-verification/WidgetVerification.stories.tsx new file mode 100644 index 0000000000..dc2a78d39a --- /dev/null +++ b/packages/mint-components/src/components/sqm-widget-verification/WidgetVerification.stories.tsx @@ -0,0 +1,37 @@ +import { h } from "@stencil/core"; + +export default { + title: "Components/Widget Verification", +}; + +export const Step1Email = () => ( + +); + +export const Step2Code = () => ( + +); + +export const Step3PartnerModal = () => ( + +); + +export const Loading = () => ( + +); diff --git a/packages/mint-components/src/components/sqm-widget-verification/keys.ts b/packages/mint-components/src/components/sqm-widget-verification/keys.ts index c46ba9d8cf..43876614ef 100644 --- a/packages/mint-components/src/components/sqm-widget-verification/keys.ts +++ b/packages/mint-components/src/components/sqm-widget-verification/keys.ts @@ -1,4 +1,6 @@ export const SHOW_CODE_NAMESPACE = "sq:code-verification"; +export const SHOW_PARTNER_MODAL_NAMESPACE = "sq:show-partner-modal"; +export const PARTNER_CREATED_NAMESPACE = "sq:partner-created"; export const VERIFICATION_EMAIL_NAMESPACE = "sq:verification-email"; export const VERIFICATION_PARENT_NAMESPACE = "sq:verification-context"; export const VERIFICATION_EVENT_KEY = "sq:code-verified"; diff --git a/packages/mint-components/src/components/sqm-widget-verification/sqm-code-verification/sqm-code-verification-view.tsx b/packages/mint-components/src/components/sqm-widget-verification/sqm-code-verification/sqm-code-verification-view.tsx index 60fd289190..7be75a8aa9 100644 --- a/packages/mint-components/src/components/sqm-widget-verification/sqm-code-verification/sqm-code-verification-view.tsx +++ b/packages/mint-components/src/components/sqm-widget-verification/sqm-code-verification/sqm-code-verification-view.tsx @@ -81,7 +81,6 @@ const style = { }, ContinueButton: { width: "100%", - maxWidth: "100px", }, FooterContainer: { display: "flex", @@ -116,7 +115,7 @@ const sheet = createStyleSheet(style); const styleString = sheet.toString(); export function WidgetCodeVerificationView( - props: WidgetCodeVerificationViewProps + props: WidgetCodeVerificationViewProps, ) { const { states, refs, callbacks, text } = props; @@ -142,7 +141,7 @@ export function WidgetCodeVerificationView( {text.resendCodeLabel} ), - } + }, ); const codeResentSuccessfully = intl.formatMessage( @@ -152,7 +151,7 @@ export function WidgetCodeVerificationView( }, { email: states.email, - } + }, ); const inputClass = states.verifyFailed @@ -175,7 +174,7 @@ export function WidgetCodeVerificationView( ? text.reverifyCodeHeaderText : text.verifyCodeHeaderText, }, - { email: states.email } + { email: states.email }, )}
@@ -199,6 +198,9 @@ export function WidgetCodeVerificationView( {states.verifyFailed && (

{text.invalidCodeText}

)} +
+ {resendCodeText} +
-
- {resendCodeText} -
); diff --git a/packages/mint-components/src/components/sqm-widget-verification/sqm-code-verification/sqm-code-verification.feature b/packages/mint-components/src/components/sqm-widget-verification/sqm-code-verification/sqm-code-verification.feature index 428cbd22e5..969425851e 100644 --- a/packages/mint-components/src/components/sqm-widget-verification/sqm-code-verification/sqm-code-verification.feature +++ b/packages/mint-components/src/components/sqm-widget-verification/sqm-code-verification/sqm-code-verification.feature @@ -13,7 +13,7 @@ Feature: Cash payout code verification widget Examples: | code | result | - | 123456 | 123456 | + | 123456 | 123456 | | 1234567 | 123456 | @minutia @@ -29,11 +29,11 @@ Feature: Cash payout code verification widget Examples: | number | next | - | 1 | 2 | - | 2 | 3 | - | 3 | 4 | - | 4 | 5 | - | 5 | 6 | + | 1 | 2 | + | 2 | 3 | + | 3 | 4 | + | 4 | 5 | + | 5 | 6 | @minutia Scenario: Clicking a code input highlights its contents @@ -42,7 +42,7 @@ Feature: Cash payout code verification widget Then the character is highlighted @minutia - Scenario: Successful code verification fires an event + Scenario: Successful code verification fires an event and advances to the partner info modal Given a valid code is entered And the mutation `verifyUserEmail` is successful And an access key is recieved @@ -51,15 +51,27 @@ Feature: Cash payout code verification widget """ { token: } """ + And the parent `sqm-widget-verification` advances to the partner info modal step @motivating - Scenario: User enters verification code and proceed to setup - Given they are viewing the code verification widget + Scenario: User enters verification code and proceeds to the partner info modal + Given they are viewing the code verification widget inside the `sqm-widget-verification` dialog And they received a code in their email from the first step When they enter the code provided in their email in to the code input And press "Verify" - Then they will gain access to the Cash payout settings form - And the Cash payout settings form appears + Then the parent `sqm-widget-verification` swaps to the partner info modal step inside the same dialog + And the dialog cannot be dismissed + When the user successfully submits the partner info modal + Then the widget verification dialog is hidden + And the verified slot of `sqm-widget-verification` is rendered + + @motivating + Scenario: Standalone code verification still gains access without the partner modal + Given `sqm-code-verification` is rendered without a `sqm-widget-verification` parent + When the user submits a valid 2FA code + Then the "sq:code-verified" event is dispatched + And the parent provider (if any) is set to verified + But no partner info modal is shown by `sqm-code-verification` itself @minutia @ui Scenario: Code verification header text changes based on user email @@ -95,7 +107,7 @@ Feature: Cash payout code verification widget """ Please check your code and try again. If you’re still having trouble, try resending your code. """ - And they will not gain access to the Cash payout settings form + And the partner info modal step is not shown @motivating Scenario: code-verification sends a 2FA email on first load if it's rendered by itself @@ -130,4 +142,4 @@ Feature: Cash payout code verification widget | participantEmail | impactConnection | mutation | | null | null | N/A | | participant@example.com | null | verifyUserEmail | - | participantEmail | { user: { email: impact@example.com }} | submitImpactPublisherEmail2FACode | \ No newline at end of file + | participantEmail | { user: { email: impact@example.com }} | submitImpactPublisherEmail2FACode | diff --git a/packages/mint-components/src/components/sqm-widget-verification/sqm-email-verification/sqm-email-verification-view.tsx b/packages/mint-components/src/components/sqm-widget-verification/sqm-email-verification/sqm-email-verification-view.tsx index ce8d1b7735..681367dab9 100644 --- a/packages/mint-components/src/components/sqm-widget-verification/sqm-email-verification/sqm-email-verification-view.tsx +++ b/packages/mint-components/src/components/sqm-widget-verification/sqm-email-verification/sqm-email-verification-view.tsx @@ -37,11 +37,9 @@ const style = { gap: "var(--sl-spacing-medium)", position: "relative", flexDirection: "column", - maxWidth: "320px", }, ContinueButton: { width: "100%", - maxWidth: "100px", }, SkeletonOne: { width: "50%", @@ -214,7 +212,7 @@ const sheet = createStyleSheet(style); const styleString = sheet.toString(); export function WidgetEmailVerificationView( - props: WidgetEmailVerificationViewProps + props: WidgetEmailVerificationViewProps, ) { const { states, callbacks, text } = props; @@ -259,7 +257,7 @@ export function WidgetEmailVerificationView( {text.supportLink} ), - } + }, )} )} diff --git a/packages/mint-components/src/components/sqm-widget-verification/sqm-email-verification/useEmailVerification.ts b/packages/mint-components/src/components/sqm-widget-verification/sqm-email-verification/useEmailVerification.ts index 3a9e9bbbbf..f62f85dbde 100644 --- a/packages/mint-components/src/components/sqm-widget-verification/sqm-email-verification/useEmailVerification.ts +++ b/packages/mint-components/src/components/sqm-widget-verification/sqm-email-verification/useEmailVerification.ts @@ -53,7 +53,7 @@ export const UserLookupQuery = gql` export function useUpsertUserEmail() { const user = useUserIdentity(); const [request, { loading: loading, data, errors }] = useMutation( - UpsertUserEmailMutation + UpsertUserEmailMutation, ); const upsertUserEmail = async (email: string) => { @@ -85,10 +85,10 @@ export function useUpsertUserEmail() { } export function useWidgetEmailVerification( - props: WidgetEmailVerification + props: WidgetEmailVerification, ): WidgetEmailVerificationViewProps { - const [_, setShowCode] = useParent(SHOW_CODE_NAMESPACE); - const [email, setEmail] = useParent(VERIFICATION_EMAIL_NAMESPACE); + const [_, setShowCode] = useParent(SHOW_CODE_NAMESPACE); + const [_email, setEmail] = useParent(VERIFICATION_EMAIL_NAMESPACE); const [emailExists, setEmailExists] = useState(false); const [error, setError] = useState(false); @@ -121,27 +121,39 @@ export function useWidgetEmailVerification( if (!data?.viewer) return; setLoading(true); - const toAddress = data.viewer.email; + setMutationError(false); + setError(false); + + let toAddress = data.viewer.email; if (!toAddress) { - // If no email on the user, set one - const formData = e.detail.formData; - const newEmail = formData.get("email").toString(); + const formData = e.detail?.formData; + const newEmail = formData?.get("email")?.toString() ?? ""; if (!emailRegex.test(newEmail)) { setError(true); + setLoading(false); return; } - const result = await upsertUserEmail(newEmail); - if (!result || !result.user.email) setError(true); + const upsertResult = await upsertUserEmail(newEmail); + if (!upsertResult || !upsertResult.upsertUser?.email) { + setError(true); + setLoading(false); + return; + } + toAddress = upsertResult.upsertUser.email; } // UI should not allow this call til initialisation is done - if (!initialized) return; + if (!initialized) { + setLoading(false); + return; + } const result = await sendEmail(); - if (!result || !result.success) setMutationError(true); - else { + if (!result || !result.success) { + setMutationError(true); + } else { // This is used to let the code verification widget know an email was already sent setEmail(toAddress); setShowCode(true); diff --git a/packages/mint-components/src/components/sqm-widget-verification/sqm-widget-verification.feature b/packages/mint-components/src/components/sqm-widget-verification/sqm-widget-verification.feature index 479139c48a..a7559c4214 100644 --- a/packages/mint-components/src/components/sqm-widget-verification/sqm-widget-verification.feature +++ b/packages/mint-components/src/components/sqm-widget-verification/sqm-widget-verification.feature @@ -11,10 +11,13 @@ Feature: Widget verification flow """ When the html is loaded - Then the page displays the widget verification flow + Then the page displays the widget verification flow inside a non-dismissable sl-dialog + And the dialog title is "Let's get you ready for rewards" When an email is successfully verified + Then the partner info modal step is displayed inside the same dialog + When the partner info modal is completed successfully Then the html inside the "verified" template is displayed - And the widget verification flow is hidden + And the widget verification flow dialog is hidden @motivating @ui Scenario: Entering an email for verification @@ -40,6 +43,36 @@ Feature: Widget verification flow Then an email containing a 2FA is sent to the provided email address And the component displays the code verification step + @motivating + Scenario: Successful code verification opens the partner info modal step in the same dialog + Given a user on the code verification step + When they enter a valid 2FA code + And they click "Verify" + Then the partner creation step is displayed inside the same widget verification dialog + And the embedded `sqm-partner-info-modal` is rendered with the `inModal` prop + And the dialog title remains "Let's get you ready for rewards" + And the dialog cannot be dismissed by clicking the overlay or pressing Escape + + @motivating + Scenario: Successful partner creation completes the verification flow + Given the partner info modal step is displayed + When the user successfully submits the partner info modal + Then the widget verification dialog is hidden + And the html inside the "verified" template slot is displayed + + @motivating + Scenario Outline: Initial load skips ahead based on the user's verification and connection state + Given a user with `emailVerified` + And `impactConnection.connected` + When the `sqm-widget-verification` component loads + Then the is shown + + Examples: + | emailVerified | connected | step | + | false | false | email verification step | + | true | false | partner info modal step (email/code skipped) | + | true | true | verified slot (entire flow skipped) | + @motivating @ui Scenario: Code verification step Given a user on the code verification step @@ -67,7 +100,7 @@ Feature: Widget verification flow Examples: | code | time | - | 234567 | 1 minute | + | 234567 | 1 minute | | 123456 | 30 minutes | | 234567 | 30 minutes | @@ -120,13 +153,16 @@ Feature: Widget verification flow And the html includes "" When "sqm-widget-verification" is selected in the editor Then the following states are displayed - | state | - | Email Step | - | Code Step | - When "Email Step" is selected + | state | + | Step 1: Enter email | + | Step 2: Enter code | + | Step 3: Create Partner | + When "Step 1: Enter email" is selected Then "sqm-widget-verification" displays the email verification flow - When "Code Step" is selected + When "Step 2: Enter code" is selected Then "sqm-widget-verification" displays the 2FA verification flow + When "Step 3: Create Partner" is selected + Then "sqm-widget-verification" displays the embedded `sqm-partner-info-modal` @motivating Scenario: sqm-widget-verification-controller editor states @@ -140,4 +176,4 @@ Feature: Widget verification flow When "Verified" is selected Then "sqm-widget-verification-controller" displays whatever is slotted in the template slot "verified" When "Not Verified" is selected - Then "sqm-widget-verification-controller" displays whatever is slotted in the template slot "not-verified" \ No newline at end of file + Then "sqm-widget-verification-controller" displays whatever is slotted in the template slot "not-verified" diff --git a/packages/mint-components/src/components/sqm-widget-verification/sqm-widget-verification.tsx b/packages/mint-components/src/components/sqm-widget-verification/sqm-widget-verification.tsx index 2f10ee7c96..d396a9916d 100644 --- a/packages/mint-components/src/components/sqm-widget-verification/sqm-widget-verification.tsx +++ b/packages/mint-components/src/components/sqm-widget-verification/sqm-widget-verification.tsx @@ -15,6 +15,37 @@ import { VERIFICATION_PARENT_NAMESPACE, } from "./keys"; import { useWidgetVerification } from "./useWidgetVerification"; +import { createStyleSheet } from "../../styling/JSS"; + +const style = { + Dialog: { + "&::part(panel)": { + maxWidth: "480px", + }, + "&::part(body)": { + padding: "0 var(--sl-spacing-x-large)", + fontSize: "var(--sl-font-size-small)", + overflow: "visible", + }, + "&::part(footer)": { + display: "flex", + flexDirection: "column", + gap: "var(--sl-spacing-small)", + }, + "&::part(overlay)": { + background: "rgba(0, 0, 0, 0.5)", + }, + }, + DialogTitle: { + fontSize: "var(--sl-font-size-x-large)", + fontWeight: "600", + padding: "var(--sl-spacing-x-large) 0 0 0", + margin: "0", + }, +}; + +const sheet = createStyleSheet(style); +const styleString = sheet.toString(); /** * @uiName Widget Verification Flow @@ -26,23 +57,16 @@ import { useWidgetVerification } from "./useWidgetVerification"; shadow: true, }) export class WidgetVerification { - // ! Any updated must be reflected in sqm-widget-verification-internal AND sqm-email-verification AND sqm-code-verification + // ! Any updated must be reflected in sqm-widget-verification-internal AND sqm-email-verification AND sqm-code-verification AND sqm-partner-info-modal /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ GENERAL PROPS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ /** - * @uiName General verify widget header text - * @uiGroup General Text - */ - @Prop() - general_verifyEmailHeader = "Verify your email"; - /** - * @uiName General verify widget description text + * @uiName General widget header text with partner creation * @uiGroup General Text */ @Prop() - general_verifyEmailDescription = - "To get your cash paid out directly to your bank account, please complete your account setup"; + general_widgetHeaderWithPartnerCreation = "Let's get you ready for rewards"; /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ EMAIL STEP PROPS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ @@ -87,7 +111,7 @@ export class WidgetVerification { emailStep_emailValidationErrorText: string = "Please enter a valid email"; /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - EMAIL STEP PROPS + CODE STEP PROPS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ /** * @uiName Verify code widget header text @@ -141,10 +165,106 @@ export class WidgetVerification { @Prop() codeStep_networkErrorMessage: string = "An error occurred while verifying your email. Please refresh the page and try again."; + /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + PARTNER CREATION STEP PROPS + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + /** + * @uiName New partner header + * @uiGroup Partner Creation Step + * @uiWidget textArea + */ + @Prop() + createPartnerStep_modalHeader: string = "Let's get you ready for rewards"; + /** + * @uiName Existing partner header + * @uiGroup Partner Creation Step + * @uiWidget textArea + */ + @Prop() + createPartnerStep_modalHeaderExistingPartner: string = + "We found an existing account"; + /** + * @uiName New partner description + * @uiGroup Partner Creation Step + * @uiWidget textArea + */ + @Prop() + createPartnerStep_descriptionNewPartner: string = + "Confirm your country and currency now to get your future rewards faster."; + /** + * @uiName Existing partner description + * @uiGroup Partner Creation Step + * @uiWidget textArea + */ + @Prop() + createPartnerStep_descriptionExistingPartner: string = + "We found an account with this email on our referral program provider, impact.com. Please confirm your country and currency now to get your future rewards faster."; + /** + * @uiName Existing partner support description + * @uiGroup Partner Creation Step + * @uiWidget textArea + */ + @Prop() + createPartnerStep_supportDescriptionExistingPartner: string = + "If this is a mistake, please contact Support or sign up for this referral program with a different email."; + /** + * @uiName Country label + * @uiGroup Partner Creation Step + */ + @Prop() + createPartnerStep_countryLabel: string = "Country"; + /** + * @uiName Currency label + * @uiGroup Partner Creation Step + */ + @Prop() + createPartnerStep_currencyLabel: string = "Currency"; + /** + * @uiName Submit button label + * @uiGroup Partner Creation Step + */ + @Prop() + createPartnerStep_submitButtonLabel: string = "Submit"; + /** + * @uiName Confirm button label + * @uiGroup Partner Creation Step + */ + @Prop() + createPartnerStep_confirmButtonLabel: string = "Confirm"; + /** + * @uiName Search country placeholder + * @uiGroup Partner Creation Step + */ + @Prop() + createPartnerStep_searchCountryPlaceholder: string = "Search for a country"; + /** + * @uiName Search currency placeholder + * @uiGroup Partner Creation Step + */ + @Prop() + createPartnerStep_searchCurrencyPlaceholder: string = "Search for a currency"; + /** + * @uiName Network error text + * @uiGroup Partner Creation Step + * @uiWidget textArea + */ + @Prop() + createPartnerStep_networkErrorText: string = + "An error occurred. Please try again."; + /** + * @uiName Missing fields error text + * @uiGroup Partner Creation Step + * @uiWidget textArea + */ + @Prop() + createPartnerStep_missingFieldsErrorText: string = + "Please select both a country and currency."; + /** * @undocumented * @componentState { "title": "Step 1: Enter email", "props": { "showCode": false }, "dependencies": ["sqm-email-verification"], "uiGroup": "Email Verification Step" } * @componentState { "title": "Step 2: Enter code", "props": { "showCode": true }, "dependencies": ["sqm-code-verification"], "uiGroup": "Code Verification Step" } + * @componentState { "title": "Step 3: Create Partner", "props": { "showPartnerModal": true }, "dependencies": ["sqm-partner-info-modal"], "uiGroup": "Partner Creation Step" } */ @Prop() stateController: string = "{}"; @@ -165,34 +285,68 @@ export class WidgetVerification { if (props.loading) return ; - const generalText = this.getStepTextProps("general_"); + // for handling state previews in content editor + const partnerState = + typeof props["sqm-partner-info-modal_stateController"] === "string" + ? parseStates(props["sqm-partner-info-modal_stateController"]) + : props["sqm-partner-info-modal_stateController"]; + if (props.showPartnerModal && partnerState?.states?.open === false) { + return
; + } - return ( -
-

- {generalText.verifyEmailHeader} -

-

- {generalText.verifyEmailDescription} -

- {props.showCode ? ( + const partnerText = this.getStepTextProps("createPartnerStep_"); + + const dialogLabel = this.general_widgetHeaderWithPartnerCreation; + + const renderStepContent = () => { + if (props.showPartnerModal) { + return ( + + ); + } + if (props.showCode) { + return ( - ) : ( - - )} + ); + } + return ( + + ); + }; + + return ( +
+ + { + e.preventDefault(); + }} + onSl-hide={(e: any) => { + if (e.target?.tagName === "SL-DIALOG") { + e.preventDefault(); + } + }} + > +

{dialogLabel}

+ {renderStepContent()} +
); } @@ -215,7 +369,7 @@ function useDemoWidgetVerificationInternal(props: WidgetVerification) { key === "sqm-widget-verification" ? { ...prev, ...states[key] } : { ...prev, [`${key}_stateController`]: states[key] }, - {} + {}, ); const onVerification = () => { @@ -223,8 +377,14 @@ function useDemoWidgetVerificationInternal(props: WidgetVerification) { }; return deepmerge( - { showCode, onVerification, loading: false }, + { + showCode, + showPartnerModal: false, + onVerification, + onPartnerModalComplete: () => {}, + loading: false, + }, formatted || {}, - { arrayMerge: (_, a) => a } + { arrayMerge: (_, a) => a }, ); } diff --git a/packages/mint-components/src/components/sqm-widget-verification/useWidgetVerification.ts b/packages/mint-components/src/components/sqm-widget-verification/useWidgetVerification.ts index ec7442ce05..b3056b3161 100644 --- a/packages/mint-components/src/components/sqm-widget-verification/useWidgetVerification.ts +++ b/packages/mint-components/src/components/sqm-widget-verification/useWidgetVerification.ts @@ -2,13 +2,14 @@ import { useLazyQuery, useParentState, useSetParent, - useUserIdentity, } from "@saasquatch/component-boilerplate"; import { useState } from "@saasquatch/stencil-hooks"; import { useEffect } from "@saasquatch/universal-hooks"; import { gql } from "graphql-request"; import { + PARTNER_CREATED_NAMESPACE, SHOW_CODE_NAMESPACE, + SHOW_PARTNER_MODAL_NAMESPACE, VERIFICATION_EMAIL_NAMESPACE, VERIFICATION_PARENT_NAMESPACE, } from "./keys"; @@ -25,23 +26,36 @@ const USER_LOOKUP = gql` email emailVerified } + impactConnection { + connected + } } } } `; export function useWidgetVerification() { - const userIdentity = useUserIdentity(); - const [showCode, setShowCode] = useParentState({ + const [showCode] = useParentState({ namespace: SHOW_CODE_NAMESPACE, initialValue: false, }); - const [email, setEmail] = useParentState({ + + useParentState({ namespace: VERIFICATION_EMAIL_NAMESPACE, - initialValue: userIdentity?.email, + initialValue: undefined, }); + const setContext = useSetParent(VERIFICATION_PARENT_NAMESPACE); const [loading, setLoading] = useState(true); + const [showPartnerModal, setShowPartnerModal] = useParentState({ + namespace: SHOW_PARTNER_MODAL_NAMESPACE, + initialValue: false, + }); + + const [partnerCreated, setPartnerCreated] = useParentState({ + namespace: PARTNER_CREATED_NAMESPACE, + initialValue: false, + }); const [fetch] = useLazyQuery(USER_LOOKUP); useEffect(() => { @@ -50,8 +64,19 @@ export function useWidgetVerification() { const res = await fetch({}); if (!res || res instanceof Error) throw new Error(); - if (res?.viewer?.emailVerified) setContext(true); - else if (res?.viewer?.managedIdentity?.emailVerified) setContext(true); + // Flow changed to send email -> verify code -> show early partner creation modal + const emailVerified = + res?.viewer?.emailVerified || + res?.viewer?.managedIdentity?.emailVerified; + const isConnected = res?.viewer?.impactConnection?.connected; + + if (isConnected) { + // Partner already created, show widget content + setContext(true); + } else if (emailVerified) { + // Email verified but no partner yet, show partner modal + setShowPartnerModal(true); + } } catch (e) { console.error("Could not fetch user information:", e); } finally { @@ -63,8 +88,26 @@ export function useWidgetVerification() { }, []); const onVerification = () => { + setShowPartnerModal(true); + }; + + const onPartnerModalComplete = () => { + setShowPartnerModal(false); + // Reset the latch so a later partner creation can re-trigger this effect + setPartnerCreated(false); setContext(true); }; - return { showCode, onVerification, loading }; + // The modal signals completion by flipping partnerCreated; advance the flow here + useEffect(() => { + if (partnerCreated) onPartnerModalComplete(); + }, [partnerCreated]); + + return { + showCode, + showPartnerModal, + onVerification, + onPartnerModalComplete, + loading, + }; } diff --git a/packages/mint-components/src/components/tax-and-cash/TaxForm.stories.tsx b/packages/mint-components/src/components/tax-and-cash/TaxForm.stories.tsx index 59639b3e54..d7515ca226 100644 --- a/packages/mint-components/src/components/tax-and-cash/TaxForm.stories.tsx +++ b/packages/mint-components/src/components/tax-and-cash/TaxForm.stories.tsx @@ -30,6 +30,8 @@ const stepOneProps: StoryDemoData = { disabled: false, isPartner: false, isUser: false, + isPartnerLegacy: false, + isUserLegacy: false, loadingError: false, formState: { firstName: "Bob", @@ -151,6 +153,7 @@ const stepTwoProps: StoryDemoData = { disabled: false, loading: false, isPartner: false, + isPartnerLegacy: false, loadingError: false, formState: { checked: undefined, diff --git a/packages/mint-components/src/components/tax-and-cash/data.ts b/packages/mint-components/src/components/tax-and-cash/data.ts index 3f3b78e9e5..1e2f7d2396 100644 --- a/packages/mint-components/src/components/tax-and-cash/data.ts +++ b/packages/mint-components/src/components/tax-and-cash/data.ts @@ -47,6 +47,7 @@ export const GET_USER = gql` user: viewer { ... on User { id + accountId firstName lastName email @@ -71,6 +72,7 @@ export const GET_USER = gql` } impactConnection { connected + connectionStatus user { firstName lastName @@ -170,6 +172,8 @@ export type ImpactUser = { }; export type UserQuery = { user: { + id?: string; + accountId?: string; firstName?: string; lastName?: string; email?: string; @@ -194,6 +198,7 @@ export type UserQuery = { impactConnection: null | { connected: boolean; user: null | ImpactUser; + connectionStatus: "NOT_STARTED" | "STARTED" | "COMPLETED"; publisher: null | ImpactPublisher; }; }; diff --git a/packages/mint-components/src/components/tax-and-cash/phoneExtensions.ts b/packages/mint-components/src/components/tax-and-cash/phoneExtensions.ts index 5e29f65dfe..0961ce8a75 100644 --- a/packages/mint-components/src/components/tax-and-cash/phoneExtensions.ts +++ b/packages/mint-components/src/components/tax-and-cash/phoneExtensions.ts @@ -219,6 +219,10 @@ export const PHONE_EXTENSIONS = { name: "Cuba", dial_code: "+53", }, + CW: { + name: "Curaçao", + dial_code: "+599", + }, CY: { name: "Cyprus", dial_code: "+357", @@ -299,6 +303,10 @@ export const PHONE_EXTENSIONS = { name: "French Polynesia", dial_code: "+689", }, + TF: { + name: "French Southern Territories", + dial_code: "+262", + }, GA: { name: "Gabon", dial_code: "+241", @@ -955,6 +963,10 @@ export const PHONE_EXTENSIONS = { name: "Wallis and Futuna", dial_code: "+681", }, + EH: { + name: "Western Sahara", + dial_code: "+212", + }, YE: { name: "Yemen", dial_code: "+967", diff --git a/packages/mint-components/src/components/tax-and-cash/sqm-banking-info-form/formDefinitions.tsx b/packages/mint-components/src/components/tax-and-cash/sqm-banking-info-form/formDefinitions.tsx index 1b1b7e7406..7c26a8b74f 100644 --- a/packages/mint-components/src/components/tax-and-cash/sqm-banking-info-form/formDefinitions.tsx +++ b/packages/mint-components/src/components/tax-and-cash/sqm-banking-info-form/formDefinitions.tsx @@ -11,6 +11,8 @@ export function getFormMap({ getValidationErrorMessage: (props: { type: "required" | "invalid"; label: string; + errorCode?: string; + fieldName?: string; }) => string; bankCountry?: string; }) { @@ -32,6 +34,8 @@ export function getFormMap({ helpText: getValidationErrorMessage({ type: errors?.inputErrors?.beneficiaryAccountName?.type, label: props.text.beneficiaryAccountNameLabel, + errorCode: errors?.inputErrors?.beneficiaryAccountName?.errorCode, + fieldName: "beneficiaryAccountName", }), })} > @@ -50,6 +54,8 @@ export function getFormMap({ helpText: getValidationErrorMessage({ type: errors?.inputErrors?.bankAccountType?.type, label: props.text.bankAccountTypeLabel, + errorCode: errors?.inputErrors?.bankAccountType?.errorCode, + fieldName: "bankAccountType", }), })} > @@ -77,6 +83,8 @@ export function getFormMap({ helpText: getValidationErrorMessage({ type: errors?.inputErrors?.bankAccountNumber?.type, label: props.text.bankAccountNumberLabel, + errorCode: errors?.inputErrors?.bankAccountNumber?.errorCode, + fieldName: "bankAccountNumber", }), })} > @@ -97,6 +105,8 @@ export function getFormMap({ helpText: getValidationErrorMessage({ type: errors?.inputErrors?.bankAccountNumber?.type, label: props.text.ibanLabel, + errorCode: errors?.inputErrors?.bankAccountNumber?.errorCode, + fieldName: "bankAccountNumber", }), })} > @@ -117,6 +127,8 @@ export function getFormMap({ helpText: getValidationErrorMessage({ type: errors?.inputErrors?.swiftCode?.type, label: props.text.swiftCodeLabel, + errorCode: errors?.inputErrors?.swiftCode?.errorCode, + fieldName: "swiftCode", }), })} > @@ -133,7 +145,7 @@ export function getFormMap({ }, { bankCountry, - } + }, )} name="/routingCode" id="routingCode" @@ -150,8 +162,10 @@ export function getFormMap({ }, { bankCountry, - } + }, ), + errorCode: errors?.inputErrors?.routingCode?.errorCode, + fieldName: "routingCode", }), })} > @@ -171,6 +185,8 @@ export function getFormMap({ helpText: getValidationErrorMessage({ type: errors?.inputErrors?.bankName?.type, label: props.text.bankNameLabel, + errorCode: errors?.inputErrors?.bankName?.errorCode, + fieldName: "bankName", }), })} > @@ -189,6 +205,9 @@ export function getFormMap({ helpText: getValidationErrorMessage({ type: errors?.inputErrors?.beneficiaryClassification?.type, label: props.text.classificationLabel, + errorCode: + errors?.inputErrors?.beneficiaryClassification?.errorCode, + fieldName: "beneficiaryClassification", }), })} > @@ -211,7 +230,7 @@ export function getFormMap({ }, { bankCountry, - } + }, )} type="text" name="/beneficiaryTaxPayerId" @@ -222,6 +241,8 @@ export function getFormMap({ helpText: getValidationErrorMessage({ type: errors?.inputErrors?.beneficiaryTaxPayerId?.type, label: props.text.taxPayerIdLabel, + errorCode: errors?.inputErrors?.taxPayerId?.errorCode, + fieldName: "taxPayerId", }), })} >, @@ -240,6 +261,9 @@ export function getFormMap({ helpText: getValidationErrorMessage({ type: errors?.inputErrors?.beneficiaryClassification?.type, label: props.text.classificationCPFLabel, + errorCode: + errors?.inputErrors?.beneficiaryClassification?.errorCode, + fieldName: "beneficiaryClassification", }), })} > @@ -262,6 +286,8 @@ export function getFormMap({ helpText: getValidationErrorMessage({ type: errors?.inputErrors?.patronymicName?.type, label: props.text.patronymicNameLabel, + errorCode: errors?.inputErrors?.patronymicName?.errorCode, + fieldName: "patronymicName", }), })} > @@ -280,6 +306,8 @@ export function getFormMap({ helpText: getValidationErrorMessage({ type: errors?.inputErrors?.voCode?.type, label: props.text.voCodeLabel, + errorCode: errors?.inputErrors?.voCode?.errorCode, + fieldName: "voCode", }), })} > @@ -299,6 +327,8 @@ export function getFormMap({ helpText: getValidationErrorMessage({ type: errors?.inputErrors?.agencyCode?.type, label: props.text.agencyCodeLabel, + errorCode: errors?.inputErrors?.agencyCode?.errorCode, + fieldName: "agencyCode", }), })} > @@ -318,6 +348,8 @@ export function getFormMap({ helpText: getValidationErrorMessage({ type: errors?.inputErrors?.bankAddress?.type, label: props.text.bankAddressLabel, + errorCode: errors?.inputErrors?.bankAddress?.errorCode, + fieldName: "bankAddress", }), })} >, @@ -333,6 +365,8 @@ export function getFormMap({ helpText: getValidationErrorMessage({ type: errors?.inputErrors?.bankCity?.type, label: props.text.bankCityLabel, + errorCode: errors?.inputErrors?.bankCity?.errorCode, + fieldName: "bankCity", }), })} >, @@ -348,6 +382,8 @@ export function getFormMap({ helpText: getValidationErrorMessage({ type: errors?.inputErrors?.bankState?.type, label: props.text.bankStateLabel, + errorCode: errors?.inputErrors?.bankState?.errorCode, + fieldName: "bankState", }), })} >, @@ -363,6 +399,8 @@ export function getFormMap({ helpText: getValidationErrorMessage({ type: errors?.inputErrors?.bankPostalCode?.type, label: props.text.bankPostalCodeLabel, + errorCode: errors?.inputErrors?.bankPostalCode?.errorCode, + fieldName: "bankPostalCode", }), })} >, @@ -382,6 +420,8 @@ export function getFormMap({ helpText: getValidationErrorMessage({ type: errors?.inputErrors?.branchCode?.type, label: props.text.branchCodeLabel, + errorCode: errors?.inputErrors?.branchCode?.errorCode, + fieldName: "branchCode", }), })} > @@ -400,6 +440,9 @@ export function getFormMap({ helpText: getValidationErrorMessage({ type: errors?.inputErrors?.beneficiaryClassification?.type, label: props.text.classificationLabel, + errorCode: + errors?.inputErrors?.beneficiaryClassification?.errorCode, + fieldName: "beneficiaryClassification", }), })} > @@ -415,5 +458,43 @@ export function getFormMap({ ), }, + 15: { + input: ( + + ), + }, + 16: { + input: ( + + ), + }, }; } diff --git a/packages/mint-components/src/components/tax-and-cash/sqm-banking-info-form/readme.md b/packages/mint-components/src/components/tax-and-cash/sqm-banking-info-form/readme.md index 53516c5e15..356098cd40 100644 --- a/packages/mint-components/src/components/tax-and-cash/sqm-banking-info-form/readme.md +++ b/packages/mint-components/src/components/tax-and-cash/sqm-banking-info-form/readme.md @@ -5,69 +5,91 @@ ## Properties -| Property | Attribute | Description | Type | Default | -| ------------------------------------- | ------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `agencyCodeLabel` | `agency-code-label` | | `string` | `"Agency code"` | -| `backButton` | `back-button` | Text for the back button in the form | `string` | `"Back"` | -| `bankAccountNumberLabel` | `bank-account-number-label` | | `string` | `"Bank account number"` | -| `bankAccountTypeLabel` | `bank-account-type-label` | | `string` | `"Bank account type"` | -| `bankAddressLabel` | `bank-address-label` | | `string` | `"Bank address"` | -| `bankCityLabel` | `bank-city-label` | | `string` | `"Bank city"` | -| `bankLocationLabel` | `bank-location-label` | | `string` | `"Bank country location"` | -| `bankNameLabel` | `bank-name-label` | | `string` | `"Bank Name"` | -| `bankPostalCodeLabel` | `bank-postal-code-label` | | `string` | `"Bank postal code"` | -| `bankStateLabel` | `bank-state-label` | | `string` | `"Bank province/state"` | -| `beneficiaryAccountNameDescription` | `beneficiary-account-name-description` | | `string` | `"The beneficiary name of your bank account. Ensure this matches the name on your tax form."` | -| `beneficiaryAccountNameLabel` | `beneficiary-account-name-label` | | `string` | `"Account holder name"` | -| `branchCodeLabel` | `branch-code-label` | | `string` | `"Branch code"` | -| `businessSelectItemLabel` | `business-select-item-label` | One of three options listed for the classification field | `string` | `"Business"` | -| `checkingSelectItemLabel` | `checking-select-item-label` | | `string` | `"Checking"` | -| `classificationCPFLabel` | `classification-c-p-f-label` | | `string` | `"Classification CPF"` | -| `classificationEntityLabel` | `classification-entity-label` | | `string` | `"Classification Entity"` | -| `classificationLabel` | `classification-label` | Label text for the classification input field | `string` | `"Classification"` | -| `continueButton` | `continue-button` | | `string` | `"Save"` | -| `demoData` | -- | | `{ states?: { showVerification: boolean; step?: string; locale?: string; loading: boolean; saveLoading?: boolean; disabled: boolean; saveDisabled: boolean; hideSteps?: boolean; hasPayPal: boolean; hideBanking?: boolean; hidePayPal?: boolean; hideBalanceThreshold?: boolean; hideFixedDay?: boolean; hideBackButton: boolean; feeCap?: string; isPartner: boolean; paymentMethodFeeLabel?: string; loadingError: boolean; formState: BankingInfoFormData & { paymentMethodChecked?: "toBankAccount" \| "toPayPalAccount"; paymentScheduleChecked?: "BALANCE_THRESHOLD" \| "FIXED_DAY"; errors?: { general?: boolean; inputErrors?: { [field: string]: { type: "required" \| "invalid"; }; }; }; }; bitset?: number; currency?: string; thresholds: string[]; countries?: { countryCode: string; displayName: string; }[]; allCountries?: { countryCode: string; displayName: string; }[]; currentPaymentOption?: any; showInputs?: boolean; bankCountry?: string; countrySearch?: string; email?: string; showModal: boolean; }; slots?: { verificationDialogSlot?: VNode; formInputsSlot?: VNode[]; countryInputSlot?: VNode; paymentMethodSlot?: VNode; paymentThresholdSelectSlot?: VNode; paymentFixedDaySelectSlot?: VNode; paypalInputSlot?: VNode; }; refs?: { formRef: any; }; }` | `undefined` | -| `directlyToBankAccount` | `directly-to-bank-account` | | `string` | `"Directly to my bank account"` | -| `eftWithdrawalLabel` | `eft-withdrawal-label` | Default payment method to the participants’ bank account. | `string` | `"EFT withdrawal (free)"` | -| `fieldInvalidError` | `field-invalid-error` | Displayed under a field when it has an invalid entry. | `string` | `"{fieldName} is invalid"` | -| `fieldRequiredError` | `field-required-error` | Displayed under a field that is missing required information. | `string` | `"{fieldName} is required"` | -| `foreignSelectItemLabel` | `foreign-select-item-label` | One of three options listed for the classification field | `string` | `"Foreign"` | -| `formStep` | `form-step` | | `string` | `"Step {step} of {count}"` | -| `fxWireProcessingFeeLabel` | `fx-wire-processing-fee-label` | | `string` | `"FX Wire (Processing Fee {currency}{defaultFxFee}.00)"` | -| `generalErrorDescription` | `general-error-description` | Part of the alert displayed at the top of the page. | `string` | `"Please review your information and try again. If this problem continues, contact our {supportLink}."` | -| `generalErrorTitle` | `general-error-title` | Part of the alert displayed at the top of the page. | `string` | `"There was a problem submitting your information"` | -| `ibanLabel` | `iban-label` | | `string` | `"IBAN"` | -| `individualSelectItemLabel` | `individual-select-item-label` | One of three options listed for the classification field | `string` | `"Individual"` | -| `isPartnerAlertDescription` | `is-partner-alert-description` | Part of the alert displayed at the top of the page if the participant is already a registered partner on impact.com. | `string` | `"If you don’t recognize this referral program provider or believe this is a mistake, please contact our {supportLink} or sign up for this referral program with a different email."` | -| `isPartnerAlertHeader` | `is-partner-alert-header` | Part of the alert displayed at the top of the page if the participant is already a registered partner on impact.com. | `string` | `"An account with this email already exists with our referral program provider, impact.com"` | -| `loadingErrorAlertDescription` | `loading-error-alert-description` | Part of the alert displayed at the top of the page. | `string` | `"Please refresh the page and try again. If this problem continues, contact Support."` | -| `loadingErrorAlertHeader` | `loading-error-alert-header` | Part of the alert displayed at the top of the page. | `string` | `"There was a problem loading your form"` | -| `modalButtonText` | `modal-button-text` | | `string` | `"I understand, update my information"` | -| `modalDescription` | `modal-description` | | `string` | `"Updating payment information places your account and payouts on hold for up to 48 hours while we verify your change. Payments scheduled during the hold period are skipped."` | -| `modalTitle` | `modal-title` | | `string` | `"Important Note"` | -| `patronymicNameLabel` | `patronymic-name-label` | | `string` | `"Patronymic name"` | -| `payPalInputLabel` | `pay-pal-input-label` | Displayed to participants who choose PayPal as their payout method | `string` | `"PayPal email"` | -| `paymentDayFifteenthOfMonthLabelText` | `payment-day-fifteenth-of-month-label-text` | Label text for the payment day select option for the fifteenth of the month | `string` | `"15th of the month"` | -| `paymentDayFirstOfMonthLabelText` | `payment-day-first-of-month-label-text` | One of two payment day options | `string` | `"1st of the month"` | -| `paymentDaySelectLabel` | `payment-day-select-label` | Let the participant choose what day of the month they’ll get paid | `string` | `"Payment Day"` | -| `paymentMethod` | `payment-method` | | `string` | `"Payment method"` | -| `paymentMethodSubtext` | `payment-method-subtext` | | `string` | `"Payouts will be sent from our referral program provider, impact.com."` | -| `paymentSchedule` | `payment-schedule` | | `string` | `"Payment schedule"` | -| `paymentScheduleBalanceThreshold` | `payment-schedule-balance-threshold` | | `string` | `"Pay me when my balance reaches a threshold"` | -| `paymentScheduleFixedDay` | `payment-schedule-fixed-day` | | `string` | `"Pay me on a fixed day of the month"` | -| `paymentThresholdSelectLabel` | `payment-threshold-select-label` | Participant use this field to select the balance at which they want to be paid | `string` | `"Payment Threshold"` | -| `routingCodeLabel` | `routing-code-label` | | `string` | `"{bankCountry, select, AU {BSB number} CA {Routing number} CZ {Bank code} HK {Clearing code} SG {Clearing code} US {ABA routing number} NZ {BSB number} ZA {Bank/Branch number} IN {IFSC} CNY {CNAPS} other {Routing code} }"` | -| `savingsSelectItemLabel` | `savings-select-item-label` | | `string` | `"Savings"` | -| `searchForCountryText` | `search-for-country-text` | Placeholder text displayed in the country search dropdown | `string` | `"Search for country.."` | -| `supportLink` | `support-link` | | `string` | `"support team"` | -| `swiftCodeLabel` | `swift-code-label` | | `string` | `"SWIFT code"` | -| `taxAndPayouts` | `tax-and-payouts` | | `string` | `"Payouts"` | -| `taxAndPayoutsDescription` | `tax-and-payouts-description` | Displayed at the top of the page on all set up steps. | `string` | `"Submit your tax documents and add your banking information to receive your rewards."` | -| `taxPayerIdLabel` | `tax-payer-id-label` | | `string` | `"{country, select, AR {CUIT/CUIL} KR {Classification ID} other { Beneficiary INN } }"` | -| `toPayPalAccount` | `to-pay-pal-account` | | `string` | `"PayPal (2% processing fee capped to {feeCap})"` | -| `verifyEmailDescriptionText` | `verify-email-description-text` | | `string` | `"Verify your email to update your payment settings. Enter the code sent to {email} from our referral provider, impact.com."` | -| `verifyEmailHeaderText` | `verify-email-header-text` | Text for verify email dialog | `string` | `"Verify your email"` | -| `voCodeLabel` | `vo-code-label` | | `string` | `"VO code"` | +| Property | Attribute | Description | Type | Default | +| ------------------------------------- | ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `agencyCodeError` | `agency-code-error` | Error messages for the agency code field. Supports error codes: empty, alphanumeric, tooShort | `string` | `"{errorCode, select, empty {Agency code is required} alphanumeric {Agency code must contain only letters and numbers} tooShort {Agency code must be at least 5 characters} other {{errorCode}}}"` | +| `agencyCodeLabel` | `agency-code-label` | | `string` | `"Agency code"` | +| `backButton` | `back-button` | Text for the back button in the form | `string` | `"Back"` | +| `bankAccountNumberError` | `bank-account-number-error` | Error messages for the bank account number / IBAN field. Supports error codes: empty, invalidUk, invalid, ibanEmpty, ibanAlphanumeric, ibanInvalid, ibanCountryMismatch | `string` | `"{errorCode, select, empty {Account number is required} invalidUk {Please enter a valid UK account number} invalid {Account number is invalid} ibanEmpty {IBAN is required} ibanAlphanumeric {IBAN must contain only letters and numbers} ibanInvalid {IBAN is invalid} ibanCountryMismatch {UK accounts must use an IBAN starting with GB} other {{errorCode}}}"` | +| `bankAccountNumberLabel` | `bank-account-number-label` | | `string` | `"Bank account number"` | +| `bankAccountTypeError` | `bank-account-type-error` | Error messages for the bank account type field. Supports error codes: empty | `string` | `"{errorCode, select, empty {Bank account type is required} other {{errorCode}}}"` | +| `bankAccountTypeLabel` | `bank-account-type-label` | | `string` | `"Bank account type"` | +| `bankAddressError` | `bank-address-error` | Error messages for the bank address field. Supports error codes: empty | `string` | `"{errorCode, select, empty {Bank address is required} other {{errorCode}}}"` | +| `bankAddressLabel` | `bank-address-label` | | `string` | `"Bank address"` | +| `bankCityError` | `bank-city-error` | Error messages for the bank city field. Supports error codes: empty | `string` | `"{errorCode, select, empty {Bank city is required} other {{errorCode}}}"` | +| `bankCityLabel` | `bank-city-label` | | `string` | `"Bank city"` | +| `bankLocationLabel` | `bank-location-label` | | `string` | `"Bank country location"` | +| `bankNameError` | `bank-name-error` | Error messages for the bank name field. Supports error codes: empty | `string` | `"{errorCode, select, empty {Bank name is required} other {{errorCode}}}"` | +| `bankNameLabel` | `bank-name-label` | | `string` | `"Bank Name"` | +| `bankPostalCodeError` | `bank-postal-code-error` | Error messages for the bank postal code field. Supports error codes: empty | `string` | `"{errorCode, select, empty {Bank postal code is required} other {{errorCode}}}"` | +| `bankPostalCodeLabel` | `bank-postal-code-label` | | `string` | `"Bank postal code"` | +| `bankStateError` | `bank-state-error` | Error messages for the bank province/state field. Supports error codes: empty | `string` | `"{errorCode, select, empty {Bank province/state is required} other {{errorCode}}}"` | +| `bankStateLabel` | `bank-state-label` | | `string` | `"Bank province/state"` | +| `beneficiaryAccountNameDescription` | `beneficiary-account-name-description` | | `string` | `"The beneficiary name of your bank account. Ensure this matches the name on your tax form."` | +| `beneficiaryAccountNameError` | `beneficiary-account-name-error` | Error messages for the beneficiary / account holder name field. Supports error codes: empty, invalidCharacters, numeric, tooLong, nonEnglish, businessNameMismatch, nameMismatch, businessPayeeMismatch, payeeMismatch | `string` | `"{errorCode, select, empty {Account holder name is required} invalidCharacters {Account holder name contains invalid characters} numeric {Account holder name cannot be purely numeric} tooLong {Account holder name must be 70 characters or fewer} nonEnglish {Account holder name must contain only English characters for this currency} businessNameMismatch {Beneficiary name must match the name on your tax document} nameMismatch {Beneficiary name must match the name on your tax document} businessPayeeMismatch {Payee name must match the name on your tax document} payeeMismatch {Payee name must match the name on your tax document} other {{errorCode}}}"` | +| `beneficiaryAccountNameLabel` | `beneficiary-account-name-label` | | `string` | `"Account holder name"` | +| `branchCodeError` | `branch-code-error` | Error messages for the branch code field. Supports error codes: invalid | `string` | `"{errorCode, select, invalid {Branch code is invalid} other {{errorCode}}}"` | +| `branchCodeLabel` | `branch-code-label` | | `string` | `"Branch code"` | +| `branchNameError` | `branch-name-error` | Error messages for the branch name field. Supports error codes: empty | `string` | `"{errorCode, select, empty {Branch name is required} other {{errorCode}}}"` | +| `branchNameLabel` | `branch-name-label` | | `string` | `"Branch name"` | +| `businessSelectItemLabel` | `business-select-item-label` | One of three options listed for the classification field | `string` | `"Business"` | +| `checkingSelectItemLabel` | `checking-select-item-label` | | `string` | `"Checking"` | +| `classificationCPFLabel` | `classification-c-p-f-label` | | `string` | `"Classification CPF"` | +| `classificationCodeError` | `classification-code-error` | Error messages for the classification code field. Supports error codes: empty, invalidKzt | `string` | `"{errorCode, select, empty {Classification code is required} invalidKzt {Classification code must be exactly 2 characters} other {{errorCode}}}"` | +| `classificationCodeLabel` | `classification-code-label` | | `string` | `"KBE code"` | +| `classificationEntityLabel` | `classification-entity-label` | | `string` | `"Classification Entity"` | +| `classificationLabel` | `classification-label` | Label text for the classification input field | `string` | `"Classification"` | +| `continueButton` | `continue-button` | | `string` | `"Save"` | +| `demoData` | -- | | `{ states?: { showVerification: boolean; step?: string; locale?: string; loading: boolean; saveLoading?: boolean; disabled: boolean; saveDisabled: boolean; hideSteps?: boolean; hasPayPal: boolean; hideBanking?: boolean; hidePayPal?: boolean; hideBalanceThreshold?: boolean; hideFixedDay?: boolean; hideBackButton: boolean; feeCap?: string; isPartner: boolean; paymentMethodFeeLabel?: string; loadingError: boolean; formState: BankingInfoFormData & { paymentMethodChecked?: "toBankAccount" \| "toPayPalAccount"; paymentScheduleChecked?: "BALANCE_THRESHOLD" \| "FIXED_DAY"; errors?: { general?: boolean; inputErrors?: { [field: string]: { type: "required" \| "invalid"; errorCode?: string; }; }; }; }; bitset?: number; currency?: string; thresholds: string[]; countries?: { countryCode: string; displayName: string; }[]; allCountries?: { countryCode: string; displayName: string; }[]; currentPaymentOption?: any; showInputs?: boolean; bankCountry?: string; countrySearch?: string; email?: string; showModal: boolean; }; slots?: { verificationDialogSlot?: VNode; formInputsSlot?: VNode[]; countryInputSlot?: VNode; paymentMethodSlot?: VNode; paymentThresholdSelectSlot?: VNode; paymentFixedDaySelectSlot?: VNode; paypalInputSlot?: VNode; }; refs?: { formRef: any; }; }` | `undefined` | +| `directlyToBankAccount` | `directly-to-bank-account` | | `string` | `"Directly to my bank account"` | +| `eftWithdrawalLabel` | `eft-withdrawal-label` | Default payment method to the participants’ bank account. | `string` | `"EFT withdrawal (free)"` | +| `fieldInvalidError` | `field-invalid-error` | Displayed under a field when it has an invalid entry. | `string` | `"{fieldName} is invalid"` | +| `fieldRequiredError` | `field-required-error` | Displayed under a field that is missing required information. | `string` | `"{fieldName} is required"` | +| `foreignSelectItemLabel` | `foreign-select-item-label` | One of three options listed for the classification field | `string` | `"Foreign"` | +| `formStep` | `form-step` | | `string` | `"Step {step} of {count}"` | +| `fxWireProcessingFeeLabel` | `fx-wire-processing-fee-label` | | `string` | `"FX Wire (Processing Fee {currency}{defaultFxFee}.00)"` | +| `generalErrorDescription` | `general-error-description` | Part of the alert displayed at the top of the page. | `string` | `"Please review your information and try again. If this problem continues, contact our {supportLink}."` | +| `generalErrorTitle` | `general-error-title` | Part of the alert displayed at the top of the page. | `string` | `"There was a problem submitting your information"` | +| `ibanLabel` | `iban-label` | | `string` | `"IBAN"` | +| `individualSelectItemLabel` | `individual-select-item-label` | One of three options listed for the classification field | `string` | `"Individual"` | +| `isPartnerAlertDescription` | `is-partner-alert-description` | Part of the alert displayed at the top of the page if the participant is already a registered partner on impact.com. | `string` | `"If you don’t recognize this referral program provider or believe this is a mistake, please contact our {supportLink} or sign up for this referral program with a different email."` | +| `isPartnerAlertHeader` | `is-partner-alert-header` | Part of the alert displayed at the top of the page if the participant is already a registered partner on impact.com. | `string` | `"An account with this email already exists with our referral program provider, impact.com"` | +| `loadingErrorAlertDescription` | `loading-error-alert-description` | Part of the alert displayed at the top of the page. | `string` | `"Please refresh the page and try again. If this problem continues, contact Support."` | +| `loadingErrorAlertHeader` | `loading-error-alert-header` | Part of the alert displayed at the top of the page. | `string` | `"There was a problem loading your form"` | +| `modalButtonText` | `modal-button-text` | | `string` | `"I understand, update my information"` | +| `modalDescription` | `modal-description` | | `string` | `"Updating payment information places your account and payouts on hold for up to 48 hours while we verify your change. Payments scheduled during the hold period are skipped."` | +| `modalTitle` | `modal-title` | | `string` | `"Important Note"` | +| `patronymicNameError` | `patronymic-name-error` | Error messages for the patronymic name field. Supports error codes: empty, alphanumeric | `string` | `"{errorCode, select, empty {Patronymic name is required} alphanumeric {Patronymic name must contain only letters and numbers} other {{errorCode}}}"` | +| `patronymicNameLabel` | `patronymic-name-label` | | `string` | `"Patronymic name"` | +| `payPalInputLabel` | `pay-pal-input-label` | Displayed to participants who choose PayPal as their payout method | `string` | `"PayPal email"` | +| `paymentDayError` | `payment-day-error` | Error messages for the payment day field. Supports error codes: empty, invalid | `string` | `"{errorCode, select, empty {Payment day is required} invalid {Payment day must be the 1st or the 15th} other {{errorCode}}}"` | +| `paymentDayFifteenthOfMonthLabelText` | `payment-day-fifteenth-of-month-label-text` | Label text for the payment day select option for the fifteenth of the month | `string` | `"15th of the month"` | +| `paymentDayFirstOfMonthLabelText` | `payment-day-first-of-month-label-text` | One of two payment day options | `string` | `"1st of the month"` | +| `paymentDaySelectLabel` | `payment-day-select-label` | Let the participant choose what day of the month they’ll get paid | `string` | `"Payment Day"` | +| `paymentMethod` | `payment-method` | | `string` | `"Payment method"` | +| `paymentMethodSubtext` | `payment-method-subtext` | | `string` | `"Payouts will be sent from our referral program provider, impact.com."` | +| `paymentSchedule` | `payment-schedule` | | `string` | `"Payment schedule"` | +| `paymentScheduleBalanceThreshold` | `payment-schedule-balance-threshold` | | `string` | `"Pay me when my balance reaches a threshold"` | +| `paymentScheduleFixedDay` | `payment-schedule-fixed-day` | | `string` | `"Pay me on a fixed day of the month"` | +| `paymentThresholdError` | `payment-threshold-error` | Error messages for the payment threshold field. Supports error codes: empty, invalid | `string` | `"{errorCode, select, empty {Payment threshold is required} invalid {Payment threshold is invalid} other {{errorCode}}}"` | +| `paymentThresholdSelectLabel` | `payment-threshold-select-label` | Participant use this field to select the balance at which they want to be paid | `string` | `"Payment Threshold"` | +| `paypalEmailError` | `paypal-email-error` | Error messages for the PayPal email field. Supports error codes: empty, unsupportedCurrency, invalidEmail, verificationIncomplete | `string` | `"{errorCode, select, empty {PayPal email is required} unsupportedCurrency {PayPal is not supported for this currency} invalidEmail {Please enter a valid email address} verificationIncomplete {PayPal verification is not complete} other {{errorCode}}}"` | +| `routingCodeError` | `routing-code-error` | Error messages for the routing code / sort code / BSB field. Supports error codes: invalidBsb, invalidSortCode, empty, invalid | `string` | `"{errorCode, select, invalidBsb {Please enter a valid BSB number} invalidSortCode {Please enter a valid sort code} empty {Routing number is required} invalid {Routing number is invalid} other {{errorCode}}}"` | +| `routingCodeLabel` | `routing-code-label` | | `string` | `"{bankCountry, select, AU {BSB number} CA {Routing number} CZ {Bank code} HK {Clearing code} SG {Clearing code} US {ABA routing number} NZ {BSB number} ZA {Bank/Branch number} IN {IFSC} CNY {CNAPS} other {Routing code} }"` | +| `savingsSelectItemLabel` | `savings-select-item-label` | | `string` | `"Savings"` | +| `searchForCountryText` | `search-for-country-text` | Placeholder text displayed in the country search dropdown | `string` | `"Search for country.."` | +| `supportLink` | `support-link` | | `string` | `"support team"` | +| `swiftCodeError` | `swift-code-error` | Error messages for the SWIFT / BIC code field. Supports error codes: empty, alphanumeric, invalid | `string` | `"{errorCode, select, empty {SWIFT/BIC code is required} alphanumeric {SWIFT/BIC code must contain only letters and numbers} invalid {SWIFT/BIC code is invalid} other {{errorCode}}}"` | +| `swiftCodeLabel` | `swift-code-label` | | `string` | `"SWIFT code"` | +| `taxAndPayouts` | `tax-and-payouts` | | `string` | `"Payouts"` | +| `taxAndPayoutsDescription` | `tax-and-payouts-description` | Displayed at the top of the page on all set up steps. | `string` | `"Submit your tax documents and add your banking information to receive your rewards."` | +| `taxPayerIdError` | `tax-payer-id-error` | Error messages for the tax payer ID / classification entity field. Supports error codes: empty, emptyAr, emptyKr, alphanumeric, alphanumericAr, alphanumericKr, invalid, invalidAr, invalidKr, invalidKzt, cnpjTooShort, cpfTooShort | `string` | `"{errorCode, select, empty {Tax payer ID is required} emptyAr {CUIT/CUIL is required} emptyKr {Classification ID is required} alphanumeric {Tax payer ID must contain only letters and numbers} alphanumericAr {CUIT/CUIL must contain only letters and numbers} alphanumericKr {Classification ID must contain only letters and numbers} invalid {Tax payer ID is invalid} invalidAr {CUIT/CUIL must be 11 characters} invalidKr {Classification ID length is invalid} invalidKzt {Tax payer ID must be 12 characters for KZT} cnpjTooShort {CNPJ must be at least 14 characters} cpfTooShort {CPF must be at least 11 characters} other {{errorCode}}}"` | +| `taxPayerIdLabel` | `tax-payer-id-label` | | `string` | `"{country, select, AR {CUIT/CUIL} KR {Classification ID} other { Beneficiary INN } }"` | +| `toPayPalAccount` | `to-pay-pal-account` | | `string` | `"PayPal (2% processing fee capped to {feeCap})"` | +| `verifyEmailDescriptionText` | `verify-email-description-text` | | `string` | `"Verify your email to update your payment settings. Enter the code sent to {email} from our referral provider, impact.com."` | +| `verifyEmailHeaderText` | `verify-email-header-text` | Text for verify email dialog | `string` | `"Verify your email"` | +| `voCodeError` | `vo-code-error` | Error messages for the VO code field. Supports error codes: empty, alphanumeric | `string` | `"{errorCode, select, empty {VO code is required} alphanumeric {VO code must contain only letters and numbers} other {{errorCode}}}"` | +| `voCodeLabel` | `vo-code-label` | | `string` | `"VO code"` | ## Dependencies diff --git a/packages/mint-components/src/components/tax-and-cash/sqm-banking-info-form/sqm-banking-info-form-view.tsx b/packages/mint-components/src/components/tax-and-cash/sqm-banking-info-form/sqm-banking-info-form-view.tsx index 806eaf1139..4cd53a7e17 100644 --- a/packages/mint-components/src/components/tax-and-cash/sqm-banking-info-form/sqm-banking-info-form-view.tsx +++ b/packages/mint-components/src/components/tax-and-cash/sqm-banking-info-form/sqm-banking-info-form-view.tsx @@ -32,6 +32,7 @@ export interface BankingInfoFormViewProps { inputErrors?: { [field: string]: { type: "required" | "invalid"; + errorCode?: string; }; }; }; @@ -60,10 +61,10 @@ export interface BankingInfoFormViewProps { callbacks: { onVerificationHide: () => void; setPaymentMethodChecked: ( - paymentMethodChecked: "toBankAccount" | "toPayPalAccount" + paymentMethodChecked: "toBankAccount" | "toPayPalAccount", ) => void; setPaymentScheduleChecked: ( - paymentMethodChecked: "BALANCE_THRESHOLD" | "FIXED_DAY" + paymentMethodChecked: "BALANCE_THRESHOLD" | "FIXED_DAY", ) => void; onSubmit: (props: any) => Promise; onBack: (props: any) => void; @@ -105,6 +106,7 @@ export interface BankingInfoFormViewProps { generalTitle: string; generalDescription: string; }; + errorMessages?: { [field: string]: string }; }; refs: { formRef: any; @@ -293,7 +295,7 @@ export const BankingInfoFormView = (props: BankingInfoFormViewProps) => { const getLoadingSkeleton = ( checkedValue: "toBankAccount" | "toPayPalAccount" | undefined, - inputNumber?: number + inputNumber?: number, ) => { let inputNumArray = Array(inputNumber).fill(0); @@ -381,7 +383,7 @@ export const BankingInfoFormView = (props: BankingInfoFormViewProps) => { id: "formStep", defaultMessage: text.formStep, }, - { step: states.step, count: FORM_STEPS } + { step: states.step, count: FORM_STEPS }, )}

)} @@ -407,7 +409,7 @@ export const BankingInfoFormView = (props: BankingInfoFormViewProps) => { {text.supportLink} ), - } + }, )} )} @@ -429,7 +431,7 @@ export const BankingInfoFormView = (props: BankingInfoFormViewProps) => { {text.supportLink} ), - } + }, )} )} @@ -468,7 +470,7 @@ export const BankingInfoFormView = (props: BankingInfoFormViewProps) => { {states.loading ? getLoadingSkeleton( "toBankAccount", - slots.formInputsSlot.length + 3 + slots.formInputsSlot.length + 3, ) : [ slots.countryInputSlot, @@ -494,7 +496,7 @@ export const BankingInfoFormView = (props: BankingInfoFormViewProps) => { id: "paypal-input-label", defaultMessage: text.toPayPalAccount, }, - { feeCap: states.feeCap } + { feeCap: states.feeCap }, )} )} diff --git a/packages/mint-components/src/components/tax-and-cash/sqm-banking-info-form/sqm-banking-info-form.feature b/packages/mint-components/src/components/tax-and-cash/sqm-banking-info-form/sqm-banking-info-form.feature index 7c4715b640..1d2708628e 100644 --- a/packages/mint-components/src/components/tax-and-cash/sqm-banking-info-form/sqm-banking-info-form.feature +++ b/packages/mint-components/src/components/tax-and-cash/sqm-banking-info-form/sqm-banking-info-form.feature @@ -47,6 +47,7 @@ Feature: Banking Information Form | EUR | Japan | Beneficiary account name, Bank account number, SWIFT code | | JPY | Japan | Beneficiary account name, Bank account type, Bank account number, SWIFT code, Bank name, Branch code | | MAD | Western Sahara, Morocco | Beneficiary account name, Bank account number, SWIFT code | + | KZT | Kazakhstan | Beneficiary account name, IBAN, SWIFT code, Classification, Beneficiary INN, KBE code | @minutia Scenario Outline: Bank country dropdown list is dynamic depending on the partner's currency @@ -56,10 +57,7 @@ Feature: Banking Information Form Examples: | currency | countries | | USD | United States, Canada, Spain, Ireland, United Kingdom, Japan | - | GBP | United States, Canada, Spain, Ireland, United Kingdom, Japan | - | AUD | United States, Canada, Spain, Ireland, United Kingdom, Japan | | CAD | Canada | - | EUR | United States, Canada, Spain, Ireland, United Kingdom, Japan | | JPY | Japan | @minutia diff --git a/packages/mint-components/src/components/tax-and-cash/sqm-banking-info-form/sqm-banking-info-form.tsx b/packages/mint-components/src/components/tax-and-cash/sqm-banking-info-form/sqm-banking-info-form.tsx index 2a674f973d..919e065355 100644 --- a/packages/mint-components/src/components/tax-and-cash/sqm-banking-info-form/sqm-banking-info-form.tsx +++ b/packages/mint-components/src/components/tax-and-cash/sqm-banking-info-form/sqm-banking-info-form.tsx @@ -210,12 +210,22 @@ export class BankingInfoForm { */ @Prop() branchCodeLabel: string = "Branch code"; + /** + * @uiName Branch name field label + */ + @Prop() branchNameLabel: string = "Branch name"; + /** * Label text for the classification input field * @uiName Classification field label */ @Prop() classificationLabel: string = "Classification"; + /** + * @uiName Classification code field label + */ + @Prop() classificationCodeLabel: string = "KBE code"; + /** * @uiName Taxpayer ID field label */ @@ -350,6 +360,197 @@ export class BankingInfoForm { */ @Prop() modalButtonText: string = "I understand, update my information"; + // ────────────────────────────────────────────────────────────────── + // Per-field validation error messages + // Each prop uses ICU select on {errorCode} to pick the right message. + // Error codes are short frontend keys mapped from the API error codes. + // The `other` branch displays the raw API message directly via {errorCode}, + // which is already human-readable English (e.g. "Invalid Routing Code"). + // ────────────────────────────────────────────────────────────────── + + /** + * Error messages for the beneficiary / account holder name field. + * Supports error codes: empty, invalidCharacters, numeric, tooLong, + * nonEnglish, businessNameMismatch, nameMismatch, businessPayeeMismatch, payeeMismatch + * @uiName Beneficiary account name error + * @uiWidget textArea + */ + @Prop() beneficiaryAccountNameError: string = + "{errorCode, select, empty {Account holder name is required} invalidCharacters {Account holder name contains invalid characters} numeric {Account holder name cannot be purely numeric} tooLong {Account holder name must be 70 characters or fewer} nonEnglish {Account holder name must contain only English characters for this currency} businessNameMismatch {Beneficiary name must match the name on your tax document} nameMismatch {Beneficiary name must match the name on your tax document} businessPayeeMismatch {Payee name must match the name on your tax document} payeeMismatch {Payee name must match the name on your tax document} other {{errorCode}}}"; + + /** + * Error messages for the bank account number / IBAN field. + * Supports error codes: empty, invalidUk, invalid, ibanEmpty, + * ibanAlphanumeric, ibanInvalid, ibanCountryMismatch + * @uiName Bank account number / IBAN error + * @uiWidget textArea + */ + @Prop() bankAccountNumberError: string = + "{errorCode, select, empty {Account number is required} invalidUk {Please enter a valid UK account number} invalid {Account number is invalid} ibanEmpty {IBAN is required} ibanAlphanumeric {IBAN must contain only letters and numbers} ibanInvalid {IBAN is invalid} ibanCountryMismatch {UK accounts must use an IBAN starting with GB} other {{errorCode}}}"; + + /** + * Error messages for the routing code / sort code / BSB field. + * Supports error codes: invalidBsb, invalidSortCode, empty, invalid + * @uiName Routing code error + * @uiWidget textArea + */ + @Prop() routingCodeError: string = + "{errorCode, select, invalidBsb {Please enter a valid BSB number} invalidSortCode {Please enter a valid sort code} empty {Routing number is required} invalid {Routing number is invalid} other {{errorCode}}}"; + + /** + * Error messages for the SWIFT / BIC code field. + * Supports error codes: empty, alphanumeric, invalid + * @uiName SWIFT code error + * @uiWidget textArea + */ + @Prop() swiftCodeError: string = + "{errorCode, select, empty {SWIFT/BIC code is required} alphanumeric {SWIFT/BIC code must contain only letters and numbers} invalid {SWIFT/BIC code is invalid} other {{errorCode}}}"; + + /** + * Error messages for the bank account type field. + * Supports error codes: empty + * @uiName Bank account type error + * @uiWidget textArea + */ + @Prop() bankAccountTypeError: string = + "{errorCode, select, empty {Bank account type is required} other {{errorCode}}}"; + + /** + * Error messages for the bank name field. + * Supports error codes: empty + * @uiName Bank name error + * @uiWidget textArea + */ + @Prop() bankNameError: string = + "{errorCode, select, empty {Bank name is required} other {{errorCode}}}"; + + /** + * Error messages for the tax payer ID / classification entity field. + * Supports error codes: empty, emptyAr, emptyKr, alphanumeric, alphanumericAr, + * alphanumericKr, invalid, invalidAr, invalidKr, invalidKzt, cnpjTooShort, cpfTooShort + * @uiName Tax payer ID error + * @uiWidget textArea + */ + @Prop() taxPayerIdError: string = + "{errorCode, select, empty {Tax payer ID is required} emptyAr {CUIT/CUIL is required} emptyKr {Classification ID is required} alphanumeric {Tax payer ID must contain only letters and numbers} alphanumericAr {CUIT/CUIL must contain only letters and numbers} alphanumericKr {Classification ID must contain only letters and numbers} invalid {Tax payer ID is invalid} invalidAr {CUIT/CUIL must be 11 characters} invalidKr {Classification ID length is invalid} invalidKzt {Tax payer ID must be 12 characters for KZT} cnpjTooShort {CNPJ must be at least 14 characters} cpfTooShort {CPF must be at least 11 characters} other {{errorCode}}}"; + + /** + * Error messages for the patronymic name field. + * Supports error codes: empty, alphanumeric + * @uiName Patronymic name error + * @uiWidget textArea + */ + @Prop() patronymicNameError: string = + "{errorCode, select, empty {Patronymic name is required} alphanumeric {Patronymic name must contain only letters and numbers} other {{errorCode}}}"; + + /** + * Error messages for the VO code field. + * Supports error codes: empty, alphanumeric + * @uiName VO code error + * @uiWidget textArea + */ + @Prop() voCodeError: string = + "{errorCode, select, empty {VO code is required} alphanumeric {VO code must contain only letters and numbers} other {{errorCode}}}"; + + /** + * Error messages for the agency code field. + * Supports error codes: empty, alphanumeric, tooShort + * @uiName Agency code error + * @uiWidget textArea + */ + @Prop() agencyCodeError: string = + "{errorCode, select, empty {Agency code is required} alphanumeric {Agency code must contain only letters and numbers} tooShort {Agency code must be at least 5 characters} other {{errorCode}}}"; + + /** + * Error messages for the bank address field. + * Supports error codes: empty + * @uiName Bank address error + * @uiWidget textArea + */ + @Prop() bankAddressError: string = + "{errorCode, select, empty {Bank address is required} other {{errorCode}}}"; + + /** + * Error messages for the bank city field. + * Supports error codes: empty + * @uiName Bank city error + * @uiWidget textArea + */ + @Prop() bankCityError: string = + "{errorCode, select, empty {Bank city is required} other {{errorCode}}}"; + + /** + * Error messages for the bank province/state field. + * Supports error codes: empty + * @uiName Bank province/state error + * @uiWidget textArea + */ + @Prop() bankStateError: string = + "{errorCode, select, empty {Bank province/state is required} other {{errorCode}}}"; + + /** + * Error messages for the bank postal code field. + * Supports error codes: empty + * @uiName Bank postal code error + * @uiWidget textArea + */ + @Prop() bankPostalCodeError: string = + "{errorCode, select, empty {Bank postal code is required} other {{errorCode}}}"; + + /** + * Error messages for the branch code field. + * Supports error codes: invalid + * @uiName Branch code error + * @uiWidget textArea + */ + @Prop() branchCodeError: string = + "{errorCode, select, invalid {Branch code is invalid} other {{errorCode}}}"; + + /** + * Error messages for the branch name field. + * Supports error codes: empty + * @uiName Branch name error + * @uiWidget textArea + */ + @Prop() branchNameError: string = + "{errorCode, select, empty {Branch name is required} other {{errorCode}}}"; + + /** + * Error messages for the classification code field. + * Supports error codes: empty, invalidKzt + * @uiName Classification code error + * @uiWidget textArea + */ + @Prop() classificationCodeError: string = + "{errorCode, select, empty {Classification code is required} invalidKzt {Classification code must be exactly 2 characters} other {{errorCode}}}"; + + /** + * Error messages for the PayPal email field. + * Supports error codes: empty, unsupportedCurrency, invalidEmail, verificationIncomplete + * @uiName PayPal email error + * @uiWidget textArea + */ + @Prop() paypalEmailError: string = + "{errorCode, select, empty {PayPal email is required} unsupportedCurrency {PayPal is not supported for this currency} invalidEmail {Please enter a valid email address} verificationIncomplete {PayPal verification is not complete} other {{errorCode}}}"; + + /** + * Error messages for the payment threshold field. + * Supports error codes: empty, invalid + * @uiName Payment threshold error + * @uiWidget textArea + */ + @Prop() paymentThresholdError: string = + "{errorCode, select, empty {Payment threshold is required} invalid {Payment threshold is invalid} other {{errorCode}}}"; + + /** + * Error messages for the payment day field. + * Supports error codes: empty, invalid + * @uiName Payment day error + * @uiWidget textArea + */ + @Prop() paymentDayError: string = + "{errorCode, select, empty {Payment day is required} invalid {Payment day must be the 1st or the 15th} other {{errorCode}}}"; + /** * @undocumented * @uiType object @@ -373,6 +574,28 @@ export class BankingInfoForm { loadingErrorAlertDescription: props.loadingErrorAlertDescription, loadingErrorAlertHeader: props.loadingErrorAlertHeader, }, + errorMessages: { + beneficiaryAccountName: props.beneficiaryAccountNameError, + bankAccountNumber: props.bankAccountNumberError, + routingCode: props.routingCodeError, + swiftCode: props.swiftCodeError, + bankAccountType: props.bankAccountTypeError, + bankName: props.bankNameError, + taxPayerId: props.taxPayerIdError, + patronymicName: props.patronymicNameError, + voCode: props.voCodeError, + agencyCode: props.agencyCodeError, + bankAddress: props.bankAddressError, + bankCity: props.bankCityError, + bankState: props.bankStateError, + bankPostalCode: props.bankPostalCodeError, + branchCode: props.branchCodeError, + branchName: props.branchNameError, + beneficiaryClassification: props.classificationCodeError, + paypalEmailAddress: props.paypalEmailError, + paymentThreshold: props.paymentThresholdError, + paymentDay: props.paymentDayError, + }, }; } @@ -389,10 +612,32 @@ export class BankingInfoForm { function getValidationErrorMessage({ type, label, + errorCode, + fieldName, }: { type: "required" | "invalid"; label: string; + errorCode?: string; + fieldName?: string; }) { + // If we have a specific error code from the API, try to use + // the per-field ICU error message template for a rich message + if (type === "invalid" && errorCode && fieldName) { + const errorTemplate = props.text.errorMessages?.[fieldName]; + if (errorTemplate) { + return intl.formatMessage( + { + id: `fieldError-${fieldName}-${errorCode}`, + defaultMessage: errorTemplate, + }, + { + errorCode, + fieldName: label, + }, + ); + } + } + if (type === "required") { return intl.formatMessage( { @@ -401,7 +646,7 @@ export class BankingInfoForm { }, { fieldName: label, - } + }, ); } if (type === "invalid") { @@ -412,7 +657,7 @@ export class BankingInfoForm { }, { fieldName: label, - } + }, ); } return ""; @@ -471,7 +716,7 @@ export class BankingInfoForm { }, { email: props.states.email, - } + }, ); const vanillaStyle = ` @@ -525,6 +770,8 @@ export class BankingInfoForm { helpText: getValidationErrorMessage({ type: errors?.inputErrors?.bankCountry?.type, label: props.text.bankLocationLabel, + errorCode: errors?.inputErrors?.bankCountry?.errorCode, + fieldName: "bankCountry", }), })} > @@ -578,6 +825,8 @@ export class BankingInfoForm { helpText: getValidationErrorMessage({ type: errors?.inputErrors?.paymentThreshold?.type, label: props.text.paymentThresholdSelectLabel, + errorCode: errors?.inputErrors?.paymentThreshold?.errorCode, + fieldName: "paymentThreshold", }), })} > @@ -601,6 +850,8 @@ export class BankingInfoForm { helpText: getValidationErrorMessage({ type: errors?.inputErrors?.paymentDay?.type, label: props.text.paymentDaySelectLabel, + errorCode: errors?.inputErrors?.paymentDay?.errorCode, + fieldName: "paymentDay", }), })} > @@ -629,6 +880,10 @@ export class BankingInfoForm { type: props.states.formState?.errors?.inputErrors ?.paypalEmailAddress?.type, label: props.text.payPalInputLabel, + errorCode: + props.states.formState?.errors?.inputErrors + ?.paypalEmailAddress?.errorCode, + fieldName: "paypalEmailAddress", }), })} > @@ -641,7 +896,7 @@ export class BankingInfoForm { } function useDemoBankingInfoForm( - props: BankingInfoForm + props: BankingInfoForm, ): BankingInfoFormViewProps { const defaultPaymentMethodChecked = props.demoData?.states?.formState?.paymentMethodChecked; @@ -665,7 +920,7 @@ function useDemoBankingInfoForm( (paymentOption) => { if (paymentOption.countryCode === bankCountry) return true; return false; - } + }, ); const bitset = @@ -691,7 +946,7 @@ function useDemoBankingInfoForm( { currency, defaultFxFee: currentPaymentOption?.defaultFxFee || "0", - } + }, ), }; @@ -763,6 +1018,6 @@ function useDemoBankingInfoForm( }, }, props.demoData || {}, - { arrayMerge: (_, a) => a } + { arrayMerge: (_, a) => a }, ); } diff --git a/packages/mint-components/src/components/tax-and-cash/sqm-banking-info-form/useBankingInfoForm.tsx b/packages/mint-components/src/components/tax-and-cash/sqm-banking-info-form/useBankingInfoForm.tsx index 09a1cb7bfd..499a72c0b8 100644 --- a/packages/mint-components/src/components/tax-and-cash/sqm-banking-info-form/useBankingInfoForm.tsx +++ b/packages/mint-components/src/components/tax-and-cash/sqm-banking-info-form/useBankingInfoForm.tsx @@ -9,7 +9,7 @@ import { } from "@saasquatch/component-boilerplate"; import { useEffect, useRef, useState } from "@saasquatch/universal-hooks"; import { gql } from "graphql-request"; -import JSONPointer, { set } from "jsonpointer"; +import JSONPointer from "jsonpointer"; import { intl } from "../../../global/global"; import { VERIFICATION_EVENT_KEY } from "../../sqm-widget-verification/keys"; import { @@ -49,6 +49,115 @@ const ACH_PAYMENT_METHOD = 3; const WIRE_PAYMENT_METHOD = 5; const PAYPAL_PAYMENT_METHOD = 7; +/** + * Maps GraphQL validation error field names to form field names. + */ +const API_FIELD_TO_FORM_FIELD: Record = { + // bankProvinceState → form uses bankState + bankProvinceState: "bankState", +}; + +/** + * Maps Impact API error codes (from validationErrors[].code) to short, + * readable frontend error codes used in the ICU select props. + */ +const API_ERROR_CODE_TO_FRONTEND: Record = { + // Beneficiary account name + "withdrawal.settings.error.empty_beneficiaryname": "empty", + "withdrawal.settings.error.invalid_character_beneficiaryname": + "invalidCharacters", + "withdrawal.settings.error.numeric_beneficiaryname": "numeric", + "withdrawal_settings.error.beneficiaryname.size": "tooLong", + "withdrawal.settings.error.non_english_beneficiaryname": "nonEnglish", + "withdrawal_settings.error.business_beneficiaryname_match": + "businessNameMismatch", + "withdrawal_settings.error.beneficiaryname_match": "nameMismatch", + "withdrawal_settings.error.business_checkpayeename_match": + "businessPayeeMismatch", + "withdrawal_settings.error.checkpayeename_match": "payeeMismatch", + + // Bank account number + "withdrawal.settings.error.accountnumber.empty": "empty", + "withdrawal.settings.error.accountnumber.uk": "invalidUk", + "withdrawal.settings.error.bankaccount.invalid": "invalid", + + // IBAN + "withdrawal.settings.error.iban": "ibanEmpty", + "withdrawal.settings.error.iban.alphanumeric": "ibanAlphanumeric", + "withdrawal.settings.error.iban.invalid": "ibanInvalid", + "withdrawal.settings.error.iban.uk.country.mismatch": "ibanCountryMismatch", + + // Routing code + "withdrawal.settings.error.bsbNumber": "invalidBsb", + "withdrawal.settings.error.sortcode": "invalidSortCode", + "withdrawal.settings.error.routingNumber": "empty", + "withdrawal.settings.error.routingcode": "invalid", + + // SWIFT / BIC + "withdrawal.settings.error.bic": "empty", + "withdrawal.settings.error.bic.alphanumeric": "alphanumeric", + "withdrawal.settings.error.bic.invalid": "invalid", + + // Bank account type + "global.error.invalid.accounttype": "empty", + + // Bank name + "withdrawal.settings.error.bankName": "empty", + + // Tax payer ID + "withdrawal.settings.error.taxPayerId": "empty", + "withdrawal.settings.error.taxPayerId.ar": "emptyAr", + "withdrawal.settings.error.taxPayerId.kr": "emptyKr", + "withdrawal.settings.error.taxPayerId.alphanumeric": "alphanumeric", + "withdrawal.settings.error.taxPayerId.alphanumeric.ar": "alphanumericAr", + "withdrawal.settings.error.taxPayerId.alphanumeric.kr": "alphanumericKr", + "withdrawal.settings.error.taxPayerId.invalid": "invalid", + "withdrawal.settings.error.taxPayerId.invalid.ar": "invalidAr", + "withdrawal.settings.error.taxPayerId.invalid.kr": "invalidKr", + "withdrawal.settings.error.taxPayerId.invalid.kzt": "invalidKzt", + "withdrawal.settings.error.taxPayerId.cnpj": "cnpjTooShort", + "withdrawal.settings.error.taxPayerId.cpf": "cpfTooShort", + + // Patronymic name + "withdrawal.settings.error.patronymicName": "empty", + "withdrawal.settings.error.patronymicName.alphanumeric": "alphanumeric", + + // VO code + "withdrawal.settings.error.voCode": "empty", + "withdrawal.settings.error.voCode.alphanumeric": "alphanumeric", + + // Agency code + "withdrawal.settings.error.agencyCode": "empty", + "withdrawal.settings.error.agencyCode.alphanumeric": "alphanumeric", + "withdrawal.settings.error.agencyCode.length": "tooShort", + + // Bank address fields + "withdrawal.settings.error.bankAddress": "empty", + "withdrawal.settings.error.bankCity": "empty", + "withdrawal.settings.error.bankProvinceState": "empty", + "withdrawal.settings.error.bankPostalCode": "empty", + + // Branch code / name + "withdrawal.settings.error.branchCode": "invalid", + "withdrawal.settings.error.branchName": "empty", + + // Classification code + "withdrawal.settings.error.classificationCode.invalid": "empty", + "withdrawal.settings.error.classificationCode.invalid.kzt": "invalidKzt", + + // PayPal + "payment.error.email": "empty", + "payment.error.paypal_not_supported": "unsupportedCurrency", + "payment.error.email.invalid": "invalidEmail", + "payment.error.paypal_verification_incomplete": "verificationIncomplete", + + // Payment schedule + "payment.error.no_threshold": "empty", + "payment.error.invalid_threshold": "invalid", + "payment.error.no_dayOfMonth": "empty", + "payment.error.invalid_dayOfMonth": "invalid", +}; + export type BankingInfoFormData = { // Fields that are auto-filled bankCountry?: string; @@ -79,6 +188,8 @@ export type BankingInfoFormData = { bankCity?: string; bankState?: string; branchCode?: string; + branchName?: string; + classificationCode?: string; // TODO These fields aren't settable in the mutation bankName?: string; patronymicName?: string; @@ -101,7 +212,11 @@ export function getFormInputs({ bitset, formMap }) { type SetImpactPublisherWithdrawalSettingsResult = { setImpactPublisherWithdrawalSettings: { success: boolean; - validationErrors: { field: string; message: string }[]; + validationErrors: { + field: string; + message: string; + code: string; + }[]; }; }; type SetImpactPublisherWithdrawalSettingsInput = { @@ -120,7 +235,11 @@ type UpdateImpactPublisherWithdrawalSettingsInput = type UpdateImpactPublisherWithdrawalSettingsResult = { updateImpactPublisherWithdrawalSettings: { success: boolean; - validationErrors: { field: string; message: string }[]; + validationErrors: { + field: string; + message: string; + code: string; + }[]; }; }; @@ -135,6 +254,7 @@ const SAVE_WITHDRAWAL_SETTINGS = gql` validationErrors { field message + code } } } @@ -151,6 +271,7 @@ const UPDATE_WITHDRAWAL_SETTINGS = gql` validationErrors { field message + code } } } @@ -176,7 +297,7 @@ function parseImpactThreshold(threshold: string) { } export function useBankingInfoForm( - props: BankingInfoForm + props: BankingInfoForm, ): BankingInfoFormViewProps { const host = useHost(); const locale = useLocale(); @@ -193,7 +314,7 @@ export function useBankingInfoForm( loading: paymentOptionsLoading, errors: paymentOptionsError, } = useParentQueryValue( - FINANCE_NETWORK_SETTINGS_NAMESPACE + FINANCE_NETWORK_SETTINGS_NAMESPACE, ); const { data: userData, @@ -202,11 +323,11 @@ export function useBankingInfoForm( } = useParentQueryValue(USER_QUERY_NAMESPACE); const [saveWithdrawalSettings] = useMutation( - SAVE_WITHDRAWAL_SETTINGS + SAVE_WITHDRAWAL_SETTINGS, ); const [updateWithdrawalSettings] = useMutation( - UPDATE_WITHDRAWAL_SETTINGS + UPDATE_WITHDRAWAL_SETTINGS, ); const [showVerification, setShowVerification] = useState(false); @@ -244,14 +365,14 @@ export function useBankingInfoForm( { currency: currency, defaultFxFee: currentPaymentOption?.defaultFxFee || 0, - } + }, ), }; const paymentMethodFeeLabel = paymentMethodFeeMap[currentPaymentOption?.defaultFinancePaymentMethodId]; const hasPayPal = !!paymentOptions?.find( - (option) => option.defaultFinancePaymentMethodId === PAYPAL_PAYMENT_METHOD + (option) => option.defaultFinancePaymentMethodId === PAYPAL_PAYMENT_METHOD, ); const paymentMethodChecked = !hasPayPal @@ -282,7 +403,7 @@ export function useBankingInfoForm( paypalEmailAddress: withdrawalSettings.paypalEmailAddress, paymentSchedulingType: withdrawalSettings.paymentSchedulingType, paymentThreshold: parseImpactThreshold( - withdrawalSettings.paymentThreshold + withdrawalSettings.paymentThreshold, ), paymentDay: withdrawalSettings.paymentDay, }; @@ -305,7 +426,7 @@ export function useBankingInfoForm( setPaymentMethodChecked( initialData.paymentMethod === "PAYPAL" ? "toPayPalAccount" - : "toBankAccount" + : "toBankAccount", ); setCurrentPaymentOption(currentPaymentOption); setPaymentScheduleChecked(initialData.paymentSchedulingType); @@ -319,8 +440,8 @@ export function useBankingInfoForm( } else { setFilteredCountries( countries.filter((c) => - c.displayName.toLowerCase().includes(countrySearch.toLowerCase()) - ) || [] + c.displayName.toLowerCase().includes(countrySearch.toLowerCase()), + ) || [], ); } }, [countrySearch, countries]); @@ -390,15 +511,19 @@ export function useBankingInfoForm( const mappedValidationErrors = validationErrors?.reduce( (agg, error) => { + const formField = + API_FIELD_TO_FORM_FIELD[error.field] || error.field; + const errorCode = + API_ERROR_CODE_TO_FRONTEND[error.code] || error.code; return { ...agg, - - [error.field]: { + [formField]: { type: "invalid", + errorCode, }, }; }, - {} + {}, ); setErrors({ @@ -469,12 +594,12 @@ export function useBankingInfoForm( new CustomEvent(VERIFICATION_EVENT_KEY, { detail: { token }, bubbles: false, - }) + }), ); }; function setPaymentMethodChecked( - paymentMethod: "toBankAccount" | "toPayPalAccount" + paymentMethod: "toBankAccount" | "toPayPalAccount", ) { _setPaymentMethodChecked(paymentMethod); @@ -486,7 +611,7 @@ export function useBankingInfoForm( setCurrentPaymentOption(currentPaymentOption); } else if (paymentMethod === "toBankAccount") { const currentPaymentOption = paymentOptions?.find( - (paymentOption) => paymentOption.countryCode === formState.bankCountry + (paymentOption) => paymentOption.countryCode === formState.bankCountry, ); setCurrentPaymentOption(currentPaymentOption); } diff --git a/packages/mint-components/src/components/tax-and-cash/sqm-indirect-tax-form/sqm-indirect-tax-form-view.tsx b/packages/mint-components/src/components/tax-and-cash/sqm-indirect-tax-form/sqm-indirect-tax-form-view.tsx index f97bfc7747..0563024df0 100644 --- a/packages/mint-components/src/components/tax-and-cash/sqm-indirect-tax-form/sqm-indirect-tax-form-view.tsx +++ b/packages/mint-components/src/components/tax-and-cash/sqm-indirect-tax-form/sqm-indirect-tax-form-view.tsx @@ -9,6 +9,7 @@ export interface IndirectTaxFormViewProps { loading: boolean; disabled: boolean; isPartner: boolean; + isPartnerLegacy: boolean; hideSteps: boolean; loadingError: boolean; formState: { @@ -217,7 +218,7 @@ export const IndirectTaxFormView = (props: IndirectTaxFormViewProps) => { id: "formStep", defaultMessage: text.formStep, }, - { step: states.step, count: FORM_STEPS } + { step: states.step, count: FORM_STEPS }, )}

)} @@ -245,7 +246,7 @@ export const IndirectTaxFormView = (props: IndirectTaxFormViewProps) => { {text.supportLink} ), - } + }, )}
@@ -267,11 +268,11 @@ export const IndirectTaxFormView = (props: IndirectTaxFormViewProps) => { {text.supportLink} ), - } + }, )} )} - {states.isPartner && ( + {states.isPartnerLegacy && (

{text.isPartnerAlertHeader}

{intl.formatMessage( @@ -288,7 +289,7 @@ export const IndirectTaxFormView = (props: IndirectTaxFormViewProps) => { {text.supportLink} ), - } + }, )}
)} @@ -309,7 +310,7 @@ export const IndirectTaxFormView = (props: IndirectTaxFormViewProps) => { id="notRegistered" checked={formState.checked === "notRegistered"} onInput={() => callbacks.onChange("notRegistered")} - disabled={states.disabled || states.isPartner} + disabled={states.disabled} >
{text.notRegistered} @@ -325,7 +326,7 @@ export const IndirectTaxFormView = (props: IndirectTaxFormViewProps) => { id="otherRegion" checked={formState.checked === "otherRegion"} onInput={() => callbacks.onChange("otherRegion")} - disabled={states.disabled || states.isPartner} + disabled={states.disabled} >
{text.otherRegion} @@ -352,7 +353,7 @@ export const IndirectTaxFormView = (props: IndirectTaxFormViewProps) => { {text.supportLink} ), - } + }, )}
diff --git a/packages/mint-components/src/components/tax-and-cash/sqm-indirect-tax-form/sqm-indirect-tax-form.tsx b/packages/mint-components/src/components/tax-and-cash/sqm-indirect-tax-form/sqm-indirect-tax-form.tsx index 7687995a66..c2b27fa3f5 100644 --- a/packages/mint-components/src/components/tax-and-cash/sqm-indirect-tax-form/sqm-indirect-tax-form.tsx +++ b/packages/mint-components/src/components/tax-and-cash/sqm-indirect-tax-form/sqm-indirect-tax-form.tsx @@ -254,7 +254,7 @@ export class IndirectTaxForm { { const setStep = useSetParent(TAX_CONTEXT_NAMESPACE); const [option, setOption] = useState("otherRegion"); @@ -300,6 +300,7 @@ function useDemoIndirectTaxForm( disabled: false, loading: false, isPartner: false, + isPartnerLegacy: false, loadingError: false, errors: {}, formState: { @@ -372,6 +373,6 @@ function useDemoIndirectTaxForm( }, }, props.demoData || {}, - { arrayMerge: (_, a) => a } + { arrayMerge: (_, a) => a }, ); } diff --git a/packages/mint-components/src/components/tax-and-cash/sqm-indirect-tax-form/useIndirectTaxForm.tsx b/packages/mint-components/src/components/tax-and-cash/sqm-indirect-tax-form/useIndirectTaxForm.tsx index b7d9910d3e..96a63ad10e 100644 --- a/packages/mint-components/src/components/tax-and-cash/sqm-indirect-tax-form/useIndirectTaxForm.tsx +++ b/packages/mint-components/src/components/tax-and-cash/sqm-indirect-tax-form/useIndirectTaxForm.tsx @@ -55,6 +55,14 @@ export type ConnectPartnerResult = { } | null; }; }; + +export type StartImpactConnectionResult = { + startImpactConnection: ConnectPartnerResult["createImpactConnection"]; +}; + +export type CompletePartnerResult = { + completeImpactConnection: ConnectPartnerResult["createImpactConnection"]; +}; type ImpactConnectionInput = { user: { id: string; @@ -104,6 +112,35 @@ export const CONNECT_PARTNER = gql` } `; +export const COMPLETE_PARTNER = gql` + mutation completeImpactConnection($vars: ImpactConnectionInput!) { + completeImpactConnection(impactConnectionInput: $vars) { + success + validationErrors { + field + message + } + user { + id + accountId + impactConnection { + connected + publisher { + phoneNumber + phoneNumberCountryCode + brandedSignup + requiredTaxDocumentType + currentTaxDocument { + type + status + } + } + } + } + } + } +`; + function getOption(countries: TaxCountry[] | undefined, countryCode: string) { if (!countries) return; @@ -123,10 +160,10 @@ export function useIndirectTaxForm(props: IndirectTaxForm) { const context = useParentValue(TAX_FORM_CONTEXT_NAMESPACE); const [step, setStep] = useParent(TAX_CONTEXT_NAMESPACE); - const [ - connectImpactPartner, - { loading: connectLoading, errors: connectErrors }, - ] = useMutation(CONNECT_PARTNER); + const [connectImpactPartner, { loading: connectLoading }] = + useMutation(CONNECT_PARTNER); + const [completeImpactPartner, { loading: completeLoading }] = + useMutation(COMPLETE_PARTNER); const userForm = useParentValue(USER_FORM_CONTEXT_NAMESPACE); const { data: userData, @@ -141,9 +178,9 @@ export function useIndirectTaxForm(props: IndirectTaxForm) { const _countries = useMemo( () => _countriesRes?.impactPayoutCountries?.data?.map((country) => - getCountryObj({ countryCode: country.countryCode, locale: intlLocale }) + getCountryObj({ countryCode: country.countryCode, locale: intlLocale }), ), - [_countriesRes?.impactPayoutCountries?.data] + [_countriesRes?.impactPayoutCountries?.data], ); const [loading, setLoading] = useState(false); @@ -168,7 +205,7 @@ export function useIndirectTaxForm(props: IndirectTaxForm) { const _option = getOption( _countries, - publisher.taxInformation.indirectTaxCountryCode + publisher.taxInformation.indirectTaxCountryCode, ); setOption(_option); }, [publisher, _countries]); @@ -179,8 +216,8 @@ export function useIndirectTaxForm(props: IndirectTaxForm) { } else { setFilteredCountries( _countries?.filter((c) => - c.displayName.toLowerCase().includes(countrySearch.toLowerCase()) - ) || [] + c.displayName.toLowerCase().includes(countrySearch.toLowerCase()), + ) || [], ); } }, [countrySearch, _countries]); @@ -244,16 +281,31 @@ export function useIndirectTaxForm(props: IndirectTaxForm) { withholdingTaxId: formData.subRegionTaxNumber, } as ImpactConnectionInput; - const result = await connectImpactPartner({ - vars, - }); + // If the partner has already been started call completeImpactConnection + // to fill in the remaining details. Otherwise create from scratch. + + let result = null; + let connectionResult; + if (userData?.user?.impactConnection?.connectionStatus === "STARTED") { + result = await completeImpactPartner({ + vars, + }); + connectionResult = (result as CompletePartnerResult) + ?.completeImpactConnection; + } else { + result = await connectImpactPartner({ + vars, + }); + connectionResult = (result as ConnectPartnerResult) + ?.createImpactConnection; + } if (!result || (result as Error)?.message) throw new Error(); - if (!(result as ConnectPartnerResult).createImpactConnection?.success) { + if (!connectionResult?.success) { // Output backend errors to console for now console.error( "Failed to create Impact connection: ", - (result as ConnectPartnerResult).createImpactConnection.validationErrors + connectionResult?.validationErrors, ); throw new Error(); @@ -261,8 +313,7 @@ export function useIndirectTaxForm(props: IndirectTaxForm) { await refetch(); - const resultPublisher = (result as ConnectPartnerResult) - .createImpactConnection?.user?.impactConnection?.publisher; + const resultPublisher = connectionResult?.user?.impactConnection?.publisher; const hasValidCurrentDocument = validTaxDocument(resultPublisher?.requiredTaxDocumentType) && @@ -308,9 +359,8 @@ export function useIndirectTaxForm(props: IndirectTaxForm) { setLoading(true); try { - const { resultPublisher, hasValidCurrentDocument } = await connectPartner( - formData - ); + const { resultPublisher, hasValidCurrentDocument } = + await connectPartner(formData); if ( resultPublisher?.requiredTaxDocumentType && @@ -342,9 +392,13 @@ export function useIndirectTaxForm(props: IndirectTaxForm) { states: { step: step?.replace("/", ""), hideSteps: context.hideSteps, - disabled: loading || countriesLoading || connectLoading, - loading: loading || connectLoading || countriesLoading, + disabled: + loading || countriesLoading || connectLoading || completeLoading, + loading: loading || connectLoading || countriesLoading || completeLoading, isPartner: !!userData?.user?.impactConnection?.publisher, + isPartnerLegacy: + !!userData?.user?.impactConnection?.publisher && + userData?.user?.impactConnection?.connectionStatus !== "STARTED", loadingError: !!userError?.message, formState: { checked: option, diff --git a/packages/mint-components/src/components/tax-and-cash/sqm-payout-details-card/PayoutDetailsCard.stories.tsx b/packages/mint-components/src/components/tax-and-cash/sqm-payout-details-card/PayoutDetailsCard.stories.tsx index 24326db78e..7ce7355ccb 100644 --- a/packages/mint-components/src/components/tax-and-cash/sqm-payout-details-card/PayoutDetailsCard.stories.tsx +++ b/packages/mint-components/src/components/tax-and-cash/sqm-payout-details-card/PayoutDetailsCard.stories.tsx @@ -64,3 +64,22 @@ export const PaypalPayout = () => { > ); }; + +export const BadgeTextICUParsing = () => { + return ( +
+ + + +
+ ); +}; diff --git a/packages/mint-components/src/components/tax-and-cash/sqm-payout-details-card/sqm-payout-details-card-view.tsx b/packages/mint-components/src/components/tax-and-cash/sqm-payout-details-card/sqm-payout-details-card-view.tsx index 640f3449f2..5f113b8960 100644 --- a/packages/mint-components/src/components/tax-and-cash/sqm-payout-details-card/sqm-payout-details-card-view.tsx +++ b/packages/mint-components/src/components/tax-and-cash/sqm-payout-details-card/sqm-payout-details-card-view.tsx @@ -157,6 +157,15 @@ const style = { }, }, + DangerBadge: { + "&::part(base)": { + textAlign: "center", + whiteSpace: "pre-line", + background: "var(--sqm-danger-color-icon)", + color: "var(--sl-color-white)", + }, + }, + Container: { display: "flex", flexDirection: "column", @@ -189,9 +198,12 @@ export function PayoutDetailsCardView(props: PayoutDetailsCardViewProps) { }; const renderStatusBadge = (status: string, statusBadgeText: string) => { - const badgeType = status === "nextPayout" ? "success" : "primary"; const badgeClass = - badgeType === "success" ? classes.SuccessBadge : classes.PrimaryBadge; + status === "nextPayout" + ? classes.SuccessBadge + : status === "payoutToday" + ? classes.PrimaryBadge + : classes.DangerBadge; const statusText = intl.formatMessage( { id: "badgeText", @@ -199,7 +211,7 @@ export function PayoutDetailsCardView(props: PayoutDetailsCardViewProps) { }, { badgeText: status, - } + }, ); return ( @@ -216,7 +228,7 @@ export function PayoutDetailsCardView(props: PayoutDetailsCardViewProps) { }, { thresholdBalance: states.thresholdBalance, - } + }, ); return ( diff --git a/packages/mint-components/src/components/tax-and-cash/sqm-tax-and-cash-dashboard/TaxAndCashDashboardView.stories.tsx b/packages/mint-components/src/components/tax-and-cash/sqm-tax-and-cash-dashboard/TaxAndCashDashboardView.stories.tsx new file mode 100644 index 0000000000..3144635d6e --- /dev/null +++ b/packages/mint-components/src/components/tax-and-cash/sqm-tax-and-cash-dashboard/TaxAndCashDashboardView.stories.tsx @@ -0,0 +1,317 @@ +import { h } from "@stencil/core"; +import { + TaxAndCashDashboardProps, + TaxAndCashDashboardView, +} from "./sqm-tax-and-cash-dashboard-view"; +import { taxTypeToName } from "../utils"; + +export default { + title: "Components/Tax And Cash Dashboard View", +}; + +const defaultText: TaxAndCashDashboardProps["text"] = { + statusTextActive: "Active", + statusTextNotActive: "Invalid Tax Form", + statusTextNotVerified: "Not Verified", + statusTextRequired: "Required", + requiredTaxForm: + "Your payouts are on hold until you submit a {taxFormType} tax form.", + badgeTextSubmittedOn: "Submitted on {dateSubmitted}.", + badgeTextSubmittedOnW8: + "Submitted on {dateSubmitted}. Valid for three years after submission.", + badgeTextAwaitingReview: "Awaiting review. Submitted on {dateSubmitted}.", + taxAlertHeaderNotActiveW9: + "Your W-9 tax form has personal information that doesn't match your profile", + taxAlertHeaderNotActiveW8: "{documentType} tax form is invalid", + taxAlertNotActiveMessageW9: "Please resubmit a new {documentType} form.", + taxAlertNotActiveMessageW8: + "Your tax form may have expired or has personal information that doesn't match your profile. Please submit a new {documentType} form.", + invalidForm: "Make sure your information is correct and submit new form.", + bankingInformationSectionHeader: "Payout information", + taxDocumentSectionHeader: "Tax documents", + taxDocumentSectionSubHeader: "{documentType} tax form", + taxDocumentSectionSubHeaderNoDocument: "Tax form", + indirectTaxInfoSectionHeader: "Indirect tax", + indirectTaxInfoCanada: "Registered in {province}, {country}", + indirectTaxInfoOtherCountry: "Registered in {country}", + indirectTaxInfoSpain: "Registered in {country}, {subRegion}", + indirectTaxTooltipSupport: + "To make changes to your indirect tax information, please contact our {supportLink}.", + indirectTaxDetails: "{indirectTaxType} number: {indirectTaxNumber}", + newFormButton: "Submit new tax form", + editPaymentInformationButton: "Edit payout information", + notRegisteredForTax: + "Not registered for indirect tax. If you've previously registered with your tax authority, contact our {supportLink} to add your information to stay tax compliant.", + qstNumber: "QST number: {qstNumber}", + subRegionTaxNumber: "Income tax number: {subRegionTaxNumber}", + invoiceColumnTitle: "Invoice", + dateColumnTitle: "Date", + earningsColumnTitle: "Earnings", + indirectTaxColumnTitle: "Indirect tax", + earningsAfterTaxColumnTitle: "Earnings after tax", + invoiceDescription: + "View and download your invoices to report your earnings and stay tax compliant.", + invoicePrevLabel: "Prev", + invoiceMoreLabel: "Next", + invoiceHeader: "Invoices", + invoiceEmptyStateHeader: "View your invoice details", + invoiceEmptyStateText: + "Refer a friend to view the status of your invoices and rewards earned", + payoutFromImpact: + "Your balance may take up to 24 hours to update. Payouts will be sent from our referral program provider, impact.com.", + replaceTaxFormModalHeader: "Replace existing tax form", + replaceTaxFormModalBodyText: + "Submitting a new tax form will remove your existing form. Make sure to sign and complete your new tax form to prevent any issues with your next payout.", + supportLink: "support team", + cancelButton: "Cancel", + payoutHoldAlertHeader: "Your payout is on hold", + payoutHoldAlertDescription: + "Please contact our {supportLink} or check your inbox for an email from our referral program provider, impact.com.", + verificationRequiredHeader: "Verify your identity", + verificationRequiredDescription: + "Complete your verification to start receiving your cash rewards. It should only take a few minutes verify. If you run in to an issue verifying your identity contact our {supportLink}.", + verificationRequiredButtonText: "Start Verification", + verificationRequiredInternalHeader: "Identity verification in progress", + verificationRequiredInternalDescription: + "Identity verification submission has been received. Our system is currently performing additional checks and analyzing the results. You will be updated shortly. If you don't hear from us contact our {supportLink}.", + verificationReviewInternalHeader: "Identity verification under review", + verificationReviewInternalDescription: + "Identity verification requires further review due to a potential error. Our team is reviewing the information and will update you shortly. If you don't hear from us contact our {supportLink}.", + verificationFailedInternalHeader: "Identity verification unsuccessful", + verificationFailedInternalDescription: + "Identity verification has failed. Our team is reviewing the report and will contact you with further information. If you don't hear from us contact our {supportLink}.", + w9RequiredHeader: "Your next payout is on hold", + w9RequiredDescription: + "You have surpassed the $2000 threshold requiring a W-9 form or have multiple accounts with impact.com. To remove the hold, please submit your W-9 form.", + w9RequiredButtonText: "Submit W-9", + accountReviewHeader: "Your account is under review", + accountReviewDescription: + "This process takes 48 hours, payouts are on hold until it's completed. You will receive an email from our referral provider, Impact.com, if any issues arise. It contains details on how to resolve this issue. If you need further assistance, please reach out to our {supportLink}.", + paymentHoldOnChangeHeader: "We are reviewing your new payout settings", + paymentHoldOnChangeDescription: + "Your payout is temporarily on hold while we review your new payment information, this process is usually resolved within 48 hours.", + beneficiaryNameInvalidHeader: + "Your payment information does not match your tax form", + beneficiaryNameInvalidDescription: + "The account holder (beneficiary) name in your payment information does not match what was submitted in your tax form. Please review and update your payment information or tax form so that they match exactly and do not include any invalid characters. Your payouts are on hold until this is resolved.", + beneficiaryNameMismatchHeader: + "Your payment information does not match your tax form", + beneficiaryNameMismatchDescription: + "The account holder (beneficiary) name in your payment information does not match what was submitted in your tax form. Please review and update your payment information or tax form so that they match exactly and do not include any invalid characters. Your payouts are on hold until this is resolved.", + bankTaxNameMismatchHeader: + "Your payment information does not match your tax form", + bankTaxNameMismatchDescription: + "The bank account (beneficiary) name in your payment information does not match what was submitted in your tax form. Please review and update your payment information or tax form so that they match exactly and do not include any invalid characters. Your payouts are on hold until this is resolved.", + withdrawalSettingsInvalidHeader: + "Your payment information is incomplete or includes invalid characters", + withdrawalSettingsInvalidDescription: + "There are missing fields or invalid characters in your payment information. Please review your information and make sure it is correct. Your payouts are on hold until this is resolved.", + paymentReturnedHeader: "Payout unsuccessful", + paymentReturnedDescription: + "Our recent payment attempt for your earnings was unsuccessful. Please review your payment information and make sure it is correct.", + termsAndConditions: "Terms and Conditions", + error: { + generalTitle: "There was a problem submitting your information", + generalDescription: + "Please review your information and try again. If this problem continues, contact our {supportLink}.", + loadingErrorAlertHeader: "There was a problem loading your form", + loadingErrorAlertDescription: + "Please refresh the page and try again. If this problem continues, contact our {supportLink}.", + }, +}; + +const defaultCallbacks: TaxAndCashDashboardProps["callbacks"] = { + onClick: () => {}, + onVerifyClick: () => {}, + onEditPayoutInfo: () => {}, + onNewFormCancel: () => {}, + onNewFormClick: () => {}, +}; + +const defaultSlots: TaxAndCashDashboardProps["slots"] = { + payoutDetailsCardSlot: ( + + ), +}; + +const baseStates: TaxAndCashDashboardProps["states"] = { + payoutStatus: "DONE", + veriffLoading: false, + canEditPayoutInfo: true, + disabled: false, + status: "ACTIVE", + documentType: "W9", + documentTypeString: taxTypeToName("W9"), + dateSubmitted: "Jan 18th, 2025", + noFormNeeded: false, + country: "United States", + indirectTaxType: "VAT", + indirectTaxNumber: "123456", + showNewFormDialog: false, + hasHold: false, + loading: false, + loadingError: false, +}; + +// Stories not covered by TaxForm.stories.tsx (which uses sqm-tax-and-cash-dashboard WC) + +// ─── Base ───────────────────────────────────────────────────────────────────── + +export const ActiveW9Form = () => ( + +); + +export const TaxFormRequired = () => ( + +); + +// ─── Payout hold statuses not in TaxForm.stories.tsx ───────────────────────── + +export const AccountUnderReview = () => ( + +); + +export const PaymentHoldOnChange = () => ( + +); + +export const BeneficiaryNameInvalid = () => ( + +); + +export const BeneficiaryNameMismatch = () => ( + +); + +export const BankTaxNameMismatch = () => ( + +); + +export const WithdrawalSettingsInvalid = () => ( + +); + +export const PaymentReturned = () => ( + +); + +export const W9ThresholdReachedHardBlock = () => ( + +); + +// ─── Verification loading state ─────────────────────────────────────────────── + +export const VerificationRequiredLoading = () => ( + +); + +// ─── Loading and error states ───────────────────────────────────────────────── + +export const Loading = () => ( + +); + +export const LoadingError = () => ( + +); + +export const GeneralError = () => ( + +); + +export const Disabled = () => ( + +); diff --git a/packages/mint-components/src/components/tax-and-cash/sqm-tax-and-cash-dashboard/useTaxAndCashDashboard.tsx b/packages/mint-components/src/components/tax-and-cash/sqm-tax-and-cash-dashboard/useTaxAndCashDashboard.tsx index cb43daf717..3557aa2abe 100644 --- a/packages/mint-components/src/components/tax-and-cash/sqm-tax-and-cash-dashboard/useTaxAndCashDashboard.tsx +++ b/packages/mint-components/src/components/tax-and-cash/sqm-tax-and-cash-dashboard/useTaxAndCashDashboard.tsx @@ -62,7 +62,7 @@ function getIndirectTaxType(taxInformation: ImpactPublisher["taxInformation"]) { if (taxInformation?.indirectTaxRegion) { const standardRegion = taxInformation.indirectTaxRegion.replace("_", ""); const taxType = regions.find( - (r) => r.regionCode === standardRegion + (r) => r.regionCode === standardRegion, )?.taxType; if (taxType) return taxType; @@ -78,7 +78,7 @@ function getIndirectTaxType(taxInformation: ImpactPublisher["taxInformation"]) { } export const useTaxAndCashDashboard = ( - props: TaxAndCashDashboard + props: TaxAndCashDashboard, ): Omit => { const setStep = useSetParent(TAX_CONTEXT_NAMESPACE); const setContext = useSetParent(TAX_FORM_CONTEXT_NAMESPACE); @@ -91,7 +91,7 @@ export const useTaxAndCashDashboard = ( const { data: taxSettingRes } = useQuery( GET_TAX_SETTING, - {} + {}, ); const locale = useLocale(); @@ -147,7 +147,7 @@ export const useTaxAndCashDashboard = ( }; const provinceName = INDIRECT_TAX_PROVINCES.find( - (p) => p.regionCode === publisher?.taxInformation?.indirectTaxRegion + (p) => p.regionCode === publisher?.taxInformation?.indirectTaxRegion, )?.displayName; const payoutStatus = data ? getStatus(data) : null; @@ -179,7 +179,7 @@ export const useTaxAndCashDashboard = ( province: provinceName, country: getCountryName( publisher?.taxInformation?.indirectTaxCountryCode, - locale + locale, ), notRegistered: !publisher?.taxInformation?.indirectTaxId, noFormNeeded: !documentType, diff --git a/packages/mint-components/src/components/tax-and-cash/sqm-tax-and-cash/readme.md b/packages/mint-components/src/components/tax-and-cash/sqm-tax-and-cash/readme.md index 4a32ecdaff..99b03aee43 100644 --- a/packages/mint-components/src/components/tax-and-cash/sqm-tax-and-cash/readme.md +++ b/packages/mint-components/src/components/tax-and-cash/sqm-tax-and-cash/readme.md @@ -5,199 +5,200 @@ ## Properties -| Property | Attribute | Description | Type | Default | -| --------------------------------------------------- | ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `backButton` | `back-button` | | `string` | `"Back"` | -| `cancelButton` | `cancel-button` | | `string` | `"Cancel"` | -| `continueButton` | `continue-button` | | `string` | `"Continue"` | -| `dashboard_accountReviewDescription` | `dashboard_account-review-description` | | `string` | `"This process takes 48 hours, payouts are on hold until it's completed. You will receive an email from our referral provider, Impact.com, if any issues arise. It contains details on how to resolve this issue. If you need further assistance, please reach out to our {supportLink}."` | -| `dashboard_accountReviewHeader` | `dashboard_account-review-header` | | `string` | `"Your account is under review"` | -| `dashboard_accountText` | `dashboard_account-text` | Shown before the participant’s bank account information. | `string` | `"Account"` | -| `dashboard_badgeTextAwaitingReview` | `dashboard_badge-text-awaiting-review` | Additional text displayed next to the tax form's status badge. | `string` | `"Awaiting review. Submitted on {dateSubmitted}."` | -| `dashboard_badgeTextSubmittedOn` | `dashboard_badge-text-submitted-on` | Additional text displayed next to the tax form's status badge | `string` | `"Submitted on {dateSubmitted}."` | -| `dashboard_badgeTextSubmittedOnW8` | `dashboard_badge-text-submitted-on-w-8` | Additional text displayed next to the tax form's status badge. | `string` | `"Submitted on {dateSubmitted}. Valid for three years after submission."` | -| `dashboard_bankNameMismatchDescription` | `dashboard_bank-name-mismatch-description` | | `string` | `"The bank account (beneficiary) name in your payment information does not match what was submitted in your tax form. Please review and update your payment information or tax form so that they match exactly and do not include any invalid characters. Your payouts are on hold until this is resolved."` | -| `dashboard_bankNameMismatchHeader` | `dashboard_bank-name-mismatch-header` | | `string` | `"Your payment information does not match your tax form"` | -| `dashboard_bankingInformationSectionHeader` | `dashboard_banking-information-section-header` | | `string` | `"Payout Information"` | -| `dashboard_beneficiaryNameInvalidDescription` | `dashboard_beneficiary-name-invalid-description` | | `string` | `"The account holder (beneficiary) name in your payment information does not match what was submitted in your tax form. Please review and update your payment information or tax form so that they match exactly and do not include any invalid characters. Your payouts are on hold until this is resolved."` | -| `dashboard_beneficiaryNameInvalidHeader` | `dashboard_beneficiary-name-invalid-header` | | `string` | `"Your payment information does not match your tax form"` | -| `dashboard_beneficiaryNameMismatchDescription` | `dashboard_beneficiary-name-mismatch-description` | | `string` | `"The account holder (beneficiary) name in your payment information does not match what was submitted in your tax form. Please review and update your payment information or tax form so that they match exactly and do not include any invalid characters. Your payouts are on hold until this is resolved."` | -| `dashboard_beneficiaryNameMismatchHeader` | `dashboard_beneficiary-name-mismatch-header` | | `string` | `"Your payment information does not match your tax form"` | -| `dashboard_dateColumnTitle` | `dashboard_date-column-title` | Part of the Invoice table displayed at the bottom of the page. | `string` | `"Date"` | -| `dashboard_earningsAfterTaxColumnTitle` | `dashboard_earnings-after-tax-column-title` | Part of the Invoice table displayed at the bottom of the page. | `string` | `"Earnings after tax"` | -| `dashboard_earningsColumnTitle` | `dashboard_earnings-column-title` | Part of the Invoice table displayed at the bottom of the page. | `string` | `"Earnings"` | -| `dashboard_editPaymentInformationButton` | `dashboard_edit-payment-information-button` | | `string` | `"Edit payout information"` | -| `dashboard_indirectTaxColumnTitle` | `dashboard_indirect-tax-column-title` | Part of the Invoice table displayed at the bottom of the page. | `string` | `"Indirect tax"` | -| `dashboard_indirectTaxDetails` | `dashboard_indirect-tax-details` | Displayed to participants who have submitted their indirect tax information. | `string` | `"{indirectTaxType} number: {indirectTaxNumber}"` | -| `dashboard_indirectTaxInfoCanada` | `dashboard_indirect-tax-info-canada` | If the participant is registered for indirect tax in Canada, display the province they’re registered in. | `string` | `"Registered in {province}, {country}"` | -| `dashboard_indirectTaxInfoOtherCountry` | `dashboard_indirect-tax-info-other-country` | If the participant is registered for indirect tax, display the country they’re registered in. | `string` | `"Registered in {country}"` | -| `dashboard_indirectTaxInfoSectionHeader` | `dashboard_indirect-tax-info-section-header` | | `string` | `"Indirect tax"` | -| `dashboard_indirectTaxInfoSpain` | `dashboard_indirect-tax-info-spain` | If the participant is registered for indirect tax in Spain, display the region they’re registered in. | `string` | `"Registered in {country}, {subRegion}"` | -| `dashboard_indirectTaxTooltipSupport` | `dashboard_indirect-tax-tooltip-support` | | `string` | `"To make changes to your indirect tax information, please contact our {supportLink}."` | -| `dashboard_invalidForm` | `dashboard_invalid-form` | Additional text displayed next to the tax form's status badge. | `string` | `"Make sure your information is correct and submit new form."` | -| `dashboard_invoiceColumnTitle` | `dashboard_invoice-column-title` | Part of the Invoice table displayed at the bottom of the page. | `string` | `"Invoice"` | -| `dashboard_invoiceDescription` | `dashboard_invoice-description` | | `string` | `"View and download your invoices to report your earnings and stay tax compliant."` | -| `dashboard_invoiceEmptyStateHeader` | `dashboard_invoice-empty-state-header` | | `string` | `"View your invoice details"` | -| `dashboard_invoiceEmptyStateText` | `dashboard_invoice-empty-state-text` | | `string` | `"Refer a friend to view the status of your invoices and rewards earned"` | -| `dashboard_invoiceHeader` | `dashboard_invoice-header` | | `string` | `"Invoices"` | -| `dashboard_invoiceMoreLabel` | `dashboard_invoice-more-label` | | `string` | `"Next"` | -| `dashboard_invoicePrevLabel` | `dashboard_invoice-prev-label` | | `string` | `"Prev"` | -| `dashboard_newFormButton` | `dashboard_new-form-button` | | `string` | `"Submit new tax form"` | -| `dashboard_noFormNeededSubtext` | `dashboard_no-form-needed-subtext` | No other statuses or badges will be displayed in the tax form section in this case. | `string` | `"Tax documents are only required if you are based in the US or joining the referral program of a US based brand."` | -| `dashboard_notRegisteredForTax` | `dashboard_not-registered-for-tax` | | `string` | `"Not registered for indirect tax. If you’ve previously registered with your tax authority, contact our {supportLink} to add your information to stay tax compliant."` | -| `dashboard_paymentOnHoldDescription` | `dashboard_payment-on-hold-description` | | `string` | `"Your payout is temporarily on hold while we review your new payment information, this process is usually resolved within 48 hours."` | -| `dashboard_paymentOnHoldHeader` | `dashboard_payment-on-hold-header` | | `string` | `"We are reviewing your new payout settings"` | -| `dashboard_paymentReturnedDescription` | `dashboard_payment-returned-description` | | `string` | `"Our recent payment attempt for your earnings was unsuccessful. Please review your payment information and make sure it is correct."` | -| `dashboard_paymentReturnedHeader` | `dashboard_payment-returned-header` | | `string` | `"Payout unsuccessful"` | -| `dashboard_payoutFromImpact` | `dashboard_payout-from-impact` | Displayed under the payout details card. | `string` | `"Your balance may take up to 24 hours to update. Payouts will be sent from our referral program provider, impact.com."` | -| `dashboard_payoutHoldAlertDescription` | `dashboard_payout-hold-alert-description` | Part of the alert displayed at the top of the page when there’s been an issue preventing payouts. | `string` | `"Please contact our {supportLink} or check your inbox for an email from our referral program provider, impact.com."` | -| `dashboard_payoutHoldAlertHeader` | `dashboard_payout-hold-alert-header` | Part of the alert displayed at the top of the page when there’s been an issue preventing payouts. | `string` | `"Your payout is on hold"` | -| `dashboard_payoutMissingInformationText` | `dashboard_payout-missing-information-text` | Text displayed for existing publishers that do not have saved banking information. | `string` | `"Missing banking information, go to Impact.com to resolve."` | -| `dashboard_qstNumber` | `dashboard_qst-number` | Displayed to participants registered in Quebec, Canada. | `string` | `"QST number: {qstNumber}"` | -| `dashboard_replaceTaxFormModalBodyText` | `dashboard_replace-tax-form-modal-body-text` | | `string` | `"Submitting a new tax form will remove your existing form. Make sure to sign and complete your new tax form to prevent any issues with your next payout."` | -| `dashboard_replaceTaxFormModalHeader` | `dashboard_replace-tax-form-modal-header` | | `string` | `"Replace existing tax form"` | -| `dashboard_requiredTaxForm` | `dashboard_required-tax-form` | Additional text displayed next to the tax form's status badge | `string` | `"Your payouts are on hold until you submit a {taxFormType} tax form."` | -| `dashboard_statusBadgeText` | `dashboard_status-badge-text` | | `string` | `"{badgeText, select, payoutToday {Payout Today} nextPayout {Next Payout} other {Failed} }"` | -| `dashboard_statusTextActive` | `dashboard_status-text-active` | | `string` | `"Active"` | -| `dashboard_statusTextNotActive` | `dashboard_status-text-not-active` | | `string` | `"Invalid Tax Form"` | -| `dashboard_statusTextNotVerified` | `dashboard_status-text-not-verified` | Displayed when the participant submitted their form but it is awaiting review. | `string` | `"Not Verified"` | -| `dashboard_statusTextRequired` | `dashboard_status-text-required` | Displayed when the participant has not submitted their required tax form. | `string` | `"Required"` | -| `dashboard_subRegionTaxNumber` | `dashboard_sub-region-tax-number` | | `string` | `"Income tax number: {subRegionTaxNumber}"` | -| `dashboard_taxAlertHeaderNotActiveW8` | `dashboard_tax-alert-header-not-active-w-8` | Part of the alert displayed at the top of the page. | `string` | `"{documentType} tax form is invalid"` | -| `dashboard_taxAlertHeaderNotActiveW9` | `dashboard_tax-alert-header-not-active-w-9` | Part of the alert displayed at the top of the page. | `string` | `"Your W-9 tax form has personal information that doesn’t match your profile"` | -| `dashboard_taxAlertNotActiveMessageW8` | `dashboard_tax-alert-not-active-message-w-8` | Part of the alert displayed at the top of the page. | `string` | `"Your tax form may have expired or has personal information that doesn’t match your profile. Please submit a new {documentType} form."` | -| `dashboard_taxAlertNotActiveMessageW9` | `dashboard_tax-alert-not-active-message-w-9` | Part of the alert displayed at the top of the page. | `string` | `"Please resubmit a new {documentType} form."` | -| `dashboard_taxDocumentSectionHeader` | `dashboard_tax-document-section-header` | | `string` | `"Tax documents"` | -| `dashboard_taxDocumentSectionSubHeader` | `dashboard_tax-document-section-sub-header` | Displayed under the tax document section header. | `string` | `"{documentType} tax form"` | -| `dashboard_termsAndConditions` | `dashboard_terms-and-conditions` | | `string` | `"Terms and Conditions"` | -| `dashboard_thresholdPayoutText` | `dashboard_threshold-payout-text` | Display participants' payout preference on the payout information card, indicating the balance at which they want to get paid. | `string` | `"Next payout occurs when balance is {thresholdBalance}"` | -| `dashboard_verificationFailedInternalDescription` | `dashboard_verification-failed-internal-description` | | `string` | `"Identity verification has failed. Our team is reviewing the report and will contact you with further information. If you don't hear from us contact our {supportLink}."` | -| `dashboard_verificationFailedInternalHeader` | `dashboard_verification-failed-internal-header` | | `string` | `"Identity verification usuccessful"` | -| `dashboard_verificationRequiredButtonText` | `dashboard_verification-required-button-text` | Part of the alert displayed at the top of the page when the user needs to verify their identity. | `string` | `"Start Verification"` | -| `dashboard_verificationRequiredDescription` | `dashboard_verification-required-description` | Part of the alert displayed at the top of the page when the user needs to verify their identity | `string` | `"Complete your verification to start receiving your cash rewards. It should only take a few minutes verify. If you run in to an issue verifying your identity contact our {supportLink}."` | -| `dashboard_verificationRequiredHeader` | `dashboard_verification-required-header` | Part of the alert displayed at the top of the page when the user needs to verify their identity. | `string` | `"Verify your identity"` | -| `dashboard_verificationRequiredInternalDescription` | `dashboard_verification-required-internal-description` | | `string` | `"Identity verification submission has been received. Our system is currently performing additional checks and analyzing the results. You will be updated shortly. If you don't hear from us contact our {supportLink}."` | -| `dashboard_verificationRequiredInternalHeader` | `dashboard_verification-required-internal-header` | | `string` | `"Identity verification in progress"` | -| `dashboard_verificationReviewInternalDescription` | `dashboard_verification-review-internal-description` | | `string` | `"Identity verification requires further review due to a potential error. Our team is reviewing the information and will update you shortly. If you don't hear from us contact our {supportLink}."` | -| `dashboard_verificationReviewInternalHeader` | `dashboard_verification-review-internal-header` | | `string` | `"Identity verification under review"` | -| `dashboard_w9RequiredButtonText` | `dashboard_w-9-required-button-text` | | `string` | `"Submit W-9"` | -| `dashboard_w9RequiredDescription` | `dashboard_w-9-required-description` | | `string` | `"You have surpassed the $600 threshold requiring a W-9 form or have multiple accounts with impact.com. To remove the hold, please submit your W-9 form."` | -| `dashboard_w9RequiredHeader` | `dashboard_w-9-required-header` | | `string` | `"Your next payout is on hold"` | -| `dashboard_withdrawalSettingsInvalidDescription` | `dashboard_withdrawal-settings-invalid-description` | | `string` | `"There are missing fields or invalid characters in your payment information. Please review your information and make sure it is correct. Your payouts are on hold until this is resolved."` | -| `dashboard_withdrawalSettingsInvalidHeader` | `dashboard_withdrawal-settings-invalid-header` | | `string` | `"Your payment information is incomplete or includes invalid characters"` | -| `demoData` | -- | | `{ step1_firstName?: string; step1_lastName?: string; step1_email?: string; step1_country?: string; step1_phoneNumber?: string; step1_address?: string; step1_city?: string; step1_state?: string; step1_province?: string; step1_region?: string; step1_postalCode?: string; step1_currency?: string; step1_currencyHelpText?: string; step1_allowBankingCollection?: string; step1_personalInformation?: string; step1_termsAndConditionsLabel?: string; step1_termsAndConditionsLink?: string; step1_searchForCurrencyText?: string; step2_indirectTax?: string; step2_indirectTaxDescription?: string; step2_indirectTaxDetails?: string; step2_otherRegion?: string; step2_otherRegionSubtext?: string; step2_notRegistered?: string; step2_notRegisteredSubtext?: string; step2_selectedRegion?: string; step2_indirectTaxNumber?: string; step2_province?: string; step2_indirectTaxNumberError?: string; step2_qstNumber?: string; step2_isRegisteredQST?: string; step2_isRegisteredSubRegionIncomeTax?: string; step2_subRegion?: string; step2_subRegionTaxNumberLabel?: string; step2_cannotChangeInfoAlert?: string; step3_taxForm?: string; step3_taxFormLabel?: string; step3_participantType?: string; step3_businessEntity?: string; step3_individualParticipant?: string; step3_taxFormDescription?: string; step3_taxFormDescriptionIndividualParticipant?: string; step3_taxFormDescriptionBusinessEntity?: string; step3_docusignExpired?: string; step3_docusignSessionWarning?: string; step3_docusignError?: string; step3_refreshButton?: string; step3_exitButton?: string; step3_modalTitle?: string; step3_modalDescription?: string; step3_modalButtonText?: string; step4_taxAndPayouts?: string; step4_directlyToBankAccount?: string; step4_toPayPalAccount?: string; step4_paymentScheduleBalanceThreshold?: string; step4_paymentScheduleFixedDay?: string; step4_paymentDaySelectLabel?: string; step4_paymentThresholdSelectLabel?: string; step4_paymentDayFirstOfMonthLabelText?: string; step4_paymentDayFifteenthOfMonthLabelText?: string; step4_paymentMethod?: string; step4_paymentSchedule?: string; step4_paymentMethodSubtext?: string; step4_payPalInputLabel?: string; step4_bankLocationLabel?: string; step4_beneficiaryAccountNameLabel?: string; step4_beneficiaryAccountNameDescription?: string; step4_bankAccountTypeLabel?: string; step4_checkingSelectItemLabel?: string; step4_savingsSelectItemLabel?: string; step4_bankAccountNumberLabel?: string; step4_ibanLabel?: string; step4_swiftCodeLabel?: string; step4_routingCodeLabel?: string; step4_bankNameLabel?: string; step4_classificationEntityLabel?: string; step4_businessSelectItemLabel?: string; step4_individualSelectItemLabel?: string; step4_foreignSelectItemLabel?: string; step4_classificationCPFLabel?: string; step4_patronymicNameLabel?: string; step4_voCodeLabel?: string; step4_agencyCodeLabel?: string; step4_branchCodeLabel?: string; step4_classificationLabel?: string; step4_taxPayerIdLabel?: string; step4_bankAddressLabel?: string; step4_bankCityLabel?: string; step4_bankStateLabel?: string; step4_bankPostalCodeLabel?: string; step4_eftWithdrawalLabel?: string; step4_fxWireProcessingFeeLabel?: string; step4_verifyEmailHeaderText?: string; step4_verifyEmailDescriptionText?: string; step4_modalTitle?: string; step4_modalDescription?: string; step4_modalButtonText?: string; dashboard_statusTextActive?: string; dashboard_statusTextNotActive?: string; dashboard_statusTextNotVerified?: string; dashboard_statusTextRequired?: string; dashboard_requiredTaxForm?: string; dashboard_badgeTextSubmittedOn?: string; dashboard_badgeTextSubmittedOnW8?: string; dashboard_badgeTextAwaitingReview?: string; dashboard_taxAlertHeaderNotActiveW9?: string; dashboard_taxAlertHeaderNotActiveW8?: string; dashboard_taxAlertNotActiveMessageW9?: string; dashboard_taxAlertNotActiveMessageW8?: string; dashboard_invalidForm?: string; dashboard_bankingInformationSectionHeader?: string; dashboard_taxDocumentSectionHeader?: string; dashboard_taxDocumentSectionSubHeader?: string; dashboard_noFormNeededSubtext?: string; dashboard_indirectTaxInfoSectionHeader?: string; dashboard_indirectTaxInfoCanada?: string; dashboard_indirectTaxInfoOtherCountry?: string; dashboard_indirectTaxInfoSpain?: string; dashboard_indirectTaxTooltipSupport?: string; dashboard_indirectTaxDetails?: string; dashboard_newFormButton?: string; dashboard_editPaymentInformationButton?: string; dashboard_notRegisteredForTax?: string; dashboard_qstNumber?: string; dashboard_subRegionTaxNumber?: string; dashboard_statusBadgeText?: string; dashboard_thresholdPayoutText?: string; dashboard_accountText?: string; dashboard_invoiceColumnTitle?: string; dashboard_dateColumnTitle?: string; dashboard_earningsColumnTitle?: string; dashboard_indirectTaxColumnTitle?: string; dashboard_payoutFromImpact?: string; dashboard_earningsAfterTaxColumnTitle?: string; dashboard_payoutHoldAlertHeader?: string; dashboard_payoutHoldAlertDescription?: string; dashboard_payoutMissingInformationText?: string; dashboard_invoiceDescription?: string; dashboard_invoicePrevLabel?: string; dashboard_invoiceMoreLabel?: string; dashboard_invoiceHeader?: string; dashboard_invoiceEmptyStateHeader?: string; dashboard_invoiceEmptyStateText?: string; dashboard_replaceTaxFormModalHeader?: string; dashboard_replaceTaxFormModalBodyText?: string; dashboard_verificationRequiredHeader?: string; dashboard_verificationRequiredDescription?: string; dashboard_verificationRequiredInternalHeader?: string; dashboard_verificationRequiredInternalDescription?: string; dashboard_verificationReviewInternalHeader?: string; dashboard_verificationReviewInternalDescription?: string; dashboard_verificationFailedInternalHeader?: string; dashboard_verificationFailedInternalDescription?: string; dashboard_verificationRequiredButtonText?: string; dashboard_w9RequiredHeader?: string; dashboard_w9RequiredDescription?: string; dashboard_accountReviewHeader?: string; dashboard_accountReviewDescription?: string; dashboard_paymentOnHoldHeader?: string; dashboard_paymentOnHoldDescription?: string; dashboard_beneficiaryNameInvalidHeader?: string; dashboard_beneficiaryNameInvalidDescription?: string; dashboard_beneficiaryNameMismatchHeader?: string; dashboard_beneficiaryNameMismatchDescription?: string; dashboard_bankNameMismatchHeader?: string; dashboard_bankNameMismatchDescription?: string; dashboard_withdrawalSettingsInvalidHeader?: string; dashboard_withdrawalSettingsInvalidDescription?: string; dashboard_paymentReturnedHeader?: string; dashboard_paymentReturnedDescription?: string; dashboard_termsAndConditions?: string; dashboard_w9RequiredButtonText?: string; formStep?: string; generalErrorTitle?: string; generalErrorDescription?: string; fieldRequiredError?: string; invalidCharacterError?: string; fieldInvalidError?: string; continueButton?: string; backButton?: string; cancelButton?: string; isPartnerAlertHeader?: string; isPartnerAlertDescription?: string; searchForCountryText?: string; loadingErrorAlertHeader?: string; loadingErrorAlertDescription?: string; taxAndPayoutsDescription?: string; supportLink?: string; termsAndConditions?: string; stateController?: string; demoData?: Partial>; disconnectedCallback?: () => void; getGeneralStepTextProps?: (prefix: string) => PickPrefix & { generalErrorTitle: string; generalErrorDescription: string; fieldRequiredError: string; invalidCharacterError: string; fieldInvalidError: string; continueButton: string; backButton: string; cancelButton: string; isPartnerAlertHeader: string; isPartnerAlertDescription: string; loadingErrorAlertHeader: string; loadingErrorAlertDescription: string; taxAndPayoutsDescription: string; searchForCountryText: string; formStep: string; supportLink: string; }; render?: () => any; }` | `undefined` | -| `fieldInvalidError` | `field-invalid-error` | Displayed under a field when it has an invalid entry. | `string` | `"{fieldName} is invalid"` | -| `fieldRequiredError` | `field-required-error` | Displayed under a field that is missing required information. | `string` | `"{fieldName} is required"` | -| `formStep` | `form-step` | | `string` | `"Step {step} of {count}"` | -| `generalErrorDescription` | `general-error-description` | Part of the alert displayed at the top of the page. | `string` | `"Please review your information and try again. If this problem continues, contact our {supportLink}."` | -| `generalErrorTitle` | `general-error-title` | Part of the alert displayed at the top of the page. | `string` | `"There was a problem submitting your information"` | -| `invalidCharacterError` | `invalid-character-error` | Displayed under a field that includes invalid characters (non-ASCII). | `string` | `"{fieldName} includes characters that aren't supported."` | -| `isPartnerAlertDescription` | `is-partner-alert-description` | Part of the alert displayed at the top of the page if the participant is already a registered partner on impact.com. | `string` | `"If you don’t recognize this referral program provider or believe this is a mistake, please contact our {supportLink} or sign up for this referral program with a different email."` | -| `isPartnerAlertHeader` | `is-partner-alert-header` | Part of the alert displayed at the top of the page if the participant is already a registered partner on impact.com. | `string` | `"An account with this email already exists with our referral program provider, impact.com"` | -| `loadingErrorAlertDescription` | `loading-error-alert-description` | Part of the alert displayed at the top of the page. | `string` | `"Please refresh the page and try again. If this problem continues, contact our {supportLink}."` | -| `loadingErrorAlertHeader` | `loading-error-alert-header` | Part of the alert displayed at the top of the page. | `string` | `"There was a problem loading your form"` | -| `searchForCountryText` | `search-for-country-text` | Placeholder text displayed in the country search dropdown | `string` | `"Search for country.."` | -| `stateController` | `state-controller` | | `string` | `"{}"` | -| `step1_address` | `step-1_address` | | `string` | `"Address"` | -| `step1_allowBankingCollection` | `step-1_allow-banking-collection` | Edit the property called terms and conditions text to change what's displayed for {termsAndConditionsLink}. | `string` | `"I have read the {termsAndConditionsLink} and allow impact.com to collect my tax and banking information"` | -| `step1_city` | `step-1_city` | | `string` | `"City"` | -| `step1_country` | `step-1_country` | | `string` | `"Country"` | -| `step1_currency` | `step-1_currency` | | `string` | `"Currency"` | -| `step1_currencyHelpText` | `step-1_currency-help-text` | | `string` | `"Choose your preferred payout currency"` | -| `step1_email` | `step-1_email` | | `string` | `"Email"` | -| `step1_firstName` | `step-1_first-name` | | `string` | `"First name"` | -| `step1_lastName` | `step-1_last-name` | | `string` | `"Last name"` | -| `step1_personalInformation` | `step-1_personal-information` | | `string` | `"Personal Information"` | -| `step1_phoneNumber` | `step-1_phone-number` | | `string` | `"Phone number"` | -| `step1_postalCode` | `step-1_postal-code` | | `string` | `"Postal code"` | -| `step1_province` | `step-1_province` | | `string` | `"Province"` | -| `step1_region` | `step-1_region` | | `string` | `"Region"` | -| `step1_searchForCurrencyText` | `step-1_search-for-currency-text` | Placeholder text displayed in the currency search dropdown | `string` | `"Search for currency.."` | -| `step1_state` | `step-1_state` | | `string` | `"State"` | -| `step1_termsAndConditionsLabel` | `step-1_terms-and-conditions-label` | The link text that appears in the terms and conditions checkbox | `string` | `"terms and conditions"` | -| `step1_termsAndConditionsLink` | `step-1_terms-and-conditions-link` | The link that appears in the terms and conditions checkbox | `string` | `"https://terms.advocate.impact.com/PayoutTermsAndConditions.html"` | -| `step2_cannotChangeInfoAlert` | `step-2_cannot-change-info-alert` | Communicate that after this step, only Support can change personal and indirect tax information. | `string` | `"Changes to your personal and indirect tax information can only be made through our {supportLink} after you complete this step. Make sure these are correct before continuing."` | -| `step2_indirectTax` | `step-2_indirect-tax` | | `string` | `"Indirect Tax"` | -| `step2_indirectTaxDescription` | `step-2_indirect-tax-description` | Displayed under the title of this step. | `string` | `"Indirect taxes (e.g. VAT, HST, GST) are transaction based taxes often applied to goods and services. Service providers are typically required to register with their tax authority and collect these taxes on behalf governments."` | -| `step2_indirectTaxDetails` | `step-2_indirect-tax-details` | Displayed with indirect tax registration options. | `string` | `"Indirect tax details"` | -| `step2_indirectTaxNumber` | `step-2_indirect-tax-number` | | `string` | `"{taxType, select, GST {GST number} HST {HST number} VAT {VAT number} CT {CT number} SST {SST number} GENERAL {Indirect tax number} other {Tax number}}"` | -| `step2_indirectTaxNumberError` | `step-2_indirect-tax-number-error` | | `string` | `"{taxType, select, GST {GST number} HST {HST number} VAT {VAT number} CT {CT number} SST {SST number} GENERAL {Indirect tax number} other {Tax number}} is required"` | -| `step2_isRegisteredQST` | `step-2_is-registered-q-s-t` | Displayed to participants registered for indirect tax in Quebec, Canada. | `string` | `"I am registered for QST Tax"` | -| `step2_isRegisteredSubRegionIncomeTax` | `step-2_is-registered-sub-region-income-tax` | Displayed to participants registered for indirect tax in Spain. | `string` | `"I am an individual registered for Income Tax purposes in Spain, and withholding tax will apply to any payments made to me."` | -| `step2_notRegistered` | `step-2_not-registered` | | `string` | `"Not registered for indirect tax"` | -| `step2_notRegisteredSubtext` | `step-2_not-registered-subtext` | Participants based in the US are considered not registered. | `string` | `"If you’ve never set up indirect tax with your tax authority, then you’re likely not considered registered."` | -| `step2_otherRegion` | `step-2_other-region` | | `string` | `"Registered for indirect tax"` | -| `step2_otherRegionSubtext` | `step-2_other-region-subtext` | Selecting this option will display fields to enter indirect tax details. | `string` | `"If you’ve registered with your tax authority, add your information to stay tax compliant."` | -| `step2_province` | `step-2_province` | | `string` | `"Province"` | -| `step2_qstNumber` | `step-2_qst-number` | Displayed to participants registered for QST. | `string` | `"QST number"` | -| `step2_selectedRegion` | `step-2_selected-region` | | `string` | `"Country / region of indirect tax"` | -| `step2_subRegion` | `step-2_sub-region` | Displayed to participants registered in Spain. | `string` | `"Sub-region"` | -| `step2_subRegionTaxNumberLabel` | `step-2_sub-region-tax-number-label` | | `string` | `"Income Tax Number"` | -| `step3_businessEntity` | `step-3_business-entity` | An option for the participant type field. Used to determine which W-8 form is required. | `string` | `"I represent a business"` | -| `step3_docusignError` | `step-3_docusign-error` | This appears inside the Docusign frame. | `string` | `"There was a problem displaying this form. Please refresh the page. If this problem continues, contact our {supportLink}."` | -| `step3_docusignExpired` | `step-3_docusign-expired` | This appears inside the Docusign frame. | `string` | `"For your security and privacy, we automatically end your session after 20 minutes of inactivity. Please refresh and re-enter your tax information to continue."` | -| `step3_docusignSessionWarning` | `step-3_docusign-session-warning` | Remind participants their session will time out after 20 minutes of inactivity. | `string` | `"For your security, we automatically end your session when you have not interacted with the form after 20 minutes."` | -| `step3_exitButton` | `step-3_exit-button` | | `string` | `"Exit"` | -| `step3_individualParticipant` | `step-3_individual-participant` | An option for the participant type field. Used to determine which W-8 form is required. | `string` | `"I am an individual participant"` | -| `step3_modalButtonText` | `step-3_modal-button-text` | | `string` | `"I understand"` | -| `step3_modalDescription` | `step-3_modal-description` | | `string` | `"Remember the name you enter in your tax form. It must exactly match the bank account holder name configured in the next step. {br}{br}Otherwise you will have to resubmit your form again and there will be delays receiving your payout."` | -| `step3_modalTitle` | `step-3_modal-title` | | `string` | `"Important Note"` | -| `step3_participantType` | `step-3_participant-type` | | `string` | `"Participant type"` | -| `step3_refreshButton` | `step-3_refresh-button` | | `string` | `"Refresh Page"` | -| `step3_taxForm` | `step-3_tax-form` | | `string` | `"Tax form"` | -| `step3_taxFormDescription` | `step-3_tax-form-description` | Displayed at the top of the page to participants based in the US. | `string` | `"Participants based in the US need to submit a {documentType} form."` | -| `step3_taxFormDescriptionBusinessEntity` | `step-3_tax-form-description-business-entity` | Displayed at the top of the page to participants representing a business. | `string` | `"Participants residing outside of the US working with a US Brand need to submit a {documentType} form."` | -| `step3_taxFormDescriptionIndividualParticipant` | `step-3_tax-form-description-individual-participant` | Displayed at the top of the page to individuals joining a US program who reside outside the country. | `string` | `"Participants residing outside of the US, joining the referral program of a US-based company, need to submit a {documentType} form."` | -| `step3_taxFormLabel` | `step-3_tax-form-label` | Display the type of tax form that the participant must submit. | `string` | `"{documentType} Tax Form"` | -| `step4_agencyCodeLabel` | `step-4_agency-code-label` | | `string` | `"Agency code"` | -| `step4_bankAccountNumberLabel` | `step-4_bank-account-number-label` | | `string` | `"Bank account number"` | -| `step4_bankAccountTypeLabel` | `step-4_bank-account-type-label` | | `string` | `"Bank account type"` | -| `step4_bankAddressLabel` | `step-4_bank-address-label` | | `string` | `"Bank address"` | -| `step4_bankCityLabel` | `step-4_bank-city-label` | | `string` | `"Bank city"` | -| `step4_bankLocationLabel` | `step-4_bank-location-label` | | `string` | `"Bank country location"` | -| `step4_bankNameLabel` | `step-4_bank-name-label` | | `string` | `"Bank name"` | -| `step4_bankPostalCodeLabel` | `step-4_bank-postal-code-label` | | `string` | `"Bank postal code"` | -| `step4_bankStateLabel` | `step-4_bank-state-label` | | `string` | `"Bank Province / State"` | -| `step4_beneficiaryAccountNameDescription` | `step-4_beneficiary-account-name-description` | | `string` | `"The beneficiary name of your bank account. Ensure this matches the name on your tax form."` | -| `step4_beneficiaryAccountNameLabel` | `step-4_beneficiary-account-name-label` | | `string` | `"Account holder name"` | -| `step4_branchCodeLabel` | `step-4_branch-code-label` | | `string` | `"Branch code"` | -| `step4_businessSelectItemLabel` | `step-4_business-select-item-label` | One of three options listed for the classification field | `string` | `"Business"` | -| `step4_checkingSelectItemLabel` | `step-4_checking-select-item-label` | | `string` | `"Checking"` | -| `step4_classificationCPFLabel` | `step-4_classification-c-p-f-label` | | `string` | `"Classification CPF"` | -| `step4_classificationEntityLabel` | `step-4_classification-entity-label` | | `string` | `"Classification entity"` | -| `step4_classificationLabel` | `step-4_classification-label` | Label text for the classification input field | `string` | `"Classification"` | -| `step4_directlyToBankAccount` | `step-4_directly-to-bank-account` | | `string` | `"Directly to my bank account"` | -| `step4_eftWithdrawalLabel` | `step-4_eft-withdrawal-label` | Default payment method to the participants’ bank account. | `string` | `"EFT Withdrawal (free)"` | -| `step4_foreignSelectItemLabel` | `step-4_foreign-select-item-label` | One of three options listed for the classification field | `string` | `"Foreign"` | -| `step4_fxWireProcessingFeeLabel` | `step-4_fx-wire-processing-fee-label` | | `string` | `"FX Wire (Processing Fee {currency}{defaultFxFee}.00)"` | -| `step4_ibanLabel` | `step-4_iban-label` | | `string` | `"IBAN"` | -| `step4_individualSelectItemLabel` | `step-4_individual-select-item-label` | One of three options listed for the classification field | `string` | `"Individual"` | -| `step4_modalButtonText` | `step-4_modal-button-text` | | `string` | `"I understand, update my information"` | -| `step4_modalDescription` | `step-4_modal-description` | | `string` | `"Updating payment information places your account and payouts on hold for up to 48 hours while we verify your change. Payments scheduled during the hold period are skipped."` | -| `step4_modalTitle` | `step-4_modal-title` | | `string` | `"Important Note"` | -| `step4_patronymicNameLabel` | `step-4_patronymic-name-label` | | `string` | `"Patronymic name"` | -| `step4_payPalInputLabel` | `step-4_pay-pal-input-label` | Displayed to participants who choose PayPal as their payout method | `string` | `"PayPal email"` | -| `step4_paymentDayFifteenthOfMonthLabelText` | `step-4_payment-day-fifteenth-of-month-label-text` | One of two payment day options | `string` | `"15th of the month"` | -| `step4_paymentDayFirstOfMonthLabelText` | `step-4_payment-day-first-of-month-label-text` | One of two payment day options | `string` | `"1st of the month"` | -| `step4_paymentDaySelectLabel` | `step-4_payment-day-select-label` | Let the participant choose what day of the month they’ll get paid | `string` | `"Payment Day"` | -| `step4_paymentMethod` | `step-4_payment-method` | | `string` | `"Payment method"` | -| `step4_paymentMethodSubtext` | `step-4_payment-method-subtext` | | `string` | `"Payouts will be sent from our referral program provider, impact.com."` | -| `step4_paymentSchedule` | `step-4_payment-schedule` | | `string` | `"Payment schedule"` | -| `step4_paymentScheduleBalanceThreshold` | `step-4_payment-schedule-balance-threshold` | | `string` | `"Pay me when my balance reaches a threshold"` | -| `step4_paymentScheduleFixedDay` | `step-4_payment-schedule-fixed-day` | | `string` | `"Pay me on a fixed day of the month"` | -| `step4_paymentThresholdSelectLabel` | `step-4_payment-threshold-select-label` | Participant use this field to select the balance at which they want to be paid | `string` | `"Payment threshold"` | -| `step4_routingCodeLabel` | `step-4_routing-code-label` | | `string` | `"{bankCountry, select, AU {BSB number} CA {Routing number} CZ {Bank code} HK {Clearing code} SG {Clearing code} US {ABA routing number} NZ {BSB number} ZA {Bank/Branch number} IN {IFSC} CNY {CNAPS} other {Routing code} }"` | -| `step4_savingsSelectItemLabel` | `step-4_savings-select-item-label` | Label text for the savings account type select item | `string` | `"Savings"` | -| `step4_swiftCodeLabel` | `step-4_swift-code-label` | | `string` | `"SWIFT code"` | -| `step4_taxAndPayouts` | `step-4_tax-and-payouts` | | `string` | `"Payouts"` | -| `step4_taxPayerIdLabel` | `step-4_tax-payer-id-label` | | `string` | `"Beneficiary INN"` | -| `step4_toPayPalAccount` | `step-4_to-pay-pal-account` | | `string` | `"PayPal (2% processing fee capped to {feeCap})"` | -| `step4_verifyEmailDescriptionText` | `step-4_verify-email-description-text` | | `string` | `"Verify your email to update your payment settings. Enter the code sent to {email} from our referral provider, impact.com."` | -| `step4_verifyEmailHeaderText` | `step-4_verify-email-header-text` | | `string` | `"Verify your email"` | -| `step4_voCodeLabel` | `step-4_vo-code-label` | | `string` | `"VO code"` | -| `supportLink` | `support-link` | Link text for contacting support team | `string` | `"support team"` | -| `taxAndPayoutsDescription` | `tax-and-payouts-description` | Displayed at the top of the page on all set up steps. | `string` | `"Submit your tax documents and add your banking information to receive your rewards."` | -| `termsAndConditions` | `terms-and-conditions` | Link text for Terms and Conditions | `string` | `"Terms and Conditions"` | +| Property | Attribute | Description | Type | Default | +| --------------------------------------------------- | ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `backButton` | `back-button` | | `string` | `"Back"` | +| `cancelButton` | `cancel-button` | | `string` | `"Cancel"` | +| `continueButton` | `continue-button` | | `string` | `"Continue"` | +| `dashboard_accountReviewDescription` | `dashboard_account-review-description` | | `string` | `"This process takes 48 hours, payouts are on hold until it's completed. You will receive an email from our referral provider, Impact.com, if any issues arise. It contains details on how to resolve this issue. If you need further assistance, please reach out to our {supportLink}."` | +| `dashboard_accountReviewHeader` | `dashboard_account-review-header` | | `string` | `"Your account is under review"` | +| `dashboard_accountText` | `dashboard_account-text` | Shown before the participant’s bank account information. | `string` | `"Account"` | +| `dashboard_badgeTextAwaitingReview` | `dashboard_badge-text-awaiting-review` | Additional text displayed next to the tax form's status badge. | `string` | `"Awaiting review. Submitted on {dateSubmitted}."` | +| `dashboard_badgeTextSubmittedOn` | `dashboard_badge-text-submitted-on` | Additional text displayed next to the tax form's status badge | `string` | `"Submitted on {dateSubmitted}."` | +| `dashboard_badgeTextSubmittedOnW8` | `dashboard_badge-text-submitted-on-w-8` | Additional text displayed next to the tax form's status badge. | `string` | `"Submitted on {dateSubmitted}. Valid for three years after submission."` | +| `dashboard_bankNameMismatchDescription` | `dashboard_bank-name-mismatch-description` | | `string` | `"The bank account (beneficiary) name in your payment information does not match what was submitted in your tax form. Please review and update your payment information or tax form so that they match exactly and do not include any invalid characters. Your payouts are on hold until this is resolved."` | +| `dashboard_bankNameMismatchHeader` | `dashboard_bank-name-mismatch-header` | | `string` | `"Your payment information does not match your tax form"` | +| `dashboard_bankingInformationSectionHeader` | `dashboard_banking-information-section-header` | | `string` | `"Payout Information"` | +| `dashboard_beneficiaryNameInvalidDescription` | `dashboard_beneficiary-name-invalid-description` | | `string` | `"The account holder (beneficiary) name in your payment information does not match what was submitted in your tax form. Please review and update your payment information or tax form so that they match exactly and do not include any invalid characters. Your payouts are on hold until this is resolved."` | +| `dashboard_beneficiaryNameInvalidHeader` | `dashboard_beneficiary-name-invalid-header` | | `string` | `"Your payment information does not match your tax form"` | +| `dashboard_beneficiaryNameMismatchDescription` | `dashboard_beneficiary-name-mismatch-description` | | `string` | `"The account holder (beneficiary) name in your payment information does not match what was submitted in your tax form. Please review and update your payment information or tax form so that they match exactly and do not include any invalid characters. Your payouts are on hold until this is resolved."` | +| `dashboard_beneficiaryNameMismatchHeader` | `dashboard_beneficiary-name-mismatch-header` | | `string` | `"Your payment information does not match your tax form"` | +| `dashboard_dateColumnTitle` | `dashboard_date-column-title` | Part of the Invoice table displayed at the bottom of the page. | `string` | `"Date"` | +| `dashboard_earningsAfterTaxColumnTitle` | `dashboard_earnings-after-tax-column-title` | Part of the Invoice table displayed at the bottom of the page. | `string` | `"Earnings after tax"` | +| `dashboard_earningsColumnTitle` | `dashboard_earnings-column-title` | Part of the Invoice table displayed at the bottom of the page. | `string` | `"Earnings"` | +| `dashboard_editPaymentInformationButton` | `dashboard_edit-payment-information-button` | | `string` | `"Edit payout information"` | +| `dashboard_indirectTaxColumnTitle` | `dashboard_indirect-tax-column-title` | Part of the Invoice table displayed at the bottom of the page. | `string` | `"Indirect tax"` | +| `dashboard_indirectTaxDetails` | `dashboard_indirect-tax-details` | Displayed to participants who have submitted their indirect tax information. | `string` | `"{indirectTaxType} number: {indirectTaxNumber}"` | +| `dashboard_indirectTaxInfoCanada` | `dashboard_indirect-tax-info-canada` | If the participant is registered for indirect tax in Canada, display the province they’re registered in. | `string` | `"Registered in {province}, {country}"` | +| `dashboard_indirectTaxInfoOtherCountry` | `dashboard_indirect-tax-info-other-country` | If the participant is registered for indirect tax, display the country they’re registered in. | `string` | `"Registered in {country}"` | +| `dashboard_indirectTaxInfoSectionHeader` | `dashboard_indirect-tax-info-section-header` | | `string` | `"Indirect tax"` | +| `dashboard_indirectTaxInfoSpain` | `dashboard_indirect-tax-info-spain` | If the participant is registered for indirect tax in Spain, display the region they’re registered in. | `string` | `"Registered in {country}, {subRegion}"` | +| `dashboard_indirectTaxTooltipSupport` | `dashboard_indirect-tax-tooltip-support` | | `string` | `"To make changes to your indirect tax information, please contact our {supportLink}."` | +| `dashboard_invalidForm` | `dashboard_invalid-form` | Additional text displayed next to the tax form's status badge. | `string` | `"Make sure your information is correct and submit new form."` | +| `dashboard_invoiceColumnTitle` | `dashboard_invoice-column-title` | Part of the Invoice table displayed at the bottom of the page. | `string` | `"Invoice"` | +| `dashboard_invoiceDescription` | `dashboard_invoice-description` | | `string` | `"View and download your invoices to report your earnings and stay tax compliant."` | +| `dashboard_invoiceEmptyStateHeader` | `dashboard_invoice-empty-state-header` | | `string` | `"View your invoice details"` | +| `dashboard_invoiceEmptyStateText` | `dashboard_invoice-empty-state-text` | | `string` | `"Refer a friend to view the status of your invoices and rewards earned"` | +| `dashboard_invoiceHeader` | `dashboard_invoice-header` | | `string` | `"Invoices"` | +| `dashboard_invoiceMoreLabel` | `dashboard_invoice-more-label` | | `string` | `"Next"` | +| `dashboard_invoicePrevLabel` | `dashboard_invoice-prev-label` | | `string` | `"Prev"` | +| `dashboard_newFormButton` | `dashboard_new-form-button` | | `string` | `"Submit new tax form"` | +| `dashboard_noFormNeededSubtext` | `dashboard_no-form-needed-subtext` | No other statuses or badges will be displayed in the tax form section in this case. | `string` | `"Tax documents are only required if you are based in the US or joining the referral program of a US based brand."` | +| `dashboard_notRegisteredForTax` | `dashboard_not-registered-for-tax` | | `string` | `"Not registered for indirect tax. If you’ve previously registered with your tax authority, contact our {supportLink} to add your information to stay tax compliant."` | +| `dashboard_paymentOnHoldDescription` | `dashboard_payment-on-hold-description` | | `string` | `"Your payout is temporarily on hold while we review your new payment information, this process is usually resolved within 48 hours."` | +| `dashboard_paymentOnHoldHeader` | `dashboard_payment-on-hold-header` | | `string` | `"We are reviewing your new payout settings"` | +| `dashboard_paymentReturnedDescription` | `dashboard_payment-returned-description` | | `string` | `"Our recent payment attempt for your earnings was unsuccessful. Please review your payment information and make sure it is correct."` | +| `dashboard_paymentReturnedHeader` | `dashboard_payment-returned-header` | | `string` | `"Payout unsuccessful"` | +| `dashboard_payoutFromImpact` | `dashboard_payout-from-impact` | Displayed under the payout details card. | `string` | `"Your balance may take up to 24 hours to update. Payouts will be sent from our referral program provider, impact.com."` | +| `dashboard_payoutHoldAlertDescription` | `dashboard_payout-hold-alert-description` | Part of the alert displayed at the top of the page when there’s been an issue preventing payouts. | `string` | `"Please contact our {supportLink} or check your inbox for an email from our referral program provider, impact.com."` | +| `dashboard_payoutHoldAlertHeader` | `dashboard_payout-hold-alert-header` | Part of the alert displayed at the top of the page when there’s been an issue preventing payouts. | `string` | `"Your payout is on hold"` | +| `dashboard_payoutMissingInformationText` | `dashboard_payout-missing-information-text` | Text displayed for existing publishers that do not have saved banking information. | `string` | `"Missing banking information, go to Impact.com to resolve."` | +| `dashboard_qstNumber` | `dashboard_qst-number` | Displayed to participants registered in Quebec, Canada. | `string` | `"QST number: {qstNumber}"` | +| `dashboard_replaceTaxFormModalBodyText` | `dashboard_replace-tax-form-modal-body-text` | | `string` | `"Submitting a new tax form will remove your existing form. Make sure to sign and complete your new tax form to prevent any issues with your next payout."` | +| `dashboard_replaceTaxFormModalHeader` | `dashboard_replace-tax-form-modal-header` | | `string` | `"Replace existing tax form"` | +| `dashboard_requiredTaxForm` | `dashboard_required-tax-form` | Additional text displayed next to the tax form's status badge | `string` | `"Your payouts are on hold until you submit a {taxFormType} tax form."` | +| `dashboard_statusBadgeText` | `dashboard_status-badge-text` | | `string` | `"{badgeText, select, payoutToday {Payout Today} nextPayout {Next Payout} other {Failed} }"` | +| `dashboard_statusTextActive` | `dashboard_status-text-active` | | `string` | `"Active"` | +| `dashboard_statusTextNotActive` | `dashboard_status-text-not-active` | | `string` | `"Invalid Tax Form"` | +| `dashboard_statusTextNotVerified` | `dashboard_status-text-not-verified` | Displayed when the participant submitted their form but it is awaiting review. | `string` | `"Not Verified"` | +| `dashboard_statusTextRequired` | `dashboard_status-text-required` | Displayed when the participant has not submitted their required tax form. | `string` | `"Required"` | +| `dashboard_subRegionTaxNumber` | `dashboard_sub-region-tax-number` | | `string` | `"Income tax number: {subRegionTaxNumber}"` | +| `dashboard_taxAlertHeaderNotActiveW8` | `dashboard_tax-alert-header-not-active-w-8` | Part of the alert displayed at the top of the page. | `string` | `"{documentType} tax form is invalid"` | +| `dashboard_taxAlertHeaderNotActiveW9` | `dashboard_tax-alert-header-not-active-w-9` | Part of the alert displayed at the top of the page. | `string` | `"Your W-9 tax form has personal information that doesn’t match your profile"` | +| `dashboard_taxAlertNotActiveMessageW8` | `dashboard_tax-alert-not-active-message-w-8` | Part of the alert displayed at the top of the page. | `string` | `"Your tax form may have expired or has personal information that doesn’t match your profile. Please submit a new {documentType} form."` | +| `dashboard_taxAlertNotActiveMessageW9` | `dashboard_tax-alert-not-active-message-w-9` | Part of the alert displayed at the top of the page. | `string` | `"Please resubmit a new {documentType} form."` | +| `dashboard_taxDocumentSectionHeader` | `dashboard_tax-document-section-header` | | `string` | `"Tax documents"` | +| `dashboard_taxDocumentSectionSubHeader` | `dashboard_tax-document-section-sub-header` | Displayed under the tax document section header. | `string` | `"{documentType} tax form"` | +| `dashboard_termsAndConditions` | `dashboard_terms-and-conditions` | | `string` | `"Terms and Conditions"` | +| `dashboard_thresholdPayoutText` | `dashboard_threshold-payout-text` | Display participants' payout preference on the payout information card, indicating the balance at which they want to get paid. | `string` | `"Next payout occurs when balance is {thresholdBalance}"` | +| `dashboard_verificationFailedInternalDescription` | `dashboard_verification-failed-internal-description` | | `string` | `"Identity verification has failed. Our team is reviewing the report and will contact you with further information. If you don't hear from us contact our {supportLink}."` | +| `dashboard_verificationFailedInternalHeader` | `dashboard_verification-failed-internal-header` | | `string` | `"Identity verification usuccessful"` | +| `dashboard_verificationRequiredButtonText` | `dashboard_verification-required-button-text` | Part of the alert displayed at the top of the page when the user needs to verify their identity. | `string` | `"Start Verification"` | +| `dashboard_verificationRequiredDescription` | `dashboard_verification-required-description` | Part of the alert displayed at the top of the page when the user needs to verify their identity | `string` | `"Complete your verification to start receiving your cash rewards. It should only take a few minutes verify. If you run in to an issue verifying your identity contact our {supportLink}."` | +| `dashboard_verificationRequiredHeader` | `dashboard_verification-required-header` | Part of the alert displayed at the top of the page when the user needs to verify their identity. | `string` | `"Verify your identity"` | +| `dashboard_verificationRequiredInternalDescription` | `dashboard_verification-required-internal-description` | | `string` | `"Identity verification submission has been received. Our system is currently performing additional checks and analyzing the results. You will be updated shortly. If you don't hear from us contact our {supportLink}."` | +| `dashboard_verificationRequiredInternalHeader` | `dashboard_verification-required-internal-header` | | `string` | `"Identity verification in progress"` | +| `dashboard_verificationReviewInternalDescription` | `dashboard_verification-review-internal-description` | | `string` | `"Identity verification requires further review due to a potential error. Our team is reviewing the information and will update you shortly. If you don't hear from us contact our {supportLink}."` | +| `dashboard_verificationReviewInternalHeader` | `dashboard_verification-review-internal-header` | | `string` | `"Identity verification under review"` | +| `dashboard_w9RequiredButtonText` | `dashboard_w-9-required-button-text` | | `string` | `"Submit W-9"` | +| `dashboard_w9RequiredDescription` | `dashboard_w-9-required-description` | | `string` | `"You have surpassed the $600 threshold requiring a W-9 form or have multiple accounts with impact.com. To remove the hold, please submit your W-9 form."` | +| `dashboard_w9RequiredHeader` | `dashboard_w-9-required-header` | | `string` | `"Your next payout is on hold"` | +| `dashboard_withdrawalSettingsInvalidDescription` | `dashboard_withdrawal-settings-invalid-description` | | `string` | `"There are missing fields or invalid characters in your payment information. Please review your information and make sure it is correct. Your payouts are on hold until this is resolved."` | +| `dashboard_withdrawalSettingsInvalidHeader` | `dashboard_withdrawal-settings-invalid-header` | | `string` | `"Your payment information is incomplete or includes invalid characters"` | +| `demoData` | -- | | `{ step1_firstName?: string; step1_lastName?: string; step1_email?: string; step1_country?: string; step1_phoneNumber?: string; step1_address?: string; step1_city?: string; step1_state?: string; step1_province?: string; step1_region?: string; step1_postalCode?: string; step1_currency?: string; step1_allowBankingCollection?: string; step1_personalInformation?: string; step1_termsAndConditionsLabel?: string; step1_termsAndConditionsLink?: string; step1_searchForCurrencyText?: string; step2_indirectTax?: string; step2_indirectTaxDescription?: string; step2_indirectTaxDetails?: string; step2_otherRegion?: string; step2_otherRegionSubtext?: string; step2_notRegistered?: string; step2_notRegisteredSubtext?: string; step2_selectedRegion?: string; step2_indirectTaxNumber?: string; step2_province?: string; step2_indirectTaxNumberError?: string; step2_qstNumber?: string; step2_isRegisteredQST?: string; step2_isRegisteredSubRegionIncomeTax?: string; step2_subRegion?: string; step2_subRegionTaxNumberLabel?: string; step2_cannotChangeInfoAlert?: string; step3_taxForm?: string; step3_taxFormLabel?: string; step3_participantType?: string; step3_businessEntity?: string; step3_individualParticipant?: string; step3_taxFormDescription?: string; step3_taxFormDescriptionIndividualParticipant?: string; step3_taxFormDescriptionBusinessEntity?: string; step3_docusignExpired?: string; step3_docusignSessionWarning?: string; step3_docusignError?: string; step3_refreshButton?: string; step3_exitButton?: string; step3_modalTitle?: string; step3_modalDescription?: string; step3_modalButtonText?: string; step4_taxAndPayouts?: string; step4_directlyToBankAccount?: string; step4_toPayPalAccount?: string; step4_paymentScheduleBalanceThreshold?: string; step4_paymentScheduleFixedDay?: string; step4_paymentDaySelectLabel?: string; step4_paymentThresholdSelectLabel?: string; step4_paymentDayFirstOfMonthLabelText?: string; step4_paymentDayFifteenthOfMonthLabelText?: string; step4_paymentMethod?: string; step4_paymentSchedule?: string; step4_paymentMethodSubtext?: string; step4_payPalInputLabel?: string; step4_bankLocationLabel?: string; step4_beneficiaryAccountNameLabel?: string; step4_beneficiaryAccountNameDescription?: string; step4_bankAccountTypeLabel?: string; step4_checkingSelectItemLabel?: string; step4_savingsSelectItemLabel?: string; step4_bankAccountNumberLabel?: string; step4_ibanLabel?: string; step4_swiftCodeLabel?: string; step4_routingCodeLabel?: string; step4_bankNameLabel?: string; step4_classificationEntityLabel?: string; step4_businessSelectItemLabel?: string; step4_individualSelectItemLabel?: string; step4_foreignSelectItemLabel?: string; step4_classificationCPFLabel?: string; step4_patronymicNameLabel?: string; step4_voCodeLabel?: string; step4_agencyCodeLabel?: string; step4_branchCodeLabel?: string; step4_branchNameLabel?: string; step4_classificationLabel?: string; step4_classificationCodeLabel?: string; step4_taxPayerIdLabel?: string; step4_bankAddressLabel?: string; step4_bankCityLabel?: string; step4_bankStateLabel?: string; step4_bankPostalCodeLabel?: string; step4_eftWithdrawalLabel?: string; step4_fxWireProcessingFeeLabel?: string; step4_verifyEmailHeaderText?: string; step4_verifyEmailDescriptionText?: string; step4_modalTitle?: string; step4_modalDescription?: string; step4_modalButtonText?: string; dashboard_statusTextActive?: string; dashboard_statusTextNotActive?: string; dashboard_statusTextNotVerified?: string; dashboard_statusTextRequired?: string; dashboard_requiredTaxForm?: string; dashboard_badgeTextSubmittedOn?: string; dashboard_badgeTextSubmittedOnW8?: string; dashboard_badgeTextAwaitingReview?: string; dashboard_taxAlertHeaderNotActiveW9?: string; dashboard_taxAlertHeaderNotActiveW8?: string; dashboard_taxAlertNotActiveMessageW9?: string; dashboard_taxAlertNotActiveMessageW8?: string; dashboard_invalidForm?: string; dashboard_bankingInformationSectionHeader?: string; dashboard_taxDocumentSectionHeader?: string; dashboard_taxDocumentSectionSubHeader?: string; dashboard_noFormNeededSubtext?: string; dashboard_indirectTaxInfoSectionHeader?: string; dashboard_indirectTaxInfoCanada?: string; dashboard_indirectTaxInfoOtherCountry?: string; dashboard_indirectTaxInfoSpain?: string; dashboard_indirectTaxTooltipSupport?: string; dashboard_indirectTaxDetails?: string; dashboard_newFormButton?: string; dashboard_editPaymentInformationButton?: string; dashboard_notRegisteredForTax?: string; dashboard_qstNumber?: string; dashboard_subRegionTaxNumber?: string; dashboard_statusBadgeText?: string; dashboard_thresholdPayoutText?: string; dashboard_accountText?: string; dashboard_invoiceColumnTitle?: string; dashboard_dateColumnTitle?: string; dashboard_earningsColumnTitle?: string; dashboard_indirectTaxColumnTitle?: string; dashboard_payoutFromImpact?: string; dashboard_earningsAfterTaxColumnTitle?: string; dashboard_payoutHoldAlertHeader?: string; dashboard_payoutHoldAlertDescription?: string; dashboard_payoutMissingInformationText?: string; dashboard_invoiceDescription?: string; dashboard_invoicePrevLabel?: string; dashboard_invoiceMoreLabel?: string; dashboard_invoiceHeader?: string; dashboard_invoiceEmptyStateHeader?: string; dashboard_invoiceEmptyStateText?: string; dashboard_replaceTaxFormModalHeader?: string; dashboard_replaceTaxFormModalBodyText?: string; dashboard_verificationRequiredHeader?: string; dashboard_verificationRequiredDescription?: string; dashboard_verificationRequiredInternalHeader?: string; dashboard_verificationRequiredInternalDescription?: string; dashboard_verificationReviewInternalHeader?: string; dashboard_verificationReviewInternalDescription?: string; dashboard_verificationFailedInternalHeader?: string; dashboard_verificationFailedInternalDescription?: string; dashboard_verificationRequiredButtonText?: string; dashboard_w9RequiredHeader?: string; dashboard_w9RequiredDescription?: string; dashboard_accountReviewHeader?: string; dashboard_accountReviewDescription?: string; dashboard_paymentOnHoldHeader?: string; dashboard_paymentOnHoldDescription?: string; dashboard_beneficiaryNameInvalidHeader?: string; dashboard_beneficiaryNameInvalidDescription?: string; dashboard_beneficiaryNameMismatchHeader?: string; dashboard_beneficiaryNameMismatchDescription?: string; dashboard_bankNameMismatchHeader?: string; dashboard_bankNameMismatchDescription?: string; dashboard_withdrawalSettingsInvalidHeader?: string; dashboard_withdrawalSettingsInvalidDescription?: string; dashboard_paymentReturnedHeader?: string; dashboard_paymentReturnedDescription?: string; dashboard_termsAndConditions?: string; dashboard_w9RequiredButtonText?: string; formStep?: string; generalErrorTitle?: string; generalErrorDescription?: string; fieldRequiredError?: string; invalidCharacterError?: string; fieldInvalidError?: string; continueButton?: string; backButton?: string; cancelButton?: string; isPartnerAlertHeader?: string; isPartnerAlertDescription?: string; searchForCountryText?: string; loadingErrorAlertHeader?: string; loadingErrorAlertDescription?: string; taxAndPayoutsDescription?: string; supportLink?: string; termsAndConditions?: string; stateController?: string; demoData?: Partial>; disconnectedCallback?: () => void; getGeneralStepTextProps?: (prefix: string) => PickPrefix & { generalErrorTitle: string; generalErrorDescription: string; fieldRequiredError: string; invalidCharacterError: string; fieldInvalidError: string; continueButton: string; backButton: string; cancelButton: string; isPartnerAlertHeader: string; isPartnerAlertDescription: string; loadingErrorAlertHeader: string; loadingErrorAlertDescription: string; taxAndPayoutsDescription: string; searchForCountryText: string; formStep: string; supportLink: string; }; render?: () => any; }` | `undefined` | +| `fieldInvalidError` | `field-invalid-error` | Displayed under a field when it has an invalid entry. | `string` | `"{fieldName} is invalid"` | +| `fieldRequiredError` | `field-required-error` | Displayed under a field that is missing required information. | `string` | `"{fieldName} is required"` | +| `formStep` | `form-step` | | `string` | `"Step {step} of {count}"` | +| `generalErrorDescription` | `general-error-description` | Part of the alert displayed at the top of the page. | `string` | `"Please review your information and try again. If this problem continues, contact our {supportLink}."` | +| `generalErrorTitle` | `general-error-title` | Part of the alert displayed at the top of the page. | `string` | `"There was a problem submitting your information"` | +| `invalidCharacterError` | `invalid-character-error` | Displayed under a field that includes invalid characters (non-ASCII). | `string` | `"{fieldName} includes characters that aren't supported."` | +| `isPartnerAlertDescription` | `is-partner-alert-description` | Part of the alert displayed at the top of the page if the participant is already a registered partner on impact.com. | `string` | `"If you don’t recognize this referral program provider or believe this is a mistake, please contact our {supportLink} or sign up for this referral program with a different email."` | +| `isPartnerAlertHeader` | `is-partner-alert-header` | Part of the alert displayed at the top of the page if the participant is already a registered partner on impact.com. | `string` | `"An account with this email already exists with our referral program provider, impact.com"` | +| `loadingErrorAlertDescription` | `loading-error-alert-description` | Part of the alert displayed at the top of the page. | `string` | `"Please refresh the page and try again. If this problem continues, contact our {supportLink}."` | +| `loadingErrorAlertHeader` | `loading-error-alert-header` | Part of the alert displayed at the top of the page. | `string` | `"There was a problem loading your form"` | +| `searchForCountryText` | `search-for-country-text` | Placeholder text displayed in the country search dropdown | `string` | `"Search for country.."` | +| `stateController` | `state-controller` | | `string` | `"{}"` | +| `step1_address` | `step-1_address` | | `string` | `"Address"` | +| `step1_allowBankingCollection` | `step-1_allow-banking-collection` | Edit the property called terms and conditions text to change what's displayed for {termsAndConditionsLink}. | `string` | `"I have read the {termsAndConditionsLink} and allow impact.com to collect my tax and banking information"` | +| `step1_city` | `step-1_city` | | `string` | `"City"` | +| `step1_country` | `step-1_country` | | `string` | `"Country"` | +| `step1_currency` | `step-1_currency` | | `string` | `"Currency"` | +| `step1_email` | `step-1_email` | | `string` | `"Email"` | +| `step1_firstName` | `step-1_first-name` | | `string` | `"First name"` | +| `step1_lastName` | `step-1_last-name` | | `string` | `"Last name"` | +| `step1_personalInformation` | `step-1_personal-information` | | `string` | `"Personal Information"` | +| `step1_phoneNumber` | `step-1_phone-number` | | `string` | `"Phone number"` | +| `step1_postalCode` | `step-1_postal-code` | | `string` | `"Postal code"` | +| `step1_province` | `step-1_province` | | `string` | `"Province"` | +| `step1_region` | `step-1_region` | | `string` | `"Region"` | +| `step1_searchForCurrencyText` | `step-1_search-for-currency-text` | Placeholder text displayed in the currency search dropdown | `string` | `"Search for currency.."` | +| `step1_state` | `step-1_state` | | `string` | `"State"` | +| `step1_termsAndConditionsLabel` | `step-1_terms-and-conditions-label` | The link text that appears in the terms and conditions checkbox | `string` | `"terms and conditions"` | +| `step1_termsAndConditionsLink` | `step-1_terms-and-conditions-link` | The link that appears in the terms and conditions checkbox | `string` | `"https://terms.advocate.impact.com/PayoutTermsAndConditions.html"` | +| `step2_cannotChangeInfoAlert` | `step-2_cannot-change-info-alert` | Communicate that after this step, only Support can change personal and indirect tax information. | `string` | `"Changes to your personal and indirect tax information can only be made through our {supportLink} after you complete this step. Make sure these are correct before continuing."` | +| `step2_indirectTax` | `step-2_indirect-tax` | | `string` | `"Indirect Tax"` | +| `step2_indirectTaxDescription` | `step-2_indirect-tax-description` | Displayed under the title of this step. | `string` | `"Indirect taxes (e.g. VAT, HST, GST) are transaction based taxes often applied to goods and services. Service providers are typically required to register with their tax authority and collect these taxes on behalf governments."` | +| `step2_indirectTaxDetails` | `step-2_indirect-tax-details` | Displayed with indirect tax registration options. | `string` | `"Indirect tax details"` | +| `step2_indirectTaxNumber` | `step-2_indirect-tax-number` | | `string` | `"{taxType, select, GST {GST number} HST {HST number} VAT {VAT number} CT {CT number} SST {SST number} GENERAL {Indirect tax number} other {Tax number}}"` | +| `step2_indirectTaxNumberError` | `step-2_indirect-tax-number-error` | | `string` | `"{taxType, select, GST {GST number} HST {HST number} VAT {VAT number} CT {CT number} SST {SST number} GENERAL {Indirect tax number} other {Tax number}} is required"` | +| `step2_isRegisteredQST` | `step-2_is-registered-q-s-t` | Displayed to participants registered for indirect tax in Quebec, Canada. | `string` | `"I am registered for QST Tax"` | +| `step2_isRegisteredSubRegionIncomeTax` | `step-2_is-registered-sub-region-income-tax` | Displayed to participants registered for indirect tax in Spain. | `string` | `"I am an individual registered for Income Tax purposes in Spain, and withholding tax will apply to any payments made to me."` | +| `step2_notRegistered` | `step-2_not-registered` | | `string` | `"Not registered for indirect tax"` | +| `step2_notRegisteredSubtext` | `step-2_not-registered-subtext` | Participants based in the US are considered not registered. | `string` | `"If you’ve never set up indirect tax with your tax authority, then you’re likely not considered registered."` | +| `step2_otherRegion` | `step-2_other-region` | | `string` | `"Registered for indirect tax"` | +| `step2_otherRegionSubtext` | `step-2_other-region-subtext` | Selecting this option will display fields to enter indirect tax details. | `string` | `"If you’ve registered with your tax authority, add your information to stay tax compliant."` | +| `step2_province` | `step-2_province` | | `string` | `"Province"` | +| `step2_qstNumber` | `step-2_qst-number` | Displayed to participants registered for QST. | `string` | `"QST number"` | +| `step2_selectedRegion` | `step-2_selected-region` | | `string` | `"Country / region of indirect tax"` | +| `step2_subRegion` | `step-2_sub-region` | Displayed to participants registered in Spain. | `string` | `"Sub-region"` | +| `step2_subRegionTaxNumberLabel` | `step-2_sub-region-tax-number-label` | | `string` | `"Income Tax Number"` | +| `step3_businessEntity` | `step-3_business-entity` | An option for the participant type field. Used to determine which W-8 form is required. | `string` | `"I represent a business"` | +| `step3_docusignError` | `step-3_docusign-error` | This appears inside the Docusign frame. | `string` | `"There was a problem displaying this form. Please refresh the page. If this problem continues, contact our {supportLink}."` | +| `step3_docusignExpired` | `step-3_docusign-expired` | This appears inside the Docusign frame. | `string` | `"For your security and privacy, we automatically end your session after 20 minutes of inactivity. Please refresh and re-enter your tax information to continue."` | +| `step3_docusignSessionWarning` | `step-3_docusign-session-warning` | Remind participants their session will time out after 20 minutes of inactivity. | `string` | `"For your security, we automatically end your session when you have not interacted with the form after 20 minutes."` | +| `step3_exitButton` | `step-3_exit-button` | | `string` | `"Exit"` | +| `step3_individualParticipant` | `step-3_individual-participant` | An option for the participant type field. Used to determine which W-8 form is required. | `string` | `"I am an individual participant"` | +| `step3_modalButtonText` | `step-3_modal-button-text` | | `string` | `"I understand"` | +| `step3_modalDescription` | `step-3_modal-description` | | `string` | `"Remember the name you enter in your tax form. It must exactly match the bank account holder name configured in the next step. {br}{br}Otherwise you will have to resubmit your form again and there will be delays receiving your payout."` | +| `step3_modalTitle` | `step-3_modal-title` | | `string` | `"Important Note"` | +| `step3_participantType` | `step-3_participant-type` | | `string` | `"Participant type"` | +| `step3_refreshButton` | `step-3_refresh-button` | | `string` | `"Refresh Page"` | +| `step3_taxForm` | `step-3_tax-form` | | `string` | `"Tax form"` | +| `step3_taxFormDescription` | `step-3_tax-form-description` | Displayed at the top of the page to participants based in the US. | `string` | `"Participants based in the US need to submit a {documentType} form."` | +| `step3_taxFormDescriptionBusinessEntity` | `step-3_tax-form-description-business-entity` | Displayed at the top of the page to participants representing a business. | `string` | `"Participants residing outside of the US working with a US Brand need to submit a {documentType} form."` | +| `step3_taxFormDescriptionIndividualParticipant` | `step-3_tax-form-description-individual-participant` | Displayed at the top of the page to individuals joining a US program who reside outside the country. | `string` | `"Participants residing outside of the US, joining the referral program of a US-based company, need to submit a {documentType} form."` | +| `step3_taxFormLabel` | `step-3_tax-form-label` | Display the type of tax form that the participant must submit. | `string` | `"{documentType} Tax Form"` | +| `step4_agencyCodeLabel` | `step-4_agency-code-label` | | `string` | `"Agency code"` | +| `step4_bankAccountNumberLabel` | `step-4_bank-account-number-label` | | `string` | `"Bank account number"` | +| `step4_bankAccountTypeLabel` | `step-4_bank-account-type-label` | | `string` | `"Bank account type"` | +| `step4_bankAddressLabel` | `step-4_bank-address-label` | | `string` | `"Bank address"` | +| `step4_bankCityLabel` | `step-4_bank-city-label` | | `string` | `"Bank city"` | +| `step4_bankLocationLabel` | `step-4_bank-location-label` | | `string` | `"Bank country location"` | +| `step4_bankNameLabel` | `step-4_bank-name-label` | | `string` | `"Bank name"` | +| `step4_bankPostalCodeLabel` | `step-4_bank-postal-code-label` | | `string` | `"Bank postal code"` | +| `step4_bankStateLabel` | `step-4_bank-state-label` | | `string` | `"Bank Province / State"` | +| `step4_beneficiaryAccountNameDescription` | `step-4_beneficiary-account-name-description` | | `string` | `"The beneficiary name of your bank account. Ensure this matches the name on your tax form."` | +| `step4_beneficiaryAccountNameLabel` | `step-4_beneficiary-account-name-label` | | `string` | `"Account holder name"` | +| `step4_branchCodeLabel` | `step-4_branch-code-label` | | `string` | `"Branch code"` | +| `step4_branchNameLabel` | `step-4_branch-name-label` | | `string` | `"Branch name"` | +| `step4_businessSelectItemLabel` | `step-4_business-select-item-label` | One of three options listed for the classification field | `string` | `"Business"` | +| `step4_checkingSelectItemLabel` | `step-4_checking-select-item-label` | | `string` | `"Checking"` | +| `step4_classificationCPFLabel` | `step-4_classification-c-p-f-label` | | `string` | `"Classification CPF"` | +| `step4_classificationCodeLabel` | `step-4_classification-code-label` | | `string` | `"KBE code"` | +| `step4_classificationEntityLabel` | `step-4_classification-entity-label` | | `string` | `"Classification entity"` | +| `step4_classificationLabel` | `step-4_classification-label` | Label text for the classification input field | `string` | `"Classification"` | +| `step4_directlyToBankAccount` | `step-4_directly-to-bank-account` | | `string` | `"Directly to my bank account"` | +| `step4_eftWithdrawalLabel` | `step-4_eft-withdrawal-label` | Default payment method to the participants’ bank account. | `string` | `"EFT Withdrawal (free)"` | +| `step4_foreignSelectItemLabel` | `step-4_foreign-select-item-label` | One of three options listed for the classification field | `string` | `"Foreign"` | +| `step4_fxWireProcessingFeeLabel` | `step-4_fx-wire-processing-fee-label` | | `string` | `"FX Wire (Processing Fee {currency}{defaultFxFee}.00)"` | +| `step4_ibanLabel` | `step-4_iban-label` | | `string` | `"IBAN"` | +| `step4_individualSelectItemLabel` | `step-4_individual-select-item-label` | One of three options listed for the classification field | `string` | `"Individual"` | +| `step4_modalButtonText` | `step-4_modal-button-text` | | `string` | `"I understand, update my information"` | +| `step4_modalDescription` | `step-4_modal-description` | | `string` | `"Updating payment information places your account and payouts on hold for up to 48 hours while we verify your change. Payments scheduled during the hold period are skipped."` | +| `step4_modalTitle` | `step-4_modal-title` | | `string` | `"Important Note"` | +| `step4_patronymicNameLabel` | `step-4_patronymic-name-label` | | `string` | `"Patronymic name"` | +| `step4_payPalInputLabel` | `step-4_pay-pal-input-label` | Displayed to participants who choose PayPal as their payout method | `string` | `"PayPal email"` | +| `step4_paymentDayFifteenthOfMonthLabelText` | `step-4_payment-day-fifteenth-of-month-label-text` | One of two payment day options | `string` | `"15th of the month"` | +| `step4_paymentDayFirstOfMonthLabelText` | `step-4_payment-day-first-of-month-label-text` | One of two payment day options | `string` | `"1st of the month"` | +| `step4_paymentDaySelectLabel` | `step-4_payment-day-select-label` | Let the participant choose what day of the month they’ll get paid | `string` | `"Payment Day"` | +| `step4_paymentMethod` | `step-4_payment-method` | | `string` | `"Payment method"` | +| `step4_paymentMethodSubtext` | `step-4_payment-method-subtext` | | `string` | `"Payouts will be sent from our referral program provider, impact.com."` | +| `step4_paymentSchedule` | `step-4_payment-schedule` | | `string` | `"Payment schedule"` | +| `step4_paymentScheduleBalanceThreshold` | `step-4_payment-schedule-balance-threshold` | | `string` | `"Pay me when my balance reaches a threshold"` | +| `step4_paymentScheduleFixedDay` | `step-4_payment-schedule-fixed-day` | | `string` | `"Pay me on a fixed day of the month"` | +| `step4_paymentThresholdSelectLabel` | `step-4_payment-threshold-select-label` | Participant use this field to select the balance at which they want to be paid | `string` | `"Payment threshold"` | +| `step4_routingCodeLabel` | `step-4_routing-code-label` | | `string` | `"{bankCountry, select, AU {BSB number} CA {Routing number} CZ {Bank code} HK {Clearing code} SG {Clearing code} US {ABA routing number} NZ {BSB number} ZA {Bank/Branch number} IN {IFSC} CNY {CNAPS} other {Routing code} }"` | +| `step4_savingsSelectItemLabel` | `step-4_savings-select-item-label` | Label text for the savings account type select item | `string` | `"Savings"` | +| `step4_swiftCodeLabel` | `step-4_swift-code-label` | | `string` | `"SWIFT code"` | +| `step4_taxAndPayouts` | `step-4_tax-and-payouts` | | `string` | `"Payouts"` | +| `step4_taxPayerIdLabel` | `step-4_tax-payer-id-label` | | `string` | `"Beneficiary INN"` | +| `step4_toPayPalAccount` | `step-4_to-pay-pal-account` | | `string` | `"PayPal (2% processing fee capped to {feeCap})"` | +| `step4_verifyEmailDescriptionText` | `step-4_verify-email-description-text` | | `string` | `"Verify your email to update your payment settings. Enter the code sent to {email} from our referral provider, impact.com."` | +| `step4_verifyEmailHeaderText` | `step-4_verify-email-header-text` | | `string` | `"Verify your email"` | +| `step4_voCodeLabel` | `step-4_vo-code-label` | | `string` | `"VO code"` | +| `supportLink` | `support-link` | Link text for contacting support team | `string` | `"support team"` | +| `taxAndPayoutsDescription` | `tax-and-payouts-description` | Displayed at the top of the page on all set up steps. | `string` | `"Submit your tax documents and add your banking information to receive your rewards."` | +| `termsAndConditions` | `terms-and-conditions` | Link text for Terms and Conditions | `string` | `"Terms and Conditions"` | ## Dependencies diff --git a/packages/mint-components/src/components/tax-and-cash/sqm-tax-and-cash/sqm-tax-and-cash.tsx b/packages/mint-components/src/components/tax-and-cash/sqm-tax-and-cash/sqm-tax-and-cash.tsx index 4ac362a10a..3d27b0569a 100644 --- a/packages/mint-components/src/components/tax-and-cash/sqm-tax-and-cash/sqm-tax-and-cash.tsx +++ b/packages/mint-components/src/components/tax-and-cash/sqm-tax-and-cash/sqm-tax-and-cash.tsx @@ -91,12 +91,6 @@ export class TaxAndCashMonolith { * @uiGroup Step 1 Properties */ @Prop() step1_currency: string = "Currency"; - /** - * @uiName Currency field help text - * @uiGroup Step 1 Properties - */ - @Prop() step1_currencyHelpText: string = - "Choose your preferred payout currency"; /** * Edit the property called terms and conditions text to change what's displayed for {termsAndConditionsLink}. * @uiName Terms and conditions checkbox @@ -557,6 +551,12 @@ export class TaxAndCashMonolith { */ @Prop() step4_branchCodeLabel: string = "Branch code"; + /** + * @uiName Branch name field label + * @uiGroup Step 4 Properties + */ + @Prop() step4_branchNameLabel: string = "Branch name"; + /** * Label text for the classification input field * @uiName Classification field label @@ -564,6 +564,12 @@ export class TaxAndCashMonolith { */ @Prop() step4_classificationLabel: string = "Classification"; + /** + * @uiName Classification code field label + * @uiGroup Step 4 Properties + */ + @Prop() step4_classificationCodeLabel: string = "KBE code"; + /** * @uiName Taxpayer ID field label * @uiGroup Step 4 Properties @@ -1385,7 +1391,7 @@ export class TaxAndCashMonolith { {this.supportLink} ), - } + }, ) as string } /> @@ -1408,7 +1414,7 @@ function useDemoTaxAndCash(props: TaxAndCashMonolith) { key === "sqm-tax-and-cash" ? { ...prev, ...states[key] } : { ...prev, [`${key}_stateController`]: states[key] }, - {} + {}, ); return deepmerge( @@ -1417,6 +1423,6 @@ function useDemoTaxAndCash(props: TaxAndCashMonolith) { setStep, }, props.demoData || formatted || {}, - { arrayMerge: (_, a) => a } + { arrayMerge: (_, a) => a }, ); } diff --git a/packages/mint-components/src/components/tax-and-cash/sqm-tax-and-cash/useTaxAndCash.tsx b/packages/mint-components/src/components/tax-and-cash/sqm-tax-and-cash/useTaxAndCash.tsx index 2d3cd65de2..ef2402e701 100644 --- a/packages/mint-components/src/components/tax-and-cash/sqm-tax-and-cash/useTaxAndCash.tsx +++ b/packages/mint-components/src/components/tax-and-cash/sqm-tax-and-cash/useTaxAndCash.tsx @@ -2,12 +2,13 @@ import { getContextValueName, useHost, useLocale, + useMutation, useParentQuery, useParentState, useUserIdentity, } from "@saasquatch/component-boilerplate"; import { useEffect, useMemo } from "@saasquatch/universal-hooks"; -import { getCountryObj } from "../utils"; +import { getCountryObj, isValidI18nPhoneNumber } from "../utils"; import { COUNTRIES_NAMESPACE, COUNTRIES_QUERY_NAMESPACE, @@ -32,6 +33,11 @@ import { UserFormContext, UserQuery, } from "../data"; +import { ImpactConnection } from "../../../saasquatch"; +import { + COMPLETE_PARTNER, + CompletePartnerResult, +} from "../sqm-indirect-tax-form/useIndirectTaxForm"; function getCurrentStep(user: UserQuery["user"]) { if (!user.impactConnection?.connected || !user.impactConnection?.publisher) { @@ -44,8 +50,31 @@ function getCurrentStep(user: UserQuery["user"]) { withdrawalSettings, brandedSignup, payoutsAccount, + billingAddress, + billingCity, + billingCountryCode, + billingPostalCode, + phoneNumber, + phoneNumberCountryCode, } = user.impactConnection.publisher; + const isCompleted = user.impactConnection.connectionStatus === "COMPLETED"; + + if (!isCompleted) { + const hasBillingInfo = + billingAddress && + billingCity && + billingCountryCode && + billingPostalCode && + phoneNumberCountryCode && + phoneNumber && + isValidI18nPhoneNumber(phoneNumberCountryCode, phoneNumber); + + if (!hasBillingInfo) { + return "/1"; + } + } + // If they do have a required document, look at current document if (requiredTaxDocumentType && !currentTaxDocument) { // Specific to custom CASH setting, @@ -68,6 +97,9 @@ export function useTaxAndCash() { const user = useUserIdentity(); const locale = useLocale(); + const [completeImpactPartner, { loading: completeLoading }] = + useMutation(COMPLETE_PARTNER); + // State for current step of form const [step, setStep] = useParentState({ namespace: TAX_CONTEXT_NAMESPACE, @@ -97,7 +129,7 @@ export function useTaxAndCash() { { namespace: CURRENCIES_NAMESPACE, initialValue: [], - } + }, ); const [_countriesContext, _setCountriesContext] = useParentState< @@ -158,7 +190,7 @@ export function useTaxAndCash() { financeNetworkData?.impactFinanceNetworkSettings?.data?.reduce( (agg, settings) => { const currency = currenciesData?.currencies?.data?.find( - (currency) => currency.currencyCode === settings.currency + (currency) => currency.currencyCode === settings.currency, ); // Currency not in supported list if (!currency) return agg; @@ -176,7 +208,7 @@ export function useTaxAndCash() { return [...agg, currency]; }, - [] + [], ); return allValidCurrencies; }, [financeNetworkData, countryCode]); @@ -194,9 +226,9 @@ export function useTaxAndCash() { new Set( paymentOptions ?.map((option) => option.countryCode) - .filter((value) => value) + .filter((value) => value), ), - [paymentOptions] + [paymentOptions], ); const _topCountries = ["CA", "GB", "US"]; @@ -205,7 +237,7 @@ export function useTaxAndCash() { () => Array.from(availableCountries) .map((countryCode) => - getCountryObj({ countryCode, locale: intlLocale }) + getCountryObj({ countryCode, locale: intlLocale }), ) .sort(sortByName) .reduce((prev, countryObj) => { @@ -213,7 +245,7 @@ export function useTaxAndCash() { return [countryObj, ...prev]; return [...prev, countryObj]; }, []), - [availableCountries] + [availableCountries], ); useEffect(() => { @@ -231,13 +263,67 @@ export function useTaxAndCash() { } if (!host || !user) return; + async function completeConnection(user: UserQuery["user"]) { + const publisher = user?.impactConnection?.publisher; + + const hasBillingInfo = + publisher.billingAddress && + publisher.billingCity && + publisher.billingCountryCode && + publisher.billingPostalCode && + publisher.phoneNumberCountryCode && + publisher.phoneNumber && + isValidI18nPhoneNumber( + publisher.phoneNumberCountryCode, + publisher.phoneNumber, + ); + + if ( + hasBillingInfo && + user?.impactConnection?.connectionStatus === "STARTED" + ) { + const vars = { + user: { + id: data?.user?.id, + accountId: data?.user?.accountId, + }, + firstName: data?.user?.firstName, + lastName: data?.user?.lastName, + countryCode: publisher.billingCountryCode, + currency: publisher.currency, + address: publisher.billingAddress, + city: publisher.billingCity, + state: publisher.billingState, + postalCode: publisher.billingPostalCode, + phoneNumber: publisher.phoneNumber, + phoneNumberCountryCode: publisher.phoneNumberCountryCode, + } as Partial; + await completeImpactPartner({ + vars, + }); + } + } + if (data) { const user = data?.user; if (!user || step !== "/loading") return; - const currentStep = getCurrentStep(user); - setStep(currentStep); + async function routeAfterMaybeCompleting() { + if ( + user?.impactConnection?.publisher && + user?.impactConnection.connectionStatus === "STARTED" + ) { + // Finish the early-created connection before routing so the next + // step sees the completed status + await completeConnection(user); + await refetch(); + } + const currentStep = getCurrentStep(user); + setStep(currentStep); + } + + routeAfterMaybeCompleting(); } }, [host, user, data?.user?.email, errors]); diff --git a/packages/mint-components/src/components/tax-and-cash/sqm-user-info-form/UserInfoFormView.stories.tsx b/packages/mint-components/src/components/tax-and-cash/sqm-user-info-form/UserInfoFormView.stories.tsx index 67fcb15ea2..af5b9cb1fc 100644 --- a/packages/mint-components/src/components/tax-and-cash/sqm-user-info-form/UserInfoFormView.stories.tsx +++ b/packages/mint-components/src/components/tax-and-cash/sqm-user-info-form/UserInfoFormView.stories.tsx @@ -16,6 +16,11 @@ const mockCountries = [ { countryCode: "DE", displayName: "Germany" }, { countryCode: "FR", displayName: "France" }, { countryCode: "ES", displayName: "Spain" }, + // Show handling of long country names + { + countryCode: "CD", + displayName: "Congo, The Democratic Republic of the Congo", + }, ]; const mockCurrencies = [ @@ -79,7 +84,6 @@ const defaultText = { region: "Region", postalCode: "Postal Code", currency: "Currency", - currencyHelpText: "Select your preferred currency for payouts", allowBankingCollection: "I agree to the {termsAndConditionsLink}", personalInformation: "Personal Information", continueButton: "Continue", @@ -116,6 +120,8 @@ const baseProps: UserInfoFormViewProps = { loading: false, isPartner: false, isUser: false, + isPartnerLegacy: false, + isUserLegacy: false, formState: { firstName: "", lastName: "", @@ -264,6 +270,7 @@ export const PartnerAccount = () => { states={{ ...baseProps.states, isPartner: true, + isPartnerLegacy: true, formState: { ...baseProps.states.formState, firstName: "Jane", @@ -287,6 +294,55 @@ export const PartnerAccount = () => { ); }; +// Early partner created via : connectionStatus is "STARTED", +// publisher has only countryCode + currency populated. Banner must NOT show; only +// country + currency disabled; firstName/lastName disabled via userData; everything +// else (phone, address, city, state, postalCode) stays editable. +export const EarlyPartnerFromModal = () => { + return ( + + ); +}; + export const UserAccount = () => { return ( { states={{ ...baseProps.states, isUser: true, + isUserLegacy: true, formState: { ...baseProps.states.formState, firstName: "Bob", @@ -319,6 +376,7 @@ export const UserAccountMissingLastName = () => { states={{ ...baseProps.states, isUser: true, + isUserLegacy: true, formState: { ...baseProps.states.formState, firstName: "Bob", diff --git a/packages/mint-components/src/components/tax-and-cash/sqm-user-info-form/readme.md b/packages/mint-components/src/components/tax-and-cash/sqm-user-info-form/readme.md index c9c8b69af4..48e355e998 100644 --- a/packages/mint-components/src/components/tax-and-cash/sqm-user-info-form/readme.md +++ b/packages/mint-components/src/components/tax-and-cash/sqm-user-info-form/readme.md @@ -15,7 +15,6 @@ | `continueButton` | `continue-button` | | `string` | `"Continue"` | | `country` | `country` | | `string` | `"Country"` | | `currency` | `currency` | | `string` | `"Currency"` | -| `currencyHelpText` | `currency-help-text` | | `string` | `"Choose your preferred payout currency"` | | `demoData` | -- | | `{ states?: { step: string; hideState: boolean; hideSteps: boolean; disabled: boolean; loadingError: boolean; loading: boolean; isPartner: boolean; isUser: boolean; formState: { errors: {}; firstName: string; lastName: string; email: string; phoneNumberCountryCode: string; phoneNumber: string; countryCode: string; currency: string; address: string; city: string; state: string; postalCode: string; }; }; refs?: { formRef: Ref; currencyRef: Ref; phoneCountryRef: Ref; }; data?: { currencies: { displayName: string; currencyCode: string; }[]; countries: TaxCountry[]; phoneCountries: TaxCountry[]; allCurrencies: { displayName: string; currencyCode: string; }[]; allCountries: TaxCountry[]; regionLabelEnum: "STATE" \| "PROVINCE" \| "REGION"; regions: { label: string; value: string; }[]; partnerData: ImpactPublisher; userData: ImpactUser; }; setStep?: (value: string) => void; onSubmit?: (event: any) => Promise; }` | `undefined` | | `email` | `email` | | `string` | `"Email"` | | `fieldInvalidError` | `field-invalid-error` | Displayed under a field when it has an invalid entry. | `string` | `"{fieldName} is invalid"` | diff --git a/packages/mint-components/src/components/tax-and-cash/sqm-user-info-form/small-views/IndirectTaxDetailsView.tsx b/packages/mint-components/src/components/tax-and-cash/sqm-user-info-form/small-views/IndirectTaxDetailsView.tsx index 5fca562096..6b3bf6695d 100644 --- a/packages/mint-components/src/components/tax-and-cash/sqm-user-info-form/small-views/IndirectTaxDetailsView.tsx +++ b/packages/mint-components/src/components/tax-and-cash/sqm-user-info-form/small-views/IndirectTaxDetailsView.tsx @@ -160,7 +160,7 @@ export const OtherRegionSlotView = (props: IndirectDetailsSlotViewProps) => { id: `tax-field-label${taxType}`, defaultMessage: text.indirectTaxNumber, }, - { taxType } + { taxType }, ); }; @@ -170,7 +170,7 @@ export const OtherRegionSlotView = (props: IndirectDetailsSlotViewProps) => { id: `tax-field-label${taxType}`, defaultMessage: text.error.indirectTaxNumber, }, - { taxType } + { taxType }, ); }; @@ -215,7 +215,7 @@ export const OtherRegionSlotView = (props: IndirectDetailsSlotViewProps) => { class: classes.ErrorInput, helpText: formatErrorMessage( text.subRegion, - text.error.fieldRequiredError + text.error.fieldRequiredError, ), })} id="subRegion" @@ -235,7 +235,7 @@ export const OtherRegionSlotView = (props: IndirectDetailsSlotViewProps) => { exportparts="label: input-label, base: input-base" checked={formState.hasSubRegionTaxNumber} onSl-change={callbacks.onSpainToggle} - disabled={states.disabled || states.isPartner} + disabled={states.disabled} > {text.isRegisteredSubRegionIncomeTax} @@ -251,7 +251,7 @@ export const OtherRegionSlotView = (props: IndirectDetailsSlotViewProps) => { class: classes.ErrorInput, helpText: formatErrorMessage( text.subRegionTaxNumberLabel, - text.error.fieldRequiredError + text.error.fieldRequiredError, ), })} id={"subRegionTaxNumber"} @@ -266,7 +266,7 @@ export const OtherRegionSlotView = (props: IndirectDetailsSlotViewProps) => { const { classes } = sheet; const currentTaxType = INDIRECT_TAX_PROVINCES?.find( - (p) => p.regionCode === formState.province + (p) => p.regionCode === formState.province, )?.taxType as TaxType | undefined; return ( @@ -283,7 +283,7 @@ export const OtherRegionSlotView = (props: IndirectDetailsSlotViewProps) => { class: classes.ErrorInput, helpText: formatErrorMessage( text.province, - text.error.fieldRequiredError + text.error.fieldRequiredError, ), })} id="province" @@ -314,7 +314,7 @@ export const OtherRegionSlotView = (props: IndirectDetailsSlotViewProps) => { exportparts="label: input-label, base: input-base" onSl-change={callbacks.onQstToggle} checked={formState.hasQst} - disabled={states.isPartner || states.disabled} + disabled={states.disabled} > {text.isRegisteredQST} @@ -324,7 +324,7 @@ export const OtherRegionSlotView = (props: IndirectDetailsSlotViewProps) => { label={text.qstNumber} error={formatErrorMessage( text.qstNumber, - text.error.fieldRequiredError + text.error.fieldRequiredError, )} /> )} @@ -352,7 +352,7 @@ export const OtherRegionSlotView = (props: IndirectDetailsSlotViewProps) => { {...(formState.errors?.indirectTaxNumber && { class: classes.ErrorInput, helpText: getTaxFieldError( - vatLabels[selectedRegion] || "GENERAL" + vatLabels[selectedRegion] || "GENERAL", ), })} id="indirectTaxNumber" @@ -385,7 +385,7 @@ export const OtherRegionSlotView = (props: IndirectDetailsSlotViewProps) => { class: classes.ErrorInput, helpText: formatErrorMessage( text.selectedRegion, - text.error.fieldRequiredError + text.error.fieldRequiredError, ), })} id="selectedRegion" diff --git a/packages/mint-components/src/components/tax-and-cash/sqm-user-info-form/sqm-user-info-form-view.tsx b/packages/mint-components/src/components/tax-and-cash/sqm-user-info-form/sqm-user-info-form-view.tsx index 07897c630f..f4a79340d6 100644 --- a/packages/mint-components/src/components/tax-and-cash/sqm-user-info-form/sqm-user-info-form-view.tsx +++ b/packages/mint-components/src/components/tax-and-cash/sqm-user-info-form/sqm-user-info-form-view.tsx @@ -1,10 +1,15 @@ import { h } from "@stencil/core"; +import { AsYouType, CountryCode } from "libphonenumber-js"; import { intl } from "../../../global/global"; import { createStyleSheet } from "../../../styling/JSS"; import { FORM_STEPS, ImpactPublisher, ImpactUser } from "../data"; import { PHONE_EXTENSIONS } from "../phoneExtensions"; import LoadingView from "../sqm-tax-and-cash/LoadingView"; -import { formatErrorMessage, validateBillingField } from "../utils"; +import { + formatErrorMessage, + isValidI18nPhoneNumber, + validateBillingField, +} from "../utils"; export interface UserInfoFormViewProps { states: { @@ -13,6 +18,8 @@ export interface UserInfoFormViewProps { disabled: boolean; isPartner: boolean; isUser: boolean; + isPartnerLegacy: boolean; + isUserLegacy: boolean; hideSteps: boolean; hideState: boolean; loadingError?: boolean; @@ -93,7 +100,6 @@ export interface UserInfoFormViewProps { region: string; postalCode: string; currency: string; - currencyHelpText: string; allowBankingCollection: string; personalInformation: string; continueButton: string; @@ -234,10 +240,6 @@ const vanillaStyle = ` flex-direction: column; } - sl-select#phoneNumberCountryCode::part(menu) { - min-width: 250px; - } - sl-button[type="primary"]::part(base){ background-color: var(--sqm-primary-button-background); color: var(--sqm-primary-button-color); @@ -331,6 +333,7 @@ const vanillaStyle = ` background: var(--sqm-input-background, inherit); color: var(--sqm-input-color, inherit); border:none; + width: max-content; } sl-menu-item::part(base) { @@ -413,7 +416,7 @@ export const UserInfoFormView = (props: UserInfoFormViewProps) => { {text.termsAndConditionsLabel} ), - } + }, ); let regionLabel = undefined; @@ -431,7 +434,25 @@ export const UserInfoFormView = (props: UserInfoFormViewProps) => { regionLabel = text.state; } + // when creating an impact connection without sending phoneNumber data, the impactAPI defaults the value to "0000000" and the phoneNumberCountryCode to "DZ" function isDisabledPartnerInput(field: string) { + if ( + data.partnerData?.phoneNumber === "0000000" && + (field === "phoneNumber" || field === "phoneNumberCountryCode") + ) { + return false; + } + // if bad phone number was previously saved, unlock the field so user can correct it + if ( + (field === "phoneNumber" || field === "phoneNumberCountryCode") && + states.isPartner && + !isValidI18nPhoneNumber( + data.partnerData?.phoneNumberCountryCode, + data.partnerData?.phoneNumber, + ) + ) { + return false; + } return states.isPartner && !!data.partnerData?.[field]; } @@ -469,7 +490,7 @@ export const UserInfoFormView = (props: UserInfoFormViewProps) => { {text.supportLink} ), - } + }, )}

@@ -489,7 +510,7 @@ export const UserInfoFormView = (props: UserInfoFormViewProps) => { id: "formStep", defaultMessage: text.formStep, }, - { step: states.step, count: FORM_STEPS } + { step: states.step, count: FORM_STEPS }, )}

)} @@ -518,13 +539,12 @@ export const UserInfoFormView = (props: UserInfoFormViewProps) => { {text.supportLink} ), - } + }, )}

)} - - {(states.isPartner || states.isUser) && ( + {(states.isPartnerLegacy || states.isUserLegacy) && (

{text.isPartnerAlertHeader}

@@ -542,7 +562,7 @@ export const UserInfoFormView = (props: UserInfoFormViewProps) => { {text.supportLink} ), - } + }, )}

@@ -561,7 +581,7 @@ export const UserInfoFormView = (props: UserInfoFormViewProps) => { class: classes.ErrorInput, helpText: formatErrorMessage( text.firstName, - formState.errors.firstName + formState.errors.firstName, ), } : {})} @@ -579,7 +599,7 @@ export const UserInfoFormView = (props: UserInfoFormViewProps) => { class: classes.ErrorInput, helpText: formatErrorMessage( text.lastName, - formState.errors.lastName + formState.errors.lastName, ), } : {})} @@ -611,7 +631,7 @@ export const UserInfoFormView = (props: UserInfoFormViewProps) => { class: classes.ErrorInput, helpText: formatErrorMessage( text.country, - formState.errors.countryCode + formState.errors.countryCode, ), } : {})} @@ -704,14 +724,30 @@ export const UserInfoFormView = (props: UserInfoFormViewProps) => { exportparts="label: input-label, base: input-base" id="phoneNumber" name="/phoneNumber" - value={formState.phoneNumber} - validationError={({ value }) => { - // Naive phone number validation - validateBillingField(/[a-zA-Z]+/, value) && - formatErrorMessage( - text.phoneNumber, - text.error.fieldInvalidError - ); + value={ + formState.phoneNumber !== null + ? formState.phoneNumber + : "" + } + onSl-input={(e) => { + const target = e.target as HTMLInputElement; + const country = ((refs.phoneCountryRef.current + ?.value as string) || "US") as CountryCode; + target.value = new AsYouType(country).input(target.value); + }} + validationError={({ control, value, formData }) => { + // skip validation for values the user can't edit + if (control?.disabled) return undefined; + if (!value?.trim()) return undefined; + return isValidI18nPhoneNumber( + formData.phoneNumberCountryCode, + value, + ) + ? undefined + : formatErrorMessage( + text.phoneNumber, + text.error.fieldInvalidError, + ); }} disabled={ states.disabled || isDisabledPartnerInput("phoneNumber") @@ -721,7 +757,7 @@ export const UserInfoFormView = (props: UserInfoFormViewProps) => { class: classes.ErrorInput, helpText: formatErrorMessage( text.phoneNumber, - formState.errors.phoneNumber + formState.errors.phoneNumber, ), } : {})} @@ -740,7 +776,7 @@ export const UserInfoFormView = (props: UserInfoFormViewProps) => { !validateBillingField(/^[\x20-\xFF]+$/, value) && formatErrorMessage( text.address, - text.error.invalidCharacterError + text.error.invalidCharacterError, ) } disabled={ @@ -751,7 +787,7 @@ export const UserInfoFormView = (props: UserInfoFormViewProps) => { class: classes.ErrorInput, helpText: formatErrorMessage( text.address, - formState.errors.address + formState.errors.address, ), } : {})} @@ -768,7 +804,7 @@ export const UserInfoFormView = (props: UserInfoFormViewProps) => { !validateBillingField(/^[\x20-\xFF]+$/, value) && formatErrorMessage( text.city, - text.error.invalidCharacterError + text.error.invalidCharacterError, ) } disabled={ @@ -779,7 +815,7 @@ export const UserInfoFormView = (props: UserInfoFormViewProps) => { class: classes.ErrorInput, helpText: formatErrorMessage( text.city, - formState.errors.city + formState.errors.city, ), } : {})} @@ -800,7 +836,7 @@ export const UserInfoFormView = (props: UserInfoFormViewProps) => { class: classes.ErrorInput, helpText: formatErrorMessage( text.state, - formState.errors.state + formState.errors.state, ), } : {})} @@ -825,7 +861,7 @@ export const UserInfoFormView = (props: UserInfoFormViewProps) => { class: classes.ErrorInput, helpText: formatErrorMessage( text.postalCode, - formState.errors.postalCode + formState.errors.postalCode, ), } : {})} @@ -839,13 +875,12 @@ export const UserInfoFormView = (props: UserInfoFormViewProps) => { menu value={formState.currency} disabled={states.disabled || isDisabledPartnerInput("currency")} - helpText={text.currencyHelpText} {...(formState.errors?.currency ? { class: classes.ErrorInput, helpText: formatErrorMessage( text.currency, - formState.errors.currency + formState.errors.currency, ), } : {})} @@ -877,30 +912,6 @@ export const UserInfoFormView = (props: UserInfoFormViewProps) => { ))} - -
- { - e.target.value = e.target.checked; - }} - disabled={states.disabled} - required - value={formState.allowBankingCollection} - id="allowBankingCollection" - name="/allowBankingCollection" - > - {bankingCollectionText} - - {formState.errors?.allowBankingCollection && ( -

- {formatErrorMessage( - text.termsAndConditionsLabel, - formState.errors.allowBankingCollection - )} -

- )} -
And is input type And they see a "Continue" button + Examples: | label | inputType | | First name | text | @@ -114,7 +115,6 @@ Feature: Tax Form Step One | CAN | USD, AUD, EUR, GBP, CAN | | IND | USD, AUD, EUR, GBP, INR | - @minutia Scenario: Currency select is searchable When they press the Currency select @@ -201,19 +201,20 @@ Feature: Tax Form Step One {fieldName} contains invalid characters. """ # Note: SPACE and NUL mean the characters. Both are for documentation purposes. + Examples: | fieldName | string | may | | Address | SPACE | will not | | Address | abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ | will not | - | Address | 0123456789 | will not | - | Address | !"#$%&'()*+'-,/:;<=>?@[\]^_`~ | will not | + | Address | 0123456789 | will not | + | Address | !"#$%&'()*+'-,/:;<=>?@[\\]^_`~ | will not | | Address | æùíöêø | will not | | Address | ぁ ㍿ ・ | will | | Address | NUL | will | | City | SPACE | will not | | City | abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ | will not | - | City | 0123456789 | will not | - | City | !"#$%&'()*+'-,/:;<=>?@[\]^_`~ | will not | + | City | 0123456789 | will not | + | City | !"#$%&'()*+'-,/:;<=>?@[\\]^_`~ | will not | | City | æùíöêø | will not | | City | ぁ ㍿ ・ | will | | City | NUL | will | @@ -227,11 +228,12 @@ Feature: Tax Form Step One """ Phone number is invalid. """ + Examples: | string | may | | abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ | will | - | 0123456789 | will not | - | !"#$%&'()*+'-,/:;<=>?@[\]^_`~ | will not | + | 0123456789 | will not | + | !"#$%&'()*+'-,/:;<=>?@[\\]^_`~ | will not | @minutia Scenario Outline: "State" field changes based on country selected @@ -239,6 +241,7 @@ Feature: Tax Form Step One When is selected via the dropdown Then the "State" select menu updates to the valid states of that country And the "State" field's label changes to